summaryrefslogtreecommitdiff
path: root/src/LispExecutionContext.kt
blob: 0a1dc41b23bbd1d22ec373d060ec1207009205ec (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
package moe.nea.lisp

class LispExecutionContext() {

    val rootStackFrame = StackFrame(null)
    val unloadedModules = mutableMapOf<String, LispAst.Program>()
    val modules = mutableMapOf<String, Map<String, LispData>>()

    fun genBindings(): StackFrame {
        return StackFrame(rootStackFrame)
    }

    fun setupStandardBindings() {
        CoreBindings.offerAllTo(rootStackFrame)
        registerModule("builtins", Builtins.builtinProgram)
        registerModule("test", Builtins.testProgram)
        modules["ntest"] = TestFramework.realizedTestModule
        importModule("builtins", rootStackFrame, object : HasLispPosition {
            override val position: LispPosition
                get() = error("Builtin import failed")
        })
    }

    fun runTests(
        program: LispAst.Program,
        name: String,
        stackFrame: StackFrame = genBindings(),
        testList: List<String> = emptyList(),
        isWhitelist: Boolean = false
    ): TestFramework.TestSuite {
        val testSuite = TestFramework.setup(stackFrame, name, testList, isWhitelist)
        val output = OutputCapture.captureOutput(stackFrame)
        executeProgram(stackFrame, program)
        testSuite.isTesting = false
        testSuite.otherOutput = output.asString
        return testSuite
    }

    fun registerModule(moduleName: String, program: LispAst.Program) {
        if (moduleName in unloadedModules || moduleName in modules) {
            error("Cannot register already registered module $moduleName")
        }
        unloadedModules[moduleName] = program
    }

    fun importModule(moduleName: String, into: StackFrame, position: HasLispPosition) {
        var exports = modules[moduleName]
        if (exports == null) {
            val module = unloadedModules[moduleName]
            if (module == null) {
                into.reportError("Could not find module $moduleName", position)
                return
            }
            exports = realizeModule(moduleName)
        }
        into.variables.putAll(exports)
    }

    private fun realizeModule(moduleName: String): Map<String, LispData> {
        val map = mutableMapOf<String, LispData>()
        modules[moduleName] = map
        val module = unloadedModules.remove(moduleName) ?: error("Could not find module $moduleName")
        val stackFrame = genBindings()
        stackFrame.setValueLocal("export", LispData.externalRawCall("export") { context, callsite, stackFrame, args ->
            args.forEach { name ->
                if (name !is LispAst.Reference) {
                    stackFrame.reportError("Invalid export", name)
                    return@forEach
                }
                map[name.label] = context.resolveValue(stackFrame, name)
            }
            return@externalRawCall LispData.LispNil
        })
        executeProgram(stackFrame, module)
        return map
    }

    fun executeProgram(stackFrame: StackFrame, program: LispAst.Program): LispData? {
        var lastValue: LispData? = null
        for (node in program.nodes) {
            lastValue = executeLisp(stackFrame, node)
        }
        return lastValue
    }


    fun executeLisp(stackFrame: StackFrame, node: LispAst.LispNode): LispData {
        when (node) {
            is LispAst.Parenthesis -> {
                val first = node.items.firstOrNull()
                    ?: return stackFrame.reportError("Cannot execute empty parenthesis ()", node)

                val rest = node.items.drop(1)
                return when (val resolvedValue = resolveValue(stackFrame, first)) {
                    is LispData.LispExecutable -> {
                        resolvedValue.execute(this, node, stackFrame, rest)
                    }

                    else -> stackFrame.reportError("Cannot evaluate expression of type $resolvedValue", node)
                }

            }

            else -> return stackFrame.reportError("Expected invocation", node)
        }
    }

    fun resolveValue(stackFrame: StackFrame, node: LispAst.LispNode): LispData {
        return when (node) {
            is LispAst.Atom -> LispData.Atom(node.label)
            is LispAst.Parenthesis -> executeLisp(stackFrame, node)
            is LispAst.Reference -> stackFrame.resolveReference(node.label)
                ?: stackFrame.reportError("Could not resolve variable ${node.label}", node)

            is LispAst.NumberLiteral -> LispData.LispNumber(node.numberValue)
            is LispAst.StringLiteral -> LispData.LispString(node.parsedString)
        }
    }
}