Skip to content

Instantly share code, notes, and snippets.

@praeclarum
Last active November 26, 2020 22:31
Show Gist options
  • Save praeclarum/446700356b7ad69a4fcc to your computer and use it in GitHub Desktop.
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.
//
// 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