1/* 2 * gomacro - A Go interpreter with Lisp-like macros 3 * 4 * Copyright (C) 2017-2019 Massimiliano Ghilardi 5 * 6 * This Source Code Form is subject to the terms of the Mozilla Public 7 * License, v. 2.0. If a copy of the MPL was not distributed with this 8 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * 11 * macroexpand.go 12 * 13 * Created on Jun 09, 2017 14 * Author Massimiliano Ghilardi 15 */ 16 17package fast 18 19import ( 20 "go/ast" 21 r "reflect" 22 23 . "github.com/cosmos72/gomacro/ast2" 24 . "github.com/cosmos72/gomacro/base" 25 mt "github.com/cosmos72/gomacro/token" 26) 27 28// MacroExpandNodeCodewalk traverses the whole AST tree using pre-order traversal, 29// and replaces each node with the result of MacroExpandNode(node). 30// It implements the macroexpansion phase 31func (c *Comp) MacroExpandNodeCodewalk(in ast.Node) (out ast.Node, anythingExpanded bool) { 32 if in == nil { 33 return nil, false 34 } 35 var form Ast = ToAst(in) 36 form, anythingExpanded = c.MacroExpandCodewalk(form) 37 out = ToNode(form) 38 // if !anythingExpanded { 39 // c.Debugf("MacroExpand1() nothing to expand: %v <%v>", out, r.TypeOf(out)) 40 //} 41 return out, anythingExpanded 42} 43 44// MacroExpandCodewalk traverses the whole AST tree using pre-order traversal, 45// and replaces each node with the result of MacroExpand(node). 46// It implements the macroexpansion phase 47func (c *Comp) MacroExpandCodewalk(in Ast) (out Ast, anythingExpanded bool) { 48 return c.macroExpandCodewalk(in, 0) 49} 50 51func (c *Comp) macroExpandCodewalk(in Ast, quasiquoteDepth int) (out Ast, anythingExpanded bool) { 52 if in == nil || in.Size() == 0 { 53 return in, false 54 } 55 debug := c.Options&OptDebugMacroExpand != 0 56 if quasiquoteDepth <= 0 { 57 if debug { 58 c.Debugf("MacroExpandCodewalk: qq = %d, macroexpanding %v", quasiquoteDepth, in.Interface()) 59 } 60 in, anythingExpanded = c.MacroExpand(in) 61 } 62 if in != nil { 63 in = UnwrapTrivialAst(in) 64 } 65 if in == nil { 66 return in, anythingExpanded 67 } 68 saved := in 69 70 if expr, ok := in.(UnaryExpr); ok { 71 op := expr.X.Op 72 switch op { 73 case mt.MACRO: 74 break 75 case mt.QUOTE: 76 // QUOTE prevents macroexpansion only if found outside any QUASIQUOTE 77 if quasiquoteDepth == 0 { 78 return saved, anythingExpanded 79 } 80 case mt.QUASIQUOTE: 81 // extract the body of QUASIQUOTE 82 quasiquoteDepth++ 83 case mt.UNQUOTE, mt.UNQUOTE_SPLICE: 84 // extract the body of UNQUOTE or UNQUOTE_SPLICE 85 quasiquoteDepth-- 86 default: 87 goto Recurse 88 } 89 inChild := UnwrapTrivialAst(in.Get(0).Get(1)) 90 outChild, expanded := c.macroExpandCodewalk(inChild, quasiquoteDepth) 91 if op == mt.MACRO { 92 return outChild, expanded 93 } 94 out := in 95 if expanded { 96 out = MakeQuote2(expr, outChild.(AstWithNode)) 97 } 98 return out, expanded 99 } 100Recurse: 101 if in == nil { 102 return saved, anythingExpanded 103 } 104 if debug { 105 c.Debugf("MacroExpandCodewalk: qq = %d, recursing on %v", quasiquoteDepth, in) 106 } 107 out = in.New() 108 n := in.Size() 109 if outSlice, appendable := out.(AstWithSlice); appendable { 110 // New() returns zero-length slice... resize it 111 for outSlice.Size() < n { 112 outSlice = outSlice.Append(nil) 113 } 114 out = outSlice 115 } 116 for i := 0; i < n; i++ { 117 child := UnwrapTrivialAst(in.Get(i)) 118 if child != nil { 119 expanded := false 120 if child.Size() != 0 { 121 child, expanded = c.macroExpandCodewalk(child, quasiquoteDepth) 122 } 123 if expanded { 124 anythingExpanded = true 125 } 126 } 127 out.Set(i, child) 128 } 129 if debug { 130 c.Debugf("MacroExpandCodewalk: qq = %d, expanded to %v", quasiquoteDepth, out) 131 } 132 return out, anythingExpanded 133} 134 135// MacroExpandNode repeatedly invokes MacroExpandNode1 136// as long as the node represents a macro call. 137// it returns the resulting node. 138func (c *Comp) MacroExpandNode(in ast.Node) (out ast.Node, everExpanded bool) { 139 if in == nil { 140 return nil, false 141 } 142 inAst := ToAst(in) 143 outAst, everExpanded := c.MacroExpand(inAst) 144 out = ToNode(outAst) 145 // if !everExpanded { 146 // c.Debugf("MacroExpand1() not a macro: %v <%v>", out, r.TypeOf(out)) 147 //} 148 return out, everExpanded 149} 150 151// MacroExpand repeatedly invokes MacroExpand 152// as long as the node represents a macro call. 153// it returns the resulting node. 154func (c *Comp) MacroExpand(form Ast) (out Ast, everExpanded bool) { 155 var expanded bool 156 for { 157 form, expanded = c.MacroExpand1(form) 158 if !expanded { 159 return form, everExpanded 160 } 161 everExpanded = true 162 } 163} 164 165// if node represents a macro call, MacroExpandNode1 executes it 166// and returns the resulting node. 167// Otherwise returns the node argument unchanged 168func (c *Comp) MacroExpandNode1(in ast.Node) (out ast.Node, expanded bool) { 169 if in == nil { 170 return nil, false 171 } 172 var form Ast = ToAst(in) 173 form, expanded = c.MacroExpand1(form) 174 out = ToNode(form) 175 // if !expanded { 176 // c.Debugf("MacroExpandNode1: not a macro: %v <%v>", out, r.TypeOf(out)) 177 //} 178 return out, expanded 179} 180 181func (c *Comp) extractMacroCall(form Ast) Macro { 182 form = UnwrapTrivialAst(form) 183 switch form := form.(type) { 184 case Ident: 185 sym := c.TryResolve(form.X.Name) 186 if sym != nil && sym.Bind.Desc.Class() == ConstBind && sym.Type != nil && sym.Type.Kind() == r.Struct { 187 switch value := sym.Value.(type) { 188 case Macro: 189 if c.Options&OptDebugMacroExpand != 0 { 190 c.Debugf("MacroExpand1: found macro: %v", form.X.Name) 191 } 192 return value 193 } 194 } 195 } 196 return Macro{} 197} 198 199// if node represents a macro call, MacroExpandNode1 executes it 200// and returns the resulting node. 201// Otherwise returns the node argument unchanged 202func (c *Comp) MacroExpand1(in Ast) (out Ast, expanded bool) { 203 if in == nil { 204 return nil, false 205 } 206 // unwrap trivial nodes: DeclStmt, ParenExpr, ExprStmt 207 in = UnwrapTrivialAstKeepBlocks(in) 208 ins, ok := in.(AstWithSlice) 209 if !ok { 210 return in, false 211 } 212 debug := c.Options&OptDebugMacroExpand != 0 213 if debug { 214 c.Debugf("MacroExpand1: found list: %v", ins.Interface()) 215 } 216 n := ins.Size() 217 outs := ins.New().(AstWithSlice) 218 219 // since macro calls are sequences of statements, 220 // we must scan the whole list, 221 // consume it as needed by the macros we find, 222 // and build a new list accumulating the results of macroexpansion 223 for i := 0; i < n; i++ { 224 elt := ins.Get(i) 225 macro := c.extractMacroCall(elt) 226 if macro.closure == nil { 227 outs = outs.Append(elt) 228 continue 229 } 230 argn := macro.argNum 231 leftn := n - i - 1 232 var args []r.Value 233 if argn > leftn { 234 args := make([]r.Value, leftn+1) // include the macro itself 235 for j := 0; j <= leftn; j++ { 236 args[j] = r.ValueOf(ins.Get(i + j).Interface()) 237 } 238 c.Errorf("not enough arguments for macroexpansion of %v: expecting %d, found %d", args, macro.argNum, leftn) 239 return in, false 240 } 241 if debug { 242 c.Debugf("MacroExpand1: found macro call %v at %d-th position of %v", elt.Interface(), i, ins.Interface()) 243 } 244 // wrap each ast.Node into a reflect.Value 245 args = make([]r.Value, argn) 246 for j := 0; j < argn; j++ { 247 args[j] = r.ValueOf(ToNode(ins.Get(i + j + 1))) 248 } 249 // invoke the macro 250 results := macro.closure(args) 251 if debug { 252 c.Debugf("MacroExpand1: macro expanded to: %v", results) 253 } 254 var out Ast 255 switch len(results) { 256 default: 257 args = append([]r.Value{r.ValueOf(elt.Interface())}, args...) 258 c.Warnf("macroexpansion returned %d values, using only the first one: %v %v returned %v", 259 len(results), args, results) 260 fallthrough 261 case 1: 262 any := results[0].Interface() 263 if any != nil { 264 out = anyToAst(any, "macroexpansion") 265 break 266 } 267 fallthrough 268 case 0: 269 // do not insert nil nodes... they would wreak havok, convert them to the identifier nil 270 out = Ident{&ast.Ident{Name: "nil"}} 271 } 272 outs = outs.Append(out) 273 i += argn 274 expanded = true 275 } 276 if !expanded { 277 return in, false 278 } 279 if outs.Size() == 0 { 280 return EmptyStmt{&ast.EmptyStmt{}}, true 281 } 282 return UnwrapTrivialAst(outs), true 283} 284