Skip to content

Instantly share code, notes, and snippets.

@Danappelxx
Last active May 13, 2016 09:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Danappelxx/c8b75352181ef627ebb6 to your computer and use it in GitHub Desktop.
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.
// 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