Skip to content

Instantly share code, notes, and snippets.

@jmcd
Created March 22, 2019 14:54
Show Gist options
  • Save jmcd/e7a57e989227c6b23c30cb544866d16f to your computer and use it in GitHub Desktop.
Save jmcd/e7a57e989227c6b23c30cb544866d16f to your computer and use it in GitHub Desktop.
Shunting Yard
public struct Calculator {
public static func execute(_ input: String) throws -> Double {
let tokens = try Token.tokenize(input)
let rpn = try ShuntingYard.toReversePolishNotation(infix: tokens)
return try ReversePolishNotation.execute(rpn: rpn)
}
}
import Foundation
public enum Associativity {
case left, right
}
public struct Operator: Equatable, CustomStringConvertible {
public var description: String { return body }
let body: String
let precedence: Int
let associativity: Associativity
let impl: ((Double, Double) -> Double)
public static let nop = Operator(body: "@", precedence: 0, associativity: .left, impl: { (lhs, rhs) in 0.0 })
static let all: [Operator] = [
Operator(body: "^", precedence: 4, associativity: .right, impl: pow),
Operator(body: "*", precedence: 3, associativity: .left, impl: *),
Operator(body: "/", precedence: 3, associativity: .left, impl: /),
Operator(body: "+", precedence: 2, associativity: .left, impl: +),
Operator(body: "-", precedence: 2, associativity: .left, impl: -),
]
public static let byBody: [String: Operator] = Dictionary(uniqueKeysWithValues: Operator.all.map { (key: $0.body, value: $0) })
public func apply(lhs: Double, rhs: Double) -> Double {
return impl(lhs, rhs)
}
}
public func == (lhs: Operator, rhs: Operator) -> Bool { return rhs.body == lhs.body }
public enum ReversePolishNotationErrors: Error {
case unexpectedEndOfInput
}
public struct ReversePolishNotation {
public static func execute(rpn tokens: [Token]) throws -> Double {
let outstack = try tokens.reduce([Double]()) { (stack, token) in
var mutableStack = stack
switch token {
case let .operator(opr):
guard let op2 = mutableStack.popLast(), let op1 = mutableStack.popLast() else {
throw ReversePolishNotationErrors.unexpectedEndOfInput
}
let result = opr.apply(lhs: op1, rhs: op2)
mutableStack.append(result)
case let .number(d):
mutableStack.append(d)
default:
break
}
return mutableStack
}
assert(outstack.count == 1)
return outstack[0]
}
}
import Foundation
public enum InfixError: Error {
case mismatchedParentheses
}
public struct ShuntingYard {
private static func topIsNotLeftParam(_ operatorStack: [Token]) -> Bool {
if let top = operatorStack.last, top != Token.leftParen {
return true
}
return false
}
private static func topTakesPrecedence(_ operatorStack: [Token], over value: Operator) -> Bool {
if let top = operatorStack.last {
if case let Token.operator(topOperator) = top, (topOperator.precedence > value.precedence
|| (topOperator.precedence == value.precedence && topOperator.associativity == .left))
&& top != Token.leftParen {
return true
}
}
return false
}
public static func toReversePolishNotation(infix tokens: [Token]) throws -> [Token] {
var outputQueue = [Token]()
var operatorStack = [Token]()
for token in tokens {
switch token {
case .number:
outputQueue.append(token)
case let .operator(value):
while topTakesPrecedence(operatorStack, over: value), let popped = operatorStack.popLast() {
outputQueue.append(popped)
}
operatorStack.append(token)
case .leftParen:
operatorStack.append(token)
case .rightParen:
while topIsNotLeftParam(operatorStack), let top = operatorStack.popLast() {
outputQueue.append(top)
}
if operatorStack.isEmpty {
throw InfixError.mismatchedParentheses
}
if let top = operatorStack.last, top == Token.leftParen {
operatorStack.removeLast()
}
}
}
while let operatorToken = operatorStack.popLast() {
if operatorToken == Token.leftParen || operatorToken == Token.rightParen {
throw InfixError.mismatchedParentheses
}
outputQueue.append(operatorToken)
}
return outputQueue
}
}
import Foundation
public enum TokenError: Error {
case unexpectedInput(string: String)
}
public enum Token: Equatable, CustomStringConvertible {
public var description: String {
switch self {
case let .number(value): return String(describing: value)
case let .operator(value): return String(describing: value)
case .leftParen: return "("
case .rightParen: return ")"
}
}
case number(value: Double)
case `operator`(value: Operator)
case leftParen, rightParen
public static func tokenize(_ input: String) throws -> [Token] {
let scanner = Scanner(string: input)
var tks = [Token]()
while !scanner.isAtEnd {
var op: Operator = Operator.nop
var d = 0.0
if scanner.scanOperator(&op) {
tks.append(.operator(value: op))
} else if scanner.scanString("(", into: nil) {
tks.append(.leftParen)
} else if scanner.scanString(")", into: nil) {
tks.append(.rightParen)
} else if scanner.scanDouble(&d) {
tks.append(.number(value: d))
} else {
var s: NSString? = ""
scanner.scanCharacters(from: CharacterSet().inverted, into: &s)
let remainder: String = (s as String?) ?? ""
throw TokenError.unexpectedInput(string: remainder)
}
}
return tks
}
}
extension Scanner {
func scanOperator(_ o: UnsafeMutablePointer<Operator>?) -> Bool {
for operatorBody in Operator.byBody.keys {
if scanString(operatorBody, into: nil) {
if var `operator` = Operator.byBody[operatorBody] {
o?.assign(from: &`operator`, count: 1)
return true
}
}
}
return false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment