mirror of
https://github.com/kanaka/mal.git
synced 2024-11-13 01:43:50 +03:00
commit
23b7e1085b
@ -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]}
|
||||
|
3
Makefile
3
Makefile
@ -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
|
||||
|
14
README.md
14
README.md
@ -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
44
swift4/Dockerfile
Normal 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
40
swift4/Makefile
Normal 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
209
swift4/Sources/core.swift
Normal 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
43
swift4/Sources/env.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
51
swift4/Sources/printer.swift
Normal file
51
swift4/Sources/printer.swift
Normal 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
147
swift4/Sources/reader.swift
Normal 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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
25
swift4/Sources/step0_repl/main.swift
Normal file
25
swift4/Sources/step0_repl/main.swift
Normal 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))
|
||||
}
|
||||
}
|
31
swift4/Sources/step1_read_print/main.swift
Normal file
31
swift4/Sources/step1_read_print/main.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
78
swift4/Sources/step2_eval/main.swift
Normal file
78
swift4/Sources/step2_eval/main.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
94
swift4/Sources/step3_env/main.swift
Normal file
94
swift4/Sources/step3_env/main.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
108
swift4/Sources/step4_if_fn_do/main.swift
Normal file
108
swift4/Sources/step4_if_fn_do/main.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
138
swift4/Sources/step5_tco/main.swift
Normal file
138
swift4/Sources/step5_tco/main.swift
Normal 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 (a,b)(+ 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())
|
||||
}
|
||||
}
|
||||
}
|
136
swift4/Sources/step6_file/main.swift
Normal file
136
swift4/Sources/step6_file/main.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
158
swift4/Sources/step7_quote/main.swift
Normal file
158
swift4/Sources/step7_quote/main.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
189
swift4/Sources/step8_macros/main.swift
Normal file
189
swift4/Sources/step8_macros/main.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
208
swift4/Sources/step9_try/main.swift
Normal file
208
swift4/Sources/step9_try/main.swift
Normal 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)))
|
||||
}
|
||||
}
|
||||
}
|
213
swift4/Sources/stepA_mal/main.swift
Normal file
213
swift4/Sources/stepA_mal/main.swift
Normal 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
151
swift4/Sources/types.swift
Normal 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
2
swift4/run
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exec $(dirname $0)/${STEP:-stepA_mal} "${@}"
|
Loading…
Reference in New Issue
Block a user