aboutsummaryrefslogtreecommitdiff
path: root/evaluator/macro_expansion.go
diff options
context:
space:
mode:
Diffstat (limited to 'evaluator/macro_expansion.go')
-rw-r--r--evaluator/macro_expansion.go123
1 files changed, 123 insertions, 0 deletions
diff --git a/evaluator/macro_expansion.go b/evaluator/macro_expansion.go
new file mode 100644
index 0000000..1fdac0a
--- /dev/null
+++ b/evaluator/macro_expansion.go
@@ -0,0 +1,123 @@
+package evaluator
+
+import (
+ "monkey/ast"
+ "monkey/object"
+)
+
+func DefineMacros(program *ast.Program, env *object.Environment) {
+ definitions := []int{}
+
+ for i, statement := range program.Statements {
+ if isMacroDefinition(statement) {
+ addMacro(statement, env)
+ definitions = append(definitions, i)
+ }
+ }
+
+ for i := len(definitions) - 1; i >= 0; i = i - 1 {
+ definitionIndex := definitions[i]
+ program.Statements = append(
+ program.Statements[:definitionIndex],
+ program.Statements[definitionIndex+1:]...,
+ )
+ }
+}
+
+func isMacroDefinition(node ast.Statement) bool {
+ letStatement, ok := node.(*ast.LetStatement)
+ if !ok {
+ return false
+ }
+
+ _, ok = letStatement.Value.(*ast.MacroLiteral)
+ if !ok {
+ return false
+ }
+
+ return true
+}
+
+func addMacro(stmt ast.Statement, env *object.Environment) {
+ letStatement, _ := stmt.(*ast.LetStatement)
+ macroLiteral, _ := letStatement.Value.(*ast.MacroLiteral)
+
+ macro := &object.Macro{
+ Parameters: macroLiteral.Parameters,
+ Env: env,
+ Body: macroLiteral.Body,
+ }
+
+ env.Set(letStatement.Name.Value, macro)
+}
+
+func ExpandMacros(program ast.Node, env *object.Environment) ast.Node {
+ return ast.Modify(program, func(node ast.Node) ast.Node {
+ callExpression, ok := node.(*ast.CallExpression)
+ if !ok {
+ return node
+ }
+
+ macro, ok := isMacroCall(callExpression, env)
+ if !ok {
+ return node
+ }
+
+ args := quoteArgs(callExpression)
+ evalEnv := extendMacroEnv(macro, args)
+
+ evaluated := Eval(macro.Body, evalEnv)
+
+ quote, ok := evaluated.(*object.Quote)
+ if !ok {
+ panic("we only support returning AST-nodes from macros")
+ }
+
+ return quote.Node
+ })
+}
+
+func isMacroCall(
+ exp *ast.CallExpression,
+ env *object.Environment,
+) (*object.Macro, bool) {
+ identifier, ok := exp.Function.(*ast.Identifier)
+ if !ok {
+ return nil, false
+ }
+
+ obj, ok := env.Get(identifier.Value)
+ if !ok {
+ return nil, false
+ }
+
+ macro, ok := obj.(*object.Macro)
+ if !ok {
+ return nil, false
+ }
+
+ return macro, true
+}
+
+func quoteArgs(exp *ast.CallExpression) []*object.Quote {
+ args := []*object.Quote{}
+
+ for _, a := range exp.Arguments {
+ args = append(args, &object.Quote{Node: a})
+ }
+
+ return args
+}
+
+func extendMacroEnv(
+ macro *object.Macro,
+ args []*object.Quote,
+) *object.Environment {
+ extended := object.NewEnclosedEnvironment(macro.Env)
+
+ for paramIdx, param := range macro.Parameters {
+ extended.Set(param.Value, args[paramIdx])
+ }
+
+ return extended
+}