├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── abstract.txt ├── benchmarks ├── array-mult.scm └── array-mult.zy ├── cmd └── zygo │ └── main.go ├── emacs └── zygo.el ├── go.mod ├── go.sum ├── imaginarium ├── array.zy ├── matrix.zy └── talk-brainstorm.txt ├── media └── high_altitude_gopher.png ├── slides ├── all.output ├── all.snoopy.zy ├── arrays.run ├── calltree.txt ├── coro │ ├── after1.go │ ├── after1full.go │ ├── before1.go │ ├── coro.png │ ├── coroutines_goroutines.2016sept20.dfw_golang_meetup.pdf │ ├── first.go │ ├── getmore1.go │ ├── getmore2.go │ ├── havestuff.go │ ├── infparse.go │ ├── infparse_nocomment.go │ ├── large.png │ ├── large2.jpg │ ├── lexer.go │ ├── main.go │ ├── med2.jpg │ ├── orig.parser.go │ ├── orig.parser1.go │ ├── parser.go │ ├── parser1.go │ ├── parser_api.go │ ├── pausable.slide │ └── repl.go ├── curse_you_red_baron.png ├── declare.struct ├── demo ├── exp.txt ├── final.jason.zygo.talk.2016march16.pdf ├── first.go ├── flyer.interface ├── harry.record ├── harry.zy ├── hash.demo ├── hof.foldr ├── infix.session ├── infix.txt ├── main.go ├── make.it.rain ├── make.planes ├── michoacan_pocket_gopher.jpeg ├── msgp.txt ├── other.planes ├── parseTree.jpg ├── plane.interact ├── plane.schema ├── pocket_gopher.jpeg ├── pocket_gopher2.jpeg ├── pocket_gopher3.jpeg ├── pocket_gopher4.jpeg ├── printable.short.slide ├── sample.code ├── sample.run ├── sample.run1 ├── sample.run2 ├── sample.run3 ├── short.slide ├── slice.demo ├── slice2.demo ├── snoopy.interact ├── snoopy.methods ├── snoopy.struct ├── snoopy1 ├── snoopy2 ├── snoopy_ace.png ├── unlang.notes ├── unused.txt ├── use.first ├── weather └── zygo.slide ├── tests ├── anonhash.zy ├── append.zy ├── arrays.zy ├── assign.zy ├── break.zy ├── char.zy ├── chomp.zy ├── closure.zy ├── closure2.zy ├── closure3.zy ├── colonop.zy ├── comma.zy ├── comments.zy ├── comparisons.zy ├── continue.zy ├── controlflow.zy ├── coroutines.zy ├── ctor.zy ├── decl_field.zy ├── decl_fun.zy ├── decl_pointer.zy ├── decl_slice.zy ├── declare.zy ├── defined.zy ├── dotcall.zy ├── dotsym.zy ├── dynprob.zy ├── dynscope.zy ├── eval.zy ├── event.zy ├── expect-error.zy ├── foo.pkg ├── for.zy ├── functions.zy ├── funfun.zy ├── gob.zy ├── hash.zy ├── hof.zy ├── if.zy ├── if2.zy ├── import.zy ├── inc.g ├── inc1.g ├── inc2.g ├── inc3.g ├── include.zy ├── incr.g ├── indepf.zy ├── infix.zy ├── infixAssign.zy ├── infixMixHashArray.zy ├── joinsym.zy ├── json2.zy ├── json_msgpack.zy ├── label.zy ├── late.zy ├── len.zy ├── lines ├── lists.zy ├── macexp.zy ├── macros.zy ├── manual-leak-check.zy ├── methodcall.zy ├── methodls.zy ├── msgpack-map.zy ├── multiple_assignment.zy ├── mux-function.zy ├── nan.zy ├── newscope ├── nsplit.zy ├── numberkeys.zy ├── numbers.zy ├── owrite.zy ├── package.zy ├── pkghelp ├── pointer.zy ├── prepackage ├── printf.zy ├── quotedsym.zy ├── range.zy ├── raw.zy ├── recur.zy ├── regexp.zy ├── rmsym.zy ├── sample.json ├── sci.zy ├── scoping.zy ├── set.zy ├── setenv.zy ├── sigils.zy ├── slurp.zy ├── sourcer ├── split.zy ├── str.zy ├── strings.zy ├── struct.zy ├── symbols.zy ├── syntax-quote.zy ├── system.zy ├── tailcallresid.zy ├── tailrecur.zy ├── testall.sh ├── timeit.zy ├── type.zy ├── typelist.zy ├── uint64.zy └── users.zy └── zygo ├── address.go ├── arrayutils.go ├── basetypes.go ├── blake2.go ├── bsave.go ├── builders.go ├── callgo.go ├── callgo2_test.go ├── callgo_test.go ├── cfg.go ├── channels.go ├── check.go ├── closing.go ├── comment.go ├── comparisons.go ├── coroutines.go ├── datastack.go ├── demo_go_structs.go ├── demo_go_structs_gen.go ├── demo_go_structs_gen_test.go ├── doc.go ├── environment.go ├── environment_test.go ├── exists.go ├── expressions.go ├── func.go ├── functions.go ├── generator.go ├── gitcommit.go ├── gob.go ├── gotypereg.go ├── hashutils.go ├── import.go ├── import_test.go ├── jsonmsgp.go ├── jsonmsgp_test.go ├── lexer.go ├── lexer_test.go ├── liner.go ├── listutils.go ├── makego.go ├── msgpackmap.go ├── numerictower.go ├── panicon.go ├── parser.go ├── pratt.go ├── printstate.go ├── printstate_test.go ├── ptrcheck.go ├── random.go ├── rawutils.go ├── regexp.go ├── repl.go ├── reuse_test.go ├── scopes.go ├── slurp.go ├── source.go ├── stack.go ├── stack_test.go ├── strutils.go ├── system.go ├── time.go ├── typeutils.go ├── version.go ├── vm.go └── vprint.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.prof 2 | *.mprof 3 | *~ 4 | tests/lines2 5 | 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, The zygomys authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build test all 2 | 3 | all: build test 4 | 5 | build: 6 | /bin/echo "package zygo" > zygo/gitcommit.go 7 | /bin/echo "func init() { GITLASTTAG = \"$(shell git describe --abbrev=0 --tags)\"; GITLASTCOMMIT = \"$(shell git rev-parse HEAD)\" }" >> zygo/gitcommit.go 8 | cd cmd/zygo; go install . 9 | 10 | test: 11 | tests/testall.sh && echo "running 'go test'" && cd zygo && go test -v 12 | -------------------------------------------------------------------------------- /abstract.txt: -------------------------------------------------------------------------------- 1 | 2 | Title: Scripting your Go code, an introduction to zygomys, an object-oriented modern Lisp 3 | Speaker: Jason E. Aten, Ph.D. 4 | Affiliation: Betable.com, San Francisco, CA. 5 | 6 | The free and open-source programming language Go, helmed by a team from 7 | Google, offers fast compilation, static-typing, and a multicore-oriented 8 | programming environment that produces fast binaries from easy-to-maintain 9 | code. 10 | 11 | What Go has lacked is an integrated scripting language -- one that is 12 | equally multicore friendly and portable -- to provide extendable 13 | configuration and dynamic scripting. 14 | 15 | Complex programs and changing business and customer requirements demand 16 | complex and frequently changing configuration. This leads to non-standard 17 | and hard-to-extend control languages, little wheels that get invented again 18 | and again. 19 | 20 | Designed for scripting Go programs, zygomys is a modern incarnation of 21 | Lisp for the Go platform. It is open source at 22 | https://github.com/glycerine/zygomys under a permissive 2-clause BSD 23 | license. Written in 100% Go (no CGO required!), zygomys makes it 24 | easy to dynamically control compiled code. zygomys automatically maps 25 | Lisp records to nested Go structures, including structs and slices that 26 | contain Go interfaces. Should scripts become large, zygomys makes it 27 | easy to translate a piece of script code into compiled Go code to 28 | optimize execution. 29 | 30 | In this talk I will start with an introduction to Lisp for beginners. 31 | Audience members will quickly understand the strange looking but 32 | actually extremely simple S-expression syntax at the heart of Lisp. 33 | Then for intermediate audience members, I will give an overview of 34 | zygomys and highlight the design points that give it a modern 35 | Object-oriented feel while retaining the power of 36 | 'programs-that-write-programs' that Lisp is famous for. 37 | 38 | Continuing for the intermediate level audience and touching on 39 | advanced points, I will show how to extend the capabilities of the 40 | base interpreter to call your own compiled Go methods, and how to 41 | use zygomys to produce a domain-specific-language (DSL) to drive 42 | your application with a principled and reusable scripting language. 43 | 44 | To quote Paul Graham of Y-combinator/Hackernews: 45 | 46 | "Sometimes, in desperation, competitors would try to 47 | introduce features that we didn't have. But with Lisp 48 | our development cycle was so fast that we could 49 | sometimes duplicate a new feature within a day or 50 | two of a competitor announcing it in a press 51 | release. By the time journalists covering the 52 | press release got round to calling us, we would 53 | have the new feature too." 54 | -- http://www.paulgraham.com/avg.html 55 | 56 | -------------------------------------------------------------------------------- /benchmarks/array-mult.scm: -------------------------------------------------------------------------------- 1 | (define (mult-vector-loop a b res i) 2 | (if (= i (vector-length a)) res 3 | (begin 4 | (vector-set! res i (* (vector-ref a i) (vector-ref b i))) 5 | (mult-vector-loop a b res (+ i 1))))) 6 | 7 | (define (mult-vector a b) 8 | (mult-vector-loop a b (make-vector (vector-length a)) 0)) 9 | 10 | (define (random-vector vec i) 11 | (if (= i (vector-length vec)) 12 | vec 13 | (begin 14 | (vector-set! vec i (random 1.0)) 15 | (random-vector vec (+ i 1))))) 16 | 17 | (define (do-in-loop func times) 18 | (if (= times 0) '() 19 | (begin 20 | (func) 21 | (do-in-loop func (- times 1))))) 22 | 23 | (let ((a (random-vector (make-vector 100) 0)) 24 | (b (random-vector (make-vector 100) 0))) 25 | (do-in-loop (lambda () (mult-vector a b)) 200)) 26 | 27 | -------------------------------------------------------------------------------- /benchmarks/array-mult.zy: -------------------------------------------------------------------------------- 1 | // this is a straight port of the Scheme code in array-mult.scm 2 | (defn multArrayLoop [a b res i] 3 | (cond (== i (len a)) res 4 | (begin 5 | (aset res i (* (aget a i) (aget b i))) 6 | (multArrayLoop a b res (+ i 1))))) 7 | 8 | (defn multArray [a b] 9 | (multArrayLoop a b (makeArray (len a)) 0)) 10 | 11 | (defn randomArray [arr i] 12 | (cond (== i (len arr)) 13 | arr 14 | (begin 15 | (aset arr i (random)) 16 | (randomArray arr (+ i 1))))) 17 | 18 | (defn doInLoop [fx times] 19 | (cond (== times 0) nil 20 | (begin 21 | (fx) 22 | (doInLoop fx (- times 1))))) 23 | 24 | // 25 | (let [ 26 | a (randomArray (makeArray 100) 0) 27 | b (randomArray (makeArray 100) 0) 28 | ] 29 | (doInLoop (fn [] (multArray a b)) 200)) 30 | 31 | /* on my laptop, about 34x slower than Chez scheme. 32 | 33 | jaten@jatens-MacBook-Pro ~/go/src/github.com/glycerine/zygomys/benchmarks (master) $ time zygo ./array-mult.zy 34 | 35 | real 0m4.322s 36 | user 0m5.737s 37 | sys 0m0.191s 38 | jaten@jatens-MacBook-Pro ~/go/src/github.com/glycerine/zygomys/benchmarks (master) $ time scheme --script ./array-mult.scm 39 | 40 | real 0m0.169s 41 | user 0m0.136s 42 | sys 0m0.021s 43 | $ gi -q 44 | gi> = 5.737 / 0.169 45 | 33.94674556213 46 | */ 47 | -------------------------------------------------------------------------------- /cmd/zygo/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | The zygomys command line REPL is known as `zygo`. 3 | */ 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "github.com/glycerine/zygomys/v9/zygo" 10 | "os" 11 | ) 12 | 13 | func usage(myflags *flag.FlagSet) { 14 | fmt.Printf("zygo command line help:\n") 15 | myflags.PrintDefaults() 16 | os.Exit(1) 17 | } 18 | 19 | func main() { 20 | cfg := zygo.NewZlispConfig("zygo") 21 | cfg.DefineFlags() 22 | err := cfg.Flags.Parse(os.Args[1:]) 23 | if err == flag.ErrHelp { 24 | usage(cfg.Flags) 25 | } 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | err = cfg.ValidateConfig() 31 | if err != nil { 32 | fmt.Fprintf(os.Stderr, "zygo command line error: '%v'\n", err) 33 | usage(cfg.Flags) 34 | } 35 | 36 | // the library does all the heavy lifting. 37 | zygo.ReplMain(cfg) 38 | } 39 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/glycerine/zygomys/v9 2 | 3 | go 1.23.5 4 | 5 | require ( 6 | github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be 7 | github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 8 | github.com/glycerine/greenpack v0.529.0 9 | github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0 10 | github.com/shurcooL/go-goon v1.0.0 11 | github.com/tinylib/msgp v1.1.2 12 | github.com/ugorji/go/codec v1.1.7 13 | ) 14 | 15 | require ( 16 | github.com/glycerine/fwd v1.1.4-beta.jea // indirect 17 | github.com/gopherjs/gopherjs v1.17.2 // indirect 18 | github.com/jtolds/gls v4.20.0+incompatible // indirect 19 | github.com/philhofer/fwd v1.0.0 // indirect 20 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be h1:XBJdPGgA3qqhW+p9CANCAVdF7ZIXdu3pZAkypMkKAjE= 2 | github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be/go.mod h1:OSCrScrFAjcBObrulk6BEQlytA462OkG1UGB5NYj9kE= 3 | github.com/glycerine/fwd v1.1.4-beta.jea h1:penEwsXMBPCMCH+iUj/HKRIuRzfESzLIk99OcttgqTU= 4 | github.com/glycerine/fwd v1.1.4-beta.jea/go.mod h1:N5KPZnp8z9AaPcR/9r5u2BcdqnlfcTcEGbX1In/yyIE= 5 | github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= 6 | github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= 7 | github.com/glycerine/greenpack v0.529.0 h1:u0eWU1m4mr/wK8nyMLuDq3n2wbnBcHMm6u05se7EqLI= 8 | github.com/glycerine/greenpack v0.529.0/go.mod h1:rDJGUPrlVMJ2UNwMzuCOD0CYjON7x+J8Kpp6Hwxx5Xc= 9 | github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0 h1:4ZegphJXBTc4uFQ08UVoWYmQXorGa+ipXetUj83sMBc= 10 | github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0/go.mod h1:AqJLs6UeoC65dnHxyCQ6MO31P5STpjcmgaANAU+No8Q= 11 | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= 12 | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= 13 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 14 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 15 | github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= 16 | github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 17 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= 18 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 19 | github.com/shurcooL/go-goon v1.0.0 h1:BCQPvxGkHHJ4WpBO4m/9FXbITVIsvAm/T66cCcCGI7E= 20 | github.com/shurcooL/go-goon v1.0.0/go.mod h1:2wTHMsGo7qnpmqA8ADYZtP4I1DD94JpXGQ3Dxq2YQ5w= 21 | github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= 22 | github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= 23 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 24 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 25 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 26 | -------------------------------------------------------------------------------- /imaginarium/array.zy: -------------------------------------------------------------------------------- 1 | // is array declaration possible 2 | 3 | (a[1 2] = 23) 4 | 5 | (b[0] = 2.4) 6 | 7 | // and we need a complex number parsing notation 8 | 9 | 3+2i 10 | 11 | 5+4j 12 | 13 | 5.5+4.6i 14 | 15 | // and how about a scientific notation? 16 | 17 | 3.3e9+2.2e-1i 18 | 19 | // simple math expressions (3+3)*3 mod 4 would be great 20 | 21 | (infix ``) 22 | 23 | << 3 + 5 / 4 >> 24 | 25 | translates to 26 | 27 | (+ 3 (/ 5 4)) 28 | 29 | can we use @ and ~ outside of macros to mean something else? 30 | 31 | 32 | (a[1 2] = 23) // how about if the a is still the controller; 33 | 34 | a is an object that is being sent a message [1 2], it then 35 | knows to return a reference to a setable element. 36 | 37 | is defining a grammar like defining a type? 38 | 39 | can we parse a type and parse a grammar in the same way? 40 | 41 | claim of pratt parser is dynamic extensibility: truly 42 | extensible languages. 43 | 44 | would this imply extensible types? 45 | 46 | haskell: 47 | 48 | data Bool = False | True 49 | 50 | I already have (bool ) the record type. 51 | 52 | lets have a general system for sigil symbols, somewhat perl like 53 | 54 | #symbol 55 | $symbol 56 | ?symbol 57 | @symbol 58 | ~symbol 59 | 60 | can we have 61 | @symbol: as an element in the array? sure why not? 62 | 63 | 64 | The goal with a: is to avoid having to deal with (quote a) everywhere, and to have it print back the same way as it was provided in the first place. Do we look up the key by querying for 'a' or for 'a:' then? by 'a'. So the 'a' must be what is entered into the hash table. 65 | 66 | But if we want the symbol to display as a: then it needs to be its own symbol. 67 | Because the suffix makes it lookup differently. problem: searching on 'a' 68 | name alone won't find 'a:' in the hash table. 69 | 70 | if we leave off distinguishing characters ':' from what is hashed, then our hash 71 | table won't carry those attributes along, i.e. we'll lookup the same symbol 72 | 73 | the description wants a: to be a sigil like symbol. But upon entry into the hash table, we need to enter the symbol 'a'. 74 | 75 | 76 | how about dot symbols referring to these symbols 77 | 78 | .?symbol .... hmm no. 79 | 80 | I like the idea of #symbols being special kind of thing, 81 | like maybe they are objects that have their own lookup rules. 82 | 83 | abilty to define grammars, and parse grammars. where 84 | the #symbol represents a non-terminal in the grammar. 85 | 86 | one of these can be a type variable. 87 | 88 | user-defined symbols 89 | type variables 90 | 91 | ?symbols 92 | 93 | (#a[3 3] = 3+i) 94 | 95 | can we get back : so that := works? 96 | 97 | well, then what happens to (a:3 b:2) 98 | 99 | we could make := distict from; it already is. 100 | 101 | ------------- 102 | 103 | querying data, including querying data defined in the language itself. 104 | 105 | constructing a timeseries. 106 | 107 | any up-spike resets the prediction, and invalidates it for at least K points. 108 | 109 | (K is what?) 110 | 111 | query for all of a particular type of event 112 | - within a parituclar window of time 113 | - with a specific game or customer_id 114 | 115 | data table like syntax? 116 | 117 | what are the operations: 118 | read : select-where 119 | read-for-update: update-where 120 | filter by function 121 | group by function 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /imaginarium/matrix.zy: -------------------------------------------------------------------------------- 1 | // comments 2 | // ; as matrix line separator 3 | // (def a [8 4 1 7 ; 1 2 3 4]) 4 | // assert (== 8 (:0 a))) 5 | 6 | // tree 7 | 8 | // type variables 9 | 10 | // can we mix infix in? 11 | nil 12 | -------------------------------------------------------------------------------- /media/high_altitude_gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/media/high_altitude_gopher.png -------------------------------------------------------------------------------- /slides/all.output: -------------------------------------------------------------------------------- 1 | zygo> (def w (weather type:"stormy")) 2 | zygo> w 3 | (weather type:"stormy") 4 | zygo> (fieldls w) 5 | ["Time time.Time" "Size int64" "Type string" "Details []uint8"] 6 | zygo> (methodls w) 7 | ["DecodeMsg func(*zygo.Weather, *msgp.Reader) error" "EncodeMsg func(*zygo.Weather, *msgp.Writer) error" "MarshalMsg func(*zygo.Weather, []uint8) ([]uint8, error)" "Msgsize func(*zygo.Weather) int" "UnmarshalMsg func(*zygo.Weather, []uint8) ([]uint8, error)"] 8 | zygo> (type? w) 9 | "weather" 10 | zygo> (str weather) 11 | "zygo.Weather" 12 | zygo> (def he (hellcat speed:567)) 13 | (hellcat speed:567) 14 | zygo> (def ho (hornet SpanCm:12)) 15 | (hornet SpanCm:12) 16 | zygo> (def snoopdog (snoopy friends:[he ho] cry:"Curse you, Red Baron!")) 17 | (snoopy friends:[ (hellcat speed:567) (hornet SpanCm:12)] cry:"Curse you, Red Baron!") 18 | zygo> (def ans (_method snoopdog EchoWeather: w)) 19 | [ (weather time:nil size:0 type:"stormy" details:[]byte(nil))] 20 | zygo> ans 21 | [ (weather time:nil size:0 type:"stormy" details:[]byte(nil))] 22 | zygo> (_method snoopdog Fly: w) 23 | Snoopy sees weather 'VERY stormy', cries 'Curse you, Red Baron!' 24 | Hellcat.Fly() called. I sees weather VERY stormy 25 | Hornet.Fly() called. I see weather VERY stormy 26 | ["Snoopy sees weather 'VERY stormy', cries 'Curse you, Red Baron!'" nil] 27 | zygo> 28 | -------------------------------------------------------------------------------- /slides/all.snoopy.zy: -------------------------------------------------------------------------------- 1 | 2 | (def w (weather type:"stormy")) 3 | w 4 | (fieldls w) 5 | (methodls w) 6 | (type? w) 7 | (str weather) 8 | (def he (hellcat speed:567)) 9 | (def ho (hornet SpanCm:12)) 10 | (def snoopdog (snoopy friends:[he ho] cry:"Curse you, Red Baron!")) 11 | (def ans (_method snoopdog EchoWeather: w)) 12 | ans 13 | (_method snoopdog Fly: w) 14 | -------------------------------------------------------------------------------- /slides/arrays.run: -------------------------------------------------------------------------------- 1 | zygo> (def ar [4 5 6 7]) 2 | [4 5 6 7] 3 | zygo> (aget ar 0) 4 | 4 5 | zygo> (aget ar 1) 6 | 5 7 | zygo> (aset ar 1 999) 8 | zygo> ar 9 | [4 999 6 7] 10 | zygo> 11 | -------------------------------------------------------------------------------- /slides/calltree.txt: -------------------------------------------------------------------------------- 1 | GenerateBegin 2 | | 3 | V 4 | Generate 5 | | 6 | V> 7 | GenerateAssignment GenerateCall GenerateArray 8 | | | \-> GenerateAll -> Generate 9 | V V> 10 | GenerateDef GenerateCallBySymbol GenerateDispatch GenerateBuilder 11 | | 12 | V 13 | GetLHS Generate 14 | 15 | -------------------------------------------------------------------------------- /slides/coro/after1.go: -------------------------------------------------------------------------------- 1 | // AFTER: we call getMoreInput() 2 | func (parser *Parser) parseArray(depth int) (Sexp, error) { 3 | ... 4 | 5 | if tok.typ != TokenEnd { 6 | break getTok 7 | } else { 8 | // we ask for more, and then loop 9 | err = parser.getMoreInput(nil, ErrMoreInputNeeded) <<<<=== key change 10 | switch err { 11 | case ParserHaltRequested: 12 | return SexpNull, err 13 | case ResetRequested: 14 | return SexpEnd, err 15 | } 16 | } 17 | ... 18 | -------------------------------------------------------------------------------- /slides/coro/after1full.go: -------------------------------------------------------------------------------- 1 | // AFTER in context 2 | func (parser *Parser) parseArray(depth int) (Sexp, error) { 3 | for { // get the next token, then break 4 | getTok: 5 | for { 6 | tok, err = parser.lexer.peekNextToken() 7 | if err != nil { 8 | return SexpEnd, err 9 | } 10 | if tok.typ == TokenComma { 11 | // pop off the , 12 | _, _ = parser.lexer.getNextToken() 13 | continue getTok 14 | } 15 | if tok.typ != TokenEnd { 16 | break getTok // got a token 17 | } else { 18 | // we ask for more, and then loop 19 | err = parser.getMoreInput(nil, ErrMoreInputNeeded) // <<<=== key change 20 | switch err { 21 | case ParserHaltRequested: 22 | return SexpNull, err 23 | case ResetRequested: 24 | return SexpEnd, err 25 | } 26 | } 27 | } 28 | 29 | if tok.typ == TokenRSquare { 30 | // pop off the ] 31 | _, _ = parser.lexer.getNextToken() 32 | break 33 | } 34 | 35 | expr, err := parser.parseExpression(depth + 1) 36 | if err != nil { 37 | return SexpNull, err 38 | } 39 | arr = append(arr, expr) 40 | } 41 | 42 | return &SexpArray{Val: arr, Env: parser.env}, nil 43 | } 44 | -------------------------------------------------------------------------------- /slides/coro/before1.go: -------------------------------------------------------------------------------- 1 | // BEFORE: original straight-line code 2 | func (parser *Parser) parseArray(depth int) (Sexp, error) { 3 | ... 4 | if tok.typ != TokenEnd { 5 | break getTok // got a token 6 | } else { 7 | return nil, io.EOF // <<<<<<<<<<<<< sad, done before finding ']' 8 | } 9 | ... 10 | -------------------------------------------------------------------------------- /slides/coro/coro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/coro/coro.png -------------------------------------------------------------------------------- /slides/coro/coroutines_goroutines.2016sept20.dfw_golang_meetup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/coro/coroutines_goroutines.2016sept20.dfw_golang_meetup.pdf -------------------------------------------------------------------------------- /slides/coro/first.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | // START OMIT 4 | // Go: see https://github.com/glycerine/zygomys/blob/master/repl/functions.go 5 | // 6 | func FirstFunction(env *Glisp, name string, args []Sexp) (Sexp, error) { 7 | if len(args) != 1 { 8 | return SexpNull, WrongNargs 9 | } 10 | switch expr := args[0].(type) { 11 | case *SexpPair: 12 | return expr.Head, nil 13 | case *SexpArray: 14 | if len(expr.Val) > 0 { 15 | return expr.Val[0], nil 16 | } 17 | return SexpNull, fmt.Errorf("first called on empty array") 18 | } 19 | return SexpNull, WrongType 20 | } 21 | 22 | // END OMIT 23 | -------------------------------------------------------------------------------- /slides/coro/getmore1.go: -------------------------------------------------------------------------------- 1 | // getMoreInput is called by the Parser routines mid-parse, if 2 | // need be, to obtain the next line/rune of input. 3 | // 4 | // getMoreInput() is used by Parser.ParseList(), Parser.ParseArray(), 5 | // Parser.ParseBlockComment(), and Parser.ParseInfix(). 6 | // 7 | // getMoreInput() is also used by Parser.infiniteParsingLoop() which 8 | // is the main driver behind parsing. 9 | // 10 | // This function should *return* when it has more input 11 | // for the parser/lexer, which will call it when they get wedged. 12 | // 13 | // Listeners on p.ParsedOutput should know the Convention: sending 14 | // a length 0 []ParserReply on p.ParsedOutput channel means: we need more 15 | // input! They should send some in on p.AddInput channel; or request 16 | // a reset and simultaneously give us new input with p.ReqReset channel. 17 | func (p *Parser) getMoreInput(deliverThese []Sexp, errorToReport error) error { 18 | if len(deliverThese) == 0 && errorToReport == nil { 19 | p.FlagSendNeedInput = true 20 | } else { 21 | p.sendMe = append(p.sendMe,ParserReply{Expr: deliverThese,Err: errorToReport}) 22 | } 23 | for { 24 | select { 25 | case <-p.reqStop: 26 | return ParserHaltRequested 27 | case input := <-p.AddInput: 28 | p.lexer.AddNextStream(input) 29 | p.FlagSendNeedInput = false 30 | return nil 31 | case input := <-p.ReqReset: 32 | p.lexer.Reset() 33 | p.lexer.AddNextStream(input) 34 | p.FlagSendNeedInput = false 35 | return ResetRequested 36 | case p.HaveStuffToSend() <- p.sendMe: 37 | // that was a conditional send, because 38 | // HaveStuffToSend() will return us a 39 | // nil channel if there's nothing ready. 40 | p.sendMe = make([]ParserReply, 0, 1) 41 | p.FlagSendNeedInput = false 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /slides/coro/getmore2.go: -------------------------------------------------------------------------------- 1 | // getMoreInput does I/O: it is called by the Parser routines mid-parse to get the user's next line 2 | func (p *Parser) getMoreInput(deliverThese []Sexp, errorToReport error) error { 3 | if len(deliverThese) == 0 && errorToReport == nil { 4 | p.FlagSendNeedInput = true 5 | } else { 6 | p.sendMe = append(p.sendMe,ParserReply{Expr: deliverThese,Err: errorToReport}) 7 | } 8 | for { 9 | select { 10 | case <-p.reqStop: 11 | return ParserHaltRequested 12 | case input := <-p.AddInput: 13 | p.lexer.AddNextStream(input) 14 | p.FlagSendNeedInput = false 15 | return nil 16 | case input := <-p.ReqReset: 17 | p.lexer.Reset() 18 | p.lexer.AddNextStream(input) 19 | p.FlagSendNeedInput = false 20 | return ResetRequested 21 | case p.HaveStuffToSend() <- p.sendMe: // a conditional send! 22 | p.sendMe = make([]ParserReply, 0, 1) 23 | p.FlagSendNeedInput = false 24 | }}} 25 | -------------------------------------------------------------------------------- /slides/coro/havestuff.go: -------------------------------------------------------------------------------- 1 | func (p *Parser) HaveStuffToSend() chan []ParserReply { 2 | if len(p.sendMe) > 0 || p.FlagSendNeedInput { 3 | return p.ParsedOutput 4 | } 5 | return nil 6 | } 7 | -------------------------------------------------------------------------------- /slides/coro/infparse.go: -------------------------------------------------------------------------------- 1 | func (p *Parser) infiniteParsingLoop() { 2 | defer close(p.Done) 3 | expressions := make([]Sexp, 0, SliceDefaultCap) 4 | for { 5 | expr, err := p.parseExpression(0) 6 | if err != nil || expr == SexpEnd { 7 | if err == ParserHaltRequested { 8 | return 9 | } 10 | // expr == SexpEnd means that parserExpression 11 | // couldn't read another token, so a call to 12 | // getMoreInput() is required. 13 | 14 | // provide accumulated expressions 15 | // back to the client here 16 | err = p.getMoreInput(expressions, err) 17 | if err == ParserHaltRequested { 18 | return 19 | } 20 | 21 | // getMoreInput() will have delivered 22 | // expressions to the client. Reset expressions since we 23 | // don't own that memory any more. 24 | expressions = make([]Sexp, 0, SliceDefaultCap) 25 | } else { 26 | // INVAR: err == nil && expr is not SexpEnd 27 | expressions = append(expressions, expr) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /slides/coro/infparse_nocomment.go: -------------------------------------------------------------------------------- 1 | // Start() commences the background infinite loop of parsing 2 | func (p *Parser) Start() { 3 | go func() { 4 | defer close(p.Done) 5 | expressions := make([]Sexp, 0, SliceDefaultCap) 6 | for { 7 | expr, err := p.parseExpression(0) 8 | if err != nil || expr == SexpEnd { 9 | if err == ParserHaltRequested { 10 | return 11 | } 12 | err = p.getMoreInput(expressions, err) // SexpEnd means we need more input 13 | if err == ParserHaltRequested { 14 | return 15 | } 16 | expressions = make([]Sexp, 0, SliceDefaultCap) 17 | } else { 18 | expressions = append(expressions, expr) 19 | } 20 | } 21 | }() 22 | } 23 | 24 | -------------------------------------------------------------------------------- /slides/coro/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/coro/large.png -------------------------------------------------------------------------------- /slides/coro/large2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/coro/large2.jpg -------------------------------------------------------------------------------- /slides/coro/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | The zygomys command line REPL is known as `zygo`. 3 | */ 4 | package main 5 | 6 | // START OMIT 7 | import ( 8 | ... 9 | zygo "github.com/glycerine/zygomys/repl" 10 | ) 11 | func main() { 12 | // (1) configure it 13 | // See library configuration convention: https://github.com/glycerine/configs-in-golang 14 | cfg := zygo.NewGlispConfig("zygo") 15 | 16 | // (2) register your Go structs; give them a nickname 17 | // here we register snoopy as a handle to Go struct &Snoopy{} 18 | zygo.GoStructRegistry.RegisterUserdef("snoopy", 19 | &zygo.RegisteredType{GenDefMap: true, 20 | Factory: func(env *zygo.Glisp) (interface{}, error) { 21 | return &Snoopy{}, nil 22 | }}, true) 23 | 24 | // (3) run the zygo repl 25 | // -- the library does all the heavy lifting. 26 | zygo.ReplMain(cfg) 27 | } 28 | // END OMIT 29 | 30 | func usage(myflags *flag.FlagSet) { 31 | fmt.Printf("zygo command line help:\n") 32 | myflags.PrintDefaults() 33 | os.Exit(1) 34 | } 35 | -------------------------------------------------------------------------------- /slides/coro/med2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/coro/med2.jpg -------------------------------------------------------------------------------- /slides/coro/orig.parser1.go: -------------------------------------------------------------------------------- 1 | // (original straight-line code:) parseArray handles `[2, 4, 5, "six"]` arrays of expressions 2 | func (parser *Parser) parseArray(depth int) (Sexp, error) { 3 | for { // get the next token, then break 4 | getTok: 5 | for { 6 | tok, err = parser.lexer.peekNextToken() 7 | if err != nil { 8 | return SexpEnd, err 9 | } 10 | 11 | if tok.typ == TokenComma { 12 | // pop off the , 13 | _, _ = parser.lexer.getNextToken() 14 | continue getTok 15 | } 16 | 17 | if tok.typ != TokenEnd { 18 | break getTok // got a token 19 | } else { 20 | return nil, io.EOF // <<<<<<<<<<<<< sad, done before finding ']' 21 | } 22 | } 23 | 24 | if tok.typ == TokenRSquare { 25 | // pop off the ] 26 | _, _ = parser.lexer.getNextToken() 27 | break 28 | } 29 | 30 | expr, err := parser.parseExpression(depth + 1) 31 | if err != nil { 32 | return SexpNull, err 33 | } 34 | arr = append(arr, expr) 35 | } 36 | 37 | return &SexpArray{Val: arr, Env: parser.env}, nil 38 | } 39 | -------------------------------------------------------------------------------- /slides/coro/parser_api.go: -------------------------------------------------------------------------------- 1 | // Start() commences the background parse loop goroutine. 2 | func (p *Parser) Start() 3 | 4 | 5 | // ParseTokens is the main service the Parser provides. 6 | // Currently returns first error encountered, ignoring 7 | // any expressions after that. 8 | func (p *Parser) ParseTokens() ([]Sexp, error) 9 | 10 | 11 | // NewInput is the principal API function to 12 | // supply parser with addition textual 13 | // input lines 14 | func (p *Parser) NewInput(s io.RuneScanner) 15 | 16 | 17 | // ResetAddNewInput is the principal API function to 18 | // tell the parser to forget everything it has stored, 19 | // reset, and take as new input the scanner s. 20 | func (p *Parser) ResetAddNewInput(s io.RuneScanner) { 21 | 22 | 23 | // Stop gracefully shutsdown the parser and its background goroutine. 24 | func (p *Parser) Stop() error 25 | 26 | -------------------------------------------------------------------------------- /slides/coro/pausable.slide: -------------------------------------------------------------------------------- 1 | Pause-able Parsing and Elegant Interpreters in Go: Using Goroutines as Coroutines 2 | 20 September 2016 3 | Tags: zygomys, coroutines, pause-able parsing 4 | 5 | Jason E. Aten, Ph.D. 6 | Principal Engineer, Sauce Labs 7 | j.e.aten@gmail.com 8 | @jasonaten_ 9 | 10 | https://github.com/glycerine/zygomys 11 | 12 | [[https://github.com/glycerine/zygomys/wiki.]] The wiki has details, examples, and discussion. 13 | 14 | * problem: implementating an interpreter efficiently 15 | 16 | - suppose your code is running, and deep inside a nested set of possibly mutually recursive calls... 17 | - and you run out of input. 18 | - ... do you start all over? 19 | - ... and take O(n^2) time to parse an n-line program? Ouch. 20 | - you want to save your state, and resume later, exactly where you left off... 21 | - this is exactly what happens at the interpreter prompt 22 | 23 | * generally 24 | 25 | - how to refactor your straight line code... 26 | - to pause - and - resume gracefully 27 | - to be interuptable 28 | - to be lazy 29 | 30 | * benefits of this style 31 | 32 | - more coherency: keep the readability of straight-line code 33 | - insert pause points after the fact 34 | - easier to read => means easier to maintain, refactor, and extend 35 | 36 | 37 | * context: zygomys interpreter 38 | 39 | - an interpreted scripting language 40 | - built in Go, for steering Go 41 | - reflect to invoke compiled Go code 42 | - zygomys has closures with lexical scope 43 | - for loops 44 | - higher order functions 45 | - readable math: anything inside curly braces {} is infix. example: a = 2 * 5 + 4 / 2 46 | .link https://github.com/glycerine/zygomys https://github.com/glycerine/zygomys 47 | 48 | * context II: architecture / overview of zygomys implementation 49 | 50 | - a) lexer produces tokens 51 | - b) parser produces lists and arrays of symbols <<<== focus of this talk 52 | - c) macros run at definition type 53 | - d) codegen produces s-expression byte-code 54 | - e) a virtual machine executes the byte-code 55 | 56 | * what specifically changes to make code pause-able? And more importantly, resumable? 57 | 58 | * original parseArray (only 50% shown/fits on a screen) 59 | 60 | .code orig.parser1.go 61 | 62 | * before, closeup 63 | 64 | .code before1.go 65 | 66 | * after, closeup 67 | 68 | .code after1.go 69 | 70 | * zoom out: after in full context 71 | 72 | .code after1full.go 73 | 74 | * the key was getMoreInput() call instead of returning io.EOF... simple enough, but... 75 | 76 | * that begs the question, how does getMoreInput() work... 77 | 78 | * apparently the real magic is in getMoreInput(). It must be doing the heavy lifting... 79 | 80 | * getMoreInput() 81 | 82 | .code getmore2.go 83 | 84 | * HaveStuffToSend() is easy... 85 | 86 | .code havestuff.go 87 | 88 | * what is unusual about getMoreInput() 89 | 90 | - it can be called from multiple places 91 | - callers get to retain the entire context of their call stack 92 | - getMoreInput() returns to its caller precisely once the caller can continue 93 | - and in the meantime, it does the channel work in a select{} to get more input from an asynchronous source 94 | 95 | - In my humble experience, this is rare: a co-routine pattern 96 | - Caller's code gets to pause. And then resume, right where it left off. 97 | 98 | * supporting player: a background goroutine running an infinite loop that drives parsing. It also calls getMoreInput() to start top-level parsing. 99 | 100 | .code infparse_nocomment.go 101 | 102 | * call graph 103 | 104 | .image med2.jpg 105 | 106 | * so what does the Parser API look like from the outside? 107 | 108 | * here is what the user sees: 109 | 110 | * what the Parser API looks like 111 | 112 | .code parser_api.go 113 | 114 | * conclusion 115 | 116 | - coroutine patterns are viable in Go 117 | - we can avoid O(n^2) time interpreter parsing 118 | - other uses: functional programming patterns like (lazy) generators[1] 119 | 120 | - 121 | - 122 | 123 | [1] John Hughes, Why Functional Programming Matters 124 | .link https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf 125 | -------------------------------------------------------------------------------- /slides/coro/repl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // reference: github.com/glycerine/zygomys/repl/repl.go 4 | // 5 | // much detail elided for instructional purposes... 6 | 7 | func main() { 8 | // env represents one interpreter: 9 | // It will have one parsing and one execution goroutine. 10 | env = NewGlisp() 11 | 12 | // cfg configures the env 13 | cfg := &GlsipConfig{ 14 | // some of the options: 15 | ExitOnFailure: false, // action to take on error 16 | Sandboxed: false, // restrict scripts to sandobox? 17 | Quiet: false, // display startup banner? 18 | Trace: false, // for debugging, print actions step-by-step 19 | } 20 | 21 | // start a repl 22 | Repl(env, cfg) 23 | } 24 | 25 | func Repl(env *Glisp, cfg *GlispConfig) { 26 | 27 | // Prompter prints a prompt and returns a single line 28 | // of input from stdin when pr.getExpressionWithLiner(env) 29 | // is invoked. 30 | pr := NewPrompter() 31 | defer pr.Close() 32 | 33 | // the LOOP of the REPL. REPL stands for 34 | // 35 | // (1) READ, (2) EVAL, (3) PRINT, (4) LOOP 36 | // 37 | for { 38 | // (1) READ 39 | line, exprsInput, err := pr.getExpressionWithLiner(env) 40 | if err != nil { 41 | fmt.Println(err) 42 | if err == io.EOF { 43 | os.Exit(0) 44 | } 45 | env.Clear() 46 | continue 47 | } 48 | 49 | // (2) EVAL 50 | expr, err := env.Eval(exprsInput) 51 | switch err { 52 | case nil: 53 | case NoExpressionsFound: 54 | env.Clear() 55 | continue 56 | default: 57 | // display error. reset. 58 | fmt.Print(env.GetStackTrace(err)) 59 | env.Clear() 60 | continue 61 | } 62 | 63 | if expr != SexpNull { 64 | // (3) PRINT expr to stdout here 65 | // ... 66 | } 67 | } 68 | } 69 | 70 | // continuationPrompt is displayed when the parser needs more input 71 | var continuationPrompt = "... " 72 | 73 | // reads Stdin only 74 | func (pr *Prompter) getExpressionWithLiner(env *Glisp) (readin string, xs []Sexp, err error) { 75 | 76 | line, err := pr.Getline(nil) 77 | if err != nil { 78 | return "", nil, err 79 | } 80 | 81 | err = UnexpectedEnd 82 | var x []Sexp 83 | 84 | // parse and pause the parser if we need more input. 85 | env.parser.ResetAddNewInput(bytes.NewBuffer([]byte(line + "\n"))) 86 | x, err = env.parser.ParseTokens() 87 | 88 | if len(x) > 0 { 89 | xs = append(xs, x...) 90 | } 91 | 92 | for err == ErrMoreInputNeeded || err == UnexpectedEnd || err == ResetRequested { 93 | nextline, err := pr.Getline(&continuationPrompt) 94 | if err != nil { 95 | return "", nil, err 96 | } 97 | // provide more input 98 | env.parser.NewInput(bytes.NewBuffer([]byte(nextline + "\n"))) 99 | 100 | // get parsed expression tree(s) back in x 101 | x, err = env.parser.ParseTokens() 102 | if len(x) > 0 { 103 | for i := range x { 104 | if x[i] == SexpEnd { 105 | P("found an SexpEnd token, omitting it") 106 | continue 107 | } 108 | xs = append(xs, x[i]) 109 | } 110 | } 111 | switch err { 112 | case nil: 113 | line += "\n" + nextline 114 | // no problem, parsing went fine. 115 | return line, xs, nil 116 | case ResetRequested: 117 | continue 118 | case ErrMoreInputNeeded: 119 | continue 120 | default: 121 | return "", nil, fmt.Errorf("Error on line %d: %v\n", env.parser.lexer.Linenum(), err) 122 | } 123 | } 124 | return line, xs, nil 125 | } 126 | -------------------------------------------------------------------------------- /slides/curse_you_red_baron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/curse_you_red_baron.png -------------------------------------------------------------------------------- /slides/declare.struct: -------------------------------------------------------------------------------- 1 | // struct type definition 2 | // Note the `e` numbers for evolution (like protobufs); + the deprecated notation 3 | (struct Car [ 4 | (field Id: int64 e:0 gotags:`json:"id",name:"id"` ) 5 | (field Name: string e:1 gotags:`json:"name"` ) 6 | (field BadApple: string e:2 deprecated:true ) 7 | (field SliceOfLife: ([]string) e:3 ) // slice of string, matching Go's syntax. 8 | (field PointerSisters: (* int64) e:4 ) // pointer to an int64 9 | (field OtherCar: (* Car) e:5 ) // pointer to a Car struct 10 | ]) 11 | -------------------------------------------------------------------------------- /slides/demo: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | //go:generate msgp 9 | 10 | //msgp:ignore Plane Wings Snoopy Hornet Hellcat 11 | 12 | type Event struct { 13 | Id int `json:"id" msg:"id"` 14 | User Person `json:"user" msg:"user"` 15 | Flight string `json:"flight" msg:"flight"` 16 | Pilot []string `json:"pilot" msg:"pilot"` 17 | } 18 | 19 | type Person struct { 20 | First string `json:"first" msg:"first"` 21 | Last string `json:"last" msg:"last"` 22 | } 23 | 24 | func (ev *Event) DisplayEvent(from string) { 25 | fmt.Printf("%s %#v", from, ev) 26 | } 27 | 28 | type Wings struct { 29 | SpanCm int 30 | } 31 | 32 | // the interface Flyer confounds the msgp msgpack code generator, 33 | // so put the msgp:ignore Plane above 34 | type Plane struct { 35 | Wings 36 | 37 | //Name string `json:"name" msg:"name"` 38 | Speed int `json:"speed" msg:"speed"` 39 | Chld Flyer `json:"chld" msg:"chld"` 40 | Friends []Flyer `json:"friends"` 41 | } 42 | 43 | type Snoopy struct { 44 | Plane `json:"plane" msg:"plane"` 45 | Cry string `json:"cry" msg:"cry"` 46 | Pack []int `json:"pack"` 47 | Carrying []Flyer `json:"carrying"` 48 | } 49 | 50 | type Hornet struct { 51 | Plane `json:"plane" msg:"plane"` 52 | Mass float64 53 | Nickname string 54 | } 55 | 56 | type Hellcat struct { 57 | Plane `json:"plane" msg:"plane"` 58 | } 59 | 60 | func (p *Snoopy) Fly(ev *Weather) (s string, err error) { 61 | s = fmt.Sprintf("Snoopy sees weather '%s', cries '%s'", ev.Type, p.Cry) 62 | fmt.Println(s) 63 | return 64 | } 65 | 66 | func (p *Snoopy) GetCry() string { 67 | return p.Cry 68 | } 69 | 70 | func (p *Snoopy) EchoWeather(w *Weather) *Weather { 71 | return w 72 | } 73 | 74 | func (p *Snoopy) Sideeffect() { 75 | fmt.Printf("Sideeffect() called! p = %p\n", p) 76 | } 77 | 78 | func (b *Hornet) Fly(ev *Weather) (s string, err error) { 79 | fmt.Printf("Hornet sees weather %v", ev) 80 | return 81 | } 82 | 83 | func (b *Hellcat) Fly(ev *Weather) (s string, err error) { 84 | fmt.Printf("Hellcat sees weather %v", ev) 85 | return 86 | } 87 | 88 | type Flyer interface { 89 | Fly(ev *Weather) (s string, err error) 90 | } 91 | 92 | type Weather struct { 93 | Time time.Time `json:"time" msg:"time"` 94 | Size int64 `json:"size" msg:"size"` 95 | Type string `json:"type" msg:"type"` 96 | Details []byte `json:"details" msg:"details"` 97 | } 98 | -------------------------------------------------------------------------------- /slides/exp.txt: -------------------------------------------------------------------------------- 1 | type Sexp interface { 2 | SexpString(indent int) string 3 | Type() *RegisteredType 4 | } 5 | -------------------------------------------------------------------------------- /slides/final.jason.zygo.talk.2016march16.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/final.jason.zygo.talk.2016march16.pdf -------------------------------------------------------------------------------- /slides/first.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | // START OMIT 4 | // Go: see https://github.com/glycerine/zygomys/blob/master/repl/functions.go 5 | // 6 | func FirstFunction(env *Glisp, name string, args []Sexp) (Sexp, error) { 7 | if len(args) != 1 { 8 | return SexpNull, WrongNargs 9 | } 10 | switch expr := args[0].(type) { 11 | case *SexpPair: 12 | return expr.Head, nil 13 | case *SexpArray: 14 | if len(expr.Val) > 0 { 15 | return expr.Val[0], nil 16 | } 17 | return SexpNull, fmt.Errorf("first called on empty array") 18 | } 19 | return SexpNull, WrongType 20 | } 21 | 22 | // END OMIT 23 | -------------------------------------------------------------------------------- /slides/flyer.interface: -------------------------------------------------------------------------------- 1 | 2 | type Flyer interface { 3 | Fly(ev *Weather) (s string, err error) 4 | } 5 | -------------------------------------------------------------------------------- /slides/harry.record: -------------------------------------------------------------------------------- 1 | /* Harry Potter goes West in 'Hogwild!'. We'll build and then query this structure: 2 | (ranch 3 | cowboy:"Harry" 4 | cowgirl:"Hermonie" 5 | bunk1: (bunkhouse 6 | bed1:"Lucius" 7 | bed2:"Dumbledore" 8 | closet1: (closet 9 | broom:"Nimbus2k" ) ) ) ) 10 | */ 11 | zygo> (defmap ranch) 12 | zygo> (def hogwild (ranch cowboy:"Harry" cowgirl:"Hermonie")) 13 | zygo> (defmap bunkhouse) 14 | zygo> (hset hogwild bunk1:(bunkhouse bed1:"Lucius" bed2: "Dumbledore")) 15 | zygo> (defmap closet) 16 | zygo> (hset (:bunk1 hogwild) closet1:(closet broom:"Nimbus2k")) 17 | 18 | zygo> (hget (hget (hget hogwild bunk1:) closet1:) broom:) // step by step query 19 | "Nimbus2k" 20 | zygo> (-> hogwild bunk1: closet1: broom:) // clojure style threading 21 | "Nimbus2k" 22 | -------------------------------------------------------------------------------- /slides/harry.zy: -------------------------------------------------------------------------------- 1 | (defmap ranch) 2 | (def hogwild (ranch cowboy:"Harry" cowgirl:"Hermonie")) 3 | 4 | // records can be nested: 5 | (defmap bunkhouse) 6 | (hset hogwild bunk1:(bunkhouse bed1:"Luciuos" bed2: "Dumbledore")) 7 | 8 | // and nested again 9 | (defmap closet) 10 | (hset (:bunk1 hogwild) closet1:(closet broom:"Nimbuz2")) // add 11 | 12 | // and then threaded: 13 | (-> hogwild bunk1: closet1: broom:) 14 | "Nimbuz2" 15 | (.hogwild.bunk1.closet1.broom) 16 | 17 | (hogwild 18 | (ranch 19 | cowboy:"Harry" 20 | cowgirl:"Hermonie" 21 | bunk1: (bunkhouse 22 | bed1:"Luciuos" 23 | bed2:"Dumbledore" 24 | closet1: (closet 25 | broom:"Nimbuz2" ) ) ) ) 26 | -------------------------------------------------------------------------------- /slides/hash.demo: -------------------------------------------------------------------------------- 1 | zygo> (def h (hash key1:"val1" key2:222)) 2 | zygo> (hget h key1:) 3 | "val1" 4 | 5 | // keys and values can be any s-expression (Sexp) type 6 | zygo> (hset h key3:(list 5 6 7)) // create and store a list under the symbol key3 7 | zygo> (hget h key3:) 8 | (5 6 7) 9 | 10 | zygo> h 11 | (hash key1:"val1" key2:222 key3:(5 6 7)) 12 | 13 | zygo> (hdel h key2:) // delete from the hashmap 14 | zygo> 15 | zygo> h 16 | (hash key1:"val1" key3:(5 6 7)) 17 | 18 | zygo> (:key3 h) // shorthand for hget 19 | (5 6 7) 20 | zygo> 21 | zygo> (hget h 43 %notFound) // specify arbitrary 3rd arg, here %notFound, as missing indicator 22 | notFound 23 | zygo> -------------------------------------------------------------------------------- /slides/hof.foldr: -------------------------------------------------------------------------------- 1 | // foldr: right fold is a classic higher order function 2 | // It runs a function over each element in a list. 3 | // 4 | // lst: pair list, the input 5 | // fun: processes one element in the list 6 | // acc: the accumulated result, the output 7 | // 8 | (defn foldr [lst fun acc] 9 | (cond // cond is zygo's if-then-else. 10 | (empty? lst) acc // return acculated output if no more input. 11 | (fun // else call fun on the head of lst 12 | (car lst) 13 | (foldr (cdr lst) fun acc)) // recursive call on the tail of the input 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /slides/infix.session: -------------------------------------------------------------------------------- 1 | zygo> { a = 10 } 2 | 10 3 | zygo> { b = 12 } 4 | 12 5 | zygo> { a + b * 2 } 6 | 34 7 | zygo> { a + b * 2 / .5} 8 | 58 9 | zygo> (defn cube [x] (* x x x)) // or (defn cube [x] {x*x*x}) 10 | zygo> a 11 | 10 12 | zygo> b 13 | 12 14 | zygo> cube 15 | (defn cube [x] (* x x x)) 16 | zygo> (cube b) 17 | 1728 18 | zygo> { a + (cube b) ** 2} // L ** R means raise L to the power R. 19 | 2985994 20 | zygo> -------------------------------------------------------------------------------- /slides/infix.txt: -------------------------------------------------------------------------------- 1 | 2 | zygo> {2 * 3 / 4 - 5} 3 | -3.5 4 | zygo> (infixExpand {2 * 3 / 4 - 5} ) 5 | (quote (- (/ (* 2 3) 4) 5)) 6 | 7 | // {} become your "parenthesis" when you want to force ordering: 8 | zygo> {2 * 3 / {4 - 5}} 9 | -6 10 | 11 | zygo> (infixExpand {2 * 3 /{4 - 5}} ) 12 | (quote (/ (* 2 3) (infix [4 - 5]))) 13 | zygo> 14 | -------------------------------------------------------------------------------- /slides/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | The zygomys command line REPL is known as `zygo`. 3 | */ 4 | package main 5 | 6 | // START OMIT 7 | import ( 8 | ... 9 | zygo "github.com/glycerine/zygomys/repl" 10 | ) 11 | func main() { 12 | // (1) configure it 13 | // See library configuration convention: https://github.com/glycerine/configs-in-golang 14 | cfg := zygo.NewGlispConfig("zygo") 15 | 16 | // (2) register your Go structs; give them a nickname 17 | // here we register snoopy as a handle to Go struct &Snoopy{} 18 | zygo.GoStructRegistry.RegisterUserdef("snoopy", 19 | &zygo.RegisteredType{GenDefMap: true, 20 | Factory: func(env *zygo.Glisp) (interface{}, error) { 21 | return &Snoopy{}, nil 22 | }}, true) 23 | 24 | // (3) run the zygo repl 25 | // -- the library does all the heavy lifting. 26 | zygo.ReplMain(cfg) 27 | } 28 | // END OMIT 29 | 30 | func usage(myflags *flag.FlagSet) { 31 | fmt.Printf("zygo command line help:\n") 32 | myflags.PrintDefaults() 33 | os.Exit(1) 34 | } 35 | -------------------------------------------------------------------------------- /slides/make.it.rain: -------------------------------------------------------------------------------- 1 | zygo> (def w (weather type:"stormy")) 2 | 3 | zygo> w 4 | (weather type:"stormy") 5 | 6 | zygo> (fieldls w) 7 | ["Type string"] 8 | 9 | zygo> (methodls w) 10 | ["IsSunny func(*zygo.Weather) bool"] 11 | 12 | zygo> 13 | zygo> (type? w) // run-time type query 14 | "weather" 15 | 16 | zygo> (str weather) // stringification, shows the Go (shadow struct) type with package prefix 17 | "zygo.Weather" 18 | 19 | zygo> 20 | -------------------------------------------------------------------------------- /slides/make.planes: -------------------------------------------------------------------------------- 1 | zygo> (def he (hellcat speed:567)) 2 | zygo> (def ho (hornet SpanCm:12)) 3 | zygo> (def snoopdog (snoopy friends:[he ho] cry:"Curse you, Red Baron!" number:320)) 4 | zygo> -------------------------------------------------------------------------------- /slides/michoacan_pocket_gopher.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/michoacan_pocket_gopher.jpeg -------------------------------------------------------------------------------- /slides/msgp.txt: -------------------------------------------------------------------------------- 1 | Conversion map: 2 | 3 | Go map[string]interface{} <--(1)--> lisp 4 | ^ ^ | 5 | | / | 6 | (2) ------------ (4) -----------/ (5) 7 | | / | 8 | V V V 9 | msgpack <--(3)--> go struct, strongly typed 10 | 11 | (1) we provide these herein; see jsonmsgp_test.go too. 12 | (a) SexpToGo() 13 | (b) GoToSexp() 14 | (2) provided by ugorji/go/codec; see examples also herein 15 | (a) MsgpackToGo() / JsonToGo() 16 | (b) GoToMsgpack() / GoToJson() 17 | (3) provided by tinylib/msgp, and by ugorji/go/codec 18 | by using pre-compiled or just decoding into an instance 19 | of the struct. 20 | (4) see herein 21 | (a) SexpToMsgpack() and SexpToJson() 22 | (b) MsgpackToSexp(); uses (4) = (2) + (1) 23 | (5) The SexpToGoStructs() and ToGoFunction() in this file. 24 | -------------------------------------------------------------------------------- /slides/other.planes: -------------------------------------------------------------------------------- 1 | // Go: 2 | // https://en.wikipedia.org/wiki/McDonnell_Douglas_F/A-18_Hornet (1978 - present) 3 | type Hornet struct { 4 | Plane 5 | Mass float64 6 | } 7 | // https://en.wikipedia.org/wiki/Grumman_F6F_Hellcat (1943 - 1960) 8 | type Hellcat struct { 9 | Plane 10 | } 11 | func (b *Hornet) Fly(w *Weather) (s string, err error) { 12 | fmt.Printf("Hornet.Fly() called. I see weather %v\n", w.Type) 13 | return 14 | } 15 | func (b *Hellcat) Fly(w *Weather) (s string, err error) { 16 | fmt.Printf("Hellcat.Fly() called. I see weather %v\n", w.Type) 17 | return 18 | } 19 | // give Weather a method to list 20 | func(w *Weather) IsSunny() bool { 21 | return w.Type == "sunny" 22 | } -------------------------------------------------------------------------------- /slides/parseTree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/parseTree.jpg -------------------------------------------------------------------------------- /slides/plane.interact: -------------------------------------------------------------------------------- 1 | zygo> 2 | zygo> (_method snoopdog Fly: w) 3 | Snoopy sees weather 'VERY stormy', cries 'Curse you, Red Baron!' 4 | Hellcat.Fly() called. I see weather VERY stormy 5 | Hornet.Fly() called. I see weather VERY stormy 6 | ["Snoopy sees weather 'VERY stormy', cries 'Curse you, Red Baron!'" nil] 7 | zygo> 8 | -------------------------------------------------------------------------------- /slides/plane.schema: -------------------------------------------------------------------------------- 1 | // fragment from `github.com/glycerine/zygomys/repl/demo_go_structs.go` 2 | 3 | type Snoopy struct { 4 | Plane `json:"plane" msg:"plane"` 5 | Cry string `json:"cry" msg:"cry"` 6 | Pack []int `json:"pack"` 7 | Carrying []Flyer `json:"carrying"` 8 | } 9 | 10 | type Hornet struct { 11 | Plane `json:"plane" msg:"plane"` 12 | Mass float64 13 | Nickname string 14 | } 15 | 16 | type Hellcat struct { 17 | Plane `json:"plane" msg:"plane"` 18 | } 19 | 20 | func (p *Snoopy) Fly(ev *Weather) (s string, err error) { 21 | s = fmt.Sprintf("Snoopy sees weather '%s', cries '%s'", ev.Type, p.Cry) 22 | fmt.Println(s) 23 | return 24 | } 25 | 26 | func (p *Snoopy) GetCry() string { 27 | return p.Cry 28 | } 29 | 30 | func (p *Snoopy) EchoWeather(w *Weather) *Weather { 31 | return w 32 | } 33 | 34 | func (p *Snoopy) Sideeffect() { 35 | fmt.Printf("Sideeffect() called! p = %p\n", p) 36 | } 37 | 38 | 39 | 40 | (def he (hellcat speed:567)) 41 | (def snoop (snoopy chld:he)) 42 | -------------------------------------------------------------------------------- /slides/pocket_gopher.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/pocket_gopher.jpeg -------------------------------------------------------------------------------- /slides/pocket_gopher2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/pocket_gopher2.jpeg -------------------------------------------------------------------------------- /slides/pocket_gopher3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/pocket_gopher3.jpeg -------------------------------------------------------------------------------- /slides/pocket_gopher4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/pocket_gopher4.jpeg -------------------------------------------------------------------------------- /slides/sample.code: -------------------------------------------------------------------------------- 1 | 2 | // define a hashmap with four key-value pairs: 3 | (def h (hash a:44 b:55 c:77 d:99)) 4 | 5 | // define a string to concat stuff to 6 | (def s "") 7 | 8 | // iterate over the hashmap h, adding to s 9 | (range k v h (set s (concat s " " (str k) "-maps->" (str v) "\n"))) 10 | 11 | // inspect the output in s 12 | s 13 | -------------------------------------------------------------------------------- /slides/sample.run: -------------------------------------------------------------------------------- 1 | zygo> // define a hashmap with four key-value pairs: 2 | zygo> (def h (hash a:44 b:55 c:77 d:99)) 3 | (hash a:44 b:55 c:77 d:99) 4 | zygo> // define a string to concat stuff to 5 | zygo> (def s "") 6 | "" 7 | zygo> // iterate over the hashmap h, adding to s 8 | zygo> (range k v h (set s (concat s " " (str k) "-maps->" (str v) "\n"))) 9 | zygo> 10 | zygo> // inspect the output in s 11 | zygo> s 12 | " a-maps->44\n b-maps->55\n c-maps->77\n d-maps->99\n" 13 | zygo> -------------------------------------------------------------------------------- /slides/sample.run1: -------------------------------------------------------------------------------- 1 | zygo> // define a hashmap with four key-value pairs: 2 | zygo> (def hsh (hash a:44 b:55 c:77 d:99)) 3 | (hash a:44 b:55 c:77 d:99) 4 | zygo> 5 | zygo> // define a string to concat stuff to 6 | zygo> (def s "") 7 | "" 8 | -------------------------------------------------------------------------------- /slides/sample.run2: -------------------------------------------------------------------------------- 1 | for k, v := range h { 2 | s += string(k) + "-maps->" + string(v) + "\n" 3 | } 4 | -------------------------------------------------------------------------------- /slides/sample.run3: -------------------------------------------------------------------------------- 1 | zygo> // iterate over the hashmap hsh, adding to s 2 | zygo> (range k v hsh 3 | ... /*body of loop starts -- here the body is this set call*/ 4 | ... (set s (concat s " " (str k) "-maps->" (str v) "\n"))) 5 | zygo> 6 | zygo> s 7 | " a-maps->44\n b-maps->55\n c-maps->77\n d-maps->99\n" 8 | zygo> -------------------------------------------------------------------------------- /slides/slice.demo: -------------------------------------------------------------------------------- 1 | zygo> (def a [8 4 1 7]) 2 | [8 4 1 7] 3 | zygo> (hget a 1) 4 | 4 5 | zygo> (:3 a) 6 | 7 7 | zygo> 8 | -------------------------------------------------------------------------------- /slides/slice2.demo: -------------------------------------------------------------------------------- 1 | zygo> a 2 | [8 4 1 7] 3 | zygo> (aset a 0 12) 4 | zygo> a 5 | [12 4 1 7] 6 | zygo> (assert (== %default (:99 a %default))) 7 | zygo> (:99 a %default) // get %default back if 99 is out-of-bounds 8 | default 9 | zygo> (append a [77 88]) 10 | [12 4 1 7 [77 88]] // todo: needs append with ... 11 | zygo> a 12 | [12 4 1 7] 13 | zygo> 14 | -------------------------------------------------------------------------------- /slides/snoopy.interact: -------------------------------------------------------------------------------- 1 | (def he (hellcat speed:567)) 2 | (def ho (hornet)) 3 | (def snoop (snoopy chld:he)) 4 | 5 | (def snoop (snoopy pack:[8 9 4])) 6 | 7 | (def he (hellcat speed:567)) 8 | (def ho (hornet SpanCm:12)) 9 | (def snoop (snoopy carrying:[he ho])) 10 | 11 | (def he (hellcat speed:567)) 12 | (def ho (hornet SpanCm:12)) 13 | (def snoop (snoopy friends:[he ho])) 14 | 15 | (def he (hellcat speed:567)) 16 | (def ho (hornet SpanCm:12)) 17 | (def snoop (snoopy friends:[he ho] cry:"yowza")) 18 | 19 | (_method snoop GetCry: ) 20 | 21 | (_method snoop Fly: (weather time:(now) size:12 ` + 22 | `type:"sunny" details:(raw "123"))) 23 | -------------------------------------------------------------------------------- /slides/snoopy.methods: -------------------------------------------------------------------------------- 1 | 2 | type Snoopy struct { 3 | Plane `json:"plane" msg:"plane"` 4 | Cry string `json:"cry" msg:"cry"` 5 | Pack []int `json:"pack"` 6 | Carrying []Flyer `json:"carrying"` 7 | } 8 | 9 | type Wings struct { 10 | SpanCm int 11 | } 12 | 13 | type Plane struct { 14 | Wings 15 | Speed int `json:"speed" msg:"speed"` 16 | Chld Flyer `json:"chld" msg:"chld"` 17 | Friends []Flyer `json:"friends"` 18 | } 19 | 20 | -------------------------------------------------------------------------------- /slides/snoopy.struct: -------------------------------------------------------------------------------- 1 | 2 | type Snoopy struct { 3 | Plane `json:"plane" msg:"plane"` 4 | Cry string `json:"cry" msg:"cry"` 5 | Pack []int `json:"pack"` 6 | Carrying []Flyer `json:"carrying"` 7 | } 8 | -------------------------------------------------------------------------------- /slides/snoopy1: -------------------------------------------------------------------------------- 1 | // Go: 2 | type Plane struct { 3 | Number int 4 | Friends []Flyer 5 | } 6 | type Weather struct { 7 | Type string // "sunny", "stormy", ... 8 | } 9 | type Flyer interface { 10 | Fly(w *Weather) (string, error) // flight is informed by the current Weather 11 | } 12 | type Snoopy struct { 13 | Plane 14 | Cry string 15 | } 16 | func (p *Snoopy) Fly(w *Weather) (string, error) { 17 | w.Type = "VERY " + w.Type // side-effect, for demo purposes 18 | s := fmt.Sprintf("Snoopy sees weather '%s', cries '%s'", w.Type, p.Cry) 19 | fmt.Println(s) 20 | for _, flyer := range p.Friends { flyer.Fly(w) } 21 | return s, nil 22 | } 23 | -------------------------------------------------------------------------------- /slides/snoopy2: -------------------------------------------------------------------------------- 1 | // Go: 2 | 3 | type Flyer interface { 4 | Fly(w *Weather) (s string, err error) 5 | } 6 | 7 | func (p *Snoopy) Fly(w *Weather) (s string, err error) { 8 | w.Type = "VERY " + w.Type // side-effect, for demo purposes 9 | s = fmt.Sprintf("Snoopy sees weather '%s', cries '%s'", w.Type, p.Cry) 10 | fmt.Println(s) 11 | for _, flyer := range p.Friends { 12 | flyer.Fly(w) 13 | } 14 | return s, err 15 | } 16 | 17 | -------------------------------------------------------------------------------- /slides/snoopy_ace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glycerine/zygomys/b4b68f48f7a30a4735fd668793703fd9283a2ac0/slides/snoopy_ace.png -------------------------------------------------------------------------------- /slides/unlang.notes: -------------------------------------------------------------------------------- 1 | 2 | * argument: use JSON/YAML/other static data-only language 3 | - meh. 4 | - just avoids the scripting problem, moving it elsewhere. 5 | - no opportunity to compile-down 6 | - painful to type JSON interactively 7 | - doesn't support exploratory analysis 8 | - DSLs awkward 9 | - no language design 10 | - I hate having to put double quotes around everything 11 | - will never support interaction with complex number types, bignums, matrices, tensors, etc. 12 | -------------------------------------------------------------------------------- /slides/unused.txt: -------------------------------------------------------------------------------- 1 | * Generate call tree 2 | 3 | .code calltree.txt 4 | 5 | * maxims 6 | 7 | - this is a toolbox (TIAT) 8 | - you have the tools (YHTT) 9 | - Client Before Server (CBS) 10 | - write tests first (a tiny language fragment counts) 11 | - CBS are ways of saying: TDD is the key to evolutionary extensions and sharing. The test suite tells you and others when you've broken earlier/others features; and when you've successfully integated a new feature. 12 | 13 | * origins 14 | 15 | - I started with Howard Mao's Glisp project. 16 | - Nicely segmented design, good foundation. 17 | - Some of that architecture remains (e.g. name of the env class is *Glisp). 18 | - Many new features (very different syntax and identifiers, Go style comments, true lexical scope, sandboxing, reflection, json/msgpack, record types, etc). 19 | - Very different feel and aims meant a whole new name. 20 | - I encourage you: start with zygomys, make something of your own. 2-clause BSD license. 21 | 22 | * non-goals 23 | 24 | - this is not a sales pitch for you to "use this product" 25 | - One language to rule them all? Not a goal 26 | 27 | // fold: left fold is a classic higher order function 28 | // 29 | // lst: pair list, the input 30 | // fun: processes one element in the list 31 | // acc: the accumulated result, the output 32 | // 33 | (defn foldl [lst fun acc] 34 | (cond // cond is zygo's if-then-else. 35 | (empty? lst) acc // return acc if input done. 36 | (foldl // else call ourselves recursively. 37 | (cdr lst) // cdr extracts the 2nd node from lst. 38 | fun 39 | (fun (car lst) acc)) // car extracts the head element 40 | ) 41 | ) 42 | 43 | Taken verbatim from [[https://github.com/glycerine/zygomys/blob/master/tests/closure.zy][ zygo's closure tests]]. 44 | 45 | [[https://en.wikipedia.org/wiki/Fold_(higher-order_function)]] 46 | 47 | // fold: right fold is a classic higher order function 48 | // 49 | // lst: pair list, the input 50 | // fun: processes one element in the list 51 | // acc: the accumulated result, the output 52 | // 53 | (defn foldr [lst fun acc] 54 | (cond // cond is zygo's if-then-else. 55 | (empty? lst) acc // return acculated output if no more input. 56 | (fun // else call fun on the head of lst 57 | (car lst) 58 | (foldr (cdr lst) fun acc)) // recursive call on the tail of the input 59 | ) 60 | ) 61 | 62 | 63 | * why use an interpreter 64 | - high personal productivity (examples: python, javascript, Matlab, R, Mathematica, lisp, scheme) 65 | - fast feedback 66 | - essential for exploratory data analysis 67 | - script your game/application 68 | - become a language designer 69 | - DSL creation: model a complex/dynamic problem, configure a complex/dynamic solution 70 | - fun to write 71 | - experiment with design 72 | 73 | 74 | * side-effects 75 | 76 | * perspective on TDD 77 | 78 | - test-driven design is incredibly powerful at bringing up cross-layer issues. 79 | - no where more apparent than in a very layered design like an interpreter/compiler. When you make a small language change in the lexer/parser, the test suite will tell what/if you've broken anything else. 80 | - easily one of the most important techniques I've ever learned 81 | - feel like you have super powers 82 | 83 | - interpreter work really taught me the power of TDD 84 | - TDD is not a test technique, its an design technique 85 | 86 | - "To specify the server, write the client before the server." 87 | 88 | * components 89 | 90 | - s-expression: lexer and parser 91 | - unification (for type-system and other); this is how parametric polymorphism could be implemented. 92 | - repl. Read-eval-print-loop. an interactive prompt. 93 | 94 | 95 | 96 | 97 | * goals: learning and fun 98 | 99 | - programming languages are fun! 100 | - Lisp shows us: they don't have to be difficult to write 101 | - particularly great context to learn about Test-driven design 102 | - writing tests is trivial: write little language fragments 103 | 104 | * fun 105 | 106 | - Great for learning about interpreters, compilers, langauge structure. 107 | - The whole project is a playground for experimentation. Evolve a design. 108 | - I'll show you the architecture; take it and explore, try new stuff. 109 | 110 | * overview 111 | 112 | - explain overall architecture 113 | - explain how to add a feature 114 | - explain debug tools 115 | - how to see what is happening 116 | 117 | * if time 118 | 119 | - I'll show the Go API/interface 120 | - how to extend your language with a new function. 121 | - lots of examples on github; the `github.com/gycerine/zygomys/repl/` directory is the central place. 122 | 123 | 124 | * plain (hash), a hashmap constructor, is available without a prior (defmap recordName) call 125 | 126 | .code hash.demo 127 | 128 | -------------------------------------------------------------------------------- /slides/use.first: -------------------------------------------------------------------------------- 1 | zygo> (first [5 6 7]) 2 | 5 3 | zygo> (first (list "hi" "there")) 4 | "hi" 5 | zygo> -------------------------------------------------------------------------------- /slides/weather: -------------------------------------------------------------------------------- 1 | // Go: 2 | type Weather struct { 3 | Type string 4 | } 5 | -------------------------------------------------------------------------------- /tests/anonhash.zy: -------------------------------------------------------------------------------- 1 | 2 | // { symbol: ... } should create a (hash ), it is simply a 3 | // syntax shortcut that is similar to 4 | // JSON anonymous {} objects (aka hash maps). 5 | // Like Ruby, we allow {} to mean both basic block in infix, or hash map. 6 | 7 | // test nesting inside other structs/records/named maps 8 | (defmap castle) 9 | 10 | (def shortcut { a:10 b:12 c:14 d:771819 g:(castle h:7 j:10 q:(castle again:45)) }) 11 | 12 | (assert (hash? shortcut)) 13 | 14 | (assert (== 10 (:a shortcut))) 15 | (assert (== 7 shortcut.g.h)) 16 | 17 | (assert (== 45 shortcut.g.q.again)) 18 | 19 | (def g { `quotedstring`: 10 }) 20 | 21 | (assert (== {g["quotedstring"]} 10)) 22 | 23 | -------------------------------------------------------------------------------- /tests/append.zy: -------------------------------------------------------------------------------- 1 | (def a [2,3,4]) 2 | (append a 5) 3 | 4 | // appendslice is like using ... as in Go's append(x, xslice...) to 5 | // avoid getting [2, 3, 4, [5, 6]] when we want [2, 3, 4, 5, 6] 6 | // 7 | (def b (appendslice a [5,6])) 8 | (assert (== b [2,3,4,5,6])) 9 | -------------------------------------------------------------------------------- /tests/arrays.zy: -------------------------------------------------------------------------------- 1 | (def testarr [1 2 3 4 5 6]) 2 | 3 | (assert (== 3 (aget testarr 2))) 4 | (assert (== 1 (first testarr))) 5 | (assert (== [2 3 4 5 6] (rest testarr))) 6 | (aset testarr 1 0) 7 | (assert (== [1 0 3 4 5 6] testarr)) 8 | (assert (== [3 4] (slice testarr 2 4))) 9 | (assert (== [1 2 3] (append [1 2] 3))) 10 | (assert (== [0 1 2 3] (concat [0 1] [2 3]))) 11 | (assert (== 6 (len testarr))) 12 | (assert (== [%() %() %()] (makeArray 3))) 13 | (assert (== [0 0 0] (makeArray 3 0))) 14 | 15 | (let [a 0] 16 | (assert (== [a 3] (array 0 3)))) 17 | 18 | (assert (array? [1 2 3])) 19 | (assert (empty? [])) 20 | (assert (not (empty? [1]))) 21 | 22 | // colon used as shorthand for aget 23 | (def a [8 4 1 7]) 24 | (assert (== 8 (:0 a))) 25 | (assert (== 4 (:1 a))) 26 | (assert (== 1 (:2 a))) 27 | (assert (== 7 (:3 a))) 28 | 29 | (assert (== %default (:99 a %default))) 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/assign.zy: -------------------------------------------------------------------------------- 1 | // assignment 2 | (def a 10) 3 | // -- assignment through dotsym eliminated in favor of infix now. 4 | //(.a = 24) 5 | //(assert (== 24 a)) 6 | 7 | // assignment with := works 8 | (b := 2) 9 | (assert (== b 2)) 10 | 11 | (g:=2) 12 | (assert (== g 2)) 13 | -------------------------------------------------------------------------------- /tests/break.zy: -------------------------------------------------------------------------------- 1 | // simplest 2 | 3 | (def i 0) 4 | (for [() true ()] 5 | (cond (> i 5) (break) 6 | ()) 7 | (set i (+ i 1)) 8 | ) 9 | (assert (== i 6)) 10 | 11 | -------------------------------------------------------------------------------- /tests/char.zy: -------------------------------------------------------------------------------- 1 | // characters (runes) should be definable. 2 | (def b '%') // was crashing the lexer 3 | (def a 'A') 4 | (def n '\n') // was unrecognized 5 | (println (append (appendslice `hi ` n) "yoyo")) 6 | -------------------------------------------------------------------------------- /tests/chomp.zy: -------------------------------------------------------------------------------- 1 | (def s `hi 2 | `) 3 | (assert (== (len (chomp s)) 2)) 4 | -------------------------------------------------------------------------------- /tests/closure.zy: -------------------------------------------------------------------------------- 1 | // left-fold produces output list that is reversed from the lst input 2 | (defn foldl [lst fun acc] 3 | (cond 4 | (empty? lst) acc 5 | (foldl (cdr lst) fun (fun acc (car lst))) 6 | )) 7 | 8 | // right-fold preserves order of the lst in the acc output 9 | (defn foldr [lst fun acc] 10 | (cond 11 | (empty? lst) acc 12 | (fun (car lst) (foldr (cdr lst) fun acc)) 13 | )) 14 | 15 | (defn filter [lst fun] 16 | (foldl lst 17 | (fn [l x] 18 | (cond 19 | (fun x) (append l x) 20 | l)) 21 | []) 22 | ) 23 | 24 | (defn gmap [fun lst] 25 | (foldl lst (fn [l x] (fun x)) [])) 26 | 27 | (defn even? [x] 28 | (cond 29 | (number? x) 30 | (cond 31 | (float? x) false 32 | (== (bitAnd x 1) 0)) 33 | false 34 | )) 35 | 36 | (defn newStore [items] 37 | (letseq [ 38 | idx [-1] 39 | fun (fn [] 40 | (cond 41 | (< (+ (aget idx 0) 1) (len items)) 42 | (begin 43 | (aset idx 0 (+ (aget idx 0) 1)) 44 | (aget items (aget idx 0)) 45 | ) 46 | ())) 47 | ] fun)) 48 | 49 | 50 | (def evens (newStore [2 4 6 8 10])) 51 | 52 | (map (fn [x] 53 | (assert (== x (evens)))) 54 | (filter [1 2 3 4 5 6 7 8 9 10] even?) 55 | ) 56 | 57 | (def s (newStore [10 9 8 7 6 5 4 3 2 1])) 58 | (gmap (fn [x] (assert (== x (s)))) [10 9 8 7 6 5 4 3 2 1]) 59 | (def s (newStore [10 9 8 7 6 5 4 3 2 1])) 60 | (map (fn [x] (assert (== x (s)))) [10 9 8 7 6 5 4 3 2 1]) 61 | 62 | (def decending (newStore [10 9 8 7 6 5 4 3 2 1])) 63 | 64 | 65 | (def s (newStore [10 9 8 7 6 5 4 3 2 1])) 66 | 67 | (defn asserteq [a b] (cond (== a b) true 68 | (begin (printf "%v was != %v\n" a b) true))) 69 | 70 | (defn drainStore [] 71 | (let [ v (s) ] 72 | (cond 73 | (empty? v) (assert (== (decending) ())) 74 | ((begin 75 | (assert (== (decending) v)) 76 | (drainStore))))) 77 | ) 78 | 79 | 80 | (drainStore) 81 | 82 | (def a 1) 83 | (defn enclosure [] 84 | (let [func1 (fn [thunk] (let [a 2] (thunk))) 85 | func2 (fn [] (assert (== a 1)))] 86 | (func1 func2))) 87 | 88 | (enclosure) 89 | -------------------------------------------------------------------------------- /tests/closure2.zy: -------------------------------------------------------------------------------- 1 | // indendent invocations of same closure 2 | // should each get their own scope for that instantiation 3 | (defn newClosure [sym] (letseq [a 1 4 | f (fn [addme] (set a (+ a addme)) (printf "a is now %v\n" a) a)] f)) 5 | (def g (newClosure %hello_g)) 6 | (_closdump g) 7 | (def h (newClosure %hello_h)) 8 | (_closdump h) 9 | (_closdump g) // showing hello_h arg!!! 10 | (assert (== (g 1) 2)) 11 | // if they are mistakenly sharing the same closure variables. 12 | // then the increment on g will have impacted h as well. 13 | (assert (== (h 1) 2)) 14 | 15 | 16 | // but two functions that closed over the same variable should 17 | // share it! 18 | 19 | (defn factory [] 20 | (letseq [a [1] // a is shared by f and g! 21 | f (fn [addme] (aset a 0 (+ (aget a 0) addme)) (aget a 0)) 22 | g (fn [addme] (aset a 0 (+ (aget a 0) addme)) (aget a 0))] 23 | (list f g))) 24 | 25 | (def pair (factory)) 26 | (def one (first pair)) 27 | (def two (first (rest pair))) 28 | 29 | (assert (== 1 (one 0))) 30 | (assert (== 1 (two 0))) 31 | 32 | (assert (== 2 (one 1))) 33 | (assert (== 2 (two 0))) 34 | 35 | (assert (== 3 (two 1))) 36 | (assert (== 3 (one 0))) 37 | 38 | 39 | (defn m [inp] (letseq [a 2] 40 | (letseq [b 12] 41 | (defn q [] (cond (!= 0 inp) (set a inp) ()) (printf "a is now %v\n" a)) q))) 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/closure3.zy: -------------------------------------------------------------------------------- 1 | // closure3.zy https://github.com/glycerine/zygomys/issues/23 2 | // anonymous functions as closures. 3 | // 4 | 5 | (def res (let [x 123] (fn [] ((fn [] x))))) 6 | 7 | (assert (== (res) 123)) 8 | 9 | (def mypkg (package "mypkg" 10 | (defn Double [x] 11 | (+ x x)) 12 | (defn DoubleAll [xs] 13 | (println (Double 10)) 14 | (map (fn [x] (Double x)) xs)))) 15 | 16 | (def v [3, 4]) 17 | (def w (mypkg.DoubleAll v)) 18 | //error in __anon276:3: Error calling 'infix': Error calling 'map': symbol `Double` not found 19 | // assert that we get [6, 8] back. 20 | (assert (== (:0 w) 6)) 21 | (assert (== (:1 w) 8)) 22 | 23 | 24 | // check going through 3 functions 25 | (defn outer [x] (defn middle [] (defn inner [] x) inner) middle) 26 | (assert (== 7 (((outer 7))))) 27 | (assert (== 8 (((outer 8))))) 28 | 29 | // check that the let 'y' overrides the global 'y'. 30 | (def y 999) 31 | (assert (== 777 ((let [x 123] (fn [] (let [y 777] ((fn [] y)))))))) 32 | 33 | 34 | // the let 'x' should override the global 'x' 35 | (def x 1) 36 | (assert (== 10 ((let [x 3] (fn [] (let [y 7] ((fn [] (+ y x))))))))) 37 | 38 | // going through 4 functions to find 'x89' 39 | (defn superOuter [x89] (defn outer [] (defn middle [] (defn inner [] x89) inner) middle) outer) 40 | (assert (== 7 ((((superOuter 7)))))) 41 | 42 | // going through lots functions and lets to find 'x99' 43 | (defn ultimate [x99] (def ups -10) (let [stellar -2] (defn superDuper [] (let [z9 60] (defn outer [] (defn middle [] (def ups -8) (defn inner [] (+ z9 x99 stellar ups)) inner) middle) outer)) superDuper)) 44 | (assert (== 57 (((((ultimate 7))))))) 45 | -------------------------------------------------------------------------------- /tests/colonop.zy: -------------------------------------------------------------------------------- 1 | // colon operator works like aget on arrays 2 | (def a [4 5 6 7]) 3 | (assert (== 4 (:0 a))) 4 | 5 | // symbol access 6 | (def y 1) 7 | (assert (== 5 (:y a))) 8 | 9 | // function access 10 | (defn g [] 0) 11 | (defn f [] 3) 12 | (assert (== 4 (:(g) a))) 13 | (assert (== 7 (:(f) a))) 14 | 15 | // colon operator works like hget on hashes 16 | (def h (hash karin:9 heath:10)) 17 | (assert (== (:karin h) 9)) 18 | (assert (== (:heath h) 10)) 19 | 20 | // NB doesn't work because hash expects symbols as keys 21 | // (defn f [] %heath) 22 | // (assert (== (:(f) h) 10)) 23 | -------------------------------------------------------------------------------- /tests/comma.zy: -------------------------------------------------------------------------------- 1 | // commas are actual tokens, not just white space; 2 | // they build up an array 3 | {a,b,c = true,false,true} 4 | (assert a) 5 | (assert c) 6 | (assert (not b)) 7 | 8 | // array on RHS is okay 9 | {aa,bb,cc = [11, 22, 33]} 10 | (assert (== aa 11)) 11 | (assert (== bb 22)) 12 | (assert (== cc 33)) 13 | 14 | // array on LHS is not presently okay 15 | // [f,g,h] = [4,5,6] 16 | 17 | // (comma %a) should return [a], but was giving [] 18 | (comma %a) 19 | 20 | {a11,b22,j33 = %a ,4,"hi"} 21 | //re-assign within same types should be fine 22 | {b22,a11= 56,%hello} 23 | 24 | (assert (== (comma %a 3) [%a 3])) 25 | 26 | {a1,b2 = 1,2} 27 | // already bound, type mismatch: {a1,b2 = %hi, %there} 28 | -------------------------------------------------------------------------------- /tests/comments.zy: -------------------------------------------------------------------------------- 1 | 2 | /* (assert false) */ 3 | 4 | (assert true) // comments after tokens on a line are okay. 5 | // allow block comments too... 6 | /* 7 | (assert false) yo! 8 | */ 9 | (def r %(a b /*comment*/ 1 /*more*/)) // okay 10 | 11 | (defn f [] 1 /*one 12 | */ ) 13 | 14 | (/*1*/ def /*2*/ a /*3*/ 5 /*4*/) 15 | 16 | /*5*/ 17 | 18 | (def h (hash /*hi there*/ a: /*comm*/ true /*yo*/)) 19 | (assert (:a h)) 20 | 21 | (defn factory [] 22 | (letseq [a [1] // a is shared by f and g! 23 | ] a)) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/comparisons.zy: -------------------------------------------------------------------------------- 1 | (assert (< 0 1)) 2 | (assert (< 3.2 4.7)) 3 | (assert (> 1.6 -3.1)) 4 | (assert (== 1.1 1.1)) 5 | (assert (<= 1 1)) 6 | (assert (>= 1 1)) 7 | 8 | (assert (< "a" "b")) 9 | (assert (> 'b' 'a')) 10 | (assert (< "abc" "abcd")) 11 | 12 | (assert (< %(1 2 3) %(1 2 4))) 13 | 14 | 15 | (assert (!= %(1 2 3) %(1 2 4))) 16 | (assert (== %(1 2 3) %(1 2 3))) 17 | -------------------------------------------------------------------------------- /tests/continue.zy: -------------------------------------------------------------------------------- 1 | // break and continue 2 | (def sum 0) 3 | (for [(def i 0) (< i 10) (def i (+ i 1))] 4 | (set sum (+ sum i)) 5 | //(printf "\n wwwwwwhat: i = %d\n" i) 6 | (cond (> i 2) (break) 7 | (continue)) 8 | (set sum (+ sum 1000)) // check continue works 9 | ) 10 | (printf "sum is %d\n" sum) 11 | (assert (== sum 6)) 12 | 13 | -------------------------------------------------------------------------------- /tests/controlflow.zy: -------------------------------------------------------------------------------- 1 | (assert (not (and false false))) 2 | (assert (not (and true false))) 3 | (assert (not (and false true))) 4 | (assert (and true true)) 5 | (assert (== 0 (and 1 0))) 6 | (assert (== 0 (and 0 1))) 7 | (assert (== 2 (and 1 2))) 8 | 9 | (assert (not (or false false))) 10 | (assert (or false true)) 11 | (assert (or true false)) 12 | (assert (or true true)) 13 | (assert (== 1 (or 0 1))) 14 | (assert (== 1 (or 1 0))) 15 | (assert (== 1 (or 1 2))) 16 | 17 | (assert (== %a 18 | (cond true %a %b))) 19 | 20 | (assert (== %b 21 | (cond false %a %b))) 22 | 23 | (assert (== %c 24 | (cond 25 | false %a 26 | false %b 27 | %c))) 28 | -------------------------------------------------------------------------------- /tests/coroutines.zy: -------------------------------------------------------------------------------- 1 | // warning: the race detector fires on (go (send ch %foo)) currently. 2 | // So we must comment this out while checking for other races. 3 | // 4 | // In fact, we should deprecate using the co/goroutines via (go) 5 | // because it is not goroutine safe. Comment this out in preparation for that. 6 | /* 7 | (def ch (makeChan)) 8 | 9 | // test that channels and symbol translation are working 10 | (go (send ch %foo)) 11 | (assert (== %foo () 49 | // notice that we do field: while clojure does :field 50 | // 51 | (defmap bunkhouse) 52 | (hset lazy8 bunk1:(bunkhouse bed1:"Howie" bed2: "Mamie")) 53 | (assert (== (-> lazy8 bunk1: bed2:) "Mamie")) 54 | 55 | (defmap closet) 56 | (hset (:bunk1 lazy8) closet1:(closet broom:"Nimbuz2")) 57 | (assert (== (-> lazy8 bunk1: closet1: broom:) "Nimbuz2")) 58 | (assert (== (-> lazy8 bunk1:closet1:broom:) "Nimbuz2")) 59 | 60 | // delete and print should be okay 61 | (def h (hash key1:"val1" key2:222)) 62 | (hget h key1:) 63 | (hset h key3:(list 5 6 7)) 64 | (hget h key3:) 65 | h 66 | (hdel h key2:) 67 | h 68 | 69 | // hashes should accept arrays as indexes 70 | (hset h [0 0] %a) 71 | (hset h [0 1] %b) 72 | (hset h [1 0] %c) 73 | (hset h [1 1] %d) 74 | (assert (== {h[0 0]} %a)) 75 | (hset h [6] %e) 76 | (assert (== {h[6] } %e)) 77 | 78 | {h2=(hash [4 4]:%slicekey)} 79 | (hash [4 4]:%slicekey) 80 | 81 | // calling functions defined in hashes should work 82 | {h.b = (fn [] 42)} 83 | (assert (== (h.b) 42)) 84 | 85 | -------------------------------------------------------------------------------- /tests/hof.zy: -------------------------------------------------------------------------------- 1 | (defn add3 [a] (+ a 3)) 2 | 3 | (assert (== %(4 5 6) (map add3 %(1 2 3)))) 4 | (assert (== [4 5 6] (map add3 [1 2 3]))) 5 | 6 | (assert (== (apply (fn [a b] (* 2 a b)) [1 2]) 4)) 7 | (assert (== (apply (fn [a b] (* 2 a b)) %(1 2)) 4)) 8 | -------------------------------------------------------------------------------- /tests/if.zy: -------------------------------------------------------------------------------- 1 | // infix if and if-else 2 | 3 | (assert (== (infixExpand {if 1 == 2 { 3 } else { 4 }}) (quote (quote (cond (== 1 2) (infix [3]) (infix [4])))))) 4 | (assert (== (str (infixExpand {if 1 == 2 { 3 }})) (str (quote (quote (cond (== 1 2) {3} nil)))))) 5 | 6 | (assert (== (infixExpand { a = 10; b = 12 }) (quote (quote (set a 10) (set b 12))))) 7 | 8 | (infixExpand { a = 10; b = 0; if a < 9 { b++ } else { b += 10 }}) 9 | 10 | (infixExpand { a = 10; b = 0; { if a < 9 { b++ } else { b += 10 } (assert (== b 10)) }}) 11 | 12 | // smallest example of the first if-parse problem, now fixed. Semi-colons were not being ignored 13 | // at the end of an expression. 14 | (assert (== (str (infixExpand { a=10; if a < 9 56 })) (str (quote (quote (set a 10) (cond (< a 9) 56 nil)))))) 15 | 16 | // manually inserting semi-colons will fix the lost-else problem: 17 | (assert (== (str (infixExpand { a = 10; b = 0; if a < 9 { b++ } else { b += 10 } ; (assert (== b 10)) ; if a > 9 { b++ }; (assert (== b 11)) })) "(quote (set a 10) (set b 0) (cond (< a 9) (infix [b ++]) (infix [b += 10])) (assert (== b 10)) (cond (> a 9) (infix [b ++]) nil) (assert (== b 11)))")) 18 | 19 | { a=10; if a > 9 56 else 67 } // okay 20 | 21 | // if-then-else-if-the-else works: 22 | (assert (== "the 4th alt" {if false { print "printing on false"} else if false { "printing the else-on-true" } else if false {} else { "the 4th alt" }})) 23 | 24 | // the lost else problem: 25 | 26 | // this next omits the else terms when parsing, and the 2nd then. 27 | // adding in the semi-colons manually fixes the lost else: 28 | // works: 29 | { a = 10; b = 0; if a < 9 { b++ } else { b += 10 } ; (assert (== b 10)) ; if a > 9 { b++ }; (assert (== b 11)) } 30 | 31 | (assert (== (str (infixExpand { a = 10; b = 0; if a < 9 { b++ } else { b += 10 } ; (assert (== b 10)) ; if a > 9 { b++ }; (assert (== b 11)) })) "(quote (set a 10) (set b 0) (cond (< a 9) (infix [b ++]) (infix [b += 10])) (assert (== b 10)) (cond (> a 9) (infix [b ++]) nil) (assert (== b 11)))")) 32 | // mis-parses, bad, lost else: 33 | { a = 10; b = 0; if a < 9 { b++ } else { b += 10 } (assert (== b 10)) ; if a > 9 { b++ } (assert (== b 11)) } 34 | 35 | // this next omits the else terms when parsing, or the 2nd then 36 | { a = 10; 37 | b = 0; 38 | if a < 9 { 39 | b++ 40 | } else { 41 | b += 10 42 | } 43 | (assert (== b 10)) 44 | 45 | if a > 9 { 46 | b++ 47 | } 48 | (assert (== b 11)) 49 | } 50 | -------------------------------------------------------------------------------- /tests/if2.zy: -------------------------------------------------------------------------------- 1 | { a = 10; 2 | b = 0; 3 | if a < 9 { 4 | b++ 5 | } else { 6 | b += 10 // either this was getting lost 7 | } 8 | (assert (== b 10)) // or this was getting lost 9 | 10 | if a > 9 { 11 | b++ 12 | } 13 | (assert (== b 11)) 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tests/import.zy: -------------------------------------------------------------------------------- 1 | // define import analogously to Go: 2 | // as a define and sourcing a package from a path 3 | (import k "tests/prepackage") 4 | 5 | (assert (== (k.Funky "yipee") "yipee roverDog chases cat")) 6 | (assert (== k.Kit "cat")) 7 | 8 | (import "tests/prepackage") // default to helloKit package name 9 | 10 | (assert (== (helloKit.Funky "yipee") "yipee roverDog chases cat")) 11 | (assert (== helloKit.Kit "cat")) 12 | -------------------------------------------------------------------------------- /tests/inc.g: -------------------------------------------------------------------------------- 1 | 2 | (defn simple [] "from include") 3 | 4 | (source "./tests/inc1.g") 5 | -------------------------------------------------------------------------------- /tests/inc1.g: -------------------------------------------------------------------------------- 1 | 2 | (defn simple1 [] "from include 1") 3 | -------------------------------------------------------------------------------- /tests/inc2.g: -------------------------------------------------------------------------------- 1 | // include2 2 | 3 | (defn inc2 [] "from inc2.g") 4 | 5 | (include "./tests/inc3.g") 6 | 7 | -------------------------------------------------------------------------------- /tests/inc3.g: -------------------------------------------------------------------------------- 1 | // include 3 2 | 3 | (defn inc3 [] "from inc3.g") 4 | -------------------------------------------------------------------------------- /tests/include.zy: -------------------------------------------------------------------------------- 1 | 2 | // (println "Here in base ") 3 | 4 | // recursively sources inc1.g as well 5 | (source "./tests/inc.g") 6 | 7 | // (println "Calling function defined in inc.g") 8 | 9 | (assert (== (simple) "from include")) 10 | 11 | // (println "Calling function defined in inc1.g") 12 | 13 | (assert (== (simple1) "from include 1")) 14 | 15 | // (println (simple)) 16 | // (println (simple1)) 17 | 18 | // recursively includes inc3.g as well 19 | (include "./tests/inc2.g") 20 | 21 | (assert (== (inc2) "from inc2.g")) 22 | (assert (== (inc3) "from inc3.g")) 23 | 24 | // check that we are impacting the global env with our includes 25 | // and confirm that req macro works too. req is same as source 26 | // but uses a macro so you don't have to put double quotes around 27 | // the path. 28 | (def a 10) 29 | (source "tests/incr.g") 30 | (source "tests/incr.g") 31 | (assert (== a 12)) 32 | -------------------------------------------------------------------------------- /tests/incr.g: -------------------------------------------------------------------------------- 1 | (def a (+ a 1)) 2 | -------------------------------------------------------------------------------- /tests/indepf.zy: -------------------------------------------------------------------------------- 1 | // indendent invocations of same closure 2 | // should each get their own scope for that instantiation 3 | (defn newClosure [] (letseq [a 1 4 | f (fn [addme] (set a (+ a addme)) (printf "a is now %v\n" a) a)] f)) 5 | (def g (newClosure)) 6 | (def h (newClosure)) 7 | (assert (== (g 1) 2)) 8 | // if they are mistakenly sharing the same closure variables. 9 | // then the increment on g will have impacted h as well. 10 | (assert (== (h 1) 2)) 11 | -------------------------------------------------------------------------------- /tests/infix.zy: -------------------------------------------------------------------------------- 1 | // infix will be contained within {} 2 | (assert (== "hash" (type? (macexpand {})))) 3 | (assert (== "nil" (str (infix)))) 4 | 5 | (assert (== {3 + 4} 7)) 6 | 7 | {3 + 4} 8 | 9 | (assert (== {9 - 8} 1)) 10 | 11 | (assert (== { 9 - {1 - 3}} 11)) 12 | 13 | (assert (== {4 + 2 * 3} 10)) 14 | 15 | (assert (== {4 - 4 / 2} 2)) 16 | 17 | (assert (== {3 * 2 ** 3} 24)) 18 | 19 | // and can mix in function calls to infix expressions 20 | (defn f [] 7) 21 | (assert (== -1020 {4 - 2 ** {3 + (f)}})) 22 | 23 | (assert (== {true and not false} {not false and true and true})) 24 | 25 | // can put infix as arguments to sexp calls 26 | (defn add [a b] (+ a b)) 27 | (assert (== (add {4 + 1} {6 - 1}) 10)) 28 | 29 | //(def a [3 4 5]) 30 | //(assert (== "4" (str {a[1]}))) 31 | //(assert {a[1] == 4}) 32 | 33 | { newvar = 3} 34 | (assert (== newvar 3)) 35 | 36 | (assert (== {5 mod 3} 2)) 37 | 38 | {newvar++} 39 | (assert (== newvar 4)) 40 | {newvar--} 41 | (assert (== newvar 3)) 42 | 43 | // pow should be right associative 44 | (assert (== {2 ** 3 ** 3} 134217728)) 45 | 46 | // lack of spacing between builtin operators should not matter, 47 | // expect that -1 is preferred over subtract 1 so 6-1 won't parse. 48 | (assert (== (add {4+1} {6 - 1}) 10)) 49 | 50 | // debug help: infixExpand shows the conversion from infix to s-expression 51 | (assert (== "(quote (set a 4))" (str (infixExpand { a = 4})) )) 52 | 53 | (assert (== {4/10} 0.4)) 54 | 55 | // comparisons 56 | (assert (== true { 2 < 3})) 57 | (assert (== true { 4 > 2})) 58 | 59 | (assert (== true { 2 <= 2})) 60 | (assert (== false { 3 <= 2})) 61 | (assert (== true { 2 >= 2})) 62 | (assert (== false { 1 >= 2})) 63 | 64 | (assert (== true { 1 == 1})) 65 | (assert (== true { 1 != 0})) 66 | 67 | (assert (== "(quote (<= 2 3))" (str (infixExpand {2<=3})) )) 68 | (assert {2 < 3}) 69 | 70 | (def h (hash a:(hash b:[12 4 6]))) 71 | (assert (== {h.a.b[0]} 12)) 72 | // (quote (arrayidx h.a.b [0])) 73 | 74 | (infixExpand {h.a.b[2] = 99}) 75 | {h.a.b[2] = 99} 76 | (assert (== {h.a.b[2]} 99)) 77 | 78 | (infixExpand {}) 79 | 80 | // single assignment 81 | {g = 12} 82 | (assert (== g 12)) 83 | 84 | // multiple assignment 85 | //{a b = 5 6} 86 | //(infixExpand {a b = 5 6}) 87 | 88 | // was giving errors having a function call follow a semicolon: Error calling 'infix': LeftBindingPower: unhandled sx :&zygo.SexpPair{Head:(*zygo.SexpSymbol)(0xc82022d530), Tail:(*zygo.SexpPair)(0xc820241aa0)} 89 | // 90 | {a = 10; b = 12; d = 3; (println {a+b*d})} 91 | 92 | // mixed parses fine, and takes the value from the e at the end; the last expression. 93 | (assert (== 126 { (println "one"); (println "two"); a = 10; d = (+ 1 2); e = 100 + (+ a d {5 + 6}) + 2; (println e); e})) 94 | -------------------------------------------------------------------------------- /tests/infixAssign.zy: -------------------------------------------------------------------------------- 1 | 2 | // assignment using hashidx 3 | (def h (hash Mickey:"mouse")) 4 | 5 | // we should be able to create hashidx for 6 | // non-existent fields, about to be assigned: 7 | // (hashidx h .e) 8 | 9 | (set (hashidx h .e) 9) 10 | 11 | // assignment after array and dot selection: 12 | {q = [0 3 4 (hash a:%b c:%d)]} 13 | (arrayidx q [3]) 14 | (set (hashidx (arrayidx q [3]) .e) 9) 15 | {q} 16 | 17 | (hashidx {q[3]} .e) 18 | {q[3].e = 9} 19 | {q} 20 | (infixExpand {q[3].e = 9}) 21 | (infixExpand {q[3].e = 9}) 22 | // (quote (set (hget (arrayidx q [3]) .e) 9)) 23 | // or 24 | // (quote (set (hashidx (arrayidx q [3]) .e) 9)) 25 | (assert (== {q[3].e} 9)) 26 | 27 | -------------------------------------------------------------------------------- /tests/infixMixHashArray.zy: -------------------------------------------------------------------------------- 1 | 2 | // dot after array indexing 3 | (def arr [(hash a:(hash b:[3 4]))]) 4 | 5 | arr[0].a 6 | 7 | // was problem with hashidx: 8 | {arr[0].a.b[1]} 9 | // (arrayidx (hashidx (arrayidx arr [0]) .a.b) [1])) 10 | 11 | 12 | (hashidx (arrayidx arr [0]) .a.b) 13 | (printf ">>> %#v <<<" (hashidx (arrayidx arr [0]) .a.b)) 14 | (arrayidx (hashidx (arrayidx arr [0]) .a.b) [1]) 15 | 16 | 17 | 18 | (assert (== {arr[0].a.b[1]} 4)) 19 | (infixExpand {arr[0].a.b[1]}) 20 | 21 | (infixExpand {arr[0].a.b}) 22 | 23 | // simpler: using dot symbols as the key an hget call 24 | (def h (hash a:(hash b:[3 4] d:9))) 25 | {h.a.b[0]} 26 | (assert (== (hget h .a.b) [3 4])) 27 | 28 | (assert (== (* h.a.d) 9)) 29 | (assert (== {* h.a.d} 9)) 30 | //(infixExpand {h.a.b}) 31 | 32 | (assert (== {h.a.b[0] + 5} 8)) 33 | {j=1} 34 | (assert (== {h.a.b[j] + 5} 9)) 35 | (assert (== {h.a.b[j] * 5} 20)) 36 | 37 | (defn sq [x] {x*x}) 38 | // want this to give us 29, not 4 -- at the repl 39 | {(sq 5) + 4} 40 | 41 | (defn multi [x] { 42 | x = x + 1; 43 | x = x + 2; 44 | x = x + 3; 45 | }) 46 | 47 | (assert (== (multi 1) 7)) 48 | 49 | // should be able to set via dot symbol now 50 | 51 | {a=(hash b:2 c:3 d:4)} 52 | (set a.d 6) 53 | (assert (== a.d 6)) 54 | {a.d=8} 55 | (assert (== a.d 8)) 56 | //(infixExpand {a.d=6}) 57 | 58 | (set a.bebe 7) 59 | (assert (== a.bebe 7)) 60 | (set a.b 77) 61 | (assert (== a.b 77)) 62 | (expectError "Error generating (set a.b = 7):\nWrong number of arguments to set" (set a.b = 7)) // error, not panic 63 | 64 | {b=[(hash b:[(hash g:(hash h:[1 2 (hash i:[8 9]) [99 [101 102 (hash [1 7]:%one)]]]))])]} 65 | (assert (== {b[0].b[0].g.h[2].i[1]} 9)) 66 | (assert (== {b[0].b[0].g.h[3][1]} [101 102 (hash [1 7]:%one)])) 67 | (assert (== {b[0].b[0].g.h[3][1][0]} 101)) 68 | 69 | // we can intermix the has [1 1] access at the end, and 70 | // the arrayidx should still work: 71 | (assert (== {b[0].b[0].g.h[3][1][2][1 7]} %one)) 72 | 73 | (def f (hash a:(hash b:(hash c:(hash d:[(hash e:(hash f:8))]))))) 74 | (assert (== {f.a.b.c.d[0].e.f} 8)) 75 | -------------------------------------------------------------------------------- /tests/joinsym.zy: -------------------------------------------------------------------------------- 1 | (assert (== (joinsym %abc %def) %abcdef)) 2 | (assert (== (joinsym %abc %def) %abcdef)) 3 | (assert (== (joinsym [%abc %def]) %abcdef)) 4 | (assert (== (joinsym (list %abc %def)) %abcdef)) 5 | -------------------------------------------------------------------------------- /tests/json2.zy: -------------------------------------------------------------------------------- 1 | // the simple (json2) function displays 2 | // hashes in json, whereas the first (json) function returns 3 | // a raw byte stream with some meta data (field order, name of hash record) included. 4 | // 5 | // Our expressions lack commas, a deliberate, clean design choice. 6 | // JSON needs perfectly placed commas to parse (and no trailing commas) to parse. 7 | // 8 | (def j (source "tests/sample.json")) 9 | 10 | (def s (json2 j)) 11 | 12 | (assert (== s "{\"glossary\":{\"title\":\"example glossary\", \"GlossDiv\":{\"title\":\"S\", \"GlossList\":{\"GlossEntry\":{\"ID\":\"SGML\", \"SortAs\":\"SGML\", \"GlossTerm\":\"Standard Generalized Markup Language\", \"Acronym\":\"SGML\", \"Abbrev\":\"ISO 8879:1986\", \"GlossDef\":{\"para\":\"A meta-markup language, used to create markup languages such as DocBook.\", \"GlossSeeAlso\":[\"GML\", \"XML\"]}, \"GlossSee\":\"markup\"}}}}}")) 13 | 14 | -------------------------------------------------------------------------------- /tests/json_msgpack.zy: -------------------------------------------------------------------------------- 1 | // setup 2 | (defmap ranch) 3 | (def lazy8 (ranch cowboy:"Jim" cowgirl:"Jane" cows:["Zelda" "Montgommery" "Bartholomew"])) 4 | (assert (== (:cowboy lazy8) "Jim")) 5 | 6 | // serialize 7 | (def j (json lazy8)) 8 | 9 | // unserialize 10 | (def l9 (unjson j)) 11 | (assert (== (:cowboy l9) "Jim")) 12 | (assert (== (:cowgirl l9) "Jane")) 13 | l9 14 | 15 | // serialize msgpack 16 | (def m (msgpack lazy8)) 17 | 18 | // unserialize msgpack 19 | (def l10 (unmsgpack m)) 20 | (assert (== (:cowboy l10) "Jim")) 21 | (assert (== (:cowgirl l10) "Jane")) 22 | 23 | // symbols become strings 24 | (defmap castle) (unjson (json (castle cats:%loveit))) 25 | -------------------------------------------------------------------------------- /tests/label.zy: -------------------------------------------------------------------------------- 1 | // break/continue in for loop with label 2 | (def isum 0) 3 | (def jsum 0) 4 | (for outerLoop: [(def i 1) (< i 5) (++ i)] 5 | //(set isum (+ isum i)) 6 | (set isum (+ isum i)) 7 | 8 | (for innerLoop: [(def j 1) (< j 5) (++ j)] 9 | //(printf "i = %v j = %v\n" i j) 10 | (set jsum (+ jsum j)) 11 | //(printf " -- after inner increment, jsum is %v\n" jsum) 12 | (cond (> j 2) (continue outerLoop:) 13 | (and (> i 2) (> j 3)) (break outerLoop:) 14 | null) 15 | //(printf "advancing jsum %v -> %v\n" jsum (+ jsum 1000)) 16 | (set jsum (+ jsum 1000))// check continue works 17 | ) 18 | ) 19 | //(printf "isum is %d\n" isum) 20 | //(printf "jsum is %d\n" jsum) 21 | (assert (== isum 10)) 22 | (assert (== jsum 8024)) 23 | -------------------------------------------------------------------------------- /tests/late.zy: -------------------------------------------------------------------------------- 1 | // scopes working 2 | (defn f [] 23) 3 | (defn g [] 4 | (let [f (fn [] 11)] 5 | (assert (== (f) 11))) 6 | 45) 7 | g() 8 | 9 | // check for late binding 10 | (defn a [] (b)) 11 | (defn b [] (c)) 12 | (defn c [] 88) 13 | 14 | (defn c [] 33) 15 | (assert (== (a) 33)) 16 | -------------------------------------------------------------------------------- /tests/len.zy: -------------------------------------------------------------------------------- 1 | // list len 2 | (def a %(a b c d)) 3 | (assert (== (len a) 4)) 4 | (assert (== (len ()) 0)) 5 | (assert (== (len (rest a)) 3)) 6 | -------------------------------------------------------------------------------- /tests/lines: -------------------------------------------------------------------------------- 1 | one 2 | two 3 | thr 4 | -------------------------------------------------------------------------------- /tests/lists.zy: -------------------------------------------------------------------------------- 1 | (assert (== %(1 2 3) (cons 1 %(2 3)))) 2 | 3 | (assert (== 1 (first %(1 2 3)))) 4 | 5 | (assert (== %(2 3) (rest %(1 2 3)))) 6 | 7 | (assert (== 2 (first (rest %(1 2 3))))) 8 | 9 | (let [a 3] 10 | (assert (== %(0 3) (list 0 a)))) 11 | 12 | (assert (== %(1 2 4 5) (concat %(1 2) %(4 5)))) 13 | 14 | (assert (== %(1 2 4 5 7 8) (concat %(1 2) %(4 5) %(7 8)))) 15 | 16 | // test not-list pairs 17 | (assert (== %(1 \ 2) (cons 1 2))) 18 | (assert (== 2 (rest %(1 \ 2)))) 19 | (assert (not (list? %(1 \ 2)))) 20 | (assert (list? %())) 21 | (assert (list? %(1 2 3))) 22 | (assert (empty? %())) 23 | (assert (not (empty? %(1 2)))) 24 | -------------------------------------------------------------------------------- /tests/macexp.zy: -------------------------------------------------------------------------------- 1 | (defmac himac [a] (list 4 a a a a)) 2 | (def x (macexpand (himac 78))) 3 | (assert (== (str x) "(quote 4 78 78 78 78)")) 4 | -------------------------------------------------------------------------------- /tests/macros.zy: -------------------------------------------------------------------------------- 1 | (def l %(1 2 3)) 2 | (def b 4) 3 | 4 | (assert (== ^(0 ~@l ~b) %(0 1 2 3 4))) 5 | 6 | // note that we use ^ caret to start a template, 7 | // as opposed to the traditional lisp `` backtick. 8 | // This lets us use Go-style string literals that 9 | // are demarcated by backticks. 10 | (defmac when [predicate & body] 11 | ^(cond ~predicate 12 | (begin 13 | ~@body) %())) 14 | 15 | (assert (null? (when false %c))) 16 | (assert (== %a (when true %c %b %a))) 17 | 18 | (def h (hash a:1 b:2)) 19 | // check that arrays and hashes are getting scanned for ~ syntax unquotes. 20 | (defmac sizer [myHash] ^(let [n (len ~myHash) g (hash sz: (len ~myHash))] (+ n (:sz g)))) 21 | (assert (== (sizer h) 4)) 22 | 23 | // this shouldn't give an error, but it was: error in __main:5: Error on line 1: Unexpected end of input 24 | (defn greet [name] ^(hello ~name)) 25 | 26 | /* We rolled back the change that made issue 54 feature work: 27 | 28 | a) macros run can corrupt the datastack; 29 | b) the user didn't really need it badly; it was a corner case for them, 30 | and they said better to skip it. 31 | 32 | Since it is more important to have the Go/zygo script interface 33 | work, and this depends on datastack leaving the right values 34 | for Go to find, we have rolled back the change that enabled 35 | the issues/54 feature. So we also comment out this test. 36 | 37 | // zygomys/issues/54 (gensym) in defmac giving same symbol 38 | (assert (!= (gensym) (gensym))) 39 | (defmac aaa [] (gensym) ^()) 40 | (aaa) // was causing gensym not to increment env.nextsymbol 41 | (assert (!= (gensym) (gensym))) 42 | 43 | // zygomys/issues/54 corner case, user wants macros to be able to arbitrarily alter 44 | // the current environment. 45 | (def myfunc "") 46 | (def myout1 "") 47 | (def myout2 "") 48 | (defmac new1 [] (let [name (gensym "func")] (set myfunc name) ^(defn ~name [] (setq myout1 "function1")))) 49 | (defmac new2 [] (let [name (gensym "func")] (set myfunc name) ^(defn ~name [] (setq myout2 "function2")))) 50 | (new1) 51 | (def prevfunc myfunc) 52 | (printf "prevfunc is '%v'\n" (str prevfunc)) 53 | (new2) 54 | (printf "myfunc is now '%v'\n" (str myfunc)) 55 | // assert that two different myfunc names were generated by the two different gensym invocations. 56 | (assert (!= myfunc prevfunc)) 57 | 58 | */ 59 | -------------------------------------------------------------------------------- /tests/manual-leak-check.zy: -------------------------------------------------------------------------------- 1 | // check for scope leaks in the tail recursion 2 | (defn foldl [lst fun acc] 3 | (cond 4 | (empty? lst) acc 5 | (foldl (cdr lst) fun (fun (car lst) acc)) 6 | )) 7 | (defn f [a acc] (+ 1 acc)) 8 | 9 | (foldl %(a b c d e f g h i j) f 0) 10 | // check for any leftovers manually here 11 | -------------------------------------------------------------------------------- /tests/methodcall.zy: -------------------------------------------------------------------------------- 1 | // calls into Go produce expected results 2 | (def callres (_method (snoopy cry:"yeah!") Fly: (weather type:"awesomesauce"))) 3 | (assert (== callres 4 | ["Snoopy sees weather 'VERY awesomesauce', cries 'yeah!'" ()])) 5 | 6 | // structs coming back into zygo from Go 7 | (def w (weather type:"delightful" size:888)) 8 | (def c2 (_method (snoopy cry:"yeah!") EchoWeather: w)) 9 | (assert (== (str c2) `[ (weather time:nil size:888 type:"delightful" details:[]byte(nil))]`)) 10 | 11 | // passing in []byte to a method 12 | (def w (weather)) 13 | (assert (== (len (_method w MarshalMsg: (raw))) 2)) 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/methodls.zy: -------------------------------------------------------------------------------- 1 | //(defmap snoopy) 2 | (def s (snoopy)) 3 | (assert (== 4 | ["EchoWeather func(*zygo.Snoopy, *zygo.Weather) *zygo.Weather" "Fly func(*zygo.Snoopy, *zygo.Weather) (string, error)" "GetCry func(*zygo.Snoopy) string" "Sideeffect func(*zygo.Snoopy)"] 5 | (methodls s))) 6 | 7 | (def f (fieldls s)) 8 | (assert (== f ["Plane zygo.Plane" "Wings zygo.Wings embed-path" "SpanCm int embed-path" "ID int embed-path" "Speed int embed-path" "Chld zygo.Flyer embed-path" "Friends []zygo.Flyer embed-path" "Cry string" "Pack []int" "Carrying []zygo.Flyer"])) 9 | 10 | -------------------------------------------------------------------------------- /tests/msgpack-map.zy: -------------------------------------------------------------------------------- 1 | (def h (hash a:b:)) // check kw: symbols: should not crash 2 | 3 | (def recor (eventdemo id:"Id" persondemo: (persondemo first:"george" last:"washington"))) 4 | 5 | (assert (== (hget recor id:) "Id")) 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/multiple_assignment.zy: -------------------------------------------------------------------------------- 1 | (a b c = 1 2 3) 2 | (assert (== a 1)) 3 | (assert (== b 2)) 4 | (assert (== c 3)) 5 | 6 | (defn f [x y] (+ x y)) 7 | (r s t = (f 1 2) (f 3 4) (f 5 6)) 8 | (assert (== r 3)) 9 | (assert (== s 7)) 10 | (assert (== t 11)) 11 | -------------------------------------------------------------------------------- /tests/mux-function.zy: -------------------------------------------------------------------------------- 1 | (defn sub [f a b] (f a b)) 2 | (assert (== 1 (sub - 2 1))) 3 | -------------------------------------------------------------------------------- /tests/nan.zy: -------------------------------------------------------------------------------- 1 | (def a NaN) 2 | (def b NaN) 3 | 4 | // NaN != NaN needs to return true, but 5 | // NaN == NaN needs to return false. 6 | // 7 | // confirmed this is Go: 8 | // see https://stackoverflow.com/questions/1565164/what-is-the-rationale-for-all-comparisons-returning-false-for-ieee754-nan-values 9 | // 10 | // a := math.NaN() 11 | // b := math.NaN() 12 | // fmt.Printf("nan != nan = '%v'\n", a != b) // true 13 | // fmt.Printf("nan == nan = '%v'\n", a == b) // false 14 | // 15 | // match in zygo: 16 | 17 | (assert (!= a b)) 18 | (assert (== false (== a b))) 19 | 20 | (assert (!= NaN NaN)) 21 | (assert (!= nan nan)) 22 | 23 | (assert (== false (== NaN NaN))) 24 | (assert (== false (== NaN nan))) 25 | (assert (== false (== nan NaN))) 26 | (assert (== false (== nan nan))) 27 | 28 | 29 | (def c NaN) 30 | (def d NaN) 31 | 32 | 33 | (assert (!= c d)) 34 | 35 | // NaN should not equal some other number 36 | // Match the floating point behavior we see in Go: 37 | // 38 | // fmt.Printf("nan == 3.0 = '%v'\n", a == 3.0) // false 39 | // fmt.Printf("nan != 3.0 = '%v'\n", a != 3.0) // true 40 | // 41 | // fmt.Printf("nan == nan = '%v'\n", a == b) // false 42 | // 43 | // fmt.Printf("nan > nan = '%v'\n", a > b) // false 44 | // fmt.Printf("nan >= nan = '%v'\n", a >= b) // false 45 | // 46 | // fmt.Printf("nan < nan = '%v'\n", a < b) // false 47 | // fmt.Printf("nan <= nan = '%v'\n", a <= b) // false 48 | 49 | (assert (== false (== 3.0 nan))) 50 | (assert (!= 3.0 nan)) 51 | (assert (!= nan 4.0)) 52 | 53 | (assert (== false (== nan nan))) 54 | 55 | (assert (== false (> nan nan))) 56 | (assert (== false (>= nan nan))) 57 | 58 | (assert (== false (< nan nan))) 59 | (assert (== false (<= nan nan))) 60 | -------------------------------------------------------------------------------- /tests/newscope: -------------------------------------------------------------------------------- 1 | // source all the tests - interesting but fraught with peril 2 | // because tests can collide and stomp on each others variables. 3 | // Regular testing should prefer the testall.sh script herein for 4 | // this reason. It will run each test in its own zygo process. 5 | (def tests (nsplit (system `ls -1 tests/*.zy`))) 6 | (for [(def irunner 7) (< irunner (len tests)) (def irunner (+ irunner 1))] 7 | (def cur (aget tests irunner)) 8 | (cond (!= cur "new-scope") 9 | (begin 10 | (printf "starting %d '%s'\n" irunner cur) 11 | (new-scope (source cur)) 12 | (printf "done with %d '%s'\n" irunner cur)) 13 | ())) 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/nsplit.zy: -------------------------------------------------------------------------------- 1 | (assert (== "anonhash.zy" (first (nsplit (sys "ls tests/"))))) 2 | 3 | -------------------------------------------------------------------------------- /tests/numberkeys.zy: -------------------------------------------------------------------------------- 1 | // numbers should work as keys in hashes 2 | (def a (hash 0:4 2:5)) 3 | // avoid: error in __main:13: Error on line 1: Unrecognized atom: '0:' 4 | 5 | (assert (== 4 {a[0]})) 6 | -------------------------------------------------------------------------------- /tests/numbers.zy: -------------------------------------------------------------------------------- 1 | // test different ways of writing an integer 2 | (assert (== 24 0x18)) 3 | (assert (== 63 0o77)) 4 | (assert (== 13 0b1101)) 5 | 6 | // test shift operations 7 | (assert (== 4 (sll 1 2))) 8 | (assert (== -1 (sra -4 2))) 9 | (assert (== 2 (srl 4 1))) 10 | 11 | // bitwise operations 12 | (assert (== 0b0001 (bitAnd 0b0011 0b0101))) 13 | (assert (== 0b0111 (bitOr 0b0011 0b0101))) 14 | (assert (== 0b0110 (bitXor 0b0011 0b0101))) 15 | (assert (== 0b1100 (bitAnd (bitNot 0b0011) 0b1111))) 16 | 17 | // arithmetic 18 | (assert (== 5 (+ 3 2))) 19 | (assert (== 2.4 (* 2 1.2))) 20 | (assert (== 2 (mod 5 3))) 21 | (assert (== 1.5 (/ 3 2))) 22 | (assert (== 1.2e3 (* 1.2e2 10))) 23 | 24 | (def selection %(1 1.0 0 0.0)) 25 | 26 | (assert (== %(true true true true) (map number? selection))) 27 | (assert (== %(true false true false) (map int? selection))) 28 | (assert (== %(false true false true) (map float? selection))) 29 | (assert (== %(false false true true) (map zero? selection))) 30 | 31 | // exponentiation 32 | (assert (== 16 (** 2 4))) 33 | (assert (== 1024.0 (** 2.0 10.0))) 34 | 35 | // 1_000 underscores 36 | (assert (== 1_000 1000)) 37 | 38 | // +Inf, -Inf, and even - Inf with a space after the minus. 39 | (assert (== Inf (* -Inf - Inf))) 40 | (assert (== +Inf (* -Inf - Inf))) 41 | (assert (== inf (* -inf - inf))) 42 | (assert (== +inf (* -inf - inf))) 43 | -------------------------------------------------------------------------------- /tests/owrite.zy: -------------------------------------------------------------------------------- 1 | // owrite writes lines out to a file 2 | (def b (slurpf "tests/lines")) 3 | (owritef b "tests/lines2") 4 | 5 | // diff not in the assummed location on windows. 6 | (cond (== (GOOS) "windows") () 7 | (assert (== "" (system "/usr/bin/diff tests/lines tests/lines2")))) 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/package.zy: -------------------------------------------------------------------------------- 1 | // one can define a package directly, like this: 2 | (def hi (package "hello" 3 | { World := "earth"; (defn Myfun [x] (concat World x)) } 4 | )) 5 | 6 | // but now preferred is to use the (import) functionality, tested in import.zy 7 | 8 | (assert (== hi.World "earth")) 9 | (assert (== (hi.Myfun "yes") "earthyes")) 10 | 11 | // or one can define a package by sourcing another file: 12 | {p = (package "doggerel" (source "tests/pkghelp"))} 13 | 14 | (assert (== p.A.B 42)) 15 | (assert (== p.A.Dog "bella")) 16 | (assert (== (p.A.F) 54)) // we can call functions 17 | (assert (== (p.Assistor 9) (+ 9 42 3 7))) // functions can refer to package local values and functions 18 | 19 | // and test importing pre-made packages: 20 | {k := (source "tests/prepackage")} 21 | 22 | (assert (== (k.Funky "yipee") "yipee roverDog chases cat")) 23 | (assert (== k.Kit "cat")) 24 | 25 | // package visibility rules like in Go: lowercase => private, Uppercase => Public. 26 | (expectError "Error calling '+': Cannot access private member 'privetLane' of package 'helloKit'" (+ k.privetLane 1)) 27 | (expectError "Error calling 'infix': Cannot access private member 'privetLane' of package 'helloKit'" {k.privetLane = 1 }) 28 | 29 | // check that visibility rules apply to imports of imports, and that we don't infinite loop: 30 | (def outer (package "outerSpace" (def inner (source "tests/prepackage")))) 31 | (assert (== outer.inner.Kit "cat")) 32 | 33 | (expectError "Error calling 'concat': Cannot access private member 'privetLane' of package 'helloKit'" 34 | (concat "" outer.inner.privetLane)) 35 | 36 | (expectError "Error calling '+': Cannot access private member 'privetLane' of package 'helloKit'" (printf "the number is %v" (+ 0 outer.inner.privetLane))) 37 | 38 | // but that we can inspect package inside package: 39 | (expectError "" outer.inner) 40 | 41 | // verify that functions inside the package can access their own private data 42 | (assert (== (outer.inner.UsePriv) "my number is 42")) 43 | -------------------------------------------------------------------------------- /tests/pkghelp: -------------------------------------------------------------------------------- 1 | // helper file, used in package.zy to be the contents of a package 2 | (def A (hash B:42 Dog: "bella" F: (fn [] 54) g: (fn [x] (+ x 7)))) 3 | (defn Assistor [y] (+ y A.B (A.g 3))) 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/pointer.zy: -------------------------------------------------------------------------------- 1 | // to point to non-records in a persistent fashion. 2 | (var a int64) 3 | (ptr = (& a)) 4 | (assert (== 0 a)) 5 | (assert (== a 0)) 6 | 7 | (ptr2 = (&a)) 8 | (assert (== ptr ptr2)) 9 | 10 | 11 | (var s string) 12 | (assert (== s "")) 13 | (assert (== "" s)) 14 | 15 | (sptr = (& s)) 16 | &a 17 | &s 18 | 19 | (ptr2 = (& a)) 20 | ptr2 21 | 22 | // derefSet is a setter that is equivalent to *ptr = 1 in Go. 23 | (derefSet ptr 1) 24 | (assert (== 1 a)) 25 | 26 | // deref with only 1 argument is a getter; same as (* ptr) 27 | (assert (== 1 (deref ptr))) 28 | (assert (== 1 (deref ptr2))) 29 | (assert (== 1 (* ptr))) 30 | (assert (== 1 (* ptr2))) 31 | 32 | // set a string through a pointer 33 | (derefSet sptr "hiya") 34 | (assert (== s "hiya")) 35 | 36 | // cross type assignment doesn't type check 37 | (expectError "Error calling 'derefSet': type mismatch: value of type 'int64' is not assignable to type 'string'" (derefSet sptr 3)) 38 | (expectError "Error calling 'derefSet': type mismatch: value of type 'string' is not assignable to 'int64'" (derefSet ptr "a-string")) 39 | 40 | 41 | // set a struct through a pointer 42 | (struct Dog [ 43 | (field Name: string e:0) 44 | (field Number: int64 e:1) 45 | ]) 46 | (def d (Dog Name:"Rover")) 47 | 48 | (pdog = (& d)) 49 | (derefSet pdog (Dog Name:"Benicia")) 50 | (assert (== d.Name "Benicia")) 51 | 52 | (expectError "Error calling 'derefSet': cannot assign type 'Dog' to type 'string'" (derefSet sptr d)) 53 | (expectError "Error calling 'derefSet': type mismatch: value of type 'string' is not assignable to 'Dog'" (derefSet pdog "hi")) 54 | (derefSet pdog (Dog Name:"Rov2")) 55 | 56 | (struct Cat [(field Name:string)]) 57 | 58 | (expectError "Error calling 'derefSet': cannot assign type 'Cat' to type 'Dog'" 59 | (derefSet pdog (Cat Name:"meower"))) 60 | 61 | (var pcat (* Cat)) 62 | (expectError "Error calling 'derefSet': cannot assign type 'Cat' to type '*Cat'" 63 | (derefSet pcat (Cat Name:"James"))) 64 | 65 | (pcat = (& (Cat Name:"Earl"))) 66 | (assert (== (:Name (* pcat)) "Earl")) 67 | 68 | (expectError "Error calling 'derefSet': cannot assign type 'Dog' to type 'Cat'" 69 | (derefSet pcat (Dog Name:"barker"))) 70 | 71 | 72 | (def iii (& 34)) 73 | (derefSet iii 5) 74 | (assert (== (deref iii) 5)) 75 | 76 | (def sss (& "sad")) 77 | (derefSet sss "happy") 78 | (assert (== (* sss) "happy")) 79 | 80 | // derefSet doesn't work now... 81 | 82 | (def h (hash a:(& 1) b:2)) 83 | (derefSet (* h.a) 45) 84 | (assert (== (* (* h.a)) 45)) 85 | 86 | (def cat (Cat Name:"Claude")) 87 | (expectError "Error calling 'derefSet': derefSet only operates on pointers (*SexpPointer); we saw *zygo.SexpStr instead" 88 | (derefSet (:Name cat) "Jupiter")) 89 | 90 | (struct Kanga [(field roo: (* Cat))]) 91 | 92 | (def kanga (Kanga roo: (& cat))) 93 | (assert (== (:Name (*(* kanga.roo))) "Claude")) 94 | (def jup (Cat Name:"Jupiter")) 95 | (derefSet (:roo kanga) jup) 96 | (assert (== (:Name (*(* kanga.roo))) "Jupiter")) 97 | 98 | (def sn1 (snoopy of:"charlie")) 99 | (def sn2 (snoopy of:"sarah")) 100 | (psnoop = (& sn1)) 101 | (* psnoop) 102 | (derefSet psnoop sn2) 103 | -------------------------------------------------------------------------------- /tests/prepackage: -------------------------------------------------------------------------------- 1 | // a pre-made package 2 | (package "helloKit" 3 | (def Kit "cat") 4 | (def Rov "erDog") 5 | (def privetLane 42) 6 | (defn Funky [a] (concat a " rov" Rov " chases " Kit)) 7 | (defn UsePriv [] (concat "my number is " (str privetLane))) 8 | ) 9 | -------------------------------------------------------------------------------- /tests/printf.zy: -------------------------------------------------------------------------------- 1 | (assert (null? (printf "%x" 123))) 2 | -------------------------------------------------------------------------------- /tests/quotedsym.zy: -------------------------------------------------------------------------------- 1 | (set %y 10) 2 | (def %x 10) 3 | (assert (== (+ x y) 20)) 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/range.zy: -------------------------------------------------------------------------------- 1 | // range loop components: mdef and hpair 2 | 3 | // mdef 4 | (mdef a b c (list 4 5 6)) 5 | (assert (== a 4)) 6 | (assert (== b 5)) 7 | (assert (== c 6)) 8 | 9 | // hpair 10 | (def h (hash a:3 b:9 c:27 d:-4)) 11 | (assert (== (first (hpair h 0)) a:)) 12 | (assert (== (first (hpair h 1)) b:)) 13 | (assert (== (first (hpair h 2)) c:)) 14 | (assert (== (first (hpair h 3)) d:)) 15 | 16 | (assert (== (second (hpair h 0)) 3)) 17 | (assert (== (second (hpair h 1)) 9)) 18 | (assert (== (second (hpair h 2)) 27)) 19 | (assert (== (second (hpair h 3)) -4)) 20 | 21 | (defmac range [key value myHash & body] 22 | ^(let [n (len ~myHash)] 23 | (for [(def i 0) (< i n) (def i (+ i 1))] 24 | (begin 25 | (mdef (quote ~key) (quote ~value) (hpair ~myHash i)) 26 | ~@body)))) 27 | 28 | 29 | // verify that range over hashes works 30 | (def h (hash a:44 b:55 c:77 d:99)) 31 | (def s "") 32 | (range k v h (set s (concat s " " (str k) "-maps->" (str v)))) 33 | (assert (== s " a-maps->44 b-maps->55 c-maps->77 d-maps->99")) 34 | 35 | -------------------------------------------------------------------------------- /tests/raw.zy: -------------------------------------------------------------------------------- 1 | (assert (== (raw2str (raw `sup`)) "sup")) 2 | (def s `hi 3 | there 34 4 | symbols`) 5 | (assert (== s "hi\nthere 34\n symbols")) 6 | 7 | // long, multi-line raw strings, even in at the repl infix. 8 | // NB it turns out this doesn't actually test the 9 | // new feature. But if you entered this by hand 10 | // --without the {} curly braces, in the old repl-- 11 | // then you would see the difference. Hard to set 12 | // without a way to shift into that same mode. 13 | // Avoid this: 14 | // 15 | { s2 = ` 16 | 17 | 18 | `} 19 | (assert (== s2 "\n\n\n")) 20 | -------------------------------------------------------------------------------- /tests/recur.zy: -------------------------------------------------------------------------------- 1 | // recursive function definition 2 | (defn f [] 3 | (defn g [] 23) 4 | (defn h [] 77) 5 | (+ (g) (h))) 6 | 7 | (assert (== (f) 100)) 8 | -------------------------------------------------------------------------------- /tests/regexp.zy: -------------------------------------------------------------------------------- 1 | (letseq 2 | [re (regexpCompile "hello") 3 | loc (regexpFindIndex re "012345hello!")] 4 | (assert (== (aget loc 0) 6)) 5 | (assert (== (aget loc 1) 11)) 6 | (assert (== "hello" (regexpFind re "ahellob"))) 7 | (assert (regexpMatch re "hello")) 8 | (assert (not (regexpMatch re "hell")))) 9 | -------------------------------------------------------------------------------- /tests/rmsym.zy: -------------------------------------------------------------------------------- 1 | // rmsym removes a symbol from the local scope 2 | (assert (== `12` (newScope (def b 5) (newScope (def b 12) (str b) )))) 3 | (assert (== `5` (newScope (def b 5) (newScope (def b 12) (rmsym %b) (str b) )))) 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/sample.json: -------------------------------------------------------------------------------- 1 | { // line comment 0 2 | /*sneaky-0.5*/ "glossary": /*block-comment-1*/ { /*block-comment-2*/ /*block-comment-3*/ // line comment 5 3 | "title": "example glossary", /*block comment 6*/ // line comment 7 4 | "GlossDiv": { 5 | "title": "S", 6 | "GlossList": { 7 | "GlossEntry": { 8 | "ID": "SGML", 9 | "SortAs": "SGML", 10 | "GlossTerm": "Standard Generalized Markup Language", 11 | "Acronym": "SGML", 12 | "Abbrev": "ISO 8879:1986", 13 | "GlossDef": { 14 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 15 | "GlossSeeAlso": ["GML", "XML"] 16 | }, 17 | "GlossSee": "markup" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/sci.zy: -------------------------------------------------------------------------------- 1 | // scientific notation recognized and accepted 2 | (def a 1e-1) 3 | (def b 1E+1) 4 | (def z 1e1) 5 | (assert (== 1.0 (* a b))) 6 | (def y 4.5e10) 7 | (def y 4.5e-10) 8 | -------------------------------------------------------------------------------- /tests/scoping.zy: -------------------------------------------------------------------------------- 1 | (letseq [a 0 2 | b a] 3 | (assert (== b 0))) 4 | 5 | (let [a 5] 6 | (let [] 7 | (def a 6) 8 | (assert (== a 6))) 9 | (assert (== a 5))) 10 | 11 | (newScope 12 | (def b 12) 13 | (newScope 14 | (def b 99) 15 | (newScope 16 | (def b 7) 17 | (assert (== b 7))) 18 | (assert (== b 99))) 19 | (assert (== b 12))) 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/set.zy: -------------------------------------------------------------------------------- 1 | // should be able to set on the output of arrayidx 2 | (def ar [4 5 6]) 3 | (def i 2) 4 | (set (arrayidx ar [i]) 99) 5 | (assert {ar[i] == 99}) 6 | (= {ar[i]} 1010101) 7 | 8 | // series of infix ;-separated expressions should all be evaluated 9 | { b = 2; d = 4 } 10 | (assert {b+2 == d}) 11 | -------------------------------------------------------------------------------- /tests/setenv.zy: -------------------------------------------------------------------------------- 1 | (setenv "willy" "wonka") 2 | (def w (getenv "willy")) 3 | (assert (== w "wonka")) 4 | -------------------------------------------------------------------------------- /tests/sigils.zy: -------------------------------------------------------------------------------- 1 | // sigil system. allow sigils to 2 | // designate special symbols, to 3 | // represent various types of special 4 | // symbols: objects, type variables, grammar non-terminals, 5 | // etc. 6 | 7 | (#symbol = "I'm a sigil-symbol") 8 | (expectError "cannot assign int64 to string" (#symbol = 23)) 9 | ($symbol = "I'm a sigil-symbol") 10 | (?symbol = "I'm a sigil-symbol") 11 | 12 | // can a: always be a sigil-symbol, colon style, 13 | // so that it prints without extra rigamorole. 14 | (a: = 1) 15 | 16 | // sigils should eval to themselves 17 | (assert (== $a %$a)) 18 | 19 | -------------------------------------------------------------------------------- /tests/slurp.zy: -------------------------------------------------------------------------------- 1 | // slurp reads lines from a file. It ignores the newlines. 2 | (def b (slurpf "tests/lines")) 3 | (assert (== 3 (len b))) 4 | (def sum 0) 5 | (for [(def i 0) (< i (len b)) (def i (+ i 1))] 6 | (def cur (aget b i)) 7 | (set sum (+ sum (len cur))) 8 | (println cur)) 9 | (printf "sum is %v\n" sum) 10 | (assert (== sum 9)) 11 | 12 | -------------------------------------------------------------------------------- /tests/sourcer: -------------------------------------------------------------------------------- 1 | // source all the tests - interesting but fraught with peril 2 | // because tests can collide and stomp on each others variables. 3 | // Regular testing should prefer the testall.sh script herein for 4 | // this reason. It will run each test in its own zygo process. 5 | (def tests (nsplit (system `ls -1 tests/*.zy`))) 6 | (for [(def irunner 0) (< irunner (len tests)) (def irunner (+ irunner 1))] 7 | (def cur (aget tests irunner)) 8 | (printf "starting %d '%s'\n" irunner cur) 9 | (new-scope 10 | (source cur)) 11 | (printf "done with %d '%s'\n" irunner cur)) 12 | -------------------------------------------------------------------------------- /tests/split.zy: -------------------------------------------------------------------------------- 1 | (def testStr "this is my \n test str") 2 | 3 | // split on newline 4 | (assert (== 5 | "this is my " (first 6 | (split testStr "\n")))) 7 | 8 | // second index 9 | (assert (== 10 | " test str" (aget 11 | (split testStr "\n") 1))) 12 | 13 | // split on space 14 | (assert (== 15 | 6 (len 16 | (split testStr " ")))) 17 | 18 | // don't split on char that doesn't exist 19 | (assert (== 20 | 1 (len 21 | (split testStr ".")))) 22 | -------------------------------------------------------------------------------- /tests/str.zy: -------------------------------------------------------------------------------- 1 | // should be able to show function defintions, at 2 | // the repl and with (str) 3 | 4 | (def a (fn [] (println "a says hi"))) 5 | 6 | (assert (== (str a) "(fn [] (println \"a says hi\"))")) 7 | -------------------------------------------------------------------------------- /tests/strings.zy: -------------------------------------------------------------------------------- 1 | (assert (== "abc" (append "ab" 'c'))) 2 | (assert (== "abcd" (concat "ab" "cd"))) 3 | (assert (== "bc" (slice "abcd" 1 3))) 4 | (assert (== 'c' (sget "abcd" 2))) 5 | (assert (== 3 (len "abc"))) 6 | 7 | (assert (string? "asdfsdaf")) 8 | (assert (char? 'c')) 9 | (assert (symbol? %a)) 10 | 11 | // back tick quoted strings work too now. 12 | (assert (== (type? `hello`) "string")) 13 | 14 | (assert (== (type? `hello 15 | world 16 | with newlines 17 | inside`) "string")) 18 | -------------------------------------------------------------------------------- /tests/struct.zy: -------------------------------------------------------------------------------- 1 | // verify that struct assign typechecks 2 | (struct Dog [ 3 | (field Name: string e:0) 4 | (field Number: int64 e:1) 5 | ]) 6 | (def d (Dog Name:"Rover")) 7 | (def e (Dog Name:"Egbert")) 8 | 9 | (d = e) 10 | (assert (== d.Name "Egbert")) 11 | (expectError "cannot assign int64 to Dog" (e = 12)) 12 | (f = 4) 13 | (expectError "cannot assign Dog to int64" (f = d)) 14 | 15 | (struct Cat [(field Nip: string e:0)]) 16 | (c = (Cat Nip:"green")) 17 | 18 | (expectError "cannot assign Cat to Dog" (d = c)) 19 | (expectError "cannot assign Dog to Cat" (c = d)) 20 | 21 | (var cc Cat) 22 | (cc = c) 23 | 24 | // type checking the fields of a struct 25 | (expectError "Error calling 'Dog': field Dog.Name is string, cannot assign int64 '12'" (Dog Name:12)) 26 | (expectError `Error calling 'Dog': field Dog.Number is int64, cannot assign string '"hello"'` (Dog Number:"hello")) 27 | (expectError `Error calling 'Dog': Dog has no field 'NotHere' [err 2]` (Dog NotHere:"hello")) 28 | 29 | -------------------------------------------------------------------------------- /tests/symbols.zy: -------------------------------------------------------------------------------- 1 | (assert (string? (sym2str %abc))) 2 | (assert (== "abc" (sym2str %abc))) 3 | 4 | (assert (symbol? (str2sym "abc"))) 5 | (assert (== %abc (str2sym "abc"))) 6 | 7 | // require: longer version of req, to 8 | // illustrate a macro. The req macro in contrast is 9 | // defined in the zygo, so it is always 10 | // available. 11 | (defmac require [sympath] 12 | ^(source (sym2str (quote ~sympath)))) 13 | -------------------------------------------------------------------------------- /tests/syntax-quote.zy: -------------------------------------------------------------------------------- 1 | (def a 7) 2 | (def x ^[(hash g:((hash b:[~a])))]) 3 | (assert (== (str x) "[(hash g ((hash b [7])))]")) 4 | -------------------------------------------------------------------------------- /tests/system.zy: -------------------------------------------------------------------------------- 1 | (def w (trim (system "echo welcome home"))) 2 | (assert (== w "welcome home")) 3 | -------------------------------------------------------------------------------- /tests/tailcallresid.zy: -------------------------------------------------------------------------------- 1 | // tail calling was leaving residual lexical scopes on the 2 | // linear (call) stack, that would cause the func driveIt 3 | // decl below to fail to lookup int64... check for this. 4 | 5 | (defn factTc [n accum] 6 | (cond (== n 0) accum 7 | (let [newn (- n 1) 8 | newaccum (* accum n)] 9 | (factTc newn newaccum)))) 10 | 11 | (factTc 5 1) 12 | 13 | (func driveIt [a:int64 b:string] [n:int64 err:error]) 14 | -------------------------------------------------------------------------------- /tests/tailrecur.zy: -------------------------------------------------------------------------------- 1 | // tail recursion 2 | 3 | // check for scope leaks in the tail recursion 4 | (defn foldl [lst fun acc] 5 | (cond 6 | (empty? lst) acc 7 | (foldl (cdr lst) fun (fun (car lst) acc)) 8 | )) 9 | (defn f [a acc] (+ 1 acc)) 10 | 11 | (foldl %(a b c d e f g h i j) f 0) 12 | 13 | 14 | (defn recursivecall [a & b] 15 | (cond (null? b) a (recursivecall a))) 16 | 17 | (assert (== 100 (recursivecall 100))) 18 | (assert (== 100 (recursivecall 100 "any"))) 19 | -------------------------------------------------------------------------------- /tests/testall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | for lispfile in tests/*.zy 4 | do 5 | zygo -demo -exitonfail "${lispfile}" || (echo "${lispfile} failed" && exit 1) 6 | echo "${lispfile} passed" 7 | done 8 | echo 9 | echo "good: all tests/ scripts passed." 10 | -------------------------------------------------------------------------------- /tests/timeit.zy: -------------------------------------------------------------------------------- 1 | // timeit shows time to execute 2 | // a function 10_000 times (up to 10 seconds) by 3 | // default; or 2nd argument can 4 | // specify iteration count with no timeout. 5 | (timeit (fn [] (def a 0) 6 | (for [(def i 0) (< i 100) (def i (+ i 1))] { 7 | a = a + 1 8 | })) 9 | 1 // iteration count 10 | ) 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/type.zy: -------------------------------------------------------------------------------- 1 | (defn a [] "hi") 2 | (assert (== (type? a) "func")) 3 | (assert (== (type? 123) "int64")) 4 | (assert (== (type? 5.5) "float64")) 5 | (assert (== (type? (hash)) "hash")) 6 | (assert (== (type? []) "array")) 7 | (assert (== (type? ()) "nil")) 8 | (assert (== (type? %(%a \ %b)) "list")) 9 | (assert (== (type? true) "bool")) 10 | -------------------------------------------------------------------------------- /tests/typelist.zy: -------------------------------------------------------------------------------- 1 | // typelist should have some basic types in it 2 | (tl = (typelist)) 3 | (found = false) 4 | (range k v tl 5 | (cond (== v "int64") 6 | (begin 7 | (set found true) 8 | (printf "found int64 at pos %v\n" k) 9 | (break)) 10 | nil)) 11 | 12 | (assert found) 13 | -------------------------------------------------------------------------------- /tests/uint64.zy: -------------------------------------------------------------------------------- 1 | // Max uint64 value, will overflow an int64 2 | {x := 18446744073709551615ULL} 3 | 4 | (assert (== 0ULL (+ 1ULL x))) // wrap around 5 | 6 | // hex 7 | {h := 0xffULL } // == 255 decimal 8 | 9 | // octal 10 | {o := 0o70ULL } // == 56 decimal 11 | 12 | // equality and addition work 13 | (assert (== 311ULL (+ h o))) 14 | 15 | // subtraction works 16 | (assert (== 199ULL (- h o))) 17 | 18 | // multiplication 19 | (assert (== 14280ULL (* h o))) 20 | 21 | {d := 0o377ULL} 22 | // asUint64 conversion 23 | (assert (== d (asUint64 {3*64 + 7*8 + 7}))) // == 255 decimal 24 | 25 | // type 26 | (assert (== (type? h) "uint64")) 27 | (assert (== (type? d) "uint64")) 28 | 29 | // division 30 | (assert (== 1ULL (/ h d))) 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/users.zy: -------------------------------------------------------------------------------- 1 | (defmap user) 2 | 3 | // multi-line input was getting 'End' SexpSentinel tokens after each comma 4 | // causing the MakeHash call to fail with odd arg count. 5 | 6 | // should work, but wasn't 7 | // error in infix:-1: Error calling 'infix': Error calling 'msgmap': hash requires even number of arguments 8 | {u3 = (user a:false, 9 | b: false)} 10 | 11 | // should work, but wasn't 12 | // Error on line 2: Invalid syntax, don't know what to do with 1 ')' 13 | {u3 = (user a:false, 14 | b: false,)} 15 | 16 | // multi-line input was getting 'End' SexpSentinel tokens after each comma 17 | {u2 = (user 18 | a:false, 19 | b:false, 20 | c:true)} 21 | 22 | // comma alone doing the same, should be okay now. 23 | , 24 | , 25 | -------------------------------------------------------------------------------- /zygo/address.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | type Address struct { 4 | function *SexpFunction 5 | position int 6 | } 7 | 8 | func (a Address) IsStackElem() {} 9 | 10 | func (stack *Stack) PushAddr(function *SexpFunction, pc int) { 11 | stack.Push(Address{function, pc}) 12 | } 13 | 14 | func (stack *Stack) PopAddr() (*SexpFunction, int, error) { 15 | elem, err := stack.Pop() 16 | if err != nil { 17 | return MissingFunction, 0, err 18 | } 19 | addr := elem.(Address) 20 | return addr.function, addr.position, nil 21 | } 22 | -------------------------------------------------------------------------------- /zygo/basetypes.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | -------------------------------------------------------------------------------- /zygo/blake2.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/glycerine/blake2b" 6 | ) 7 | 8 | // Blake2bUint64 returns an 8 byte BLAKE2b cryptographic 9 | // hash of the raw. 10 | // 11 | // we're using the pure go: https://github.com/dchest/blake2b 12 | // 13 | // but the C-wrapped refence may be helpful as well -- 14 | // 15 | // reference: https://godoc.org/github.com/codahale/blake2 16 | // reference: https://blake2.net/ 17 | // reference: https://tools.ietf.org/html/rfc7693 18 | // 19 | func Blake2bUint64(raw []byte) uint64 { 20 | cfg := &blake2b.Config{Size: 8} 21 | h, err := blake2b.New(cfg) 22 | panicOn(err) 23 | h.Write(raw) 24 | by := h.Sum(nil) 25 | return binary.LittleEndian.Uint64(by[:8]) 26 | } 27 | -------------------------------------------------------------------------------- /zygo/bsave.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | 10 | "github.com/glycerine/greenpack/msgp" 11 | ) 12 | 13 | // (bsave value path) writes value as greenpack to file. 14 | // 15 | // (greenpack value) writes value as greenpack to SexpRaw in memory. 16 | // 17 | // bsave converts to binary with (togo) then saves the binary to file. 18 | func WriteShadowGreenpackToFileFunction(name string) ZlispUserFunction { 19 | return func(env *Zlisp, _ string, args []Sexp) (Sexp, error) { 20 | narg := len(args) 21 | if narg < 1 || narg > 2 { 22 | return SexpNull, WrongNargs 23 | } 24 | // check arg[0] 25 | var asHash *SexpHash 26 | switch x := args[0].(type) { 27 | default: 28 | return SexpNull, fmt.Errorf("%s error: top value must be a hash or defmap; we see '%T'", name, args[0]) 29 | case *SexpHash: 30 | // okay, good 31 | asHash = x 32 | } 33 | 34 | switch name { 35 | case "bsave": 36 | if narg != 2 { 37 | return SexpNull, WrongNargs 38 | } 39 | 40 | case "greenpack": 41 | if narg != 1 { 42 | return SexpNull, WrongNargs 43 | } 44 | var buf bytes.Buffer 45 | _, err := toGreenpackHelper(env, asHash, &buf, "memory") 46 | if err != nil { 47 | return SexpNull, err 48 | } 49 | return &SexpRaw{Val: buf.Bytes()}, nil 50 | } 51 | 52 | // check arg[1] 53 | var fn string 54 | switch fna := args[1].(type) { 55 | case *SexpStr: 56 | fn = fna.S 57 | default: 58 | return SexpNull, fmt.Errorf("error: %s requires a string (SexpStr) path to write to as the second argument. we got type %T / value = %v", name, args[1], args[1]) 59 | } 60 | 61 | // don't overwrite existing file 62 | if FileExists(fn) { 63 | return SexpNull, fmt.Errorf("error: %s refusing to write to existing file '%s'", 64 | name, fn) 65 | } 66 | 67 | f, err := os.Create(fn) 68 | if err != nil { 69 | return SexpNull, fmt.Errorf("error: %s sees error trying to create file '%s': '%v'", name, fn, err) 70 | } 71 | defer f.Close() 72 | 73 | _, err = toGreenpackHelper(env, asHash, f, fn) 74 | return SexpNull, err 75 | } 76 | } 77 | 78 | func toGreenpackHelper(env *Zlisp, asHash *SexpHash, f io.Writer, fn string) (Sexp, error) { 79 | 80 | // create shadow structs 81 | _, err := ToGoFunction(env, "togo", []Sexp{asHash}) 82 | if err != nil { 83 | return SexpNull, fmt.Errorf("ToGo call sees error: '%v'", err) 84 | } 85 | 86 | if asHash.GoShadowStruct == nil { 87 | return SexpNull, fmt.Errorf("GoShadowStruct was nil, on attempt to write to '%s'", fn) 88 | } 89 | 90 | enc, ok := interface{}(asHash.GoShadowStruct).(msgp.Encodable) 91 | if !ok { 92 | return SexpNull, fmt.Errorf("error: GoShadowStruct was not greenpack Encodable -- run `go generate` or add greenpack to the source file for type '%T'. on attempt to save to '%s'", asHash.GoShadowStruct, fn) 93 | } 94 | w := msgp.NewWriter(f) 95 | err = msgp.Encode(w, enc) 96 | if err != nil { 97 | return SexpNull, fmt.Errorf("error: greenpack encoding to file '%s' of type '%T' sees error '%v'", fn, asHash.GoShadowStruct, err) 98 | } 99 | err = w.Flush() 100 | return SexpNull, err 101 | } 102 | 103 | func ReadGreenpackFromFileFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 104 | narg := len(args) 105 | 106 | if narg != 1 { 107 | return SexpNull, WrongNargs 108 | } 109 | var fn string 110 | switch fna := args[0].(type) { 111 | case *SexpStr: 112 | fn = fna.S 113 | default: 114 | return SexpNull, fmt.Errorf("%s requires a string path to read. we got type %T / value = %v", name, args[0], args[0]) 115 | } 116 | 117 | if !FileExists(string(fn)) { 118 | return SexpNull, fmt.Errorf("file '%s' does not exist", fn) 119 | } 120 | f, err := os.Open(fn) 121 | if err != nil { 122 | return SexpNull, err 123 | } 124 | defer f.Close() 125 | by, err := ioutil.ReadAll(f) 126 | if err != nil { 127 | return SexpNull, err 128 | } 129 | return MsgpackToSexp(by, env) 130 | } 131 | -------------------------------------------------------------------------------- /zygo/callgo2_test.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | cv "github.com/glycerine/goconvey/convey" 8 | ) 9 | 10 | // more demonstrating how to pass data between script and Go. 11 | 12 | var _ = fmt.Printf 13 | 14 | type Table struct { 15 | Headers []string `json:"headers" msg:"headers"` 16 | Rows [][]string `json:"rows" msg:"rows"` 17 | } 18 | 19 | func Test019_ScriptCreatesData_GoReadsIt(t *testing.T) { 20 | 21 | cv.Convey(`example zygo script created content being then read from Go`, t, func() { 22 | 23 | env := NewZlisp() 24 | defer env.Close() 25 | 26 | // Typically you want to call env.StandardSetup() 27 | // right after creating a new env. 28 | // It will setup alot of parts of the env, 29 | // like defining the base types, allowing imports, etc. 30 | // 31 | // A sandboxed env, however, may not want to do this. 32 | env.StandardSetup() 33 | 34 | // Register the above Table struct, so we can copy 35 | // from zygo (table) to Go Table{} 36 | GoStructRegistry.RegisterUserdef( 37 | &RegisteredType{ 38 | GenDefMap: true, Factory: func(env *Zlisp, h *SexpHash) (interface{}, error) { 39 | return &Table{}, nil 40 | }}, true, "table") 41 | 42 | code := ` 43 | // A defmap is needed to define the table struct inside env. 44 | // The registry doesn't know about env(s), so it 45 | // can't do it for us automatically. 46 | (defmap table) 47 | 48 | // Create an instance of table, with some data in it. 49 | (def t 50 | (table headers: ["wood" "metal"] 51 | rows: [["oak" "silver"] 52 | ["pine" "tin" ]]))` 53 | 54 | //env.debugExec = true 55 | x, err := env.EvalString(code) 56 | panicOn(err) 57 | 58 | //vv("x = '%#v'", x) 59 | cv.So(x.(*SexpHash).TypeName, cv.ShouldEqual, "table") 60 | 61 | // provide a top level struct to fill in. In this 62 | // case the tree is just a 1 node deep. 63 | table := &Table{} 64 | tmp, err := SexpToGoStructs(x, table, env, nil, 0, table) 65 | panicOn(err) 66 | 67 | // The table and tmp are equal pointers. They point to the same Table. 68 | cv.So(table == tmp, cv.ShouldBeTrue) 69 | 70 | // The script created content is accessible from Go via tmp/table now. 71 | // Note that this is a copy. 72 | cv.So(table.Headers, cv.ShouldResemble, []string{"wood", "metal"}) 73 | 74 | // So if we write to the copy... 75 | table.Headers[0] += "en ships, on the water" 76 | 77 | // the script version is unchanged... 78 | // 79 | // (Note that the assert will panic if it is not true.) 80 | _, err = env.EvalString(`(assert {t.headers[0] == "wood"})`) 81 | panicOn(err) 82 | 83 | switch f := tmp.(type) { 84 | case *Table: 85 | _ = f 86 | //fmt.Printf("my f is indeed a *Table: '%#v'", f) 87 | default: 88 | panic("wrong type") 89 | } 90 | 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /zygo/cfg.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | // configure a glisp repl 8 | type ZlispConfig struct { 9 | CpuProfile string 10 | MemProfile string 11 | ExitOnFailure bool 12 | CountFuncCalls bool 13 | Flags *flag.FlagSet 14 | ExtensionsVersion string 15 | Command string 16 | Sandboxed bool 17 | Quiet bool 18 | Trace bool 19 | LoadDemoStructs bool 20 | AfterScriptDontExit bool 21 | 22 | // liner bombs under emacs, avoid it with this flag. 23 | NoLiner bool 24 | Prompt string // default "zygo> " 25 | 26 | } 27 | 28 | func NewZlispConfig(cmdname string) *ZlispConfig { 29 | return &ZlispConfig{ 30 | Flags: flag.NewFlagSet(cmdname, flag.ExitOnError), 31 | } 32 | } 33 | 34 | // call DefineFlags before myflags.Parse() 35 | func (c *ZlispConfig) DefineFlags() { 36 | c.Flags.StringVar(&c.CpuProfile, "cpuprofile", "", "write cpu profile to file") 37 | c.Flags.StringVar(&c.MemProfile, "memprofile", "", "write mem profile to file") 38 | c.Flags.BoolVar(&c.ExitOnFailure, "exitonfail", false, "exit on failure instead of starting repl") 39 | c.Flags.BoolVar(&c.CountFuncCalls, "countcalls", false, "count how many times each function is run") 40 | c.Flags.StringVar(&c.Command, "c", "", "expressions to evaluate") 41 | c.Flags.BoolVar(&c.AfterScriptDontExit, "i", false, "after running the command line script, remain interactive rather than exiting") 42 | c.Flags.BoolVar(&c.Sandboxed, "sandbox", false, "run sandboxed; disallow system/external interaction functions") 43 | c.Flags.BoolVar(&c.Quiet, "quiet", false, "start repl without printing the version/mode/help banner") 44 | c.Flags.BoolVar(&c.Trace, "trace", false, "trace execution (warning: very verbose and slow)") 45 | c.Flags.BoolVar(&c.LoadDemoStructs, "demo", false, "load the demo structs: Event, Snoopy, Hornet, Weather and friends.") 46 | } 47 | 48 | // call c.ValidateConfig() after myflags.Parse() 49 | func (c *ZlispConfig) ValidateConfig() error { 50 | if c.Prompt == "" { 51 | c.Prompt = "zygo> " 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /zygo/channels.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type SexpChannel struct { 9 | Val chan Sexp 10 | Typ *RegisteredType 11 | } 12 | 13 | func (ch *SexpChannel) SexpString(ps *PrintState) string { 14 | return "[chan]" 15 | } 16 | 17 | func (ch *SexpChannel) Type() *RegisteredType { 18 | return ch.Typ // TODO what should this be? 19 | } 20 | 21 | func MakeChanFunction(env *Zlisp, name string, 22 | args []Sexp) (Sexp, error) { 23 | if len(args) > 1 { 24 | return SexpNull, WrongNargs 25 | } 26 | 27 | size := 0 28 | if len(args) == 1 { 29 | switch t := args[0].(type) { 30 | case *SexpInt: 31 | size = int(t.Val) 32 | default: 33 | return SexpNull, errors.New( 34 | fmt.Sprintf("argument to %s must be int", name)) 35 | } 36 | } 37 | 38 | return &SexpChannel{Val: make(chan Sexp, size)}, nil 39 | } 40 | 41 | func ChanTxFunction(env *Zlisp, name string, 42 | args []Sexp) (Sexp, error) { 43 | if len(args) < 1 { 44 | return SexpNull, WrongNargs 45 | } 46 | var channel chan Sexp 47 | switch t := args[0].(type) { 48 | case *SexpChannel: 49 | channel = chan Sexp(t.Val) 50 | default: 51 | return SexpNull, errors.New( 52 | fmt.Sprintf("argument 0 of %s must be channel", name)) 53 | } 54 | 55 | if name == "send" { 56 | if len(args) != 2 { 57 | return SexpNull, WrongNargs 58 | } 59 | channel <- args[1] 60 | return SexpNull, nil 61 | } 62 | 63 | return <-channel, nil 64 | } 65 | 66 | func (env *Zlisp) ImportChannels() { 67 | env.AddFunction("makeChan", MakeChanFunction) 68 | env.AddFunction("send", ChanTxFunction) 69 | env.AddFunction(" 0 { 67 | if haveByName != f.inputTypes.NumKeys { 68 | return fmt.Errorf("named arguments count %v != expected %v", haveByName, f.inputTypes.NumKeys) 69 | } 70 | 71 | // prep finalArgs in the order dictated 72 | for i, key := range f.inputTypes.KeyOrder { 73 | switch sy := key.(type) { 74 | case *SexpSymbol: 75 | // search for sy.name in our submittedByName args 76 | a, found := submittedByName[sy.name] 77 | if found { 78 | //Q("%s call: matching %v-th argument named '%s': passing value '%s'", 79 | // f.name, i, sy.name, a.SexpString(nil)) 80 | finalArgs[i] = a 81 | } 82 | default: 83 | return fmt.Errorf("unsupported argument-name type %T", key) 84 | } 85 | 86 | } 87 | } else { 88 | // not using named parameters, restore the arguments to the stack as they were. 89 | finalArgs = exprs 90 | } 91 | *nargs = len(finalArgs) 92 | return env.datastack.PushExpressions(finalArgs) 93 | } 94 | -------------------------------------------------------------------------------- /zygo/closing.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | // where we store our closure-supporing stack pointers 4 | type Closing struct { 5 | Stack *Stack 6 | Name string 7 | env *Zlisp 8 | } 9 | 10 | func NewClosing(name string, env *Zlisp) *Closing { 11 | stk := env.linearstack.Clone() 12 | // be super strict: only store up to our 13 | // enclosing function definition, because after 14 | // that, the definition time of that function 15 | // should be what we use. 16 | 17 | return &Closing{ 18 | Stack: stk, 19 | Name: name, 20 | env: env} 21 | } 22 | 23 | func NewEmptyClosing(name string, env *Zlisp) *Closing { 24 | return &Closing{ 25 | Stack: env.NewStack(0), 26 | Name: name, 27 | env: env} 28 | } 29 | 30 | func (c *Closing) IsStackElem() {} 31 | 32 | func (c *Closing) LookupSymbolUntilFunction(sym *SexpSymbol, setVal *Sexp, maximumFuncToSearch int, checkCaptures bool) (Sexp, error, *Scope) { 33 | return c.Stack.LookupSymbolUntilFunction(sym, setVal, maximumFuncToSearch, checkCaptures) 34 | } 35 | func (c *Closing) LookupSymbol(sym *SexpSymbol, setVal *Sexp) (Sexp, error, *Scope) { 36 | return c.Stack.LookupSymbol(sym, setVal) 37 | } 38 | 39 | func (c *Closing) Show(env *Zlisp, ps *PrintState, label string) (string, error) { 40 | return c.Stack.Show(env, ps, label) 41 | } 42 | 43 | func (c *Closing) TopScope() *Scope { 44 | return c.Stack.GetTop().(*Scope) 45 | } 46 | -------------------------------------------------------------------------------- /zygo/comment.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | type SexpComment struct { 4 | Comment string 5 | Block bool 6 | } 7 | 8 | func (p *SexpComment) SexpString(ps *PrintState) string { 9 | return p.Comment 10 | } 11 | 12 | func (p *SexpComment) Type() *RegisteredType { 13 | return GoStructRegistry.Registry["comment"] 14 | } 15 | 16 | // Filters return true to keep, false to drop. 17 | type Filter func(x Sexp) bool 18 | 19 | func RemoveCommentsFilter(x Sexp) bool { 20 | switch x.(type) { 21 | case *SexpComment: 22 | //P("RemoveCommentsFilter called on x= %T/val=%v. return false", x, x) 23 | return false 24 | default: 25 | //P("RemoveCommentsFilter called on x= %T/val=%v. return true", x, x) 26 | return true 27 | } 28 | } 29 | 30 | // detect SexpEnd values and return false on them to filter them out. 31 | func RemoveEndsFilter(x Sexp) bool { 32 | switch n := x.(type) { 33 | case *SexpSentinel: 34 | if n.Val == SexpEnd.Val { 35 | return false 36 | } 37 | } 38 | return true 39 | } 40 | 41 | // detect SexpComma values and return false on them to filter them out. 42 | func RemoveCommasFilter(x Sexp) bool { 43 | switch x.(type) { 44 | case *SexpComma: 45 | return false 46 | } 47 | return true 48 | } 49 | 50 | func (env *Zlisp) FilterAny(x Sexp, f Filter) (filtered Sexp, keep bool) { 51 | switch ele := x.(type) { 52 | case *SexpArray: 53 | res := &SexpArray{Val: env.FilterArray(ele.Val, f), Typ: ele.Typ, IsFuncDeclTypeArray: ele.IsFuncDeclTypeArray, Env: env} 54 | return res, true 55 | case *SexpPair: 56 | return env.FilterList(ele, f), true 57 | case *SexpHash: 58 | return env.FilterHash(ele, f), true 59 | default: 60 | keep = f(x) 61 | if keep { 62 | return x, true 63 | } 64 | return SexpNull, false 65 | } 66 | } 67 | 68 | func (env *Zlisp) FilterArray(x []Sexp, f Filter) []Sexp { 69 | //P("FilterArray: before: %d in size", len(x)) 70 | //for i := range x { 71 | //P("x[i=%d] = %v", i, x[i].SexpString()) 72 | //} 73 | res := []Sexp{} 74 | for i := range x { 75 | filtered, keep := env.FilterAny(x[i], f) 76 | if keep { 77 | res = append(res, filtered) 78 | } 79 | } 80 | //P("FilterArray: after: %d in size", len(res)) 81 | //for i := range res { 82 | //P("x[i=%d] = %v", i, res[i].SexpString()) 83 | //} 84 | return res 85 | } 86 | 87 | func (env *Zlisp) FilterHash(h *SexpHash, f Filter) *SexpHash { 88 | // should not actually need this, since hashes 89 | // don't yet exist in parsed symbols. (they are 90 | // still lists). 91 | //P("in FilterHash") 92 | return h 93 | } 94 | 95 | func (env *Zlisp) FilterList(h *SexpPair, f Filter) Sexp { 96 | //P("in FilterList") 97 | arr, err := ListToArray(h) 98 | res := []Sexp{} 99 | if err == NotAList { 100 | // don't filter pair lists 101 | return h 102 | } 103 | res = env.FilterArray(arr, f) 104 | return MakeList(res) 105 | } 106 | -------------------------------------------------------------------------------- /zygo/coroutines.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type SexpGoroutine struct { 8 | env *Zlisp 9 | } 10 | 11 | func (goro *SexpGoroutine) SexpString(ps *PrintState) string { 12 | return "[coroutine]" 13 | } 14 | func (goro *SexpGoroutine) Type() *RegisteredType { 15 | return nil // TODO what goes here 16 | } 17 | 18 | func StartGoroutineFunction(env *Zlisp, name string, 19 | args []Sexp) (Sexp, error) { 20 | switch t := args[0].(type) { 21 | case *SexpGoroutine: 22 | go t.env.Run() 23 | default: 24 | return SexpNull, errors.New("not a goroutine") 25 | } 26 | return SexpNull, nil 27 | } 28 | 29 | func CreateGoroutineMacro(env *Zlisp, name string, 30 | args []Sexp) (Sexp, error) { 31 | goroenv := env.Duplicate() 32 | err := goroenv.LoadExpressions(args) 33 | if err != nil { 34 | return SexpNull, nil 35 | } 36 | goro := &SexpGoroutine{goroenv} 37 | 38 | // (apply StartGoroutineFunction [goro]) 39 | return MakeList([]Sexp{env.MakeSymbol("apply"), 40 | MakeUserFunction("__start", StartGoroutineFunction), 41 | &SexpArray{Val: []Sexp{goro}, Env: env}}), nil 42 | } 43 | 44 | func (env *Zlisp) ImportGoroutines() { 45 | env.AddMacro("go", CreateGoroutineMacro) 46 | } 47 | -------------------------------------------------------------------------------- /zygo/datastack.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type DataStackElem struct { 9 | expr Sexp 10 | } 11 | 12 | func (d DataStackElem) IsStackElem() {} 13 | 14 | func (stack *Stack) PushExpr(expr Sexp) { 15 | stack.Push(DataStackElem{expr}) 16 | } 17 | 18 | func (stack *Stack) PushExpressions(expr []Sexp) error { 19 | for _, x := range expr { 20 | stack.Push(DataStackElem{x}) 21 | } 22 | return nil 23 | } 24 | 25 | func (stack *Stack) PopExpr() (Sexp, error) { 26 | elem, err := stack.Pop() 27 | if err != nil { 28 | return nil, err 29 | } 30 | return elem.(DataStackElem).expr, nil 31 | } 32 | 33 | func (stack *Stack) GetExpressions(n int) ([]Sexp, error) { 34 | stack_start := stack.tos - n + 1 35 | if stack_start < 0 { 36 | return nil, errors.New("not enough items on stack") 37 | } 38 | arr := make([]Sexp, n) 39 | for i := 0; i < n; i++ { 40 | arr[i] = stack.elements[stack_start+i].(DataStackElem).expr 41 | } 42 | return arr, nil 43 | } 44 | 45 | func (stack *Stack) PopExpressions(n int) ([]Sexp, error) { 46 | origSz := stack.Size() 47 | expressions, err := stack.GetExpressions(n) 48 | if err != nil { 49 | return nil, err 50 | } 51 | stack.TruncateToSize(origSz - n) 52 | return expressions, nil 53 | } 54 | 55 | func (stack *Stack) GetExpr(n int) (Sexp, error) { 56 | elem, err := stack.Get(n) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return elem.(DataStackElem).expr, nil 61 | } 62 | 63 | func (stack *Stack) PrintStack() { 64 | for i := 0; i <= stack.tos; i++ { 65 | expr := stack.elements[i].(DataStackElem).expr 66 | fmt.Println("\t" + expr.SexpString(nil)) 67 | } 68 | } 69 | 70 | func (stack *Stack) PrintScopeStack() { 71 | for i := 0; i <= stack.tos; i++ { 72 | scop := stack.elements[i].(*Scope) 73 | scop.Show(stack.env, NewPrintStateWithIndent(4), "") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /zygo/demo_go_structs.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | //go:generate msgp 9 | 10 | //msgp:ignore Plane Wings Snoopy Hornet Hellcat SetOfPlanes 11 | 12 | // the pointer wasn't getting followed. 13 | type NestOuter struct { 14 | Inner *NestInner `msg:"inner" json:"inner" zid:"0"` 15 | } 16 | 17 | type NestInner struct { 18 | Hello string `msg:"hello" json:"hello" zid:"0"` 19 | } 20 | 21 | type Event struct { 22 | Id int `json:"id" msg:"id"` 23 | User Person `json:"user" msg:"user"` 24 | Flight string `json:"flight" msg:"flight"` 25 | Pilot []string `json:"pilot" msg:"pilot"` 26 | Cancelled bool `json:"cancelled" msg:"cancelled"` 27 | } 28 | 29 | type Person struct { 30 | First string `json:"first" msg:"first"` 31 | Last string `json:"last" msg:"last"` 32 | } 33 | 34 | func (ev *Event) DisplayEvent(from string) { 35 | fmt.Printf("%s %#v", from, ev) 36 | } 37 | 38 | type Wings struct { 39 | SpanCm int 40 | } 41 | 42 | type SetOfPlanes struct { 43 | Flyers []Flyer `json:"flyers" msg:"flyers"` 44 | } 45 | 46 | // the interface Flyer confounds the msgp msgpack code generator, 47 | // so put the msgp:ignore Plane above 48 | type Plane struct { 49 | Wings 50 | 51 | ID int `json:"id" msg:"id"` 52 | Speed int `json:"speed" msg:"speed"` 53 | Chld Flyer `json:"chld" msg:"chld"` 54 | Friends []Flyer `json:"friends"` 55 | } 56 | 57 | type Snoopy struct { 58 | Plane `json:"plane" msg:"plane"` 59 | Cry string `json:"cry" msg:"cry"` 60 | Pack []int `json:"pack"` 61 | Carrying []Flyer `json:"carrying"` 62 | } 63 | 64 | type Hornet struct { 65 | Plane `json:"plane" msg:"plane"` 66 | Mass float64 67 | Nickname string 68 | } 69 | 70 | type Hellcat struct { 71 | Plane `json:"plane" msg:"plane"` 72 | } 73 | 74 | func (p *Snoopy) Fly(w *Weather) (s string, err error) { 75 | w.Type = "VERY " + w.Type // side-effect, for demo purposes 76 | s = fmt.Sprintf("Snoopy sees weather '%s', cries '%s'", w.Type, p.Cry) 77 | fmt.Println(s) 78 | for _, flyer := range p.Friends { 79 | flyer.Fly(w) 80 | } 81 | return 82 | } 83 | 84 | func (p *Snoopy) GetCry() string { 85 | return p.Cry 86 | } 87 | 88 | func (p *Snoopy) EchoWeather(w *Weather) *Weather { 89 | return w 90 | } 91 | 92 | func (p *Snoopy) Sideeffect() { 93 | fmt.Printf("Sideeffect() called! p = %p\n", p) 94 | } 95 | 96 | func (b *Hornet) Fly(w *Weather) (s string, err error) { 97 | fmt.Printf("Hornet.Fly() called. I see weather %v\n", w.Type) 98 | return 99 | } 100 | 101 | func (b *Hellcat) Fly(w *Weather) (s string, err error) { 102 | fmt.Printf("Hellcat.Fly() called. I see weather %v\n", w.Type) 103 | return 104 | } 105 | 106 | type Flyer interface { 107 | Fly(w *Weather) (s string, err error) 108 | } 109 | 110 | type Weather struct { 111 | Time time.Time `json:"time" msg:"time"` 112 | Size int64 `json:"size" msg:"size"` 113 | Type string `json:"type" msg:"type"` 114 | Details []byte `json:"details" msg:"details"` 115 | } 116 | 117 | func (w *Weather) IsSunny() bool { 118 | return w.Type == "sunny" 119 | } 120 | 121 | func (env *Zlisp) ImportDemoData() { 122 | 123 | env.AddFunction("nestouter", DemoNestInnerOuterFunction) 124 | env.AddFunction("nestinner", DemoNestInnerOuterFunction) 125 | 126 | rt := &RegisteredType{GenDefMap: true, Factory: func(env *Zlisp, h *SexpHash) (interface{}, error) { 127 | return &NestOuter{}, nil 128 | }} 129 | GoStructRegistry.RegisterUserdef(rt, true, "nestouter", "NestOuter") 130 | 131 | rt = &RegisteredType{GenDefMap: true, Factory: func(env *Zlisp, h *SexpHash) (interface{}, error) { 132 | return &NestInner{}, nil 133 | }} 134 | GoStructRegistry.RegisterUserdef(rt, true, "nestinner", "NestInner") 135 | 136 | } 137 | 138 | // constructor 139 | func DemoNestInnerOuterFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 140 | 141 | n := len(args) 142 | switch n { 143 | case 0: 144 | return SexpNull, WrongNargs 145 | default: 146 | // many parameters, treat as key:value pairs in the hash/record. 147 | return ConstructorFunction("msgmap")(env, "msgmap", append([]Sexp{&SexpStr{S: name}}, MakeList(args))) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /zygo/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This project does not use godoc. Instead there is extensive 3 | and detailed description of the language features maintained 4 | on the wiki. See the following link. 5 | 6 | https://github.com/glycerine/zygomys/wiki 7 | */ 8 | package zygo 9 | -------------------------------------------------------------------------------- /zygo/environment_test.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | cv "github.com/glycerine/goconvey/convey" 8 | ) 9 | 10 | func Test400SandboxFunctions(t *testing.T) { 11 | 12 | cv.Convey(`Given that the developer wishes to sandbox the Zygo interpreter when embedding it in their program, the NewZlispSandbox() function should return an interpreter that cannot call system/filesystem functions`, t, func() { 13 | 14 | sysFuncs := SystemFunctions() 15 | sandSafeFuncs := SandboxSafeFunctions() 16 | { 17 | env := NewZlispSandbox() 18 | 19 | // no system functions should pass 20 | for name := range sysFuncs { 21 | env.Clear() 22 | //P("checking name = '%v'", name) 23 | res, err := env.EvalString(fmt.Sprintf("(defined? %%%s)", name)) 24 | cv.So(res, cv.ShouldResemble, &SexpBool{Val: false}) 25 | cv.So(err, cv.ShouldResemble, nil) 26 | } 27 | 28 | // all sandSafeFuncs should be fine 29 | for name := range sandSafeFuncs { 30 | env.Clear() 31 | res, err := env.EvalString(fmt.Sprintf("(defined? %%%s)", name)) 32 | switch y := res.(type) { 33 | case *SexpSentinel: 34 | P("'%s' wasn't defined but should be; defined? returned '%s'", name, y.SexpString(nil)) 35 | case *SexpBool: 36 | cv.So(res, cv.ShouldResemble, &SexpBool{Val: true}) 37 | } 38 | cv.So(err, cv.ShouldEqual, nil) 39 | } 40 | } 41 | 42 | { 43 | fmt.Printf("\n and all functions should be reachable from a non-sandboxed environment.\n") 44 | env := NewZlisp() 45 | for name := range sysFuncs { 46 | env.Clear() 47 | res, err := env.EvalString(fmt.Sprintf("(defined? %%%s)", name)) 48 | cv.So(res, cv.ShouldResemble, &SexpBool{Val: true}) 49 | cv.So(err, cv.ShouldEqual, nil) 50 | } 51 | 52 | // all sandSafeFuncs should be fine 53 | for name := range sandSafeFuncs { 54 | env.Clear() 55 | res, err := env.EvalString(fmt.Sprintf("(defined? %%%s)", name)) 56 | cv.So(res, cv.ShouldResemble, &SexpBool{Val: true}) 57 | cv.So(err, cv.ShouldEqual, nil) 58 | } 59 | 60 | } 61 | }) 62 | } 63 | 64 | func TestCallUserFunction(t *testing.T) { 65 | cv.Convey(`It should recover from user-land panics and give stack traces`, t, func() { 66 | env := NewZlisp() 67 | env.AddFunction("dosomething", func(*Zlisp, string, []Sexp) (r Sexp, err error) { 68 | panic("I don't know how to do anything") 69 | }) 70 | _, err := env.EvalString("(dosomething)") 71 | cv.So(err, cv.ShouldNotBeNil) 72 | cv.So(err.Error(), cv.ShouldContainSubstring, "stack trace:") 73 | cv.So(err.Error(), cv.ShouldContainSubstring, "github.com/glycerine/zygomys") 74 | cv.So(err.Error(), cv.ShouldContainSubstring, "zygo.(*Zlisp).CallUserFunction") 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /zygo/exists.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func FileExists(name string) bool { 8 | fi, err := os.Stat(name) 9 | if err != nil { 10 | return false 11 | } 12 | if fi.IsDir() { 13 | return false 14 | } 15 | return true 16 | } 17 | 18 | func DirExists(name string) bool { 19 | fi, err := os.Stat(name) 20 | if err != nil { 21 | return false 22 | } 23 | if fi.IsDir() { 24 | return true 25 | } 26 | return false 27 | } 28 | -------------------------------------------------------------------------------- /zygo/gitcommit.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | func init() { GITLASTTAG = "v9.0.7"; GITLASTCOMMIT = "ec0e7ed30f85ebed81bb1ce1ee50cdc93b4e57f4" } 3 | -------------------------------------------------------------------------------- /zygo/gob.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | ) 8 | 9 | func GobEncodeFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 10 | if len(args) != 1 { 11 | return SexpNull, WrongNargs 12 | } 13 | 14 | h, isHash := args[0].(*SexpHash) 15 | if !isHash { 16 | return SexpNull, fmt.Errorf("gob argument must be a hash or defmap") 17 | } 18 | 19 | // fill the go shadow struct 20 | _, err := ToGoFunction(env, "togo", []Sexp{h}) 21 | if err != nil { 22 | return SexpNull, fmt.Errorf("error converting object to Go struct: '%s'", err) 23 | } 24 | 25 | // serialize to gob 26 | var gobBytes bytes.Buffer 27 | 28 | enc := gob.NewEncoder(&gobBytes) 29 | err = enc.Encode(h.GoShadowStruct) 30 | if err != nil { 31 | return SexpNull, fmt.Errorf("gob encode error: '%s'", err) 32 | } 33 | 34 | return &SexpRaw{Val: gobBytes.Bytes()}, nil 35 | } 36 | 37 | func GobDecodeFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 38 | if len(args) != 1 { 39 | return SexpNull, WrongNargs 40 | } 41 | 42 | raw, isRaw := args[0].(*SexpRaw) 43 | if !isRaw { 44 | return SexpNull, fmt.Errorf("ungob argument must be raw []byte") 45 | } 46 | 47 | rawBuf := bytes.NewBuffer(raw.Val) 48 | dec := gob.NewDecoder(rawBuf) 49 | var iface interface{} 50 | err := dec.Decode(iface) 51 | if err != nil { 52 | return SexpNull, fmt.Errorf("gob decode error: '%s'", err) 53 | } 54 | 55 | // TODO convert to hash 56 | panic("not done yet!") 57 | 58 | //return SexpNull, nil 59 | } 60 | -------------------------------------------------------------------------------- /zygo/import.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // import a package, analagous to Golang. 8 | func ImportPackageBuilder(env *Zlisp, name string, args []Sexp) (Sexp, error) { 9 | //P("starting ImportPackageBuilder") 10 | n := len(args) 11 | if n != 1 && n != 2 { 12 | return SexpNull, WrongNargs 13 | } 14 | 15 | var path Sexp 16 | var alias string 17 | 18 | switch n { 19 | case 1: 20 | path = args[0] 21 | case 2: 22 | path = args[1] 23 | //P("import debug: alias position at args[0] is '%#v'", args[0]) 24 | switch sy := args[0].(type) { 25 | case *SexpSymbol: 26 | //P("import debug: alias is symbol, ok: '%v'", sy.name) 27 | alias = sy.name 28 | default: 29 | return SexpNull, fmt.Errorf("import error: alias was not a symbol name") 30 | } 31 | } 32 | 33 | var pth string 34 | switch x := path.(type) { 35 | case *SexpStr: 36 | pth = x.S 37 | default: 38 | return SexpNull, fmt.Errorf("import error: path argument must be string") 39 | } 40 | if !FileExists(pth) { 41 | return SexpNull, fmt.Errorf("import error: path '%s' does not exist", pth) 42 | } 43 | 44 | pkg, err := SourceFileFunction(env, "source", []Sexp{path}) 45 | if err != nil { 46 | return SexpNull, fmt.Errorf("import error: attempt to import path '%s' resulted in: '%s'", pth, err) 47 | } 48 | //P("pkg = '%#v'", pkg) 49 | 50 | asPkg, isPkg := pkg.(*Stack) 51 | if !isPkg || !asPkg.IsPackage { 52 | return SexpNull, fmt.Errorf("import error: attempt to import path '%s' resulted value that was not a package, but rather '%T'", pth, pkg) 53 | } 54 | 55 | if n == 1 { 56 | alias = asPkg.PackageName 57 | } 58 | //P("using alias = '%s'", alias) 59 | 60 | // now set alias in the current env 61 | err = env.LexicalBindSymbol(env.MakeSymbol(alias), asPkg) 62 | if err != nil { 63 | return SexpNull, err 64 | } 65 | 66 | return pkg, nil 67 | } 68 | -------------------------------------------------------------------------------- /zygo/import_test.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | cv "github.com/glycerine/goconvey/convey" 5 | "testing" 6 | ) 7 | 8 | func Test050ImportWorks(t *testing.T) { 9 | 10 | cv.Convey(`import test for https://github.com/glycerine/zygomys/issues/64`, t, func() { 11 | 12 | str := `(import "../tests/foo.pkg") 13 | (assert (== foo.B "I am a Public string"))` 14 | 15 | dothings := func() error { 16 | var err error 17 | env := NewZlisp() 18 | env.StandardSetup() 19 | 20 | if err = env.LoadString(str); err != nil { 21 | panic(err) 22 | } 23 | expr, err := env.Run() 24 | _ = expr 25 | panicOn(err) 26 | //vv("expr = '%v'", expr.SexpString(nil)) 27 | return nil 28 | } 29 | panicOn(dothings()) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /zygo/liner.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | 7 | "github.com/glycerine/liner" 8 | ) 9 | 10 | // filled at init time based on BuiltinFunctions 11 | var completion_keywords = []string{`(`} 12 | 13 | var math_funcs = []string{`* `, `** `, `+ `, `- `, `-> `, `/ `, `< `, `<= `, `== `, `> `, `>= `, `\ `} 14 | 15 | func init() { 16 | // fill in our auto-complete keywords 17 | sortme := []*SymtabE{} 18 | for f, _ := range AllBuiltinFunctions() { 19 | sortme = append(sortme, &SymtabE{Key: f}) 20 | } 21 | sort.Sort(SymtabSorter(sortme)) 22 | for i := range sortme { 23 | completion_keywords = append(completion_keywords, "("+sortme[i].Key) 24 | } 25 | 26 | for i := range math_funcs { 27 | completion_keywords = append(completion_keywords, "("+math_funcs[i]) 28 | } 29 | } 30 | 31 | type Prompter struct { 32 | prompt string 33 | prompter *liner.State 34 | origMode liner.ModeApplier 35 | rawMode liner.ModeApplier 36 | } 37 | 38 | // complete phrases that start with '(' 39 | func MyWordCompleter(line string, pos int) (head string, c []string, tail string) { 40 | 41 | beg := []rune(line[:pos]) 42 | end := line[pos:] 43 | Q("\nline = '%s' pos=%v\n", line, pos) 44 | Q("\nbeg = '%v'\nend = '%s'\n", string(beg), end) 45 | // find most recent paren in beg 46 | n := len(beg) 47 | last := n - 1 48 | var i int 49 | var p int = -1 50 | outer: 51 | for i = last; i >= 0; i-- { 52 | Q("\nbeg[i=%v] is '%v'\n", i, string(beg[i])) 53 | switch beg[i] { 54 | case ' ': 55 | break outer 56 | case '(': 57 | p = i 58 | Q("\n found paren at p = %v\n", i) 59 | break outer 60 | } 61 | } 62 | Q("p=%d\n", p) 63 | prefix := string(beg) 64 | extendme := "" 65 | if p == 0 { 66 | prefix = "" 67 | extendme = string(beg) 68 | } else if p > 0 { 69 | prefix = string(beg[:p]) 70 | extendme = string(beg[p:]) 71 | } 72 | Q("prefix = '%s'\nextendme = '%s'\n", prefix, extendme) 73 | 74 | for _, n := range completion_keywords { 75 | if strings.HasPrefix(n, strings.ToLower(extendme)) { 76 | Q("n='%s' has prefix = '%s'\n", n, extendme) 77 | c = append(c, n) 78 | } 79 | } 80 | 81 | return prefix, c, end 82 | } 83 | 84 | func NewPrompter(prompt string) *Prompter { 85 | origMode, err := liner.TerminalMode() 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | p := &Prompter{ 91 | prompt: prompt, 92 | prompter: liner.NewLiner(), 93 | origMode: origMode, 94 | } 95 | 96 | rawMode, err := liner.TerminalMode() 97 | if err != nil { 98 | panic(err) 99 | } 100 | p.rawMode = rawMode 101 | 102 | p.prompter.SetCtrlCAborts(false) 103 | p.prompter.SetWordCompleter(liner.WordCompleter(MyWordCompleter)) 104 | 105 | return p 106 | } 107 | 108 | func (p *Prompter) Close() { 109 | defer p.prompter.Close() 110 | } 111 | 112 | func (p *Prompter) Getline(prompt *string) (line string, err error) { 113 | applyErr := p.rawMode.ApplyMode() 114 | if applyErr != nil { 115 | panic(applyErr) 116 | } 117 | defer func() { 118 | applyErr := p.origMode.ApplyMode() 119 | if applyErr != nil { 120 | panic(applyErr) 121 | } 122 | }() 123 | 124 | if prompt == nil { 125 | line, err = p.prompter.Prompt(p.prompt) 126 | } else { 127 | line, err = p.prompter.Prompt(*prompt) 128 | } 129 | if err == nil { 130 | p.prompter.AppendHistory(line) 131 | return line, nil 132 | } 133 | return "", err 134 | } 135 | -------------------------------------------------------------------------------- /zygo/listutils.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var NotAList = errors.New("not a list") 9 | 10 | func ListToArray(expr Sexp) ([]Sexp, error) { 11 | if !IsList(expr) { 12 | return nil, NotAList 13 | } 14 | arr := make([]Sexp, 0) 15 | 16 | for expr != SexpNull { 17 | list := expr.(*SexpPair) 18 | arr = append(arr, list.Head) 19 | expr = list.Tail 20 | } 21 | 22 | return arr, nil 23 | } 24 | 25 | func MakeList(expressions []Sexp) Sexp { 26 | if len(expressions) == 0 { 27 | return SexpNull 28 | } 29 | 30 | return Cons(expressions[0], MakeList(expressions[1:])) 31 | } 32 | 33 | func MapList(env *Zlisp, fun *SexpFunction, expr Sexp) (Sexp, error) { 34 | if expr == SexpNull { 35 | return SexpNull, nil 36 | } 37 | 38 | var list = &SexpPair{} 39 | switch e := expr.(type) { 40 | case *SexpPair: 41 | list.Head = e.Head 42 | list.Tail = e.Tail 43 | default: 44 | return SexpNull, NotAList 45 | } 46 | 47 | var err error 48 | 49 | list.Head, err = env.Apply(fun, []Sexp{list.Head}) 50 | 51 | if err != nil { 52 | return SexpNull, err 53 | } 54 | 55 | list.Tail, err = MapList(env, fun, list.Tail) 56 | 57 | if err != nil { 58 | return SexpNull, err 59 | } 60 | 61 | return list, nil 62 | } 63 | 64 | // O(n^2) for n total nodes in all lists. So this is 65 | // not super efficient. We have to 66 | // find the tail of each list in turn by 67 | // linear search. Avoid lists if possible in favor 68 | // of arrays. 69 | func ConcatLists(a *SexpPair, bs []Sexp) (Sexp, error) { 70 | result := a 71 | for _, b := range bs { 72 | res, err := ConcatTwoLists(result, b) 73 | if err != nil { 74 | return SexpNull, err 75 | } 76 | x, ok := res.(*SexpPair) 77 | if !ok { 78 | return SexpNull, NotAList 79 | } 80 | result = x 81 | } 82 | return result, nil 83 | } 84 | 85 | func ConcatTwoLists(a *SexpPair, b Sexp) (Sexp, error) { 86 | if !IsList(b) { 87 | return SexpNull, NotAList 88 | } 89 | 90 | if a.Tail == SexpNull { 91 | return Cons(a.Head, b), nil 92 | } 93 | 94 | switch t := a.Tail.(type) { 95 | case *SexpPair: 96 | newtail, err := ConcatTwoLists(t, b) 97 | if err != nil { 98 | return SexpNull, err 99 | } 100 | return Cons(a.Head, newtail), nil 101 | } 102 | 103 | return SexpNull, NotAList 104 | } 105 | 106 | func ListLen(expr Sexp) (int, error) { 107 | sz := 0 108 | var list *SexpPair 109 | ok := false 110 | for expr != SexpNull { 111 | list, ok = expr.(*SexpPair) 112 | if !ok { 113 | return 0, fmt.Errorf("ListLen() called on non-list") 114 | } 115 | sz++ 116 | expr = list.Tail 117 | } 118 | return sz, nil 119 | } 120 | -------------------------------------------------------------------------------- /zygo/makego.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | -------------------------------------------------------------------------------- /zygo/msgpackmap.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func (env *Zlisp) ImportMsgpackMap() { 9 | env.AddMacro("msgpack-map", MsgpackMapMacro) 10 | env.AddFunction("declare-msgpack-map", DeclareMsgpackMapFunction) 11 | } 12 | 13 | // declare a new record type 14 | func MsgpackMapMacro(env *Zlisp, name string, 15 | args []Sexp) (Sexp, error) { 16 | 17 | if len(args) < 1 { 18 | return SexpNull, fmt.Errorf("struct-name is missing. use: " + 19 | "(msgpack-map struct-name)\n") 20 | } 21 | 22 | return MakeList([]Sexp{ 23 | env.MakeSymbol("def"), 24 | args[0], 25 | MakeList([]Sexp{ 26 | env.MakeSymbol("quote"), 27 | env.MakeSymbol("msgmap"), 28 | &SexpStr{S: args[0].(*SexpSymbol).name}, 29 | }), 30 | }), nil 31 | } 32 | 33 | func DeclareMsgpackMapFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 34 | if len(args) != 1 { 35 | return SexpNull, WrongNargs 36 | } 37 | 38 | switch t := args[0].(type) { 39 | case *SexpStr: 40 | return t, nil 41 | } 42 | return SexpNull, errors.New("argument must be string: the name of the new msgpack-map constructor function to create") 43 | } 44 | -------------------------------------------------------------------------------- /zygo/panicon.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | func panicOn(err error) { 4 | if err != nil { 5 | panic(err) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /zygo/printstate.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // begin supporting SexpString structs 8 | 9 | // PrintState threads the state of display through SexpString() and Show() calls, 10 | // to give pretty-printing indentation and to avoid infinite looping on 11 | // cyclic data structures. 12 | type PrintState struct { 13 | Indent int 14 | Seen Seen 15 | PrintJSON bool 16 | } 17 | 18 | func (ps *PrintState) SetSeen(x interface{}, name string) { 19 | if ps == nil { 20 | panic("can't SetSeen on a nil PrintState") 21 | } 22 | ps.Seen[x] = struct{}{} 23 | } 24 | 25 | func (ps *PrintState) GetSeen(x interface{}) bool { 26 | if ps == nil { 27 | return false 28 | } 29 | _, ok := ps.Seen[x] 30 | return ok 31 | } 32 | 33 | func (ps *PrintState) GetIndent() int { 34 | if ps == nil { 35 | return 0 36 | } 37 | return ps.Indent 38 | } 39 | 40 | func (ps *PrintState) AddIndent(addme int) *PrintState { 41 | if ps == nil { 42 | return &PrintState{ 43 | Indent: addme, 44 | Seen: NewSeen(), 45 | } 46 | } 47 | return &PrintState{ 48 | Indent: ps.Indent + addme, 49 | Seen: ps.Seen, 50 | PrintJSON: ps.PrintJSON, 51 | } 52 | } 53 | 54 | func NewPrintState() *PrintState { 55 | return &PrintState{ 56 | Seen: NewSeen(), 57 | } 58 | } 59 | 60 | func NewPrintStateWithIndent(indent int) *PrintState { 61 | return &PrintState{ 62 | Indent: indent, 63 | Seen: NewSeen(), 64 | } 65 | } 66 | 67 | func (ps *PrintState) Clear() { 68 | ps.Indent = 0 69 | ps.Seen = NewSeen() 70 | } 71 | 72 | func (ps *PrintState) Dump() { 73 | fmt.Printf("ps Dump: ") 74 | if ps == nil { 75 | fmt.Printf("nil\n") 76 | return 77 | } 78 | for k, v := range ps.Seen { 79 | fmt.Printf("ps Dump: %p -- %v\n", k, v) 80 | } 81 | fmt.Printf("\n") 82 | } 83 | 84 | // Seen tracks if a value has already been displayed, to 85 | // detect and avoid cycles. 86 | // 87 | /* Q: How to do garbage-collection safe graph traversal in a graph of Go objects? 88 | 89 | A: "Instead of converting the pointer to a uintptr, just store the pointer 90 | itself in a map[interface{}]bool. If you encounter the same pointer 91 | again, you will get the same map entry. The GC must guarantee that 92 | using pointers as map keys will work even if the pointers move." 93 | 94 | - Ian Lance Taylor on golang-nuts (2016 June 20). 95 | */ 96 | type Seen map[interface{}]struct{} 97 | 98 | func NewSeen() Seen { 99 | return Seen(make(map[interface{}]struct{})) 100 | } 101 | 102 | // end supporting SexpString structs 103 | -------------------------------------------------------------------------------- /zygo/printstate_test.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | cv "github.com/glycerine/goconvey/convey" 5 | "testing" 6 | ) 7 | 8 | func Test040SeenMapWorks(t *testing.T) { 9 | 10 | cv.Convey(`To allow cycle detection, given a set of pointers of various types, Seen should set and note when they have seen.`, t, func() { 11 | ps := NewPrintState() 12 | a := &SexpPair{} 13 | b := &SexpPointer{} 14 | d := &SexpStr{} 15 | cv.So(ps.GetSeen(a), cv.ShouldBeFalse) 16 | cv.So(ps.GetSeen(b), cv.ShouldBeFalse) 17 | cv.So(ps.GetSeen(d), cv.ShouldBeFalse) 18 | 19 | ps.SetSeen(a, "a") 20 | ps.SetSeen(b, "b") 21 | 22 | cv.So(ps.GetSeen(a), cv.ShouldBeTrue) 23 | cv.So(ps.GetSeen(b), cv.ShouldBeTrue) 24 | cv.So(ps.GetSeen(d), cv.ShouldBeFalse) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /zygo/ptrcheck.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // true if target is type *T where T 8 | // is a struct/string/int/other-non-pointer type. 9 | func IsExactlySinglePointer(target interface{}) bool { 10 | // va, isVa := target.(reflect.Value) 11 | // if isVa { 12 | // return IsExactlySinglePointerType(va.Type()) 13 | // } 14 | typ := reflect.ValueOf(target).Type() 15 | return IsExactlySinglePointerType(typ) 16 | } 17 | func IsExactlySinglePointerType(typ reflect.Type) bool { 18 | kind := typ.Kind() 19 | if kind != reflect.Ptr { 20 | return false 21 | } 22 | typ2 := typ.Elem() 23 | kind2 := typ2.Kind() 24 | if kind2 == reflect.Ptr { 25 | return false // two level pointer 26 | } 27 | return true 28 | } 29 | 30 | // true if target is of type **T where T is 31 | // a struct/string/int/other-non-pointer type. 32 | func IsExactlyDoublePointer(target interface{}) bool { 33 | typ := reflect.ValueOf(target).Type() 34 | kind := typ.Kind() 35 | if kind != reflect.Ptr { 36 | return false 37 | } 38 | typ2 := typ.Elem() 39 | kind2 := typ2.Kind() 40 | if kind2 != reflect.Ptr { 41 | return false 42 | } 43 | if typ2.Elem().Kind() == reflect.Ptr { 44 | return false // triple level pointer, not double. 45 | } 46 | return true 47 | } 48 | 49 | func PointerDepth(typ reflect.Type) int { 50 | return pointerDepthHelper(typ, 0) 51 | } 52 | 53 | func pointerDepthHelper(typ reflect.Type, accum int) int { 54 | kind := typ.Kind() 55 | if kind != reflect.Ptr { 56 | return accum 57 | } 58 | return pointerDepthHelper(typ.Elem(), accum+1) 59 | } 60 | -------------------------------------------------------------------------------- /zygo/random.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | var defaultRand = rand.New(rand.NewSource(time.Now().Unix())) 9 | 10 | func RandomFunction(env *Zlisp, name string, 11 | args []Sexp) (Sexp, error) { 12 | return &SexpFloat{Val: defaultRand.Float64()}, nil 13 | } 14 | 15 | func (env *Zlisp) ImportRandom() { 16 | env.AddFunction("random", RandomFunction) 17 | } 18 | -------------------------------------------------------------------------------- /zygo/rawutils.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func MakeRaw(args []Sexp) (*SexpRaw, error) { 9 | raw := make([]byte, 0) 10 | for i := 0; i < len(args); i++ { 11 | switch e := args[i].(type) { 12 | case *SexpStr: 13 | a := []byte(e.S) 14 | raw = append(raw, a...) 15 | default: 16 | return &SexpRaw{}, 17 | fmt.Errorf("raw takes only string arguments. We see %T: '%v'", e, e) 18 | } 19 | } 20 | return &SexpRaw{Val: raw}, nil 21 | } 22 | 23 | func RawToStringFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 24 | if len(args) != 1 { 25 | return SexpNull, WrongNargs 26 | } 27 | 28 | switch t := args[0].(type) { 29 | case *SexpRaw: 30 | return &SexpStr{S: string(t.Val)}, nil 31 | } 32 | return SexpNull, errors.New("argument must be raw") 33 | } 34 | -------------------------------------------------------------------------------- /zygo/regexp.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | ) 8 | 9 | type SexpRegexp regexp.Regexp 10 | 11 | func (re *SexpRegexp) SexpString(ps *PrintState) string { 12 | r := (*regexp.Regexp)(re) 13 | return fmt.Sprintf(`(regexpCompile "%v")`, r.String()) 14 | } 15 | 16 | func (r *SexpRegexp) Type() *RegisteredType { 17 | return nil // TODO what should this be? 18 | } 19 | 20 | func regexpFindIndex(env *Zlisp, 21 | needle *regexp.Regexp, haystack string) (Sexp, error) { 22 | 23 | loc := needle.FindStringIndex(haystack) 24 | 25 | arr := make([]Sexp, len(loc)) 26 | for i := range arr { 27 | arr[i] = Sexp(&SexpInt{Val: int64(loc[i])}) 28 | } 29 | 30 | return &SexpArray{Val: arr, Env: env}, nil 31 | } 32 | 33 | func RegexpFind(env *Zlisp, name string, 34 | args []Sexp) (Sexp, error) { 35 | if len(args) != 2 { 36 | return SexpNull, WrongNargs 37 | } 38 | var haystack string 39 | switch t := args[1].(type) { 40 | case *SexpStr: 41 | haystack = t.S 42 | default: 43 | return SexpNull, 44 | errors.New(fmt.Sprintf("2nd argument of %v should be a string", name)) 45 | } 46 | 47 | var needle *regexp.Regexp 48 | switch t := args[0].(type) { 49 | case *SexpRegexp: 50 | needle = (*regexp.Regexp)(t) 51 | default: 52 | return SexpNull, 53 | errors.New(fmt.Sprintf("1st argument of %v should be a compiled regular expression", name)) 54 | } 55 | 56 | switch name { 57 | case "regexpFind": 58 | str := needle.FindString(haystack) 59 | return &SexpStr{S: str}, nil 60 | case "regexpFindIndex": 61 | return regexpFindIndex(env, needle, haystack) 62 | case "regexpMatch": 63 | matches := needle.MatchString(haystack) 64 | return &SexpBool{Val: matches}, nil 65 | } 66 | 67 | return SexpNull, errors.New("unknown function") 68 | } 69 | 70 | func RegexpCompile(env *Zlisp, name string, 71 | args []Sexp) (Sexp, error) { 72 | if len(args) < 1 { 73 | return SexpNull, WrongNargs 74 | } 75 | 76 | var re string 77 | switch t := args[0].(type) { 78 | case *SexpStr: 79 | re = t.S 80 | default: 81 | return SexpNull, 82 | errors.New("argument of regexpCompile should be a string") 83 | } 84 | 85 | r, err := regexp.Compile(re) 86 | 87 | if err != nil { 88 | return SexpNull, errors.New( 89 | fmt.Sprintf("error during regexpCompile: '%v'", err)) 90 | } 91 | 92 | return Sexp((*SexpRegexp)(r)), nil 93 | } 94 | 95 | func (env *Zlisp) ImportRegex() { 96 | env.AddFunction("regexpCompile", RegexpCompile) 97 | env.AddFunction("regexpFindIndex", RegexpFind) 98 | env.AddFunction("regexpFind", RegexpFind) 99 | env.AddFunction("regexpMatch", RegexpFind) 100 | } 101 | -------------------------------------------------------------------------------- /zygo/reuse_test.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "github.com/shurcooL/go-goon" 5 | "testing" 6 | 7 | cv "github.com/glycerine/goconvey/convey" 8 | ) 9 | 10 | func Test101ConversionToAndFromMsgpackAndJson(t *testing.T) { 11 | 12 | cv.Convey(` 13 | SexpToGo() should notice when it sees the same hash/with-shadow-struct re-used, 14 | and doesn't generate a 2nd shadow struct but instead re-uses the prior one 15 | `, t, func() { 16 | event := `(def reUseMe (snoopy id:123));(def dad (hornet id:8 friends:[reUseMe])); (def mom (hellcat id:7 friends:[reUseMe]));(setOfPlanes flyers:[mom dad])` 17 | env := NewZlisp() 18 | defer env.Close() 19 | 20 | env.StandardSetup() 21 | env.ImportDemoData() 22 | 23 | x, err := env.EvalString(event) 24 | panicOn(err) 25 | P("\n x = %#v /\n\n string: '%s'\n", x, x.SexpString(nil)) 26 | 27 | var set SetOfPlanes 28 | _, err = SexpToGoStructs(x, &set, env, nil, 0, &set) 29 | panicOn(err) 30 | P("\n set = %#v\n", set) 31 | goon.Dump(set) 32 | shared := &Snoopy{ 33 | Plane: Plane{ 34 | ID: 123, 35 | }} 36 | _ = shared 37 | cv.So(&set, cv.ShouldResemble, &SetOfPlanes{Flyers: []Flyer{&Hellcat{Plane: Plane{ID: 7, Friends: []Flyer{shared}}}, &Hornet{Plane: Plane{ID: 8, Friends: []Flyer{shared}}}}}) 38 | 39 | // should actually *be* the same struct pointed to. 40 | ptr0 := set.Flyers[0].(*Hellcat).Friends[0].(*Snoopy) 41 | ptr1 := set.Flyers[1].(*Hornet).Friends[0].(*Snoopy) 42 | P("ptr0 = %p, ptr1 = %p", ptr0, ptr1) 43 | cv.So(ptr0, cv.ShouldEqual, ptr1) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /zygo/source.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | ) 10 | 11 | // alternative. simpler, currently panics. 12 | func SimpleSourceFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 13 | if len(args) != 1 { 14 | return SexpNull, WrongNargs 15 | } 16 | 17 | src, isStr := args[0].(*SexpStr) 18 | if !isStr { 19 | return SexpNull, fmt.Errorf("-> error: first argument must be a string") 20 | } 21 | 22 | file := src.S 23 | if !FileExists(file) { 24 | return SexpNull, fmt.Errorf("path '%s' does not exist", file) 25 | } 26 | 27 | env2 := env.Duplicate() 28 | 29 | f, err := os.Open(file) 30 | if err != nil { 31 | return SexpNull, err 32 | } 33 | defer f.Close() 34 | 35 | err = env2.LoadFile(f) 36 | if err != nil { 37 | return SexpNull, err 38 | } 39 | 40 | _, err = env2.Run() 41 | 42 | return SexpNull, err 43 | } 44 | 45 | // existing 46 | 47 | // SourceExpressions, this should be called from a user func context 48 | func (env *Zlisp) SourceExpressions(expressions []Sexp) error { 49 | gen := NewGenerator(env) 50 | 51 | err := gen.GenerateBegin(expressions) 52 | if err != nil { 53 | return err 54 | } 55 | //P("debug: in SourceExpressions, FROM expressions='%s'", (&SexpArray{Val: expressions, Env: env}).SexpString(0)) 56 | //P("debug: in SourceExpressions, gen=") 57 | //DumpFunction(ZlispFunction(gen.instructions), -1) 58 | curfunc := env.curfunc 59 | curpc := env.pc 60 | 61 | env.curfunc = env.MakeFunction("__source", 0, false, 62 | gen.instructions, nil) 63 | env.pc = 0 64 | 65 | result, err := env.Run() 66 | if err != nil { 67 | return err 68 | } 69 | 70 | //P("end of SourceExpressions, result going onto datastack is: '%s'", result.SexpString(0)) 71 | env.datastack.PushExpr(result) 72 | 73 | //P("debug done with Run in source, now stack is:") 74 | //env.datastack.PrintStack() 75 | 76 | env.pc = curpc 77 | env.curfunc = curfunc 78 | 79 | return nil 80 | } 81 | 82 | func (env *Zlisp) SourceStream(stream io.RuneScanner) error { 83 | env.parser.ResetAddNewInput(stream) 84 | expressions, err := env.parser.ParseTokens() 85 | if err != nil { 86 | return errors.New(fmt.Sprintf( 87 | "Error parsing on line %d: %v\n", env.parser.Linenum(), err)) 88 | } 89 | 90 | // like LoadExpressions in environment.go, remove comments. 91 | expressions = env.FilterArray(expressions, RemoveCommentsFilter) 92 | 93 | return env.SourceExpressions(expressions) 94 | } 95 | 96 | func (env *Zlisp) SourceFile(file *os.File) error { 97 | return env.SourceStream(bufio.NewReader(file)) 98 | } 99 | 100 | func SourceFileFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 101 | if len(args) < 1 { 102 | return SexpNull, WrongNargs 103 | } 104 | 105 | for _, v := range args { 106 | if err := env.sourceItem(v); err != nil { 107 | return SexpNull, err 108 | } 109 | } 110 | 111 | result, err := env.datastack.PopExpr() 112 | if err != nil { 113 | return SexpNull, err 114 | } 115 | return result, nil 116 | } 117 | 118 | // helper for SourceFileFunction recursion 119 | func (env *Zlisp) sourceItem(item Sexp) error { 120 | switch t := item.(type) { 121 | case *SexpArray: 122 | for _, v := range t.Val { 123 | if err := env.sourceItem(v); err != nil { 124 | return err 125 | } 126 | } 127 | case *SexpPair: 128 | expr := item 129 | for expr != SexpNull { 130 | list := expr.(*SexpPair) 131 | if err := env.sourceItem(list.Head); err != nil { 132 | return err 133 | } 134 | expr = list.Tail 135 | } 136 | case *SexpStr: 137 | var f *os.File 138 | var err error 139 | 140 | if f, err = os.Open(t.S); err != nil { 141 | return err 142 | } 143 | defer f.Close() 144 | if err = env.SourceFile(f); err != nil { 145 | return err 146 | } 147 | 148 | default: 149 | return fmt.Errorf("source: Expected `string`, `list`, `array`. Instead found type %T val %v", item, item) 150 | } 151 | 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /zygo/stack_test.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | cv "github.com/glycerine/goconvey/convey" 8 | ) 9 | 10 | func Test020StacksDontAlias(t *testing.T) { 11 | 12 | cv.Convey(`stack.Clone() should avoid all aliasing, as should Pop()`, t, func() { 13 | env := NewZlisp() 14 | defer env.Close() 15 | 16 | t := env.NewStack(5) 17 | a := env.NewScope() 18 | b := env.NewScope() 19 | c := env.NewScope() 20 | 21 | t.Push(a) 22 | t.Push(b) 23 | 24 | show := func(s *Stack, b string) { 25 | pr, _ := s.Show(env, NewPrintState(), b) 26 | fmt.Println(pr) 27 | } 28 | show(t, "t") 29 | 30 | r := t.Clone() 31 | show(r, "r") 32 | 33 | t.Push(c) 34 | 35 | show(t, "t after t.push(c)") 36 | show(r, "r after t.push(c)") 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /zygo/strutils.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func ConcatStr(str *SexpStr, rest []Sexp) (*SexpStr, error) { 10 | res := &SexpStr{S: str.S} 11 | for i, x := range rest { 12 | switch t := x.(type) { 13 | case *SexpStr: 14 | res.S += t.S 15 | case *SexpChar: 16 | res.S += string(t.Val) 17 | default: 18 | return &SexpStr{}, fmt.Errorf("ConcatStr error: %d-th argument (0-based) is "+ 19 | "not a string (was %T)", i, t) 20 | } 21 | } 22 | 23 | return res, nil 24 | } 25 | 26 | func AppendStr(str *SexpStr, expr Sexp) (*SexpStr, error) { 27 | var chr *SexpChar 28 | switch t := expr.(type) { 29 | case *SexpChar: 30 | chr = t 31 | case *SexpStr: 32 | return &SexpStr{S: str.S + t.S}, nil 33 | default: 34 | return &SexpStr{}, errors.New("second argument is not a char") 35 | } 36 | 37 | return &SexpStr{S: str.S + string(chr.Val)}, nil 38 | } 39 | 40 | func StringUtilFunction(name string) ZlispUserFunction { 41 | return func(env *Zlisp, _ string, args []Sexp) (Sexp, error) { 42 | if len(args) != 1 { 43 | return SexpNull, WrongNargs 44 | } 45 | var s string 46 | switch str := args[0].(type) { 47 | case *SexpStr: 48 | s = str.S 49 | default: 50 | return SexpNull, fmt.Errorf("string required, got %T", s) 51 | } 52 | 53 | switch name { 54 | case "chomp": 55 | n := len(s) 56 | if n > 0 && s[n-1] == '\n' { 57 | return &SexpStr{S: s[:n-1]}, nil 58 | } 59 | return &SexpStr{S: s}, nil 60 | case "trim": 61 | return &SexpStr{S: strings.TrimSpace(s)}, nil 62 | } 63 | return SexpNull, fmt.Errorf("unrecognized command '%s'", name) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /zygo/system.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | var ShellCmd string = "/bin/bash" 12 | 13 | func init() { 14 | SetShellCmd() 15 | } 16 | 17 | // set ShellCmd as used by SystemFunction 18 | func SetShellCmd() { 19 | if runtime.GOOS == "windows" { 20 | ShellCmd = os.Getenv("COMSPEC") 21 | return 22 | } 23 | try := []string{"/usr/bin/bash"} 24 | if !FileExists(ShellCmd) { 25 | for i := range try { 26 | b := try[i] 27 | if FileExists(b) { 28 | ShellCmd = b 29 | return 30 | } 31 | } 32 | } 33 | } 34 | 35 | // sys is a builder. shell out, return the combined output. 36 | func SystemBuilder(env *Zlisp, name string, args []Sexp) (Sexp, error) { 37 | //P("SystemBuilder called with args='%#v'", args) 38 | return SystemFunction(env, name, args) 39 | } 40 | 41 | func SystemFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 42 | if len(args) == 0 { 43 | return SexpNull, WrongNargs 44 | } 45 | 46 | flat, err := flattenToWordsHelper(args) 47 | if err != nil { 48 | return SexpNull, fmt.Errorf("flatten on '%#v' failed with error '%s'", args, err) 49 | } 50 | if len(flat) == 0 { 51 | return SexpNull, WrongNargs 52 | } 53 | 54 | joined := strings.Join(flat, " ") 55 | cmd := ShellCmd 56 | 57 | var out []byte 58 | if runtime.GOOS == "windows" { 59 | out, err = exec.Command(cmd, "/c", joined).CombinedOutput() 60 | } else { 61 | out, err = exec.Command(cmd, "-c", joined).CombinedOutput() 62 | } 63 | if err != nil { 64 | return SexpNull, fmt.Errorf("error from command: '%s'. Output:'%s'", err, string(Chomp(out))) 65 | } 66 | return &SexpStr{S: string(Chomp(out))}, nil 67 | } 68 | 69 | // given strings/lists of strings with possible whitespace 70 | // flatten out to a array of SexpStr with no internal whitespace, 71 | // suitable for passing along to (system) / exec.Command() 72 | func FlattenToWordsFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { 73 | if len(args) == 0 { 74 | return SexpNull, WrongNargs 75 | } 76 | stringArgs, err := flattenToWordsHelper(args) 77 | if err != nil { 78 | return SexpNull, err 79 | } 80 | 81 | // Now convert to []Sexp{SexpStr} 82 | res := make([]Sexp, len(stringArgs)) 83 | for i := range stringArgs { 84 | res[i] = &SexpStr{S: stringArgs[i]} 85 | } 86 | return env.NewSexpArray(res), nil 87 | } 88 | 89 | func flattenToWordsHelper(args []Sexp) ([]string, error) { 90 | stringArgs := []string{} 91 | 92 | for i := range args { 93 | switch c := args[i].(type) { 94 | case *SexpStr: 95 | many := strings.Split(c.S, " ") 96 | stringArgs = append(stringArgs, many...) 97 | case *SexpSymbol: 98 | stringArgs = append(stringArgs, c.name) 99 | case *SexpPair: 100 | carry, err := ListToArray(c) 101 | if err != nil { 102 | return []string{}, fmt.Errorf("tried to convert list of strings to array but failed with error '%s'. Input was type %T / val = '%#v'", err, c, c) 103 | } 104 | moreWords, err := flattenToWordsHelper(carry) 105 | if err != nil { 106 | return []string{}, err 107 | } 108 | stringArgs = append(stringArgs, moreWords...) 109 | default: 110 | return []string{}, fmt.Errorf("arguments to system must be strings; instead we have %T / val = '%#v'", c, c) 111 | } 112 | } // end i over args 113 | // INVAR: stringArgs has our flattened list. 114 | return stringArgs, nil 115 | } 116 | 117 | func Chomp(by []byte) []byte { 118 | if len(by) > 0 { 119 | n := len(by) 120 | if by[n-1] == '\n' { 121 | return by[:n-1] 122 | } 123 | } 124 | return by 125 | } 126 | -------------------------------------------------------------------------------- /zygo/time.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | var UtcTz *time.Location 10 | var NYC *time.Location 11 | 12 | func init() { 13 | var err error 14 | UtcTz, err = time.LoadLocation("UTC") 15 | panicOn(err) 16 | NYC, err = time.LoadLocation("America/New_York") 17 | panicOn(err) 18 | } 19 | 20 | type SexpTime struct { 21 | Tm time.Time 22 | } 23 | 24 | func (r *SexpTime) Type() *RegisteredType { 25 | return nil // TODO what should this be? 26 | } 27 | 28 | func (t *SexpTime) SexpString(ps *PrintState) string { 29 | return t.Tm.String() 30 | } 31 | 32 | func NowFunction(env *Zlisp, name string, 33 | args []Sexp) (Sexp, error) { 34 | return &SexpTime{Tm: time.Now()}, nil 35 | } 36 | 37 | // string -> time.Time 38 | func AsTmFunction(env *Zlisp, name string, 39 | args []Sexp) (Sexp, error) { 40 | 41 | if len(args) != 1 { 42 | return SexpNull, WrongNargs 43 | } 44 | 45 | var str *SexpStr 46 | switch t := args[0].(type) { 47 | case *SexpStr: 48 | str = t 49 | default: 50 | return SexpNull, 51 | errors.New("argument of astm should be a string RFC3999Nano timestamp that we want to convert to time.Time") 52 | } 53 | 54 | tm, err := time.ParseInLocation(time.RFC3339Nano, str.S, NYC) 55 | if err != nil { 56 | return SexpNull, err 57 | } 58 | return &SexpTime{Tm: tm.In(NYC)}, nil 59 | } 60 | 61 | func TimeitFunction(env *Zlisp, name string, 62 | args []Sexp) (Sexp, error) { 63 | nargs := len(args) 64 | if nargs != 1 && nargs != 2 { 65 | return SexpNull, WrongNargs 66 | } 67 | 68 | var fun *SexpFunction 69 | switch t := args[0].(type) { 70 | case *SexpFunction: 71 | fun = t 72 | default: 73 | return SexpNull, 74 | errors.New("1st argument of timeit should be function") 75 | } 76 | 77 | starttime := time.Now() 78 | maxseconds := 10.0 79 | iterations := int64(1) 80 | if nargs == 2 { 81 | switch t := args[1].(type) { 82 | case *SexpInt: 83 | iterations = t.Val 84 | case *SexpUint64: 85 | iterations = int64(t.Val) 86 | case *SexpFloat: 87 | iterations = int64(t.Val) 88 | default: 89 | return SexpNull, 90 | fmt.Errorf("2nd argument to timeit should be the iteration count (default 1); got type '%T'", args[1]) 91 | } 92 | } 93 | //fmt.Printf("nargs = %v; iterations = %v\n", nargs, iterations) 94 | for i := int64(0); i < iterations; i++ { 95 | _, err := env.Apply(fun, []Sexp{}) 96 | if err != nil { 97 | return SexpNull, err 98 | } 99 | if nargs == 1 { 100 | // only limit to 10 seconds if using default iteration count. 101 | elapsed := time.Since(starttime) 102 | if elapsed.Seconds() > maxseconds { 103 | break 104 | } 105 | } 106 | } 107 | 108 | elapsed := time.Since(starttime) 109 | fmt.Printf("ran %d iterations in %f seconds\n", 110 | iterations, elapsed.Seconds()) 111 | fmt.Printf("average %f seconds per run\n", 112 | elapsed.Seconds()/float64(iterations)) 113 | 114 | return SexpNull, nil 115 | } 116 | 117 | func MillisFunction(env *Zlisp, name string, 118 | args []Sexp) (Sexp, error) { 119 | millis := time.Now().UnixNano() / 1000000 120 | return &SexpInt{Val: int64(millis)}, nil 121 | } 122 | 123 | func (env *Zlisp) ImportTime() { 124 | env.AddFunction("now", NowFunction) 125 | env.AddFunction("timeit", TimeitFunction) 126 | env.AddFunction("astm", AsTmFunction) 127 | env.AddFunction("millis", MillisFunction) 128 | } 129 | -------------------------------------------------------------------------------- /zygo/typeutils.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func IsArray(expr Sexp) bool { 9 | switch expr.(type) { 10 | case *SexpArray: 11 | return true 12 | } 13 | return false 14 | } 15 | 16 | func IsList(expr Sexp) bool { 17 | if expr == SexpNull { 18 | return true 19 | } 20 | switch list := expr.(type) { 21 | case *SexpPair: 22 | return IsList(list.Tail) 23 | } 24 | return false 25 | } 26 | 27 | func IsAssignmentList(expr Sexp, pos int) (bool, int) { 28 | if expr == SexpNull { 29 | return false, -1 30 | } 31 | switch list := expr.(type) { 32 | case *SexpPair: 33 | sym, isSym := list.Head.(*SexpSymbol) 34 | if !isSym { 35 | return IsAssignmentList(list.Tail, pos+1) 36 | } 37 | if sym.name == "=" || sym.name == ":=" { 38 | return true, pos 39 | } 40 | return IsAssignmentList(list.Tail, pos+1) 41 | } 42 | return false, -1 43 | } 44 | 45 | func IsFloat(expr Sexp) bool { 46 | switch expr.(type) { 47 | case *SexpFloat: 48 | return true 49 | } 50 | return false 51 | } 52 | 53 | func IsInt(expr Sexp) bool { 54 | switch expr.(type) { 55 | case *SexpInt: 56 | return true 57 | } 58 | return false 59 | } 60 | 61 | func IsString(expr Sexp) bool { 62 | switch expr.(type) { 63 | case *SexpStr: 64 | return true 65 | } 66 | return false 67 | } 68 | 69 | func IsChar(expr Sexp) bool { 70 | switch expr.(type) { 71 | case *SexpChar: 72 | return true 73 | } 74 | return false 75 | } 76 | 77 | func IsNumber(expr Sexp) bool { 78 | switch expr.(type) { 79 | case *SexpFloat: 80 | return true 81 | case *SexpInt: 82 | return true 83 | case *SexpChar: 84 | return true 85 | } 86 | return false 87 | } 88 | 89 | func IsSymbol(expr Sexp) bool { 90 | switch expr.(type) { 91 | case *SexpSymbol: 92 | return true 93 | } 94 | return false 95 | } 96 | 97 | func IsHash(expr Sexp) bool { 98 | switch expr.(type) { 99 | case *SexpHash: 100 | return true 101 | } 102 | return false 103 | } 104 | 105 | func IsZero(expr Sexp) bool { 106 | switch e := expr.(type) { 107 | case *SexpInt: 108 | return int(e.Val) == 0 109 | case *SexpChar: 110 | return int(e.Val) == 0 111 | case *SexpFloat: 112 | return float64(e.Val) == 0.0 113 | } 114 | return false 115 | } 116 | 117 | func IsEmpty(expr Sexp) bool { 118 | if expr == SexpNull { 119 | return true 120 | } 121 | 122 | switch e := expr.(type) { 123 | case *SexpArray: 124 | return len(e.Val) == 0 125 | case *SexpHash: 126 | return HashIsEmpty(e) 127 | } 128 | 129 | return false 130 | } 131 | 132 | func IsFunc(expr Sexp) bool { 133 | switch expr.(type) { 134 | case *SexpFunction: 135 | return true 136 | } 137 | return false 138 | } 139 | 140 | func TypeOf(expr Sexp) *SexpStr { 141 | v := "" 142 | switch e := expr.(type) { 143 | case *SexpRaw: 144 | v = "raw" 145 | case *SexpBool: 146 | v = "bool" 147 | case *SexpArray: 148 | v = "array" 149 | case *SexpInt: 150 | v = "int64" 151 | case *SexpUint64: 152 | v = "uint64" 153 | case *SexpStr: 154 | v = "string" 155 | case *SexpChar: 156 | v = "char" 157 | case *SexpFloat: 158 | v = "float64" 159 | case *SexpHash: 160 | v = e.TypeName 161 | case *SexpPair: 162 | v = "list" 163 | case *SexpSymbol: 164 | v = "symbol" 165 | case *SexpFunction: 166 | v = "func" 167 | case *SexpSentinel: 168 | v = "nil" 169 | case *SexpTime: 170 | v = "time.Time" 171 | case *RegisteredType: 172 | v = "regtype" 173 | case *SexpPointer: 174 | v = e.MyType.RegisteredName 175 | case *SexpArraySelector: 176 | v = "arraySelector" 177 | case *SexpHashSelector: 178 | v = "hashSelector" 179 | case *SexpReflect: 180 | rt := expr.Type() 181 | if rt != nil { 182 | return &SexpStr{S: rt.RegisteredName} 183 | } 184 | //v = reflect.Value(e).Type().Name() 185 | //if v == "Ptr" { 186 | // v = reflect.Value(e).Type().Elem().Kind().String() 187 | //} 188 | kind := reflect.Value(e.Val).Type().Kind() 189 | if kind == reflect.Ptr { 190 | v = reflect.Value(e.Val).Elem().Type().Name() 191 | } else { 192 | P("kind = %v", kind) 193 | v = "reflect.Value" 194 | } 195 | case *Stack: 196 | if e.IsPackage { 197 | v = "package" 198 | } else { 199 | v = "stack" 200 | } 201 | default: 202 | fmt.Printf("\n error: unknown type: %T in '%#v'\n", e, e) 203 | } 204 | return &SexpStr{S: v} 205 | } 206 | -------------------------------------------------------------------------------- /zygo/version.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import "fmt" 4 | 5 | // version information. See Makefile and gitcommit.go for update/init. 6 | var GITLASTTAG string 7 | var GITLASTCOMMIT string 8 | 9 | func Version() string { 10 | return fmt.Sprintf("%s/%s", GITLASTTAG, GITLASTCOMMIT) 11 | } 12 | -------------------------------------------------------------------------------- /zygo/vprint.go: -------------------------------------------------------------------------------- 1 | package zygo 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path" 8 | "runtime" 9 | "runtime/debug" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | var Verbose bool = true // set to true to debug 15 | var Working bool // currently under investigation 16 | 17 | // var V = VPrintf 18 | var W = WPrintf 19 | var Q = func(quietly_ignored ...interface{}) {} // quiet 20 | 21 | var vv = VPrintf2 22 | 23 | // P is a shortcut for a call to fmt.Printf that implicitly starts 24 | // and ends its message with a newline. 25 | func P(format string, stuff ...interface{}) { 26 | fmt.Printf("\n "+format+"\n", stuff...) 27 | } 28 | 29 | // get timestamp for logging purposes 30 | func ts() string { 31 | return time.Now().Format("2006-01-02 15:04:05.999 -0700 MST") 32 | } 33 | 34 | // so we can multi write easily, use our own printf 35 | var OurStdout io.Writer = os.Stdout 36 | 37 | // Printf formats according to a format specifier and writes to standard output. 38 | // It returns the number of bytes written and any write error encountered. 39 | func Printf(format string, a ...interface{}) (n int, err error) { 40 | return fmt.Fprintf(OurStdout, format, a...) 41 | } 42 | 43 | var tsPrintfMut sync.Mutex 44 | 45 | // time-stamped printf 46 | func TSPrintf(format string, a ...interface{}) { 47 | tsPrintfMut.Lock() 48 | Printf("\n%s %s ", FileLine(3), ts()) 49 | Printf(format+"\n", a...) 50 | tsPrintfMut.Unlock() 51 | } 52 | 53 | func VPrintf2(format string, a ...interface{}) { 54 | if Verbose { 55 | TSPrintf(format+"\n", a...) 56 | } 57 | } 58 | 59 | func WPrintf(format string, a ...interface{}) { 60 | if Working { 61 | TSPrintf(format+"\n", a...) 62 | } 63 | } 64 | 65 | func FileLine(depth int) string { 66 | _, fileName, fileLine, ok := runtime.Caller(depth) 67 | var s string 68 | if ok { 69 | s = fmt.Sprintf("%s:%d", path.Base(fileName), fileLine) 70 | } else { 71 | s = "" 72 | } 73 | return s 74 | } 75 | 76 | func Caller(upStack int) string { 77 | // elide ourself and runtime.Callers 78 | target := upStack + 2 79 | 80 | pc := make([]uintptr, target+2) 81 | n := runtime.Callers(0, pc) 82 | 83 | f := runtime.Frame{Function: "unknown"} 84 | if n > 0 { 85 | frames := runtime.CallersFrames(pc[:n]) 86 | for i := 0; i <= target; i++ { 87 | contender, more := frames.Next() 88 | if i == target { 89 | f = contender 90 | } 91 | if !more { 92 | break 93 | } 94 | } 95 | } 96 | return f.Function 97 | } 98 | 99 | func stack() string { 100 | return string(debug.Stack()) 101 | } 102 | --------------------------------------------------------------------------------