1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
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 in digits) {
return parseNumber()
}
if (paren == ":") {
return parseAtom()
}
val ident = parseIdentifier()
return LispAst.Reference(racer.span(start), ident)
}
fun parseNumber(): LispAst.NumberLiteral {
val start = racer.idx
racer.pushState()
val number = racer.consumeWhile { it.last().let { it in digits || it == '.' } }
val double = number.toDoubleOrNull()
if (double == null) {
racer.popState()
racer.error("Could not parse number")
}
racer.discardState()
return LispAst.NumberLiteral(racer.span(start), double)
}
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())
}
}
}
|