1
1
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:
Javier Fernandez-Ivern 2015-10-24 22:10:53 -05:00
parent eb76b9fbce
commit 53c2ea70cf
16 changed files with 725 additions and 2 deletions

4
.gitignore vendored
View File

@ -64,4 +64,6 @@ elixir/_build
elixir/deps
elixir/erl_crash.dump
elixir/*.ez
kotlin/*.jar
kotlin/.idea
kotlin/*.iml

View File

@ -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),") #"

View File

@ -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
View 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
View 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
View 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
View 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
View 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)
}
}

View 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)

View 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)
}
}
}

View 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)
}
}
}

View 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)
}
}
}

View 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)
}
}
}

View 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
View 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
View 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")