1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-21 02:27:10 +03:00

Merge pull request #344 from LispLY/add-swift4

Add swift4 implement
This commit is contained in:
Joel Martin 2019-03-06 12:33:49 -06:00 committed by GitHub
commit 23b7e1085b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 2082 additions and 2 deletions

View File

@ -86,6 +86,8 @@ matrix:
- {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7.3}
- {env: IMPL=swift3, services: [docker]}
- {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8}
- {env: IMPL=swift4, services: [docker]}
- {env: IMPL=swift4 NO_DOCKER=1, os: osx, osx_image: xcode10}
- {env: IMPL=tcl, services: [docker]}
- {env: IMPL=ts, services: [docker]}
- {env: IMPL=vb, services: [docker]}

View File

@ -86,7 +86,7 @@ IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d d
guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \
matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp plpgsql \
plsql powershell ps python r racket rexx rpython ruby rust scala scheme skew \
swift swift3 tcl ts vb vhdl vimscript wasm yorick
swift swift3 swift4 tcl ts vb vhdl vimscript wasm yorick
EXTENSION = .mal
@ -239,6 +239,7 @@ scheme_STEP_TO_PROG = $(scheme_STEP_TO_PROG_$(scheme_MODE))
skew_STEP_TO_PROG = skew/$($(1)).js
swift_STEP_TO_PROG = swift/$($(1))
swift3_STEP_TO_PROG = swift3/$($(1))
swift4_STEP_TO_PROG = swift4/$($(1))
tcl_STEP_TO_PROG = tcl/$($(1)).tcl
ts_STEP_TO_PROG = ts/$($(1)).js
vb_STEP_TO_PROG = vb/$($(1)).exe

View File

@ -6,7 +6,7 @@
**1. Mal is a Clojure inspired Lisp interpreter**
**2. Mal is implemented in 74 languages**
**2. Mal is implemented in 75 languages**
| Language | Creator |
| -------- | ------- |
@ -77,6 +77,7 @@
| [Skew](#skew) | [Dov Murik](https://github.com/dubek) |
| [Swift](#swift) | [Keith Rollin](https://github.com/keith-rollin) |
| [Swift 3](#swift-3) | [Joel Martin](https://github.com/kanaka) |
| [Swift 4](#swift-4) | [陆遥](https://github.com/LispLY) |
| [Tcl](#tcl-86) | [Dov Murik](https://github.com/dubek) |
| [TypeScript](#typescript) | [Masahiro Wakame](https://github.com/vvakame) |
| [VHDL](#vhdl) | [Dov Murik](https://github.com/dubek) |
@ -930,6 +931,17 @@ make
./stepX_YYY
```
### Swift 4
The Swift 4 implementation of mal requires the Swift 4.0 compiler. It
has been tested with Swift 4.2.3 release.
```
cd swift4
make
./stepX_YYY
```
### Tcl 8.6
The Tcl implementation of mal requires Tcl 8.6 to run. For readline line

44
swift4/Dockerfile Normal file
View File

@ -0,0 +1,44 @@
FROM ubuntu:xenial
MAINTAINER Joel Martin <github@martintribe.org>
##########################################################
# General requirements for testing or common across many
# implementations
##########################################################
RUN apt-get -y update
# Required for running tests
RUN apt-get -y install make python
# Some typical implementation and test requirements
RUN apt-get -y install curl libreadline-dev libedit-dev
RUN mkdir -p /mal
WORKDIR /mal
##########################################################
# Specific implementation requirements
##########################################################
# Swift
RUN apt-get -y install clang-3.6 cmake pkg-config \
git ninja-build uuid-dev libicu-dev icu-devtools \
libbsd-dev libedit-dev libxml2-dev libsqlite3-dev \
swig libpython-dev libncurses5-dev
# TODO: better way to do this?
RUN ln -sf /usr/lib/llvm-3.6/bin/clang++ /usr/bin/clang++
RUN ln -sf /usr/lib/llvm-3.6/bin/clang /usr/bin/clang
ENV SWIFT_PREFIX swift-4.2.3-RELEASE
ENV SWIFT_RELEASE ${SWIFT_PREFIX}-ubuntu16.04
RUN cd /opt && \
curl -O https://swift.org/builds/swift-4.2.3-release/ubuntu1604/${SWIFT_PREFIX}/${SWIFT_RELEASE}.tar.gz && \
tar xvzf ${SWIFT_RELEASE}.tar.gz && \
rm ${SWIFT_RELEASE}.tar.gz
ENV PATH /opt/${SWIFT_RELEASE}/usr/bin/:$PATH

40
swift4/Makefile Normal file
View File

@ -0,0 +1,40 @@
ifneq ($(shell which xcrun),)
SWIFT = xcrun -sdk macosx swiftc
else
SWIFT = swiftc
endif
STEP3_DEPS = Sources/types.swift Sources/reader.swift Sources/printer.swift Sources/env.swift
STEP4_DEPS = $(STEP3_DEPS) Sources/core.swift
SOURCES = $(STEP4_DEPS) Sources/stepA_mal/main.swift
SOURCES_LISP = Sources/env.swift Sources/core.swift Sources/stepA_mal/main.swift
STEPS = step0_repl step1_read_print step2_eval step3_env \
step4_if_fn_do step5_tco step6_file step7_quote \
step8_macros step9_try stepA_mal
all: $(STEPS)
dist: mal
mal: stepA_mal
cp $< $@
step1_read_print step2_eval step3_env: $(STEP3_DEPS)
step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal: $(STEP4_DEPS)
step%: Sources/step%/main.swift
$(SWIFT) $+ -o $@
clean:
rm -f $(STEPS) mal
.PHONY: stats tests
stats: $(SOURCES)
@wc $^
@printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
stats-lisp: $(SOURCES_LISP)
@wc $^
@printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"

209
swift4/Sources/core.swift Normal file
View File

@ -0,0 +1,209 @@
import Foundation
func calculate(_ args: [MalData], op: (Number, Number) -> Number) throws -> MalData {
guard args.count == 2, args[0] is Number, args[1] is Number else {
throw MalError.InvalidArgument
}
return op(args[0] as! Number, args[1] as! Number)
}
func isEqualList(_ l: [MalData], _ r: [MalData]) -> Bool {
guard l.count == r.count else {
return false
}
for i in l.indices {
if !isEqual(l[i], r[i]) { return false }
}
return true
}
func isEqualHashMap (_ l: [String: MalData], _ r: [String: MalData]) -> Bool {
guard l.count == r.count else {
return false
}
for key in l.keys {
guard let lValue = l[key], let rValue = r[key] else { return false }
if !isEqual(lValue, rValue) { return false }
}
return true
}
func isEqual(_ l: MalData, _ r: MalData) -> Bool {
switch (l.dataType, r.dataType) {
case (.Symbol, .Symbol):
return (l as! Symbol).name == (r as! Symbol).name
case (.String, .String), (.Keyword, .Keyword):
return (l as! String) == (r as! String)
case (.Number, .Number):
return (l as! Number) == (r as! Number)
case (.List, .List), (.Vector, .Vector), (.List, .Vector), (. Vector, .List):
return isEqualList(l.listForm, r.listForm)
case (.HashMap, .HashMap):
return isEqualHashMap((l as! [String: MalData]), (r as! [String: MalData]))
case (.Nil, .Nil), (.True, .True), (.False, .False):
return true
default: // atom, function
return false
}
}
func hashMap(fromList list: [MalData]) throws -> [String: MalData] {
var hashMap: [String: MalData] = [:]
for index in stride(from: 0, to: list.count, by: 2) {
guard list[index] is String, index+1 < list.count else { throw MalError.Error }
hashMap.updateValue(list[index+1], forKey: list[index] as! String)
}
return hashMap
}
let ns: [String: ([MalData]) throws -> MalData] =
["+": { try calculate($0, op: +) },
"-": { try calculate($0, op: -) },
"*": { try calculate($0, op: *) },
"/": { try calculate($0, op: /) },
"<": { args in (args[0] as! Number) < (args[1] as! Number) },
">": { args in (args[0] as! Number) > (args[1] as! Number) },
"<=": { args in (args[0] as! Number) <= (args[1] as! Number) },
">=": { args in (args[0] as! Number) >= (args[1] as! Number) },
"=": { args in let left = args[0], right = args[1]; return isEqual(left, right) },
"pr-str": { $0.map { pr_str($0, print_readably: true)}.joined(separator: " ") },
"str": { $0.map { pr_str($0, print_readably: false)}.joined(separator: "") },
"prn": { print($0.map { pr_str($0, print_readably: true)}.joined(separator: " ")); return Nil() },
"println": { print($0.map { pr_str($0, print_readably: false)}.joined(separator: " ")); return Nil() },
"list": { List($0) },
"list?": { let param = $0[0]; return param is [MalData] },
"empty?": { $0[0].count == 0 },
"count": { $0[0].count },
"read-string": { try read_str($0[0] as! String) },
"slurp": { try String(contentsOfFile: $0[0] as! String) },
"atom": { Atom($0[0]) },
"atom?": { $0[0] is Atom },
"deref": { ($0[0] as? Atom)?.value ?? Nil() },
"reset!": { args in (args[0] as! Atom).value = args[1]; return args[1] },
"swap!": { args in
let atom = args[0] as! Atom, fn = args[1] as! Function,
others = args.dropFirst(2).listForm
atom.value = try fn.fn([atom.value] + others)
return atom.value
},
"cons": { args in [args[0]] + args[1].listForm },
"concat": { $0.reduce([]) { (result, array ) in result + array.listForm } },
"nth": { args in
let list = args[0].listForm, i = args[1] as! Int
guard list.indices.contains(i) else { throw MalError.IndexOutOfBounds }
return list[i]
},
"first": { $0[0].listForm.first ?? Nil() },
"rest": { $0[0].listForm.dropFirst().listForm },
"throw": { throw MalError.MalException($0[0]) },
"apply": { args in
let fn = args[0] as! Function
let newArgs = args.dropFirst().dropLast().listForm + args.last!.listForm
return try fn.fn(newArgs)
},
"map": { args in
let fn = args[0] as! Function
let closure = fn.fn
var result: [MalData] = []
for element in args[1].listForm {
result.append(try fn.fn([element])) }
return result
},
"nil?": { $0[0] is Nil },
"true?": { $0[0].dataType == .True },
"false?": { $0[0].dataType == .False },
"symbol?": { $0[0].dataType == .Symbol },
"symbol": { Symbol($0[0] as! String) },
"keyword": { ($0[0].dataType == .Keyword) ? $0[0] : "\u{029E}" + ($0[0] as! String) },
"keyword?":{ $0[0].dataType == .Keyword },
"vector": { Vector($0) },
"vector?": { $0[0].dataType == .Vector },
"hash-map":{ try hashMap(fromList: $0) },
"map?": { $0[0].dataType == .HashMap },
"assoc": {
let map = $0[0] as! [String: MalData]
return map.merging(try hashMap(fromList: $0.dropFirst().listForm)) { (_, new) in new }
},
"dissoc": { args in
let map = args[0] as! [String: MalData]
return map.filter { (key, _) in !(args.dropFirst().listForm as! [String]).contains(key) }
},
"get": {
if let map = $0[0] as? [String: MalData] {
return map[$0[1] as! String] ?? Nil() }
return Nil()
},
"contains?": { ($0[0] as! [String: MalData])[$0[1] as! String] != nil },
"keys": {
($0[0] as! [String: MalData]).reduce([]) { result, element in
let (key, _) = element
return result + [key] }
},
"vals": {
($0[0] as! [String: MalData]).reduce([]) { result, element in
let (_, value) = element
return result + [value] }
},
"sequential?": { [.List, .Vector].contains($0[0].dataType) },
"readline": {
print($0[0] as! String, terminator: "")
return readLine(strippingNewline: true) ?? Nil() },
"meta": {
switch $0[0].dataType {
case .Function:
return ($0[0] as! Function).meta ?? Nil()
default:
return Nil()
}},
"with-meta": {
switch $0[0].dataType {
case .Function:
return Function(withFunction: $0[0] as! Function, meta: $0[1])
default:
return $0[0]
}},
"time-ms": { _ in Int(Date().timeIntervalSince1970 * 1000) },
"conj": {
if let list = $0[0] as? [MalData] {
return $0.dropFirst().reversed().listForm + list
} else { // vector
return ($0[0] as! Vector) + Vector($0.dropFirst())
}},
"string?": { $0[0].dataType == .String },
"number?": { $0[0].dataType == .Number },
"fn?": {
if let fn = $0[0] as? Function {
return !fn.isMacro
} else {
return false
}},
"macro?": {
if let fn = $0[0] as? Function {
return fn.isMacro
} else {
return false
}},
"seq": {
if $0[0].count == 0 { return Nil() }
switch $0[0].dataType {
case .List:
return $0[0] as! List<MalData>
case .Vector:
return List($0[0] as! ContiguousArray<MalData>)
case .String:
return List($0[0] as! String).map { String($0) }
default:
return Nil()
}},
]

43
swift4/Sources/env.swift Normal file
View File

@ -0,0 +1,43 @@
import Foundation
class Env {
let outer: Env?
var data: [String: MalData] = [:]
init(outer: Env) {
self.outer = outer
}
init() {
outer = nil
}
init(binds: [Symbol], exprs: [MalData], outer: Env) {
self.outer = outer
self.data = [:]
for i in binds.indices {
if binds[i].name == "&" {
data.updateValue(List(exprs[i..<exprs.count]), forKey: binds[i+1].name)
return
}
data.updateValue(exprs[i], forKey: binds[i].name)
}
}
func set(_ value: MalData, forKey key:Symbol) {
data.updateValue(value, forKey: key.name)
}
func find(_ key: Symbol) -> Env? {
if let _ = data[key.name] {
return self
} else {
return outer?.find(key)
}
}
func get(forKey key: Symbol) throws -> MalData {
if let env = find(key), let value = env.data[key.name] {
return value
} else {
throw MalError.SymbolNotFound(key)
}
}
}

View File

@ -0,0 +1,51 @@
import Foundation
func pr_str(_ input: MalData, print_readably: Bool) -> String {
switch input.dataType {
case .Symbol:
let symbol = input as! Symbol
return symbol.name
case .Number:
let number = input as! Number
return String(number)
case .True:
return "true"
case .False:
return "false"
case .Nil:
return "nil"
case .Keyword:
let keyword = input as! String
return keyword.replacingCharacters(in: keyword.startIndex...keyword.startIndex, with: ":")
case .String:
let string = input as! String
if print_readably {
return "\"" + string.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "\"", with: "\\\"")
.replacingOccurrences(of: "\n", with: "\\n") + "\""
} else {
return string
}
case .List:
let list = input as! List<MalData>
let stringOfElements = list.map { pr_str($0, print_readably: print_readably) }.joined(separator: " ")
return "(" + stringOfElements + ")"
case .Vector:
let vector = input as! Vector<MalData>
let stringOfElements = vector.map { pr_str($0, print_readably: print_readably) }.joined(separator: " ")
return "[" + stringOfElements + "]"
case .HashMap:
let hashMap = input as! [String: MalData]
let stringOfElements = hashMap.map { (key, value) in
pr_str(key, print_readably: print_readably) + " " + pr_str(value, print_readably: print_readably)
}.joined(separator: " ")
return "{" + stringOfElements + "}"
case .Atom:
return pr_str("(atom \((input as! Atom).value))", print_readably: false)
case .Function:
return "#<function>"
default:
return "error type!"
}
}

147
swift4/Sources/reader.swift Normal file
View File

@ -0,0 +1,147 @@
import Foundation
struct Reader {
let tokens: [String]
var position = 0
init(tokens: [String]) {
self.tokens = tokens
}
mutating func next() -> String? {
guard tokens.indices.contains(position) else {
return nil
}
position += 1
return tokens[position - 1]
}
func peak() -> String? {
guard tokens.indices.contains(position) else {
return nil
}
return tokens[position]
}
mutating func pass() {
guard tokens.indices.contains(position) else {
return
}
position += 1
}
mutating func read_form() throws -> MalData {
guard let token = peak() else { throw MalError.Error }
switch token {
case "(", "[", "{":
return try read_list(startWith: token)
case "'", "`", "~", "~@", "@":
let readerMacros = ["'": "quote",
"`": "quasiquote",
"~": "unquote",
"~@": "splice-unquote",
"@": "deref"]
pass() // pass the mark
return try [Symbol(readerMacros[token]!), read_form()]
case "^":
pass() // pass the mark
let meta = try read_form()
return try [Symbol("with-meta"), read_form(), meta]
default:
return try read_atom()
}
}
mutating func read_list(startWith leftParen: String) throws -> MalData {
pass() // pass the left paren
defer {
pass() // pass the right paren
}
var list: [MalData] = []
while ![")", "]", "}"].contains(peak()) {
guard peak() != nil else {
throw MalError.ParensMismatch
}
list.append(try read_form())
}
switch (leftParen, peak()) {
case ("(", ")"):
return list
case ("[", "]"):
return Vector(list)
case ("{", "}"):
var hashMap: [String: MalData] = [:]
for index in stride(from: 0, to: list.count, by: 2) {
guard list[index] is String, index+1 < list.count else { throw MalError.Error }
hashMap.updateValue(list[index+1], forKey: list[index] as! String)
}
return hashMap
default:
throw MalError.ParensMismatch
}
}
mutating func read_atom() throws -> MalData {
let token = next()!
let regexInt = "^-?[0-9]+$"
let regexString = "\"(?:\\\\.|[^\\\\\"])*\""
let regexStringUnbalanced = "\"(?:\\\\.|[^\\\\\"])*"
let regexKeyword = "^:"
func match(string: String, regex: String) -> Bool {
return token.range(of: regex, options: .regularExpression, range: token.startIndex..<token.endIndex) != nil
}
switch token {
case let token where match(string: token, regex: regexInt):
return Int(token)!
case let token where match(string: token, regex: regexKeyword):
let firstChar = token.startIndex...token.startIndex
return token.replacingCharacters(in: firstChar, with: "\u{029E}")
case let token where match(string: token, regex: regexString):
let stripped = token.dropFirst().dropLast()
return stripped.replacingOccurrences(of: "\\\\", with: "\u{029E}")
.replacingOccurrences(of: "\\\"", with: "\"")
.replacingOccurrences(of: "\\n", with: "\n")
.replacingOccurrences(of: "\u{029E}", with: "\\")
case let token where match(string: token, regex: regexStringUnbalanced):
throw MalError.QuotationMarkMismatch
case "true":
return true
case "false":
return false
case "nil":
return Nil()
default:
return Symbol(token)
}
}
}
func tokenizer(_ input: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}()'\"`@,;]+)", options: .useUnixLineSeparators)
else { return [] }
let matches = regex.matches(in: input, range: NSMakeRange(0, input.count))
return matches.map { match in
String(input[Range(match.range(at: 1), in: input)!])
}.filter { token in
!token.hasPrefix(";") && !token.isEmpty }
}
func read_str(_ input: String) throws -> MalData {
let tokens = tokenizer(input)
guard tokens.count>0 else {
throw MalError.EmptyData
}
var reader = Reader(tokens: tokens)
return try reader.read_form()
}

View File

@ -0,0 +1,25 @@
import Foundation
func READ(_ input:String) -> String {
return input
}
func EVAL(_ input:String) -> String {
return input
}
func PRINT(_ input:String) -> String {
return input
}
@discardableResult func rep(_ input:String) -> String {
return PRINT(EVAL(READ(input)))
}
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
print(rep(input))
}
}

View File

@ -0,0 +1,31 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ input: MalData) throws -> MalData {
return input
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String) throws -> String {
return try PRINT(EVAL(READ(input)))
}
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,78 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ ast: MalData, env: [String: MalData]) throws -> MalData {
switch ast.dataType {
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .List:
let list = ast as! [MalData]
if list.isEmpty { return list }
let evaluated = try eval_ast(list, env: env) as! [MalData]
if let function = evaluated[0] as? Function {
return try function.fn(List(evaluated.dropFirst()))
} else {
throw MalError.SymbolNotFound(list[0] as! Symbol)
}
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String) throws -> String{
func calculate(_ args: [MalData], op: (Number, Number) -> Number) throws -> MalData {
guard args.count == 2, args[0] is Number, args[1] is Number else { throw MalError.InvalidArgument }
return op(args[0] as! Number, args[1] as! Number)
}
let repl_env = ["+": Function(fn: { args in try calculate(args, op: +) }),
"-": Function(fn: { args in try calculate(args, op: -) }),
"*": Function(fn: { args in try calculate(args, op: *) }),
"/": Function(fn: { args in try calculate(args, op: /) })]
return try PRINT(EVAL(READ(input), env: repl_env))
}
func eval_ast(_ ast: MalData, env: [String: MalData]) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = env[sym.name] {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
default:
return ast
}
}
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,94 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .List:
let list = ast as! [MalData]
guard !list.isEmpty else { return list }
guard let sym = list[0] as? Symbol else { throw MalError.Error }
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = (list[1] is Vector<MalData>) ? List(list[1] as! Vector<MalData>) : list[1] as! List<MalData>
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
return try EVAL(expr, env: newEnv)
default:
let evaluated = try eval_ast(list, env: env) as! [MalData]
if let function = evaluated[0] as? Function {
return try function.fn(List(evaluated.dropFirst()))
} else {
throw MalError.SymbolNotFound(list[0] as! Symbol)
}
}
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
default:
return ast
}
}
func calculate(_ args: [MalData], op: (Number, Number) -> Number) throws -> MalData {
guard args.count == 2, args[0] is Number, args[1] is Number else { throw MalError.InvalidArgument }
return op(args[0] as! Number, args[1] as! Number)
}
let repl_env = Env()
repl_env.set(Function(fn: { args in try calculate(args, op: +) }), forKey: Symbol("+"))
repl_env.set(Function(fn: { args in try calculate(args, op: -) }), forKey: Symbol("-"))
repl_env.set(Function(fn: { args in try calculate(args, op: *) }), forKey: Symbol("*"))
repl_env.set(Function(fn: { args in try calculate(args, op: /) }), forKey: Symbol("/"))
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,108 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .List:
let list = ast as! [MalData]
guard !list.isEmpty else { return list }
if let sym = list[0] as? Symbol {
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = (list[1] is Vector<MalData>) ? List(list[1] as! Vector<MalData>) : list[1] as! List<MalData>
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
return try EVAL(expr, env: newEnv)
case "do":
return try list.dropFirst().map { try EVAL($0, env: env) }.last ?? Nil()
case "if":
let predicate = try EVAL(list[1], env: env)
if predicate as? Bool == false || predicate is Nil {
return list.count>3 ? try EVAL(list[3], env: env) : Nil()
} else {
return try EVAL(list[2], env: env)
}
case "fn*":
let ops = {(params: [MalData]) -> MalData in
let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
return try EVAL(list[2], env: newEnv)
}
return Function(fn: ops)
default:
break
}
}
// not a symbol. maybe: function, list, or some wrong type
let evaluated = try eval_ast(list, env: env) as! [MalData]
if let function = evaluated[0] as? Function {
return try function.fn(List(evaluated.dropFirst()))
} else {
throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
}
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
default:
return ast
}
}
var repl_env = Env()
for (key, value) in ns {
repl_env.set(Function(fn: value), forKey: Symbol(key))
}
try _ = rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,138 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData {
var ast = anAst, env = anEnv
while true {
switch ast.dataType {
case .List:
let list = ast as! [MalData]
guard !list.isEmpty else { return list }
if let sym = list[0] as? Symbol {
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = list[1].listForm
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
env = newEnv
ast = expr
continue
case "do":
try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) }
ast = list.last ?? Nil()
continue
case "if":
let predicate = try EVAL(list[1], env: env)
if predicate as? Bool == false || predicate is Nil {
ast = list.count>3 ? list[3] : Nil()
} else {
ast = list[2]
}
continue
case "fn*":
let fn = {(params: [MalData]) -> MalData in
let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
return try EVAL(list[2], env: newEnv)
}
return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn)
default:
break
}
}
// not a symbol. maybe: function, list, or some wrong type
let evaluated = try eval_ast(list, env: env) as! [MalData]
guard let function = evaluated[0] as? Function else {
throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
}
if let fnAst = function.ast { // a full fn
ast = fnAst
env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!)
} else { // normal function
return try function.fn(evaluated.dropFirst().listForm)
}
continue
/* fn
fn fn ab+ a b 1 2
fn
1.
1.1 =
1.2 使
2.使
使使over
fn TCO
1.: + fn时的环境
2.使
使 fn env env
*/
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
default:
return ast
}
}
var repl_env = Env()
for (key, value) in ns {
repl_env.set(Function(fn: value), forKey: Symbol(key))
}
try _ = rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,136 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData {
var ast = anAst, env = anEnv
while true {
switch ast.dataType {
case .List:
let list = ast as! [MalData]
guard !list.isEmpty else { return list }
if let sym = list[0] as? Symbol {
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = list[1].listForm
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
env = newEnv
ast = expr
continue
case "do":
try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) }
ast = list.last ?? Nil()
continue
case "if":
let predicate = try EVAL(list[1], env: env)
if predicate as? Bool == false || predicate is Nil {
ast = list.count>3 ? list[3] : Nil()
} else {
ast = list[2]
}
continue
case "fn*":
let fn = {(params: [MalData]) -> MalData in
let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
return try EVAL(list[2], env: newEnv)
}
return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn)
default:
break
}
}
// not a symbol. maybe: function, list, or some wrong type
let evaluated = try eval_ast(list, env: env) as! [MalData]
guard let function = evaluated[0] as? Function else {
throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
}
if let fnAst = function.ast { // a full fn
ast = fnAst
env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!)
} else { // normal function
return try function.fn(evaluated.dropFirst().listForm)
}
continue
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
case .Atom:
return (ast as! Atom).value
default:
return ast
}
}
var repl_env = Env()
for (key, value) in ns {
repl_env.set(Function(fn: value), forKey: Symbol(key))
}
repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval"))
repl_env.set([], forKey: Symbol("*ARGV*"))
try rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
if CommandLine.argc > 1 {
let fileName = CommandLine.arguments[1],
args = List(CommandLine.arguments.dropFirst(2))
repl_env.set(args, forKey: Symbol("*ARGV*"))
try rep("(load-file \"\(fileName)\")", env: repl_env)
exit(0)
}
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,158 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData {
func is_pair(_ ast: MalData) -> Bool { // not used
return (ast is [MalData]) && (ast.count != 0)
}
func quasiquote(_ ast: MalData) -> MalData {
let list = ast.listForm
if list.isEmpty {
return [Symbol("quote"), ast]
}
if let sym = list[0] as? Symbol, sym.name == "unquote" {
return list[1]
}
let innerList = list[0].listForm
if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" {
return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)]
}
return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)]
}
var ast = anAst, env = anEnv
while true {
switch ast.dataType {
case .List:
let list = ast as! [MalData]
guard !list.isEmpty else { return list }
if let sym = list[0] as? Symbol {
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = list[1].listForm
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
env = newEnv
ast = expr
continue
case "do":
try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) }
ast = list.last ?? Nil()
continue
case "if":
let predicate = try EVAL(list[1], env: env)
if predicate as? Bool == false || predicate is Nil {
ast = list.count>3 ? list[3] : Nil()
} else {
ast = list[2]
}
continue
case "fn*":
let fn = {(params: [MalData]) -> MalData in
let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
return try EVAL(list[2], env: newEnv)
}
return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn)
case "quote":
return list[1]
case "quasiquote":
ast = quasiquote(list[1])
continue
default:
break
}
}
// not a symbol. maybe: function, list, or some wrong type
let evaluated = try eval_ast(list, env: env) as! [MalData]
guard let function = evaluated[0] as? Function else {
throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
}
if let fnAst = function.ast { // a full fn
ast = fnAst
env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!)
} else { // normal function
return try function.fn(evaluated.dropFirst().listForm)
}
continue
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
case .Atom:
return (ast as! Atom).value
default:
return ast
}
}
var repl_env = Env()
for (key, value) in ns {
repl_env.set(Function(fn: value), forKey: Symbol(key))
}
repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval"))
repl_env.set([], forKey: Symbol("*ARGV*"))
try rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
if CommandLine.argc > 1 {
let fileName = CommandLine.arguments[1],
args = List(CommandLine.arguments.dropFirst(2))
repl_env.set(args, forKey: Symbol("*ARGV*"))
try rep("(load-file \"\(fileName)\")", env: repl_env)
exit(0)
}
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,189 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData {
func is_pair(_ ast: MalData) -> Bool { // not used
return (ast is [MalData]) && (ast.count != 0)
}
func quasiquote(_ ast: MalData) -> MalData {
let list = ast.listForm
if list.isEmpty {
return [Symbol("quote"), ast]
}
if let sym = list[0] as? Symbol, sym.name == "unquote" {
return list[1]
}
let innerList = list[0].listForm
if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" {
return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)]
}
return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)]
}
func macroexpand(_ anAst: MalData, env: Env) throws -> MalData {
func isMacro_call(_ ast: MalData, env: Env) -> Bool { // not used
if let list = ast as? [MalData],
let symbol = list[0] as? Symbol,
let fn = try? env.get(forKey: symbol) as? Function {
return fn?.isMacro ?? false
}
return false
}
var ast = anAst
while let list = ast as? [MalData],
let symbol = list[0] as? Symbol,
let fn = try? env.get(forKey: symbol) as? Function,
let isMacro = fn?.isMacro, isMacro == true {
ast = try fn!.fn(List(list.dropFirst()))
}
return ast
}
/// Apply
var ast = anAst, env = anEnv
while true {
switch ast.dataType {
case .List:
ast = try macroexpand(ast, env: env)
guard let list = ast as? [MalData] else { return try eval_ast(ast, env: env) }
guard !list.isEmpty else { return list }
if let sym = list[0] as? Symbol {
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "defmacro!":
let fn = try EVAL(list[2], env: env) as! Function, key = list[1] as! Symbol
let macro = Function(withFunction: fn, isMacro: true)
env.set(macro, forKey: key)
return macro
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = list[1].listForm
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
env = newEnv
ast = expr
continue
case "do":
try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) }
ast = list.last ?? Nil()
continue
case "if":
let predicate = try EVAL(list[1], env: env)
if predicate as? Bool == false || predicate is Nil {
ast = list.count>3 ? list[3] : Nil()
} else {
ast = list[2]
}
continue
case "fn*":
let fn = {(params: [MalData]) -> MalData in
let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
return try EVAL(list[2], env: newEnv)
}
return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn)
case "quote":
return list[1]
case "quasiquote":
ast = quasiquote(list[1])
continue
case "macroexpand":
return try macroexpand(list[1], env: env)
default:
break
}
}
// not a symbol. maybe: function, list, or some wrong type
let evaluated = try eval_ast(list, env: env) as! [MalData]
guard let function = evaluated[0] as? Function else {
throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
}
if let fnAst = function.ast { // a full fn
ast = fnAst
env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!)
} else { // normal function
return try function.fn(evaluated.dropFirst().listForm)
}
continue
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
case .Atom:
return (ast as! Atom).value
default:
return ast
}
}
var repl_env = Env()
for (key, value) in ns {
repl_env.set(Function(fn: value), forKey: Symbol(key))
}
repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval"))
repl_env.set([], forKey: Symbol("*ARGV*"))
try rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
try rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env: repl_env)
try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env: repl_env)
if CommandLine.argc > 1 {
let fileName = CommandLine.arguments[1],
args = List(CommandLine.arguments.dropFirst(2))
repl_env.set(args, forKey: Symbol("*ARGV*"))
try rep("(load-file \"\(fileName)\")", env: repl_env)
exit(0)
}
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch let error as MalError {
print(error.info())
}
}
}

View File

@ -0,0 +1,208 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData {
func is_pair(_ ast: MalData) -> Bool { // not used
return (ast is [MalData]) && (ast.count != 0)
}
func quasiquote(_ ast: MalData) -> MalData {
let list = ast.listForm
if list.isEmpty {
return [Symbol("quote"), ast]
}
if let sym = list[0] as? Symbol, sym.name == "unquote" {
return list[1]
}
let innerList = list[0].listForm
if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" {
return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)]
}
return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)]
}
func macroexpand(_ anAst: MalData, env: Env) throws -> MalData {
func isMacro_call(_ ast: MalData, env: Env) -> Bool { // not used
if let list = ast as? [MalData],
let symbol = list[0] as? Symbol,
let fn = try? env.get(forKey: symbol) as? Function {
return fn?.isMacro ?? false
}
return false
}
var ast = anAst
while let list = ast as? [MalData],
let symbol = list[0] as? Symbol,
let fn = try? env.get(forKey: symbol) as? Function,
let isMacro = fn?.isMacro, isMacro == true {
ast = try fn!.fn(List(list.dropFirst()))
}
return ast
}
/// Apply
var ast = anAst, env = anEnv
while true {
switch ast.dataType {
case .List:
ast = try macroexpand(ast, env: env)
guard let list = ast as? [MalData] else { return try eval_ast(ast, env: env) }
guard !list.isEmpty else { return list }
if let sym = list[0] as? Symbol {
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "defmacro!":
let fn = try EVAL(list[2], env: env) as! Function, key = list[1] as! Symbol
let macro = Function(withFunction: fn, isMacro: true)
env.set(macro, forKey: key)
return macro
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = list[1].listForm
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
env = newEnv
ast = expr
continue
case "do":
try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) }
ast = list.last ?? Nil()
continue
case "if":
let predicate = try EVAL(list[1], env: env)
if predicate as? Bool == false || predicate is Nil {
ast = list.count>3 ? list[3] : Nil()
} else {
ast = list[2]
}
continue
case "fn*":
let fn = {(params: [MalData]) -> MalData in
let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
return try EVAL(list[2], env: newEnv)
}
return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn)
case "quote":
return list[1]
case "quasiquote":
ast = quasiquote(list[1])
continue
case "macroexpand":
return try macroexpand(list[1], env: env)
// (try* A (catch* B C))
case "try*":
do {
return try EVAL(list[1], env: env)
} catch let error as MalError {
if list.count > 2 {
let catchList = list[2] as! [MalData]
let catchEnv = Env(binds: [catchList[1] as! Symbol], exprs:[error.info()] , outer: env)
return try EVAL(catchList[2], env: catchEnv)
} else {
throw error
}
}
default:
break
}
}
// not a symbol. maybe: function, list, or some wrong type
let evaluated = try eval_ast(list, env: env) as! [MalData]
guard let function = evaluated[0] as? Function else {
throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
}
if let fnAst = function.ast { // a full fn
ast = fnAst
env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!)
} else { // normal function
return try function.fn(evaluated.dropFirst().listForm)
}
continue
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
case .Atom:
return (ast as! Atom).value
default:
return ast
}
}
var repl_env = Env()
for (key, value) in ns {
repl_env.set(Function(fn: value), forKey: Symbol(key))
}
repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval"))
repl_env.set([], forKey: Symbol("*ARGV*"))
try rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
try rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env: repl_env)
try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env: repl_env)
if CommandLine.argc > 1 {
let fileName = CommandLine.arguments[1],
args = List(CommandLine.arguments.dropFirst(2))
repl_env.set(args, forKey: Symbol("*ARGV*"))
try rep("(load-file \"\(fileName)\")", env: repl_env)
exit(0)
}
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch MalError.MalException(let data) {
if let description = data as? String {
print("Exception." + description)
} else if let dic = data as? [String: String], !dic.isEmpty {
print("Exception." + dic.keys.first! + "." + dic.values.first!)
}
} catch let error as MalError {
print((pr_str(error.info(), print_readably: false)))
}
}
}

View File

@ -0,0 +1,213 @@
import Foundation
func READ(_ input: String) throws -> MalData {
return try read_str(input)
}
func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData {
func is_pair(_ ast: MalData) -> Bool { // not used
return (ast is [MalData]) && (ast.count != 0)
}
func quasiquote(_ ast: MalData) -> MalData {
let list = ast.listForm
if list.isEmpty {
return [Symbol("quote"), ast]
}
if let sym = list[0] as? Symbol, sym.name == "unquote" {
return list[1]
}
let innerList = list[0].listForm
if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" {
return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)]
}
return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)]
}
func macroexpand(_ anAst: MalData, env: Env) throws -> MalData {
func isMacro_call(_ ast: MalData, env: Env) -> Bool { // not used
if let list = ast as? [MalData],
let symbol = list[0] as? Symbol,
let fn = try? env.get(forKey: symbol) as? Function {
return fn?.isMacro ?? false
}
return false
}
var ast = anAst
while let list = ast as? [MalData],
let symbol = list[0] as? Symbol,
let fn = try? env.get(forKey: symbol) as? Function,
let isMacro = fn?.isMacro, isMacro == true {
ast = try fn!.fn(List(list.dropFirst()))
}
return ast
}
/// Apply
var ast = anAst, env = anEnv
while true {
switch ast.dataType {
case .List:
ast = try macroexpand(ast, env: env)
guard let list = ast as? [MalData] else { return try eval_ast(ast, env: env) }
guard !list.isEmpty else { return list }
if let sym = list[0] as? Symbol {
switch sym.name {
case "def!":
let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
env.set(value, forKey: key)
return value
case "defmacro!":
let fn = try EVAL(list[2], env: env) as! Function, key = list[1] as! Symbol
let macro = Function(withFunction: fn, isMacro: true)
env.set(macro, forKey: key)
return macro
case "let*":
let newEnv = Env(outer: env), expr = list[2]
let bindings = list[1].listForm
for i in stride(from: 0, to: bindings.count-1, by: 2) {
let key = bindings[i], value = bindings[i+1]
let result = try EVAL(value, env: newEnv)
newEnv.set(result, forKey: key as! Symbol)
}
env = newEnv
ast = expr
continue
case "do":
try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) }
ast = list.last ?? Nil()
continue
case "if":
let predicate = try EVAL(list[1], env: env)
if predicate as? Bool == false || predicate is Nil {
ast = list.count>3 ? list[3] : Nil()
} else {
ast = list[2]
}
continue
case "fn*":
let fn = {(params: [MalData]) -> MalData in
let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
return try EVAL(list[2], env: newEnv)
}
return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn)
case "quote":
return list[1]
case "quasiquote":
ast = quasiquote(list[1])
continue
case "macroexpand":
return try macroexpand(list[1], env: env)
case "try*":
do {
return try EVAL(list[1], env: env)
} catch let error as MalError {
if list.count > 2 {
let catchList = list[2] as! [MalData]
let catchEnv = Env(binds: [catchList[1] as! Symbol], exprs:[error.info()] , outer: env)
return try EVAL(catchList[2], env: catchEnv)
} else {
throw error
}
}
default:
break
}
}
// not a symbol. maybe: function, list, or some wrong type
let evaluated = try eval_ast(list, env: env) as! [MalData]
guard let function = evaluated[0] as? Function else {
throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
}
if let fnAst = function.ast { // a full fn
ast = fnAst
env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!)
} else { // normal function
return try function.fn(evaluated.dropFirst().listForm)
}
continue
case .Vector:
let vector = ast as! ContiguousArray<MalData>
return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
case .HashMap:
let hashMap = ast as! HashMap<String, MalData>
return try hashMap.mapValues { value in try EVAL(value, env: env) }
default:
return try eval_ast(ast, env: env)
}
}
}
func PRINT(_ input: MalData) -> String {
return pr_str(input, print_readably: true)
}
@discardableResult func rep(_ input: String, env: Env) throws -> String {
return try PRINT(EVAL(READ(input), env: env))
}
func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
switch ast.dataType {
case .Symbol:
let sym = ast as! Symbol
if let function = try? env.get(forKey: sym) {
return function
} else {
throw MalError.SymbolNotFound(sym)
}
case .List:
let list = ast as! [MalData]
return try list.map { element in try EVAL(element, env: env) }
case .Atom:
return (ast as! Atom).value
default:
return ast
}
}
var repl_env = Env()
for (key, value) in ns {
repl_env.set(Function(fn: value), forKey: Symbol(key))
}
repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval"))
repl_env.set([], forKey: Symbol("*ARGV*"))
repl_env.set("Swift4", forKey: Symbol("*host-language*"))
try rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env)
try rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env: repl_env)
try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env: repl_env)
try rep("(def! *gensym-counter* (atom 0))", env: repl_env)
try rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", env: repl_env)
try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", env: repl_env)
if CommandLine.argc > 1 {
let fileName = CommandLine.arguments[1],
args = List(CommandLine.arguments.dropFirst(2))
repl_env.set(args, forKey: Symbol("*ARGV*"))
try rep("(load-file \"\(fileName)\")", env: repl_env)
exit(0)
}
try rep("(println (str \"Mal [\" *host-language* \"]\"))", env: repl_env)
while true {
print("user> ", terminator: "")
if let input = readLine(strippingNewline: true) {
guard input != "" else { continue }
do {
try print(rep(input, env: repl_env))
} catch MalError.MalException(let data) {
if let description = data as? String {
print("Exception." + description)
} else if let dic = data as? [String: String], !dic.isEmpty {
print("Exception." + dic.keys.first! + "." + dic.values.first!)
}
} catch let error as MalError {
print((pr_str(error.info(), print_readably: false)))
}
}
}

151
swift4/Sources/types.swift Normal file
View File

@ -0,0 +1,151 @@
import Foundation
enum MalDataType: String {
case Number, String, List, Vector, HashMap, Symbol, Keyword, Atom, Nil, True, False, Function, Unknown
}
protocol MalData {
var dataType: MalDataType { get }
var count: Int { get }
var listForm: [MalData] { get }
}
extension MalData {
var dataType: MalDataType { // not used
return MalDataType(rawValue: String(describing: type(of: self))) ?? MalDataType.Unknown
}
var count: Int { return 0 }
var listForm: [MalData] { return [] }
}
typealias Number = Int
typealias List = Array
typealias Vector = ContiguousArray
typealias HashMap = Dictionary
struct Symbol: MalData {
let dataType = MalDataType.Symbol
let name: String
init(_ name: String) {
self.name = name
}
}
struct Nil: MalData {
let dataType = MalDataType.Nil
}
class Atom: MalData {
let dataType = MalDataType.Atom
var value: MalData
init(_ value: MalData) {
self.value = value
}
}
struct Function: MalData {
let dataType = MalDataType.Function
let ast: MalData?
let params: [Symbol]?
let env: Env?
let fn: (([MalData]) throws -> MalData)
let isMacro: Bool
let meta: MalData?
init(ast: MalData? = nil, params: [Symbol]? = nil, env: Env? = nil, isMacro: Bool = false, meta: MalData? = nil,
fn: @escaping ([MalData]) throws -> MalData) {
self.ast = ast
self.params = params
self.env = env
self.isMacro = isMacro
self.fn = fn
self.meta = meta
}
init(withFunction function: Function, isMacro: Bool) {
self.ast = function.ast
self.params = function.params
self.env = function.env
self.fn = function.fn
self.meta = function.meta
self.isMacro = isMacro
}
init(withFunction function: Function, meta: MalData) {
self.ast = function.ast
self.params = function.params
self.env = function.env
self.fn = function.fn
self.isMacro = function.isMacro
self.meta = meta
}
}
extension String: MalData {
var dataType: MalDataType {
return !self.isEmpty && self[startIndex] == "\u{029E}" ? .Keyword : .String }
}
extension Number: MalData {
var dataType: MalDataType { return .Number }
}
extension Bool : MalData {
var dataType: MalDataType { return self == true ? .True : .False }
}
extension List : MalData {
var dataType: MalDataType { return .List }
var listForm: [MalData] { return self as! [MalData] }
}
extension Vector: MalData {
var dataType: MalDataType { return .Vector }
var listForm: [MalData] { return List(self) as! [MalData] }
}
extension ArraySlice: MalData {
var dataType: MalDataType { return .List }
var listForm: [MalData] { return List(self) as! [MalData] }
}
extension HashMap: MalData {
var dataType: MalDataType { return .HashMap }
static func hashMap(fromList list: [MalData]) throws -> [String: MalData] {
var hashMap: [String: MalData] = [:]
for index in stride(from: 0, to: list.count, by: 2) {
guard list[index] is String, index+1 < list.count else { throw MalError.Error }
hashMap.updateValue(list[index+1], forKey: list[index] as! String)
}
return hashMap
}
}
// MARK: Errors
enum MalError: Error {
case ParensMismatch
case QuotationMarkMismatch
case EmptyData
case SymbolNotFound(Symbol)
case InvalidArgument
case Error
case IndexOutOfBounds
case MalException(MalData)
func info() -> MalData {
switch self {
case .ParensMismatch:
return "unbalanced parens"
case .QuotationMarkMismatch:
return "unbalanced quotation mark"
case .EmptyData:
return "empty data"
case .InvalidArgument:
return "invalid argument"
case .SymbolNotFound(let symbol):
return "'\(symbol.name)' not found"
case .IndexOutOfBounds:
return "index out of bounds"
case .MalException(let data):
return data
default:
return "uncaught error!"
}
}
}

2
swift4/run Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
exec $(dirname $0)/${STEP:-stepA_mal} "${@}"