summaryrefslogtreecommitdiff
path: root/src/LispParser.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/LispParser.kt')
-rw-r--r--src/LispParser.kt116
1 files changed, 116 insertions, 0 deletions
diff --git a/src/LispParser.kt b/src/LispParser.kt
new file mode 100644
index 0000000..1117397
--- /dev/null
+++ b/src/LispParser.kt
@@ -0,0 +1,116 @@
+package moe.nea.lisp
+
+import java.io.File
+
+class LispParser private constructor(filename: String, string: String) {
+ val racer = StringRacer(filename, string)
+ val program = parseProgram()
+
+ companion object {
+ fun parse(filename: String, string: String): LispAst.Program {
+ return LispParser(filename, string).program
+ }
+ fun parse(file: File): LispAst.Program {
+ return parse(file.absolutePath, file.readText())
+ }
+
+ val digits = "1234567890"
+ val alphabet = "abcdefghijklmnopqrstuvwxyz"
+ val validStartingIdentifiers = "-.#+*'!$%&/=?_~|^" + alphabet + alphabet.uppercase()
+ val validIdentifiers = validStartingIdentifiers + digits
+ val parenthesisMatches = mapOf(
+ "(" to ")",
+ "[" to "]",
+ "{" to "}",
+ "<" to ">",
+ )
+ }
+
+ fun parseProgram(): LispAst.Program {
+ val start = racer.idx
+ val nodes = mutableListOf<LispAst.LispNode>()
+ while (true) {
+ racer.skipWhitespace()
+ if (racer.finished())
+ break
+ nodes.add(parseNode())
+ }
+ return LispAst.Program(racer.span(start), nodes)
+ }
+
+ private fun parseNode(): LispAst.LispNode {
+ val start = racer.idx
+ val paren = racer.peekReq(1) ?: racer.error("Expected start of expression")
+ val matchingParen = parenthesisMatches[paren]
+ if (matchingParen != null) {
+ val paren = parseParenthesis(paren, matchingParen)
+ return LispAst.Parenthesis(racer.span(start), paren)
+ }
+ if (paren == "\"") {
+ return parseString()
+ }
+ if (paren == ":") {
+ return parseAtom()
+ }
+ val ident = parseIdentifier()
+ return LispAst.Reference(racer.span(start), ident)
+ }
+
+
+ fun parseAtom(): LispAst.Atom {
+ val start = racer.idx
+ racer.expect(":", "Expected : at start of atom")
+ val ident = parseIdentifier()
+ return LispAst.Atom(racer.span(start), ident)
+ }
+
+
+ fun parseIdentifier(): String {
+ return racer.consumeWhile { it.first() in validStartingIdentifiers && it.last() in validIdentifiers }.also {
+ if (it.isEmpty()) racer.error("Expected identifier")
+ }
+ }
+
+ fun parseString(): LispAst.StringLiteral {
+ val start = racer.idx
+ val quoted = parseQuotedString()
+ return LispAst.StringLiteral(racer.span(start), quoted)
+ }
+
+ fun parseQuotedString(): String {
+ racer.expect("\"", "Expected '\"' at string start")
+ val sb = StringBuilder()
+ while (true) {
+ when (val peek = racer.consumeCountReq(1)) {
+ "\"" -> break
+ "\\" -> {
+ val escaped = racer.consumeCountReq(1) ?: racer.error("Unfinished backslash escape")
+ if (escaped != "\"" && escaped != "\\") {
+ // Surprisingly i couldn't find unicode escapes to be generated by the original minecraft 1.8.9 implementation
+ racer.idx--
+ racer.error("Invalid backslash escape '$escaped'")
+ }
+ sb.append(escaped)
+ }
+
+ null -> racer.error("Unfinished string")
+ else -> {
+ sb.append(peek)
+ }
+ }
+ }
+ return sb.toString()
+ }
+
+ private fun parseParenthesis(opening: String, closing: String): List<LispAst.LispNode> {
+ val l = mutableListOf<LispAst.LispNode>()
+ racer.expect(opening, "Expected $opening")
+ while (true) {
+ racer.skipWhitespace()
+ if (racer.tryConsume(closing)) {
+ return l
+ }
+ l.add(parseNode())
+ }
+ }
+}