Last active
November 26, 2020 22:31
-
-
Save praeclarum/446700356b7ad69a4fcc to your computer and use it in GitHub Desktop.
A little scripting language written in Swift. It supports first class functions (closures) and variable mutation. This code includes the AST, the interpreter, and a JavaScript parser.
This file contains 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
// | |
// Script.swift | |
// | |
// Created by Frank A. Krueger on 6/28/15. | |
// Copyright © 2015 Krueger Systems, Inc. All rights reserved. | |
// | |
import Foundation | |
enum Val { | |
case UndefinedVal | |
case StringVal(String) | |
case NumberVal(Double) | |
case FuncVal(Funcish) | |
} | |
/// HACK: Recursive enums not yet supported 7/6/2015 | |
protocol Funcish { | |
func apply(withArguments args: [Val], inEnv env: Env) -> Val | |
} | |
enum Func: Funcish { | |
case ExprFunc(environment: Env, parameters: [String], value: Exprish) | |
case InternalFunc([Val] -> Val) | |
} | |
/// HACK: Recursive enums not yet supported 7/1/2015 | |
protocol Exprish { | |
func eval(inEnv env: Env) -> Val | |
} | |
enum Expr: Exprish { | |
case UndefinedExpr | |
case StringExpr(String) | |
case NumberExpr(Double) | |
case VariableExpr(String) | |
case FuncExpr([String], Exprish) | |
case ApplyExpr(Exprish, [Exprish]) | |
case AssignExpr(String, Exprish) | |
} | |
extension Func { | |
func apply(withArguments args: [Val], inEnv env: Env) -> Val { | |
switch self { | |
case .ExprFunc(environment: let fenv, parameters: let ps, value: let v): | |
var namedArgs: [String: Val] = [:] | |
for (p,a) in zip(ps, args) { namedArgs[p] = a } | |
return v.eval(inEnv: Env(values: namedArgs, withParent: fenv)) | |
case .InternalFunc(let ff): | |
return ff(args) | |
} | |
} | |
} | |
extension Expr { | |
func eval(inEnv env: Env) -> Val { | |
switch self { | |
case .UndefinedExpr: return .UndefinedVal | |
case .StringExpr(let v): return .StringVal(v) | |
case .NumberExpr(let v): return .NumberVal(v) | |
case .FuncExpr(let ps, let ve): | |
return .FuncVal(Func.ExprFunc(environment: env, parameters: ps, value: ve as! Expr)) | |
case .ApplyExpr(let fe, let argse): | |
let args = argse.map { $0.eval(inEnv: env) } | |
if case .FuncVal(let f) = fe.eval(inEnv: env) { | |
return f.apply(withArguments: args, inEnv: env) | |
} | |
else { | |
return .UndefinedVal | |
} | |
case .VariableExpr(let n): return env[n] | |
case .AssignExpr(let n, let ve): | |
let v = ve.eval(inEnv: env) | |
env[n] = v | |
return v | |
} | |
} | |
} | |
class Env { | |
private var vals: [String: Val] = [:] | |
private var parent: Env? | |
init(values: [String: Val], withParent: Env?) { | |
vals = values | |
parent = withParent | |
} | |
static var empty: Env { return Env(values: [:], withParent: nil) } | |
private func trySet(name: String, val: Val) -> Bool { | |
if let _ = vals[name] { | |
vals[name] = val | |
return true | |
} | |
if let p = parent { | |
return p.trySet(name, val: val) | |
} | |
return false | |
} | |
subscript(name: String) -> Val { | |
get { | |
if let v = vals[name] { | |
return v | |
} | |
if let p = parent { | |
return p[name] | |
} | |
return .UndefinedVal | |
} | |
set { | |
// Try to overwrite an existing value | |
if !trySet(name, val: newValue) { | |
// Add the value if there was nothing to overwrite | |
vals[name] = newValue | |
} | |
} | |
} | |
static var stdlib: Env { | |
func n2(op: (Double, Double) -> Double) -> Val { | |
return .FuncVal(Func.InternalFunc { args in | |
if args.count != 2 { return .UndefinedVal } | |
if case let .NumberVal(x) = args[0], | |
case let .NumberVal(y) = args[1] { | |
return .NumberVal(op(x,y)) | |
} | |
return .UndefinedVal | |
}) | |
} | |
let values: [String: Val] = [ | |
"+": n2 { (x, y) in x + y }, | |
"-": n2 { (x, y) in x - y }, | |
"*": n2 { (x, y) in x * y }, | |
"/": n2 { (x, y) in x / y } | |
] | |
return Env(values: values, withParent: nil) | |
} | |
} | |
/// JavaScript parser | |
extension Expr { | |
enum JSToken { | |
case NumberToken(Double) | |
case OpToken(String) | |
case IdentifierToken(String) | |
} | |
static func tokenize(javaScript javaScript: String) -> [JSToken] { | |
let s = javaScript.unicodeScalars | |
let e = s.endIndex | |
var p = s.startIndex | |
var r: [JSToken] = [] | |
func isdigit(i: String.UnicodeScalarIndex) -> Bool { return iswdigit(wint_t(s[i].value)) != 0 } | |
while p < e { | |
while p < e && iswspace(wint_t(s[p].value)) != 0 { p++ } | |
if p >= e { break } | |
if isdigit(p) { | |
var pe = p.successor() | |
while pe < e && isdigit(pe) { pe++ } | |
let n = (String(s[p..<pe]) as NSString).doubleValue | |
r.append(JSToken.NumberToken(n)) | |
p = pe | |
} | |
else { | |
let n = String(s[p...p]) | |
r.append(JSToken.OpToken(n)) | |
p++ | |
} | |
} | |
return r | |
} | |
init(javaScript: String) { | |
typealias ParseResult = (Expr, Int)? | |
typealias Parser = Int -> ParseResult | |
let toks = Expr.tokenize(javaScript: javaScript) | |
func pop(op: String) -> Parser { | |
return { i in | |
if i >= toks.count { return nil } | |
if case let .OpToken(v) = toks[i] where v == op { | |
return (.VariableExpr(v), i + 1) | |
} | |
return nil | |
} | |
} | |
func por(ps: [Parser]) -> Parser { | |
return { i in | |
if i >= toks.count { return nil } | |
for p in ps { | |
if let pr = p(i) { | |
return pr | |
} | |
} | |
return nil | |
} | |
} | |
func pseq(ps: [Parser], combine: [Expr] -> Expr) -> Parser { | |
return { ii in | |
if ii >= toks.count { return nil } | |
var es: [Expr] = [] | |
var i = ii | |
for p in ps { | |
if let (ne, ni) = p(i) { | |
es.append(ne) | |
i = ni | |
} | |
else { | |
return nil | |
} | |
} | |
return (combine(es), i) | |
} | |
} | |
func ptree(p: Parser, _ ops: [String]) -> Parser { | |
let pops = por(ops.map { pop($0) }) | |
return { i in | |
if i >= toks.count { return nil } | |
if let r0 = p(i) { | |
var rx = r0 | |
while rx.1 < toks.count - 1 { | |
if let rop = pops(rx.1) where rop.1 < toks.count { | |
if let ry = p(rop.1) { | |
rx = (Expr.ApplyExpr(rop.0, [rx.0, ry.0]), ry.1) | |
} | |
else { | |
break | |
} | |
} | |
else { | |
break | |
} | |
} | |
return rx | |
} | |
else { | |
return nil | |
} | |
} | |
} | |
func num(i: Int) -> ParseResult { | |
if i >= toks.count { return nil } | |
if case let .NumberToken(v) = toks[i] { | |
return (.NumberExpr(v), i + 1) | |
} | |
return nil | |
} | |
func ident(i: Int) -> ParseResult { | |
if i >= toks.count { return nil } | |
if case let .IdentifierToken(v) = toks[i] { | |
return (.VariableExpr(v), i + 1) | |
} | |
return nil | |
} | |
func primary(i: Int) -> ParseResult { | |
return por([ | |
pseq([pop("("), expr, pop(")")]) { args in args[1] }, | |
ident, | |
num])(i) | |
} | |
func member(i: Int) -> ParseResult { | |
return primary(i) | |
} | |
func unary(i: Int) -> ParseResult { | |
return por([ | |
pseq([por([pop("+"),pop("-")]), unary]) { args in args[1] }, | |
member])(i) | |
} | |
func mult(i: Int) -> ParseResult { | |
return ptree(unary, ["*", "/"])(i) | |
} | |
func add(i: Int) -> ParseResult { | |
return ptree(mult, ["+", "-"])(i) | |
} | |
func shift(i: Int) -> ParseResult { | |
return ptree(add, ["<<", ">>"])(i) | |
} | |
func relational(i: Int) -> ParseResult { | |
return ptree(shift, ["<", ">=", ">", ">="])(i) | |
} | |
func equality(i: Int) -> ParseResult { | |
return ptree(relational, ["==", "!="])(i) | |
} | |
func and(i: Int) -> ParseResult { | |
return ptree(equality, ["&&"])(i) | |
} | |
func or(i: Int) -> ParseResult { | |
return ptree(and, ["||"])(i) | |
} | |
func conditional(i: Int) -> ParseResult { | |
return por([ | |
pseq([or, pop("?"), assign, pop(":"), assign]) { args in args[0] }, | |
or])(i) | |
} | |
func assign(i: Int) -> ParseResult { | |
let ppp = pseq([ident, pop("="), assign]) { args in return Expr.AssignExpr("\(args[0])", args[2]) } | |
return por([ | |
ppp, | |
conditional])(i) | |
} | |
func expr(i: Int) -> ParseResult { | |
if let p = assign(i) { | |
return p | |
} | |
return nil | |
} | |
if let (e, _) = expr(0) { | |
self = e | |
} | |
else { | |
self = Expr.UndefinedExpr | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment