├── 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 |
--------------------------------------------------------------------------------