Created
February 14, 2015 08:48
-
-
Save yaraki/bbecffc8da412ca86291 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.arakitech.kotlisp | |
import java.util.HashMap | |
fun StringBuilder.clear() { | |
this.setLength(0) | |
} | |
class Tokenizer(val input: String) { | |
private var position = 0 | |
private var preserved: String? = null | |
fun next(): String? { | |
val sb = StringBuilder() | |
val length = input.length() | |
while (position < length) { | |
val c = input[position] | |
when (c) { | |
' ', '\t', '\r', '\n' -> { | |
if (0 < sb.length()) { | |
position++ | |
return sb.toString() | |
} | |
} | |
'(', ')', '[', ']', '\'', '`' -> { | |
if (0 < sb.length()) { | |
return sb.toString() | |
} | |
position++ | |
return c.toString() | |
} | |
',' -> { | |
if (0 < sb.length()) { | |
return sb.toString() | |
} | |
position++ | |
if ('@' == input[position]) { | |
position++ | |
return ",@" | |
} else { | |
return "," | |
} | |
} | |
else -> sb.append(c) | |
} | |
position++ | |
} | |
if (0 < sb.length()) { | |
return sb.toString() | |
} | |
return null | |
} | |
fun hasNext() = position < input.length() | |
} | |
abstract class LExpr { | |
abstract fun show(): String | |
} | |
abstract class LLiteral : LExpr() { | |
} | |
class LSymbol(val name: String) : LExpr() { | |
override fun show(): String { | |
return name | |
} | |
} | |
class LInteger(val value: Int) : LLiteral() { | |
override fun show(): String { | |
return value.toString() | |
} | |
} | |
abstract class LList : LExpr() { | |
abstract fun nil(): Boolean | |
abstract fun showWithoutParen(): String | |
override fun show(): String { | |
return "(${showWithoutParen()})" | |
} | |
} | |
class LNil : LList() { | |
class object { | |
val NIL = LNil() | |
} | |
override fun showWithoutParen(): String { | |
return "nil" | |
} | |
override fun nil(): Boolean { | |
return true | |
} | |
} | |
class LPair(val head: LExpr, val tail: LList) : LList() { | |
override fun nil(): Boolean { | |
return false; | |
} | |
override fun showWithoutParen(): String { | |
return if (tail.nil()) head.show() else "${head.show()} ${tail.showWithoutParen()}" | |
} | |
override fun show(): String { | |
return "(${showWithoutParen()})" | |
} | |
} | |
fun read(tokenizer: Tokenizer, asList: Boolean): LExpr? { | |
val token = tokenizer.next() | |
if (token == null) { | |
return null | |
} | |
var expr: LExpr? = null | |
if ("(" == token) { | |
expr = read(tokenizer, true) | |
} else if (")" == token) { | |
expr = LNil.NIL | |
} else if (token.matches("[0-9.]+")) { | |
expr = LInteger(Integer.parseInt(token)) | |
} else { | |
expr = LSymbol(token) | |
} | |
return if (asList) { | |
if (expr == LNil.NIL) { | |
LNil.NIL | |
} else { | |
LPair(expr as LExpr, read(tokenizer, true) as LList) | |
} | |
} else { | |
expr | |
} | |
} | |
fun read(s: String): LExpr? { | |
val tokenizer = Tokenizer(s) | |
return read(tokenizer, false) | |
} | |
class LFunction(val f: (LList) -> LExpr) : LExpr() { | |
override fun show(): String { | |
return "#function" | |
} | |
fun apply(args: LList): LExpr { | |
return f(args) | |
} | |
} | |
class Env { | |
private val table = HashMap<String, LExpr>() | |
fun intern(s: String, expr: LExpr) { | |
table.put(s, expr) | |
} | |
fun evalEach(list: LList): LList { | |
return when (list) { | |
is LPair -> LPair(eval(list.head), evalEach(list.tail)) | |
else -> LNil.NIL | |
} | |
} | |
fun eval(expr: LExpr): LExpr = | |
when (expr ) { | |
is LLiteral -> expr | |
is LSymbol -> { | |
table[expr.name] | |
} | |
is LPair -> { | |
val head = eval(expr.head) | |
when (head) { | |
is LFunction -> head.apply(evalEach(expr.tail)) | |
else -> LNil.NIL | |
} | |
} | |
else -> LNil.NIL | |
} | |
} | |
fun main(args: Array<String>) { | |
val s = LSymbol("+") | |
println(s.show()) | |
val i1 = LInteger(1) | |
val i2 = LInteger(2) | |
println(i1.show()) | |
val p1 = LPair(i1, LNil.NIL) | |
val p2 = LPair(i2, p1) | |
val p3 = LPair(s, p2) | |
println(p3.show()) | |
// | |
println("==================================") | |
val t = Tokenizer("(`(cons ,@a ,b) [+ 123 abc])") | |
while (t.hasNext()) { | |
val token = t.next() | |
if (token == null) break | |
println(token) | |
} | |
println("==================================") | |
println(read("(+ 1 2 3)")?.show()) | |
println(read("(+ 1 (* 2 3))")?.show()) | |
val env = Env() | |
env.intern("+", LFunction({ args -> | |
var l = args | |
var i = 0 | |
while (l !is LNil) { | |
val v = ((l as LPair).head as LInteger) | |
i += v.value | |
l = (l as LPair).tail | |
} | |
LInteger(i) | |
})) | |
val ll = read("(+ 1 2 3 (+ 4 5 (+ 6 7)))")!! | |
println("# ${ll.show()}") | |
val result = env.eval(ll) | |
if (result != null) { | |
println(result.show()) | |
} | |
} |
This is just a joke, but it's working nevertheless. Have a look here: http://kotlin-demo.jetbrains.com/?publicLink=104074971561017308771-1697121195
The April fool's blog post about it: http://blog.jetbrains.com/kotlin/2014/04/kotlin-gets-support-for-s-expressions/
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is my very first Kotlin program, so there should definitely be a plenty of room for improvements.