├── LICENSE ├── README.md ├── ast.go ├── ast ├── parser.go └── parser_test.go ├── bench_test.go ├── cmd └── twik │ └── main.go ├── eval_test.go ├── globals.go └── scope.go /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2011-2014 - Canonical Inc. 3 | 4 | This software is licensed under the LGPLv3, included below. 5 | 6 | As a special exception to the GNU Lesser General Public License version 3 7 | ("LGPL3"), the copyright holders of this Library give you permission to 8 | convey to a third party a Combined Work that links statically or dynamically 9 | to this Library without providing any Minimal Corresponding Source or 10 | Minimal Application Code as set out in 4d or providing the installation 11 | information set out in section 4e, provided that you comply with the other 12 | provisions of LGPL3 and provided that you meet, for the Application the 13 | terms and conditions of the license(s) which apply to the Application. 14 | 15 | Except as stated in this special exception, the provisions of LGPL3 will 16 | continue to comply in full to this Library. If you modify this Library, you 17 | may apply this exception to your version of this Library, but you are not 18 | obliged to do so. If you do not wish to do so, delete this exception 19 | statement from your version. This exception does not (and cannot) modify any 20 | license terms which apply to the Application, with which you must still 21 | comply. 22 | 23 | 24 | GNU LESSER GENERAL PUBLIC LICENSE 25 | Version 3, 29 June 2007 26 | 27 | Copyright (C) 2007 Free Software Foundation, Inc. 28 | Everyone is permitted to copy and distribute verbatim copies 29 | of this license document, but changing it is not allowed. 30 | 31 | 32 | This version of the GNU Lesser General Public License incorporates 33 | the terms and conditions of version 3 of the GNU General Public 34 | License, supplemented by the additional permissions listed below. 35 | 36 | 0. Additional Definitions. 37 | 38 | As used herein, "this License" refers to version 3 of the GNU Lesser 39 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 40 | General Public License. 41 | 42 | "The Library" refers to a covered work governed by this License, 43 | other than an Application or a Combined Work as defined below. 44 | 45 | An "Application" is any work that makes use of an interface provided 46 | by the Library, but which is not otherwise based on the Library. 47 | Defining a subclass of a class defined by the Library is deemed a mode 48 | of using an interface provided by the Library. 49 | 50 | A "Combined Work" is a work produced by combining or linking an 51 | Application with the Library. The particular version of the Library 52 | with which the Combined Work was made is also called the "Linked 53 | Version". 54 | 55 | The "Minimal Corresponding Source" for a Combined Work means the 56 | Corresponding Source for the Combined Work, excluding any source code 57 | for portions of the Combined Work that, considered in isolation, are 58 | based on the Application, and not on the Linked Version. 59 | 60 | The "Corresponding Application Code" for a Combined Work means the 61 | object code and/or source code for the Application, including any data 62 | and utility programs needed for reproducing the Combined Work from the 63 | Application, but excluding the System Libraries of the Combined Work. 64 | 65 | 1. Exception to Section 3 of the GNU GPL. 66 | 67 | You may convey a covered work under sections 3 and 4 of this License 68 | without being bound by section 3 of the GNU GPL. 69 | 70 | 2. Conveying Modified Versions. 71 | 72 | If you modify a copy of the Library, and, in your modifications, a 73 | facility refers to a function or data to be supplied by an Application 74 | that uses the facility (other than as an argument passed when the 75 | facility is invoked), then you may convey a copy of the modified 76 | version: 77 | 78 | a) under this License, provided that you make a good faith effort to 79 | ensure that, in the event an Application does not supply the 80 | function or data, the facility still operates, and performs 81 | whatever part of its purpose remains meaningful, or 82 | 83 | b) under the GNU GPL, with none of the additional permissions of 84 | this License applicable to that copy. 85 | 86 | 3. Object Code Incorporating Material from Library Header Files. 87 | 88 | The object code form of an Application may incorporate material from 89 | a header file that is part of the Library. You may convey such object 90 | code under terms of your choice, provided that, if the incorporated 91 | material is not limited to numerical parameters, data structure 92 | layouts and accessors, or small macros, inline functions and templates 93 | (ten or fewer lines in length), you do both of the following: 94 | 95 | a) Give prominent notice with each copy of the object code that the 96 | Library is used in it and that the Library and its use are 97 | covered by this License. 98 | 99 | b) Accompany the object code with a copy of the GNU GPL and this license 100 | document. 101 | 102 | 4. Combined Works. 103 | 104 | You may convey a Combined Work under terms of your choice that, 105 | taken together, effectively do not restrict modification of the 106 | portions of the Library contained in the Combined Work and reverse 107 | engineering for debugging such modifications, if you also do each of 108 | the following: 109 | 110 | a) Give prominent notice with each copy of the Combined Work that 111 | the Library is used in it and that the Library and its use are 112 | covered by this License. 113 | 114 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 115 | document. 116 | 117 | c) For a Combined Work that displays copyright notices during 118 | execution, include the copyright notice for the Library among 119 | these notices, as well as a reference directing the user to the 120 | copies of the GNU GPL and this license document. 121 | 122 | d) Do one of the following: 123 | 124 | 0) Convey the Minimal Corresponding Source under the terms of this 125 | License, and the Corresponding Application Code in a form 126 | suitable for, and under terms that permit, the user to 127 | recombine or relink the Application with a modified version of 128 | the Linked Version to produce a modified Combined Work, in the 129 | manner specified by section 6 of the GNU GPL for conveying 130 | Corresponding Source. 131 | 132 | 1) Use a suitable shared library mechanism for linking with the 133 | Library. A suitable mechanism is one that (a) uses at run time 134 | a copy of the Library already present on the user's computer 135 | system, and (b) will operate properly with a modified version 136 | of the Library that is interface-compatible with the Linked 137 | Version. 138 | 139 | e) Provide Installation Information, but only if you would otherwise 140 | be required to provide such information under section 6 of the 141 | GNU GPL, and only to the extent that such information is 142 | necessary to install and execute a modified version of the 143 | Combined Work produced by recombining or relinking the 144 | Application with a modified version of the Linked Version. (If 145 | you use option 4d0, the Installation Information must accompany 146 | the Minimal Corresponding Source and Corresponding Application 147 | Code. If you use option 4d1, you must provide the Installation 148 | Information in the manner specified by section 6 of the GNU GPL 149 | for conveying Corresponding Source.) 150 | 151 | 5. Combined Libraries. 152 | 153 | You may place library facilities that are a work based on the 154 | Library side by side in a single library together with other library 155 | facilities that are not Applications and are not covered by this 156 | License, and convey such a combined library under terms of your 157 | choice, if you do both of the following: 158 | 159 | a) Accompany the combined library with a copy of the same work based 160 | on the Library, uncombined with any other library facilities, 161 | conveyed under the terms of this License. 162 | 163 | b) Give prominent notice with the combined library that part of it 164 | is a work based on the Library, and explaining where to find the 165 | accompanying uncombined form of the same work. 166 | 167 | 6. Revised Versions of the GNU Lesser General Public License. 168 | 169 | The Free Software Foundation may publish revised and/or new versions 170 | of the GNU Lesser General Public License from time to time. Such new 171 | versions will be similar in spirit to the present version, but may 172 | differ in detail to address new problems or concerns. 173 | 174 | Each version is given a distinguishing version number. If the 175 | Library as you received it specifies that a certain numbered version 176 | of the GNU Lesser General Public License "or any later version" 177 | applies to it, you have the option of following the terms and 178 | conditions either of that published version or of any later version 179 | published by the Free Software Foundation. If the Library as you 180 | received it does not specify a version number of the GNU Lesser 181 | General Public License, you may choose any version of the GNU Lesser 182 | General Public License ever published by the Free Software Foundation. 183 | 184 | If the Library as you received it specifies that a proxy can decide 185 | whether future versions of the GNU Lesser General Public License shall 186 | apply, that proxy's public statement of acceptance of any version is 187 | permanent authorization for you to choose that version for the 188 | Library. 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Installation and usage 2 | ---------------------- 3 | 4 | See [gopkg.in/twik.v1](https://gopkg.in/twik.v1) for documentation and usage details. 5 | -------------------------------------------------------------------------------- /ast.go: -------------------------------------------------------------------------------- 1 | package twik 2 | 3 | import ( 4 | "gopkg.in/twik.v1/ast" 5 | ) 6 | 7 | // NewFileSet returns a new FileSet to hold positioning information 8 | // for a set of parsed twik sources. 9 | func NewFileSet() *ast.FileSet { 10 | return ast.NewFileSet() 11 | } 12 | 13 | // Parse parses a byte slice containing twik code and returns 14 | // the resulting parsed tree. 15 | // 16 | // Positioning information for the parsed code will be stored in 17 | // fset under the given name. 18 | func Parse(fset *ast.FileSet, name string, code []byte) (ast.Node, error) { 19 | return ast.ParseString(fset, name, string(code)) 20 | } 21 | 22 | // ParseString parses a string containing twik code and returns 23 | // the resulting parsed tree. 24 | // 25 | // Positioning information for the parsed code will be stored in 26 | // fset under the given name. 27 | func ParseString(fset *ast.FileSet, name string, code string) (ast.Node, error) { 28 | return ast.ParseString(fset, name, string(code)) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /ast/parser.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "strconv" 8 | "strings" 9 | "unicode" 10 | "unicode/utf8" 11 | ) 12 | 13 | // Pos is a position marker within a file set. Use the FileSet's PosInfo 14 | // method to obtain human-oriented details for the position. 15 | type Pos int 16 | 17 | // The Node interface is implemented by all AST nodes that result 18 | // from parsing twik code. 19 | type Node interface { 20 | Pos() Pos 21 | End() Pos 22 | } 23 | 24 | // Int represents an integer literal in parsed twik code. 25 | type Int struct { 26 | Input string 27 | InputPos Pos 28 | Value int64 29 | } 30 | 31 | func (l *Int) Pos() Pos { return l.InputPos } 32 | func (l *Int) End() Pos { return l.InputPos + Pos(len(l.Input)) } 33 | 34 | // Float represents a float literal in parsed twik code. 35 | type Float struct { 36 | Input string 37 | InputPos Pos 38 | Value float64 39 | } 40 | 41 | func (l *Float) Pos() Pos { return l.InputPos } 42 | func (l *Float) End() Pos { return l.InputPos + Pos(len(l.Input)) } 43 | 44 | // String represents a string literal in parsed twik code. 45 | type String struct { 46 | Input string 47 | InputPos Pos 48 | Value string 49 | } 50 | 51 | func (l *String) Pos() Pos { return l.InputPos } 52 | func (l *String) End() Pos { return l.InputPos + Pos(len(l.Input)) } 53 | 54 | // Symbol represents a symbol in parsed twik code. 55 | type Symbol struct { 56 | Name string 57 | NamePos Pos 58 | } 59 | 60 | func (s *Symbol) Pos() Pos { return s.NamePos } 61 | func (s *Symbol) End() Pos { return s.NamePos + Pos(len(s.Name)) } 62 | 63 | // List represents a list of entries from parsed twik code. 64 | type List struct { 65 | LParens Pos 66 | RParens Pos 67 | Nodes []Node 68 | } 69 | 70 | func (s *List) Pos() Pos { return s.LParens } 71 | func (s *List) End() Pos { return s.RParens + 1 } 72 | 73 | // Root represents the root of parsed twik code. 74 | type Root struct { 75 | First Pos 76 | After Pos 77 | Nodes []Node 78 | } 79 | 80 | func (s *Root) Pos() Pos { return s.First } 81 | func (s *Root) End() Pos { return s.After } 82 | 83 | // Parse parses a byte slice containing twik code and returns 84 | // the resulting parsed tree. 85 | // 86 | // Positioning information for the parsed code will be stored in 87 | // fset under the given name. 88 | func Parse(fset *FileSet, name string, code []byte) (Node, error) { 89 | return ParseString(fset, name, string(code)) 90 | } 91 | 92 | // ParseString parses a string containing twik code and returns 93 | // the resulting parsed tree. 94 | // 95 | // Positioning information for the parsed code will be stored in 96 | // fset under the given name. 97 | func ParseString(fset *FileSet, name string, code string) (Node, error) { 98 | base := fset.nextBase() 99 | fset.files = append(fset.files, file{name: name, code: code, base: base}) 100 | 101 | p := parser{fset: fset, code: code, base: base} 102 | root := Root{First: p.pos(0)} 103 | node, err := p.next() 104 | for err == nil { 105 | root.Nodes = append(root.Nodes, node) 106 | node, err = p.next() 107 | } 108 | if err != io.EOF { 109 | if err == errOpened || err == errClosed { 110 | return nil, p.ierrorf(p.i, "%v", err) 111 | } 112 | return nil, err 113 | } 114 | root.After = p.pos(p.i) 115 | return &root, nil 116 | } 117 | 118 | type parser struct { 119 | fset *FileSet 120 | code string 121 | base Pos 122 | i int 123 | } 124 | 125 | var errClosed = errors.New("unexpected )") 126 | var errOpened = errors.New("missing )") 127 | 128 | type closedError struct { 129 | } 130 | 131 | func (p *parser) pos(i int) Pos { 132 | return p.base + Pos(i) 133 | } 134 | 135 | func (p *parser) ierrorf(i int, format string, args ...interface{}) error { 136 | pinfo := p.fset.PosInfo(p.pos(i)) 137 | return fmt.Errorf("%s %s", pinfo, fmt.Sprintf(format, args...)) 138 | } 139 | 140 | func (p *parser) next() (Node, error) { 141 | if p.i == len(p.code) { 142 | return nil, io.EOF 143 | } 144 | 145 | r, size := utf8.DecodeRuneInString(p.code[p.i:]) 146 | for r == ';' || unicode.IsSpace(r) { 147 | p.i += size 148 | if r == ';' { 149 | for p.i < len(p.code) && r != '\n' { 150 | r, size = utf8.DecodeRuneInString(p.code[p.i:]) 151 | p.i += size 152 | } 153 | } 154 | if p.i == len(p.code) { 155 | return nil, io.EOF 156 | } 157 | r, size = utf8.DecodeRuneInString(p.code[p.i:]) 158 | } 159 | start := p.i 160 | p.i += size 161 | 162 | if r == ')' { 163 | return nil, errClosed 164 | } 165 | if r == '(' { 166 | var nodes []Node 167 | for { 168 | node, err := p.next() 169 | if err == errClosed { 170 | break 171 | } 172 | if err == io.EOF { 173 | return nil, errOpened 174 | } 175 | if err != nil { 176 | return nil, err 177 | } 178 | nodes = append(nodes, node) 179 | } 180 | list := &List{ 181 | LParens: p.pos(start), 182 | RParens: p.pos(p.i - 1), 183 | Nodes: nodes, 184 | } 185 | return list, nil 186 | } 187 | 188 | if r == '-' && p.i < len(p.code) { 189 | r, size = utf8.DecodeRuneInString(p.code[p.i:]) 190 | if r >= '0' && r <= '9' { 191 | // It's a digit; consume minus now and fall onto number case. 192 | p.i += size 193 | } 194 | } 195 | 196 | // int, float 197 | if r >= '0' && r <= '9' { 198 | dot := false 199 | for p.i < len(p.code) { 200 | r, size = utf8.DecodeRuneInString(p.code[p.i:]) 201 | if r == '.' { 202 | dot = true 203 | } else if r == ')' || unicode.IsSpace(r) { 204 | break 205 | } 206 | p.i += size 207 | } 208 | input := p.code[start:p.i] 209 | if dot { 210 | value, err := strconv.ParseFloat(input, 64) 211 | if err != nil { 212 | return nil, p.ierrorf(start, "invalid float literal: %s", input) 213 | } 214 | return &Float{Input: input, InputPos: p.pos(start), Value: value}, nil 215 | } else { 216 | value, err := strconv.ParseInt(input, 0, 64) 217 | if err != nil { 218 | return nil, p.ierrorf(start, "invalid int literal: %s", input) 219 | } 220 | return &Int{Input: input, InputPos: p.pos(start), Value: value}, nil 221 | } 222 | } 223 | 224 | if r == '\'' { 225 | var c rune 226 | if p.i < len(p.code) { 227 | c, size = utf8.DecodeRuneInString(p.code[p.i:]) 228 | p.i += size 229 | if c == '\\' && p.i < len(p.code) { 230 | c, size = utf8.DecodeRuneInString(p.code[p.i:]) 231 | p.i += size 232 | } else if c == '\'' { 233 | return nil, p.ierrorf(start, "invalid single quote") 234 | } 235 | } 236 | if p.i == len(p.code) { 237 | return nil, p.ierrorf(start, "invalid single quote") 238 | } 239 | r, size = utf8.DecodeRuneInString(p.code[p.i:]) 240 | p.i += size 241 | if r != '\'' { 242 | return nil, p.ierrorf(start, "unclosed single quote") 243 | } 244 | return &Int{Input: p.code[start:p.i], InputPos: p.pos(start), Value: int64(c)}, nil 245 | } 246 | 247 | // string 248 | if r == '"' { 249 | escaped := false 250 | for { 251 | if p.i == len(p.code) { 252 | return nil, p.ierrorf(start, "unclosed string literal: %s", p.code[start:]) 253 | } 254 | r, size = utf8.DecodeRuneInString(p.code[p.i:]) 255 | p.i += size 256 | if r == '"' && !escaped { 257 | break 258 | } 259 | escaped = r == '\\' 260 | } 261 | input := p.code[start:p.i] 262 | value, err := strconv.Unquote(input) 263 | if err != nil { 264 | return nil, p.ierrorf(start, "invalid string literal: %s", input) 265 | } 266 | return &String{Input: input, InputPos: p.pos(start), Value: value}, nil 267 | } 268 | 269 | // symbol 270 | for p.i < len(p.code) { 271 | r, size = utf8.DecodeRuneInString(p.code[p.i:]) 272 | if r == ')' || unicode.IsSpace(r) { 273 | break 274 | } 275 | p.i += size 276 | } 277 | symbol := &Symbol{ 278 | Name: p.code[start:p.i], 279 | NamePos: p.pos(start), 280 | } 281 | return symbol, nil 282 | } 283 | 284 | // NewFileSet returns a new FileSet. 285 | func NewFileSet() *FileSet { 286 | return &FileSet{} 287 | } 288 | 289 | // FileSet holds positioning information for parsed twik code. 290 | type FileSet struct { 291 | files []file 292 | } 293 | 294 | type file struct { 295 | name string 296 | code string 297 | base Pos 298 | } 299 | 300 | func (fset *FileSet) nextBase() Pos { 301 | if len(fset.files) == 0 { 302 | return 1 303 | } else { 304 | last := fset.files[len(fset.files)-1] 305 | return last.base + Pos(len(last.code)) + 1 306 | } 307 | } 308 | 309 | // PosInfo returns the line and column for pos, and the name the 310 | // file containing that position was parsed with. 311 | func (fset *FileSet) PosInfo(pos Pos) *PosInfo { 312 | pinfo := &PosInfo{} 313 | for _, f := range fset.files { 314 | if pos <= f.base+Pos(len(f.code)) { 315 | offset := int(pos - f.base) 316 | code := f.code[:offset] 317 | pinfo.Name = f.name 318 | pinfo.Line = 1 + strings.Count(code, "\n") 319 | if i := strings.LastIndex(code, "\n"); i >= 0 { 320 | pinfo.Column = offset - i 321 | } else { 322 | pinfo.Column = 1 + len(code) 323 | } 324 | } 325 | } 326 | return pinfo 327 | } 328 | 329 | // PosInfo holds human-oriented positioning details about a Pos. 330 | type PosInfo struct { 331 | Name string 332 | Line int 333 | Column int 334 | } 335 | 336 | func (info *PosInfo) String() string { 337 | name := "twik source" 338 | if info.Name != "" { 339 | name = info.Name 340 | } 341 | return fmt.Sprintf("%s:%d:%d:", name, info.Line, info.Column) 342 | } 343 | -------------------------------------------------------------------------------- /ast/parser_test.go: -------------------------------------------------------------------------------- 1 | package ast_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kr/pretty" 8 | . "gopkg.in/check.v1" 9 | "gopkg.in/twik.v1/ast" 10 | ) 11 | 12 | func Test(t *testing.T) { TestingT(t) } 13 | 14 | var _ = Suite(S{}) 15 | 16 | type S struct{} 17 | 18 | func (S) TestParser(c *C) { 19 | for _, test := range parserTests { 20 | fset := ast.NewFileSet() 21 | root, err := ast.ParseString(fset, "", test.code) 22 | if e, ok := test.value.(error); ok { 23 | c.Assert(err, ErrorMatches, e.Error()) 24 | c.Assert(root, IsNil) 25 | } else { 26 | c.Assert(err, IsNil) 27 | if !c.Check(root.(*ast.Root).Nodes, DeepEquals, test.value) { 28 | c.Logf("Obtained: %# v", pretty.Formatter(root.(*ast.Root).Nodes)) 29 | c.Logf("Expected: %# v", pretty.Formatter(test.value)) 30 | c.FailNow() 31 | } 32 | } 33 | } 34 | } 35 | 36 | func errorf(format string, args ...interface{}) error { 37 | return fmt.Errorf(format, args...) 38 | } 39 | 40 | var parserTests = []struct { 41 | code string 42 | value interface{} 43 | }{ 44 | { 45 | `1`, 46 | []ast.Node{ 47 | &ast.Int{Input: "1", InputPos: 1, Value: 1}, 48 | }, 49 | }, { 50 | `-1`, 51 | []ast.Node{ 52 | &ast.Int{Input: "-1", InputPos: 1, Value: -1}, 53 | }, 54 | }, { 55 | ` 1 `, 56 | []ast.Node{ 57 | &ast.Int{Input: "1", InputPos: 2, Value: 1}, 58 | }, 59 | }, { 60 | `0x10`, 61 | []ast.Node{ 62 | &ast.Int{Input: "0x10", InputPos: 1, Value: 16}, 63 | }, 64 | }, { 65 | `0n10`, 66 | errorf(".*: invalid int literal: 0n10"), 67 | }, { 68 | `'a'`, 69 | []ast.Node{ 70 | &ast.Int{Input: "'a'", InputPos: 1, Value: 'a'}, 71 | }, 72 | }, { 73 | `'\''`, 74 | []ast.Node{ 75 | &ast.Int{Input: `'\''`, InputPos: 1, Value: '\''}, 76 | }, 77 | }, { 78 | `'`, 79 | errorf(".*: invalid single quote"), 80 | }, { 81 | `''`, 82 | errorf(".*: invalid single quote"), 83 | }, { 84 | ` 1.0 `, 85 | []ast.Node{ 86 | &ast.Float{Input: "1.0", InputPos: 2, Value: 1}, 87 | }, 88 | }, { 89 | `()`, 90 | []ast.Node{ 91 | &ast.List{LParens: 1, RParens: 2}, 92 | }, 93 | }, { 94 | ` ( ) `, 95 | []ast.Node{ 96 | &ast.List{LParens: 2, RParens: 4}, 97 | }, 98 | }, { 99 | `"foo\"bar"`, 100 | []ast.Node{ 101 | &ast.String{Input: `"foo\"bar"`, InputPos: 1, Value: "foo\"bar"}, 102 | }, 103 | }, { 104 | ` "foo" `, 105 | []ast.Node{ 106 | &ast.String{Input: `"foo"`, InputPos: 2, Value: "foo"}, 107 | }, 108 | }, { 109 | ` "foo `, 110 | errorf(`.*: unclosed string literal: "foo `), 111 | }, { 112 | `"\m"`, 113 | errorf(`.*: invalid string literal: "\\m"`), 114 | }, { 115 | `(+ 1 (- 2 3) 4)`, 116 | []ast.Node{ 117 | &ast.List{ 118 | LParens: 1, 119 | Nodes: []ast.Node{ 120 | &ast.Symbol{Name: "+", NamePos: 2}, 121 | &ast.Int{Input: "1", InputPos: 4, Value: 1}, 122 | &ast.List{ 123 | LParens: 6, 124 | Nodes: []ast.Node{ 125 | &ast.Symbol{Name: "-", NamePos: 7}, 126 | &ast.Int{Input: "2", InputPos: 9, Value: 2}, 127 | &ast.Int{Input: "3", InputPos: 11, Value: 3}, 128 | }, 129 | RParens: 12, 130 | }, 131 | &ast.Int{Input: "4", InputPos: 14, Value: 4}, 132 | }, 133 | RParens: 15, 134 | }, 135 | }, 136 | }, 137 | 138 | { 139 | "(a\nb\nc", 140 | errorf(`twik source:3:2: missing \)`), 141 | }, { 142 | "(a\nb\n 1n \n)", 143 | errorf(`twik source:3:2: invalid int literal: 1n`), 144 | }, { 145 | "1n", 146 | errorf(`twik source:1:1: invalid int literal: 1n`), 147 | }, { 148 | "; Comment\n1", 149 | []ast.Node{ 150 | &ast.Int{Input: "1", InputPos: 11, Value: 1}, 151 | }, 152 | }, { 153 | "(; Comment\n1)", 154 | []ast.Node{ 155 | &ast.List{ 156 | LParens: 1, 157 | Nodes: []ast.Node{ 158 | &ast.Int{Input: "1", InputPos: 12, Value: 1}, 159 | }, 160 | RParens: 13, 161 | }, 162 | }, 163 | }, 164 | } 165 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package twik_test 2 | 3 | import ( 4 | . "gopkg.in/check.v1" 5 | "gopkg.in/twik.v1" 6 | ) 7 | 8 | func (S) BenchmarkParse0(c *C) { 9 | fset := twik.NewFileSet() 10 | var err error 11 | for i := 0; i < c.N; i++ { 12 | _, err = twik.ParseString(fset, "", "0") 13 | } 14 | c.StopTimer() 15 | c.Assert(err, IsNil) 16 | } 17 | 18 | func (S) BenchmarkEval0(c *C) { 19 | fset := twik.NewFileSet() 20 | node, err := twik.ParseString(fset, "", "0") 21 | c.Assert(err, IsNil) 22 | var value interface{} 23 | c.ResetTimer() 24 | for i := 0; i < c.N; i++ { 25 | value, err = twik.NewScope(fset).Eval(node) 26 | } 27 | c.StopTimer() 28 | c.Assert(err, IsNil) 29 | c.Assert(value, Equals, int64(0)) 30 | } 31 | 32 | 33 | func (S) BenchmarkParseFib(c *C) { 34 | fset := twik.NewFileSet() 35 | var err error 36 | c.ResetTimer() 37 | for i := 0; i < c.N; i++ { 38 | _, err = twik.ParseString(fset, "", "(func fib (n) (if (== n 0) 0 (if (== n 1) 1 (+ (fib (- n 1)) (fib (- n 2))))))") 39 | } 40 | c.StopTimer() 41 | c.Assert(err, IsNil) 42 | } 43 | 44 | func (S) BenchmarkEvalFib10(c *C) { 45 | fset := twik.NewFileSet() 46 | node, err := twik.ParseString(fset, "", "(func fib (n) (if (== n 0) 0 (if (== n 1) 1 (+ (fib (- n 1)) (fib (- n 2)))))) (fib 10)") 47 | c.Assert(err, IsNil) 48 | var value interface{} 49 | c.ResetTimer() 50 | for i := 0; i < c.N; i++ { 51 | value, err = twik.NewScope(fset).Eval(node) 52 | } 53 | c.StopTimer() 54 | c.Assert(err, IsNil) 55 | c.Assert(value, NotNil) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/twik/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "reflect" 9 | "strings" 10 | 11 | "code.google.com/p/go.crypto/ssh/terminal" 12 | 13 | "gopkg.in/twik.v1" 14 | ) 15 | 16 | func main() { 17 | err := run() 18 | if err != nil { 19 | fmt.Fprintf(os.Stderr, "error: %v\n", err) 20 | os.Exit(1) 21 | } 22 | } 23 | 24 | func printfFn(args []interface{}) (interface{}, error) { 25 | if len(args) > 0 { 26 | if format, ok := args[0].(string); ok { 27 | _, err := fmt.Printf(format, args[1:]...) 28 | return nil, err 29 | } 30 | } 31 | return nil, fmt.Errorf("printf takes a format string") 32 | } 33 | 34 | func listFn(args []interface{}) (interface{}, error) { 35 | return args, nil 36 | } 37 | 38 | func run() error { 39 | fset := twik.NewFileSet() 40 | scope := twik.NewScope(fset) 41 | scope.Create("printf", printfFn) 42 | scope.Create("list", listFn) 43 | 44 | if len(os.Args) > 1 { 45 | if strings.HasPrefix(os.Args[1], "-") { 46 | return fmt.Errorf("usage: twik []") 47 | } 48 | f, err := os.Open(os.Args[1]) 49 | if err != nil { 50 | return err 51 | } 52 | defer f.Close() 53 | data, err := ioutil.ReadAll(f) 54 | if err != nil { 55 | return err 56 | } 57 | node, err := twik.Parse(fset, os.Args[1], data) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | _, err = scope.Eval(node) 63 | return err 64 | } 65 | 66 | state, err := terminal.MakeRaw(1) 67 | if err != nil { 68 | return err 69 | } 70 | defer terminal.Restore(1, state) 71 | 72 | t := terminal.NewTerminal(os.Stdout, "> ") 73 | unclosed := "" 74 | for { 75 | line, err := t.ReadLine() 76 | if err == io.EOF { 77 | break 78 | } 79 | if err != nil { 80 | return err 81 | } 82 | if unclosed != "" { 83 | line = unclosed + "\n" + line 84 | } 85 | unclosed = "" 86 | t.SetPrompt("> ") 87 | node, err := twik.ParseString(fset, "", line) 88 | if err != nil { 89 | if strings.HasSuffix(err.Error(), "missing )") { 90 | unclosed = line 91 | t.SetPrompt(". ") 92 | continue 93 | } 94 | fmt.Println(err) 95 | continue 96 | } 97 | value, err := scope.Eval(node) 98 | if err != nil { 99 | fmt.Println(err) 100 | continue 101 | } 102 | if value != nil { 103 | if reflect.TypeOf(value).Kind() == reflect.Func { 104 | fmt.Println("#func") 105 | } else if v, ok := value.([]interface{}); ok { 106 | if len(v) == 0 { 107 | fmt.Println("()") 108 | } else { 109 | fmt.Print("(list") 110 | for _, e := range v { 111 | fmt.Printf(" %#v", e) 112 | } 113 | fmt.Println(")") 114 | } 115 | } else { 116 | fmt.Printf("%#v\n", value) 117 | } 118 | } 119 | } 120 | fmt.Println() 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /eval_test.go: -------------------------------------------------------------------------------- 1 | package twik_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | . "gopkg.in/check.v1" 8 | "gopkg.in/twik.v1" 9 | ) 10 | 11 | func Test(t *testing.T) { TestingT(t) } 12 | 13 | var _ = Suite(S{}) 14 | 15 | type S struct{} 16 | 17 | func (S) TestEval(c *C) { 18 | for _, test := range evalList { 19 | fset := twik.NewFileSet() 20 | node, err := twik.ParseString(fset, "", test.code) 21 | c.Assert(err, IsNil) 22 | scope := twik.NewScope(fset) 23 | scope.Create("sprintf", sprintfFn) 24 | scope.Create("list", listFn) 25 | scope.Create("append", appendFn) 26 | value, err := scope.Eval(node) 27 | if e, ok := test.value.(error); ok { 28 | c.Assert(err, ErrorMatches, e.Error(), Commentf("Code: %s", test.code)) 29 | c.Assert(value, IsNil) 30 | } else { 31 | tvalue := test.value 32 | if i, ok := tvalue.(int); ok { 33 | tvalue = int64(i) 34 | } 35 | c.Assert(err, IsNil, Commentf("Code: %s", test.code)) 36 | c.Assert(value, DeepEquals, tvalue, Commentf("Code: %s", test.code)) 37 | } 38 | } 39 | } 40 | 41 | func sprintfFn(args []interface{}) (interface{}, error) { 42 | if len(args) < 1 { 43 | return nil, fmt.Errorf("sprintf takes at least one format argument") 44 | } 45 | format, ok := args[0].(string) 46 | if !ok { 47 | return nil, fmt.Errorf("sprintf takes format string as first argument") 48 | } 49 | return fmt.Sprintf(format, args[1:]...), nil 50 | } 51 | 52 | func listFn(args []interface{}) (interface{}, error) { 53 | return args, nil 54 | } 55 | 56 | func appendFn(args []interface{}) (interface{}, error) { 57 | list, ok := args[0].([]interface{}) 58 | if !ok { 59 | return nil, fmt.Errorf("append takes list as first argument") 60 | } 61 | return append(list, args[1:]...), nil 62 | } 63 | 64 | func errorf(format string, args ...interface{}) error { 65 | return fmt.Errorf(format, args...) 66 | } 67 | 68 | var evalList = []struct { 69 | code string 70 | value interface{} 71 | }{ 72 | // some basics 73 | { 74 | `1`, 75 | 1, 76 | }, { 77 | `1.0`, 78 | 1.0, 79 | }, { 80 | `0x10`, 81 | 16, 82 | }, { 83 | `010`, 84 | 8, 85 | }, { 86 | `"foo\"bar"`, 87 | `foo"bar`, 88 | }, { 89 | `foo`, 90 | errorf("twik source:1:1: undefined symbol: foo"), 91 | }, { 92 | `(1)`, 93 | errorf(`twik source:1:2: cannot use 1 as a function`), 94 | }, { 95 | `true`, 96 | true, 97 | }, { 98 | `false`, 99 | false, 100 | }, { 101 | `nil`, 102 | nil, 103 | }, { 104 | `1 2 3`, 105 | 3, 106 | }, 107 | 108 | // error 109 | { 110 | "(\nerror \"error message\")", 111 | errorf("twik source:2:1: error message"), 112 | }, { 113 | `(error)`, 114 | errorf("twik source:1:2: error function takes a single string argument"), 115 | }, { 116 | `(error 1)`, 117 | errorf("twik source:1:2: error function takes a single string argument"), 118 | }, { 119 | `(error "foo" 2)`, 120 | errorf("twik source:1:2: error function takes a single string argument"), 121 | }, 122 | 123 | // + 124 | { 125 | `(+)`, 126 | 0, 127 | }, { 128 | `(+ 1)`, 129 | 1, 130 | }, { 131 | `(+ 1 2)`, 132 | 3, 133 | }, { 134 | `(+ 1 (+ 2 3))`, 135 | 6, 136 | }, { 137 | `(+ "123")`, 138 | errorf(`twik source:1:2: cannot sum "123"`), 139 | }, { 140 | `(+ 1.5)`, 141 | 1.5, 142 | }, { 143 | `(+ 1.5 1.5)`, 144 | 3.0, 145 | }, { 146 | `(+ 1.5 1)`, 147 | 2.5, 148 | }, { 149 | `(+ 1 1.5)`, 150 | 2.5, 151 | }, 152 | 153 | // - 154 | { 155 | `(-)`, 156 | errorf(`twik source:1:2: function "-" takes one or more arguments`), 157 | }, { 158 | `(- 1)`, 159 | -1, 160 | }, { 161 | `(- 10 1)`, 162 | 9, 163 | }, { 164 | `(- 10 1 2)`, 165 | 7, 166 | }, { 167 | `(- 10 (- 2 1))`, 168 | 9, 169 | }, { 170 | `(- "123")`, 171 | errorf(`twik source:1:2: cannot subtract "123"`), 172 | }, { 173 | `(- 1.5)`, 174 | -1.5, 175 | }, { 176 | `(- 2.0 1.5)`, 177 | 0.5, 178 | }, { 179 | `(- 1.5 1)`, 180 | 0.5, 181 | }, { 182 | `(- 1 1.5)`, 183 | -0.5, 184 | }, 185 | 186 | // * 187 | { 188 | `(*)`, 189 | 1, 190 | }, { 191 | `(* 1)`, 192 | 1, 193 | }, { 194 | `(* 2 3 4)`, 195 | 24, 196 | }, { 197 | `(* 2 (* 3 4))`, 198 | 24, 199 | }, { 200 | `(* "123")`, 201 | errorf(`twik source:1:2: cannot multiply "123"`), 202 | }, { 203 | `(* 1.5)`, 204 | 1.5, 205 | }, { 206 | `(* 2.0 1.5)`, 207 | 3.0, 208 | }, { 209 | `(* 1.5 1)`, 210 | 1.5, 211 | }, { 212 | `(* 1 1.5)`, 213 | 1.5, 214 | }, 215 | 216 | // / 217 | { 218 | `(/)`, 219 | errorf(`twik source:1:2: function "/" takes two or more arguments`), 220 | }, { 221 | `(/ 1)`, 222 | errorf(`twik source:1:2: function "/" takes two or more arguments`), 223 | }, { 224 | `(/ 10 2)`, 225 | 5, 226 | }, { 227 | `(/ 30 3 2)`, 228 | 5, 229 | }, { 230 | `(/ 30 (/ 10 2))`, 231 | 6, 232 | }, { 233 | `(/ 10 "123")`, 234 | errorf(`twik source:1:2: cannot divide with "123"`), 235 | }, { 236 | `(/ 10.0 2.0)`, 237 | 5.0, 238 | }, { 239 | `(/ 10.0 2)`, 240 | 5.0, 241 | }, { 242 | `(/ 10 2.0)`, 243 | 5.0, 244 | }, 245 | 246 | 247 | // == 248 | { 249 | `(== "a" "a")`, 250 | true, 251 | }, { 252 | `(== "a" "b")`, 253 | false, 254 | }, { 255 | `(== 42 42)`, 256 | true, 257 | }, { 258 | `(== 42 43)`, 259 | false, 260 | }, { 261 | `(== 42 "a")`, 262 | false, 263 | }, { 264 | `(== 42 42.0)`, 265 | false, 266 | }, { 267 | `(== 1 2 3)`, 268 | errorf("twik source:1:2: == takes two values"), 269 | }, { 270 | `(==)`, 271 | errorf("twik source:1:2: == takes two values"), 272 | }, 273 | 274 | // != 275 | { 276 | `(!= "a" "a")`, 277 | false, 278 | }, { 279 | `(!= "a" "b")`, 280 | true, 281 | }, { 282 | `(!= 42 42)`, 283 | false, 284 | }, { 285 | `(!= 42 43)`, 286 | true, 287 | }, { 288 | `(!= 42 "a")`, 289 | true, 290 | }, { 291 | `(!= 42 42.0)`, 292 | true, 293 | }, { 294 | `(!= 1 2 3)`, 295 | errorf("twik source:1:2: != takes two values"), 296 | }, { 297 | `(!=)`, 298 | errorf("twik source:1:2: != takes two values"), 299 | }, 300 | 301 | 302 | // or 303 | { 304 | `(or)`, 305 | false, 306 | }, { 307 | `(or false 1 2 (error "must not get here"))`, 308 | 1, 309 | }, { 310 | `(or (error "boom") 1 2 3)`, 311 | errorf("twik source:1:6: boom"), 312 | }, 313 | 314 | // and 315 | { 316 | `(and)`, 317 | true, 318 | }, { 319 | `(and 1 2 3)`, 320 | 3, 321 | }, { 322 | `(and false (error "must not get here"))`, 323 | false, 324 | }, { 325 | `(and (error "boom") true)`, 326 | errorf("twik source:1:7: boom"), 327 | }, 328 | 329 | // var 330 | { 331 | `(var x (+ 1 2)) x`, 332 | 3, 333 | }, { 334 | `(var x) x`, 335 | nil, 336 | }, { 337 | `(var x 1 2)`, 338 | errorf("twik source:1:2: var takes one or two arguments"), 339 | }, { 340 | `(var)`, 341 | errorf("twik source:1:2: var takes one or two arguments"), 342 | }, { 343 | "(var x)\n(var x)", 344 | errorf("twik source:2:2: symbol already defined in current scope: x"), 345 | }, 346 | 347 | // set 348 | { 349 | `(var x) (set x 2) (+ x 3)`, 350 | 5, 351 | }, { 352 | `(set x 1)`, 353 | errorf("twik source:1:2: cannot set undefined symbol: x"), 354 | }, { 355 | `(var x) (set x 1 2)`, 356 | errorf(`twik source:1:10: function "set" takes two arguments`), 357 | }, { 358 | `(var x) (set x)`, 359 | errorf(`twik source:1:10: function "set" takes two arguments`), 360 | }, { 361 | `(var x) (set)`, 362 | errorf(`twik source:1:10: function "set" takes two arguments`), 363 | }, 364 | 365 | // do 366 | { 367 | `(do)`, 368 | nil, 369 | }, { 370 | `(do 1 2 3)`, 371 | 3, 372 | }, { 373 | `(var x 1) (do (set x 2) x)`, 374 | 2, 375 | }, { 376 | `(var x 1) (do (set x 2)) x`, 377 | 2, 378 | }, { 379 | `(var x 1) (do (var x) (set x 2) x)`, 380 | 2, 381 | }, { 382 | `(var x 1) (do (var x) (set x 2)) x`, 383 | 1, 384 | }, 385 | 386 | // func 387 | { 388 | `((func (a b) (+ a b)) 1 2)`, 389 | 3, 390 | }, { 391 | `(var add (do (var x 0) (func (n) (set x (+ x n)) x))) (add 1) (add 2)`, 392 | 3, 393 | }, { 394 | `(func add (a b) (+ a b)) (add 1 2)`, 395 | 3, 396 | }, { 397 | `(func)`, 398 | errorf("twik source:1:2: func takes three or more arguments"), 399 | }, { 400 | `(func x)`, 401 | errorf("twik source:1:2: func takes three or more arguments"), 402 | }, { 403 | `(func 1 2)`, 404 | errorf("twik source:1:2: func takes a list of parameters"), 405 | }, { 406 | `(func f 2)`, 407 | errorf("twik source:1:2: func takes a list of parameters"), 408 | }, { 409 | `(func f (a)) (f 1 2)`, 410 | errorf(`twik source:1:2: func takes a body sequence`), 411 | }, { 412 | "(var f (func (a) 1))\n(f 1 2)", 413 | errorf(`twik source:2:2: anonymous function takes one argument`), 414 | }, { 415 | "(func f () 1)\n(f 1)", 416 | errorf(`twik source:2:2: function "f" takes no arguments`), 417 | }, { 418 | "(func f (a) 1)\n(f 1 2)", 419 | errorf(`twik source:2:2: function "f" takes one argument`), 420 | }, { 421 | "(func f (a b) 1)\n(f 1)", 422 | errorf(`twik source:2:2: function "f" takes 2 arguments`), 423 | }, 424 | 425 | // if 426 | { 427 | `(if true 1)`, 428 | 1, 429 | }, { 430 | `(if 0 1)`, 431 | 1, 432 | }, { 433 | `(if false 1)`, 434 | false, 435 | }, { 436 | `(if false 1 2)`, 437 | 2, 438 | }, { 439 | `(if)`, 440 | errorf(`twik source:1:2: function "if" takes two or three arguments`), 441 | }, { 442 | `(if 1)`, 443 | errorf(`twik source:1:2: function "if" takes two or three arguments`), 444 | }, 445 | 446 | // for 447 | { 448 | `(for 1 2 3)`, 449 | errorf("twik source:1:2: for takes four or more arguments"), 450 | }, { 451 | `(for (error "init") (error "test") (error "step") (error "code"))`, 452 | errorf("twik source:1:7: init"), 453 | }, { 454 | `(for () (error "test") (error "step") (error "code"))`, 455 | errorf("twik source:1:10: test"), 456 | }, { 457 | `(for () () (error "step") (error "code"))`, 458 | errorf("twik source:1:28: code"), 459 | }, { 460 | `(for () () (error "step") ())`, 461 | errorf("twik source:1:13: step"), 462 | }, { 463 | `(for (var i 0) false () ()) i`, 464 | errorf("twik source:1:29: undefined symbol: i"), 465 | }, { 466 | `(var x 0) (for (var i 0) (!= i 4) (set i (+ i 1)) (set x (+ x i)) (* 2 x))`, 467 | 12, 468 | }, 469 | 470 | // range 471 | { 472 | `(range 1 2)`, 473 | errorf("twik source:1:2: range takes three or more arguments"), 474 | }, { 475 | `(range 1 2 3)`, 476 | errorf(`twik source:1:2: range takes var name or \(i elem\) var name pair as first argument`), 477 | }, { 478 | `(range i 0 ()) i`, 479 | errorf("twik source:1:16: undefined symbol: i"), 480 | }, { 481 | `(var x 0) (range i 4 (set x (+ x i)) (* 2 x))`, 482 | 12, 483 | }, { 484 | `(var l ()) (range (i e) (list "A" "B" "C") (set l (append l i e))) l`, 485 | []interface {}{0, "A", 1, "B", 2, "C"}, 486 | }, 487 | 488 | 489 | // calling of custom functions 490 | { 491 | `(sprintf "Value: %.02f" 1.0)`, 492 | "Value: 1.00", 493 | }, 494 | } 495 | -------------------------------------------------------------------------------- /globals.go: -------------------------------------------------------------------------------- 1 | package twik 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "gopkg.in/twik.v1/ast" 8 | ) 9 | 10 | var defaultGlobals = []struct { 11 | name string 12 | value interface{} 13 | }{ 14 | {"true", true}, 15 | {"false", false}, 16 | {"nil", nil}, 17 | {"error", errorFn}, 18 | {"==", eqFn}, 19 | {"!=", neFn}, 20 | {"+", plusFn}, 21 | {"-", minusFn}, 22 | {"*", mulFn}, 23 | {"/", divFn}, 24 | {"or", orFn}, 25 | {"and", andFn}, 26 | {"if", ifFn}, 27 | {"var", varFn}, 28 | {"set", setFn}, 29 | {"do", doFn}, 30 | {"func", funcFn}, 31 | {"for", forFn}, 32 | {"range", rangeFn}, 33 | } 34 | 35 | func errorFn(args []interface{}) (value interface{}, err error) { 36 | if len(args) == 1 { 37 | if s, ok := args[0].(string); ok { 38 | return nil, errors.New(s) 39 | } 40 | } 41 | return nil, errors.New("error function takes a single string argument") 42 | } 43 | 44 | func eqFn(args []interface{}) (value interface{}, err error) { 45 | if len(args) != 2 { 46 | return nil, errors.New("== takes two values") 47 | } 48 | return args[0] == args[1], nil 49 | } 50 | 51 | func neFn(args []interface{}) (value interface{}, err error) { 52 | if len(args) != 2 { 53 | return nil, errors.New("!= takes two values") 54 | } 55 | return args[0] != args[1], nil 56 | } 57 | 58 | func plusFn(args []interface{}) (value interface{}, err error) { 59 | var resi int64 60 | var resf float64 61 | var f bool 62 | for _, arg := range args { 63 | switch arg := arg.(type) { 64 | case int64: 65 | resi += arg 66 | resf += float64(arg) 67 | case float64: 68 | resf += arg 69 | f = true 70 | default: 71 | return nil, fmt.Errorf("cannot sum %#v", arg) 72 | } 73 | } 74 | if f { 75 | return resf, nil 76 | } 77 | return resi, nil 78 | } 79 | 80 | func minusFn(args []interface{}) (value interface{}, err error) { 81 | if len(args) == 0 { 82 | return nil, fmt.Errorf(`function "-" takes one or more arguments`) 83 | } 84 | var resi int64 85 | var resf float64 86 | var f bool 87 | for i, arg := range args { 88 | switch arg := arg.(type) { 89 | case int64: 90 | if i == 0 && len(args) > 1 { 91 | resi = arg 92 | resf = float64(arg) 93 | } else { 94 | resi -= arg 95 | resf -= float64(arg) 96 | } 97 | case float64: 98 | if i == 0 && len(args) > 1 { 99 | resf = arg 100 | } else { 101 | resf -= arg 102 | } 103 | f = true 104 | default: 105 | return nil, fmt.Errorf("cannot subtract %#v", arg) 106 | } 107 | } 108 | if f { 109 | return resf, nil 110 | } 111 | return resi, nil 112 | } 113 | 114 | func mulFn(args []interface{}) (value interface{}, err error) { 115 | var resi = int64(1) 116 | var resf = float64(1) 117 | var f bool 118 | for _, arg := range args { 119 | switch arg := arg.(type) { 120 | case int64: 121 | resi *= arg 122 | resf *= float64(arg) 123 | case float64: 124 | resf *= arg 125 | f = true 126 | default: 127 | return nil, fmt.Errorf("cannot multiply %#v", arg) 128 | } 129 | } 130 | if f { 131 | return resf, nil 132 | } 133 | return resi, nil 134 | } 135 | 136 | func divFn(args []interface{}) (value interface{}, err error) { 137 | if len(args) < 2 { 138 | return nil, fmt.Errorf(`function "/" takes two or more arguments`) 139 | } 140 | var resi int64 141 | var resf float64 142 | var f bool 143 | for i, arg := range args { 144 | switch arg := arg.(type) { 145 | case int64: 146 | if i == 0 && len(args) > 1 { 147 | resi = arg 148 | resf = float64(arg) 149 | } else { 150 | resi /= arg 151 | resf /= float64(arg) 152 | } 153 | case float64: 154 | if i == 0 && len(args) > 1 { 155 | resf = float64(arg) 156 | } else { 157 | resf /= arg 158 | } 159 | f = true 160 | default: 161 | return nil, fmt.Errorf("cannot divide with %#v", arg) 162 | } 163 | } 164 | if f { 165 | return resf, nil 166 | } 167 | return resi, nil 168 | } 169 | 170 | func andFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 171 | if len(args) == 0 { 172 | return true, nil 173 | } 174 | for _, arg := range args { 175 | value, err = scope.Eval(arg) 176 | if err != nil { 177 | return nil, err 178 | } 179 | if value == false { 180 | return false, nil 181 | } 182 | } 183 | return value, err 184 | } 185 | 186 | func orFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 187 | if len(args) == 0 { 188 | return false, nil 189 | } 190 | for _, arg := range args { 191 | value, err = scope.Eval(arg) 192 | if err != nil { 193 | return nil, err 194 | } 195 | if value != false { 196 | return value, nil 197 | } 198 | } 199 | return value, err 200 | } 201 | 202 | func ifFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 203 | if len(args) < 2 || len(args) > 3 { 204 | return nil, errors.New(`function "if" takes two or three arguments`) 205 | } 206 | value, err = scope.Eval(args[0]) 207 | if err != nil { 208 | return nil, err 209 | } 210 | if value == false { 211 | if len(args) == 3 { 212 | return scope.Eval(args[2]) 213 | } 214 | return false, nil 215 | } 216 | return scope.Eval(args[1]) 217 | } 218 | 219 | func varFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 220 | if len(args) == 0 || len(args) > 2 { 221 | return nil, errors.New("var takes one or two arguments") 222 | } 223 | symbol, ok := args[0].(*ast.Symbol) 224 | if !ok { 225 | return nil, errors.New("var takes a symbol as first argument") 226 | } 227 | if len(args) == 1 { 228 | value = nil 229 | } else { 230 | value, err = scope.Eval(args[1]) 231 | if err != nil { 232 | return nil, err 233 | } 234 | } 235 | return nil, scope.Create(symbol.Name, value) 236 | } 237 | 238 | func setFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 239 | if len(args) != 2 { 240 | return nil, errors.New(`function "set" takes two arguments`) 241 | } 242 | symbol, ok := args[0].(*ast.Symbol) 243 | if !ok { 244 | return nil, errors.New(`function "set" takes a symbol as first argument`) 245 | } 246 | value, err = scope.Eval(args[1]) 247 | if err != nil { 248 | return nil, err 249 | } 250 | return nil, scope.Set(symbol.Name, value) 251 | } 252 | 253 | func doFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 254 | scope = scope.Branch() 255 | for _, arg := range args { 256 | value, err = scope.Eval(arg) 257 | if err != nil { 258 | return nil, err 259 | } 260 | } 261 | return value, nil 262 | } 263 | 264 | func funcFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 265 | if len(args) < 2 { 266 | return nil, errors.New(`func takes three or more arguments`) 267 | } 268 | i := 0 269 | var name string 270 | if symbol, ok := args[0].(*ast.Symbol); ok { 271 | name = symbol.Name 272 | i++ 273 | } 274 | list, ok := args[i].(*ast.List) 275 | if !ok { 276 | return nil, errors.New(`func takes a list of parameters`) 277 | } 278 | params := list.Nodes 279 | for _, param := range params { 280 | if _, ok := param.(*ast.Symbol); !ok { 281 | return nil, errors.New("func's list of parameters must be a list of symbols") 282 | } 283 | } 284 | body := args[i+1:] 285 | if len(body) == 0 { 286 | return nil, fmt.Errorf("func takes a body sequence") 287 | } 288 | fn := func(args []interface{}) (value interface{}, err error) { 289 | if len(args) != len(params) { 290 | nameInfo := "anonymous function" 291 | if name != "" { 292 | nameInfo = fmt.Sprintf("function %q", name) 293 | } 294 | switch len(params) { 295 | case 0: 296 | return nil, fmt.Errorf("%s takes no arguments", nameInfo) 297 | case 1: 298 | return nil, fmt.Errorf("%s takes one argument", nameInfo) 299 | default: 300 | return nil, fmt.Errorf("%s takes %d arguments", nameInfo, len(params)) 301 | } 302 | } 303 | scope = scope.Branch() 304 | for i, arg := range args { 305 | err := scope.Create(params[i].(*ast.Symbol).Name, arg) 306 | if err != nil { 307 | panic("must not happen: " + err.Error()) 308 | } 309 | } 310 | for _, node := range body { 311 | value, err = scope.Eval(node) 312 | if err != nil { 313 | return nil, err 314 | } 315 | } 316 | return value, nil 317 | } 318 | if name != "" { 319 | if err = scope.Create(name, fn); err != nil { 320 | return nil, err 321 | } 322 | } 323 | return fn, nil 324 | } 325 | 326 | func forFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 327 | if len(args) < 4 { 328 | return nil, errors.New(`for takes four or more arguments`) 329 | } 330 | init, test, step, code := args[0], args[1], args[2], args[3:] 331 | scope = scope.Branch() 332 | _, err = scope.Eval(init) 333 | if err != nil { 334 | return nil, err 335 | } 336 | for { 337 | more, err := scope.Eval(test) 338 | if err != nil { 339 | return nil, err 340 | } 341 | if more == false { 342 | return value, nil 343 | } 344 | 345 | for _, c := range code { 346 | value, err = scope.Eval(c) 347 | if err != nil { 348 | return nil, err 349 | } 350 | } 351 | 352 | _, err = scope.Eval(step) 353 | if err != nil { 354 | return nil, err 355 | } 356 | } 357 | panic("unreachable") 358 | } 359 | 360 | func rangeFn(scope *Scope, args []ast.Node) (value interface{}, err error) { 361 | if len(args) < 3 { 362 | return nil, errors.New(`range takes three or more arguments`) 363 | } 364 | var iname, ename string 365 | if symbol, ok := args[0].(*ast.Symbol); ok { 366 | iname = symbol.Name 367 | } else if list, ok := args[0].(*ast.List); ok && len(list.Nodes) == 2 { 368 | symbol1, ok1 := list.Nodes[0].(*ast.Symbol) 369 | symbol2, ok2 := list.Nodes[1].(*ast.Symbol) 370 | if ok1 && ok2 { 371 | iname = symbol1.Name 372 | ename = symbol2.Name 373 | } 374 | } 375 | if iname == "" { 376 | return nil, errors.New(`range takes var name or (i elem) var name pair as first argument`) 377 | } 378 | scope = scope.Branch() 379 | value, err = scope.Eval(args[1]) 380 | if err != nil { 381 | return nil, err 382 | } 383 | code := args[2:] 384 | if n, ok := value.(int64); ok { 385 | scope.Create(iname, 0) 386 | for i := int64(0); i < n; i++ { 387 | scope.Set(iname, i) 388 | for _, c := range code { 389 | value, err = scope.Eval(c) 390 | if err != nil { 391 | return nil, err 392 | } 393 | } 394 | } 395 | return value, nil 396 | } 397 | if list, ok := value.([]interface{}); ok { 398 | scope.Create(iname, 0) 399 | scope.Create(ename, nil) 400 | for i, e := range list { 401 | scope.Set(iname, i) 402 | scope.Set(ename, e) 403 | for _, c := range code { 404 | value, err = scope.Eval(c) 405 | if err != nil { 406 | return nil, err 407 | } 408 | } 409 | } 410 | return value, nil 411 | } 412 | return nil, errors.New(`range takes an integer or a list as second argument`) 413 | } 414 | -------------------------------------------------------------------------------- /scope.go: -------------------------------------------------------------------------------- 1 | // Package twik implements a tiny embeddable language for Go. 2 | // 3 | // For details, see the blog post: 4 | // 5 | // http://blog.labix.org/2013/07/16/twik-a-tiny-language-for-go 6 | // 7 | package twik 8 | 9 | import ( 10 | "fmt" 11 | 12 | "gopkg.in/twik.v1/ast" 13 | ) 14 | 15 | // Scope is an environment where twik logic may be evaluated in. 16 | type Scope struct { 17 | parent *Scope 18 | fset *ast.FileSet 19 | vars map[string]interface{} 20 | } 21 | 22 | // Error holds an error and the source position where the error was found. 23 | type Error struct { 24 | Err error 25 | PosInfo *ast.PosInfo 26 | } 27 | 28 | func (e *Error) Error() string { 29 | return fmt.Sprintf("%s %v", e.PosInfo, e.Err) 30 | } 31 | 32 | // NewScope returns a new scope for evaluating logic that was parsed into fset. 33 | func NewScope(fset *ast.FileSet) *Scope { 34 | vars := make(map[string]interface{}) 35 | for _, global := range defaultGlobals { 36 | vars[global.name] = global.value 37 | } 38 | return &Scope{fset: fset, vars: vars} 39 | } 40 | 41 | // Create defines a new symbol with the given value in the s scope. 42 | // It is an error to redefine an existent symbol. 43 | func (s *Scope) Create(symbol string, value interface{}) error { 44 | if _, ok := s.vars[symbol]; ok { 45 | return fmt.Errorf("symbol already defined in current scope: %s", symbol) 46 | } 47 | if s.vars == nil { 48 | s.vars = make(map[string]interface{}) 49 | } 50 | s.vars[symbol] = value 51 | return nil 52 | } 53 | 54 | // Set sets symbol to the given value in the shallowest scope it is defined in. 55 | // It is an error to set an undefined symbol. 56 | func (s *Scope) Set(symbol string, value interface{}) error { 57 | for s != nil { 58 | if _, ok := s.vars[symbol]; ok { 59 | s.vars[symbol] = value 60 | return nil 61 | } 62 | s = s.parent 63 | } 64 | return fmt.Errorf("cannot set undefined symbol: %s", symbol) 65 | } 66 | 67 | // Get returns the value of symbol in the shallowest scope it is defined in. 68 | // It is an error to get the value of an undefined symbol. 69 | func (s *Scope) Get(symbol string) (value interface{}, err error) { 70 | for s != nil { 71 | if value, ok := s.vars[symbol]; ok { 72 | return value, nil 73 | } 74 | s = s.parent 75 | } 76 | return nil, fmt.Errorf("undefined symbol: %s", symbol) 77 | } 78 | 79 | // Branch returns a new scope that has s as a parent. 80 | func (s *Scope) Branch() *Scope { 81 | return &Scope{parent: s, fset: s.fset} 82 | } 83 | 84 | var emptyList = make([]interface{}, 0) 85 | 86 | func (s *Scope) errorAt(node ast.Node, err error) error { 87 | if _, ok := err.(*Error); ok { 88 | return err 89 | } 90 | return &Error{err, s.fset.PosInfo(node.Pos())} 91 | } 92 | 93 | // Eval evaluates node in the s scope and returns the resulting value. 94 | func (s *Scope) Eval(node ast.Node) (value interface{}, err error) { 95 | switch node := node.(type) { 96 | case *ast.Symbol: 97 | value, err := s.Get(node.Name) 98 | if err != nil { 99 | return nil, s.errorAt(node, err) 100 | } 101 | return value, nil 102 | case *ast.Int: 103 | return node.Value, nil 104 | case *ast.Float: 105 | return node.Value, nil 106 | case *ast.String: 107 | return node.Value, nil 108 | case *ast.List: 109 | if len(node.Nodes) == 0 { 110 | return emptyList, nil 111 | } 112 | fn, err := s.Eval(node.Nodes[0]) 113 | if err != nil { 114 | return nil, s.errorAt(node.Nodes[0], err) 115 | } 116 | value, err := s.call(fn, node.Nodes[1:]) 117 | if err != nil { 118 | return nil, s.errorAt(node.Nodes[0], err) 119 | } 120 | return value, nil 121 | case *ast.Root: 122 | for _, node := range node.Nodes { 123 | value, err = s.Eval(node) 124 | if err != nil { 125 | return nil, s.errorAt(node, err) 126 | } 127 | } 128 | return value, nil 129 | } 130 | return nil, fmt.Errorf("support for %#v not yet implemeted", node) 131 | } 132 | 133 | func (s *Scope) call(fn interface{}, args []ast.Node) (value interface{}, err error) { 134 | if fn, ok := fn.(func(*Scope, []ast.Node) (interface{}, error)); ok { 135 | return fn(s, args) 136 | } 137 | if fn, ok := fn.(func([]interface{}) (interface{}, error)); ok { 138 | vargs := make([]interface{}, len(args)) 139 | for i, arg := range args { 140 | value, err := s.Eval(arg) 141 | if err != nil { 142 | return nil, err 143 | } 144 | vargs[i] = value 145 | } 146 | return fn(vargs) 147 | } 148 | return nil, fmt.Errorf("cannot use %#v as a function", fn) 149 | } 150 | --------------------------------------------------------------------------------