diff options
Diffstat (limited to 'src/LispParser.kt')
-rw-r--r-- | src/LispParser.kt | 116 |
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()) + } + } +} |