Last active
May 13, 2016 09:28
-
-
Save Danappelxx/c8b75352181ef627ebb6 to your computer and use it in GitHub Desktop.
A simple expression interpreter which parses tokens, arranges them into Hungarian Notation, and finally evaluates them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// For evaluation purposes | |
indirect enum Token: CustomStringConvertible { | |
case Value(Int) | |
case Add(Token, Token) | |
case Subtract(Token, Token) | |
case Multiply(Token, Token) | |
case Divide(Token, Token) | |
var description: String { | |
switch self { | |
case let .Value(val): return String(val) | |
case let .Add(t1, t2): return "(" + t1.description + " + " + t2.description + ")" | |
case let .Subtract(t1, t2): return "(" + t1.description + " - " + t2.description + ")" | |
case let .Multiply(t1, t2): return "(" + t1.description + " * " + t2.description + ")" | |
case let .Divide(t1, t2): return "(" + t1.description + " / " + t2.description + ")" | |
} | |
} | |
} | |
// For parsing purposes | |
enum InputToken { | |
// (namespaced because of collisions) | |
case IToken(Token) | |
case IOperation(Operation) | |
case INumber(Int) | |
var token: Token? { | |
switch self { | |
case .INumber(let val): return .Value(val) | |
case .IOperation(_): return nil | |
case .IToken(let token): return token | |
} | |
} | |
} | |
// For parsing purposes | |
enum Operation: Character { | |
case Add = "+" | |
case Subtract = "-" | |
case Multiply = "*" | |
case Divide = "/" | |
func combineTokens(t1 t1: Token, t2: Token) -> Token { | |
switch self { | |
case .Add: return .Add(t1, t2) | |
case .Subtract: return .Subtract(t1, t2) | |
case .Multiply: return .Multiply(t1, t2) | |
case .Divide: return .Divide(t1, t2) | |
} | |
} | |
} | |
// lex a string into tokens | |
func lex(input: String) -> [InputToken] { | |
var tokens = [InputToken]() | |
for char in input.characters { | |
switch char { | |
case "+", "-", "*", "/": tokens.append(.IOperation(Operation(rawValue: char)!)) | |
case "0"..."9": tokens.append(.INumber(Int(String(char))!)) | |
default: fatalError("tokenizing syntax error") | |
} | |
} | |
guard tokens.count > 0 else { return tokens } | |
// combine number tokens into one token (ie 3 + 55 -> 3, +, 5, 5 -> 3, +, 55) | |
tokens = tokens.reduce([InputToken]()) { total, curr in | |
guard let last = total.last else { return [curr] } | |
switch (last, curr) { | |
case let (.INumber(num1), .INumber(num2)): // combine last two numbers into one number (concat digits) | |
let untilLast = Array(total[0..<total.count-1]) | |
// lol | |
let combined = Int(String(num1) + String(num2))! | |
return untilLast + [.INumber(combined)] | |
default: return total + [curr] | |
} | |
} | |
return tokens | |
} | |
// combine input tokens into an actual token | |
func parse(input: [InputToken]) -> Token { | |
var input = input | |
let orderOfOperations: [Operation] = [.Multiply, .Divide, .Add, .Subtract] | |
for currOperation in orderOfOperations { | |
var index = input.startIndex | |
while index < input.endIndex { | |
let curr = input[index] | |
switch curr { | |
case .IOperation(let operation) where operation == currOperation: | |
let prev = input[index.predecessor()] | |
let next = input[index.successor()] | |
guard let prevToken = prev.token, nextToken = next.token else { | |
fatalError("syntax parsing error - two operations in a row") | |
} | |
let combined = operation.combineTokens(t1: prevToken, t2: nextToken) | |
// replace previous with this token | |
input[index.predecessor()] = .IToken(combined) | |
// remove next token | |
input.removeAtIndex(index.successor()) | |
// remove current token | |
input.removeAtIndex(index) | |
case .IOperation(_): break // operation that we dont care about yet - ignore it | |
case .IToken(_): break // do nothing | |
case .INumber(_): break // do nothing | |
} | |
index = index.successor() | |
} | |
} | |
// ensure that there is only one token and that it is actually a token (not a number, for example) | |
guard case let InputToken.IToken(combinedToken) = input[0] where input.count == 1 else { fatalError("parsing tokens failed") } | |
return combinedToken | |
} | |
// recursively evaluate the token | |
func evaluate(token: Token) -> Int { | |
switch token { | |
case let .Value(value): return value | |
case let .Add(t1, t2): return evaluate(t1) + evaluate(t2) | |
case let .Subtract(t1, t2): return evaluate(t1) - evaluate(t2) | |
case let .Multiply(t1, t2): return evaluate(t1) * evaluate(t2) | |
case let .Divide(t1, t2): return evaluate(t1) / evaluate(t2) | |
} | |
} | |
let input = "5+3*4-10" // -> ((5 + (3 * 4)) - 10) -> 7 | |
let tokens = lex(input) | |
let parsed = parse(tokens) | |
let result = evaluate(parsed) // -> 7 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment