summaryrefslogtreecommitdiff
path: root/src/CoreBindings.kt
blob: 13c6de35fb03993e91061a3372a2d479a36a7450 (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
package moe.nea.lisp

object CoreBindings {
    val nil = LispData.LispNil
    val def = LispData.externalRawCall { context, callsite, stackFrame, args ->
        if (args.size != 2) {
            return@externalRawCall context.reportError("Function define expects exactly two arguments", callsite)
        }
        val (name, value) = args
        if (name !is LispAst.Reference) {
            return@externalRawCall context.reportError("Define expects a name as first argument", name)
        }
        if (name.label in stackFrame.variables) {
            return@externalRawCall context.reportError("Cannot redefine value in local context", name)
        }
        return@externalRawCall stackFrame.setValueLocal(name.label, context.resolveValue(stackFrame, value))
    }

    val pure = LispData.externalCall { args, reportError ->
        return@externalCall args.singleOrNull()?.let { value ->
            LispData.externalCall { args, reportError ->
                if (args.isNotEmpty())
                    reportError("Pure function does not expect arguments")
                else
                    value
            }
        } ?: reportError("Function pure expects exactly one argument")
    }

    val lambda = LispData.externalRawCall { context, callsite, stackFrame, args ->
        if (args.size != 2) {
            return@externalRawCall context.reportError("Lambda needs exactly 2 arguments", callsite)
        }
        val (argumentNames, body) = args
        if (argumentNames !is LispAst.Parenthesis) {
            return@externalRawCall context.reportError("Lambda has invalid argument declaration", argumentNames)
        }
        val argumentNamesString = argumentNames.items.map {
            val ref = it as? LispAst.Reference
            if (ref == null) {
                return@externalRawCall context.reportError("Lambda has invalid argument declaration", it)
            }
            ref.label
        }
        if (body !is LispAst.Parenthesis) {
            return@externalRawCall context.reportError("Lambda has invalid body declaration", body)
        }
        LispData.createLambda(stackFrame, argumentNamesString, body)
    }

    val defun = LispData.externalRawCall { context, callSite, stackFrame, lispAsts ->
        if (lispAsts.size != 3) {
            return@externalRawCall context.reportError("Invalid function definition", callSite)
        }
        val (name, args, body) = lispAsts
        if (name !is LispAst.Reference) {
            return@externalRawCall context.reportError("Invalid function definition name", name)
        }
        if (name.label in stackFrame.variables) {
            return@externalRawCall context.reportError("Cannot redefine function in local context", name)
        }
        if (args !is LispAst.Parenthesis) {
            return@externalRawCall context.reportError("Invalid function definition arguments", args)
        }
        val argumentNames = args.items.map {
            val ref = it as? LispAst.Reference
                ?: return@externalRawCall context.reportError("Invalid function definition argument name", it)
            ref.label
        }
        if (body !is LispAst.Parenthesis) {
            return@externalRawCall context.reportError("Invalid function definition body", body)
        }
        return@externalRawCall stackFrame.setValueLocal(
            name.label,
            LispData.createLambda(stackFrame, argumentNames, body, name.label)
        )
    }
    val seq = LispData.externalRawCall { context, callsite, stackFrame, args ->
        var lastResult: LispData? = null
        for (arg in args) {
            lastResult = context.executeLisp(stackFrame, arg)
        }
        lastResult ?: context.reportError("Seq cannot be invoked with 0 argumens", callsite)
    }

    val debuglog = LispData.externalRawCall { context, callsite, stackFrame, args ->
        println(args.joinToString(" ") { arg ->
            when (val resolved = context.resolveValue(stackFrame, arg)) {
                is LispData.Atom -> ":${resolved.label}"
                is LispData.JavaExecutable -> "<native code>"
                LispData.LispNil -> "nil"
                is LispData.LispNode -> resolved.node.toSource()
                is LispData.LispString -> resolved.string
                is LispData.LispNumber -> resolved.value.toString()
                is LispData.LispInterpretedCallable -> "<function ${resolved.name ?: ""} ${resolved.argNames} ${resolved.body.toSource()}>"
            }
        })
        LispData.LispNil
    }
    val add = LispData.externalCall { args, reportError ->
        if (args.size == 0) {
            return@externalCall reportError("Cannot call add without at least 1 argument")
        }
        LispData.LispNumber(args.fold(0.0) { a, b ->
            a + (b as? LispData.LispNumber
                ?: return@externalCall reportError("Unexpected argument $b, expected number")).value
        })
    }

    fun offerArithmeticTo(bindings: StackFrame) {
        bindings.setValueLocal("+", add)
    }

    fun offerAllTo(bindings: StackFrame) {
        bindings.setValueLocal("nil", nil)
        bindings.setValueLocal("def", def)
        bindings.setValueLocal("pure", pure)
        bindings.setValueLocal("lambda", lambda)
        bindings.setValueLocal("defun", defun)
        bindings.setValueLocal("seq", seq)
        bindings.setValueLocal("debuglog", debuglog)
        offerArithmeticTo(bindings)
    }
}