mirror of
https://github.com/kanaka/mal.git
synced 2024-09-11 13:55:55 +03:00
Implemented Kotlin MAL up to step 4
This commit is contained in:
parent
eb76b9fbce
commit
53c2ea70cf
4
.gitignore
vendored
4
.gitignore
vendored
@ -64,4 +64,6 @@ elixir/_build
|
||||
elixir/deps
|
||||
elixir/erl_crash.dump
|
||||
elixir/*.ez
|
||||
|
||||
kotlin/*.jar
|
||||
kotlin/.idea
|
||||
kotlin/*.iml
|
||||
|
4
Makefile
4
Makefile
@ -17,7 +17,7 @@ mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
|
||||
#
|
||||
|
||||
IMPLS = awk bash c clojure coffee cpp crystal cs erlang elixir es6 factor forth fsharp go groovy \
|
||||
haskell java julia js lua make mal ocaml matlab miniMAL nim \
|
||||
haskell java julia js kotlin lua make mal ocaml matlab miniMAL nim \
|
||||
perl php ps python r racket rpython ruby rust scala swift vb vimscript guile
|
||||
|
||||
step0 = step0_repl
|
||||
@ -83,6 +83,7 @@ java_STEP_TO_PROG = java/src/main/java/mal/$($(1)).java
|
||||
haskell_STEP_TO_PROG = haskell/$($(1))
|
||||
julia_STEP_TO_PROG = julia/$($(1)).jl
|
||||
js_STEP_TO_PROG = js/$($(1)).js
|
||||
kotlin_STEP_TO_PROG = kotlin/$($(1)).jar
|
||||
lua_STEP_TO_PROG = lua/$($(1)).lua
|
||||
make_STEP_TO_PROG = make/$($(1)).mk
|
||||
mal_STEP_TO_PROG = mal/$($(1)).mal
|
||||
@ -131,6 +132,7 @@ haskell_RUNSTEP = ../$(2) $(3)
|
||||
java_RUNSTEP = mvn -quiet exec:java -Dexec.mainClass="mal.$($(1))" $(if $(3), -Dexec.args="$(3)",)
|
||||
julia_RUNSTEP = ../$(2) $(3)
|
||||
js_RUNSTEP = node ../$(2) $(3)
|
||||
kotlin_RUNSTEP = java -jar ../$(2) $(3)
|
||||
lua_RUNSTEP = ../$(2) $(3)
|
||||
make_RUNSTEP = make -f ../$(2) $(3)
|
||||
mal_RUNSTEP = $(call $(MAL_IMPL)_RUNSTEP,stepA,$(call $(MAL_IMPL)_STEP_TO_PROG,stepA),../$(2),") #"
|
||||
|
13
README.md
13
README.md
@ -29,6 +29,7 @@ Mal is implemented in 41 different languages:
|
||||
* Java
|
||||
* JavaScript ([Online Demo](http://kanaka.github.io/mal))
|
||||
* Julia
|
||||
* Kotlin
|
||||
* Lua
|
||||
* GNU Make
|
||||
* mal itself
|
||||
@ -324,6 +325,18 @@ cd julia
|
||||
julia stepX_YYY.jl
|
||||
```
|
||||
|
||||
### Kotlin
|
||||
|
||||
*The Kotlin implementation was created by [Javier Fernandez-Ivern](https://github.com/ivern)*
|
||||
|
||||
The Kotlin implementation of mal has been tested with Kotlin M14.
|
||||
|
||||
```
|
||||
cd kotlin
|
||||
make
|
||||
java -jar stepX_YYY.jar
|
||||
```
|
||||
|
||||
### Lua
|
||||
|
||||
Running the Lua implementation of mal requires lua 5.1 or later,
|
||||
|
12
kotlin/Makefile
Normal file
12
kotlin/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
SOURCES = reader.kt printer.kt types.kt env.kt core.kt readline.kt
|
||||
|
||||
SRCS = step0_repl.kt step1_read_print.kt step2_eval.kt step3_env.kt step4_if_fn_do.kt step5_tco.kt
|
||||
JARS = $(SRCS:%.kt=%.jar)
|
||||
|
||||
all: $(JARS)
|
||||
|
||||
clean:
|
||||
rm -vf $(JARS)
|
||||
|
||||
$(JARS): %.jar: src/mal/%.kt $(SOURCES:%.kt=src/mal/%.kt)
|
||||
kotlinc src/mal/$(@:%.jar=%.kt) $(SOURCES:%.kt=src/mal/%.kt) -include-runtime -d $@
|
44
kotlin/src/mal/core.kt
Normal file
44
kotlin/src/mal/core.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package mal
|
||||
|
||||
val ns = hashMapOf(
|
||||
Pair(MalSymbol("+"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger + y as MalInteger }) })),
|
||||
Pair(MalSymbol("-"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger - y as MalInteger }) })),
|
||||
Pair(MalSymbol("*"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger * y as MalInteger }) })),
|
||||
Pair(MalSymbol("/"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger / y as MalInteger }) })),
|
||||
|
||||
Pair(MalSymbol("list"), MalFunction({ a: ISeq -> MalList(a) })),
|
||||
Pair(MalSymbol("list?"), MalFunction({ a: ISeq -> if (a.first() is MalList) TRUE else FALSE })),
|
||||
Pair(MalSymbol("empty?"), MalFunction({
|
||||
a: ISeq -> if (a.first() !is ISeq || !(a.first() as ISeq).seq().any()) TRUE else FALSE
|
||||
})),
|
||||
Pair(MalSymbol("count"), MalFunction({
|
||||
a: ISeq -> if (a.first() is ISeq) MalInteger((a.first() as ISeq).seq().count()) else MalInteger(0)
|
||||
})),
|
||||
|
||||
Pair(MalSymbol("="), MalFunction({ a: ISeq -> pairwiseEquals(a) })),
|
||||
Pair(MalSymbol("<"), MalFunction({ a: ISeq -> pairwise(a, { x, y -> x.value < y.value }) })),
|
||||
Pair(MalSymbol("<="), MalFunction({ a: ISeq -> pairwise(a, { x, y -> x.value <= y.value }) })),
|
||||
Pair(MalSymbol(">"), MalFunction({ a: ISeq -> pairwise(a, { x, y -> x.value > y.value }) })),
|
||||
Pair(MalSymbol(">="), MalFunction({ a: ISeq -> pairwise(a, { x, y -> x.value >= y.value }) })),
|
||||
|
||||
Pair(MalSymbol("pr-str"), MalFunction({
|
||||
a: ISeq -> MalString(a.seq().map({ it -> pr_str(it, print_readably = true) }).joinToString(" "))
|
||||
})),
|
||||
Pair(MalSymbol("str"), MalFunction({
|
||||
a: ISeq -> MalString(a.seq().map({ it -> pr_str(it, print_readably = false) }).joinToString(""))
|
||||
})),
|
||||
Pair(MalSymbol("prn"), MalFunction({
|
||||
a: ISeq -> println(a.seq().map({ it -> pr_str(it, print_readably = true) }).joinToString(" ")); NIL
|
||||
})),
|
||||
Pair(MalSymbol("println"), MalFunction({
|
||||
a: ISeq -> println(a.seq().map({ it -> pr_str(it, print_readably = false) }).joinToString(" ")); NIL
|
||||
}))
|
||||
)
|
||||
|
||||
fun pairwiseEquals(s: ISeq): MalConstant =
|
||||
if (s.seq().zip(s.seq().drop(1)).all({ it -> it.first == it.second })) TRUE else FALSE
|
||||
|
||||
fun pairwise(s: ISeq, pred: (MalInteger, MalInteger) -> Boolean): MalConstant =
|
||||
if (s.seq().zip(s.seq().drop(1)).all({
|
||||
it -> pred(it.first as MalInteger, it.second as MalInteger)
|
||||
})) TRUE else FALSE
|
37
kotlin/src/mal/env.kt
Normal file
37
kotlin/src/mal/env.kt
Normal file
@ -0,0 +1,37 @@
|
||||
package mal
|
||||
|
||||
import java.util.*
|
||||
|
||||
class Env(val outer: Env?, binds: Sequence<MalSymbol>?, exprs: Sequence<MalType>?) {
|
||||
val data = HashMap<String, MalType>()
|
||||
|
||||
init {
|
||||
// TODO idiomatic?
|
||||
if (binds != null && exprs != null) {
|
||||
val itb = binds.iterator()
|
||||
val ite = exprs.iterator()
|
||||
while (itb.hasNext()) {
|
||||
val b = itb.next()
|
||||
if (b.value != "&") {
|
||||
set(b, if (ite.hasNext()) ite.next() else NIL)
|
||||
} else {
|
||||
if (!itb.hasNext()) throw MalException("expected a symbol name for varargs")
|
||||
set(itb.next(), MalList(ite.asSequence().toLinkedList()))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() : this(null, null, null)
|
||||
constructor(outer: Env?) : this(outer, null, null)
|
||||
|
||||
fun set(key: MalSymbol, value: MalType): MalType {
|
||||
data.put(key.value, value)
|
||||
return value
|
||||
}
|
||||
|
||||
fun find(key: MalSymbol): MalType? = data.getOrElse(key.value) { outer?.find(key) }
|
||||
|
||||
fun get(key: MalSymbol): MalType = find(key) ?: throw MalException("'${key.value}' not found")
|
||||
}
|
30
kotlin/src/mal/printer.kt
Normal file
30
kotlin/src/mal/printer.kt
Normal file
@ -0,0 +1,30 @@
|
||||
package mal
|
||||
|
||||
fun pr_str(malType: MalType, print_readably: Boolean = false): String =
|
||||
if (malType is MalInteger) {
|
||||
malType.value.toString()
|
||||
} else if (malType is MalKeyword) {
|
||||
":" + malType.value.substring(1)
|
||||
} else if (malType is MalString) {
|
||||
if (print_readably) {
|
||||
"\"" + malType.value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""
|
||||
} else malType.value
|
||||
} else if (malType is MalConstant) {
|
||||
malType.value
|
||||
} else if (malType is MalFunction) {
|
||||
"#" + malType
|
||||
} else if (malType is MalList) {
|
||||
pr_str(malType.elements, "(", ")", print_readably)
|
||||
} else if (malType is MalVector) {
|
||||
pr_str(malType.elements, "[", "]", print_readably)
|
||||
} else if (malType is MalHashMap) {
|
||||
malType.elements.map({ it -> pr_str(it, print_readably) }).joinToString(" ", "{", "}")
|
||||
} else {
|
||||
throw MalPrinterException("Unrecognized MalType: " + malType)
|
||||
}
|
||||
|
||||
private fun pr_str(coll: Collection<MalType>, start: String, end: String, print_readably: Boolean = false): String =
|
||||
coll.map({ it -> pr_str(it, print_readably) }).joinToString(" ", start, end)
|
||||
|
||||
private fun pr_str(mapEntry: Map.Entry<MalString, MalType>, print_readably: Boolean = false): String =
|
||||
pr_str(mapEntry.key, print_readably) + " " + pr_str(mapEntry.value, print_readably)
|
120
kotlin/src/mal/reader.kt
Normal file
120
kotlin/src/mal/reader.kt
Normal file
@ -0,0 +1,120 @@
|
||||
package mal
|
||||
|
||||
import kotlin.text.Regex
|
||||
|
||||
val TOKEN_REGEX = Regex("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)")
|
||||
val ATOM_REGEX = Regex("(^-?[0-9]+$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)")
|
||||
|
||||
class Reader(sequence: Sequence<String>) {
|
||||
val tokens = sequence.iterator()
|
||||
var current = advance()
|
||||
|
||||
fun next(): String? {
|
||||
var result = current
|
||||
current = advance()
|
||||
return result
|
||||
}
|
||||
|
||||
fun peek(): String? = current
|
||||
|
||||
private fun advance(): String? = if (tokens.hasNext()) tokens.next() else null
|
||||
}
|
||||
|
||||
fun read_str(input: String?): MalType {
|
||||
val tokens = tokenizer(input) ?: return NIL
|
||||
return read_form(Reader(tokens))
|
||||
}
|
||||
|
||||
fun tokenizer(input: String?): Sequence<String>? {
|
||||
if (input == null) return null
|
||||
|
||||
return TOKEN_REGEX.findAll(input)
|
||||
.map({ it -> it.groups[1]?.value as String })
|
||||
.filter({ it != "" && !it.startsWith(";")})
|
||||
}
|
||||
|
||||
fun read_form(reader: Reader): MalType =
|
||||
when (reader.peek()) {
|
||||
null -> throw MalContinue()
|
||||
"(" -> read_list(reader)
|
||||
")" -> throw MalReaderException("expected form, got ')'")
|
||||
"[" -> read_vector(reader)
|
||||
"]" -> throw MalReaderException("expected form, got ']'")
|
||||
"{" -> read_hashmap(reader)
|
||||
"}" -> throw MalReaderException("expected form, got '}'")
|
||||
else -> read_atom(reader)
|
||||
}
|
||||
|
||||
fun read_list(reader: Reader): MalType = read_sequence(reader, MalList(), ")")
|
||||
fun read_vector(reader: Reader): MalType = read_sequence(reader, MalVector(), "]")
|
||||
|
||||
private fun read_sequence(reader: Reader, sequence: IMutableSeq, end: String): MalType {
|
||||
reader.next()
|
||||
|
||||
do {
|
||||
val form = when (reader.peek()) {
|
||||
null -> throw MalReaderException("expected '$end', got EOF")
|
||||
end -> { reader.next(); null }
|
||||
else -> read_form(reader)
|
||||
}
|
||||
|
||||
if (form != null) {
|
||||
sequence.conj_BANG(form)
|
||||
}
|
||||
} while (form != null)
|
||||
|
||||
return sequence
|
||||
}
|
||||
|
||||
fun read_hashmap(reader: Reader): MalType {
|
||||
reader.next()
|
||||
val hashMap = MalHashMap()
|
||||
|
||||
do {
|
||||
var value : MalType? = null;
|
||||
val key = when (reader.peek()) {
|
||||
null -> throw MalReaderException("expected '}', got EOF")
|
||||
"}" -> { reader.next(); null }
|
||||
else -> {
|
||||
var key = read_form(reader)
|
||||
if (key !is MalString) {
|
||||
throw MalReaderException("hash-map keys must be strings or keywords")
|
||||
}
|
||||
value = when (reader.peek()) {
|
||||
null -> throw MalReaderException("expected form, got EOF")
|
||||
else -> read_form(reader)
|
||||
}
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
hashMap.assoc_BANG(key as MalString, value as MalType)
|
||||
}
|
||||
} while (key != null)
|
||||
|
||||
return hashMap
|
||||
}
|
||||
|
||||
fun read_atom(reader: Reader): MalType {
|
||||
val next = reader.next() ?: throw MalReaderException("Unexpected null token")
|
||||
val groups = ATOM_REGEX.find(next)?.groups ?: throw MalReaderException("Unrecognized token: " + next)
|
||||
|
||||
return if (groups[1]?.value != null) {
|
||||
MalInteger(Integer.valueOf(groups[1]?.value))
|
||||
} else if (groups[2]?.value != null) {
|
||||
NIL
|
||||
} else if (groups[3]?.value != null) {
|
||||
TRUE
|
||||
} else if (groups[4]?.value != null) {
|
||||
FALSE
|
||||
} else if (groups[5]?.value != null) {
|
||||
MalString((groups[5]?.value as String).replace("\\n", "\n").replace("\\\"", "\""))
|
||||
} else if (groups[6]?.value != null) {
|
||||
MalKeyword(groups[6]?.value as String)
|
||||
} else if (groups[7]?.value != null) {
|
||||
MalSymbol(groups[7]?.value as String)
|
||||
} else {
|
||||
throw MalReaderException("Unrecognized token: " + next)
|
||||
}
|
||||
}
|
8
kotlin/src/mal/readline.kt
Normal file
8
kotlin/src/mal/readline.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package mal
|
||||
|
||||
fun raw_readline(prompt: String): String? {
|
||||
print(prompt)
|
||||
return readLine()
|
||||
}
|
||||
|
||||
fun readline(prompt: String): String? = raw_readline(prompt)
|
17
kotlin/src/mal/step0_repl.kt
Normal file
17
kotlin/src/mal/step0_repl.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package mal
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
fun read(input: String?): String? = input
|
||||
fun eval(expression: String?): String? = expression
|
||||
fun print(result: String?): String? = result
|
||||
|
||||
while (true) {
|
||||
val input = readline("user> ") ?: break
|
||||
|
||||
try {
|
||||
println(print(eval(read(input))))
|
||||
} catch (t: Throwable) {
|
||||
println("Uncaught " + t + ": " + t.message)
|
||||
}
|
||||
}
|
||||
}
|
20
kotlin/src/mal/step1_read_print.kt
Normal file
20
kotlin/src/mal/step1_read_print.kt
Normal file
@ -0,0 +1,20 @@
|
||||
package mal
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
fun read(input: String?): MalType = read_str(input)
|
||||
fun eval(expression: MalType): MalType = expression
|
||||
fun print(result: MalType) = pr_str(result, print_readably = false)
|
||||
|
||||
while (true) {
|
||||
val input = readline("user> ") ?: break
|
||||
|
||||
try {
|
||||
println(print(eval(read(input))))
|
||||
} catch (e: MalContinue) {
|
||||
} catch (e: MalException) {
|
||||
println("Error: " + e.message)
|
||||
} catch (t: Throwable) {
|
||||
println("Uncaught " + t + ": " + t.message)
|
||||
}
|
||||
}
|
||||
}
|
45
kotlin/src/mal/step2_eval.kt
Normal file
45
kotlin/src/mal/step2_eval.kt
Normal file
@ -0,0 +1,45 @@
|
||||
package mal
|
||||
|
||||
fun read(input: String?): MalType = read_str(input)
|
||||
|
||||
fun eval(ast: MalType, env: Map<String, MalType>): MalType =
|
||||
if (ast is MalList) {
|
||||
val evaluated = eval_ast(ast, env) as ISeq
|
||||
if (evaluated.first() !is MalFunction) throw MalException("cannot execute non-function")
|
||||
(evaluated.first() as MalFunction).apply(evaluated.rest())
|
||||
} else eval_ast(ast, env)
|
||||
|
||||
fun eval_ast(ast: MalType, env: Map<String, MalType>): MalType =
|
||||
if (ast is MalSymbol) {
|
||||
env.get(ast.value) ?: throw MalException("'${ast.value}' not found")
|
||||
} else if (ast is MalList) {
|
||||
ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalVector) {
|
||||
ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalHashMap) {
|
||||
ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a })
|
||||
} else ast
|
||||
|
||||
fun print(result: MalType) = pr_str(result, print_readably = true)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val env = hashMapOf(
|
||||
Pair("+", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger + y as MalInteger }) })),
|
||||
Pair("-", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger - y as MalInteger }) })),
|
||||
Pair("*", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger * y as MalInteger }) })),
|
||||
Pair("/", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger / y as MalInteger }) }))
|
||||
)
|
||||
|
||||
while (true) {
|
||||
val input = readline("user> ") ?: break
|
||||
|
||||
try {
|
||||
println(print(eval(read(input), env)))
|
||||
} catch (e: MalContinue) {
|
||||
} catch (e: MalException) {
|
||||
println("Error: " + e.message)
|
||||
} catch (t: Throwable) {
|
||||
println("Uncaught " + t + ": " + t.message)
|
||||
}
|
||||
}
|
||||
}
|
61
kotlin/src/mal/step3_env.kt
Normal file
61
kotlin/src/mal/step3_env.kt
Normal file
@ -0,0 +1,61 @@
|
||||
package mal
|
||||
|
||||
fun read(input: String?): MalType = read_str(input)
|
||||
|
||||
fun eval(ast: MalType, env: Env): MalType =
|
||||
if (ast is MalList) {
|
||||
val first = ast.first()
|
||||
if (first is MalSymbol && first.value == "def!") {
|
||||
env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env))
|
||||
} else if (first is MalSymbol && first.value == "let*") {
|
||||
val child = Env(env)
|
||||
val bindings = ast.nth(1)
|
||||
if (bindings !is ISeq) throw MalException("expected sequence as the first parameter to let*")
|
||||
val it = bindings.seq().iterator()
|
||||
while (it.hasNext()) {
|
||||
val key = it.next()
|
||||
if (!it.hasNext()) throw MalException("odd number of binding elements in let*")
|
||||
val value = eval(it.next(), child)
|
||||
child.set(key as MalSymbol, value)
|
||||
}
|
||||
eval(ast.nth(2), child)
|
||||
} else {
|
||||
val evaluated = eval_ast(ast, env) as ISeq
|
||||
if (evaluated.first() !is MalFunction) throw MalException("cannot execute non-function")
|
||||
(evaluated.first() as MalFunction).apply(evaluated.rest())
|
||||
}
|
||||
} else eval_ast(ast, env)
|
||||
|
||||
fun eval_ast(ast: MalType, env: Env): MalType =
|
||||
if (ast is MalSymbol) {
|
||||
env.get(ast)
|
||||
} else if (ast is MalList) {
|
||||
ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalVector) {
|
||||
ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalHashMap) {
|
||||
ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a })
|
||||
} else ast
|
||||
|
||||
fun print(result: MalType) = pr_str(result, print_readably = true)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val env = Env()
|
||||
env.set(MalSymbol("+"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger + y as MalInteger }) }))
|
||||
env.set(MalSymbol("-"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger - y as MalInteger }) }))
|
||||
env.set(MalSymbol("*"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger * y as MalInteger }) }))
|
||||
env.set(MalSymbol("/"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger / y as MalInteger }) }))
|
||||
|
||||
while (true) {
|
||||
val input = readline("user> ") ?: break
|
||||
|
||||
try {
|
||||
println(print(eval(read(input), env)))
|
||||
} catch (e: MalContinue) {
|
||||
} catch (e: MalException) {
|
||||
println("Error: " + e.message)
|
||||
} catch (t: Throwable) {
|
||||
println("Uncaught " + t + ": " + t.message)
|
||||
}
|
||||
}
|
||||
}
|
103
kotlin/src/mal/step4_if_fn_do.kt
Normal file
103
kotlin/src/mal/step4_if_fn_do.kt
Normal file
@ -0,0 +1,103 @@
|
||||
package mal
|
||||
|
||||
fun read(input: String?): MalType = read_str(input)
|
||||
|
||||
fun eval(ast: MalType, env: Env): MalType =
|
||||
if (ast is MalList) {
|
||||
val first = ast.first()
|
||||
if (first is MalSymbol) {
|
||||
when (first.value) {
|
||||
"def!" -> eval_def_BANG(ast, env)
|
||||
"let*" -> eval_let_STAR(ast, env)
|
||||
"fn*" -> eval_fn_STAR(ast, env)
|
||||
"do" -> eval_do(ast, env)
|
||||
"if" -> eval_if(ast, env)
|
||||
else -> eval_function_call(ast, env)
|
||||
}
|
||||
} else eval_function_call(ast, env)
|
||||
} else eval_ast(ast, env)
|
||||
|
||||
private fun eval_def_BANG(ast: ISeq, env: Env): MalType =
|
||||
env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env))
|
||||
|
||||
private fun eval_let_STAR(ast: ISeq, env: Env): MalType {
|
||||
val child = Env(env)
|
||||
val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*")
|
||||
|
||||
val it = bindings.seq().iterator()
|
||||
while (it.hasNext()) {
|
||||
val key = it.next()
|
||||
if (!it.hasNext()) throw MalException("odd number of binding elements in let*")
|
||||
|
||||
val value = eval(it.next(), child)
|
||||
child.set(key as MalSymbol, value)
|
||||
}
|
||||
|
||||
return eval(ast.nth(2), child)
|
||||
}
|
||||
|
||||
private fun eval_fn_STAR(ast: ISeq, env: Env): MalType {
|
||||
val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter")
|
||||
val symbols = binds.seq().filterIsInstance<MalSymbol>() // TODO error if any non-symbols?
|
||||
val body = ast.nth(2)
|
||||
|
||||
return MalFunction({ s: ISeq ->
|
||||
eval(body, Env(env, symbols, s.seq()))
|
||||
})
|
||||
}
|
||||
|
||||
private fun eval_do(ast: ISeq, env: Env): MalType =
|
||||
(eval_ast(MalList(ast.rest()), env) as ISeq).seq().last()
|
||||
|
||||
private fun eval_if(ast: ISeq, env: Env): MalType {
|
||||
val check = eval(ast.nth(1), env)
|
||||
|
||||
return if (check != NIL && check != FALSE) {
|
||||
eval(ast.nth(2), env)
|
||||
} else if (ast.seq().asSequence().count() > 3) {
|
||||
eval(ast.nth(3), env)
|
||||
} else NIL
|
||||
}
|
||||
|
||||
private fun eval_function_call(ast: ISeq, env: Env): MalType {
|
||||
val evaluated = eval_ast(ast, env) as ISeq
|
||||
val first = evaluated.first() as? MalFunction ?: throw MalException("cannot execute non-function")
|
||||
return first.apply(evaluated.rest())
|
||||
}
|
||||
|
||||
fun eval_ast(ast: MalType, env: Env): MalType =
|
||||
if (ast is MalSymbol) {
|
||||
env.get(ast)
|
||||
} else if (ast is MalList) {
|
||||
ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalVector) {
|
||||
ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalHashMap) {
|
||||
ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a })
|
||||
} else ast
|
||||
|
||||
fun print(result: MalType) = pr_str(result, print_readably = true)
|
||||
|
||||
fun rep(input: String, env: Env): String =
|
||||
print(eval(read(input), env))
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val repl_env = Env()
|
||||
ns.forEach({ it -> repl_env.set(it.key, it.value) })
|
||||
|
||||
rep("(def! not (fn* (a) (if a false true)))", repl_env)
|
||||
|
||||
while (true) {
|
||||
val input = readline("user> ") ?: break
|
||||
|
||||
try {
|
||||
println(rep(input, repl_env))
|
||||
} catch (e: MalContinue) {
|
||||
} catch (e: MalException) {
|
||||
println("Error: " + e.message)
|
||||
} catch (t: Throwable) {
|
||||
println("Uncaught " + t + ": " + t.message)
|
||||
t.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
103
kotlin/src/mal/step5_tco.kt
Normal file
103
kotlin/src/mal/step5_tco.kt
Normal file
@ -0,0 +1,103 @@
|
||||
package mal
|
||||
|
||||
fun read(input: String?): MalType = read_str(input)
|
||||
|
||||
fun eval(ast: MalType, env: Env): MalType =
|
||||
if (ast is MalList) {
|
||||
val first = ast.first()
|
||||
if (first is MalSymbol) {
|
||||
when (first.value) {
|
||||
"def!" -> eval_def_BANG(ast, env)
|
||||
"let*" -> eval_let_STAR(ast, env)
|
||||
"fn*" -> eval_fn_STAR(ast, env)
|
||||
"do" -> eval_do(ast, env)
|
||||
"if" -> eval_if(ast, env)
|
||||
else -> eval_function_call(ast, env)
|
||||
}
|
||||
} else eval_function_call(ast, env)
|
||||
} else eval_ast(ast, env)
|
||||
|
||||
private fun eval_def_BANG(ast: ISeq, env: Env): MalType =
|
||||
env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env))
|
||||
|
||||
private fun eval_let_STAR(ast: ISeq, env: Env): MalType {
|
||||
val child = Env(env)
|
||||
val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*")
|
||||
|
||||
val it = bindings.seq().iterator()
|
||||
while (it.hasNext()) {
|
||||
val key = it.next()
|
||||
if (!it.hasNext()) throw MalException("odd number of binding elements in let*")
|
||||
|
||||
val value = eval(it.next(), child)
|
||||
child.set(key as MalSymbol, value)
|
||||
}
|
||||
|
||||
return eval(ast.nth(2), child)
|
||||
}
|
||||
|
||||
private fun eval_fn_STAR(ast: ISeq, env: Env): MalType {
|
||||
val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter")
|
||||
val symbols = binds.seq().filterIsInstance<MalSymbol>() // TODO error if any non-symbols?
|
||||
val body = ast.nth(2)
|
||||
|
||||
return MalFunction({ s: ISeq ->
|
||||
eval(body, Env(env, symbols, s.seq()))
|
||||
})
|
||||
}
|
||||
|
||||
private fun eval_do(ast: ISeq, env: Env): MalType =
|
||||
(eval_ast(MalList(ast.rest()), env) as ISeq).seq().last()
|
||||
|
||||
private fun eval_if(ast: ISeq, env: Env): MalType {
|
||||
val check = eval(ast.nth(1), env)
|
||||
|
||||
return if (check != NIL && check != FALSE) {
|
||||
eval(ast.nth(2), env)
|
||||
} else if (ast.seq().asSequence().count() > 3) {
|
||||
eval(ast.nth(3), env)
|
||||
} else NIL
|
||||
}
|
||||
|
||||
private fun eval_function_call(ast: ISeq, env: Env): MalType {
|
||||
val evaluated = eval_ast(ast, env) as ISeq
|
||||
val first = evaluated.first() as? MalFunction ?: throw MalException("cannot execute non-function")
|
||||
return first.apply(evaluated.rest())
|
||||
}
|
||||
|
||||
fun eval_ast(ast: MalType, env: Env): MalType =
|
||||
if (ast is MalSymbol) {
|
||||
env.get(ast)
|
||||
} else if (ast is MalList) {
|
||||
ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalVector) {
|
||||
ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a })
|
||||
} else if (ast is MalHashMap) {
|
||||
ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a })
|
||||
} else ast
|
||||
|
||||
fun print(result: MalType) = pr_str(result, print_readably = true)
|
||||
|
||||
fun rep(input: String, env: Env): String =
|
||||
print(eval(read(input), env))
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val repl_env = Env()
|
||||
ns.forEach({ it -> repl_env.set(it.key, it.value) })
|
||||
|
||||
rep("(def! not (fn* (a) (if a false true)))", repl_env)
|
||||
|
||||
while (true) {
|
||||
val input = readline("user> ") ?: break
|
||||
|
||||
try {
|
||||
println(rep(input, repl_env))
|
||||
} catch (e: MalContinue) {
|
||||
} catch (e: MalException) {
|
||||
println("Error: " + e.message)
|
||||
} catch (t: Throwable) {
|
||||
println("Uncaught " + t + ": " + t.message)
|
||||
t.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
106
kotlin/src/mal/types.kt
Normal file
106
kotlin/src/mal/types.kt
Normal file
@ -0,0 +1,106 @@
|
||||
package mal
|
||||
|
||||
import java.util.*
|
||||
|
||||
open class MalException(message: String) : Exception(message) { }
|
||||
class MalContinue() : MalException("continue") { }
|
||||
class MalReaderException(message: String) : MalException(message) { }
|
||||
class MalPrinterException(message: String) : MalException(message) { }
|
||||
|
||||
interface MalType {
|
||||
}
|
||||
|
||||
open class MalConstant(val value: String) : MalType {
|
||||
override fun equals(other: Any?): Boolean = other is MalConstant && value.equals(other.value)
|
||||
}
|
||||
|
||||
class MalInteger(val value: Int) : MalType {
|
||||
operator fun plus(a: MalInteger): MalInteger = MalInteger(value + a.value)
|
||||
operator fun minus(a: MalInteger): MalInteger = MalInteger(value - a.value)
|
||||
operator fun times(a: MalInteger): MalInteger = MalInteger(value * a.value)
|
||||
operator fun div(a: MalInteger): MalInteger = MalInteger(value / a.value)
|
||||
operator fun compareTo(a: MalInteger): Int = value.compareTo(a.value)
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is MalInteger && value.equals(other.value)
|
||||
}
|
||||
|
||||
class MalSymbol(value: String) : MalConstant(value)
|
||||
|
||||
open class MalString(value: String) : MalConstant(value)
|
||||
|
||||
class MalKeyword(value: String) : MalString("\u029E" + value)
|
||||
|
||||
interface ILambda : MalType {
|
||||
fun apply(seq: ISeq): MalType
|
||||
}
|
||||
|
||||
class MalFunction(val lambda: (ISeq) -> MalType) : MalType, ILambda {
|
||||
override fun apply(seq: ISeq): MalType = lambda(seq)
|
||||
}
|
||||
|
||||
interface ISeq : MalType {
|
||||
fun seq(): Sequence<MalType>
|
||||
fun first(): MalType
|
||||
fun rest(): ISeq
|
||||
fun nth(n: Int): MalType
|
||||
}
|
||||
|
||||
interface IMutableSeq : ISeq {
|
||||
fun conj_BANG(form: MalType)
|
||||
}
|
||||
|
||||
class MalSequence(val elements : Sequence<MalType>) : MalType, ISeq {
|
||||
override fun seq(): Sequence<MalType> = elements
|
||||
override fun first(): MalType = elements.first()
|
||||
override fun rest(): ISeq = MalSequence(elements.drop(1))
|
||||
override fun nth(n: Int): MalType = elements.elementAt(n)
|
||||
}
|
||||
|
||||
class MalList(val elements: MutableList<MalType>) : MalType, IMutableSeq {
|
||||
constructor() : this(LinkedList<MalType>())
|
||||
constructor(s: ISeq) : this(s.seq().toLinkedList())
|
||||
|
||||
override fun seq(): Sequence<MalType> = elements.asSequence()
|
||||
override fun first(): MalType = elements.first()
|
||||
override fun rest(): ISeq = MalSequence(elements.drop(1).asSequence())
|
||||
override fun nth(n: Int): MalType = elements.elementAt(n)
|
||||
|
||||
override fun conj_BANG(form: MalType) {
|
||||
elements.add(form)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
(other is ISeq)
|
||||
&& elements.size == other.seq().count() // TODO optimize counting?
|
||||
&& elements.asSequence().zip(other.seq()).all({ it -> it.first == it.second })
|
||||
}
|
||||
|
||||
class MalVector(val elements: MutableList<MalType>) : MalType, IMutableSeq {
|
||||
constructor() : this(ArrayList<MalType>())
|
||||
constructor(s: ISeq) : this(s.seq().toArrayList())
|
||||
|
||||
override fun seq(): Sequence<MalType> = elements.asSequence()
|
||||
override fun first(): MalType = elements.first()
|
||||
override fun rest(): ISeq = MalSequence(elements.drop(1).asSequence())
|
||||
override fun nth(n: Int): MalType = elements.elementAt(n)
|
||||
|
||||
override fun conj_BANG(form: MalType) {
|
||||
elements.add(form)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
(other is ISeq)
|
||||
&& elements.size == other.seq().count() // TODO optimize counting?
|
||||
&& elements.asSequence().zip(other.seq()).all({ it -> it.first == it.second })
|
||||
}
|
||||
|
||||
class MalHashMap : MalType {
|
||||
val elements = HashMap<MalString, MalType>()
|
||||
|
||||
fun assoc_BANG(key: MalString, value: MalType) = elements.put(key, value)
|
||||
}
|
||||
|
||||
// TODO add truthiness checking
|
||||
val NIL = MalConstant("nil")
|
||||
val TRUE = MalConstant("true")
|
||||
val FALSE = MalConstant("false")
|
Loading…
Reference in New Issue
Block a user