1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-11 13:55:55 +03:00

Merge pull request #465 from dubek/wren-impl

Wren implementation
This commit is contained in:
Joel Martin 2019-11-02 15:49:25 -05:00 committed by GitHub
commit 3a9018f17b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1874 additions and 2 deletions

View File

@ -111,6 +111,7 @@ matrix:
- {env: IMPL=wasm wasm_MODE=node NO_SELF_HOST_PERF=1, services: [docker]}
- {env: IMPL=wasm wasm_MODE=warpy NO_SELF_HOST_PERF=1, services: [docker]}
- {env: IMPL=wasm wasm_MODE=wace_libc NO_SELF_HOST_PERF=1, services: [docker]}
- {env: IMPL=wren, services: [docker]}
- {env: IMPL=yorick, services: [docker]}
script:

View File

@ -94,7 +94,7 @@ IMPLS = ada ada.2 awk bash basic bbc-basic c chuck clojure coffee common-lisp cp
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 pike plpgsql \
plsql powershell ps python python.2 r racket rexx rpython ruby rust scala scheme skew \
swift swift3 swift4 tcl ts vala vb vhdl vimscript wasm yorick
swift swift3 swift4 tcl ts vala vb vhdl vimscript wasm wren yorick
EXTENSION = .mal
@ -262,6 +262,7 @@ vb_STEP_TO_PROG = vb/$($(1)).exe
vhdl_STEP_TO_PROG = vhdl/$($(1))
vimscript_STEP_TO_PROG = vimscript/$($(1)).vim
wasm_STEP_TO_PROG = wasm/$($(1)).$(if $(filter lucet,$(wasm_MODE)),so,wasm)
wren_STEP_TO_PROG = wren/$($(1)).wren
yorick_STEP_TO_PROG = yorick/$($(1)).i

View File

@ -6,7 +6,7 @@
**1. Mal is a Clojure inspired Lisp interpreter**
**2. Mal is implemented in 78 languages (80 different implementations and 101 runtime modes)**
**2. Mal is implemented in 79 languages (81 different implementations and 102 runtime modes)**
| Language | Creator |
| -------- | ------- |
@ -89,6 +89,7 @@
| [Vimscript](#vimscript) | [Dov Murik](https://github.com/dubek) |
| [Visual Basic.NET](#visual-basicnet) | [Joel Martin](https://github.com/kanaka) |
| [WebAssembly](#webassembly-wasm) (wasm) | [Joel Martin](https://github.com/kanaka) |
| [Wren](#wren) | [Dov Murik](https://github.com/dubek) |
| [Yorick](#yorick) | [Dov Murik](https://github.com/dubek) |
@ -1119,6 +1120,15 @@ make wasm_MODE=warpy
warpy --argv --memory-pages 256 ./stepX_YYY.wasm
```
### Wren
The Wren implementation of mal was tested on Wren 0.2.0.
```
cd wren
wren ./stepX_YYY.wren
```
### Yorick
The Yorick implementation of mal was tested on Yorick 2.2.04.

33
wren/Dockerfile Normal file
View File

@ -0,0 +1,33 @@
FROM ubuntu:18.04
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
##########################################################
RUN apt-get -y install g++
RUN apt-get -y install git
COPY wren-add-gettimeofday.patch /tmp/
RUN cd /tmp && git clone --depth=1 https://github.com/wren-lang/wren.git \
&& cd wren \
&& patch -p1 < /tmp/wren-add-gettimeofday.patch \
&& make \
&& cp ./wren /usr/local/bin/ \
&& cd /tmp && rm -rf wren

14
wren/Makefile Normal file
View File

@ -0,0 +1,14 @@
SOURCES = types.wren env.wren printer.wren reader.wren readline.wren interop.wren core.wren stepA_mal.wren
all:
true
dist: mal
mal.wren: $(SOURCES)
cat $+ | grep -v '^import "./' > $@
mal: mal.wren
echo "#!/usr/bin/env wren" > $@
cat $< >> $@
chmod +x $@

15
wren/README.md Normal file
View File

@ -0,0 +1,15 @@
# Wren implementation
### Adding a time function
Since Wren doesn't have a time function, we add a `System.gettimeofday`
function which returns a float with the number of seconds since epoch (with
fractions of seconds).
This is done by applying the patch in `wren-add-gettimeofday.path` to Wren's
source code before compiling it (see `Dockerfile`).
### Wren interop
See examples in `tests/stepA_mal.mal` for usage of `wren-eval` to evaluate Wren
expressions inside a Mal program.

105
wren/core.wren Normal file
View File

@ -0,0 +1,105 @@
import "io" for File
import "./reader" for MalReader
import "./readline" for Readline
import "./printer" for Printer
import "./types" for MalVal, MalSymbol, MalSequential, MalList, MalVector, MalMap, MalNativeFn, MalFn, MalAtom, MalException
import "./interop" for Interop
class Core {
static fn(func) { MalNativeFn.new(func) }
static ns {
return {
"=": fn { |a| a[0] == a[1] },
"throw": fn { |a|
MalException.set(a[0])
Fiber.abort("___MalException___")
},
"nil?": fn { |a| a[0] == null },
"true?": fn { |a| a[0] == true },
"false?": fn { |a| a[0] == false },
"string?": fn { |a| a[0] is String && !MalVal.isKeyword(a[0]) },
"symbol": fn { |a| a[0] is MalSymbol ? a[0] : MalSymbol.new(a[0]) },
"symbol?": fn { |a| a[0] is MalSymbol },
"keyword": fn { |a| MalVal.isKeyword(a[0]) ? a[0] : MalVal.newKeyword(a[0]) },
"keyword?": fn { |a| MalVal.isKeyword(a[0]) },
"number?": fn { |a| a[0] is Num },
"fn?": fn { |a| a[0] is MalNativeFn || (a[0] is MalFn && !a[0].isMacro) },
"macro?": fn { |a| a[0] is MalFn && a[0].isMacro },
"pr-str": fn { |a| a.map { |e| Printer.pr_str(e, true) }.join(" ") },
"str": fn { |a| a.map { |e| Printer.pr_str(e, false) }.join() },
"prn": fn { |a|
System.print(a.map { |e| Printer.pr_str(e, true) }.join(" "))
return null
},
"println": fn { |a|
System.print(a.map { |e| Printer.pr_str(e, false) }.join(" "))
return null
},
"read-string": fn { |a| MalReader.read_str(a[0]) },
"readline": fn { |a| Readline.readLine(a[0]) },
"slurp": fn { |a| File.read(a[0]) },
"<": fn { |a| a[0] < a[1] },
"<=": fn { |a| a[0] <= a[1] },
">": fn { |a| a[0] > a[1] },
">=": fn { |a| a[0] >= a[1] },
"+": fn { |a| a[0] + a[1] },
"-": fn { |a| a[0] - a[1] },
"*": fn { |a| a[0] * a[1] },
"/": fn { |a| a[0] / a[1] },
"time-ms": fn { |a| (System.gettimeofday * 1000).floor },
"list": fn { |a| MalList.new(a) },
"list?": fn { |a| a[0] is MalList },
"vector": fn { |a| MalVector.new(a) },
"vector?": fn { |a| a[0] is MalVector },
"hash-map": fn { |a| MalMap.fromList(a) },
"map?": fn { |a| a[0] is MalMap },
"assoc": fn { |a| a[0].assoc(a[1...a.count]) },
"dissoc": fn { |a| a[0].dissoc(a[1...a.count]) },
"get": fn { |a| a[0] == null ? null : a[0].data[a[1]] },
"contains?": fn { |a| a[0].data.containsKey(a[1]) },
"keys": fn { |a| MalList.new(a[0].data.keys.toList) },
"vals": fn { |a| MalList.new(a[0].data.values.toList) },
"sequential?": fn { |a| a[0] is MalSequential },
"cons": fn { |a| MalList.new([a[0]] + a[1].elements) },
"concat": fn { |a| MalList.new(a.reduce([]) { |acc,e| acc + e.elements }) },
"nth": fn { |a| a[1] < a[0].count ? a[0][a[1]] : Fiber.abort("nth: index out of range") },
"first": fn { |a| a[0] == null ? null : a[0].first },
"rest": fn { |a| a[0] == null ? MalList.new([]) : a[0].rest },
"empty?": fn { |a| a[0].isEmpty },
"count": fn { |a| a[0] == null ? 0 : a[0].count },
"apply": fn { |a| a[0].call(a[1...(a.count - 1)] + a[-1].elements) },
"map": fn { |a| MalList.new(a[1].elements.map { |e| a[0].call([e]) }.toList) },
"conj": fn { |a|
if (a[0] is MalList) return MalList.new(a[-1..1] + a[0].elements)
if (a[0] is MalVector) return MalVector.new(a[0].elements + a[1..-1])
},
"seq": fn { |a|
if (a[0] == null) return null
if (a[0].count == 0) return null
if (a[0] is String) return MalList.new(a[0].toList)
if (a[0] is MalVector) return MalList.new(a[0].elements)
return a[0]
},
"meta": fn { |a| a[0].meta },
"with-meta": fn { |a|
var x = a[0].clone()
x.meta = a[1]
return x
},
"atom": fn { |a| MalAtom.new(a[0]) },
"atom?": fn { |a| a[0] is MalAtom },
"deref": fn { |a| a[0].value },
"reset!": fn { |a| a[0].value = a[1] },
"swap!": fn { |a| a[0].value = a[1].call([a[0].value] + a[2..-1]) },
"wren-eval": fn { |a| Interop.wren_eval(a[0]) }
}
}
}

40
wren/env.wren Normal file
View File

@ -0,0 +1,40 @@
import "./types" for MalList
class Env {
construct new() {
_outer = null
_data = {}
}
construct new(outer) {
_outer = outer
_data = {}
}
construct new(outer, binds, exprs) {
_outer = outer
_data = {}
for (i in 0...binds.count) {
if (binds[i].value == "&") {
_data[binds[i + 1].value] = MalList.new(exprs[i..-1])
break
} else {
_data[binds[i].value] = exprs[i]
}
}
}
set(k, v) { _data[k] = v }
find(k) {
if (_data.containsKey(k)) return this
if (_outer) return _outer.find(k)
return null
}
get(k) {
var foundEnv = find(k)
if (!foundEnv) Fiber.abort("'%(k)' not found")
return foundEnv.getValue(k)
}
getValue(k) { _data[k] }
}

23
wren/interop.wren Normal file
View File

@ -0,0 +1,23 @@
import "meta" for Meta
import "./types" for MalList, MalMap
class Interop {
static wren_eval(str) {
var f = Meta.compileExpression(str)
return f == null ? null : wren2mal(f.call())
}
static wren2mal(v) {
if (v == null || v == true || v == false) return v
if (v is Num || v is String) return v
if (v is Map) {
var m = {}
for (e in v) {
m[wren2mal(e.key)] = wren2mal(e.value)
}
return MalMap.new(m)
}
if (v is Sequence) return MalList.new(v.map { |e| wren2mal(e) }.toList)
return null
}
}

30
wren/printer.wren Normal file
View File

@ -0,0 +1,30 @@
import "./types" for MalVal, MalList, MalVector, MalMap, MalNativeFn, MalFn, MalAtom
class Printer {
static joinElements(elements, print_readably) {
return elements.map { |e| pr_str(e, print_readably) }.join(" ")
}
static joinMapElements(data, print_readably) {
return data.map { |e| pr_str(e.key, print_readably) + " " + pr_str(e.value, print_readably) }.join(" ")
}
static escape(s) {
return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") + "\""
}
static pr_str(obj) { pr_str(obj, true) }
static pr_str(obj, print_readably) {
if (obj == null) return "nil"
if (obj is MalList) return "(%(joinElements(obj.elements, print_readably)))"
if (obj is MalVector) return "[%(joinElements(obj.elements, print_readably))]"
if (obj is MalMap) return "{%(joinMapElements(obj.data, print_readably))}"
if (obj is MalNativeFn) return "#<MalNativeFn>"
if (obj is MalFn) return "#<MalFn>"
if (obj is MalAtom) return "(atom %(pr_str(obj.value, print_readably)))"
if (MalVal.isKeyword(obj)) return ":%(obj[1..-1])"
if (obj is String) return print_readably ? escape(obj) : obj
return obj.toString
}
}

170
wren/reader.wren Normal file
View File

@ -0,0 +1,170 @@
import "./types" for MalVal, MalSymbol, MalList, MalVector, MalMap
class Tokenizer {
construct new(s) {
_s = s
}
tokenize() {
_pos = 0
var tokens = []
while (true) {
var token = nextToken()
if (token == null) break
if (token.count > 0) tokens.add(token)
}
return tokens
}
static eolChars { "\r\n" }
static whitespace { " ,\r\n\t" }
static delimiters { "[]{}()'`^@" }
static separators { Tokenizer.whitespace + "[]{}()'\"`,;" }
nextToken() {
if (isEOF()) return null
var ch = curr
if (Tokenizer.whitespace.contains(ch)) {
advance()
return ""
}
if (Tokenizer.delimiters.contains(ch)) {
advance()
return ch
}
if (ch == "~") {
advance()
if (!isEOF() && curr == "@") {
advance()
return "~@"
} else {
return "~"
}
}
if (ch == ";") {
advance()
while (!isEOF() && !Tokenizer.eolChars.contains(curr)) advance()
return ""
}
if (ch == "\"") {
var s = ch
advance()
while (!isEOF() && curr != "\"") {
if (curr == "\\") {
s = s + curr
advance()
if (isEOF()) Fiber.abort("expected '\"', got EOF 111")
}
s = s + curr
advance()
}
if (isEOF()) Fiber.abort("expected '\"', got EOF 222")
s = s + curr
advance()
return s
}
var token = ch
advance()
while (!isEOF() && !Tokenizer.separators.contains(curr)) {
token = token + curr
advance()
}
return token
}
curr { _s[_pos] }
isEOF() { _pos >= _s.count }
advance() { _pos = _pos + 1 }
}
class Reader {
construct new(tokens) {
_tokens = tokens
_pos = 0
}
next() {
if (_pos >= _tokens.count) return null
var token = _tokens[_pos]
_pos = _pos + 1
return token
}
peek() {
if (_pos >= _tokens.count) return null
return _tokens[_pos]
}
}
class MalReader {
static parse_str(token) {
if (token.count <= 2) return ""
return token[1..-2].replace("\\\\", "\u029e").replace("\\\"", "\"").replace("\\n", "\n").replace("\u029e", "\\")
}
static is_all_digits(s) {
if (s.count == 0) return false
return s.all { |c| c.bytes[0] >= 0x30 && c.bytes[0] <= 0x39 }
}
static is_number(token) {
return token.startsWith("-") ? is_all_digits(token[1..-1]) : is_all_digits(token)
}
static read_atom(rdr) {
var token = rdr.next()
if (is_number(token)) return Num.fromString(token)
if (token.startsWith("\"")) return parse_str(token)
if (token.startsWith(":")) return MalVal.newKeyword(token[1..-1])
if (token == "nil") return null
if (token == "true") return true
if (token == "false") return false
return MalSymbol.new(token)
}
static read_seq(rdr, start, end) {
var token = rdr.next()
if (token != start) Fiber.abort("expected '%(start)'")
var elements = []
token = rdr.peek()
while (token != end) {
if (!token) Fiber.abort("expected '%(end)', got EOF")
elements.add(read_form(rdr))
token = rdr.peek()
}
rdr.next()
return elements
}
static reader_macro(rdr, sym) {
rdr.next()
return MalList.new([MalSymbol.new(sym), read_form(rdr)])
}
static read_form(rdr) {
var token = rdr.peek()
if (token == "'") return reader_macro(rdr, "quote")
if (token == "`") return reader_macro(rdr, "quasiquote")
if (token == "~") return reader_macro(rdr, "unquote")
if (token == "~@") return reader_macro(rdr, "splice-unquote")
if (token == "^") {
rdr.next()
var meta = read_form(rdr)
return MalList.new([MalSymbol.new("with-meta"), read_form(rdr), meta])
}
if (token == "@") return reader_macro(rdr, "deref")
if (token == "(") return MalList.new(read_seq(rdr, "(", ")"))
if (token == ")") Fiber.abort("unexpected ')'")
if (token == "[") return MalVector.new(read_seq(rdr, "[", "]"))
if (token == "]") Fiber.abort("unexpected ']'")
if (token == "{") return MalMap.fromList(read_seq(rdr, "{", "}"))
if (token == "}") Fiber.abort("unexpected '}'")
return read_atom(rdr)
}
static read_str(s) {
var tokens = Tokenizer.new(s).tokenize()
if (tokens.count == 0) return null
return read_form(Reader.new(tokens))
}
}

14
wren/readline.wren Normal file
View File

@ -0,0 +1,14 @@
import "io" for Stdin, Stdout
class Readline {
static readLine(prompt) {
var line = null
var fiber = Fiber.new {
System.write(prompt)
Stdout.flush()
line = Stdin.readLine()
}
var error = fiber.try()
return error ? null : line
}
}

2
wren/run Executable file
View File

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

30
wren/step0_repl.wren Normal file
View File

@ -0,0 +1,30 @@
import "./readline" for Readline
class Mal {
static read(str) {
return str
}
static eval(ast, env) {
return ast
}
static print(ast) {
return ast
}
static rep(str) {
return print(eval(read(str), null))
}
static main() {
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") System.print(rep(line))
}
System.print()
}
}
Mal.main()

View File

@ -0,0 +1,36 @@
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static eval(ast, env) {
return ast
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), null))
}
static main() {
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

66
wren/step2_eval.wren Normal file
View File

@ -0,0 +1,66 @@
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalList, MalVector, MalMap
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
if (!env.containsKey(ast.value)) Fiber.abort("'%(ast.value)' not found")
return env[ast.value]
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
return f.call(evaled_ast[1..-1])
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = {
"+": Fn.new { |a| a[0] + a[1] },
"-": Fn.new { |a| a[0] - a[1] },
"*": Fn.new { |a| a[0] * a[1] },
"/": Fn.new { |a| a[0] / a[1] }
}
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

78
wren/step3_env.wren Normal file
View File

@ -0,0 +1,78 @@
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalList, MalVector, MalMap
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
return eval(ast[2], letEnv)
}
}
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
return f.call(evaled_ast[1..-1])
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
__repl_env.set("+", Fn.new { |a| a[0] + a[1] })
__repl_env.set("-", Fn.new { |a| a[0] - a[1] })
__repl_env.set("*", Fn.new { |a| a[0] * a[1] })
__repl_env.set("/", Fn.new { |a| a[0] / a[1] })
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

90
wren/step4_if_fn_do.wren Normal file
View File

@ -0,0 +1,90 @@
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalList, MalVector, MalMap
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
return eval(ast[2], letEnv)
} else if (ast[0].value == "do") {
return eval_ast(ast.rest, env)[-1]
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
return eval(ast[2], env)
} else {
return ast.count > 3 ? eval(ast[3], env) : null
}
} else if (ast[0].value == "fn*") {
return Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) }
}
}
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
return f.call(evaled_ast[1..-1])
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))")
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

112
wren/step5_tco.wren Normal file
View File

@ -0,0 +1,112 @@
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalList, MalVector, MalMap, MalNativeFn, MalFn
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
while (true) {
var tco = false
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
ast = ast[2]
env = letEnv
tco = true
} else if (ast[0].value == "do") {
for (i in 1...(ast.count - 1)) {
eval(ast[i], env)
}
ast = ast[-1]
tco = true
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
ast = ast[2]
} else {
if (ast.count <= 3) return null
ast = ast[3]
}
tco = true
} else if (ast[0].value == "fn*") {
return MalFn.new(ast[2], ast[1].elements, env,
Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) })
}
}
if (!tco) {
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
if (f is MalNativeFn) {
return f.call(evaled_ast[1..-1])
} else if (f is MalFn) {
ast = f.ast
env = Env.new(f.env, f.params, evaled_ast[1..-1])
tco = true
} else {
Fiber.abort("unknown function type")
}
}
}
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))")
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

122
wren/step6_file.wren Normal file
View File

@ -0,0 +1,122 @@
import "os" for Process
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalList, MalVector, MalMap, MalNativeFn, MalFn
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
while (true) {
var tco = false
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
ast = ast[2]
env = letEnv
tco = true
} else if (ast[0].value == "do") {
for (i in 1...(ast.count - 1)) {
eval(ast[i], env)
}
ast = ast[-1]
tco = true
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
ast = ast[2]
} else {
if (ast.count <= 3) return null
ast = ast[3]
}
tco = true
} else if (ast[0].value == "fn*") {
return MalFn.new(ast[2], ast[1].elements, env,
Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) })
}
}
if (!tco) {
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
if (f is MalNativeFn) {
return f.call(evaled_ast[1..-1])
} else if (f is MalFn) {
ast = f.ast
env = Env.new(f.env, f.params, evaled_ast[1..-1])
tco = true
} else {
Fiber.abort("unknown function type")
}
}
}
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
__repl_env.set("eval", MalNativeFn.new { |a| eval(a[0], __repl_env) })
__repl_env.set("*ARGV*", MalList.new(Process.arguments.count > 0 ? Process.arguments[1..-1] : []))
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
if (Process.arguments.count > 0) {
rep("(load-file \"%(Process.arguments[0])\")")
return
}
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

141
wren/step7_quote.wren Normal file
View File

@ -0,0 +1,141 @@
import "os" for Process
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalSequential, MalList, MalVector, MalMap, MalNativeFn, MalFn
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static isPair(x) { x is MalSequential && !x.isEmpty }
static quasiquote(ast) {
if (!isPair(ast)) {
return MalList.new([MalSymbol.new("quote"), ast])
} else if (ast[0] is MalSymbol && ast[0].value == "unquote") {
return ast[1]
} else if (isPair(ast[0]) && ast[0][0] is MalSymbol && ast[0][0].value == "splice-unquote") {
return MalList.new([MalSymbol.new("concat"), ast[0][1], quasiquote(ast.rest)])
} else {
return MalList.new([MalSymbol.new("cons"), quasiquote(ast[0]), quasiquote(ast.rest)])
}
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
while (true) {
var tco = false
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
ast = ast[2]
env = letEnv
tco = true
} else if (ast[0].value == "quote") {
return ast[1]
} else if (ast[0].value == "quasiquote") {
ast = quasiquote(ast[1])
tco = true
} else if (ast[0].value == "do") {
for (i in 1...(ast.count - 1)) {
eval(ast[i], env)
}
ast = ast[-1]
tco = true
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
ast = ast[2]
} else {
if (ast.count <= 3) return null
ast = ast[3]
}
tco = true
} else if (ast[0].value == "fn*") {
return MalFn.new(ast[2], ast[1].elements, env,
Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) })
}
}
if (!tco) {
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
if (f is MalNativeFn) {
return f.call(evaled_ast[1..-1])
} else if (f is MalFn) {
ast = f.ast
env = Env.new(f.env, f.params, evaled_ast[1..-1])
tco = true
} else {
Fiber.abort("unknown function type")
}
}
}
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
__repl_env.set("eval", MalNativeFn.new { |a| eval(a[0], __repl_env) })
__repl_env.set("*ARGV*", MalList.new(Process.arguments.count > 0 ? Process.arguments[1..-1] : []))
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
if (Process.arguments.count > 0) {
rep("(load-file \"%(Process.arguments[0])\")")
return
}
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

165
wren/step8_macros.wren Normal file
View File

@ -0,0 +1,165 @@
import "os" for Process
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalSequential, MalList, MalVector, MalMap, MalNativeFn, MalFn
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static isPair(x) { x is MalSequential && !x.isEmpty }
static quasiquote(ast) {
if (!isPair(ast)) {
return MalList.new([MalSymbol.new("quote"), ast])
} else if (ast[0] is MalSymbol && ast[0].value == "unquote") {
return ast[1]
} else if (isPair(ast[0]) && ast[0][0] is MalSymbol && ast[0][0].value == "splice-unquote") {
return MalList.new([MalSymbol.new("concat"), ast[0][1], quasiquote(ast.rest)])
} else {
return MalList.new([MalSymbol.new("cons"), quasiquote(ast[0]), quasiquote(ast.rest)])
}
}
static isMacro(ast, env) {
return (ast is MalList &&
!ast.isEmpty &&
ast[0] is MalSymbol &&
env.find(ast[0].value) &&
env.get(ast[0].value) is MalFn &&
env.get(ast[0].value).isMacro)
}
static macroexpand(ast, env) {
while (isMacro(ast, env)) {
var macro = env.get(ast[0].value)
ast = macro.call(ast.elements[1..-1])
}
return ast
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
while (true) {
var tco = false
if (!(ast is MalList)) return eval_ast(ast, env)
ast = macroexpand(ast, env)
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
ast = ast[2]
env = letEnv
tco = true
} else if (ast[0].value == "quote") {
return ast[1]
} else if (ast[0].value == "quasiquote") {
ast = quasiquote(ast[1])
tco = true
} else if (ast[0].value == "defmacro!") {
return env.set(ast[1].value, eval(ast[2], env).makeMacro())
} else if (ast[0].value == "macroexpand") {
return macroexpand(ast[1], env)
} else if (ast[0].value == "do") {
for (i in 1...(ast.count - 1)) {
eval(ast[i], env)
}
ast = ast[-1]
tco = true
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
ast = ast[2]
} else {
if (ast.count <= 3) return null
ast = ast[3]
}
tco = true
} else if (ast[0].value == "fn*") {
return MalFn.new(ast[2], ast[1].elements, env,
Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) })
}
}
if (!tco) {
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
if (f is MalNativeFn) {
return f.call(evaled_ast[1..-1])
} else if (f is MalFn) {
ast = f.ast
env = Env.new(f.env, f.params, evaled_ast[1..-1])
tco = true
} else {
Fiber.abort("unknown function type")
}
}
}
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
__repl_env.set("eval", MalNativeFn.new { |a| eval(a[0], __repl_env) })
__repl_env.set("*ARGV*", MalList.new(Process.arguments.count > 0 ? Process.arguments[1..-1] : []))
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
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)))))))")
if (Process.arguments.count > 0) {
rep("(load-file \"%(Process.arguments[0])\")")
return
}
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
if (fiber.error) System.print("Error: %(fiber.error)")
}
}
System.print()
}
}
Mal.main()

186
wren/step9_try.wren Normal file
View File

@ -0,0 +1,186 @@
import "os" for Process
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalSequential, MalList, MalVector, MalMap, MalNativeFn, MalFn, MalException
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static isPair(x) { x is MalSequential && !x.isEmpty }
static quasiquote(ast) {
if (!isPair(ast)) {
return MalList.new([MalSymbol.new("quote"), ast])
} else if (ast[0] is MalSymbol && ast[0].value == "unquote") {
return ast[1]
} else if (isPair(ast[0]) && ast[0][0] is MalSymbol && ast[0][0].value == "splice-unquote") {
return MalList.new([MalSymbol.new("concat"), ast[0][1], quasiquote(ast.rest)])
} else {
return MalList.new([MalSymbol.new("cons"), quasiquote(ast[0]), quasiquote(ast.rest)])
}
}
static isMacro(ast, env) {
return (ast is MalList &&
!ast.isEmpty &&
ast[0] is MalSymbol &&
env.find(ast[0].value) &&
env.get(ast[0].value) is MalFn &&
env.get(ast[0].value).isMacro)
}
static macroexpand(ast, env) {
while (isMacro(ast, env)) {
var macro = env.get(ast[0].value)
ast = macro.call(ast.elements[1..-1])
}
return ast
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
while (true) {
var tco = false
if (!(ast is MalList)) return eval_ast(ast, env)
ast = macroexpand(ast, env)
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
ast = ast[2]
env = letEnv
tco = true
} else if (ast[0].value == "quote") {
return ast[1]
} else if (ast[0].value == "quasiquote") {
ast = quasiquote(ast[1])
tco = true
} else if (ast[0].value == "defmacro!") {
return env.set(ast[1].value, eval(ast[2], env).makeMacro())
} else if (ast[0].value == "macroexpand") {
return macroexpand(ast[1], env)
} else if (ast[0].value == "try*") {
if (ast.count > 2 && ast[2][0] is MalSymbol && ast[2][0].value == "catch*") {
var fiber = Fiber.new { eval(ast[1], env) }
var result = fiber.try()
var error = fiber.error
if (!error) return result
if (error == "___MalException___") {
error = MalException.value
MalException.set(null)
}
return eval(ast[2][2], Env.new(env, [ast[2][1]], [error]))
} else {
return eval(ast[1], env)
}
} else if (ast[0].value == "do") {
for (i in 1...(ast.count - 1)) {
eval(ast[i], env)
}
ast = ast[-1]
tco = true
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
ast = ast[2]
} else {
if (ast.count <= 3) return null
ast = ast[3]
}
tco = true
} else if (ast[0].value == "fn*") {
return MalFn.new(ast[2], ast[1].elements, env,
Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) })
}
}
if (!tco) {
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
if (f is MalNativeFn) {
return f.call(evaled_ast[1..-1])
} else if (f is MalFn) {
ast = f.ast
env = Env.new(f.env, f.params, evaled_ast[1..-1])
tco = true
} else {
Fiber.abort("unknown function type")
}
}
}
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
__repl_env.set("eval", MalNativeFn.new { |a| eval(a[0], __repl_env) })
__repl_env.set("*ARGV*", MalList.new(Process.arguments.count > 0 ? Process.arguments[1..-1] : []))
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
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)))))))")
if (Process.arguments.count > 0) {
rep("(load-file \"%(Process.arguments[0])\")")
return
}
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
var error = fiber.error
if (error) {
if (error == "___MalException___") {
error = Printer.pr_str(MalException.value, false)
MalException.set(null)
}
System.print("Error: %(error)")
}
}
}
System.print()
}
}
Mal.main()

188
wren/stepA_mal.wren Normal file
View File

@ -0,0 +1,188 @@
import "os" for Process
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalSequential, MalList, MalVector, MalMap, MalNativeFn, MalFn, MalException
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static isPair(x) { x is MalSequential && !x.isEmpty }
static quasiquote(ast) {
if (!isPair(ast)) {
return MalList.new([MalSymbol.new("quote"), ast])
} else if (ast[0] is MalSymbol && ast[0].value == "unquote") {
return ast[1]
} else if (isPair(ast[0]) && ast[0][0] is MalSymbol && ast[0][0].value == "splice-unquote") {
return MalList.new([MalSymbol.new("concat"), ast[0][1], quasiquote(ast.rest)])
} else {
return MalList.new([MalSymbol.new("cons"), quasiquote(ast[0]), quasiquote(ast.rest)])
}
}
static isMacro(ast, env) {
return (ast is MalList &&
!ast.isEmpty &&
ast[0] is MalSymbol &&
env.find(ast[0].value) &&
env.get(ast[0].value) is MalFn &&
env.get(ast[0].value).isMacro)
}
static macroexpand(ast, env) {
while (isMacro(ast, env)) {
var macro = env.get(ast[0].value)
ast = macro.call(ast.elements[1..-1])
}
return ast
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
while (true) {
var tco = false
if (!(ast is MalList)) return eval_ast(ast, env)
ast = macroexpand(ast, env)
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
ast = ast[2]
env = letEnv
tco = true
} else if (ast[0].value == "quote") {
return ast[1]
} else if (ast[0].value == "quasiquote") {
ast = quasiquote(ast[1])
tco = true
} else if (ast[0].value == "defmacro!") {
return env.set(ast[1].value, eval(ast[2], env).makeMacro())
} else if (ast[0].value == "macroexpand") {
return macroexpand(ast[1], env)
} else if (ast[0].value == "try*") {
if (ast.count > 2 && ast[2][0] is MalSymbol && ast[2][0].value == "catch*") {
var fiber = Fiber.new { eval(ast[1], env) }
var result = fiber.try()
var error = fiber.error
if (!error) return result
if (error == "___MalException___") {
error = MalException.value
MalException.set(null)
}
return eval(ast[2][2], Env.new(env, [ast[2][1]], [error]))
} else {
return eval(ast[1], env)
}
} else if (ast[0].value == "do") {
for (i in 1...(ast.count - 1)) {
eval(ast[i], env)
}
ast = ast[-1]
tco = true
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
ast = ast[2]
} else {
if (ast.count <= 3) return null
ast = ast[3]
}
tco = true
} else if (ast[0].value == "fn*") {
return MalFn.new(ast[2], ast[1].elements, env,
Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) })
}
}
if (!tco) {
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
if (f is MalNativeFn) {
return f.call(evaled_ast[1..-1])
} else if (f is MalFn) {
ast = f.ast
env = Env.new(f.env, f.params, evaled_ast[1..-1])
tco = true
} else {
Fiber.abort("unknown function type")
}
}
}
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
__repl_env.set("eval", MalNativeFn.new { |a| eval(a[0], __repl_env) })
__repl_env.set("*ARGV*", MalList.new(Process.arguments.count > 0 ? Process.arguments[1..-1] : []))
// core.mal: defined using the language itself
rep("(def! *host-language* \"wren\")")
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
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)))))))")
if (Process.arguments.count > 0) {
rep("(load-file \"%(Process.arguments[0])\")")
return
}
rep("(println (str \"Mal [\" *host-language* \"]\"))")
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
var error = fiber.error
if (error) {
if (error == "___MalException___") {
error = Printer.pr_str(MalException.value, false)
MalException.set(null)
}
System.print("Error: %(error)")
}
}
}
System.print()
}
}
Mal.main()

2
wren/tests/step5_tco.mal Normal file
View File

@ -0,0 +1,2 @@
;; Wren: skipping non-TCO recursion
;; Reason: completes up to 1,000,000 (with extended timeout)

34
wren/tests/stepA_mal.mal Normal file
View File

@ -0,0 +1,34 @@
;; Testing basic Wren interop
;;; wren-eval evaluates the given string as an expression.
(wren-eval "7")
;=>7
(wren-eval "0x41")
;=>65
(wren-eval "\"7\"")
;=>"7"
(wren-eval "[ 7,8,9 ]")
;=>(7 8 9)
(wren-eval "{ \"abc\": 789 }")
;=>{"abc" 789}
(wren-eval "System.print(\"hello\")")
;/hello
;=>"hello"
(wren-eval "[\"a\", \"b\", \"c\"].map { |x| \"X%(x)Y\" }.join(\" \")")
;=>"XaY XbY XcY"
(wren-eval "[1,2,3].map { |x| 1 + x }")
;=>(2 3 4)
(wren-eval "[null, (1 == 1), (1 == 2)]")
;=>(nil true false)
(wren-eval "Fiber.abort(\"AAA\" + \"BBB\")")
;/Error: AAABBB

130
wren/types.wren Normal file
View File

@ -0,0 +1,130 @@
class MalVal {
static newKeyword(value) { "\u029e%(value)" }
static isKeyword(obj) { obj is String && obj.count > 0 && obj[0] == "\u029e" }
meta { _meta }
meta=(value) { _meta = value }
}
class MalSymbol is MalVal {
construct new(value) { _value = value }
value { _value }
toString { _value }
==(other) { other is MalSymbol && other.value == _value }
!=(other) { !(this == other) }
}
class MalSequential is MalVal {
construct new(elements) { _elements = elements }
elements { _elements }
[index] { _elements[index] }
isEmpty { _elements.count == 0 }
count { _elements.count }
first { isEmpty ? null : _elements[0] }
rest { MalList.new(isEmpty ? [] : elements[1..-1]) }
==(other) {
if (!(other is MalSequential)) return false
if (other.count != count) return false
for (i in 0...count) {
if (other[i] != this[i]) return false
}
return true
}
!=(other) { !(this == other) }
}
class MalList is MalSequential {
construct new(elements) { super(elements) }
clone() { MalList.new(elements) }
}
class MalVector is MalSequential {
construct new(elements) { super(elements) }
clone() { MalVector.new(elements) }
}
class MalMap is MalVal {
construct new(data) { _data = data }
construct fromList(elements) {
_data = {}
var i = 0
while (i < elements.count) {
_data[elements[i]] = elements[i + 1]
i = i + 2
}
}
clone() { MalMap.new(_data) }
data { _data }
assoc(pairsList) {
var newData = {}
for (e in _data) {
newData[e.key] = e.value
}
var i = 0
while (i < pairsList.count) {
newData[pairsList[i]] = pairsList[i + 1]
i = i + 2
}
return MalMap.new(newData)
}
dissoc(keysList) {
var newData = {}
for (e in _data) {
newData[e.key] = e.value
}
for (k in keysList) {
newData.remove(k)
}
return MalMap.new(newData)
}
==(other) {
if (!(other is MalMap)) return false
if (other.data.count != data.count) return false
for (e in _data) {
if (other.data[e.key] != e.value) return false
}
return true
}
!=(other) { !(this == other) }
}
class MalNativeFn is MalVal {
construct new(fn) { _fn = fn }
call(args) { _fn.call(args) }
clone() { MalNativeFn.new(_fn) }
}
class MalFn is MalVal {
construct new(ast, params, env, fn) {
_ast = ast
_params = params
_env = env
_fn = fn
_isMacro = false
}
construct new(ast, params, env, fn, isMacro) {
_ast = ast
_params = params
_env = env
_fn = fn
_isMacro = isMacro
}
ast { _ast }
params { _params }
env { _env }
isMacro { _isMacro }
clone() { MalFn.new(_ast, _params, _env, _fn, _isMacro) }
makeMacro() { MalFn.new(_ast, _params, _env, _fn, true) }
call(args) { _fn.call(args) }
}
class MalAtom is MalVal {
construct new(value) { _value = value }
value { _value }
value=(other) { _value = other }
clone() { MalAtom.new(value) }
}
class MalException {
static value { __exception }
static set(exception) { __exception = exception }
}

View File

@ -0,0 +1,34 @@
diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c
index 34a13c8b..3c4e6ab8 100644
--- a/src/vm/wren_core.c
+++ b/src/vm/wren_core.c
@@ -4,6 +4,7 @@
#include <math.h>
#include <string.h>
#include <time.h>
+#include <sys/time.h>
#include "wren_common.h"
#include "wren_core.h"
@@ -1121,6 +1122,13 @@ DEF_PRIMITIVE(string_toString)
RETURN_VAL(args[0]);
}
+DEF_PRIMITIVE(system_gettimeofday)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ RETURN_NUM((double)tv.tv_sec + (double)tv.tv_usec/1000000.0);
+}
+
DEF_PRIMITIVE(system_clock)
{
RETURN_NUM((double)clock() / CLOCKS_PER_SEC);
@@ -1374,6 +1382,7 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->rangeClass, "toString", range_toString);
ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System"));
+ PRIMITIVE(systemClass->obj.classObj, "gettimeofday", system_gettimeofday);
PRIMITIVE(systemClass->obj.classObj, "clock", system_clock);
PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc);
PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString);