summaryrefslogtreecommitdiff
path: root/src/LispParser.kt
blob: 48d0d45d451ddf95fcd60d0b3ac2dda14e685763 (plain)
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
132
133
134
135
136
137
138
139
140
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())
        }

        fun isValidIdentifier(name: String): Boolean {
            return name.isNotEmpty() && name.first() in validStartingIdentifiers && name.all { it in validIdentifiers }
        }

        val digits = "1234567890"
        val hexDigits = digits + "abcdefABCDEF"
        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 hexDigits || it == '.' || it == 'x' } }
        var double = number.toDoubleOrNull()
        if (number.startsWith("0x")) {
            double = number.substring(2).toLong(16).toDouble()
        }
        if (double == null) {
            racer.popState()
            racer.error("Could not parse number $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())
        }
    }
}