├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── ast ├── ast.go ├── token.go └── visitor.go ├── builtins.go ├── bytecode.go ├── compile.go ├── examples ├── array.yo ├── constfold.yo └── hello_world.yo ├── opcode.go ├── parse ├── parser.go ├── parser_test.go └── tokenizer.go ├── pretty ├── ast.go └── func.go ├── run └── main.go ├── util.go ├── value.go └── vm.go /.gitignore: -------------------------------------------------------------------------------- 1 | yo 2 | *.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Guilherme Nemeth 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC = $(wildcard ast/*.go) 2 | SRC += $(wildcard parse/*.go) 3 | SRC += $(wildcard pretty/*.go) 4 | SRC += $(wildcard run/*.go) 5 | SRC += $(wildcard *.go) 6 | OUT = yo 7 | 8 | ifdef SystemRoot 9 | OUT = yo.exe 10 | endif 11 | 12 | $(OUT): $(SRC) 13 | @go build -o $@ ./run 14 | 15 | clean: 16 | @rm $(OUT) 17 | 18 | .PHONY: clean 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ヨ (Yo) 2 | is a dynamic scripting language targeted to be easily embeddable in host Go applications. It's overall design and syntax are inspired from Go itself, so there's almost no learning curve at all if you are already a Go developer. 3 | 4 | This is a very early work in progress and a self-learning experience foremost, but pull requests are more than welcome. 5 | 6 | ## Status 7 | List of things that are done and what needs to be. 8 | - [x] Tokenization 9 | - [x] Parsing 10 | - [x] Syntax tree 11 | - [x] Compilation to bytecode 12 | - [x] Register machine (WIP) 13 | - [ ] Go APIs 14 | - [ ] Channels and goroutines 15 | - [ ] Optimizations 16 | 17 | ## Syntax 18 | Because it's heavily inspired in Go, you should almost feel no difference when switching between your compiled code and your script, minus the types. 19 | ```go 20 | // Hello world, from Yo! 21 | println("こんにちは世界、ヨから") 22 | 23 | // functions 24 | func add(x, y) { 25 | return x + y 26 | } 27 | println(add(2, 5)) 28 | 29 | // compile-time constants 30 | const numExamples = 7 31 | println(numExamples + 3) // compiles "println(10)" 32 | 33 | // error handling, multiple return values, short variable declaration (:=) 34 | content, err := ioutil.readFile("data.txt") 35 | if err { 36 | log.fatal(err) 37 | } 38 | 39 | // arrays 40 | arr := [1, 2, 3] 41 | append(arr, 4, 5, 6) 42 | println(len(arr), " ", arr) 43 | 44 | // objects/maps 45 | Vector2 := { 46 | x: 0, 47 | y: 0 48 | } 49 | 50 | Vector3 := new(Vector2, {z: 0}) 51 | 52 | positions := { 53 | "user1": new(Vector3), 54 | "user2": new(Vector3, {x: 525.4, y: 320, z: 110.54}), 55 | } 56 | 57 | // methods 58 | func Vector3.mul(multiple) { 59 | return new(Vector3, { 60 | x: this.x * multiple, 61 | y: this.y * multiple, 62 | z: this.z * multiple, 63 | }) 64 | } 65 | 66 | // closures 67 | func seq(start) { 68 | i := 0 69 | return func() { 70 | return start + i++ 71 | } 72 | } 73 | 74 | inc := seq(1) 75 | println(inc()) // 1 76 | println(inc()) // 2 77 | println(inc()) // 3 78 | ``` 79 | 80 | ## License 81 | MIT 82 | -------------------------------------------------------------------------------- /ast/ast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | // Abstract Syntax Tree 4 | 5 | package ast 6 | 7 | import () 8 | 9 | type ( 10 | Node interface { 11 | Accept(v Visitor, data interface{}) 12 | } 13 | 14 | NodeInfo struct { 15 | Line int 16 | } 17 | 18 | // 19 | // expressions 20 | // 21 | 22 | Nil struct { 23 | NodeInfo 24 | } 25 | 26 | Bool struct { 27 | NodeInfo 28 | Value bool 29 | } 30 | 31 | Number struct { 32 | NodeInfo 33 | Value float64 34 | } 35 | 36 | Id struct { 37 | NodeInfo 38 | Value string 39 | } 40 | 41 | String struct { 42 | NodeInfo 43 | Value string 44 | } 45 | 46 | Array struct { 47 | NodeInfo 48 | Elements []Node 49 | } 50 | 51 | ObjectField struct { 52 | NodeInfo 53 | Key string 54 | Value Node 55 | } 56 | 57 | Object struct { 58 | NodeInfo 59 | Fields []*ObjectField 60 | } 61 | 62 | Function struct { 63 | NodeInfo 64 | Name Node 65 | Args []Node 66 | Body Node 67 | } 68 | 69 | Selector struct { 70 | NodeInfo 71 | Left Node 72 | Value string 73 | } 74 | 75 | Subscript struct { 76 | NodeInfo 77 | Left Node 78 | Right Node 79 | } 80 | 81 | Slice struct { 82 | NodeInfo 83 | Start Node 84 | End Node 85 | } 86 | 87 | KwArg struct { 88 | NodeInfo 89 | Key string 90 | Value Node 91 | } 92 | 93 | VarArg struct { 94 | NodeInfo 95 | Arg Node 96 | } 97 | 98 | CallExpr struct { 99 | NodeInfo 100 | Left Node 101 | Args []Node 102 | } 103 | 104 | PostfixExpr struct { 105 | NodeInfo 106 | Op Token 107 | Left Node 108 | } 109 | 110 | UnaryExpr struct { 111 | NodeInfo 112 | Op Token 113 | Right Node 114 | } 115 | 116 | BinaryExpr struct { 117 | NodeInfo 118 | Op Token 119 | Left Node 120 | Right Node 121 | } 122 | 123 | TernaryExpr struct { 124 | NodeInfo 125 | Cond Node 126 | Then Node 127 | Else Node 128 | } 129 | 130 | // 131 | // statements 132 | // 133 | 134 | Declaration struct { 135 | NodeInfo 136 | IsConst bool 137 | Left []*Id 138 | Right []Node 139 | } 140 | 141 | Assignment struct { 142 | NodeInfo 143 | Op Token 144 | Left []Node 145 | Right []Node 146 | } 147 | 148 | BranchStmt struct { 149 | NodeInfo 150 | Type Token // BREAK, CONTINUE or FALLTHROUGH 151 | } 152 | 153 | ReturnStmt struct { 154 | NodeInfo 155 | Values []Node 156 | } 157 | 158 | PanicStmt struct { 159 | NodeInfo 160 | Err Node 161 | } 162 | 163 | IfStmt struct { 164 | NodeInfo 165 | Init *Assignment 166 | Cond Node 167 | Body Node 168 | Else Node 169 | } 170 | 171 | ForIteratorStmt struct { 172 | NodeInfo 173 | Key *Id 174 | Value *Id 175 | Collection Node 176 | When Node 177 | Body Node 178 | } 179 | 180 | ForStmt struct { 181 | NodeInfo 182 | Init *Assignment 183 | Cond Node 184 | Step Node 185 | Body Node 186 | } 187 | 188 | RecoverBlock struct { 189 | NodeInfo 190 | Id *Id 191 | Block *Block 192 | } 193 | 194 | TryRecoverStmt struct { 195 | NodeInfo 196 | Try *Block 197 | Recover *RecoverBlock 198 | Finally *Block 199 | } 200 | 201 | Block struct { 202 | NodeInfo 203 | Nodes []Node 204 | } 205 | ) 206 | 207 | func (node *Nil) Accept(v Visitor, data interface{}) { 208 | v.VisitNil(node, data) 209 | } 210 | 211 | func (node *Bool) Accept(v Visitor, data interface{}) { 212 | v.VisitBool(node, data) 213 | } 214 | 215 | func (node *Number) Accept(v Visitor, data interface{}) { 216 | v.VisitNumber(node, data) 217 | } 218 | 219 | func (node *Id) Accept(v Visitor, data interface{}) { 220 | v.VisitId(node, data) 221 | } 222 | 223 | func (node *String) Accept(v Visitor, data interface{}) { 224 | v.VisitString(node, data) 225 | } 226 | 227 | func (node *Array) Accept(v Visitor, data interface{}) { 228 | v.VisitArray(node, data) 229 | } 230 | 231 | func (node *ObjectField) Accept(v Visitor, data interface{}) { 232 | v.VisitObjectField(node, data) 233 | } 234 | 235 | func (node *Object) Accept(v Visitor, data interface{}) { 236 | v.VisitObject(node, data) 237 | } 238 | 239 | func (node *Function) Accept(v Visitor, data interface{}) { 240 | v.VisitFunction(node, data) 241 | } 242 | 243 | func (node *Selector) Accept(v Visitor, data interface{}) { 244 | v.VisitSelector(node, data) 245 | } 246 | 247 | func (node *Subscript) Accept(v Visitor, data interface{}) { 248 | v.VisitSubscript(node, data) 249 | } 250 | 251 | func (node *Slice) Accept(v Visitor, data interface{}) { 252 | v.VisitSlice(node, data) 253 | } 254 | 255 | func (node *KwArg) Accept(v Visitor, data interface{}) { 256 | v.VisitKwArg(node, data) 257 | } 258 | 259 | func (node *VarArg) Accept(v Visitor, data interface{}) { 260 | v.VisitVarArg(node, data) 261 | } 262 | 263 | func (node *CallExpr) Accept(v Visitor, data interface{}) { 264 | v.VisitCallExpr(node, data) 265 | } 266 | 267 | func (node *PostfixExpr) Accept(v Visitor, data interface{}) { 268 | v.VisitPostfixExpr(node, data) 269 | } 270 | 271 | func (node *UnaryExpr) Accept(v Visitor, data interface{}) { 272 | v.VisitUnaryExpr(node, data) 273 | } 274 | 275 | func (node *TernaryExpr) Accept(v Visitor, data interface{}) { 276 | v.VisitTernaryExpr(node, data) 277 | } 278 | 279 | func (node *BinaryExpr) Accept(v Visitor, data interface{}) { 280 | v.VisitBinaryExpr(node, data) 281 | } 282 | 283 | func (node *Declaration) Accept(v Visitor, data interface{}) { 284 | v.VisitDeclaration(node, data) 285 | } 286 | 287 | func (node *Assignment) Accept(v Visitor, data interface{}) { 288 | v.VisitAssignment(node, data) 289 | } 290 | 291 | func (node *BranchStmt) Accept(v Visitor, data interface{}) { 292 | v.VisitBranchStmt(node, data) 293 | } 294 | 295 | func (node *ReturnStmt) Accept(v Visitor, data interface{}) { 296 | v.VisitReturnStmt(node, data) 297 | } 298 | 299 | func (node *PanicStmt) Accept(v Visitor, data interface{}) { 300 | v.VisitPanicStmt(node, data) 301 | } 302 | 303 | func (node *IfStmt) Accept(v Visitor, data interface{}) { 304 | v.VisitIfStmt(node, data) 305 | } 306 | 307 | func (node *ForIteratorStmt) Accept(v Visitor, data interface{}) { 308 | v.VisitForIteratorStmt(node, data) 309 | } 310 | 311 | func (node *ForStmt) Accept(v Visitor, data interface{}) { 312 | v.VisitForStmt(node, data) 313 | } 314 | 315 | func (node *RecoverBlock) Accept(v Visitor, data interface{}) { 316 | v.VisitRecoverBlock(node, data) 317 | } 318 | 319 | func (node *TryRecoverStmt) Accept(v Visitor, data interface{}) { 320 | v.VisitTryRecoverStmt(node, data) 321 | } 322 | 323 | func (node *Block) Accept(v Visitor, data interface{}) { 324 | v.VisitBlock(node, data) 325 | } 326 | 327 | // return true if the given node is a statement 328 | func IsStmt(node Node) bool { 329 | switch node.(type) { 330 | case *Assignment, *IfStmt, *ForStmt, *ForIteratorStmt, 331 | *BranchStmt, *ReturnStmt, *Declaration: 332 | return true 333 | default: 334 | return false 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /ast/token.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | // token types and representations 4 | 5 | package ast 6 | 7 | type Token int 8 | 9 | const ( 10 | TokenEos Token = iota 11 | TokenNewline 12 | TokenNil 13 | TokenTrue 14 | TokenFalse 15 | TokenIf 16 | TokenElse 17 | TokenFor 18 | TokenWhen 19 | TokenFunc 20 | TokenConst 21 | TokenVar 22 | TokenBreak 23 | TokenContinue 24 | TokenFallthrough 25 | TokenTry 26 | TokenRecover 27 | TokenFinally 28 | TokenPanic 29 | TokenReturn 30 | TokenNot 31 | TokenIn 32 | TokenId 33 | TokenString 34 | TokenInt 35 | TokenFloat 36 | 37 | // assignment operators 38 | assignOpBegin 39 | TokenEq 40 | TokenColoneq 41 | TokenPluseq 42 | TokenMinuseq 43 | TokenTimeseq 44 | TokenDiveq 45 | TokenPipeeq 46 | TokenAmpeq 47 | TokenTildeeq 48 | assignOpEnd 49 | 50 | // operators in order of precedence from lower-to-higher 51 | binaryOpBegin 52 | TokenPipepipe 53 | 54 | TokenAmpamp 55 | 56 | TokenEqeq 57 | TokenBangeq 58 | 59 | TokenLt 60 | TokenLteq 61 | TokenGt 62 | TokenGteq 63 | 64 | TokenPlus 65 | TokenMinus 66 | TokenPipe 67 | TokenTilde 68 | 69 | TokenTimes 70 | TokenTimestimes 71 | TokenDiv 72 | TokenLtlt 73 | TokenGtgt 74 | TokenAmp 75 | TokenMod 76 | binaryOpEnd 77 | 78 | TokenPlusplus 79 | TokenMinusminus 80 | TokenMinusgt 81 | TokenColon 82 | TokenSemicolon 83 | TokenComma 84 | TokenDot 85 | TokenDotdotdot 86 | TokenBang 87 | TokenQuestion 88 | TokenLparen 89 | TokenRparen 90 | TokenLbrack 91 | TokenRbrack 92 | TokenLbrace 93 | TokenRbrace 94 | TokenIllegal 95 | ) 96 | 97 | var ( 98 | // map keywords to their respective types 99 | keywords = map[string]Token{ 100 | "nil": TokenNil, 101 | "true": TokenTrue, 102 | "false": TokenFalse, 103 | "if": TokenIf, 104 | "else": TokenElse, 105 | "for": TokenFor, 106 | "when": TokenWhen, 107 | "func": TokenFunc, 108 | "const": TokenConst, 109 | "var": TokenVar, 110 | "break": TokenBreak, 111 | "continue": TokenContinue, 112 | "fallthrough": TokenFallthrough, 113 | "try": TokenTry, 114 | "recover": TokenRecover, 115 | "finally": TokenFinally, 116 | "panic": TokenPanic, 117 | "return": TokenReturn, 118 | "not": TokenNot, 119 | "in": TokenIn, 120 | } 121 | 122 | // descriptive representation of tokens 123 | strings = map[Token]string{ 124 | TokenEos: "end of source", 125 | TokenNil: "nil", 126 | TokenTrue: "true", 127 | TokenFalse: "false", 128 | TokenIf: "if", 129 | TokenElse: "else", 130 | TokenFor: "for", 131 | TokenFunc: "func", 132 | TokenWhen: "when", 133 | TokenConst: "const", 134 | TokenVar: "var", 135 | TokenBreak: "break", 136 | TokenContinue: "continue", 137 | TokenFallthrough: "fallthrough", 138 | TokenTry: "try", 139 | TokenRecover: "recover", 140 | TokenFinally: "finally", 141 | TokenPanic: "panic", 142 | TokenReturn: "return", 143 | TokenNot: "not", 144 | TokenIn: "in", 145 | TokenId: "identifier", 146 | TokenString: "string", 147 | TokenInt: "int", 148 | TokenFloat: "float", 149 | TokenPlus: "+", 150 | TokenMinus: "-", 151 | TokenTimes: "*", 152 | TokenDiv: "/", 153 | TokenAmpamp: "&&", 154 | TokenPipepipe: "||", 155 | TokenAmp: "&", 156 | TokenPipe: "|", 157 | TokenTilde: "^", 158 | TokenMod: "%", 159 | TokenLt: "<", 160 | TokenLteq: "<=", 161 | TokenLtlt: "<<", 162 | TokenGt: ">", 163 | TokenGteq: ">=", 164 | TokenGtgt: ">>", 165 | TokenEq: "=", 166 | TokenBangeq: "!=", 167 | TokenColoneq: ":=", 168 | TokenPluseq: "+=", 169 | TokenMinuseq: "-=", 170 | TokenTimeseq: "*=", 171 | TokenDiveq: "/=", 172 | TokenAmpeq: "&=", 173 | TokenPipeeq: "|=", 174 | TokenTildeeq: "^=", 175 | TokenEqeq: "==", 176 | TokenPlusplus: "++", 177 | TokenMinusminus: "--", 178 | TokenMinusgt: "->", 179 | TokenColon: ":", 180 | TokenSemicolon: ";", 181 | TokenComma: ",", 182 | TokenDot: ".", 183 | TokenDotdotdot: "...", 184 | TokenBang: "!", 185 | TokenLparen: "(", 186 | TokenRparen: ")", 187 | TokenLbrack: "[", 188 | TokenRbrack: "]", 189 | TokenLbrace: "{", 190 | TokenRbrace: "}", 191 | TokenIllegal: "illegal", 192 | } 193 | 194 | // operators precedence 195 | precedences = []int{ 196 | 10, 197 | 20, 198 | 30, 30, 199 | 40, 40, 40, 40, 200 | 50, 50, 50, 50, 201 | 60, 60, 60, 60, 60, 60, 60, 202 | } 203 | ) 204 | 205 | func IsAssignOp(tok Token) bool { 206 | return tok >= assignOpBegin && tok <= assignOpEnd 207 | } 208 | 209 | func IsBinaryOp(tok Token) bool { 210 | return tok >= binaryOpBegin && tok <= binaryOpEnd 211 | } 212 | 213 | func IsEqualityOp(tok Token) bool { 214 | return (tok == TokenEqeq || tok == TokenBangeq) 215 | } 216 | 217 | func IsPostfixOp(tok Token) bool { 218 | return (tok == TokenPlusplus || tok == TokenMinusminus) 219 | } 220 | 221 | func IsUnaryOp(tok Token) bool { 222 | return IsPostfixOp(tok) || 223 | (tok == TokenNot || tok == TokenBang || tok == TokenMinus || tok == TokenPlus || tok == TokenTilde) 224 | } 225 | 226 | func CompoundOp(tok Token) Token { 227 | switch tok { 228 | case TokenPluseq: 229 | return TokenPlus 230 | case TokenMinuseq: 231 | return TokenMinus 232 | case TokenTimeseq: 233 | return TokenTimes 234 | case TokenDiveq: 235 | return TokenDiv 236 | case TokenAmpeq: 237 | return TokenAmp 238 | case TokenPipeeq: 239 | return TokenPipe 240 | case TokenTildeeq: 241 | return TokenTilde 242 | } 243 | return Token(-1) 244 | } 245 | 246 | func Keyword(lit string) (Token, bool) { 247 | t, ok := keywords[lit] 248 | return t, ok 249 | } 250 | 251 | func Precedence(tok Token) int { 252 | return precedences[int(tok-binaryOpBegin-1)] 253 | } 254 | 255 | func RightAssociative(tok Token) bool { 256 | return tok == TokenEq 257 | } 258 | 259 | // method for Stringer interface 260 | func (tok Token) String() string { 261 | return strings[tok] 262 | } 263 | -------------------------------------------------------------------------------- /ast/visitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | // Visitor interface 4 | 5 | package ast 6 | 7 | import () 8 | 9 | type Visitor interface { 10 | VisitNil(node *Nil, data interface{}) 11 | VisitBool(node *Bool, data interface{}) 12 | VisitNumber(node *Number, data interface{}) 13 | VisitId(node *Id, data interface{}) 14 | VisitString(node *String, data interface{}) 15 | VisitArray(node *Array, data interface{}) 16 | VisitObjectField(node *ObjectField, data interface{}) 17 | VisitObject(node *Object, data interface{}) 18 | VisitFunction(node *Function, data interface{}) 19 | VisitSelector(node *Selector, data interface{}) 20 | VisitSubscript(node *Subscript, data interface{}) 21 | VisitSlice(node *Slice, data interface{}) 22 | VisitKwArg(node *KwArg, data interface{}) 23 | VisitVarArg(node *VarArg, data interface{}) 24 | VisitCallExpr(node *CallExpr, data interface{}) 25 | VisitPostfixExpr(node *PostfixExpr, data interface{}) 26 | VisitUnaryExpr(node *UnaryExpr, data interface{}) 27 | VisitBinaryExpr(node *BinaryExpr, data interface{}) 28 | VisitTernaryExpr(node *TernaryExpr, data interface{}) 29 | VisitDeclaration(node *Declaration, data interface{}) 30 | VisitAssignment(node *Assignment, data interface{}) 31 | VisitBranchStmt(node *BranchStmt, data interface{}) 32 | VisitReturnStmt(node *ReturnStmt, data interface{}) 33 | VisitPanicStmt(node *PanicStmt, data interface{}) 34 | VisitIfStmt(node *IfStmt, data interface{}) 35 | VisitForIteratorStmt(node *ForIteratorStmt, data interface{}) 36 | VisitForStmt(node *ForStmt, data interface{}) 37 | VisitRecoverBlock(node *RecoverBlock, data interface{}) 38 | VisitTryRecoverStmt(node *TryRecoverStmt, data interface{}) 39 | VisitBlock(node *Block, data interface{}) 40 | } 41 | -------------------------------------------------------------------------------- /builtins.go: -------------------------------------------------------------------------------- 1 | package yo 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func defineBuiltins(vm *VM) { 8 | vm.Define("append", GoFunc(builtinAppend)) 9 | vm.Define("isnumber", GoFunc(builtinIsNumber)) 10 | vm.Define("len", GoFunc(builtinLen)) 11 | vm.Define("println", GoFunc(builtinPrintln)) 12 | vm.Define("type", GoFunc(builtinType)) 13 | } 14 | 15 | func builtinAppend(call *FuncCall) { 16 | if call.NumArgs == uint(0) { 17 | return 18 | } 19 | 20 | ptr := call.Args[0] 21 | arr := ptr.(*Array) 22 | *arr = append(*arr, call.Args[1:]...) 23 | 24 | call.PushReturnValue(ptr) 25 | } 26 | 27 | func builtinIsNumber(call *FuncCall) { 28 | if call.NumArgs <= uint(0) { 29 | call.PushReturnValue(Bool(false)) 30 | } else { 31 | call.PushReturnValue(Bool(call.Args[0].Type() == ValueNumber)) 32 | } 33 | } 34 | 35 | func builtinLen(call *FuncCall) { 36 | if call.NumArgs == uint(0) { 37 | panic("len expects 1 argument") 38 | } else { 39 | n := len(*(call.Args[0].(*Array))) 40 | call.PushReturnValue(Number(n)) 41 | } 42 | } 43 | 44 | func builtinPrintln(call *FuncCall) { 45 | for i := uint(0); i < call.NumArgs; i++ { 46 | fmt.Printf("%v", call.Args[i]) 47 | } 48 | 49 | fmt.Println() 50 | } 51 | 52 | func builtinType(call *FuncCall) { 53 | if call.NumArgs <= uint(0) { 54 | call.PushReturnValue(String("nil")) 55 | } else { 56 | call.PushReturnValue(String(call.Args[0].Type().String())) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bytecode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package yo 4 | 5 | type LineInfo struct { 6 | Instr uint32 // the instruction index 7 | Line uint16 8 | } 9 | 10 | // Contains executable code by the VM and 11 | // static information generated at compilation time. 12 | // All runtime functions reference one of these 13 | type Bytecode struct { 14 | Source string 15 | NumConsts uint32 16 | NumCode uint32 17 | NumLines uint32 18 | NumFuncs uint32 19 | Consts []Value 20 | Code []uint32 21 | Lines []LineInfo 22 | Funcs []*Bytecode 23 | } 24 | 25 | const ( 26 | bytecodeMaxConsts = 0xffff 27 | ) 28 | 29 | func newBytecode(source string) *Bytecode { 30 | return &Bytecode{ 31 | Source: source, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /compile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package yo 4 | 5 | import ( 6 | "fmt" 7 | "github.com/glhrmfrts/yo/ast" 8 | "math" 9 | ) 10 | 11 | type ( 12 | CompileError struct { 13 | Line int 14 | File string 15 | Message string 16 | } 17 | 18 | // holds registers for a expression 19 | exprdata struct { 20 | propagate bool 21 | rega int // rega is default for write 22 | regb int // regb is default for read 23 | } 24 | 25 | // lexical scope of a name 26 | scope int 27 | 28 | // lexical context of a block, (function, loop, branch...) 29 | blockContext int 30 | 31 | nameInfo struct { 32 | isConst bool 33 | value Value // only set if isConst == true 34 | reg int 35 | scope scope 36 | block *compilerBlock 37 | } 38 | 39 | loopInfo struct { 40 | breaks []uint32 41 | continues []uint32 42 | breakTarget uint32 43 | continueTarget uint32 44 | } 45 | 46 | // lexical block structure for compiler 47 | compilerBlock struct { 48 | context blockContext 49 | register int 50 | names map[string]*nameInfo 51 | loop *loopInfo 52 | bytecode *Bytecode 53 | parent *compilerBlock 54 | } 55 | 56 | compiler struct { 57 | lastLine int 58 | filename string 59 | mainFunc *Bytecode 60 | block *compilerBlock 61 | } 62 | ) 63 | 64 | // names lexical scopes 65 | const ( 66 | kScopeLocal scope = iota 67 | kScopeClosure 68 | kScopeGlobal 69 | ) 70 | 71 | // blocks context 72 | const ( 73 | kBlockContextFunc blockContext = iota 74 | kBlockContextLoop 75 | kBlockContextBranch 76 | ) 77 | 78 | // How much registers an array can use at one time 79 | // when it's created in literal form (see VisitArray) 80 | const kArrayMaxRegisters = 10 81 | 82 | func (err *CompileError) Error() string { 83 | return fmt.Sprintf("%s:%d: %s", err.File, err.Line, err.Message) 84 | } 85 | 86 | // compilerBlock 87 | 88 | func newCompilerBlock(bytecode *Bytecode, context blockContext, parent *compilerBlock) *compilerBlock { 89 | return &compilerBlock{ 90 | bytecode: bytecode, 91 | context: context, 92 | parent: parent, 93 | names: make(map[string]*nameInfo, 128), 94 | } 95 | } 96 | 97 | func (b *compilerBlock) nameInfo(name string) (*nameInfo, bool) { 98 | var closures int 99 | block := b 100 | for block != nil { 101 | info, ok := block.names[name] 102 | if ok { 103 | if closures > 0 && info.scope == kScopeLocal { 104 | info.scope = kScopeClosure 105 | } 106 | return info, true 107 | } 108 | if block.context == kBlockContextFunc { 109 | closures++ 110 | } 111 | block = block.parent 112 | } 113 | 114 | return nil, false 115 | } 116 | 117 | func (b *compilerBlock) addNameInfo(name string, info *nameInfo) { 118 | info.block = b 119 | b.names[name] = info 120 | } 121 | 122 | // compiler 123 | 124 | func (c *compiler) error(line int, msg string) { 125 | panic(&CompileError{Line: line, File: c.filename, Message: msg}) 126 | } 127 | 128 | func (c *compiler) emitInstruction(instr uint32, line int) int { 129 | f := c.block.bytecode 130 | f.Code = append(f.Code, instr) 131 | f.NumCode++ 132 | 133 | if line != c.lastLine || f.NumLines == 0 { 134 | f.Lines = append(f.Lines, LineInfo{f.NumCode - 1, uint16(line)}) 135 | f.NumLines++ 136 | c.lastLine = line 137 | } 138 | return int(f.NumCode - 1) 139 | } 140 | 141 | func (c *compiler) modifyInstruction(index int, instr uint32) bool { 142 | f := c.block.bytecode 143 | if uint32(index) < f.NumCode { 144 | f.Code[index] = instr 145 | return true 146 | } 147 | return false 148 | } 149 | 150 | func (c *compiler) emitAB(op Opcode, a, b, line int) int { 151 | return c.emitInstruction(OpNewAB(op, a, b), line) 152 | } 153 | 154 | func (cc *compiler) emitABC(op Opcode, a, b, c, line int) int { 155 | return cc.emitInstruction(OpNewABC(op, a, b, c), line) 156 | } 157 | 158 | func (c *compiler) emitABx(op Opcode, a, b, line int) int { 159 | return c.emitInstruction(OpNewABx(op, a, b), line) 160 | } 161 | 162 | func (c *compiler) emitAsBx(op Opcode, a, b, line int) int { 163 | return c.emitInstruction(OpNewAsBx(op, a, b), line) 164 | } 165 | 166 | func (c *compiler) modifyABx(index int, op Opcode, a, b int) bool { 167 | return c.modifyInstruction(index, OpNewABx(op, a, b)) 168 | } 169 | 170 | func (c *compiler) modifyAsBx(index int, op Opcode, a, b int) bool { 171 | return c.modifyInstruction(index, OpNewAsBx(op, a, b)) 172 | } 173 | 174 | func (c *compiler) newLabel() uint32 { 175 | return c.block.bytecode.NumCode 176 | } 177 | 178 | func (c *compiler) labelOffset(label uint32) int { 179 | return int(c.block.bytecode.NumCode - label) 180 | } 181 | 182 | func (c *compiler) genRegister() int { 183 | id := c.block.register 184 | c.block.register++ 185 | return id 186 | } 187 | 188 | func (c *compiler) declareLocalVar(name string, reg int) { 189 | if _, ok := c.block.names[name]; ok { 190 | c.error(c.lastLine, fmt.Sprintf("cannot redeclare '%s'", name)) 191 | } 192 | c.block.addNameInfo(name, &nameInfo{false, nil, reg, kScopeLocal, c.block}) 193 | } 194 | 195 | func (c *compiler) enterBlock(context blockContext) { 196 | assert(c.block != nil, "c.block enterBlock") 197 | block := newCompilerBlock(c.block.bytecode, context, c.block) 198 | block.register = c.block.register 199 | 200 | if context == kBlockContextLoop { 201 | block.loop = &loopInfo{} 202 | } else if c.block.loop != nil { 203 | block.loop = c.block.loop 204 | } 205 | c.block = block 206 | } 207 | 208 | func (c *compiler) leaveBlock() { 209 | block := c.block 210 | if block.context == kBlockContextLoop { 211 | loop := block.loop 212 | for _, index := range loop.breaks { 213 | c.modifyAsBx(int(index), OpJmp, 0, int(loop.breakTarget-index-1)) 214 | } 215 | for _, index := range loop.continues { 216 | c.modifyAsBx(int(index), OpJmp, 0, int(loop.continueTarget-index-1)) 217 | } 218 | } 219 | c.block = block.parent 220 | } 221 | 222 | func (c *compiler) insideLoop() bool { 223 | block := c.block 224 | for block != nil { 225 | if block.context == kBlockContextLoop { 226 | return true 227 | } 228 | if block.context == kBlockContextFunc { 229 | return false 230 | } 231 | block = block.parent 232 | } 233 | return false 234 | } 235 | 236 | // Add a constant to the current bytecodetype's constant pool 237 | // and return it's index 238 | func (c *compiler) addConst(value Value) int { 239 | f := c.block.bytecode 240 | valueType := value.Type() 241 | for i, c := range f.Consts { 242 | if c.Type() == valueType && c == value { 243 | return i 244 | } 245 | } 246 | if f.NumConsts > bytecodeMaxConsts-1 { 247 | c.error(0, "too many constants") // should never happen 248 | } 249 | f.Consts = append(f.Consts, value) 250 | f.NumConsts++ 251 | return int(f.NumConsts - 1) 252 | } 253 | 254 | // Try to "constant fold" an expression 255 | func (c *compiler) constFold(node ast.Node) (Value, bool) { 256 | switch t := node.(type) { 257 | case *ast.Number: 258 | return Number(t.Value), true 259 | case *ast.Bool: 260 | return Bool(t.Value), true 261 | case *ast.String: 262 | return String(t.Value), true 263 | case *ast.Id: 264 | info, ok := c.block.nameInfo(t.Value) 265 | if ok && info.isConst { 266 | return info.value, true 267 | } 268 | case *ast.CallExpr: 269 | id, ok := t.Left.(*ast.Id) 270 | if ok && len(t.Args) == 1 { 271 | rv, ok := c.constFold(t.Args[0]) 272 | if !ok { 273 | return nil, false 274 | } 275 | if id.Value == "string" { 276 | return String(rv.String()), true 277 | } else if id.Value == "number" { 278 | switch rv.Type() { 279 | case ValueNumber: 280 | return rv, true 281 | case ValueString: 282 | n, err := parseNumber(rv.String()) 283 | if err != nil { 284 | return nil, false 285 | } 286 | return Number(n), true 287 | } 288 | } else if id.Value == "bool" { 289 | return Bool(rv.ToBool()), true 290 | } 291 | } 292 | case *ast.UnaryExpr: 293 | if t.Op == ast.TokenMinus { 294 | val, ok := c.constFold(t.Right) 295 | if ok && val.Type() == ValueNumber { 296 | f64, _ := val.assertFloat64() 297 | return Number(-f64), true 298 | } 299 | return nil, false 300 | } else { 301 | // 'not' operator 302 | val, ok := c.constFold(t.Right) 303 | if ok && val.Type() == ValueBool { 304 | bool_, _ := val.assertBool() 305 | return Bool(!bool_), true 306 | } 307 | return nil, false 308 | } 309 | case *ast.BinaryExpr: 310 | left, leftOk := c.constFold(t.Left) 311 | right, rightOk := c.constFold(t.Right) 312 | if leftOk && rightOk { 313 | var ret Value 314 | if left.Type() != right.Type() { 315 | if t.Op == ast.TokenEqeq { 316 | return Bool(false), true 317 | } 318 | if t.Op == ast.TokenBangeq { 319 | return Bool(true), true 320 | } 321 | 322 | return nil, false 323 | } 324 | lf64, ok := left.assertFloat64() 325 | rf64, _ := right.assertFloat64() 326 | if !ok { 327 | goto boolOps 328 | } 329 | 330 | // first check all arithmetic/relational operations 331 | switch t.Op { 332 | case ast.TokenPlus: 333 | ret = Number(lf64 + rf64) 334 | case ast.TokenMinus: 335 | ret = Number(lf64 - rf64) 336 | case ast.TokenTimes: 337 | ret = Number(lf64 * rf64) 338 | case ast.TokenDiv: 339 | ret = Number(lf64 / rf64) 340 | case ast.TokenTimestimes: 341 | ret = Number(math.Pow(lf64, rf64)) 342 | case ast.TokenLt: 343 | ret = Bool(lf64 < rf64) 344 | case ast.TokenLteq: 345 | ret = Bool(lf64 <= rf64) 346 | case ast.TokenGt: 347 | ret = Bool(lf64 > rf64) 348 | case ast.TokenGteq: 349 | ret = Bool(lf64 >= rf64) 350 | case ast.TokenEqeq: 351 | ret = Bool(lf64 == rf64) 352 | } 353 | if ret != nil { 354 | return ret, true 355 | } 356 | 357 | boolOps: 358 | // not arithmetic/relational, maybe logic? 359 | lb, ok := left.assertBool() 360 | rb, _ := right.assertBool() 361 | if !ok { 362 | goto stringOps 363 | } 364 | 365 | switch t.Op { 366 | case ast.TokenAmpamp: 367 | return Bool(lb && rb), true 368 | case ast.TokenPipepipe: 369 | return Bool(lb || rb), true 370 | } 371 | 372 | stringOps: 373 | ls, ok := left.assertString() 374 | rs, _ := right.assertString() 375 | if !ok { 376 | return nil, false 377 | } 378 | 379 | switch t.Op { 380 | case ast.TokenPlus: 381 | return String(ls + rs), true 382 | case ast.TokenLt: 383 | ret = Bool(ls < rs) 384 | case ast.TokenLteq: 385 | ret = Bool(ls <= rs) 386 | case ast.TokenGt: 387 | ret = Bool(ls > rs) 388 | case ast.TokenGteq: 389 | ret = Bool(ls >= rs) 390 | case ast.TokenEqeq: 391 | return Bool(ls == rs), true 392 | case ast.TokenBangeq: 393 | return Bool(ls != rs), true 394 | } 395 | } 396 | } 397 | return nil, false 398 | } 399 | 400 | // declare local variables 401 | // assignments are done in sequence, since the registers are created as needed 402 | func (c *compiler) declare(names []*ast.Id, values []ast.Node) { 403 | var isCall, isUnpack bool 404 | nameCount, valueCount := len(names), len(values) 405 | if valueCount > 0 { 406 | _, isCall = values[valueCount-1].(*ast.CallExpr) 407 | _, isUnpack = values[valueCount-1].(*ast.VarArg) 408 | } 409 | start := c.block.register 410 | end := start + nameCount - 1 411 | for i, id := range names { 412 | _, ok := c.block.names[id.Value] 413 | if ok { 414 | c.error(id.NodeInfo.Line, fmt.Sprintf("cannot redeclare '%s'", id.Value)) 415 | } 416 | reg := c.genRegister() 417 | 418 | exprdata := exprdata{false, reg, reg} 419 | if i == valueCount-1 && (isCall || isUnpack) { 420 | // last expression receives all the remaining registers 421 | // in case it's a function call with multiple return values 422 | rem := i + 1 423 | for rem < nameCount { 424 | // reserve the registers 425 | id := names[rem] 426 | _, ok := c.block.names[id.Value] 427 | if ok { 428 | c.error(id.NodeInfo.Line, fmt.Sprintf("cannot redeclare '%s'", id.Value)) 429 | } 430 | end = c.genRegister() 431 | c.block.addNameInfo(id.Value, &nameInfo{false, nil, end, kScopeLocal, c.block}) 432 | rem++ 433 | } 434 | exprdata.regb, start = end, end+1 435 | values[i].Accept(c, &exprdata) 436 | } else if i < valueCount { 437 | values[i].Accept(c, &exprdata) 438 | start = reg + 1 439 | } 440 | 441 | // add name info after the value (the variable should not be visible to it's own initializer) 442 | c.block.addNameInfo(id.Value, &nameInfo{false, nil, reg, kScopeLocal, c.block}) 443 | } 444 | if end >= start { 445 | // variables without initializer are set to nil 446 | c.emitAB(OpLoadnil, start, end, names[0].NodeInfo.Line) 447 | } 448 | } 449 | 450 | func (c *compiler) assignmentHelper(left ast.Node, assignReg int, valueReg int) { 451 | switch v := left.(type) { 452 | case *ast.Id: 453 | var scope scope 454 | info, ok := c.block.nameInfo(v.Value) 455 | if !ok { 456 | scope = kScopeGlobal 457 | } else { 458 | scope = info.scope 459 | } 460 | switch scope { 461 | case kScopeLocal: 462 | c.emitAB(OpMove, info.reg, valueReg, v.NodeInfo.Line) 463 | case kScopeClosure, kScopeGlobal: 464 | op := OpSetglobal 465 | if scope == kScopeClosure { 466 | op = OpSetFree 467 | } 468 | c.emitABx(op, valueReg, c.addConst(String(v.Value)), v.NodeInfo.Line) 469 | } 470 | case *ast.Subscript: 471 | arrData := exprdata{true, assignReg, assignReg} 472 | v.Left.Accept(c, &arrData) 473 | arrReg := arrData.regb 474 | 475 | subData := exprdata{true, assignReg, assignReg} 476 | v.Right.Accept(c, &subData) 477 | subReg := subData.regb 478 | c.emitABC(OpSetIndex, arrReg, subReg, valueReg, v.NodeInfo.Line) 479 | case *ast.Selector: 480 | objData := exprdata{true, assignReg, assignReg} 481 | v.Left.Accept(c, &objData) 482 | objReg := objData.regb 483 | key := OpConstOffset + c.addConst(String(v.Value)) 484 | 485 | c.emitABC(OpSetIndex, objReg, key, valueReg, v.NodeInfo.Line) 486 | } 487 | } 488 | 489 | func (c *compiler) branchConditionHelper(cond, then, else_ ast.Node, reg int) { 490 | ternaryData := exprdata{true, reg + 1, reg + 1} 491 | cond.Accept(c, &ternaryData) 492 | condr := ternaryData.regb 493 | jmpInstr := c.emitAsBx(OpJmpfalse, condr, 0, c.lastLine) 494 | thenLabel := c.newLabel() 495 | 496 | ternaryData = exprdata{false, reg, reg} 497 | then.Accept(c, &ternaryData) 498 | c.modifyAsBx(jmpInstr, OpJmpfalse, condr, c.labelOffset(thenLabel)) 499 | 500 | if else_ != nil { 501 | successInstr := c.emitAsBx(OpJmp, 0, 0, c.lastLine) 502 | 503 | elseLabel := c.newLabel() 504 | ternaryData = exprdata{false, reg, reg} 505 | else_.Accept(c, &ternaryData) 506 | 507 | c.modifyAsBx(successInstr, OpJmp, 0, c.labelOffset(elseLabel)) 508 | } 509 | } 510 | 511 | func (c *compiler) functionReturnGuard() { 512 | last := c.block.bytecode.Code[c.block.bytecode.NumCode-1] 513 | if OpGetOpcode(last) != OpReturn { 514 | c.emitAB(OpReturn, 0, 0, c.lastLine) 515 | } 516 | } 517 | 518 | // 519 | // visitor interface 520 | // 521 | 522 | func (c *compiler) VisitNil(node *ast.Nil, data interface{}) { 523 | var rega, regb int 524 | expr, ok := data.(*exprdata) 525 | if ok { 526 | rega, regb = expr.rega, expr.regb 527 | if rega > regb { 528 | regb = rega 529 | } 530 | } else { 531 | rega = c.genRegister() 532 | regb = rega 533 | } 534 | c.emitAB(OpLoadnil, rega, regb, node.NodeInfo.Line) 535 | } 536 | 537 | func (c *compiler) VisitBool(node *ast.Bool, data interface{}) { 538 | var reg int 539 | value := Bool(node.Value) 540 | expr, ok := data.(*exprdata) 541 | if ok && expr.propagate { 542 | expr.regb = OpConstOffset + c.addConst(value) 543 | return 544 | } else if ok { 545 | reg = expr.rega 546 | } else { 547 | reg = c.genRegister() 548 | } 549 | c.emitABx(OpLoadconst, reg, c.addConst(value), node.NodeInfo.Line) 550 | } 551 | 552 | func (c *compiler) VisitNumber(node *ast.Number, data interface{}) { 553 | var reg int 554 | value := Number(node.Value) 555 | expr, ok := data.(*exprdata) 556 | if ok && expr.propagate { 557 | expr.regb = OpConstOffset + c.addConst(value) 558 | return 559 | } else if ok { 560 | reg = expr.rega 561 | } else { 562 | reg = c.genRegister() 563 | } 564 | c.emitABx(OpLoadconst, reg, c.addConst(value), node.NodeInfo.Line) 565 | } 566 | 567 | func (c *compiler) VisitString(node *ast.String, data interface{}) { 568 | var reg int 569 | value := String(node.Value) 570 | expr, ok := data.(*exprdata) 571 | if ok && expr.propagate { 572 | expr.regb = OpConstOffset + c.addConst(value) 573 | return 574 | } else if ok { 575 | reg = expr.rega 576 | } else { 577 | reg = c.genRegister() 578 | } 579 | c.emitABx(OpLoadconst, reg, c.addConst(value), node.NodeInfo.Line) 580 | } 581 | 582 | func (c *compiler) VisitId(node *ast.Id, data interface{}) { 583 | var reg int 584 | var scope scope = -1 585 | expr, exprok := data.(*exprdata) 586 | if !exprok { 587 | reg = c.genRegister() 588 | } else { 589 | reg = expr.rega 590 | } 591 | info, ok := c.block.nameInfo(node.Value) 592 | if ok && info.isConst { 593 | if exprok && expr.propagate { 594 | expr.regb = OpConstOffset + c.addConst(info.value) 595 | return 596 | } 597 | c.emitABx(OpLoadconst, reg, c.addConst(info.value), node.NodeInfo.Line) 598 | } else if ok { 599 | scope = info.scope 600 | } else { 601 | // assume global if it can't be found in the lexical scope 602 | scope = kScopeGlobal 603 | } 604 | switch scope { 605 | case kScopeLocal: 606 | if exprok && expr.propagate { 607 | expr.regb = info.reg 608 | return 609 | } 610 | c.emitAB(OpMove, reg, info.reg, node.NodeInfo.Line) 611 | case kScopeClosure, kScopeGlobal: 612 | op := OpLoadglobal 613 | if scope == kScopeClosure { 614 | op = OpLoadFree 615 | } 616 | c.emitABx(op, reg, c.addConst(String(node.Value)), node.NodeInfo.Line) 617 | if exprok && expr.propagate { 618 | expr.regb = reg 619 | } 620 | } 621 | } 622 | 623 | func (c *compiler) VisitArray(node *ast.Array, data interface{}) { 624 | var reg int 625 | expr, exprok := data.(*exprdata) 626 | if exprok { 627 | reg = expr.rega 628 | } else { 629 | reg = c.genRegister() 630 | } 631 | length := len(node.Elements) 632 | c.emitAB(OpArray, reg, 0, node.NodeInfo.Line) 633 | 634 | times := length/kArrayMaxRegisters + 1 635 | for t := 0; t < times; t++ { 636 | start, end := t*kArrayMaxRegisters, (t+1)*kArrayMaxRegisters 637 | end = int(math.Min(float64(end-start), float64(length-start))) 638 | if end == 0 { 639 | break 640 | } 641 | for i := 0; i < end; i++ { 642 | el := node.Elements[start+i] 643 | exprdata := exprdata{false, reg + i + 1, reg + i + 1} 644 | el.Accept(c, &exprdata) 645 | } 646 | c.emitAB(OpAppend, reg, end, node.NodeInfo.Line) 647 | } 648 | if exprok && expr.propagate { 649 | expr.regb = reg 650 | } 651 | } 652 | 653 | func (c *compiler) VisitObjectField(node *ast.ObjectField, data interface{}) { 654 | expr, exprok := data.(*exprdata) 655 | assert(exprok, "ObjectField exprok") 656 | objreg := expr.rega 657 | key := OpConstOffset + c.addConst(String(node.Key)) 658 | 659 | valueData := exprdata{true, objreg + 1, objreg + 1} 660 | node.Value.Accept(c, &valueData) 661 | value := valueData.regb 662 | 663 | c.emitABC(OpSetIndex, objreg, key, value, node.NodeInfo.Line) 664 | } 665 | 666 | func (c *compiler) VisitObject(node *ast.Object, data interface{}) { 667 | var reg int 668 | expr, exprok := data.(*exprdata) 669 | if exprok { 670 | reg = expr.rega 671 | } else { 672 | reg = c.genRegister() 673 | } 674 | c.emitAB(OpObject, reg, 0, node.NodeInfo.Line) 675 | for _, field := range node.Fields { 676 | fieldData := exprdata{false, reg, reg} 677 | field.Accept(c, &fieldData) 678 | } 679 | if exprok && expr.propagate { 680 | expr.regb = reg 681 | } 682 | } 683 | 684 | func (c *compiler) VisitFunction(node *ast.Function, data interface{}) { 685 | var reg int 686 | expr, exprok := data.(*exprdata) 687 | if exprok { 688 | reg = expr.rega 689 | } else { 690 | reg = c.genRegister() 691 | } 692 | parent := c.block.bytecode 693 | bytecode := newBytecode(parent.Source) 694 | 695 | block := newCompilerBlock(bytecode, kBlockContextFunc, c.block) 696 | c.block = block 697 | 698 | index := int(parent.NumFuncs) 699 | parent.Funcs = append(parent.Funcs, bytecode) 700 | parent.NumFuncs++ 701 | 702 | // insert 'this' into scope 703 | c.declareLocalVar("this", c.genRegister()) 704 | 705 | // insert arguments into scope 706 | for _, n := range node.Args { 707 | switch arg := n.(type) { 708 | case *ast.Id: 709 | reg := c.genRegister() 710 | c.block.addNameInfo(arg.Value, &nameInfo{false, nil, reg, kScopeLocal, c.block}) 711 | } 712 | } 713 | 714 | node.Body.Accept(c, nil) 715 | c.functionReturnGuard() 716 | 717 | c.block = c.block.parent 718 | c.emitABx(OpFunc, reg, index, node.NodeInfo.Line) 719 | 720 | if node.Name != nil { 721 | switch name := node.Name.(type) { 722 | case *ast.Id: 723 | c.declareLocalVar(name.Value, reg) 724 | default: 725 | c.assignmentHelper(name, reg+1, reg) 726 | } 727 | } 728 | if exprok && expr.propagate { 729 | expr.regb = reg 730 | } 731 | } 732 | 733 | func (c *compiler) VisitSelector(node *ast.Selector, data interface{}) { 734 | var reg int 735 | expr, exprok := data.(*exprdata) 736 | if exprok { 737 | reg = expr.rega 738 | } else { 739 | reg = c.genRegister() 740 | } 741 | objData := exprdata{true, reg + 1, reg + 1} 742 | node.Left.Accept(c, &objData) 743 | objReg := objData.regb 744 | 745 | key := OpConstOffset + c.addConst(String(node.Value)) 746 | c.emitABC(OpGetIndex, reg, objReg, key, node.NodeInfo.Line) 747 | if exprok && expr.propagate { 748 | expr.regb = objReg 749 | } 750 | } 751 | 752 | func (c *compiler) VisitSubscript(node *ast.Subscript, data interface{}) { 753 | var reg int 754 | expr, exprok := data.(*exprdata) 755 | if exprok { 756 | reg = expr.rega 757 | } else { 758 | reg = c.genRegister() 759 | } 760 | arrData := exprdata{true, reg + 1, reg + 1} 761 | node.Left.Accept(c, &arrData) 762 | arrReg := arrData.regb 763 | 764 | _, ok := node.Right.(*ast.Slice) 765 | if ok { 766 | // TODO: generate code for slice 767 | return 768 | } 769 | 770 | indexData := exprdata{true, reg + 1, reg + 1} 771 | node.Right.Accept(c, &indexData) 772 | indexReg := indexData.regb 773 | c.emitABC(OpGetIndex, reg, arrReg, indexReg, node.NodeInfo.Line) 774 | 775 | if exprok && expr.propagate { 776 | expr.regb = reg 777 | } 778 | } 779 | 780 | func (c *compiler) VisitSlice(node *ast.Slice, data interface{}) { 781 | 782 | } 783 | 784 | func (c *compiler) VisitKwArg(node *ast.KwArg, data interface{}) { 785 | 786 | } 787 | 788 | func (c *compiler) VisitVarArg(node *ast.VarArg, data interface{}) { 789 | 790 | } 791 | 792 | func (c *compiler) VisitCallExpr(node *ast.CallExpr, data interface{}) { 793 | var startReg, endReg, resultCount int 794 | expr, exprok := data.(*exprdata) 795 | if exprok { 796 | startReg, endReg = expr.rega, expr.regb 797 | resultCount = endReg - startReg + 1 798 | } else { 799 | startReg = c.genRegister() 800 | endReg = startReg 801 | resultCount = 1 802 | } 803 | 804 | // check if it's a type conversion (string, number, bool) 805 | v, ok := c.constFold(node) 806 | if ok { 807 | c.emitABx(OpLoadconst, startReg, c.addConst(v), node.NodeInfo.Line) 808 | return 809 | } 810 | 811 | argCount := len(node.Args) 812 | var op Opcode 813 | switch node.Left.(type) { 814 | case *ast.Selector: 815 | op = OpCallmethod 816 | callerData := exprdata{true, startReg, startReg} 817 | node.Left.Accept(c, &callerData) 818 | objReg := callerData.regb 819 | 820 | // insert object as first argument 821 | endReg += 1 822 | argCount += 1 823 | c.emitAB(OpMove, endReg, objReg, node.NodeInfo.Line) 824 | default: 825 | op = OpCall 826 | callerData := exprdata{false, startReg, startReg} 827 | node.Left.Accept(c, &callerData) 828 | } 829 | 830 | for i, arg := range node.Args { 831 | reg := endReg + i + 1 832 | argData := exprdata{false, reg, reg} 833 | arg.Accept(c, &argData) 834 | } 835 | 836 | c.emitABC(op, startReg, resultCount, argCount, node.NodeInfo.Line) 837 | } 838 | 839 | func (c *compiler) VisitPostfixExpr(node *ast.PostfixExpr, data interface{}) { 840 | var reg int 841 | expr, exprok := data.(*exprdata) 842 | if exprok { 843 | reg = expr.rega 844 | } else { 845 | reg = c.genRegister() 846 | } 847 | var op Opcode 848 | switch node.Op { 849 | case ast.TokenPlusplus: 850 | op = OpAdd 851 | case ast.TokenMinusminus: 852 | op = OpSub 853 | } 854 | leftdata := exprdata{true, reg, reg} 855 | node.Left.Accept(c, &leftdata) 856 | left := leftdata.regb 857 | one := OpConstOffset + c.addConst(Number(1)) 858 | 859 | // don't bother moving if we're not in an expression 860 | if exprok { 861 | c.emitAB(OpMove, reg, left, node.NodeInfo.Line) 862 | } 863 | c.emitABC(op, left, left, one, node.NodeInfo.Line) 864 | } 865 | 866 | func (c *compiler) VisitUnaryExpr(node *ast.UnaryExpr, data interface{}) { 867 | var reg int 868 | expr, exprok := data.(*exprdata) 869 | if exprok { 870 | reg = expr.rega 871 | } else { 872 | reg = c.genRegister() 873 | } 874 | value, ok := c.constFold(node) 875 | if ok { 876 | if exprok && expr.propagate { 877 | expr.regb = OpConstOffset + c.addConst(value) 878 | return 879 | } 880 | c.emitABx(OpLoadconst, reg, c.addConst(value), node.NodeInfo.Line) 881 | } else if ast.IsPostfixOp(node.Op) { 882 | op := OpAdd 883 | if node.Op == ast.TokenMinusminus { 884 | op = OpSub 885 | } 886 | exprdata := exprdata{true, reg, reg} 887 | node.Right.Accept(c, &exprdata) 888 | one := OpConstOffset + c.addConst(Number(1)) 889 | c.emitABC(op, exprdata.regb, exprdata.regb, one, node.NodeInfo.Line) 890 | 891 | // don't bother moving if we're not in an expression 892 | if exprok { 893 | c.emitAB(OpMove, reg, exprdata.regb, node.NodeInfo.Line) 894 | } 895 | } else { 896 | var op Opcode 897 | switch node.Op { 898 | case ast.TokenMinus: 899 | op = OpUnm 900 | case ast.TokenNot, ast.TokenBang: 901 | op = OpNot 902 | case ast.TokenTilde: 903 | op = OpCmpl 904 | } 905 | exprdata := exprdata{true, reg, reg} 906 | node.Right.Accept(c, &exprdata) 907 | c.emitABx(op, reg, exprdata.regb, node.NodeInfo.Line) 908 | if exprok && expr.propagate { 909 | expr.regb = reg 910 | } 911 | } 912 | } 913 | 914 | func (c *compiler) VisitBinaryExpr(node *ast.BinaryExpr, data interface{}) { 915 | var reg int 916 | expr, exprok := data.(*exprdata) 917 | if exprok { 918 | reg = expr.rega 919 | } else { 920 | reg = c.genRegister() 921 | } 922 | value, ok := c.constFold(node) 923 | if ok { 924 | if exprok && expr.propagate { 925 | expr.regb = OpConstOffset + c.addConst(value) 926 | return 927 | } 928 | c.emitABx(OpLoadconst, reg, c.addConst(value), node.NodeInfo.Line) 929 | } else { 930 | if isAnd, isOr := node.Op == ast.TokenAmpamp, node.Op == ast.TokenPipepipe; isAnd || isOr { 931 | var op Opcode 932 | if isAnd { 933 | op = OpJmpfalse 934 | } else { 935 | op = OpJmptrue 936 | } 937 | exprdata := exprdata{expr.propagate, reg, reg} 938 | node.Left.Accept(c, &exprdata) 939 | left := exprdata.regb 940 | 941 | jmpInstr := c.emitAsBx(op, left, 0, node.NodeInfo.Line) 942 | rightLabel := c.newLabel() 943 | 944 | node.Right.Accept(c, &exprdata) 945 | c.modifyAsBx(jmpInstr, op, left, c.labelOffset(rightLabel)) 946 | return 947 | } 948 | 949 | var op Opcode 950 | switch node.Op { 951 | case ast.TokenPlus: 952 | op = OpAdd 953 | case ast.TokenMinus: 954 | op = OpSub 955 | case ast.TokenTimes: 956 | op = OpMul 957 | case ast.TokenDiv: 958 | op = OpDiv 959 | case ast.TokenTimestimes: 960 | op = OpPow 961 | case ast.TokenLtlt: 962 | op = OpShl 963 | case ast.TokenGtgt: 964 | op = OpShr 965 | case ast.TokenAmp: 966 | op = OpAnd 967 | case ast.TokenPipe: 968 | op = OpOr 969 | case ast.TokenTilde: 970 | op = OpXor 971 | case ast.TokenLt, ast.TokenGteq: 972 | op = OpLt 973 | case ast.TokenLteq, ast.TokenGt: 974 | op = OpLe 975 | case ast.TokenEq: 976 | op = OpEq 977 | case ast.TokenBangeq: 978 | op = OpNe 979 | } 980 | 981 | exprdata := exprdata{true, reg, 0} 982 | node.Left.Accept(c, &exprdata) 983 | left := exprdata.regb 984 | 985 | // temp register for right expression 986 | exprdata.rega += 1 987 | node.Right.Accept(c, &exprdata) 988 | right := exprdata.regb 989 | 990 | if node.Op == ast.TokenGt || node.Op == ast.TokenGteq { 991 | // invert operands 992 | c.emitABC(op, reg, right, left, node.NodeInfo.Line) 993 | } else { 994 | c.emitABC(op, reg, left, right, node.NodeInfo.Line) 995 | } 996 | if exprok && expr.propagate { 997 | expr.regb = reg 998 | } 999 | } 1000 | } 1001 | 1002 | func (c *compiler) VisitTernaryExpr(node *ast.TernaryExpr, data interface{}) { 1003 | var reg int 1004 | expr, exprok := data.(*exprdata) 1005 | if exprok { 1006 | reg = expr.rega 1007 | } else { 1008 | reg = c.genRegister() 1009 | } 1010 | c.branchConditionHelper(node.Cond, node.Then, node.Else, reg) 1011 | } 1012 | 1013 | func (c *compiler) VisitDeclaration(node *ast.Declaration, data interface{}) { 1014 | valueCount := len(node.Right) 1015 | if node.IsConst { 1016 | for i, id := range node.Left { 1017 | _, ok := c.block.names[id.Value] 1018 | if ok { 1019 | c.error(node.NodeInfo.Line, fmt.Sprintf("cannot redeclare '%s'", id.Value)) 1020 | } 1021 | if i >= valueCount { 1022 | c.error(node.NodeInfo.Line, fmt.Sprintf("const '%s' without initializer", id.Value)) 1023 | } 1024 | value, ok := c.constFold(node.Right[i]) 1025 | if !ok { 1026 | c.error(node.NodeInfo.Line, fmt.Sprintf("const '%s' initializer is not a constant", id.Value)) 1027 | } 1028 | c.block.addNameInfo(id.Value, &nameInfo{true, value, 0, kScopeLocal, c.block}) 1029 | } 1030 | return 1031 | } 1032 | c.declare(node.Left, node.Right) 1033 | } 1034 | 1035 | func (c *compiler) VisitAssignment(node *ast.Assignment, data interface{}) { 1036 | if node.Op == ast.TokenColoneq { 1037 | // short variable declaration 1038 | var names []*ast.Id 1039 | for _, id := range node.Left { 1040 | names = append(names, id.(*ast.Id)) 1041 | } 1042 | c.declare(names, node.Right) 1043 | return 1044 | } else if node.Op != ast.TokenEq { 1045 | // compound assignment 1046 | // a += b -> a = a + b 1047 | bin := ast.BinaryExpr{Op: ast.CompoundOp(node.Op), Left: node.Left[0], Right: node.Right[0]} 1048 | fake := ast.Assignment{Op: ast.TokenEq, Left: node.Left, Right: []ast.Node{&bin}} 1049 | fake.Accept(c, nil) 1050 | return 1051 | } 1052 | 1053 | // regular assignment, if the left-side is an identifier 1054 | // then it has to be declared already 1055 | varCount, valueCount := len(node.Left), len(node.Right) 1056 | _, isCall := node.Right[valueCount-1].(*ast.CallExpr) 1057 | _, isUnpack := node.Right[valueCount-1].(*ast.VarArg) 1058 | start := c.block.register 1059 | current := start 1060 | end := start + varCount - 1 1061 | 1062 | // evaluate all expressions first with temp registers 1063 | for i, _ := range node.Left { 1064 | reg := start + i 1065 | exprdata := exprdata{false, reg, reg} 1066 | if i == valueCount-1 && (isCall || isUnpack) { 1067 | exprdata.regb, current = end, end 1068 | node.Right[i].Accept(c, &exprdata) 1069 | break 1070 | } 1071 | if i < valueCount { 1072 | node.Right[i].Accept(c, &exprdata) 1073 | current = reg + 1 1074 | } 1075 | } 1076 | // assign the results to the variables 1077 | for i, variable := range node.Left { 1078 | valueReg := start + i 1079 | 1080 | // don't touch variables without a corresponding value 1081 | if valueReg > current { 1082 | break 1083 | } 1084 | c.assignmentHelper(variable, current+1, valueReg) 1085 | } 1086 | } 1087 | 1088 | func (c *compiler) VisitBranchStmt(node *ast.BranchStmt, data interface{}) { 1089 | if !c.insideLoop() { 1090 | c.error(node.NodeInfo.Line, fmt.Sprintf("%s outside loop", node.Type)) 1091 | } 1092 | instr := c.emitAsBx(OpJmp, 0, 0, node.NodeInfo.Line) 1093 | switch node.Type { 1094 | case ast.TokenContinue: 1095 | c.block.loop.continues = append(c.block.loop.continues, uint32(instr)) 1096 | case ast.TokenBreak: 1097 | c.block.loop.breaks = append(c.block.loop.breaks, uint32(instr)) 1098 | } 1099 | } 1100 | 1101 | func (c *compiler) VisitReturnStmt(node *ast.ReturnStmt, data interface{}) { 1102 | start := c.block.register 1103 | for _, v := range node.Values { 1104 | reg := c.genRegister() 1105 | data := exprdata{false, reg, reg} 1106 | v.Accept(c, &data) 1107 | } 1108 | c.emitAB(OpReturn, start, len(node.Values), node.NodeInfo.Line) 1109 | } 1110 | 1111 | func (c *compiler) VisitPanicStmt(node *ast.PanicStmt, data interface{}) { 1112 | 1113 | } 1114 | 1115 | func (c *compiler) VisitIfStmt(node *ast.IfStmt, data interface{}) { 1116 | _, ok := data.(*exprdata) 1117 | if !ok { 1118 | c.enterBlock(kBlockContextBranch) 1119 | defer c.leaveBlock() 1120 | } 1121 | if node.Init != nil { 1122 | node.Init.Accept(c, nil) 1123 | } 1124 | c.branchConditionHelper(node.Cond, node.Body, node.Else, c.block.register) 1125 | } 1126 | 1127 | func (c *compiler) VisitForIteratorStmt(node *ast.ForIteratorStmt, data interface{}) { 1128 | c.enterBlock(kBlockContextLoop) 1129 | defer c.leaveBlock() 1130 | 1131 | arrReg := c.genRegister() 1132 | lenReg := c.genRegister() 1133 | keyReg := c.genRegister() 1134 | idxReg := c.genRegister() 1135 | valReg := c.genRegister() 1136 | colReg := c.genRegister() 1137 | 1138 | collectionData := exprdata{false, colReg, colReg} 1139 | node.Collection.Accept(c, &collectionData) 1140 | c.emitAB(OpForbegin, arrReg, colReg, node.NodeInfo.Line) 1141 | c.emitABx(OpLoadconst, idxReg, c.addConst(Number(0)), c.lastLine) 1142 | 1143 | if node.Value == nil { 1144 | c.declareLocalVar(node.Key.Value, valReg) 1145 | } else { 1146 | c.declareLocalVar(node.Key.Value, keyReg) 1147 | c.declareLocalVar(node.Value.Value, valReg) 1148 | } 1149 | 1150 | testLabel := c.newLabel() 1151 | testReg := c.block.register 1152 | c.emitABC(OpLt, testReg, idxReg, lenReg, c.lastLine) 1153 | jmpInstr := c.emitAsBx(OpJmpfalse, testReg, 0, c.lastLine) 1154 | 1155 | c.emitABC(OpForiter, keyReg, colReg, arrReg, node.NodeInfo.Line) 1156 | c.emitABC(OpGetIndex, valReg, colReg, keyReg, c.lastLine) 1157 | 1158 | node.Body.Accept(c, nil) 1159 | c.block.loop.continueTarget = c.newLabel() 1160 | 1161 | c.emitAsBx(OpJmp, 0, -c.labelOffset(testLabel)-1, c.lastLine) 1162 | c.block.loop.breakTarget = c.newLabel() 1163 | 1164 | c.modifyAsBx(jmpInstr, OpJmpfalse, testReg, c.labelOffset(uint32(jmpInstr)+1)) 1165 | } 1166 | 1167 | func (c *compiler) VisitForStmt(node *ast.ForStmt, data interface{}) { 1168 | c.enterBlock(kBlockContextLoop) 1169 | defer c.leaveBlock() 1170 | 1171 | hasCond := node.Cond != nil 1172 | if node.Init != nil { 1173 | node.Init.Accept(c, nil) 1174 | } 1175 | 1176 | startLabel := c.newLabel() 1177 | 1178 | var cond, jmpInstr int 1179 | var jmpLabel uint32 1180 | if hasCond { 1181 | reg := c.block.register 1182 | condData := exprdata{true, reg, reg} 1183 | node.Cond.Accept(c, &condData) 1184 | 1185 | cond = condData.regb 1186 | jmpInstr = c.emitAsBx(OpJmpfalse, cond, 0, c.lastLine) 1187 | jmpLabel = c.newLabel() 1188 | } 1189 | 1190 | node.Body.Accept(c, nil) 1191 | c.block.loop.continueTarget = c.newLabel() 1192 | 1193 | if node.Step != nil { 1194 | node.Step.Accept(c, nil) 1195 | c.block.register -= 1 // discard register consumed by Step 1196 | } else { 1197 | c.block.loop.continueTarget = startLabel // saves one jump 1198 | } 1199 | 1200 | c.emitAsBx(OpJmp, 0, -c.labelOffset(startLabel)-1, c.lastLine) 1201 | 1202 | if hasCond { 1203 | c.modifyAsBx(jmpInstr, OpJmpfalse, cond, c.labelOffset(jmpLabel)) 1204 | } 1205 | c.block.loop.breakTarget = c.newLabel() 1206 | } 1207 | 1208 | func (c *compiler) VisitRecoverBlock(node *ast.RecoverBlock, data interface{}) { 1209 | 1210 | } 1211 | 1212 | func (c *compiler) VisitTryRecoverStmt(node *ast.TryRecoverStmt, data interface{}) { 1213 | 1214 | } 1215 | 1216 | func (c *compiler) VisitBlock(node *ast.Block, data interface{}) { 1217 | for _, stmt := range node.Nodes { 1218 | stmt.Accept(c, nil) 1219 | 1220 | if !ast.IsStmt(stmt) { 1221 | c.block.register -= 1 1222 | } 1223 | } 1224 | } 1225 | 1226 | // Compile receives the root node of the AST and generates code 1227 | // for the "main" function from it. 1228 | // Any type of Node is accepted, either a block representing the program 1229 | // or a single expression. 1230 | // 1231 | func Compile(root ast.Node, filename string) (res *Bytecode, err error) { 1232 | defer func() { 1233 | if r := recover(); r != nil { 1234 | if cerr, ok := r.(*CompileError); ok { 1235 | err = cerr 1236 | } else { 1237 | panic(r) 1238 | } 1239 | } 1240 | }() 1241 | 1242 | var c compiler 1243 | c.filename = filename 1244 | c.mainFunc = newBytecode(filename) 1245 | c.block = newCompilerBlock(c.mainFunc, kBlockContextFunc, nil) 1246 | 1247 | root.Accept(&c, nil) 1248 | c.functionReturnGuard() 1249 | 1250 | res = c.mainFunc 1251 | return 1252 | } 1253 | -------------------------------------------------------------------------------- /examples/array.yo: -------------------------------------------------------------------------------- 1 | arr := [1, 2, 3] 2 | println(arr) 3 | 4 | append(arr, 4, 5, 6) 5 | println(arr) 6 | 7 | println(append([], 1, 2, 3)) 8 | 9 | println(len(arr)) -------------------------------------------------------------------------------- /examples/constfold.yo: -------------------------------------------------------------------------------- 1 | // test constant folding 2 | 3 | !(!false) 4 | !true; 5 | -2; 6 | -(-(5.451)) 7 | 2 + 2 * 4 + 5 8 | "hello" == "hello" 9 | "john" != "mary" 10 | 11 | const john, mary = 5, 8.15 12 | 13 | john == 5 //=> compiled: true 14 | mary > john //=> compiled: true 15 | mary == john //=> compiled: false 16 | 17 | const world = "World" 18 | "Hello, " + world 19 | 20 | string(5) -------------------------------------------------------------------------------- /examples/hello_world.yo: -------------------------------------------------------------------------------- 1 | println("Hello world from Yo! ", "こんにちは世界、ヨから") 2 | -------------------------------------------------------------------------------- /opcode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package yo 4 | 5 | // vm instruction details and implementation. 6 | // 7 | // The arguments comes first and the opcode comes last 8 | // in the bits, e.g.: 9 | // 10 | // 9 | 9 | 8 | 6 11 | // c | b | a | op 12 | // bx | a | op 13 | // 14 | // I'm experimenting this way because we need to get 15 | // the opcode more often than the arguments so it just 16 | // avoids doing bit shifts and the opcode is available 17 | // by just doing $instr & $opcodeMask 18 | 19 | type Opcode uint 20 | 21 | // NOTE: all register ranges are inclusive 22 | const ( 23 | OpLoadnil Opcode = iota // R(A) ... R(B) = nil 24 | OpLoadconst // R(A) = K(Bx) 25 | OpLoadglobal // R(A) = globals[K(Bx)] 26 | OpSetglobal // TODO: remove this opcode 27 | OpLoadFree // R(A) = refs[K(Bx)] 28 | OpSetFree // refs[K(Bx)] = R(A) 29 | 30 | OpUnm // R(A) = -RK(Bx) 31 | OpNot // R(A) = NOT RK(Bx) 32 | OpCmpl // R(A) = ^RK(B) 33 | 34 | OpAdd // R(A) = RK(B) + RK(C) 35 | OpSub // R(A) = RK(B) - RK(C) 36 | OpMul // R(A) = RK(B) * RK(C) 37 | OpDiv // R(A) = RK(B) / RK(C) 38 | OpPow // R(A) = pow(RK(B), RK(C)) 39 | OpShl // R(A) = RK(B) << RK(C) 40 | OpShr // R(A) = RK(B) >> RK(C) 41 | OpAnd // R(A) = RK(B) & RK(C) 42 | OpOr // R(A) = RK(B) | RK(C) 43 | OpXor // R(A) = RK(B) ^ RK(C) 44 | OpLt // R(A) = RK(B) < RK(C) 45 | OpLe // R(A) = RK(B) <= RK(C) 46 | OpEq // R(A) = RK(B) == RK(C) 47 | OpNe // R(A) = RK(B) != RK(C) 48 | 49 | OpMove // R(A) = R(B) 50 | OpGetIndex // R(A) = R(B)[RK(C)] 51 | OpSetIndex // R(A)[RK(B)] = RK(C) 52 | OpAppend // R(A) = append(R(A), R(A+1) ... R(A+B)) 53 | 54 | OpCall // R(A) ... R(A+B-1) = R(A)(R(A+B) ... R(A+B+C-1)) 55 | OpCallmethod // same as OpCall, but first argument is the receiver 56 | OpArray // R(A) = [] 57 | OpObject // R(A) = {} 58 | OpFunc // R(A) = func() { proto = funcs[Bx] } 59 | 60 | OpJmp // pc = pc + sBx 61 | OpJmptrue // pc = pc + sBx if RK(A) is not false or nil 62 | OpJmpfalse // pc = pc + sBx if RK(A) is false or nil 63 | OpReturn // return R(A) ... R(A+B-1) 64 | OpForbegin // R(A), R(A+1) = objkeys(R(B)), len(objkeys(R(B))) if R(B) is an object 65 | // R(A), R(A+1) = R(B), len(R(B)) if R(B) is an array 66 | // error if not array 67 | 68 | OpForiter // R(A) = R(A+1)++ if R(B) is an array 69 | // R(A) = R(C)[R(A+1)++] if R(B) is an object (R(C) should be an array of keys of the object) 70 | kOpCount int = int(OpForiter) + 1 71 | ) 72 | 73 | // instruction parameters 74 | const ( 75 | kOpcodeMask = 0x3f 76 | kArgAMask = 0xff 77 | kArgBCMask = 0x1ff 78 | kArgBxMask = (0x1ff << 9) | 0x1ff 79 | kArgsBxMask = kArgBxMask >> 1 80 | 81 | kOpcodeSize = 6 82 | kArgASize = 8 83 | kArgBCSize = 9 84 | 85 | kArgBOffset = kOpcodeSize + kArgASize 86 | kArgCOffset = kArgBOffset + kArgBCSize 87 | ) 88 | 89 | // offset for RK 90 | const OpConstOffset = 250 91 | 92 | var ( 93 | opStrings = map[Opcode]string{ 94 | OpLoadnil: "loadnil", 95 | OpLoadconst: "loadconst", 96 | OpLoadglobal: "loadglobal", 97 | OpSetglobal: "setglobal", 98 | OpLoadFree: "loadfree", 99 | OpSetFree: "setfree", 100 | 101 | OpUnm: "neg", 102 | OpNot: "not", 103 | OpCmpl: "cmpl", 104 | 105 | OpAdd: "add", 106 | OpSub: "sub", 107 | OpMul: "mul", 108 | OpDiv: "div", 109 | OpPow: "pow", 110 | OpShl: "shl", 111 | OpShr: "shr", 112 | OpAnd: "and", 113 | OpOr: "or", 114 | OpXor: "xor", 115 | OpLt: "lt", 116 | OpLe: "le", 117 | OpEq: "eq", 118 | OpNe: "ne", 119 | 120 | OpMove: "move", 121 | OpGetIndex: "getindex", 122 | OpSetIndex: "setindex", 123 | OpAppend: "append", 124 | 125 | OpCall: "call", 126 | OpCallmethod: "callmethod", 127 | OpArray: "array", 128 | OpObject: "object", 129 | OpFunc: "func", 130 | 131 | OpJmp: "jmp", 132 | OpJmptrue: "jmptrue", 133 | OpJmpfalse: "jmpfalse", 134 | OpReturn: "return", 135 | OpForbegin: "forbegin", 136 | OpForiter: "foriter", 137 | } 138 | ) 139 | 140 | // Stringer interface 141 | func (op Opcode) String() string { 142 | return opStrings[op] 143 | } 144 | 145 | // Instruction constructors. 146 | 147 | func OpNew(op Opcode) uint32 { 148 | return uint32(op & kOpcodeMask) 149 | } 150 | 151 | func OpNewA(op Opcode, a int) uint32 { 152 | return uint32((a&kArgAMask)<> kOpcodeSize) & kArgAMask) 177 | } 178 | 179 | func OpGetB(instr uint32) uint { 180 | return uint((instr >> kArgBOffset) & kArgBCMask) 181 | } 182 | 183 | func OpGetC(instr uint32) uint { 184 | return uint((instr >> kArgCOffset) & kArgBCMask) 185 | } 186 | 187 | func OpGetBx(instr uint32) uint { 188 | return uint((instr >> kArgBOffset) & kArgBxMask) 189 | } 190 | 191 | func OpGetsBx(instr uint32) int { 192 | return int(OpGetBx(instr)) - kArgsBxMask 193 | } 194 | -------------------------------------------------------------------------------- /parse/parser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package parse 4 | 5 | import ( 6 | "fmt" 7 | "github.com/glhrmfrts/yo/ast" 8 | "strconv" 9 | ) 10 | 11 | type parser struct { 12 | tok ast.Token 13 | literal string 14 | ignoreNewlines bool 15 | tokenizer tokenizer 16 | } 17 | 18 | type ParseError struct { 19 | Guilty ast.Token 20 | Line int 21 | File string 22 | Message string 23 | } 24 | 25 | func (err *ParseError) Error() string { 26 | return fmt.Sprintf("%s:%d: %s", err.File, err.Line, err.Message) 27 | } 28 | 29 | // 30 | // common productions 31 | // 32 | 33 | func parseNumber(typ ast.Token, str string) float64 { 34 | if typ == ast.TokenFloat { 35 | f, err := strconv.ParseFloat(str, 64) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return f 40 | } else { 41 | i, err := strconv.Atoi(str) 42 | if err != nil { 43 | panic(err) 44 | } 45 | return float64(i) 46 | } 47 | } 48 | 49 | func (p *parser) error(msg string) { 50 | t := p.tokenizer 51 | panic(&ParseError{Guilty: p.tok, Line: t.lineno, File: t.filename, Message: msg}) 52 | } 53 | 54 | func (p *parser) errorExpected(expected string) { 55 | p.error(fmt.Sprintf("unexpected %s, expected %s", p.tok, expected)) 56 | } 57 | 58 | func (p *parser) line() int { 59 | return p.tokenizer.lineno 60 | } 61 | 62 | func (p *parser) next() { 63 | p.tok, p.literal = p.tokenizer.nextToken() 64 | 65 | for p.ignoreNewlines && p.tok == ast.TokenNewline { 66 | p.tok, p.literal = p.tokenizer.nextToken() 67 | } 68 | } 69 | 70 | func (p *parser) accept(toktype ast.Token) bool { 71 | if p.tok == toktype { 72 | p.next() 73 | return true 74 | } 75 | return false 76 | } 77 | 78 | func (p *parser) makeId() *ast.Id { 79 | return &ast.Id{Value: p.literal} 80 | } 81 | 82 | func (p *parser) makeSelector(left ast.Node) *ast.Selector { 83 | return &ast.Selector{Left: left, Value: p.literal} 84 | } 85 | 86 | func (p *parser) idList() []*ast.Id { 87 | var list []*ast.Id 88 | 89 | for p.tok == ast.TokenId { 90 | list = append(list, &ast.Id{Value: p.literal}) 91 | 92 | p.next() 93 | if !p.accept(ast.TokenComma) { 94 | break 95 | } 96 | } 97 | 98 | return list 99 | } 100 | 101 | // check if an expression list contains only identifiers 102 | func (p *parser) checkIdList(list []ast.Node) bool { 103 | for _, node := range list { 104 | if _, isId := node.(*ast.Id); !isId { 105 | return false 106 | } 107 | } 108 | 109 | return true 110 | } 111 | 112 | // check if an expression can be at left side of an assignment 113 | func (p *parser) checkLhs(node ast.Node) bool { 114 | switch node.(type) { 115 | case *ast.Id, *ast.Selector, *ast.Subscript: 116 | return true 117 | default: 118 | return false 119 | } 120 | } 121 | 122 | // same as above but with a list of expressions 123 | func (p *parser) checkLhsList(list []ast.Node) bool { 124 | for _, node := range list { 125 | if !p.checkLhs(node) { 126 | return false 127 | } 128 | } 129 | 130 | return true 131 | } 132 | 133 | func (p *parser) exprList(inArray bool) []ast.Node { 134 | var list []ast.Node 135 | for { 136 | // trailing comma check 137 | if inArray && p.tok == ast.TokenRbrack { 138 | break 139 | } 140 | 141 | expr := p.expr() 142 | list = append(list, expr) 143 | if !p.accept(ast.TokenComma) { 144 | break 145 | } 146 | } 147 | 148 | return list 149 | } 150 | 151 | // 152 | // grammar rules 153 | // 154 | 155 | func (p *parser) array() ast.Node { 156 | line := p.line() 157 | p.next() // '[' 158 | 159 | if p.accept(ast.TokenRbrack) { 160 | // no elements 161 | return &ast.Array{} 162 | } 163 | 164 | list := p.exprList(true) 165 | if !p.accept(ast.TokenRbrack) { 166 | p.errorExpected("closing ']'") 167 | } 168 | 169 | return &ast.Array{Elements: list, NodeInfo: ast.NodeInfo{line}} 170 | } 171 | 172 | func (p *parser) objectFieldList() []*ast.ObjectField { 173 | var list []*ast.ObjectField 174 | for { 175 | // trailing comma check 176 | if p.tok == ast.TokenRbrace { 177 | break 178 | } 179 | 180 | var key string 181 | if p.tok == ast.TokenId || p.tok == ast.TokenString { 182 | key = p.literal 183 | p.next() 184 | } else { 185 | p.errorExpected("identifier or string") 186 | } 187 | 188 | line := p.line() 189 | if !p.accept(ast.TokenColon) { 190 | list = append(list, &ast.ObjectField{Key: key, NodeInfo: ast.NodeInfo{line}}) 191 | } else { 192 | value := p.expr() 193 | list = append(list, &ast.ObjectField{Key: key, Value: value, NodeInfo: ast.NodeInfo{line}}) 194 | } 195 | 196 | if !p.accept(ast.TokenComma) { 197 | break 198 | } 199 | } 200 | 201 | return list 202 | } 203 | 204 | func (p *parser) object() ast.Node { 205 | line := p.line() 206 | p.next() // '{' 207 | 208 | if p.accept(ast.TokenRbrace) { 209 | // no elements 210 | return &ast.Object{} 211 | } 212 | 213 | fields := p.objectFieldList() 214 | if !p.accept(ast.TokenRbrace) { 215 | p.errorExpected("closing '}'") 216 | } 217 | 218 | return &ast.Object{Fields: fields, NodeInfo: ast.NodeInfo{line}} 219 | } 220 | 221 | func (p *parser) functionArgs() []ast.Node { 222 | if !p.accept(ast.TokenLparen) { 223 | p.errorExpected("'('") 224 | } 225 | 226 | var list []ast.Node 227 | if p.accept(ast.TokenRparen) { 228 | // no arguments 229 | return list 230 | } 231 | 232 | var vararg, kwarg bool 233 | for p.tok == ast.TokenId { 234 | if vararg { 235 | p.error("argument after variadic argument") 236 | } 237 | 238 | var arg ast.Node 239 | line := p.line() 240 | id := p.makeId() 241 | p.next() 242 | 243 | // '=' 244 | if p.accept(ast.TokenEq) { 245 | value := p.expr() 246 | arg = &ast.KwArg{Key: id.Value, Value: value, NodeInfo: ast.NodeInfo{line}} 247 | kwarg = true 248 | } else if p.accept(ast.TokenDotdotdot) { 249 | arg = &ast.VarArg{Arg: id, NodeInfo: ast.NodeInfo{line}} 250 | vararg = true 251 | } else { 252 | if vararg { 253 | p.error("positional argument after variadic argument") 254 | } 255 | if kwarg { 256 | p.error("positional argument after keyword argument") 257 | } 258 | arg = id 259 | } 260 | 261 | list = append(list, arg) 262 | if !p.accept(ast.TokenComma) { 263 | break 264 | } 265 | } 266 | 267 | if !p.accept(ast.TokenRparen) { 268 | p.errorExpected("closing ')'") 269 | } 270 | return list 271 | } 272 | 273 | func (p *parser) functionBody() ast.Node { 274 | line := p.line() 275 | if p.accept(ast.TokenTilde) { 276 | // '^' curried function 277 | args := p.functionArgs() 278 | body := p.functionBody() 279 | fn := &ast.Function{Args: args, Body: body, NodeInfo: ast.NodeInfo{line}} 280 | 281 | return &ast.Block{ 282 | Nodes: []ast.Node{&ast.ReturnStmt{Values: []ast.Node{fn}, NodeInfo: ast.NodeInfo{line}}}, 283 | NodeInfo: ast.NodeInfo{line}, 284 | } 285 | } else if p.accept(ast.TokenMinusgt) { 286 | // '->' short function 287 | list := p.exprList(false) 288 | 289 | return &ast.Block{ 290 | Nodes: []ast.Node{&ast.ReturnStmt{Values: list, NodeInfo: ast.NodeInfo{line}}}, 291 | NodeInfo: ast.NodeInfo{line}, 292 | } 293 | } else if p.tok == ast.TokenLbrace { 294 | // '{' regular function body 295 | return p.block() 296 | } 297 | 298 | p.errorExpected("'^', '=>' or '{'") 299 | return nil 300 | } 301 | 302 | func (p *parser) function() ast.Node { 303 | line := p.line() 304 | p.next() // 'func' 305 | 306 | var name ast.Node 307 | if p.tok != ast.TokenLparen { 308 | name = p.selectorOrSubscriptExpr(nil) 309 | if !p.checkLhs(name) { 310 | p.error("function name must be assignable") 311 | } 312 | } 313 | 314 | args := p.functionArgs() 315 | body := p.functionBody() 316 | return &ast.Function{Name: name, Args: args, Body: body, NodeInfo: ast.NodeInfo{line}} 317 | } 318 | 319 | func (p *parser) primaryExpr() ast.Node { 320 | line := p.line() 321 | // these first productions before the second 'switch' 322 | // handle the ending token themselves, so 'defer p.next()' 323 | // needs to be after them 324 | switch p.tok { 325 | case ast.TokenFunc: 326 | return p.function() 327 | case ast.TokenLbrack: 328 | return p.array() 329 | case ast.TokenLbrace: 330 | return p.object() 331 | case ast.TokenLparen: 332 | p.next() 333 | expr := p.expr() 334 | if !p.accept(ast.TokenRparen) { 335 | p.errorExpected("closing ')'") 336 | } 337 | 338 | return expr 339 | default: 340 | defer p.next() 341 | switch p.tok { 342 | case ast.TokenInt, ast.TokenFloat: 343 | return &ast.Number{Value: parseNumber(p.tok, p.literal), NodeInfo: ast.NodeInfo{line}} 344 | case ast.TokenId: 345 | return &ast.Id{Value: p.literal, NodeInfo: ast.NodeInfo{line}} 346 | case ast.TokenString: 347 | return &ast.String{Value: p.literal, NodeInfo: ast.NodeInfo{line}} 348 | case ast.TokenTrue, ast.TokenFalse: 349 | return &ast.Bool{Value: p.tok == ast.TokenTrue, NodeInfo: ast.NodeInfo{line}} 350 | case ast.TokenNil: 351 | return &ast.Nil{NodeInfo: ast.NodeInfo{line}} 352 | } 353 | } 354 | 355 | p.error(fmt.Sprintf("unexpected %s", p.tok)) 356 | return nil 357 | } 358 | 359 | func (p *parser) selectorExpr(left ast.Node) ast.Node { 360 | if !(p.tok == ast.TokenId) { 361 | p.errorExpected("identifier") 362 | } 363 | 364 | defer p.next() 365 | return p.makeSelector(left) 366 | } 367 | 368 | func (p *parser) subscriptExpr(left ast.Node) ast.Node { 369 | line := p.line() 370 | expr := p.expr() 371 | sub := &ast.Subscript{Left: left, Right: expr} 372 | if p.accept(ast.TokenColon) { 373 | expr2 := p.expr() 374 | sub.Right = &ast.Slice{Start: expr, End: expr2, NodeInfo: ast.NodeInfo{line}} 375 | } 376 | 377 | if !p.accept(ast.TokenRbrack) { 378 | p.errorExpected("closing ']'") 379 | } 380 | 381 | return sub 382 | } 383 | 384 | func (p *parser) selectorOrSubscriptExpr(left ast.Node) ast.Node { 385 | if left == nil { 386 | left = p.primaryExpr() 387 | } 388 | 389 | for { 390 | if dot, lBrack := p.tok == ast.TokenDot, p.tok == ast.TokenLbrack; dot || lBrack { 391 | line := p.line() 392 | old := p.ignoreNewlines 393 | p.ignoreNewlines = false 394 | p.next() 395 | if p.tok == ast.TokenNewline || p.tok == ast.TokenEos { 396 | p.error("expression not terminated") 397 | } 398 | p.ignoreNewlines = old 399 | 400 | if dot { 401 | left = p.selectorExpr(left) 402 | left.(*ast.Selector).NodeInfo.Line = line 403 | } else { 404 | left = p.subscriptExpr(left) 405 | left.(*ast.Subscript).NodeInfo.Line = line 406 | } 407 | } else { 408 | break 409 | } 410 | } 411 | 412 | return left 413 | } 414 | 415 | func (p *parser) callArgs() []ast.Node { 416 | var list []ast.Node 417 | if p.tok == ast.TokenRparen { 418 | // no arguments 419 | return list 420 | } 421 | 422 | for { 423 | line := p.line() 424 | arg := p.expr() 425 | 426 | // '=' 427 | if p.accept(ast.TokenEq) { 428 | value := p.expr() 429 | 430 | if id, isId := arg.(*ast.Id); isId { 431 | arg = &ast.KwArg{Key: id.Value, Value: value, NodeInfo: ast.NodeInfo{line}} 432 | } else { 433 | p.error("non-identifier in left side of keyword argument") 434 | } 435 | } else if p.accept(ast.TokenDotdotdot) { 436 | arg = &ast.VarArg{Arg: arg, NodeInfo: ast.NodeInfo{line}} 437 | } 438 | 439 | list = append(list, arg) 440 | if !p.accept(ast.TokenComma) { 441 | break 442 | } 443 | } 444 | 445 | return list 446 | } 447 | 448 | func (p *parser) callExpr() ast.Node { 449 | line := p.line() 450 | left := p.selectorOrSubscriptExpr(nil) 451 | 452 | var args []ast.Node 453 | for p.accept(ast.TokenLparen) { 454 | args = p.callArgs() 455 | if !p.accept(ast.TokenRparen) { 456 | p.errorExpected("closing ')'") 457 | } 458 | left = &ast.CallExpr{Left: left, Args: args, NodeInfo: ast.NodeInfo{line}} 459 | } 460 | 461 | return p.selectorOrSubscriptExpr(left) 462 | } 463 | 464 | func (p *parser) postfixExpr() ast.Node { 465 | line := p.line() 466 | left := p.callExpr() 467 | 468 | if ast.IsPostfixOp(p.tok) { 469 | op := p.tok 470 | p.next() 471 | return &ast.PostfixExpr{Op: op, Left: left, NodeInfo: ast.NodeInfo{line}} 472 | } 473 | 474 | return left 475 | } 476 | 477 | func (p *parser) unaryExpr() ast.Node { 478 | line := p.line() 479 | if ast.IsUnaryOp(p.tok) { 480 | op := p.tok 481 | p.next() 482 | 483 | var right ast.Node 484 | if op == ast.TokenNot { 485 | right = p.expr() 486 | } else { 487 | right = p.postfixExpr() 488 | } 489 | return &ast.UnaryExpr{Op: op, Right: right, NodeInfo: ast.NodeInfo{line}} 490 | } 491 | 492 | return p.postfixExpr() 493 | } 494 | 495 | // parse a binary expression using the legendary wikipedia's algorithm :) 496 | func (p *parser) binaryExpr(left ast.Node, minPrecedence int) ast.Node { 497 | line := p.line() 498 | for ast.IsBinaryOp(p.tok) && ast.Precedence(p.tok) >= minPrecedence { 499 | op := p.tok 500 | opPrecedence := ast.Precedence(op) 501 | 502 | // consume operator 503 | old := p.ignoreNewlines 504 | p.ignoreNewlines = false 505 | p.next() 506 | if p.tok == ast.TokenNewline || p.tok == ast.TokenEos { 507 | p.error("expression not terminated") 508 | } 509 | p.ignoreNewlines = old 510 | 511 | right := p.unaryExpr() 512 | for (ast.IsBinaryOp(p.tok) && ast.Precedence(p.tok) > opPrecedence) || 513 | (ast.RightAssociative(p.tok) && ast.Precedence(p.tok) >= opPrecedence) { 514 | right = p.binaryExpr(right, ast.Precedence(p.tok)) 515 | } 516 | left = &ast.BinaryExpr{Op: op, Left: left, Right: right, NodeInfo: ast.NodeInfo{line}} 517 | } 518 | 519 | return left 520 | } 521 | 522 | func (p *parser) ternaryExpr(left ast.Node) ast.Node { 523 | line := p.line() 524 | p.next() // '?' 525 | 526 | whenTrue := p.expr() 527 | if !p.accept(ast.TokenColon) { 528 | p.errorExpected("':'") 529 | } 530 | 531 | whenFalse := p.expr() 532 | return &ast.TernaryExpr{Cond: left, Then: whenTrue, Else: whenFalse, NodeInfo: ast.NodeInfo{line}} 533 | } 534 | 535 | func (p *parser) expr() ast.Node { 536 | left := p.binaryExpr(p.unaryExpr(), 0) 537 | 538 | // avoid unecessary calls to ternaryExpr 539 | if p.tok == ast.TokenQuestion { 540 | return p.ternaryExpr(left) 541 | } 542 | 543 | return left 544 | } 545 | 546 | func (p *parser) declaration() ast.Node { 547 | line := p.line() 548 | isConst := p.tok == ast.TokenConst 549 | p.next() 550 | 551 | left := p.idList() 552 | 553 | // '=' 554 | if !p.accept(ast.TokenEq) { 555 | // a declaration without any values 556 | return &ast.Declaration{IsConst: isConst, Left: left, NodeInfo: ast.NodeInfo{line}} 557 | } 558 | 559 | right := p.exprList(false) 560 | return &ast.Declaration{IsConst: isConst, Left: left, Right: right, NodeInfo: ast.NodeInfo{line}} 561 | } 562 | 563 | func (p *parser) assignment(left []ast.Node) ast.Node { 564 | line := p.line() 565 | 566 | if left == nil { 567 | left = p.exprList(false) 568 | } 569 | 570 | if !ast.IsAssignOp(p.tok) { 571 | if len(left) > 1 { 572 | p.error("illegal expression") 573 | } 574 | return left[0] 575 | } 576 | 577 | // ':=' 578 | if p.tok == ast.TokenColoneq { 579 | // a short variable declaration 580 | if isIdList := p.checkIdList(left); !isIdList { 581 | p.error("non-identifier at left side of ':='") 582 | } 583 | } else { 584 | // validate left side of assignment 585 | if isLhsList := p.checkLhsList(left); !isLhsList { 586 | p.error("non-assignable at left side of '='") 587 | } 588 | } 589 | 590 | op := p.tok 591 | p.next() 592 | 593 | right := p.exprList(false) 594 | return &ast.Assignment{Op: op, Left: left, Right: right, NodeInfo: ast.NodeInfo{line}} 595 | } 596 | 597 | func (p *parser) stmt() ast.Node { 598 | line := p.line() 599 | defer p.accept(ast.TokenSemicolon) 600 | switch tok := p.tok; tok { 601 | case ast.TokenConst, ast.TokenVar: 602 | return p.declaration() 603 | case ast.TokenBreak, ast.TokenContinue, ast.TokenFallthrough: 604 | p.next() 605 | return &ast.BranchStmt{Type: tok, NodeInfo: ast.NodeInfo{line}} 606 | case ast.TokenReturn: 607 | p.next() 608 | values := p.exprList(false) 609 | return &ast.ReturnStmt{Values: values, NodeInfo: ast.NodeInfo{line}} 610 | case ast.TokenPanic: 611 | p.next() 612 | err := p.expr() 613 | return &ast.PanicStmt{Err: err, NodeInfo: ast.NodeInfo{line}} 614 | case ast.TokenIf: 615 | return p.ifStmt() 616 | case ast.TokenFor: 617 | return p.forStmt() 618 | case ast.TokenTry: 619 | return p.tryRecoverStmt() 620 | default: 621 | return p.assignment(nil) 622 | } 623 | } 624 | 625 | func (p *parser) ifStmt() ast.Node { 626 | line := p.line() 627 | p.next() // 'if' 628 | 629 | var init *ast.Assignment 630 | var else_ ast.Node 631 | cond := p.assignment(nil) 632 | init, ok := cond.(*ast.Assignment) 633 | if ok { 634 | if !p.accept(ast.TokenSemicolon) { 635 | p.errorExpected("';'") 636 | } 637 | cond = p.expr() 638 | } 639 | 640 | body := p.block() 641 | if p.accept(ast.TokenElse) { 642 | if p.tok == ast.TokenLbrace { 643 | else_ = p.block() 644 | } else if p.tok == ast.TokenIf { 645 | else_ = p.ifStmt() 646 | } else { 647 | p.errorExpected("if or '{'") 648 | } 649 | } 650 | 651 | return &ast.IfStmt{Init: init, Cond: cond, Body: body, Else: else_, NodeInfo: ast.NodeInfo{line}} 652 | } 653 | 654 | func (p *parser) forIteratorStmt(ids []ast.Node) ast.Node { 655 | line := p.line() 656 | 657 | var key *ast.Id 658 | var value *ast.Id 659 | 660 | length := len(ids) 661 | if length > 2 { 662 | p.error("too many identifiers in for iterator statement") 663 | } 664 | 665 | ok := p.checkIdList(ids) 666 | if !ok { 667 | p.error("non-identifier at left-side of 'in' in for iterator statement") 668 | } else { 669 | key = ids[0].(*ast.Id) 670 | if length > 1 { 671 | value = ids[1].(*ast.Id) 672 | } 673 | } 674 | 675 | p.next() // 'in' 676 | coll := p.expr() 677 | 678 | var when ast.Node 679 | if p.accept(ast.TokenWhen) { 680 | when = p.expr() 681 | } 682 | 683 | body := p.block() 684 | return &ast.ForIteratorStmt{ 685 | Key: key, 686 | Value: value, 687 | Collection: coll, 688 | When: when, 689 | Body: body, 690 | NodeInfo: ast.NodeInfo{line}, 691 | } 692 | } 693 | 694 | func (p *parser) forStmt() ast.Node { 695 | line := p.line() 696 | p.next() // 'for' 697 | 698 | var init *ast.Assignment 699 | var left []ast.Node 700 | var cond ast.Node 701 | var step ast.Node 702 | var ok bool 703 | if p.tok == ast.TokenLbrace { 704 | goto parseBody 705 | } 706 | 707 | left = p.exprList(false) 708 | if p.tok == ast.TokenIn { 709 | return p.forIteratorStmt(left) 710 | } 711 | 712 | cond = p.assignment(left) 713 | init, ok = cond.(*ast.Assignment) 714 | if ok { 715 | if !p.accept(ast.TokenSemicolon) { 716 | p.errorExpected("';'") 717 | } 718 | if p.tok == ast.TokenLbrace { 719 | cond = nil 720 | goto parseBody 721 | } 722 | cond = p.expr() 723 | } 724 | 725 | if p.accept(ast.TokenSemicolon) && p.tok != ast.TokenLbrace { 726 | step = p.assignment(nil) 727 | } 728 | 729 | parseBody: 730 | body := p.block() 731 | return &ast.ForStmt{Init: init, Cond: cond, Step: step, Body: body, NodeInfo: ast.NodeInfo{line}} 732 | } 733 | 734 | func (p *parser) tryRecoverStmt() ast.Node { 735 | line := p.line() 736 | p.next() // 'try' 737 | 738 | tryBlock := p.block().(*ast.Block) 739 | 740 | var recoverBlock *ast.RecoverBlock 741 | if p.accept(ast.TokenRecover) { 742 | line := p.line() 743 | 744 | var id *ast.Id 745 | if p.tok == ast.TokenId { 746 | id = p.makeId() 747 | p.next() 748 | } 749 | 750 | block := p.block().(*ast.Block) 751 | recoverBlock = &ast.RecoverBlock{Id: id, Block: block, NodeInfo: ast.NodeInfo{line}} 752 | } 753 | 754 | var finallyBlock *ast.Block 755 | if p.accept(ast.TokenFinally) { 756 | finallyBlock = p.block().(*ast.Block) 757 | } 758 | 759 | return &ast.TryRecoverStmt{ 760 | Try: tryBlock, 761 | Recover: recoverBlock, 762 | Finally: finallyBlock, 763 | NodeInfo: ast.NodeInfo{line}, 764 | } 765 | } 766 | 767 | func (p *parser) block() ast.Node { 768 | line := p.line() 769 | if !p.accept(ast.TokenLbrace) { 770 | p.errorExpected("'{'") 771 | } 772 | 773 | var nodes []ast.Node 774 | for !(p.tok == ast.TokenRbrace || p.tok == ast.TokenEos) { 775 | stmt := p.stmt() 776 | nodes = append(nodes, stmt) 777 | } 778 | 779 | if !p.accept(ast.TokenRbrace) { 780 | p.errorExpected("closing '}'") 781 | } 782 | return &ast.Block{Nodes: nodes, NodeInfo: ast.NodeInfo{line}} 783 | } 784 | 785 | func (p *parser) program() ast.Node { 786 | var nodes []ast.Node 787 | for !(p.tok == ast.TokenEos) { 788 | stmt := p.stmt() 789 | nodes = append(nodes, stmt) 790 | } 791 | 792 | return &ast.Block{Nodes: nodes} 793 | } 794 | 795 | // initialization of parser 796 | 797 | func (p *parser) init(source []byte, filename string) { 798 | p.ignoreNewlines = true 799 | p.tokenizer.init(source, filename) 800 | 801 | // fetch the first token 802 | p.next() 803 | } 804 | 805 | func ParseExpr(source []byte) (expr ast.Node, err error) { 806 | defer func() { 807 | if r := recover(); r != nil { 808 | if perr, ok := r.(*ParseError); ok { 809 | err = perr 810 | } else { 811 | panic(r) 812 | } 813 | } 814 | }() 815 | 816 | var p parser 817 | p.init(source, "") 818 | expr = p.expr() 819 | return 820 | } 821 | 822 | func ParseFile(source []byte, filename string) (root ast.Node, err error) { 823 | defer func() { 824 | if r := recover(); r != nil { 825 | if perr, ok := r.(*ParseError); ok { 826 | err = perr 827 | } else { 828 | panic(r) 829 | } 830 | } 831 | }() 832 | 833 | var p parser 834 | p.init(source, filename) 835 | root = p.program() 836 | return 837 | } 838 | -------------------------------------------------------------------------------- /parse/parser_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package parse 4 | 5 | // TODO: add invalid tests expecting errors 6 | 7 | import ( 8 | "fmt" 9 | "io/ioutil" 10 | "testing" 11 | ) 12 | 13 | func TestExpr(t *testing.T) { 14 | valid := []string{ 15 | "nil", 16 | "true", 17 | "false", 18 | "2", 19 | "2065", 20 | "3.141519", 21 | "8065.54e-6", 22 | "3e+5", 23 | "0.120213", 24 | ".506", 25 | ".5", 26 | "\"Hello world!\"", 27 | "\"Hello world\nI am a string!\"", 28 | "'Hello world\nI am a longer string with single quotes'", 29 | "identifier", 30 | "__identifier", 31 | "identIfier", 32 | "object.field", 33 | "array[2]", 34 | "object.field.with.array[2 + 5]", 35 | "array[5 + 9 * (12 / 24)].with.object.field", 36 | "calling()", 37 | "callingClosure()()", 38 | "calling().field", 39 | "object.field.calling()", 40 | "2 + 30 + 405", 41 | "2.1654 * 0.123 / 180e+1", 42 | "a*b-3/(5/2)", 43 | "(((((((5/2)))))))", 44 | "[1,2,3,4,5,'hello',6.45]", 45 | "[1,2,3,4,5,'hello',trailing,]", 46 | "{field: value,field2: func() {}}", 47 | "{2 + 2: value,\n\nfield: 'value\nvalue'}", 48 | "{field: 'value', trailing: true,}", 49 | "func() {}", 50 | "func(arg) { return arg }", 51 | "func(arg) { return 2 }", 52 | "func(arg) -> arg * 2", 53 | "func(arg, arg2) -> arg2, arg * 2 / 4", 54 | "func(a) ^(b) -> a * b", 55 | "func(a) ^(b) ^(c) -> a + b + c", 56 | "func(a) ^(b) { return a * b * 3 }", 57 | "func(a, b, c) ^(d, e, f, g) -> a + b + c + d + e + f + g", 58 | "-2 + 5", 59 | "-(2 + 5)", 60 | "!false && true", 61 | "!(false && true)", 62 | "not false && true", 63 | "(not false) && true", 64 | "reallyLongNameWithALotOfNonsenseWordsOnIt", 65 | "5 < 2", 66 | "5 > 2", 67 | "a <= b * 2 / 3 * (4 ** 4)", 68 | "5 ** 5", 69 | "true ? 'is true' : 'is false'", 70 | "true ? 'is true' : true ? 'is still true' : 'is false'", 71 | "(98 < 100 ? 1 : 0) ? 'lt' : 'gt'", 72 | } 73 | 74 | fmt.Println("TestExpr:") 75 | for i, expr := range valid { 76 | _, err := ParseExpr([]byte(expr)) 77 | if err != nil { 78 | t.Errorf("(%d) error: %s\n%s\n", i, expr, err.Error()) 79 | } else { 80 | fmt.Printf("(%d) ok: %s\n\n", i, expr) 81 | } 82 | } 83 | } 84 | 85 | func TestFiles(t *testing.T) { 86 | valid := []string{ 87 | "variables.elo", 88 | } 89 | 90 | fmt.Println("TestFiles:") 91 | for i, file := range valid { 92 | source, err := ioutil.ReadFile("./../../tests/" + file) 93 | if err != nil { 94 | panic(err) 95 | } 96 | 97 | _, err = ParseFile(source, file) 98 | if err != nil { 99 | t.Errorf("(%d) error: %s\n%s\n", i, file, err.Error()) 100 | } else { 101 | fmt.Printf("(%d) ok : %s\n\n", i, file) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /parse/tokenizer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | // 3 | // Some parts of this file were taken from Go's source code, 4 | // more specifically the functions "isLetter", "isDigit", "nextChar", 5 | // "scanComment", "scanIdentifier", "digitVal", "scanMantissa", 6 | // "scanNumber", "scanEscape", "scanString", "skipWhitespace". 7 | // The copyright notice and license below apply to the specified functions. 8 | // 9 | // Copyright (c) 2012 The Go Authors. All rights reserved. 10 | // 11 | // Redistribution and use in source and binary forms, with or without 12 | // modification, are permitted provided that the following conditions are 13 | // met: 14 | // 15 | // * Redistributions of source code must retain the above copyright 16 | // notice, this list of conditions and the following disclaimer. 17 | // * Redistributions in binary form must reproduce the above 18 | // copyright notice, this list of conditions and the following disclaimer 19 | // in the documentation and/or other materials provided with the 20 | // distribution. 21 | // * Neither the name of Google Inc. nor the names of its 22 | // contributors may be used to endorse or promote products derived from 23 | // this software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | package parse 38 | 39 | import ( 40 | "fmt" 41 | "github.com/glhrmfrts/yo/ast" 42 | "os" 43 | "unicode" 44 | "unicode/utf8" 45 | ) 46 | 47 | type tokenizer struct { 48 | offset int 49 | readOffset int 50 | r rune 51 | src []byte 52 | filename string 53 | lineno int 54 | insertSemi bool 55 | last ast.Token 56 | } 57 | 58 | const bom = 0xFEFF 59 | const eof = -1 60 | 61 | func isLetter(ch rune) bool { 62 | return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) 63 | } 64 | 65 | func isDigit(ch rune) bool { 66 | return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) 67 | } 68 | 69 | func (t *tokenizer) error(msg string) { 70 | fmt.Printf("%s:%d -> syntax error: %s\n", t.filename, t.lineno, msg) 71 | os.Exit(1) 72 | } 73 | 74 | func (t *tokenizer) nextChar() bool { 75 | if t.readOffset < len(t.src) { 76 | t.offset = t.readOffset 77 | ch := t.src[t.readOffset] 78 | 79 | r, w := rune(ch), 1 80 | switch { 81 | case r == 0: 82 | t.error("illegal character NUL") 83 | case r >= 0x80: 84 | // not ASCII 85 | r, w = utf8.DecodeRune(t.src[t.offset:]) 86 | if r == utf8.RuneError && w == 1 { 87 | t.error("illegal UTF-8 encoding") 88 | } else if r == bom && t.offset > 0 { 89 | t.error("illegal byte order mark") 90 | } 91 | } 92 | 93 | if ch == '\n' { 94 | t.lineno++ 95 | } 96 | 97 | t.r = r 98 | t.readOffset += w 99 | return true 100 | } 101 | 102 | t.r = eof 103 | t.offset = len(t.src) 104 | return false 105 | } 106 | 107 | func (t *tokenizer) scanComment() bool { 108 | // initial '/' already consumed 109 | if t.r == '/' { 110 | for t.r != eof && t.r != '\n' { 111 | t.nextChar() 112 | } 113 | 114 | return true 115 | } 116 | 117 | return false 118 | } 119 | 120 | func (t *tokenizer) scanIdentifier() string { 121 | offs := t.offset 122 | for isLetter(t.r) || isDigit(t.r) { 123 | t.nextChar() 124 | } 125 | return string(t.src[offs:t.offset]) 126 | } 127 | 128 | func digitVal(ch rune) int { 129 | switch { 130 | case '0' <= ch && ch <= '9': 131 | return int(ch - '0') 132 | case 'a' <= ch && ch <= 'f': 133 | return int(ch - 'a' + 10) 134 | case 'A' <= ch && ch <= 'F': 135 | return int(ch - 'A' + 10) 136 | } 137 | return 16 138 | } 139 | 140 | func (t *tokenizer) scanMantissa(base int) { 141 | for digitVal(t.r) < base { 142 | t.nextChar() 143 | } 144 | } 145 | 146 | func (t *tokenizer) scanNumber(seenDecimalPoint bool) (ast.Token, string) { 147 | // digitVal(t.r) < 10 148 | offs := t.offset 149 | typ := ast.TokenInt 150 | 151 | if seenDecimalPoint { 152 | typ = ast.TokenFloat 153 | offs-- 154 | t.scanMantissa(10) 155 | goto exponent 156 | } 157 | 158 | if t.r == '0' { 159 | // int or float 160 | offs := t.offset 161 | t.nextChar() 162 | if t.r == 'x' || t.r == 'X' { 163 | // hexadecimal int 164 | t.nextChar() 165 | t.scanMantissa(16) 166 | if t.offset-offs <= 2 { 167 | // only scanned "0x" or "0X" 168 | t.error("illegal hexadecimal number") 169 | } 170 | } else { 171 | // octal int or float 172 | seenDecimalDigit := false 173 | t.scanMantissa(8) 174 | if t.r == '8' || t.r == '9' { 175 | // illegal octal int or float 176 | seenDecimalDigit = true 177 | t.scanMantissa(10) 178 | } 179 | if t.r == '.' || t.r == 'e' || t.r == 'E' { 180 | goto fraction 181 | } 182 | // octal int 183 | if seenDecimalDigit { 184 | t.error("illegal octal number") 185 | } 186 | } 187 | goto exit 188 | } 189 | 190 | // decimal int or float 191 | t.scanMantissa(10) 192 | 193 | fraction: 194 | if t.r == '.' { 195 | typ = ast.TokenFloat 196 | t.nextChar() 197 | t.scanMantissa(10) 198 | } 199 | 200 | exponent: 201 | if t.r == 'e' || t.r == 'E' { 202 | typ = ast.TokenFloat 203 | t.nextChar() 204 | if t.r == '-' || t.r == '+' { 205 | t.nextChar() 206 | } 207 | t.scanMantissa(10) 208 | } 209 | 210 | exit: 211 | return typ, string(t.src[offs:t.offset]) 212 | } 213 | 214 | // scans a valid escape sequence and returns the evaluated value 215 | func (t *tokenizer) scanEscape(quote rune) rune { 216 | 217 | var n int 218 | var base, max uint32 219 | var r rune 220 | 221 | switch t.r { 222 | case 'a': 223 | r = '\a' 224 | case 'b': 225 | r = '\b' 226 | case 'f': 227 | r = '\f' 228 | case 'n': 229 | r = '\n' 230 | case 'r': 231 | r = '\r' 232 | case 't': 233 | r = '\t' 234 | case 'v': 235 | r = '\v' 236 | case '\\': 237 | r = '\\' 238 | case quote: 239 | r = quote 240 | case '0', '1', '2', '3', '4', '5', '6', '7': 241 | n, base, max = 3, 8, 255 242 | case 'x': 243 | t.nextChar() 244 | n, base, max = 2, 16, 255 245 | case 'u': 246 | t.nextChar() 247 | n, base, max = 4, 16, unicode.MaxRune 248 | case 'U': 249 | t.nextChar() 250 | n, base, max = 8, 16, unicode.MaxRune 251 | default: 252 | msg := "unknown escape sequence" 253 | if t.r < 0 { 254 | msg = "escape sequence not terminated" 255 | } 256 | t.error(msg) 257 | } 258 | 259 | if r > 0 { 260 | return r 261 | } 262 | 263 | var x uint32 264 | for n > 0 { 265 | d := uint32(digitVal(t.r)) 266 | if d >= base { 267 | msg := fmt.Sprintf("illegal character %#U in escape sequence", t.r) 268 | if t.r < 0 { 269 | msg = "escape sequence not terminated" 270 | } 271 | t.error(msg) 272 | } 273 | x = x*base + d 274 | t.nextChar() 275 | n-- 276 | if n == 0 && base == 16 && max == 255 && t.r == '\\' { 277 | rd := t.readOffset 278 | t.nextChar() 279 | if t.r == 'x' { 280 | n = 2 281 | max = unicode.MaxRune 282 | t.nextChar() 283 | } else { 284 | t.readOffset = rd 285 | } 286 | } 287 | } 288 | 289 | if x > max || 0xD800 <= x && x < 0xE000 { 290 | t.error("escape sequence is invalid Unicode code point") 291 | } 292 | 293 | return rune(x) 294 | } 295 | 296 | func (t *tokenizer) scanString(quote rune) string { 297 | var result string 298 | for { 299 | ch := t.r 300 | if ch < 0 { 301 | t.error("string literal not terminated") 302 | } 303 | t.nextChar() 304 | if ch == quote { 305 | break 306 | } 307 | if ch == '\\' { 308 | ch = t.scanEscape(quote) 309 | } 310 | result += string(ch) 311 | } 312 | return result 313 | } 314 | 315 | func (t *tokenizer) skipWhitespace() { 316 | for t.r == ' ' || t.r == '\t' || t.r == '\r' { 317 | t.nextChar() 318 | } 319 | } 320 | 321 | func (t *tokenizer) needSemi(tok ast.Token) bool { 322 | return (tok == ast.TokenId || tok == ast.TokenFloat || tok == ast.TokenInt || tok == ast.TokenString || 323 | tok == ast.TokenBreak || tok == ast.TokenContinue || tok == ast.TokenReturn || tok == ast.TokenPanic) 324 | } 325 | 326 | // functions that look 1 or 2 characters ahead, 327 | // and return the given token types based on that 328 | 329 | func (t *tokenizer) maybe1(a ast.Token, c1 rune, t1 ast.Token) ast.Token { 330 | offset := t.readOffset 331 | 332 | t.nextChar() 333 | if t.r == c1 { 334 | return t1 335 | } 336 | 337 | t.readOffset = offset 338 | return a 339 | } 340 | 341 | func (t *tokenizer) maybe2(a ast.Token, c1 rune, t1 ast.Token, c2 rune, t2 ast.Token) ast.Token { 342 | offset := t.readOffset 343 | 344 | t.nextChar() 345 | if t.r == c1 { 346 | return t1 347 | } 348 | if t.r == c2 { 349 | return t2 350 | } 351 | 352 | t.readOffset = offset 353 | return a 354 | } 355 | 356 | func (t *tokenizer) maybe3(a ast.Token, c1 rune, t1 ast.Token, c2 rune, t2 ast.Token, c3 rune, t3 ast.Token) ast.Token { 357 | offset := t.readOffset 358 | 359 | t.nextChar() 360 | if t.r == c1 { 361 | return t1 362 | } 363 | if t.r == c2 { 364 | return t2 365 | } 366 | if t.r == c3 { 367 | return t3 368 | } 369 | 370 | t.readOffset = offset 371 | return a 372 | } 373 | 374 | // does the actual scanning and return the type of the token 375 | // and a literal string representing it 376 | func (t *tokenizer) scan() (ast.Token, string) { 377 | t.skipWhitespace() 378 | 379 | switch ch := t.r; { 380 | case isLetter(t.r): 381 | lit := t.scanIdentifier() 382 | kwtype, ok := ast.Keyword(lit) 383 | if ok { 384 | return kwtype, lit 385 | } 386 | return ast.TokenId, lit 387 | case isDigit(t.r): 388 | return t.scanNumber(false) 389 | case t.r == '\'' || t.r == '"': 390 | t.nextChar() 391 | return ast.TokenString, t.scanString(ch) 392 | default: 393 | if t.r == '/' { 394 | t.nextChar() 395 | if t.scanComment() { 396 | return t.nextToken() 397 | } 398 | 399 | if t.r == '=' { 400 | t.nextChar() 401 | return ast.TokenDiveq, "/=" 402 | } 403 | return ast.TokenDiv, "/" 404 | } 405 | 406 | tok := ast.Token(-1) 407 | offs := t.offset 408 | 409 | switch t.r { 410 | case '\n': 411 | tok = ast.TokenNewline 412 | case '+': 413 | tok = t.maybe2(ast.TokenPlus, '=', ast.TokenPluseq, '+', ast.TokenPlusplus) 414 | case '-': 415 | tok = t.maybe3(ast.TokenMinus, '=', ast.TokenMinuseq, '-', ast.TokenMinusminus, '>', ast.TokenMinusgt) 416 | case '*': 417 | tok = t.maybe2(ast.TokenTimes, '=', ast.TokenTimeseq, '*', ast.TokenTimestimes) 418 | case '%': 419 | tok = ast.TokenMod 420 | case '&': 421 | tok = t.maybe2(ast.TokenAmp, '=', ast.TokenAmpeq, '&', ast.TokenAmpamp) 422 | case '|': 423 | tok = t.maybe2(ast.TokenPipe, '=', ast.TokenPipeeq, '|', ast.TokenPipepipe) 424 | case '^': 425 | tok = t.maybe1(ast.TokenTilde, '=', ast.TokenTildeeq) 426 | case '<': 427 | tok = t.maybe2(ast.TokenLt, '=', ast.TokenLteq, '<', ast.TokenLtlt) 428 | case '>': 429 | tok = t.maybe2(ast.TokenGt, '=', ast.TokenGteq, '>', ast.TokenGtgt) 430 | case '=': 431 | tok = t.maybe1(ast.TokenEq, '=', ast.TokenEqeq) 432 | case ':': 433 | tok = t.maybe1(ast.TokenColon, '=', ast.TokenColoneq) 434 | case ';': 435 | tok = ast.TokenSemicolon 436 | case ',': 437 | tok = ast.TokenComma 438 | case '!': 439 | tok = t.maybe1(ast.TokenBang, '=', ast.TokenBangeq) 440 | case '?': 441 | tok = ast.TokenQuestion 442 | case '(': 443 | tok = ast.TokenLparen 444 | case ')': 445 | tok = ast.TokenRparen 446 | case '[': 447 | tok = ast.TokenLbrack 448 | case ']': 449 | tok = ast.TokenRbrack 450 | case '{': 451 | tok = ast.TokenLbrace 452 | case '}': 453 | tok = ast.TokenRbrace 454 | case '.': 455 | t.nextChar() 456 | if isDigit(t.r) { 457 | return t.scanNumber(true) 458 | } else if t.r == '.' { 459 | t.nextChar() 460 | if t.r == '.' { 461 | t.nextChar() 462 | return ast.TokenDotdotdot, "..." 463 | } 464 | } else { 465 | return ast.TokenDot, "." 466 | } 467 | } 468 | 469 | if tok != eof { 470 | t.nextChar() 471 | return tok, string(t.src[offs:t.offset]) 472 | } 473 | } 474 | 475 | if t.offset >= len(t.src) { 476 | return ast.TokenEos, "end" 477 | } 478 | 479 | fmt.Print(string(t.r)) 480 | return ast.TokenIllegal, "" 481 | } 482 | 483 | func (t *tokenizer) nextToken() (ast.Token, string) { 484 | if t.insertSemi { 485 | t.insertSemi = false 486 | t.last = ast.TokenSemicolon 487 | return ast.TokenSemicolon, ";" 488 | } 489 | tok, literal := t.scan() 490 | if tok == ast.TokenNewline && t.needSemi(t.last) { 491 | t.insertSemi = true 492 | } 493 | t.last = tok 494 | return tok, literal 495 | } 496 | 497 | func (t *tokenizer) init(source []byte, filename string) { 498 | t.src = source 499 | t.filename = filename 500 | t.lineno = 1 501 | 502 | // fetch the first char 503 | t.nextChar() 504 | } 505 | -------------------------------------------------------------------------------- /pretty/ast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | // 3 | // Pretty-print the AST to a buffer 4 | 5 | package pretty 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "github.com/glhrmfrts/yo/ast" 11 | ) 12 | 13 | type prettyprinter struct { 14 | indent int 15 | indentSize int 16 | buf bytes.Buffer 17 | } 18 | 19 | func (p *prettyprinter) doIndent() { 20 | for i := 0; i < p.indent*p.indentSize; i++ { 21 | p.buf.WriteString(" ") 22 | } 23 | } 24 | 25 | func (p *prettyprinter) VisitNil(node *ast.Nil, data interface{}) { 26 | p.buf.WriteString("(nil)") 27 | } 28 | 29 | func (p *prettyprinter) VisitBool(node *ast.Bool, data interface{}) { 30 | var val string 31 | if node.Value { 32 | val = "true" 33 | } else { 34 | val = "false" 35 | } 36 | p.buf.WriteString("(" + val + ")") 37 | } 38 | 39 | func (p *prettyprinter) VisitNumber(node *ast.Number, data interface{}) { 40 | p.buf.WriteString(fmt.Sprintf("(number %f)", node.Value)) 41 | } 42 | 43 | func (p *prettyprinter) VisitId(node *ast.Id, data interface{}) { 44 | p.buf.WriteString("(id " + node.Value + ")") 45 | } 46 | 47 | func (p *prettyprinter) VisitString(node *ast.String, data interface{}) { 48 | p.buf.WriteString("(string \"" + node.Value + "\")") 49 | } 50 | 51 | func (p *prettyprinter) VisitArray(node *ast.Array, data interface{}) { 52 | p.buf.WriteString("(array") 53 | p.indent++ 54 | 55 | for _, n := range node.Elements { 56 | p.buf.WriteString("\n") 57 | p.doIndent() 58 | n.Accept(p, nil) 59 | } 60 | 61 | p.indent-- 62 | p.buf.WriteString(")") 63 | } 64 | 65 | func (p *prettyprinter) VisitObjectField(node *ast.ObjectField, data interface{}) { 66 | p.buf.WriteString("(field\n") 67 | p.indent++ 68 | p.doIndent() 69 | 70 | p.buf.WriteString("'" + node.Key + "'") 71 | p.buf.WriteString("\n") 72 | p.doIndent() 73 | 74 | if node.Value != nil { 75 | node.Value.Accept(p, nil) 76 | } 77 | 78 | p.indent-- 79 | p.buf.WriteString(")") 80 | } 81 | 82 | func (p *prettyprinter) VisitObject(node *ast.Object, data interface{}) { 83 | p.buf.WriteString("(object") 84 | p.indent++ 85 | 86 | for _, f := range node.Fields { 87 | p.buf.WriteString("\n") 88 | p.doIndent() 89 | f.Accept(p, nil) 90 | } 91 | 92 | p.indent-- 93 | p.buf.WriteString(")") 94 | } 95 | 96 | func (p *prettyprinter) VisitFunction(node *ast.Function, data interface{}) { 97 | p.buf.WriteString("(func ") 98 | if node.Name != nil { 99 | node.Name.Accept(p, nil) 100 | } 101 | p.buf.WriteString("\n") 102 | p.indent++ 103 | 104 | for _, a := range node.Args { 105 | p.doIndent() 106 | a.Accept(p, nil) 107 | p.buf.WriteString("\n") 108 | } 109 | 110 | p.doIndent() 111 | p.buf.WriteString("->\n") 112 | 113 | p.doIndent() 114 | node.Body.Accept(p, nil) 115 | 116 | p.indent-- 117 | p.buf.WriteString(")") 118 | } 119 | 120 | func (p *prettyprinter) VisitSelector(node *ast.Selector, data interface{}) { 121 | p.buf.WriteString("(selector\n") 122 | 123 | p.indent++ 124 | p.doIndent() 125 | 126 | node.Left.Accept(p, nil) 127 | 128 | p.buf.WriteString("\n") 129 | p.doIndent() 130 | 131 | p.indent-- 132 | p.buf.WriteString("'" + node.Value + "')") 133 | } 134 | 135 | func (p *prettyprinter) VisitSubscript(node *ast.Subscript, data interface{}) { 136 | p.buf.WriteString("(subscript\n") 137 | 138 | p.indent++ 139 | p.doIndent() 140 | 141 | node.Left.Accept(p, nil) 142 | 143 | p.buf.WriteString("\n") 144 | p.doIndent() 145 | 146 | node.Right.Accept(p, nil) 147 | 148 | p.indent-- 149 | p.buf.WriteString(")") 150 | } 151 | 152 | func (p *prettyprinter) VisitSlice(node *ast.Slice, data interface{}) { 153 | p.buf.WriteString("(slice\n") 154 | 155 | p.indent++ 156 | p.doIndent() 157 | 158 | node.Start.Accept(p, nil) 159 | 160 | p.buf.WriteString("\n") 161 | p.doIndent() 162 | 163 | node.End.Accept(p, nil) 164 | 165 | p.indent-- 166 | p.buf.WriteString(")") 167 | } 168 | 169 | func (p *prettyprinter) VisitKwArg(node *ast.KwArg, data interface{}) { 170 | p.buf.WriteString("(kwarg\n") 171 | 172 | p.indent++ 173 | p.doIndent() 174 | 175 | p.buf.WriteString("'" + node.Key + "'\n") 176 | 177 | p.doIndent() 178 | node.Value.Accept(p, nil) 179 | 180 | p.indent-- 181 | p.buf.WriteString(")") 182 | } 183 | 184 | func (p *prettyprinter) VisitVarArg(node *ast.VarArg, data interface{}) { 185 | p.buf.WriteString("(vararg ") 186 | node.Arg.Accept(p, nil) 187 | p.buf.WriteString(")") 188 | } 189 | 190 | func (p *prettyprinter) VisitCallExpr(node *ast.CallExpr, data interface{}) { 191 | p.buf.WriteString("(call\n") 192 | p.indent++ 193 | p.doIndent() 194 | 195 | node.Left.Accept(p, nil) 196 | 197 | for _, arg := range node.Args { 198 | p.buf.WriteString("\n") 199 | p.doIndent() 200 | arg.Accept(p, nil) 201 | } 202 | 203 | p.indent-- 204 | p.buf.WriteString(")") 205 | } 206 | 207 | func (p *prettyprinter) VisitPostfixExpr(node *ast.PostfixExpr, data interface{}) { 208 | p.buf.WriteString(fmt.Sprintf("(postfix %s\n", node.Op)) 209 | 210 | p.indent++ 211 | p.doIndent() 212 | 213 | node.Left.Accept(p, nil) 214 | 215 | p.indent-- 216 | p.buf.WriteString(")") 217 | } 218 | 219 | func (p *prettyprinter) VisitUnaryExpr(node *ast.UnaryExpr, data interface{}) { 220 | p.buf.WriteString(fmt.Sprintf("(unary %s\n", node.Op)) 221 | 222 | p.indent++ 223 | p.doIndent() 224 | 225 | node.Right.Accept(p, nil) 226 | 227 | p.indent-- 228 | p.buf.WriteString(")") 229 | } 230 | 231 | func (p *prettyprinter) VisitBinaryExpr(node *ast.BinaryExpr, data interface{}) { 232 | p.buf.WriteString(fmt.Sprintf("(binary %s\n", node.Op)) 233 | 234 | p.indent++ 235 | p.doIndent() 236 | 237 | node.Left.Accept(p, nil) 238 | 239 | p.buf.WriteString("\n") 240 | p.doIndent() 241 | 242 | node.Right.Accept(p, nil) 243 | 244 | p.indent-- 245 | p.buf.WriteString(")") 246 | } 247 | 248 | func (p *prettyprinter) VisitTernaryExpr(node *ast.TernaryExpr, data interface{}) { 249 | p.buf.WriteString("(ternary\n") 250 | p.indent++ 251 | p.doIndent() 252 | 253 | node.Cond.Accept(p, nil) 254 | 255 | p.buf.WriteString("\n") 256 | p.doIndent() 257 | p.buf.WriteString("?\n") 258 | p.doIndent() 259 | 260 | node.Then.Accept(p, nil) 261 | 262 | p.buf.WriteString("\n") 263 | p.doIndent() 264 | p.buf.WriteString(":\n") 265 | p.doIndent() 266 | 267 | node.Else.Accept(p, nil) 268 | 269 | p.indent-- 270 | p.buf.WriteString(")") 271 | } 272 | 273 | func (p *prettyprinter) VisitDeclaration(node *ast.Declaration, data interface{}) { 274 | keyword := "var" 275 | if node.IsConst { 276 | keyword = "const" 277 | } 278 | 279 | p.buf.WriteString(fmt.Sprintf("(%s", keyword)) 280 | p.indent++ 281 | 282 | for _, id := range node.Left { 283 | p.buf.WriteString("\n") 284 | p.doIndent() 285 | id.Accept(p, nil) 286 | } 287 | 288 | p.buf.WriteString("\n") 289 | p.doIndent() 290 | p.buf.WriteString("=") 291 | 292 | for _, node := range node.Right { 293 | p.buf.WriteString("\n") 294 | p.doIndent() 295 | node.Accept(p, nil) 296 | } 297 | 298 | p.indent-- 299 | p.buf.WriteString(")") 300 | } 301 | 302 | func (p *prettyprinter) VisitAssignment(node *ast.Assignment, data interface{}) { 303 | p.buf.WriteString("(assignment") 304 | p.indent++ 305 | 306 | for _, node := range node.Left { 307 | p.buf.WriteString("\n") 308 | p.doIndent() 309 | node.Accept(p, nil) 310 | } 311 | 312 | p.buf.WriteString("\n") 313 | p.doIndent() 314 | p.buf.WriteString(node.Op.String()) 315 | 316 | for _, node := range node.Right { 317 | p.buf.WriteString("\n") 318 | p.doIndent() 319 | node.Accept(p, nil) 320 | } 321 | 322 | p.indent-- 323 | p.buf.WriteString(")") 324 | } 325 | 326 | func (p *prettyprinter) VisitBranchStmt(node *ast.BranchStmt, data interface{}) { 327 | p.buf.WriteString(fmt.Sprintf("(%s)", node.Type)) 328 | } 329 | 330 | func (p *prettyprinter) VisitReturnStmt(node *ast.ReturnStmt, data interface{}) { 331 | p.buf.WriteString("(return") 332 | p.indent++ 333 | 334 | for _, v := range node.Values { 335 | p.buf.WriteString("\n") 336 | p.doIndent() 337 | v.Accept(p, nil) 338 | } 339 | 340 | p.indent-- 341 | p.buf.WriteString(")") 342 | } 343 | 344 | func (p *prettyprinter) VisitPanicStmt(node *ast.PanicStmt, data interface{}) { 345 | p.buf.WriteString("(panic\n") 346 | p.indent++ 347 | p.doIndent() 348 | node.Err.Accept(p, nil) 349 | p.indent-- 350 | p.buf.WriteString(")") 351 | } 352 | 353 | func (p *prettyprinter) VisitIfStmt(node *ast.IfStmt, data interface{}) { 354 | p.buf.WriteString("(if\n") 355 | p.indent++ 356 | 357 | if node.Init != nil { 358 | p.doIndent() 359 | node.Init.Accept(p, nil) 360 | p.buf.WriteString("\n") 361 | } 362 | 363 | p.doIndent() 364 | node.Cond.Accept(p, nil) 365 | p.buf.WriteString("\n") 366 | 367 | p.doIndent() 368 | node.Body.Accept(p, nil) 369 | p.buf.WriteString("\n") 370 | p.doIndent() 371 | 372 | if node.Else != nil { 373 | node.Else.Accept(p, nil) 374 | } 375 | 376 | p.indent-- 377 | p.buf.WriteString(")") 378 | } 379 | 380 | func (p *prettyprinter) VisitForIteratorStmt(node *ast.ForIteratorStmt, data interface{}) { 381 | p.buf.WriteString("(for iterator\n") 382 | p.indent++ 383 | p.doIndent() 384 | p.buf.WriteString("key: ") 385 | node.Key.Accept(p, nil) 386 | 387 | if node.Value != nil { 388 | p.buf.WriteString("\n") 389 | p.doIndent() 390 | p.buf.WriteString("value: ") 391 | node.Value.Accept(p, nil) 392 | } 393 | 394 | p.buf.WriteString("\n") 395 | p.doIndent() 396 | p.buf.WriteString("collection: ") 397 | node.Collection.Accept(p, nil) 398 | 399 | if node.When != nil { 400 | p.buf.WriteString("\n") 401 | p.doIndent() 402 | p.buf.WriteString("when: ") 403 | node.When.Accept(p, nil) 404 | } 405 | 406 | p.buf.WriteString("\n") 407 | p.doIndent() 408 | node.Body.Accept(p, nil) 409 | 410 | p.indent-- 411 | p.buf.WriteString("\n") 412 | } 413 | 414 | func (p *prettyprinter) VisitForStmt(node *ast.ForStmt, data interface{}) { 415 | p.buf.WriteString("(for\n") 416 | p.indent++ 417 | 418 | if node.Init != nil { 419 | p.doIndent() 420 | p.buf.WriteString("init: ") 421 | node.Init.Accept(p, nil) 422 | p.buf.WriteString("\n") 423 | } 424 | 425 | if node.Cond != nil { 426 | p.doIndent() 427 | p.buf.WriteString("cond: ") 428 | node.Cond.Accept(p, nil) 429 | p.buf.WriteString("\n") 430 | } 431 | 432 | p.doIndent() 433 | if node.Step != nil { 434 | p.buf.WriteString("step: ") 435 | node.Step.Accept(p, nil) 436 | p.buf.WriteString("\n") 437 | p.doIndent() 438 | } 439 | 440 | node.Body.Accept(p, nil) 441 | p.indent-- 442 | p.buf.WriteString(")") 443 | } 444 | 445 | func (p *prettyprinter) VisitRecoverBlock(node *ast.RecoverBlock, data interface{}) { 446 | p.buf.WriteString(fmt.Sprintf("(recover %s ", node.Id.Value)) 447 | node.Block.Accept(p, nil) 448 | p.buf.WriteString(")") 449 | } 450 | 451 | func (p *prettyprinter) VisitTryRecoverStmt(node *ast.TryRecoverStmt, data interface{}) { 452 | p.buf.WriteString("(try\n") 453 | p.indent++ 454 | 455 | if node.Try != nil { 456 | p.doIndent() 457 | p.buf.WriteString("try: ") 458 | node.Try.Accept(p, nil) 459 | p.buf.WriteString("\n") 460 | } 461 | 462 | if node.Recover != nil { 463 | p.doIndent() 464 | p.buf.WriteString("recover: ") 465 | node.Recover.Accept(p, nil) 466 | p.buf.WriteString("\n") 467 | } 468 | 469 | p.doIndent() 470 | if node.Finally != nil { 471 | p.buf.WriteString("finally: ") 472 | node.Finally.Accept(p, nil) 473 | } 474 | 475 | p.indent-- 476 | p.buf.WriteString(")") 477 | } 478 | 479 | func (p *prettyprinter) VisitBlock(node *ast.Block, data interface{}) { 480 | p.buf.WriteString("(block") 481 | p.indent++ 482 | 483 | for _, n := range node.Nodes { 484 | p.buf.WriteString("\n") 485 | p.doIndent() 486 | n.Accept(p, nil) 487 | } 488 | 489 | p.indent-- 490 | p.buf.WriteString(")") 491 | } 492 | 493 | func SyntaxTree(root ast.Node, indentSize int) string { 494 | v := prettyprinter{indentSize: indentSize} 495 | root.Accept(&v, nil) 496 | return v.buf.String() 497 | } 498 | -------------------------------------------------------------------------------- /pretty/func.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | // 3 | // "Disassemble" a function prototype (bytecode chunk) 4 | // into a human-readable string 5 | 6 | package pretty 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "github.com/glhrmfrts/yo" 12 | ) 13 | 14 | func doIndent(buf *bytes.Buffer, indent int) { 15 | for indent > 0 { 16 | buf.WriteString(" ") 17 | indent-- 18 | } 19 | } 20 | 21 | func disasmImpl(f *yo.Bytecode, buf *bytes.Buffer, indent int) { 22 | buf.WriteString("\n\n") 23 | doIndent(buf, indent) 24 | buf.WriteString(fmt.Sprintf("function at %s {{{\n", f.Source)) 25 | doIndent(buf, indent) 26 | buf.WriteString(fmt.Sprintf("constants: %d\n", f.NumConsts)) 27 | doIndent(buf, indent) 28 | for _, c := range f.Consts { 29 | buf.WriteString(fmt.Sprint("\t", c)) 30 | } 31 | buf.WriteString("\n\n") 32 | 33 | doIndent(buf, indent) 34 | buf.WriteString(fmt.Sprintf("funcs: %d\n", f.NumFuncs)) 35 | for _, f := range f.Funcs { 36 | disasmImpl(f, buf, indent+2) 37 | } 38 | 39 | doIndent(buf, indent) 40 | buf.WriteString("line\t#\topcode\t\targs\n") 41 | 42 | getRegOrConst := func(a uint) string { 43 | if a >= yo.OpConstOffset { 44 | return fmt.Sprint(f.Consts[a-yo.OpConstOffset]) 45 | } else { 46 | return fmt.Sprintf("!%d", a) 47 | } 48 | } 49 | 50 | doIndent(buf, indent) 51 | var currentLine uint32 52 | for i, instr := range f.Code { 53 | lineChanged := false 54 | if currentLine+1 < f.NumLines && (i >= int(f.Lines[currentLine+1].Instr)) { 55 | currentLine += 1 56 | lineChanged = true 57 | } 58 | 59 | line := f.Lines[currentLine] 60 | opcode := yo.OpGetOpcode(instr) 61 | pc := i + 1 62 | 63 | if lineChanged || i == 0 { 64 | buf.WriteString(fmt.Sprint(line.Line, "\t")) 65 | } else { 66 | buf.WriteString("\t") 67 | } 68 | 69 | buf.WriteString(fmt.Sprint(i)) 70 | buf.WriteString(fmt.Sprint("\t", opcode, "\t")) 71 | 72 | switch opcode { 73 | case yo.OpLoadnil: 74 | buf.WriteString(fmt.Sprintf("\t!%d !%d", yo.OpGetA(instr), yo.OpGetB(instr))) 75 | case yo.OpLoadconst: 76 | buf.WriteString(fmt.Sprintf("!%d %s", yo.OpGetA(instr), f.Consts[yo.OpGetBx(instr)])) 77 | case yo.OpUnm, yo.OpNot, yo.OpCmpl: 78 | bx := yo.OpGetBx(instr) 79 | bstr := getRegOrConst(bx) 80 | buf.WriteString(fmt.Sprintf("\t!%d %s", yo.OpGetA(instr), bstr)) 81 | case yo.OpAdd, yo.OpSub, yo.OpMul, yo.OpDiv, yo.OpPow, yo.OpShl, yo.OpShr, 82 | yo.OpAnd, yo.OpOr, yo.OpXor, yo.OpLe, yo.OpLt, yo.OpEq, yo.OpNe, 83 | yo.OpGetIndex, yo.OpSetIndex: 84 | a, b, c := yo.OpGetA(instr), yo.OpGetB(instr), yo.OpGetC(instr) 85 | bstr, cstr := getRegOrConst(b), getRegOrConst(c) 86 | buf.WriteString(fmt.Sprintf("\t!%d %s %s", a, bstr, cstr)) 87 | case yo.OpAppend, yo.OpReturn: 88 | a, b := yo.OpGetA(instr), yo.OpGetB(instr) 89 | buf.WriteString(fmt.Sprintf("\t!%d #%d", a, b)) 90 | case yo.OpMove: 91 | a, b := yo.OpGetA(instr), yo.OpGetB(instr) 92 | buf.WriteString(fmt.Sprintf("\t!%d !%d", a, b)) 93 | case yo.OpLoadglobal, yo.OpSetglobal: 94 | a, bx := yo.OpGetA(instr), yo.OpGetBx(instr) 95 | buf.WriteString(fmt.Sprintf("!%d %s", a, f.Consts[bx])) 96 | case yo.OpLoadFree, yo.OpSetFree: 97 | a, bx := yo.OpGetA(instr), yo.OpGetBx(instr) 98 | buf.WriteString(fmt.Sprintf("\t!%d %s", a, f.Consts[bx])) 99 | case yo.OpCall, yo.OpCallmethod: 100 | a, b, c := yo.OpGetA(instr), yo.OpGetB(instr), yo.OpGetC(instr) 101 | if opcode == yo.OpCall { 102 | buf.WriteString(fmt.Sprintf("\t!%d #%d #%d", a, b, c)) 103 | } else { 104 | buf.WriteString(fmt.Sprintf("!%d #%d #%d", a, b, c)) 105 | } 106 | case yo.OpArray, yo.OpObject: 107 | buf.WriteString(fmt.Sprintf("\t!%d", yo.OpGetA(instr))) 108 | case yo.OpFunc: 109 | buf.WriteString(fmt.Sprintf("\t!%d &%d", yo.OpGetA(instr), yo.OpGetBx(instr))) 110 | case yo.OpJmp: 111 | sbx := yo.OpGetsBx(instr) 112 | buf.WriteString(fmt.Sprintf("\t->%d", pc+sbx)) 113 | case yo.OpJmptrue, yo.OpJmpfalse: 114 | a, sbx := yo.OpGetA(instr), yo.OpGetsBx(instr) 115 | astr := getRegOrConst(a) 116 | buf.WriteString(fmt.Sprintf("%s ->%d", astr, pc+sbx)) 117 | case yo.OpForbegin: 118 | a, b := yo.OpGetA(instr), yo.OpGetB(instr) 119 | buf.WriteString(fmt.Sprintf("!%d !%d", a, b)) 120 | case yo.OpForiter: 121 | a, b, c := yo.OpGetA(instr), yo.OpGetB(instr), yo.OpGetC(instr) 122 | buf.WriteString(fmt.Sprintf("\t!%d !%d !%d", a, b, c)) 123 | } 124 | 125 | buf.WriteString("\n") 126 | doIndent(buf, indent) 127 | } 128 | 129 | buf.WriteString("}}}\n\n\n") 130 | } 131 | 132 | func Disasm(f *yo.Bytecode) string { 133 | var buf bytes.Buffer 134 | disasmImpl(f, &buf, 0) 135 | return buf.String() 136 | } 137 | -------------------------------------------------------------------------------- /run/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/glhrmfrts/yo" 8 | "github.com/glhrmfrts/yo/parse" 9 | "github.com/glhrmfrts/yo/pretty" 10 | "io/ioutil" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | filename := os.Args[1] 16 | source, err := ioutil.ReadFile(filename) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | root, err := parse.ParseFile(source, filename) 22 | if err != nil { 23 | fmt.Println(err.Error()) 24 | return 25 | } 26 | 27 | //fmt.Println(pretty.SyntaxTree(root, 2)) 28 | 29 | code, err := yo.Compile(root, filename) 30 | if err != nil { 31 | fmt.Println(err.Error()) 32 | return 33 | } 34 | 35 | fmt.Println(pretty.Disasm(code)) 36 | 37 | vm := yo.NewVM() 38 | vm.RunBytecode(code) 39 | } 40 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package yo 4 | 5 | import ( 6 | "fmt" 7 | "math" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | func assert(cond bool, msg string) { 14 | if !cond { 15 | fmt.Printf("assertion failed: %s\n", msg) 16 | os.Exit(1) 17 | } 18 | } 19 | 20 | func isInt(n float64) bool { 21 | return math.Trunc(n) == n 22 | } 23 | 24 | // Parse a number from a string 25 | func parseNumber(number string) (float64, error) { 26 | var value float64 27 | number = strings.Trim(number, " \t\n") 28 | if v, err := strconv.ParseInt(number, 0, 64); err != nil { 29 | if v2, err2 := strconv.ParseFloat(number, 64); err2 != nil { 30 | return 0, err2 31 | } else { 32 | value = float64(v2) 33 | } 34 | } else { 35 | value = float64(v) 36 | } 37 | return value, nil 38 | } 39 | 40 | func println(v ...interface{}) { 41 | fmt.Println(v...) 42 | } 43 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Guilherme Nemeth 2 | 3 | package yo 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | type ( 10 | // ValueType is an internal enumeration which identifies 11 | // the type of the values without the need for type assertion. 12 | ValueType int 13 | 14 | // Value interface 15 | Value interface { 16 | // manual type retrieval seems to be faster than 17 | // go's interface type assertion (runtime.I2T2) 18 | assertFloat64() (float64, bool) 19 | assertBool() (bool, bool) 20 | assertString() (string, bool) 21 | 22 | // Type returns the corresponding ValueType enumeration value 23 | // of this value. 24 | Type() ValueType 25 | 26 | // String returns the representation of this value as a string, 27 | // it is the default way to convert a value to a string. 28 | String() string 29 | 30 | // ToBool converts the value to a boolean value. 31 | // If it's is nil or false it returns false, otherwise returns true. 32 | ToBool() bool 33 | } 34 | 35 | // Nil represents the absence of a usable value. 36 | Nil struct{} 37 | 38 | // Bool can be either true or false. 39 | Bool bool 40 | 41 | // A Number is a double-precision floating point number. 42 | Number float64 43 | 44 | // A String is an immutable sequence of bytes. 45 | String string 46 | 47 | // GoFunc is a function defined in the host application, which 48 | // is callable from the script. 49 | GoFunc func(*FuncCall) 50 | 51 | // Func is a function defined in the script. 52 | Func struct { 53 | Bytecode *Bytecode 54 | } 55 | 56 | // Array is a collection of Values stored contiguously in memory, 57 | // it's index starts at 0. 58 | Array []Value 59 | 60 | // Object is a map that maps strings to Values, and may have a 61 | // parent Object which is used to look for keys that are not in it's own map. 62 | Object struct { 63 | Fields map[string]Value 64 | Parent *Object 65 | } 66 | 67 | // GoObject is an object that allows the host application to maintain 68 | // custom data throughout the script, the script user will see it as 69 | // a regular object. 70 | GoObject struct { 71 | Object 72 | Data interface{} 73 | } 74 | 75 | // Chan is an object that allows goroutines to 76 | // communicate/send Values to one another. 77 | Chan chan Value 78 | ) 79 | 80 | const ( 81 | ValueNil ValueType = iota 82 | ValueBool 83 | ValueNumber 84 | ValueString 85 | ValueGoFunc 86 | ValueFunc 87 | ValueArray 88 | ValueObject 89 | ValueChan 90 | ) 91 | 92 | var ( 93 | valueTypeNames = [9]string{"nil", "bool", "number", "string", "func", "func", "array", "object", "chan"} 94 | ) 95 | 96 | func (t ValueType) String() string { 97 | return valueTypeNames[t] 98 | } 99 | 100 | // Nil 101 | 102 | func (v Nil) assertFloat64() (float64, bool) { return 0, false } 103 | func (v Nil) assertBool() (bool, bool) { return false, false } 104 | func (v Nil) assertString() (string, bool) { return "", false } 105 | 106 | func (v Nil) Type() ValueType { return ValueNil } 107 | func (v Nil) ToBool() bool { return false } 108 | func (v Nil) String() string { 109 | return "nil" 110 | } 111 | 112 | // Bool 113 | 114 | func (v Bool) assertFloat64() (float64, bool) { return 0, false } 115 | func (v Bool) assertBool() (bool, bool) { return bool(v), true } 116 | func (v Bool) assertString() (string, bool) { return "", false } 117 | 118 | func (v Bool) Type() ValueType { return ValueBool } 119 | func (v Bool) ToBool() bool { return bool(v) } 120 | func (v Bool) String() string { 121 | if bool(v) { 122 | return "true" 123 | } 124 | return "false" 125 | } 126 | 127 | // Number 128 | 129 | func (v Number) assertFloat64() (float64, bool) { return float64(v), true } 130 | func (v Number) assertBool() (bool, bool) { return false, false } 131 | func (v Number) assertString() (string, bool) { return "", false } 132 | 133 | func (v Number) Type() ValueType { return ValueNumber } 134 | func (v Number) ToBool() bool { return true } 135 | func (v Number) String() string { 136 | return fmt.Sprint(float64(v)) 137 | } 138 | 139 | // String 140 | 141 | func (v String) assertFloat64() (float64, bool) { return 0, false } 142 | func (v String) assertBool() (bool, bool) { return false, false } 143 | func (v String) assertString() (string, bool) { return string(v), true } 144 | 145 | func (v String) Type() ValueType { return ValueString } 146 | func (v String) ToBool() bool { return true } 147 | func (v String) String() string { 148 | return string(v) 149 | } 150 | 151 | // GoFunc 152 | 153 | func (v GoFunc) assertFloat64() (float64, bool) { return 0, false } 154 | func (v GoFunc) assertBool() (bool, bool) { return false, false } 155 | func (v GoFunc) assertString() (string, bool) { return "", false } 156 | 157 | func (v GoFunc) Type() ValueType { return ValueGoFunc } 158 | func (v GoFunc) ToBool() bool { return true } 159 | func (v GoFunc) String() string { return "func" } 160 | 161 | // Func 162 | 163 | func (v Func) assertFloat64() (float64, bool) { return 0, false } 164 | func (v Func) assertBool() (bool, bool) { return false, false } 165 | func (v Func) assertString() (string, bool) { return "", false } 166 | 167 | func (v Func) Type() ValueType { return ValueFunc } 168 | func (v Func) ToBool() bool { return true } 169 | func (v Func) String() string { return "func" } 170 | 171 | // Array 172 | 173 | func (v Array) assertFloat64() (float64, bool) { return 0, false } 174 | func (v Array) assertBool() (bool, bool) { return false, false } 175 | func (v Array) assertString() (string, bool) { return "", false } 176 | 177 | func (v Array) Type() ValueType { return ValueArray } 178 | func (v Array) ToBool() bool { return true } 179 | func (v Array) String() string { 180 | return fmt.Sprintf("%v", []Value(v)) 181 | } 182 | 183 | // Object 184 | 185 | func (v *Object) assertFloat64() (float64, bool) { return 0, false } 186 | func (v *Object) assertBool() (bool, bool) { return false, false } 187 | func (v *Object) assertString() (string, bool) { return "", false } 188 | 189 | func (v *Object) Type() ValueType { return ValueObject } 190 | func (v *Object) ToBool() bool { return true } 191 | func (v *Object) String() string { 192 | return fmt.Sprintf("%v", v.Fields) 193 | } 194 | 195 | func NewObject(parent *Object, fields map[string]Value) *Object { 196 | return &Object{ 197 | Parent: parent, 198 | Fields: fields, 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /vm.go: -------------------------------------------------------------------------------- 1 | package yo 2 | 3 | import ( 4 | "math" 5 | "github.com/glhrmfrts/yo/parse" 6 | ) 7 | 8 | const ( 9 | MaxRegisters = 249 10 | CallStackSize = 255 11 | ) 12 | 13 | type opHandler func(*VM, *callFrame, uint32) int 14 | 15 | var opTable [kOpCount]opHandler 16 | 17 | type callFrame struct { 18 | pc int 19 | line int 20 | canRecover bool 21 | fn *Func 22 | r [MaxRegisters]Value 23 | } 24 | 25 | type callFrameStack struct { 26 | sp int 27 | stack [CallStackSize]callFrame 28 | } 29 | 30 | func (stack *callFrameStack) New() *callFrame { 31 | stack.sp += 1 32 | return &stack.stack[stack.sp-1] 33 | } 34 | 35 | func (stack *callFrameStack) Last() *callFrame { 36 | if stack.sp == 0 { 37 | return nil 38 | } 39 | return &stack.stack[stack.sp-1] 40 | } 41 | 42 | type FuncCall struct { 43 | Args []Value 44 | ExpectResults uint 45 | NumArgs uint 46 | NumResults uint 47 | 48 | results []Value 49 | } 50 | 51 | func (c *FuncCall) PushReturnValue(v Value) { 52 | c.results = append(c.results, v) 53 | c.NumResults++ 54 | } 55 | 56 | type VM struct { 57 | Globals map[string]Value 58 | 59 | currentFrame *callFrame 60 | calls callFrameStack 61 | error error 62 | } 63 | 64 | func (vm *VM) Define(name string, v Value) { 65 | vm.Globals[name] = v 66 | } 67 | 68 | func (vm *VM) RunString(source []byte, filename string) error { 69 | nodes, err := parse.ParseFile(source, filename) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | code, err := Compile(nodes, filename) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | return vm.RunBytecode(code) 80 | } 81 | 82 | func (vm *VM) RunBytecode(b *Bytecode) error { 83 | vm.currentFrame = vm.calls.New() 84 | vm.currentFrame.fn = &Func{b} 85 | 86 | return mainLoop(vm) 87 | } 88 | 89 | func NewVM() *VM { 90 | vm := &VM{ 91 | Globals: make(map[string]Value, 128), 92 | } 93 | 94 | defineBuiltins(vm) 95 | 96 | return vm 97 | } 98 | 99 | func init() { 100 | opTable = [kOpCount]opHandler{ 101 | func(vm *VM, cf *callFrame, instr uint32) int { // OpLoadNil 102 | a, b := OpGetA(instr), OpGetB(instr) 103 | for a <= b { 104 | cf.r[a] = Nil{} 105 | a++ 106 | } 107 | return 0 108 | }, 109 | func(vm *VM, cf *callFrame, instr uint32) int { // OpLoadConst 110 | a, bx := OpGetA(instr), OpGetBx(instr) 111 | cf.r[a] = cf.fn.Bytecode.Consts[bx] 112 | return 0 113 | }, 114 | func(vm *VM, cf *callFrame, instr uint32) int { // OpLoadGlobal 115 | a, bx := OpGetA(instr), OpGetBx(instr) 116 | str := cf.fn.Bytecode.Consts[bx].String() 117 | if g, ok := vm.Globals[str]; ok { 118 | cf.r[a] = g 119 | } else { 120 | //vm.setError("undefined global %s", str) 121 | return 1 122 | } 123 | return 0 124 | }, 125 | func(vm *VM, cf *callFrame, instr uint32) int { // OpSetGlobal 126 | // TODO: remove this instruction 127 | return 0 128 | }, 129 | func(vm *VM, cf *callFrame, instr uint32) int { // OpLoadRef 130 | return 0 131 | }, 132 | func(vm *VM, cf *callFrame, instr uint32) int { // OpSetRef 133 | return 0 134 | }, 135 | func(vm *VM, cf *callFrame, instr uint32) int { // OpUnm 136 | a, bx := OpGetA(instr), OpGetBx(instr) 137 | var bv Value 138 | if bx >= OpConstOffset { 139 | bv = cf.fn.Bytecode.Consts[bx-OpConstOffset] 140 | } else { 141 | bv = cf.r[bx] 142 | } 143 | f, ok := bv.assertFloat64() 144 | if !ok { 145 | //vm.setError("cannot perform unary minus on %s", bv.Type()) 146 | return 1 147 | } 148 | cf.r[a] = Number(-f) 149 | return 0 150 | }, 151 | func(vm *VM, cf *callFrame, instr uint32) int { // OpNot 152 | a, bx := OpGetA(instr), OpGetBx(instr) 153 | var bv Value 154 | if bx >= OpConstOffset { 155 | bv = cf.fn.Bytecode.Consts[bx-OpConstOffset] 156 | } else { 157 | bv = cf.r[bx] 158 | } 159 | cf.r[a] = Bool(!bv.ToBool()) 160 | return 0 161 | }, 162 | func(vm *VM, cf *callFrame, instr uint32) int { // OpCmpl 163 | a, bx := OpGetA(instr), OpGetBx(instr) 164 | var bv Value 165 | if bx >= OpConstOffset { 166 | bv = cf.fn.Bytecode.Consts[bx-OpConstOffset] 167 | } else { 168 | bv = cf.r[bx] 169 | } 170 | f, ok := bv.assertFloat64() 171 | if !ok || isInt(f) { 172 | //vm.setError("cannot perform complement on %s", bv.Type()) 173 | return 1 174 | } 175 | cf.r[a] = Number(float64(^int(f))) 176 | return 0 177 | }, 178 | opArith, // OpAdd 179 | opArith, // OpSub 180 | opArith, // OpMul 181 | opArith, // OpDiv 182 | opArith, // OpPow 183 | opArith, // OpShl 184 | opArith, // OpShr 185 | opArith, // OpAnd 186 | opArith, // OpOr 187 | opArith, // OpXor 188 | opCmp, // OpLt 189 | opCmp, // OpLe 190 | opCmp, // OpEq 191 | opCmp, // opNe 192 | func(vm *VM, cf *callFrame, instr uint32) int { // OpMove 193 | a, b := OpGetA(instr), OpGetB(instr) 194 | cf.r[a] = cf.r[b] 195 | return 0 196 | }, 197 | func(vm *VM, cf *callFrame, instr uint32) int { // OpGetIndex 198 | a, b, c := OpGetA(instr), OpGetB(instr), OpGetC(instr) 199 | v := cf.r[b] 200 | if v.Type() == ValueArray { 201 | var index Value 202 | if c >= OpConstOffset { 203 | index = cf.fn.Bytecode.Consts[c - OpConstOffset] 204 | } else { 205 | index = cf.r[c] 206 | } 207 | 208 | arr := []Value(v.(Array)) 209 | n, ok := index.assertFloat64() 210 | if !ok { 211 | // panic 212 | return 1 213 | } 214 | 215 | cf.r[a] = arr[int(n)] 216 | } 217 | 218 | return 0 219 | }, 220 | func(vm *VM, cf *callFrame, instr uint32) int { // OpSetIndex 221 | return 0 222 | }, 223 | func(vm *VM, cf *callFrame, instr uint32) int { // OpAppend 224 | a, b := OpGetA(instr), OpGetB(instr) 225 | from := a + 1 226 | to := from + b 227 | ptr := cf.r[a] 228 | arr := ptr.(*Array) 229 | *arr = append(*arr, cf.r[from:to]...) 230 | return 0 231 | }, 232 | func(vm *VM, cf *callFrame, instr uint32) int { // OpCall 233 | a, b, c := OpGetA(instr), OpGetB(instr), OpGetC(instr) 234 | fn := cf.r[a] 235 | if fn.Type() == ValueGoFunc { 236 | callGoFunc(vm, cf, fn.(GoFunc), a, b, c) 237 | } 238 | return 0 239 | }, 240 | func(vm *VM, cf *callFrame, instr uint32) int { // OpCallMethod 241 | return 0 242 | }, 243 | func(vm *VM, cf *callFrame, instr uint32) int { // OpArray 244 | arr := Array([]Value{}) 245 | cf.r[OpGetA(instr)] = &arr 246 | return 0 247 | }, 248 | func(vm *VM, cf *callFrame, instr uint32) int { // OpObject 249 | cf.r[OpGetA(instr)] = NewObject(nil, make(map[string]Value)) 250 | return 0 251 | }, 252 | func(vm *VM, cf *callFrame, instr uint32) int { // OpFunc 253 | return 0 254 | }, 255 | func(vm *VM, cf *callFrame, instr uint32) int { // OpJmp 256 | cf.pc += OpGetsBx(instr) 257 | return 0 258 | }, 259 | func(vm *VM, cf *callFrame, instr uint32) int { // OpJmpTrue 260 | a := OpGetA(instr) 261 | var val Value 262 | if a >= OpConstOffset { 263 | val = cf.fn.Bytecode.Consts[a-OpConstOffset] 264 | } else { 265 | val = cf.r[a] 266 | } 267 | if val.ToBool() { 268 | cf.pc += OpGetsBx(instr) 269 | } 270 | return 0 271 | }, 272 | func(vm *VM, cf *callFrame, instr uint32) int { // OpJmpFalse 273 | a := OpGetA(instr) 274 | var val Value 275 | if a >= OpConstOffset { 276 | val = cf.fn.Bytecode.Consts[a-OpConstOffset] 277 | } else { 278 | val = cf.r[a] 279 | } 280 | if !val.ToBool() { 281 | cf.pc += OpGetsBx(instr) 282 | } 283 | return 0 284 | }, 285 | func(vm *VM, cf *callFrame, instr uint32) int { // OpReturn 286 | return 0 287 | }, 288 | func(vm *VM, cf *callFrame, instr uint32) int { // OpForBegin 289 | return 0 290 | }, 291 | func(vm *VM, cf *callFrame, instr uint32) int { // OpForIter 292 | return 0 293 | }, 294 | } 295 | } 296 | 297 | func opArith(vm *VM, cf *callFrame, instr uint32) int { 298 | var vb, vc Value 299 | a, b, c := OpGetA(instr), OpGetB(instr), OpGetC(instr) 300 | if b >= OpConstOffset { 301 | vb = cf.fn.Bytecode.Consts[b-OpConstOffset] 302 | } else { 303 | vb = cf.r[b] 304 | } 305 | if c >= OpConstOffset { 306 | vc = cf.fn.Bytecode.Consts[c-OpConstOffset] 307 | } else { 308 | vc = cf.r[c] 309 | } 310 | fb, okb := vb.assertFloat64() 311 | fc, okc := vc.assertFloat64() 312 | if okb && okc { 313 | cf.r[a] = Number(numberArith(OpGetOpcode(instr), fb, fc)) 314 | } 315 | return 0 316 | } 317 | 318 | func numberArith(op Opcode, a, b float64) float64 { 319 | switch op { 320 | case OpAdd: 321 | return a + b 322 | case OpSub: 323 | return a - b 324 | case OpMul: 325 | return a * b 326 | case OpDiv: 327 | return a / b 328 | case OpPow: 329 | return math.Pow(a, b) 330 | case OpShl: 331 | return float64(uint(a) << uint(b)) 332 | case OpShr: 333 | return float64(uint(a) >> uint(b)) 334 | case OpAnd: 335 | return float64(uint(a) & uint(b)) 336 | case OpOr: 337 | return float64(uint(a) | uint(b)) 338 | case OpXor: 339 | return float64(uint(a) ^ uint(b)) 340 | default: 341 | return 0 342 | } 343 | } 344 | 345 | func opCmp(vm *VM, cf *callFrame, instr uint32) int { 346 | var vb, vc Value 347 | a, b, c := OpGetA(instr), OpGetB(instr), OpGetC(instr) 348 | if b >= OpConstOffset { 349 | vb = cf.fn.Bytecode.Consts[b-OpConstOffset] 350 | } else { 351 | vb = cf.r[b] 352 | } 353 | if c >= OpConstOffset { 354 | vc = cf.fn.Bytecode.Consts[c-OpConstOffset] 355 | } else { 356 | vc = cf.r[c] 357 | } 358 | 359 | if (vb.Type() != ValueNil && vc.Type() != ValueNil) && vb.Type() != vc.Type() { 360 | cf.r[a] = Bool(false) 361 | return 0 362 | } 363 | 364 | op := OpGetOpcode(instr) 365 | var res bool 366 | switch vb.Type() { 367 | case ValueNil: 368 | if op == OpEq || op == OpNe { 369 | res = vc.Type() == ValueNil 370 | if op == OpNe { 371 | res = !res 372 | } 373 | } else { 374 | // throw error 375 | return 1 376 | } 377 | case ValueBool: 378 | bb, _ := vb.assertBool() 379 | bc, _ := vc.assertBool() 380 | if eq, ne := op == OpEq, op == OpNe; eq || ne { 381 | res = bb == bc 382 | if ne { 383 | res = !res 384 | } 385 | } else { 386 | // throw error 387 | return 0 388 | } 389 | case ValueString: 390 | sb, sc := vb.String(), vc.String() 391 | switch op { 392 | case OpLt: 393 | res = sb < sc 394 | case OpLe: 395 | res = sb <= sc 396 | case OpEq: 397 | res = sb == sc 398 | case OpNe: 399 | res = sb != sc 400 | } 401 | case ValueNumber: 402 | numb, _ := vb.assertFloat64() 403 | numc, _ := vb.assertFloat64() 404 | switch op { 405 | case OpLt: 406 | res = numb < numc 407 | case OpLe: 408 | res = numb <= numc 409 | case OpEq: 410 | res = numb == numc 411 | case OpNe: 412 | res = numb != numc 413 | } 414 | } 415 | cf.r[a] = Bool(res) 416 | return 0 417 | } 418 | 419 | func callGoFunc(vm *VM, cf *callFrame, fn GoFunc, a, b, c uint) { 420 | ab := a + b 421 | ac := ab + c - 1 422 | call := FuncCall{ 423 | Args: make([]Value, c), 424 | ExpectResults: ab - a, 425 | NumArgs: c, 426 | } 427 | 428 | for i, r := 0, ab; r <= ac; i, r = i+1, r+1 { 429 | call.Args[i] = cf.r[r] 430 | } 431 | fn(&call) 432 | 433 | nr := call.NumResults 434 | if nr != call.ExpectResults { 435 | nr = call.ExpectResults 436 | } 437 | 438 | for i := uint(0); i < nr; i++ { 439 | if int(i) >= len(call.results) { 440 | cf.r[a + 1] = Nil{} 441 | } else { 442 | cf.r[a + i] = call.results[i] 443 | } 444 | } 445 | } 446 | 447 | func mainLoop(vm *VM) error { 448 | var currentLine uint32 449 | cf := vm.currentFrame 450 | proto := cf.fn.Bytecode 451 | 452 | for cf.pc < int(proto.NumCode) { 453 | if currentLine+1 < proto.NumLines && (cf.pc >= int(proto.Lines[currentLine].Instr)) { 454 | currentLine += 1 455 | } 456 | 457 | instr := proto.Code[cf.pc] 458 | cf.pc++ 459 | cf.line = int(proto.Lines[currentLine].Line) 460 | if opTable[int(instr&kOpcodeMask)](vm, cf, instr) == 1 { 461 | return vm.error 462 | } 463 | 464 | if vm.currentFrame != cf { 465 | currentLine = 0 466 | } 467 | cf = vm.currentFrame 468 | proto = cf.fn.Bytecode 469 | } 470 | 471 | return nil 472 | } 473 | --------------------------------------------------------------------------------