├── lib ├── os.xin ├── map.xin ├── stat.xin ├── src.xin ├── math.xin ├── test.xin ├── std.xin ├── vec.xin └── str.xin ├── .travis.yml ├── samples ├── quine.xin ├── fact.xin ├── prompt.xin ├── hello.xin ├── first.xin ├── nest-import.xin ├── stream.xin ├── async.xin ├── log-order.xin ├── import.xin ├── prime.xin ├── map.xin ├── fib.xin ├── list.xin ├── pascal-triangle.xin ├── tail-call.xin ├── art │ ├── rand-circle.xin │ ├── demo.xin │ ├── rand-rect.xin │ └── shape.xin ├── uuid.xin ├── pi.xin ├── chart.xin ├── twin-primes.xin ├── file.xin ├── palindrome-product.xin ├── collatz.xin ├── net.xin ├── chat.xin ├── xxd.xin ├── bmp.xin ├── number-letter-counts.xin ├── freq.xin └── test.xin ├── xin.go ├── go.mod ├── .gitignore ├── cmd ├── run.go ├── stdin.go ├── root.go └── repl.go ├── pkg └── xin │ ├── crypto.go │ ├── reader.go │ ├── vm.go │ ├── parse.go │ ├── value.go │ ├── type.go │ ├── lib.go │ ├── math.go │ ├── error.go │ ├── vec.go │ ├── string.go │ ├── map.go │ ├── lex.go │ ├── eval.go │ ├── stream.go │ ├── os.go │ └── runtime.go ├── LICENSE ├── TODO.md ├── Makefile ├── util └── xin.vim ├── go.sum ├── README.md ├── SPEC.md └── statik └── statik.go /lib/os.xin: -------------------------------------------------------------------------------- 1 | ; os interface wrappers 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.x 5 | -------------------------------------------------------------------------------- /samples/quine.xin: -------------------------------------------------------------------------------- 1 | ; quine in xin 2 | 3 | ((: (q) (<- os::stdout (+ '((: ' (+ (str::replace (str q) (+ '') 'q') '))'))))) 4 | -------------------------------------------------------------------------------- /samples/fact.xin: -------------------------------------------------------------------------------- 1 | ; recursive factorial 2 | (: (fact n) 3 | (if (= n 0) 4 | 1 5 | (* n (fact (dec n))))) 6 | 7 | (log (fact 10)) 8 | -------------------------------------------------------------------------------- /xin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/thesephist/xin/cmd" 5 | ) 6 | 7 | // Xin CLI 8 | func main() { 9 | cmd.Execute() 10 | } 11 | -------------------------------------------------------------------------------- /samples/prompt.xin: -------------------------------------------------------------------------------- 1 | (: puts log) 2 | 3 | ; print your name 4 | (puts 'What is your name?') 5 | 6 | (-> os::stdin (: (f user-name) 7 | (puts (+ 'Hello, ' user-name)))) 8 | -------------------------------------------------------------------------------- /samples/hello.xin: -------------------------------------------------------------------------------- 1 | ; basic hello world 2 | (<- os::stdout 'hello world\n') 3 | 4 | ; hello world as a function 5 | (: (say-hello) 6 | (<- os::stdout 'hello world\n')) 7 | (say-hello) 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/thesephist/xin 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/fatih/color v1.9.0 7 | github.com/rakyll/statik v0.1.7 8 | github.com/spf13/cobra v0.0.5 9 | ) 10 | -------------------------------------------------------------------------------- /samples/first.xin: -------------------------------------------------------------------------------- 1 | (: limit 12) 2 | 3 | (: (add a b c) 4 | (+ a (+ b c))) 5 | 6 | (: result 7 | (if 1 8 | (add 4 limit 5) 9 | (* (vec 1 2 3) 3))) 10 | 11 | (log result) 12 | -------------------------------------------------------------------------------- /samples/nest-import.xin: -------------------------------------------------------------------------------- 1 | ; test nested imports by importing import.xin 2 | 3 | (log 'Warming up import cache...') 4 | (import 'fact') 5 | (import 'fib') 6 | 7 | (log 'Testing nested imports...') 8 | (import 'import') 9 | -------------------------------------------------------------------------------- /samples/stream.xin: -------------------------------------------------------------------------------- 1 | ; streams example 2 | (: sm (stream)) 3 | 4 | (stream::set-sink! 5 | sm log) 6 | 7 | (stream::set-source! 8 | sm 9 | (: (f) 42)) 10 | 11 | (log 'should print 42') 12 | (-> sm log) 13 | (log 'should print 39') 14 | (<- sm 39) 15 | -------------------------------------------------------------------------------- /samples/async.xin: -------------------------------------------------------------------------------- 1 | (: (say-with-pause secs saying) 2 | (os::wait secs (: (f) (log saying)))) 3 | 4 | (do 5 | (say-with-pause 2 'after two -- should be last') 6 | (say-with-pause 1.5 'in the middle') 7 | (say-with-pause 1 'after one')) 8 | 9 | (say-with-pause .5 'this should be first') 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # any generated bitmap image files 15 | *.bmp 16 | -------------------------------------------------------------------------------- /samples/log-order.xin: -------------------------------------------------------------------------------- 1 | ; guaranteed write order 2 | (loop 20 3 | (: (f) 4 | (do 5 | (log 'hi') 6 | (log 'hello')))) 7 | 8 | ; async write order 9 | (loop 20 10 | (: (f) 11 | (do 12 | (<- os::stdout 'hi\n') 13 | (<- os::stdout 'hello\n')))) 14 | -------------------------------------------------------------------------------- /samples/import.xin: -------------------------------------------------------------------------------- 1 | ; testing imports 2 | 3 | (log 'Testing imports, should only print fact 10 / fib 10:') 4 | 5 | (import 'fact') 6 | (import 'fib' fibonacci) 7 | 8 | (log 'fact 10') 9 | (log (fact 10)) 10 | (log 'fib 10') 11 | (log (fibonacci::fib 10)) 12 | 13 | ; import again to test vm-wide import cache 14 | (import 'fact' factorial) 15 | (import 'fib') 16 | -------------------------------------------------------------------------------- /samples/prime.xin: -------------------------------------------------------------------------------- 1 | ; find first N primes 2 | 3 | (: (primes-in-range ns) 4 | (vec::filter ns math::prime?)) 5 | 6 | (: (count-primes-in-range ns) 7 | (vec::size (primes-in-range ns))) 8 | 9 | ; run a primality test for range under limit 10 | (: limit 10000) 11 | (: primes 12 | (primes-in-range (seq limit))) 13 | (logf 'Number of primes under {}: {}' 14 | (vec limit (vec::size primes))) 15 | -------------------------------------------------------------------------------- /samples/map.xin: -------------------------------------------------------------------------------- 1 | ; map tests 2 | 3 | ; test that we can set each type as the key 4 | (: m (map)) 5 | (: (should-key k) 6 | (map::set! m k 0)) 7 | 8 | (should-key 1) 9 | (should-key 3.141592) 10 | (should-key 'hello world') 11 | (should-key type) 12 | (should-key should-key) 13 | (should-key (vec)) 14 | (should-key (map)) 15 | (should-key (stream)) 16 | 17 | (log 'All Xin types can be used as keys') 18 | -------------------------------------------------------------------------------- /cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "github.com/thesephist/xin/pkg/xin" 6 | ) 7 | 8 | func run(path string) { 9 | vm, err := xin.NewVm() 10 | if err != nil { 11 | color.Red("Error creating Xin VM: %s\n", xin.FormatError(err)) 12 | return 13 | } 14 | 15 | err = vm.Exec(path) 16 | if err != nil { 17 | color.Red("Error: %s\n", xin.FormatError(err)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cmd/stdin.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/fatih/color" 7 | "github.com/thesephist/xin/pkg/xin" 8 | ) 9 | 10 | func stdin() { 11 | vm, err := xin.NewVm() 12 | if err != nil { 13 | color.Red("Error creating Xin VM: %s\n", xin.FormatError(err)) 14 | return 15 | } 16 | 17 | _, err = vm.Eval("stdin", os.Stdin) 18 | if err != nil { 19 | color.Red("Error: %s\n", xin.FormatError(err)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/fib.xin: -------------------------------------------------------------------------------- 1 | ; fibonacci sequence generator 2 | 3 | ; naive approach 4 | (: (fib n) 5 | (if (< n 2) 6 | 1 7 | (+ (fib (- n 1)) 8 | (fib (- n 2))))) 9 | 10 | ; more efficient, O(n) approach 11 | ; that's tail-recursive 12 | (: (fast-fib n) 13 | ((: (f n a b) 14 | (if (< n 2) 15 | b 16 | (f (- n 1) b (+ a b)))) 17 | n 1 1)) 18 | 19 | (: limit 27) 20 | 21 | (log 'Trying naively...') 22 | (log (fib limit)) 23 | (log 'Trying tail-recursively...') 24 | (log (fast-fib limit)) 25 | -------------------------------------------------------------------------------- /pkg/xin/crypto.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | crand "crypto/rand" 5 | "encoding/binary" 6 | "math/rand" 7 | ) 8 | 9 | func cryptoRandForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 10 | buf64 := make([]byte, 8) 11 | _, err := crand.Read(buf64) 12 | if err != nil { 13 | return zeroValue, nil 14 | } 15 | 16 | n, readBytes := binary.Varint(buf64) 17 | if readBytes <= 0 { 18 | return zeroValue, nil 19 | } 20 | 21 | r := rand.New(rand.NewSource(n)) 22 | return FracValue(r.Float64()), nil 23 | } 24 | -------------------------------------------------------------------------------- /lib/map.xin: -------------------------------------------------------------------------------- 1 | ; map standard library 2 | 3 | (: (empty? m) 4 | (= (map::size m) 0)) 5 | 6 | (: (eq? m l) 7 | (& (= (map::size m) (map::size l)) 8 | (vec::every 9 | (vec::map (map::keys m) 10 | (: (cmp-val m-key) 11 | (= (map::get m m-key) (map-get l m-key))))))) 12 | 13 | (: (values m) 14 | (vec::map (map::keys m) 15 | (: (f k) 16 | (map::get m k)) 17 | (vec))) 18 | 19 | (: (entries m) 20 | (vec::map (map::keys m) 21 | (: (f k) 22 | (vec k (map::get m k))) 23 | (vec))) 24 | -------------------------------------------------------------------------------- /samples/list.xin: -------------------------------------------------------------------------------- 1 | ; common list idioms 2 | 3 | (: list-all 4 | (seq 100)) 5 | 6 | (: list 7 | (vec::slice list-all 1 11)) 8 | 9 | (log list) 10 | 11 | (: reduced 12 | (vec::reduce list 13 | (: (f a b) 14 | (+ a b)) 15 | 0)) 16 | (log reduced) 17 | 18 | (vec::each list log) 19 | 20 | (: (sq n) 21 | (* n n)) 22 | (: mapped 23 | (vec::map list sq)) 24 | (log mapped) 25 | 26 | (: (even? n) 27 | (= (% n 2) 0)) 28 | (: filtered 29 | (vec::filter list even?)) 30 | (log filtered) 31 | 32 | (log 'sorted') 33 | (: list (vec 45 2 64 4 -3 7 23 5 1 0 4 -4 3 2)) 34 | (log (vec::sort list)) 35 | (log 'original') 36 | (log list) 37 | -------------------------------------------------------------------------------- /samples/pascal-triangle.xin: -------------------------------------------------------------------------------- 1 | ; generate a pascal's triangle 2 | ; of height n 3 | 4 | (: height 18) 5 | 6 | ; n-choose-m combinatorics 7 | (: (choose n m) 8 | (/ (vec::prod (vec::map (seq m) 9 | (: (f x) 10 | (+ (inc x) (- n m))))) 11 | (vec::prod (vec::map (seq m) 12 | inc)))) 13 | 14 | ; n starts at 0 15 | (: (pascal-row y) 16 | (vec::map (seq (inc y)) 17 | (: (f x) 18 | (choose y x)))) 19 | 20 | ; render the triangle 21 | (vec::each (seq height) 22 | (: (f y) 23 | (log (str::join 24 | (vec::map (pascal-row y) str) 25 | ' ')))) 26 | -------------------------------------------------------------------------------- /samples/tail-call.xin: -------------------------------------------------------------------------------- 1 | ; testing proper tail call support 2 | 3 | ; without TCO, calling this func 4 | ; more than 10M times should overflow 5 | ; the interpreter (Go) stack. 6 | (: (hyper i max) 7 | (do (: next (inc i)) 8 | (if (> i max) 9 | i 10 | (do (if (factor? i 1000000) 11 | (log i) 12 | 0) 13 | (hyper next max))))) 14 | 15 | (hyper 0 10000000) 16 | 17 | ; regression test case for when a lazy 18 | ; variable lookup in scope cascades up 19 | ; and overflows Go stack 20 | (: (sub i j) 21 | (if (= i 1000000) 22 | j 23 | (sub (+ i 1) 24 | (+ j 1)))) 25 | 26 | (log (sub 0 0)) 27 | 28 | ; if not crashed by here, interp has TCO 29 | (log 'Verified proper TCO support.') 30 | -------------------------------------------------------------------------------- /samples/art/rand-circle.xin: -------------------------------------------------------------------------------- 1 | ; xin art library demo 2 | ; random circles! 3 | 4 | (import 'shape') 5 | 6 | (: *width* 200) 7 | (: *height* 320) 8 | (: *count* 150) 9 | 10 | ; create canvas of 120x100 pixels 11 | (: canvas (create-canvas *width* *height*)) 12 | 13 | (: (color-from-frac x) 14 | (rgb (int (* x 255)) 15 | (int (* x 255)) 16 | (int (* x 255)))) 17 | 18 | (loop *count* 19 | (: (_ i) 20 | (draw-circle! canvas 21 | (color-from-frac (/ i (frac *count*))) 22 | (math::rand-int *height*) 23 | (math::rand-int *width*) 24 | (math::rand-int 17)))) 25 | 26 | ; save to file 27 | (write-canvas canvas 28 | 'art.bmp') 29 | -------------------------------------------------------------------------------- /samples/uuid.xin: -------------------------------------------------------------------------------- 1 | ; uuid v4 library 2 | 3 | (: (rand-byte) 4 | (math::urand-int 256)) 5 | 6 | ; generate new UUID 7 | (: (new) 8 | (do (: base (vec::map (seq 16) rand-byte)) 9 | ; version bits 10 | (vec::set! base 6 11 | (+ 64 (% (vec::get base 6) 16))) 12 | ; variant bits 13 | (vec::set! base 8 14 | (+ 128 (% (vec::get base 8) 64))) 15 | (: (u i) 16 | (str::pad-start (hex::enc (vec::get base i)) 17 | 2 18 | '0')) 19 | (str::join (vec (u 0) (u 1) (u 2) (u 3) 20 | '-' (u 4) (u 5) 21 | '-' (u 6) (u 7) 22 | '-' (u 8) (u 9) 23 | '-' (u 10) (u 11) (u 12) (u 13) (u 14) (u 15) 24 | ) 25 | ''))) 26 | -------------------------------------------------------------------------------- /samples/pi.xin: -------------------------------------------------------------------------------- 1 | ; monte-carlo method estimation of pi 2 | 3 | (: (sq n) 4 | (* n n)) 5 | 6 | (: (inside? x y) 7 | (< (+ (sq x) (sq y)) 1)) 8 | 9 | (: (pi-from-ratio ratio) 10 | (* 4 ratio)) 11 | 12 | (: (run-monte-carlo times) 13 | ((: (run i inside-count) 14 | (if (= i times) 15 | (pi-from-ratio (/ inside-count 16 | (frac i))) 17 | (run (inc i) 18 | (if (inside? (math::rand) (math::rand)) 19 | (inc inside-count) 20 | inside-count)))) 0 0)) 21 | 22 | ; run with increasing number of attempts 23 | (: attempts (vec::map (seq 6) (: (f exp) (^ 10 (inc exp))))) 24 | (vec::each attempts 25 | (: (f times) 26 | (logf 'Estimate of Pi with {} MC runs: \t{}' 27 | (vec times (run-monte-carlo times))))) 28 | -------------------------------------------------------------------------------- /samples/art/demo.xin: -------------------------------------------------------------------------------- 1 | ; xin art library demo 2 | 3 | (import 'shape') 4 | 5 | ; create canvas of 120x100 pixels 6 | (: canvas (create-canvas 120 100)) 7 | 8 | ; draw some demo shapes 9 | (draw-point! canvas 10 | (rgb 200 200 200) 11 | 80 90) 12 | (draw-line! canvas 13 | (rgb 255 0 0) 14 | 0 0 99 99) 15 | (draw-line! canvas 16 | (rgb 255 255 0) 17 | 0 0 34 119) 18 | (draw-line! canvas 19 | (rgb 0 255 0) 20 | 0 0 99 24) 21 | (draw-rect! canvas 22 | (rgb 0 0 255) 23 | 25 25 75 75) 24 | (draw-circle! canvas 25 | (rgb 30 30 30) 26 | 40 60 18) 27 | (draw-circle! canvas 28 | (rgb 100 150 180) 29 | 100 90 40) 30 | 31 | ; save to file 32 | (write-canvas canvas 33 | 'art.bmp') 34 | -------------------------------------------------------------------------------- /samples/chart.xin: -------------------------------------------------------------------------------- 1 | ; some simple utilities for graphing stuff in the terminal 2 | 3 | (: (chart-with-symbol symbol f xs) 4 | (do 5 | (: label-width (vec::max (vec::map xs 6 | (: (h x) 7 | (str::size (str x)))))) 8 | (vec::each xs 9 | (: (g x) 10 | (log 11 | (+ (str::pad-start (str x) label-width ' ') 12 | (+ ' ' 13 | (* symbol (f x))))))))) 14 | 15 | (: (chart f xs) 16 | (chart-with-symbol '-' f xs)) 17 | 18 | ; examples 19 | (log 'Squares of natural numbers') 20 | (chart (: (f x) (/ (* x x) 2)) 21 | (nat 12)) 22 | (log 'Number of primes up to a cap') 23 | (chart (: (count-prime cap) 24 | (/ (vec::size (vec::filter (nat cap) math::prime?)) 3)) 25 | (range 100 2000 100)) 26 | -------------------------------------------------------------------------------- /samples/art/rand-rect.xin: -------------------------------------------------------------------------------- 1 | ; xin art library demo 2 | ; random rectangles! 3 | 4 | (import 'shape') 5 | 6 | (: *width* 300) 7 | (: *height* 485) 8 | (: *count* 200) 9 | 10 | ; create canvas of 120x100 pixels 11 | (: canvas (create-canvas *width* *height*)) 12 | 13 | (: (color-from-frac x) 14 | (rgb (int (* x 255)) 15 | (int (* x 255)) 16 | (int (* x 255)))) 17 | 18 | (loop *count* 19 | (: (_ i) 20 | (do 21 | (: x0 (math::rand-int *height*)) 22 | (: y0 (math::rand-int *width*)) 23 | (: x1 (math::rand-int *height*)) 24 | (: y1 (math::rand-int *width*)) 25 | 26 | (draw-rect! canvas 27 | (color-from-frac (/ i (frac *count*))) 28 | (if (< x0 x1) x0 x1) 29 | (if (< y0 y1) y0 y1) 30 | (if (> x0 x1) x0 x1) 31 | (if (> y0 y1) y0 y1))))) 32 | 33 | ; save to file 34 | (write-canvas canvas 35 | 'art.bmp') 36 | -------------------------------------------------------------------------------- /samples/twin-primes.xin: -------------------------------------------------------------------------------- 1 | ; find twin primes under cap 2 | 3 | (: (twin? a b) 4 | (= (+ a 2) b)) 5 | 6 | (: (primes-under cap) 7 | (vec::filter (nat cap) math::prime?)) 8 | 9 | ; shorthand for getting 1st/2nd items from vec 10 | (: (first v) (vec::head v)) 11 | (: (second v) (vec::get v 1)) 12 | 13 | (: (twin-primes-under cap) 14 | ((: (sub acc rest) 15 | (if (< (vec::size rest) 2) 16 | acc 17 | (if (twin? (first rest) (second rest)) 18 | (sub (vec::add! acc (vec (first rest) (second rest))) 19 | (vec::tail rest)) 20 | (sub acc 21 | (vec::tail rest))))) 22 | (vec) (primes-under cap))) 23 | 24 | ; main proc 25 | (: cap 26 | (if (< (vec::size (os::args)) 3) 27 | 100 28 | (int (vec::get (os::args) 2)))) 29 | (: twp (twin-primes-under cap)) 30 | (logf 'Found {} twin primes under {}:' 31 | (vec (vec::size twp) cap)) 32 | (vec::each twp 33 | (: (log-pair pair) 34 | (logf '{} + 2 = {}' pair))) 35 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | const version = "0.1" 11 | 12 | var rootCmd = &cobra.Command{ 13 | Use: "xin [files]", 14 | Short: "Run Xin programs", 15 | Long: "Xin is an extensible, functional, lisp-like general-purpose programming language.", 16 | Example: strings.Join([]string{ 17 | " xin\t\t\tstart a repl", 18 | " xin prog.xin\t\trun prog.xin", 19 | " echo file | xin\trun from stdin", 20 | }, "\n"), 21 | Version: version, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | // check if running files 24 | if len(args) >= 1 { 25 | run(args[0]) 26 | return 27 | } 28 | 29 | // check if there's stdin 30 | stat, _ := os.Stdin.Stat() 31 | if (stat.Mode() & os.ModeCharDevice) == 0 { 32 | stdin() 33 | return 34 | } 35 | 36 | // if all else fails, start repl 37 | repl() 38 | }, 39 | } 40 | 41 | func Execute() error { 42 | rootCmd.SetVersionTemplate(`{{with .Name}}{{printf "%s " .}}{{end}}{{printf "v%s" .Version}} 43 | `) 44 | 45 | return rootCmd.Execute() 46 | } 47 | -------------------------------------------------------------------------------- /lib/stat.xin: -------------------------------------------------------------------------------- 1 | ; statistics library 2 | 3 | ; arithmetic mean 4 | (: (mean xs) 5 | (/ (vec::sum xs) (frac (vec::size xs)))) 6 | 7 | ; geometric mean 8 | (: (geomean xs) 9 | (^ (vec::prod xs) (/ 1.0 (vec::size xs)))) 10 | 11 | (: (median xs) 12 | (if (even? (vec::size xs)) 13 | (do (: half (/ (vec::size xs) 2)) 14 | (mean (vec::slice (vec::sort xs) 15 | (dec half) 16 | (inc half)))) 17 | (vec::get (vec::sort xs) 18 | (/ (vec::size xs) 2) 1))) 19 | 20 | ; non-deterministic mode 21 | (: (mode xs) 22 | (do 23 | (: counts 24 | (vec::reduce xs 25 | (: (push x acc) 26 | (if (map::has? acc x) 27 | (map::set! acc x (inc (map::get acc x))) 28 | (map::set! acc x 1))) 29 | (map))) 30 | (: (get-neg-count pair) 31 | (neg (vec::get pair 1))) 32 | (vec::head (vec::head 33 | (vec::sort-by (map::entries counts) 34 | get-neg-count))))) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Linus Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/repl.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/fatih/color" 11 | "github.com/thesephist/xin/pkg/xin" 12 | ) 13 | 14 | func repl() { 15 | vm, err := xin.NewVm() 16 | if err != nil { 17 | color.Red("Error creating Xin VM: %s\n", xin.FormatError(err)) 18 | return 19 | } 20 | 21 | replCount := 0 22 | reader := bufio.NewReader(os.Stdin) 23 | for { 24 | // TODO: capture SIGINT and just continue loop 25 | // this requires running the VM in a Context{} 26 | 27 | fmt.Printf("%d ) ", replCount) 28 | 29 | text, err := reader.ReadString('\n') 30 | if err == io.EOF { 31 | break 32 | } else if err != nil { 33 | color.Red("Repl error: %s\n\n", err.Error()) 34 | continue 35 | } 36 | 37 | result, ierr := vm.Eval(fmt.Sprintf("input %d", replCount), strings.NewReader(text)) 38 | if ierr != nil { 39 | color.Red("Eval error: %s\n\n", xin.FormatError(ierr)) 40 | continue 41 | } 42 | 43 | vm.Frame.Put( 44 | fmt.Sprintf("_%d", replCount), 45 | result, 46 | ) 47 | color.Yellow("%d ) %s\n\n", replCount, result.Repr()) 48 | 49 | replCount++ 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/file.xin: -------------------------------------------------------------------------------- 1 | ; file io examples 2 | 3 | (: readme-file 4 | (os::open 'README.md')) 5 | 6 | (: new-file-name 7 | 'new.md') 8 | 9 | (: new-file 10 | (os::open new-file-name)) 11 | 12 | (: (hint s) 13 | (log (+ '-> ' s))) 14 | 15 | ; do I/O 16 | 17 | (-> readme-file (: (f data) 18 | (do 19 | (hint 'First 100 bytes of README.md:') 20 | (log (str::slice data 0 100)) 21 | (stream::close! readme-file)))) 22 | 23 | (<- new-file 'new file contents.' 24 | (: (f) 25 | (do 26 | (hint (+ 'Written to ' new-file-name)) 27 | (stream::close! new-file) 28 | 29 | (hint (+ 'Now reading from ' new-file-name)) 30 | (: new-file-read (os::open new-file-name)) 31 | (-> new-file-read 32 | (: (f data) 33 | (do 34 | (log (str::slice data 0 100)) 35 | (stream::close! new-file-read) 36 | 37 | (hint 'Now deleting new.md') 38 | (os::delete new-file-name 39 | (: (f) 40 | (hint 'deleted new.md'))))))))) 41 | -------------------------------------------------------------------------------- /lib/src.xin: -------------------------------------------------------------------------------- 1 | ; serialization/deserialization library 2 | 3 | (: (ser-str v) 4 | (+ '\'' (+ (str::escape v) 5 | '\''))) 6 | 7 | (: (ser-vec v) 8 | (if (vec::empty? v) 9 | '(vec)' 10 | (str::fmt 11 | '(vec {})' 12 | (vec 13 | (str::join (vec::map v serialize) ' '))))) 14 | 15 | (: (ser-map v) 16 | (if (map::empty? v) 17 | '(map)' 18 | (str::fmt 19 | '(do (: m (map)) {})' 20 | (vec 21 | (str::join (vec::map (map::keys m) 22 | (: (f k) 23 | (str::fmt '(map::set! m {} {})' 24 | (vec (serialize k) 25 | (serialize (map::get v k)))))) 26 | ' '))))) 27 | 28 | (: (ser-stream v) 29 | '(stream)') 30 | 31 | (do (: serializers (map)) 32 | (map::set! serializers int str) 33 | (map::set! serializers frac str) 34 | (map::set! serializers str ser-str) 35 | (map::set! serializers vec ser-vec) 36 | (map::set! serializers map ser-map) 37 | (map::set! serializers stream ser-stream)) 38 | (: (serialize v) 39 | ((map::get serializers (type v)) v)) 40 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Todos 2 | 3 | - [ ] Generating generative art postcards with Xin with bmp.xin 4 | - [ ] Runtime stack traces for errors 5 | - [ ] Well thought-out macro system. Define with `::` pass AST as `vec`s. 6 | - `MacroFormValue` 7 | - We need two utility functions, one to convert from `[]*astNode` to `VecValue`, and another to go the other way. 8 | - The main problem here is that we don't have a good way of propagating position and stack trace data thru the macro system. 9 | - I think we need a native AST representation datatypes to do this right, rather than trying to shoehorn AST data into primitive types. 10 | - [ ] Xin should incorporate an intermediate representation that reflects and allows for lots of static analysis. Tending towards a compiler. 11 | - Static analysis: 12 | - Statically resolve references, since Xin is lexically scoped all of the time 13 | - Statically determine places where lazy evaluation has no benefit, and don't lazy-evaluate (remove indirection) 14 | - Inline small functions ("small" here is probably the number of nodes in the AST) 15 | - Statically determine object / value lifetimes and maybe deterministically allocate memory for those slots 16 | -------------------------------------------------------------------------------- /samples/palindrome-product.xin: -------------------------------------------------------------------------------- 1 | ; largest palindrome product of 3-digit numbers 2 | ; project euler: https://projecteuler.net/problem=4 3 | 4 | (: (palindrome? n) 5 | (= (str n) (str::reverse (str n)))) 6 | 7 | ; simple solution, but doesn't scale... 8 | (: (palindrome-prods-between v w) 9 | (vec::filter (vec::combine v w (: (f a b) (* a b))) palindrome?)) 10 | 11 | (: (max-palindrome-prod-between v w) 12 | (vec::max (palindrome-prods-between v w))) 13 | 14 | (: (max-palindrome-prod-under n) 15 | (max-palindrome-prod-between (seq n) (seq n))) 16 | 17 | (log (max-palindrome-prod-under 100)) 18 | 19 | ; more memory-efficient solution 20 | (: (next-i i j cap) 21 | (if (= j cap) 22 | (inc i) 23 | i)) 24 | 25 | (: (next-j i j cap) 26 | (if (= j cap) 27 | (inc i) 28 | (inc j))) 29 | 30 | (: (effi-max-palindrome-prod-under n) 31 | (do (: (sub last-max i j) 32 | (if (& (= i n) (= j n)) 33 | last-max 34 | (do (: product (* i j)) 35 | (if (& (palindrome? product) (> product last-max)) 36 | (sub product (next-i i j n) (next-j i j n)) 37 | (sub last-max (next-i i j n) (next-j i j n)))))) 38 | (sub 0 0 0))) 39 | 40 | (log (effi-max-palindrome-prod-under 1000)) 41 | -------------------------------------------------------------------------------- /samples/collatz.xin: -------------------------------------------------------------------------------- 1 | ; find the longest collatz chain 2 | ; starting from a number less than N 3 | 4 | (: (even? n) 5 | (factor? n 2)) 6 | 7 | (: (collatz-next n) 8 | (if (even? n) 9 | (/ n 2) 10 | (+ 1 (* 3 n)))) 11 | 12 | (: (collatz-seq start) 13 | (do (: (sub n acc) 14 | (do (: next (collatz-next n)) 15 | (if (= next 1) 16 | (vec::add! acc next) 17 | (sub next (vec::add! acc next))))) 18 | (sub start (vec start)))) 19 | 20 | (: (longest-collatz-seq ns) 21 | (vec::reduce ns 22 | (: (f x last) 23 | (do (: this-collatz (collatz-seq x)) 24 | (if (> (vec::size this-collatz) (vec::size last)) 25 | this-collatz 26 | last))) 27 | (vec))) 28 | 29 | (: (longest-collatz-seq-below cap) 30 | (longest-collatz-seq (range 1 cap 1))) 31 | 32 | ; compute the longest collatz seq below given cap 33 | (: cap (if (< (vec::size (os::args)) 3) 34 | 1000 ; 1000 cap default, if none given 35 | (int (vec::get (os::args) 2)))) 36 | (: longest-seq 37 | (longest-collatz-seq-below cap)) 38 | (logf 'Longest Collatz sequence below {} begins with {}, is {} numbers' 39 | (vec cap (vec::head longest-seq) (vec::size longest-seq))) 40 | (log longest-seq) 41 | -------------------------------------------------------------------------------- /samples/net.xin: -------------------------------------------------------------------------------- 1 | ; network interfaces 2 | 3 | ; connection handler 4 | (: (handle conn) 5 | (<- conn 'Welcome to xin' 6 | (: (f) 7 | (-> conn 8 | (: (f data) 9 | (do 10 | (log data) 11 | (stream::close! conn))))))) 12 | 13 | ; start a tcp server 14 | (log 'Starting tcp server at port 9090') 15 | (: close-server 16 | (os::listen 'tcp' 17 | '127.0.0.1:9090' 18 | handle)) 19 | (log 'Started server...') 20 | 21 | ; start a tcp request 22 | (log 'Starting client request...') 23 | (: (request n) 24 | (do 25 | (: conn (os::dial 'tcp' ':9090')) 26 | (<- conn 27 | (str::fmt 'This client message #{}' 28 | (vec n)) 29 | (: (f) 30 | (->> conn 31 | (: (f data) 32 | (if (str::blank? data) 33 | (log 'Empty response.') 34 | (logf 'Response for {}: {}' 35 | (vec n data))))))))) 36 | 37 | ; send 5 immediate, 5 staggered requests 38 | (loop 5 request) 39 | (loop 5 40 | (: (f n) 41 | (os::wait (/ n 4.0) (: (f) 42 | (request n))))) 43 | (os::wait 1.5 (: (f) 44 | (do 45 | (close-server) 46 | (log 'Server closed gracefully.')))) 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CMD = ./xin.go 2 | XIN = go run -race ${CMD} 3 | 4 | all: init gen test 5 | 6 | # initialize development workspace 7 | init: 8 | go get github.com/rakyll/statik 9 | 10 | 11 | test: gen 12 | go build -race -o ./xin 13 | ./xin ./samples/first.xin 14 | ./xin ./samples/hello.xin 15 | ./xin ./samples/fact.xin 16 | ./xin ./samples/fib.xin 17 | ./xin ./samples/prime.xin 18 | ./xin ./samples/twin-primes.xin 19 | ./xin ./samples/collatz.xin 20 | ./xin ./samples/chart.xin 21 | ./xin ./samples/map.xin 22 | ./xin ./samples/list.xin 23 | ./xin ./samples/async.xin 24 | ./xin ./samples/stream.xin 25 | ./xin ./samples/file.xin 26 | ./xin ./samples/nest-import.xin 27 | # we echo in some input for prompt.xin testing stdin 28 | echo "Linus" | ./xin ./samples/prompt.xin 29 | ./xin ./samples/net.xin 30 | ./xin ./samples/xxd.xin ./xin.go 31 | ./xin ./samples/freq.xin ./SPEC.md 32 | ./xin ./samples/test.xin 33 | rm ./xin 34 | 35 | 36 | run-test: gen 37 | ${XIN} ./samples/test.xin 38 | 39 | 40 | # start interactive repl 41 | repl: gen 42 | ${XIN} 43 | 44 | 45 | # re-generate static files 46 | # that are bundled into the executable (standard library) 47 | gen: 48 | statik -src=./lib -f 49 | 50 | 51 | # build for specific OS target 52 | build-%: gen 53 | GOOS=$* GOARCH=amd64 go build -o xin-$* ${CMD} 54 | 55 | 56 | # install on host system 57 | install: gen 58 | cp util/xin.vim ~/.vim/syntax/xin.vim 59 | go install ${CMD} 60 | ls -l `which xin` 61 | 62 | 63 | # build for all OS targets, useful for releases 64 | build: build-linux build-darwin build-windows 65 | 66 | # clean any generated files 67 | clean: 68 | rm -rvf xin xin-* 69 | -------------------------------------------------------------------------------- /samples/chat.xin: -------------------------------------------------------------------------------- 1 | ; tcp chat server 2 | 3 | ; a chat room is a set of clients (tcp connection streams) 4 | (: room (set)) 5 | 6 | ; represents a single client 7 | (: (handle conn) 8 | (do 9 | (set::add! room conn) 10 | (<- conn 'connected.\nname? ' 11 | ; prompt for name 12 | (: (f) 13 | (-> conn 14 | (: (f name) 15 | ; start sending messages 16 | (exchange-messages name conn))))))) 17 | 18 | (: (exchange-messages name conn) 19 | (-> conn 20 | (: (f data) 21 | (if (zero? data) 22 | ; data is EOF, exited chat 23 | (do 24 | (logf '{} exited.' 25 | (vec (str::trim-end name '\r\n')))) 26 | (do 27 | (: msg 28 | (str::fmt '{}: {}' 29 | (vec (str::trim-end name '\r\n') 30 | (str::trim-end data '\r\n')))) 31 | ; log message on server 32 | (log msg) 33 | ; send msg to everyone except myself 34 | (vec::each (set::items room) 35 | (: (send client) 36 | (if (= client conn) 37 | 0 38 | ; messages from others are tabbed out 39 | (<- client (str::fmt '\t{}\n' 40 | (vec msg)))))) 41 | (exchange-messages name conn)))))) 42 | 43 | ; start server 44 | (os::listen 'tcp' '127.0.0.1:9000' 45 | ; send msg to everyone except myself 46 | handle) 47 | (log 'Started chat server.') 48 | -------------------------------------------------------------------------------- /pkg/xin/reader.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "strings" 7 | ) 8 | 9 | type reader struct { 10 | source string 11 | index int 12 | max int 13 | 14 | position 15 | } 16 | 17 | func newReader(path string, r io.Reader) (*reader, error) { 18 | allBytes, err := ioutil.ReadAll(r) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | asString := string(allBytes) 24 | rdr := reader{ 25 | source: asString, 26 | max: len(asString), 27 | } 28 | rdr.position.path = path 29 | 30 | // read shebang line if present 31 | if strings.HasPrefix(asString, "#!") { 32 | rdr.upto("\n") 33 | rdr.skip() 34 | } 35 | 36 | return &rdr, nil 37 | } 38 | func (rdr *reader) done() bool { 39 | return rdr.index >= rdr.max 40 | } 41 | 42 | func (rdr *reader) next() string { 43 | rdr.skip() 44 | return rdr.source[rdr.index-1 : rdr.index] 45 | } 46 | 47 | func (rdr *reader) skip() { 48 | rdr.index++ 49 | if rdr.done() { 50 | return 51 | } 52 | 53 | if rdr.source[rdr.index] == '\n' { 54 | rdr.line++ 55 | rdr.col = 0 56 | } else { 57 | rdr.col++ 58 | } 59 | } 60 | 61 | func (rdr *reader) lookback() string { 62 | return rdr.source[rdr.index-1 : rdr.index] 63 | } 64 | 65 | func (rdr *reader) before(n int) string { 66 | if n > rdr.index { 67 | panic("Tried to look before() start of string") 68 | } 69 | 70 | return rdr.source[rdr.index-n : rdr.index-n+1] 71 | } 72 | 73 | func (rdr *reader) peek() string { 74 | return rdr.source[rdr.index : rdr.index+1] 75 | } 76 | 77 | func (rdr *reader) upto(end string) string { 78 | s := "" 79 | for !rdr.done() && rdr.peek() != end { 80 | s += rdr.next() 81 | } 82 | return s 83 | } 84 | 85 | func (rdr *reader) currPos() position { 86 | return position{ 87 | path: rdr.path, 88 | line: rdr.position.line + 1, 89 | col: rdr.position.col + 1, 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /util/xin.vim: -------------------------------------------------------------------------------- 1 | " place this in the init path (.vimrc) 2 | " au BufNewFile,BufRead *.xin set filetype=xin 3 | 4 | if exists("b:current_syntax") 5 | finish 6 | endif 7 | 8 | " auto-format on write 9 | au BufWritePre *.xin normal gg=G'' 10 | 11 | " xin syntax definition for vi/vim 12 | syntax sync fromstart 13 | 14 | " lisp-style indentation 15 | set lisp 16 | 17 | " booleans 18 | syntax keyword xinBoolean true false 19 | highlight link xinBoolean Boolean 20 | 21 | " numbers should be consumed first by identifiers, so comes before 22 | syntax match xinNumber "\v-?\d+[.\d+]?" 23 | highlight link xinNumber Number 24 | 25 | " builtin functions 26 | syntax keyword xinKeyword if contained 27 | syntax keyword xinKeyword do contained 28 | syntax keyword xinKeyword import contained 29 | highlight link xinKeyword Keyword 30 | 31 | " functions 32 | syntax match xinFunctionForm "\v\(\s*[A-Za-z0-9\-?!+*/:><=%&|]*" contains=xinFunctionName,xinKeyword 33 | syntax match xinFunctionName "\v[A-Za-z0-9\-?!+*/:><=%&|]*" contained 34 | highlight link xinFunctionName Function 35 | 36 | syntax match xinDefinitionForm "\v\(\s*\:\s*(\(\s*)?[A-Za-z0-9\-?!+*/:><=%&|]*" contains=xinDefinition 37 | syntax match xinDefinition "\v[A-Za-z0-9\-?!+*/:><=%&|]*" contained 38 | highlight link xinDefinition Type 39 | 40 | " strings 41 | syntax region xinString start=/\v'/ skip=/\v(\\.|\r|\n)/ end=/\v'/ 42 | highlight link xinString String 43 | 44 | " comment 45 | " -- block 46 | " -- line-ending comment 47 | syntax match xinComment "\v;.*" contains=xinTodo 48 | highlight link xinComment Comment 49 | " -- shebang, highlighted as comment 50 | syntax match xinShebangComment "\v^#!.*" 51 | highlight link xinShebangComment Comment 52 | " -- TODO in comments 53 | syntax match xinTodo "\v(TODO\(.*\)|TODO)" contained 54 | syntax keyword xinTodo XXX contained 55 | highlight link xinTodo Todo 56 | 57 | syntax region xinForm start="(" end=")" transparent fold 58 | set foldmethod=syntax 59 | set foldlevel=20 60 | 61 | let b:current_syntax = "xin" 62 | -------------------------------------------------------------------------------- /lib/math.xin: -------------------------------------------------------------------------------- 1 | ; mathematics library 2 | 3 | (: (abs n) 4 | (if (< n 0) (neg n) n)) 5 | 6 | (: pi 7 | (* 2 (math::asin 1))) 8 | 9 | ; Euclid's GCD algorithm 10 | (: (gcd a b) 11 | ; prereq: a < b 12 | (do (: (sub a b) 13 | (if (= a 0) 14 | b 15 | (sub (% b a) a))) 16 | (: a (abs a)) 17 | (: b (abs b)) 18 | (if (> a b) 19 | (sub b a) 20 | (sub a b)))) 21 | 22 | ; least common multiple using gcd 23 | (: (lcm a b) 24 | (* a (/ b (gcd a b)))) 25 | 26 | (: (prime? n) 27 | (if (< n 2) 28 | false 29 | (do (: max (inc (int (sqrt n)))) 30 | ((: (sub i) 31 | (if (= i max) 32 | true 33 | (if (factor? n i) 34 | false 35 | (sub (inc i))))) 2)))) 36 | 37 | ; prime factorize natural number 38 | (: (prime-factors n) 39 | ((: (sub pfs m pf) 40 | (if (= m 1) 41 | pfs 42 | (if (factor? m pf) 43 | (sub (vec::add! pfs pf) 44 | (/ m pf) 45 | pf) 46 | (sub pfs 47 | m 48 | (inc pf))))) 49 | (vec) n 2)) 50 | 51 | ; factorize natural number 52 | ; for large n, may be more efficient to first 53 | ; prime-factorize and produce combinations of prime factors 54 | (: (factors n) 55 | ((: (sub lower upper r) 56 | (if (>= (* r r) n) 57 | (if (= (* r r) n) 58 | (+ (vec::add! lower r) 59 | (vec::reverse upper)) 60 | (+ lower (vec::reverse upper))) 61 | (if (factor? n r) 62 | (sub (vec::add! lower r) 63 | (vec::add! upper (/ n r)) 64 | (inc r)) 65 | (sub lower 66 | upper 67 | (inc r))))) 68 | (vec) (vec) 1)) 69 | 70 | (: (log base n) 71 | (/ (math::ln n) (math::ln base))) 72 | 73 | ; alias for (floor n) -- just (int n), 74 | ; which truncates the given decimal 75 | (: floor int) 76 | 77 | (: (round n) 78 | (int (+ n .5))) 79 | 80 | (: (rand-int max) 81 | (int (* (math::rand) max))) 82 | 83 | (: (urand-int max) 84 | (int (* (math::urand) max))) 85 | -------------------------------------------------------------------------------- /lib/test.xin: -------------------------------------------------------------------------------- 1 | ; test runner 2 | 3 | (: (scope label cases) 4 | (do 5 | (: all-succeeded (vec::every (vec::map cases case-succeeded?))) 6 | (: count-succeeded (vec::size (vec::filter cases case-succeeded?))) 7 | (logf '{}/{}\t{}' 8 | (vec count-succeeded 9 | (vec::size cases) 10 | label)) 11 | (if all-succeeded 12 | pass 13 | (vec::each cases 14 | (: (f c) 15 | (if (case-failed? c) 16 | (logf '\t- {}\n\t {}' 17 | (vec (desc-of-case c) 18 | (assertion-of-case c))) 19 | pass)))))) 20 | 21 | (: (case description assertion) 22 | (vec description assertion)) 23 | (: (desc-of-case c) 24 | (vec::get c 0)) 25 | (: (assertion-of-case c) 26 | (vec::get c 1)) 27 | (: (case-succeeded? c) 28 | (= (assertion-of-case c) 0)) 29 | (: (case-failed? c) 30 | (! (case-succeeded? c))) 31 | 32 | ; assertions 33 | 34 | (: (assert result) 35 | (if (= result true) 36 | pass 37 | (str::fmt 'Expected true but got {}' 38 | (vec result)))) 39 | 40 | (: (assert-false result) 41 | (if (= result false) 42 | pass 43 | (str::fmt 'Expected false but got {}' 44 | (vec result)))) 45 | 46 | (: (assert-eq result expected) 47 | (if (= result expected) 48 | pass 49 | (str::fmt 'Expected {} but got {}' 50 | (vec expected result)))) 51 | 52 | (: approx-cap 0.000001) 53 | (: (assert-eq-approx result expected) 54 | (if (< (math::abs (- result expected)) approx-cap) 55 | pass 56 | (str::fmt 'Expected approx. {} but got {}, off by >{}' 57 | (vec expected 58 | result 59 | approx-cap)))) 60 | 61 | (: (assert-eq-vec result expected) 62 | (if (vec::eq? result expected) 63 | pass 64 | (str::fmt 'Expected vec {} but got {}' 65 | (vec expected result)))) 66 | 67 | (: (assert-eq-map result expected) 68 | (if (map::eq? result expected) 69 | pass 70 | (str::fmt 'Expected map {} but got {}' 71 | (vec expected result)))) 72 | -------------------------------------------------------------------------------- /pkg/xin/vm.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | osPath "path" 8 | "sync" 9 | ) 10 | 11 | // TODO: implement call stack unwinding on interpreter error 12 | type stackRecord struct { 13 | parent *stackRecord 14 | node *astNode 15 | } 16 | 17 | func (sr stackRecord) String() string { 18 | if sr.parent == nil { 19 | return fmt.Sprintf("%s at %s", 20 | sr.node.String()[0:32], 21 | sr.node.position) 22 | } 23 | 24 | return fmt.Sprintf("%s at %s\nfrom %s", 25 | sr.node.String()[0:32], 26 | sr.node.position, 27 | sr.parent) 28 | } 29 | 30 | type Vm struct { 31 | Frame *Frame 32 | 33 | stack *stackRecord 34 | // cached imports 35 | imports map[string]*Frame 36 | // interned native forms 37 | evalers map[string]formEvaler 38 | 39 | sync.Mutex 40 | waiter sync.WaitGroup 41 | } 42 | 43 | func NewVm() (*Vm, InterpreterError) { 44 | 45 | vm := &Vm{ 46 | Frame: newFrame(nil), // no parent frame 47 | imports: make(map[string]*Frame), 48 | } 49 | vm.Frame.Vm = vm 50 | 51 | cwd, osErr := os.Getwd() 52 | if osErr != nil { 53 | return nil, RuntimeError{ 54 | reason: "Cannot find working directory", 55 | } 56 | } 57 | vm.Frame.cwd = &cwd 58 | 59 | loadAllDefaultValues(vm) 60 | loadAllNativeForms(vm) 61 | err := loadStandardLibrary(vm) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return vm, nil 67 | } 68 | 69 | func (vm *Vm) pushStack(node *astNode) { 70 | vm.stack = &stackRecord{ 71 | parent: vm.stack.parent, 72 | node: node, 73 | } 74 | } 75 | 76 | func (vm *Vm) popStack() { 77 | if vm.stack == nil { 78 | panic("Attempted to unwind an empty call stack!") 79 | } 80 | 81 | vm.stack = vm.stack.parent 82 | } 83 | 84 | func (vm *Vm) Eval(path string, r io.Reader) (Value, InterpreterError) { 85 | defer vm.waiter.Wait() 86 | 87 | toks, err := lex(path, r) 88 | if err != nil { 89 | return nil, err 90 | } 91 | rootNode, err := parse(toks) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | vm.Lock() 97 | defer vm.Unlock() 98 | 99 | val, err := unlazyEval(vm.Frame, &rootNode) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return val, nil 105 | } 106 | 107 | func (vm *Vm) Exec(path string) InterpreterError { 108 | file, err := os.Open(path) 109 | defer file.Close() 110 | if err != nil { 111 | return RuntimeError{ 112 | reason: fmt.Sprintf("Error opening file: %s", err), 113 | } 114 | } 115 | 116 | cwd := osPath.Dir(path) 117 | vm.Frame.cwd = &cwd 118 | _, ierr := vm.Eval(path, file) 119 | if ierr != nil { 120 | return ierr 121 | } 122 | 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /pkg/xin/parse.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type astNode struct { 8 | isForm bool 9 | token token 10 | leaves []*astNode 11 | position 12 | } 13 | 14 | func (n astNode) String() string { 15 | if n.isForm { 16 | parts := make([]string, len(n.leaves)) 17 | for i, leaf := range n.leaves { 18 | parts[i] = leaf.String() 19 | } 20 | return "(" + strings.Join(parts, " ") + ")" 21 | } else { 22 | return n.token.String() 23 | } 24 | } 25 | 26 | func parse(toks tokenStream) (astNode, InterpreterError) { 27 | root := astNode{ 28 | isForm: true, 29 | leaves: []*astNode{}, 30 | } 31 | // top level of a parse tree is a big do loop 32 | root.leaves = append(root.leaves, &astNode{ 33 | isForm: false, 34 | token: token{ 35 | kind: tkDoForm, 36 | }, 37 | }) 38 | 39 | index := 0 40 | max := len(toks) 41 | for index < max { 42 | n, delta, err := parseGeneric(toks[index:]) 43 | if err != nil { 44 | return astNode{}, err 45 | } 46 | root.leaves = append(root.leaves, &n) 47 | index += delta 48 | } 49 | 50 | return root, nil 51 | } 52 | 53 | func parseGeneric(toks tokenStream) (astNode, int, InterpreterError) { 54 | if toks[0].kind == tkOpenParen { 55 | return parseForm(toks) 56 | } else if toks[0].kind == tkCloseParen { 57 | return astNode{}, 0, UnexpectedTokenError{ 58 | token: toks[0], 59 | position: toks[0].position, 60 | } 61 | } else { 62 | return parseAtom(toks) 63 | } 64 | } 65 | 66 | func parseForm(toks tokenStream) (astNode, int, InterpreterError) { 67 | root := astNode{ 68 | isForm: true, 69 | leaves: []*astNode{}, 70 | position: toks[0].position, 71 | } 72 | 73 | index := 1 // Skip open paren 74 | max := len(toks) 75 | 76 | if max == 1 { 77 | return astNode{}, 0, UnexpectedEndingError{ 78 | position: toks[0].position, 79 | } 80 | } 81 | 82 | if toks[index].kind == tkCloseParen { 83 | return astNode{}, 0, InvalidFormError{ 84 | position: toks[index].position, 85 | } 86 | } 87 | 88 | for toks[index].kind != tkCloseParen { 89 | n, delta, err := parseGeneric(toks[index:]) 90 | if err != nil { 91 | return astNode{}, 0, err 92 | } 93 | root.leaves = append(root.leaves, &n) 94 | index += delta 95 | 96 | if index >= max { 97 | return astNode{}, 0, UnexpectedEndingError{ 98 | position: toks[index-1].position, 99 | } 100 | } 101 | } 102 | 103 | return root, index + 1, nil 104 | } 105 | 106 | func parseAtom(toks tokenStream) (astNode, int, InterpreterError) { 107 | if len(toks) > 0 { 108 | return astNode{ 109 | isForm: false, 110 | token: toks[0], 111 | position: toks[0].position, 112 | }, 1, nil 113 | } else { 114 | return astNode{}, 0, UnexpectedEndingError{} 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /samples/xxd.xin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xin 2 | 3 | ; show hex printout of the bytes in a given file 4 | ; 5 | ; usage: xin xxd.xin 6 | 7 | (: bytes-per-row 16) 8 | 9 | ; when we show source data in the output, we want to hide 10 | ; non-standard ascii characters. This defines which chars are 11 | ; "standard" for this purpose. 12 | (: (show-if-alphanumeric c) 13 | (if (vec::some (vec (str::letter? c) 14 | (str::digit? c) 15 | (str::has? '.,:;?!@#$%^&*(){}[]-_+=~\'\"/\\| ' 16 | c))) 17 | c 18 | '\xb7')) ; center dot 19 | 20 | ; given a vec, chunk it up into a list of smaller vecs 21 | ; of max chunk-size each 22 | (: (chunk v chunk-size) 23 | (do 24 | (: (sub i acc rest) 25 | (if (<= (vec::size rest) chunk-size) 26 | (vec::add! acc rest) 27 | (sub (inc i) 28 | (vec::add! acc (take rest chunk-size)) 29 | (vec::slice rest chunk-size (vec::size rest))))) 30 | (sub 0 (vec) v))) 31 | 32 | ; take binary file data, return vec of hexadecimal octet notations 33 | ; chunked into chunks of length max n 34 | (: (octet-rows-of-length n data) 35 | ; takes data as string 36 | (chunk 37 | (vec::map (vec::map (str::split data '') str::enc) 38 | (: (f octet) 39 | (str::pad-start (hex::enc octet) 40 | 2 41 | '0'))) 42 | n)) 43 | 44 | ; read CLI args 45 | (: filepath (vec::get (os::args) 2)) 46 | 47 | ; main proc 48 | (if (zero? (: file 49 | (os::open filepath))) 50 | (logf 'Error: could not open "{}" for reading.' 51 | (vec filepath)) 52 | (->> file 53 | (: (f content) 54 | (do 55 | (stream::close! file) 56 | 57 | (: max-count-width 58 | (str::size (hex::enc (str::size content)))) 59 | 60 | (logf 'Reading {} bytes from {}:' 61 | (vec (str::size content) 62 | filepath)) 63 | 64 | (vec::each 65 | (octet-rows-of-length bytes-per-row 66 | content) 67 | (: (f row i) 68 | (logf '{}: {} {}' 69 | (vec (str::pad-start (hex::enc (* i bytes-per-row)) 70 | max-count-width 71 | '0') 72 | (str::pad-end (str::join row ' ') 73 | (dec (* 3 bytes-per-row)) 74 | ' ') 75 | (str::map (str::slice content 76 | (* i bytes-per-row) 77 | (+ (* i bytes-per-row) 78 | bytes-per-row)) 79 | show-if-alphanumeric))))))))) 80 | -------------------------------------------------------------------------------- /samples/bmp.xin: -------------------------------------------------------------------------------- 1 | ; bitmap image format 2 | 3 | ; port of the Ink version: 4 | ; https://github.com/thesephist/ink:samples/bmp.ink 5 | 6 | (: (hexsplit n) 7 | ((: (sub p i acc) 8 | (if (| (< p 256) 9 | (> i 3)) 10 | (decode (vec::set! acc i p)) 11 | (sub (math::floor (/ p 256)) 12 | (inc i) 13 | (vec::set! acc i (% p 256))))) 14 | n 0 (vec 0 0 0 0))) 15 | 16 | (: (decode v) 17 | (str::join (vec::map v str::dec) '')) 18 | 19 | (: (bmp width height pixels) 20 | (do 21 | ; file buffer in which we build the image data 22 | (: buf '') 23 | ; append byte values to buf 24 | (: (add part) 25 | (str::add! buf part)) 26 | ; bmp requires that we pad out each pixel row to 4-byte chunks 27 | (: padding 28 | (decode (vec::get (vec (vec) 29 | (vec 0 0 0) 30 | (vec 0 0) 31 | (vec 0)) 32 | (% (* 3 width) 4)))) 33 | ; write the nth row of pixels to buf 34 | (: (wrow y) 35 | ((: (sub x) 36 | (if (= x width) 37 | (add padding) 38 | (do 39 | (add (decode (vec::get pixels (+ (* y width) x)))) 40 | (sub (inc x)))) 41 | ) 0)) 42 | 43 | ; -- bmp header: BITMAPINFOHEADER format 44 | 45 | ; bmp format identifier magic number 46 | (add 'BM') 47 | ; file size: 54 is the header bytes, plus 3 bytes per px + row-padding bytes 48 | (add (hexsplit (+ 54 49 | (* (+ (* 3 width) 50 | (str::size padding)) 51 | height)))) 52 | ; unused 4 bytes in this format 53 | (add (decode (vec 0 0 0 0))) 54 | ; pixel array data offset: always 54 if following this format 55 | (add (decode (vec 54 0 0 0))) 56 | 57 | ; -- DIB header 58 | 59 | ; num of bytes in the DIB header from here 60 | (add (decode (vec 40 0 0 0))) 61 | ; bitmap width in pixels 62 | (add (hexsplit width)) 63 | ; bitmap height in pixels, bottom to top 64 | (add (hexsplit height)) 65 | ; number of color planes used: 1 66 | (add (decode (vec 1 0))) 67 | ; number of bits per pixel: 24 (8-bit rgb) 68 | (add (decode (vec 24 0))) 69 | ; pixel array compression format: none used 70 | (add (decode (vec 0 0 0 0))) 71 | ; size of raw bitmap data: 16 bits 72 | (add (decode (vec 16 0 0 0))) 73 | ; horizontal print resolution of the image: 72 dpi = 2835 pixels/m 74 | (add (hexsplit 2835)) 75 | ; vertical print resolution of the image: 72 dpi = 2835 pixels/m 76 | (add (hexsplit 2835)) 77 | ; number of colors in palette: 0 78 | (add (decode (vec 0 0 0 0))) 79 | ; number of "important" colors: 0 80 | (add (decode (vec 0 0 0 0))) 81 | 82 | ; write the whole pixel array to buf 83 | ((: (sub y) 84 | (if (= y height) 85 | 0 86 | (do 87 | (wrow y) 88 | (sub (inc y))))) 0) 89 | 90 | ; return image file buffer 91 | buf)) 92 | -------------------------------------------------------------------------------- /samples/number-letter-counts.xin: -------------------------------------------------------------------------------- 1 | ; number of letters in 1-1000 written out 2 | ; project euler: https://projecteuler.net/problem=17 3 | 4 | (: ones (map)) 5 | (: (add-ones n word) 6 | (map::set! ones n word)) 7 | (: (get-ones n) 8 | (map::get ones n)) 9 | (add-ones 0 '') 10 | (add-ones 1 'one') 11 | (add-ones 2 'two') 12 | (add-ones 3 'three') 13 | (add-ones 4 'four') 14 | (add-ones 5 'five') 15 | (add-ones 6 'six') 16 | (add-ones 7 'seven') 17 | (add-ones 8 'eight') 18 | (add-ones 9 'nine') 19 | 20 | (: tens (map)) 21 | (: (add-tens n word) 22 | (map::set! tens n word)) 23 | (: (get-tens n) 24 | (map::get tens n)) 25 | (add-tens 0 '') 26 | (add-tens 1 'ten') 27 | (add-tens 2 'twenty') 28 | (add-tens 3 'thirty') 29 | (add-tens 4 'forty') 30 | (add-tens 5 'fifty') 31 | (add-tens 6 'sixty') 32 | (add-tens 7 'seventy') 33 | (add-tens 8 'eighty') 34 | (add-tens 9 'ninety') 35 | 36 | ; special cases that don't follow normal rules 37 | (: special (map)) 38 | (: (add-special n word) 39 | (map::set! special n word)) 40 | (: (get-special n) 41 | (map::get special n)) 42 | (: (is-special n) 43 | (map::has? special n)) 44 | (add-special 11 'eleven') 45 | (add-special 12 'twelve') 46 | (add-special 13 'thirteen') 47 | (add-special 14 'fourteen') 48 | (add-special 15 'fifteen') 49 | (add-special 16 'sixteen') 50 | (add-special 17 'seventeen') 51 | (add-special 18 'eighteen') 52 | (add-special 19 'nineteen') 53 | 54 | (: (count-digits n) 55 | (str::size (str n))) 56 | 57 | (: (write-single-digit n) 58 | (get-ones n)) 59 | 60 | (: (write-double-digit n) 61 | (if (is-special n) 62 | (get-special n) 63 | (do 64 | (: tens-digit (% (/ n 10) 10)) 65 | (+ (get-tens tens-digit) 66 | (write-number (% n 10)))))) 67 | 68 | (: (write-triple-digit n) 69 | (if (is-special n) 70 | (get-special n) 71 | (do (: hundreds-digit (% (/ n 100) 10)) 72 | (: double-digit (write-number (% n 100))) 73 | (+ (get-ones hundreds-digit) 74 | (+ 'hundred' 75 | (if (factor? n 100) 76 | '' 77 | (+ 'and' double-digit))))))) 78 | 79 | (: (write-quadruple-digit n) 80 | (if (is-special n) 81 | (get-special n) 82 | (do (: thousands-digit (% (/ n 1000) 10)) 83 | (: hundreds-digit (% (/ (% n 1000) 100) 10)) 84 | (: triple-digit (write-number (% n 1000))) 85 | (+ (get-ones thousands-digit) 86 | (+ 'thousand' 87 | (if (= hundreds-digit 0) 88 | (if (str::blank? triple-digit) 89 | '' 90 | (+ 'and' triple-digit)) 91 | triple-digit)))))) 92 | 93 | (: (write-number n) 94 | (if (<= 4 (count-digits n)) 95 | (write-quadruple-digit n) 96 | (if (= 3 (count-digits n)) 97 | (write-triple-digit n) 98 | (if (= 2 (count-digits n)) 99 | (write-double-digit n) 100 | (write-single-digit n))))) 101 | 102 | (: (count-letters n) 103 | (str::size (write-number n))) 104 | 105 | (: (letters-count-to cap) 106 | (vec::sum (vec::map (range 1 (inc cap) 1) count-letters))) 107 | (log (letters-count-to 1000)) 108 | -------------------------------------------------------------------------------- /lib/std.xin: -------------------------------------------------------------------------------- 1 | ; standard library 2 | 3 | ; boolean aliases 4 | (: true 1) 5 | (: false 0) 6 | 7 | ; want to be able to say "pass" 8 | ; on useless if-else cases, for example 9 | (: pass 0) 10 | (: and &) 11 | (: or |) 12 | (: not !) 13 | (: (bool x) 14 | (if x 'true' 'false')) 15 | 16 | ; type assertions 17 | (: (int? x) 18 | (= (type x) int)) 19 | (: (frac? x) 20 | (= (type x) frac)) 21 | (: (str? x) 22 | (= (type x) str)) 23 | (: (vec? x) 24 | (= (type x) vec)) 25 | (: (map? x) 26 | (= (type x) map)) 27 | (: (stream? x) 28 | (= (type x) stream)) 29 | (: form (type type)) 30 | (: (form? x) 31 | (= (type x) form)) 32 | 33 | ; identity function 34 | (: (identity x) x) 35 | 36 | ; formatted logging shorthand 37 | (: log os::log) 38 | (: (logf s items) 39 | (log (str::fmt s items))) 40 | 41 | ; basic math 42 | (: (!= a b) (! (= a b))) 43 | (: (>= a b) (! (< a b))) 44 | (: (<= a b) (! (> a b))) 45 | (: (sqrt x) (^ x .5)) 46 | (: (neg n) (- 0 n)) 47 | (: (pos? n) (> n 0)) 48 | (: (neg? n) (< n 0)) 49 | (: (zero? x) (= x 0)) 50 | (: (inc n) (+ n 1)) 51 | (: (dec n) (- n 1)) 52 | (: (factor? big small) 53 | (= (% big small) 0)) 54 | (: (even? n) (factor? n 2)) 55 | (: (odd? n) (! (even? n))) 56 | (: (max a b) 57 | (if (> a b) a b)) 58 | (: (min a b) 59 | (if (< a b) a b)) 60 | 61 | ; iteration primitives 62 | (: (range start end step) 63 | ((: (sub i acc) 64 | (if (< i end) 65 | (sub (+ i step) 66 | (vec::add! acc i)) 67 | acc)) 68 | start (vec))) 69 | 70 | (: (seq n) 71 | (range 0 n 1)) 72 | 73 | (: (nat n) 74 | (range 1 (inc n) 1)) 75 | 76 | (: (take v n) 77 | (vec::slice v 0 n)) 78 | 79 | (: (loop n f) 80 | ((: (sub i) 81 | (if (< i n) 82 | (do (f i) 83 | (sub (+ i 1))) 84 | 0)) 85 | 0)) 86 | 87 | ; set abstraction over map 88 | (: (set) 89 | (map)) 90 | (: (set::add! s v) 91 | (map::set! s v 0)) 92 | (: set::del! map::del!) 93 | (: set::has? map::has?) 94 | (: set::size map::size) 95 | (: set::items map::keys) 96 | 97 | ; hex encoding/decoding 98 | (: to-hex-digits 99 | (vec '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 'a' 'b' 'c' 'd' 'e' 'f')) 100 | (: (hex::enc n) 101 | (if (< n 16) 102 | (vec::get to-hex-digits (int n)) 103 | (+ (hex::enc (/ n 16)) 104 | (vec::get to-hex-digits (int (% n 16)))))) 105 | 106 | (: to-dec-number-map 107 | (do (: m (map)) 108 | (map::set! m 'a' 10) 109 | (map::set! m 'b' 11) 110 | (map::set! m 'c' 12) 111 | (map::set! m 'd' 13) 112 | (map::set! m 'e' 14) 113 | (map::set! m 'f' 15))) 114 | (: (to-dec-number s) 115 | (if (str::digit? s) 116 | (int s) 117 | (map::get to-dec-number-map s))) 118 | (: (hex::dec s) 119 | (if (= (str::size s) 1) 120 | (to-dec-number s) 121 | (+ (* (hex::dec (str::slice s 0 (dec (str::size s)))) 122 | 16) 123 | (to-dec-number (str::get s (dec (str::size s))))))) 124 | 125 | ; drain a source stream 126 | (: (->> file cb) 127 | (if (zero? file) 128 | 0 129 | ((: (sub acc) 130 | (-> file (: (f buf) 131 | (if (zero? buf) 132 | (cb acc) 133 | (sub (str::add! acc buf)))))) 134 | ''))) 135 | -------------------------------------------------------------------------------- /pkg/xin/value.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | // Value represents a value in the Xin runtime. 9 | // 10 | // It is important that Value types are: 11 | // (1) freely copyable without losing information 12 | // (i.e. copying Values around should not alter language semantics) 13 | // (2) hashable, for use as a MapValue key. StringValue is a special 14 | // exception to this case, where a proxy Value type is used instead 15 | // which is hashable. 16 | type Value interface { 17 | // String is stable, string representation of a Xin value 18 | // that can be used to back the to-string type conversion of values 19 | String() string 20 | 21 | // Repr is an unstable, human-readable representation of Xin values used for 22 | // debugging and the repl, should not be considered a stable API 23 | // to be used in the language internally. 24 | Repr() string 25 | 26 | Equal(Value) bool 27 | } 28 | 29 | type IntValue int64 30 | 31 | func (v IntValue) String() string { 32 | return strconv.FormatInt(int64(v), 10) 33 | } 34 | 35 | func (v IntValue) Repr() string { 36 | return strconv.FormatInt(int64(v), 10) 37 | } 38 | 39 | func (v IntValue) Equal(o Value) bool { 40 | if ov, ok := o.(IntValue); ok { 41 | return v == ov 42 | } 43 | 44 | return false 45 | } 46 | 47 | type FracValue float64 48 | 49 | func (v FracValue) String() string { 50 | return fmt.Sprintf("%.8f", float64(v)) 51 | } 52 | 53 | func (v FracValue) Repr() string { 54 | return fmt.Sprintf("%.8f", float64(v)) 55 | } 56 | 57 | func (v FracValue) Equal(o Value) bool { 58 | if ov, ok := o.(FracValue); ok { 59 | return v == ov 60 | } 61 | 62 | return false 63 | } 64 | 65 | type argList []string 66 | 67 | type FormValue struct { 68 | frame *Frame 69 | // this level of indirection is to allow FormValue 70 | // to be hashable for inclusion in a MapValue 71 | arguments *argList 72 | definition *astNode 73 | } 74 | 75 | func (v FormValue) String() string { 76 | ss := "" 77 | for _, a := range *v.arguments { 78 | ss += " " + a 79 | } 80 | return "(
" + ss + ") " + v.definition.String() 81 | } 82 | 83 | func (v FormValue) Repr() string { 84 | // same impl as FormValue.String() 85 | ss := "" 86 | for _, a := range *v.arguments { 87 | ss += " " + a 88 | } 89 | return "(" + ss + ") " + v.definition.String() 90 | } 91 | 92 | func (v FormValue) Equal(o Value) bool { 93 | if ov, ok := o.(FormValue); ok { 94 | return v.definition == ov.definition 95 | } 96 | 97 | return false 98 | } 99 | 100 | type LazyValue struct { 101 | frame *Frame 102 | node *astNode 103 | } 104 | 105 | func (v LazyValue) String() string { 106 | return "( " + v.node.String() + ")" 107 | } 108 | 109 | func (v LazyValue) Repr() string { 110 | // same impl as LazyValue.String() 111 | return "( " + v.node.String() + ")" 112 | } 113 | 114 | func (v LazyValue) Equal(o Value) bool { 115 | // should never run 116 | panic(" value should never be compared!") 117 | } 118 | 119 | func unlazy(v Value) (Value, InterpreterError) { 120 | // hot path, a shortcut for frequent case 121 | if _, isLazy := v.(LazyValue); !isLazy { 122 | return v, nil 123 | } 124 | 125 | var err InterpreterError 126 | for lzv, isLazy := v.(LazyValue); isLazy; lzv, isLazy = v.(LazyValue) { 127 | v, err = eval(lzv.frame, lzv.node) 128 | if err != nil { 129 | return nil, err 130 | } 131 | } 132 | 133 | return v, nil 134 | } 135 | 136 | func unlazyEval(fr *Frame, node *astNode) (Value, InterpreterError) { 137 | val, err := eval(fr, node) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | return unlazy(val) 143 | } 144 | -------------------------------------------------------------------------------- /pkg/xin/type.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | func stringForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 8 | if len(args) < 1 { 9 | return nil, IncorrectNumberOfArgsError{ 10 | required: 1, 11 | given: len(args), 12 | } 13 | } 14 | 15 | return StringValue(args[0].String()), nil 16 | } 17 | 18 | func intForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 19 | if len(args) < 1 { 20 | return nil, IncorrectNumberOfArgsError{ 21 | required: 1, 22 | given: len(args), 23 | } 24 | } 25 | 26 | first := args[0] 27 | 28 | switch val := first.(type) { 29 | case IntValue: 30 | return val, nil 31 | case FracValue: 32 | return IntValue(float64(val)), nil 33 | case StringValue: 34 | intVal, err := strconv.ParseInt(string(val), 10, 64) 35 | if err != nil { 36 | return zeroValue, nil 37 | } 38 | return IntValue(intVal), nil 39 | default: 40 | return zeroValue, nil 41 | } 42 | } 43 | 44 | func fracForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 45 | if len(args) < 1 { 46 | return nil, IncorrectNumberOfArgsError{ 47 | required: 1, 48 | given: len(args), 49 | } 50 | } 51 | 52 | first := args[0] 53 | 54 | switch val := first.(type) { 55 | case IntValue: 56 | return FracValue(val), nil 57 | case FracValue: 58 | return val, nil 59 | case StringValue: 60 | floatVal, err := strconv.ParseFloat(string(val), 64) 61 | if err != nil { 62 | return FracValue(0.0), nil 63 | } 64 | return FracValue(floatVal), nil 65 | default: 66 | return FracValue(0), nil 67 | } 68 | } 69 | 70 | func equalForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 71 | if len(args) < 2 { 72 | return nil, IncorrectNumberOfArgsError{ 73 | node: node, 74 | required: 2, 75 | given: len(args), 76 | } 77 | } 78 | 79 | first, second := args[0], args[1] 80 | 81 | if firstInt, fok := first.(IntValue); fok { 82 | if _, sok := second.(FracValue); sok { 83 | first = FracValue(float64(firstInt)) 84 | } 85 | } else if _, fok := first.(FracValue); fok { 86 | if secondInt, sok := second.(IntValue); sok { 87 | second = FracValue(float64(secondInt)) 88 | } 89 | } 90 | 91 | if first.Equal(second) { 92 | return trueValue, nil 93 | } else { 94 | return falseValue, nil 95 | } 96 | } 97 | 98 | func typeForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 99 | if len(args) < 1 { 100 | return nil, IncorrectNumberOfArgsError{ 101 | node: node, 102 | required: 1, 103 | given: len(args), 104 | } 105 | } 106 | 107 | first := args[0] 108 | 109 | switch first.(type) { 110 | case IntValue: 111 | return NativeFormValue{ 112 | name: "int", 113 | evaler: intForm, 114 | }, nil 115 | case FracValue: 116 | return NativeFormValue{ 117 | name: "frac", 118 | evaler: fracForm, 119 | }, nil 120 | case StringValue: 121 | return NativeFormValue{ 122 | name: "str", 123 | evaler: streamForm, 124 | }, nil 125 | case VecValue: 126 | return NativeFormValue{ 127 | name: "vec", 128 | evaler: vecForm, 129 | }, nil 130 | case MapValue: 131 | return NativeFormValue{ 132 | name: "map", 133 | evaler: mapForm, 134 | }, nil 135 | case StreamValue: 136 | return NativeFormValue{ 137 | name: "stream", 138 | evaler: streamForm, 139 | }, nil 140 | case FormValue, NativeFormValue: 141 | return NativeFormValue{ 142 | name: "form", 143 | evaler: func(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 144 | panic("form constructor as type should never be called") 145 | }, 146 | }, nil 147 | } 148 | 149 | return nil, MismatchedArgumentsError{ 150 | node: node, 151 | args: args, 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /pkg/xin/lib.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | osPath "path" 7 | 8 | "github.com/rakyll/statik/fs" 9 | _ "github.com/thesephist/xin/statik" 10 | ) 11 | 12 | func loadStandardLibrary(vm *Vm) InterpreterError { 13 | statikFs, err := fs.New() 14 | if err != nil { 15 | fmt.Println("Standard library error:", err.Error()) 16 | } 17 | 18 | // import order matters here, later libs 19 | // have dependency on the preceding ones 20 | libFiles := []string{ 21 | "std", 22 | "math", 23 | "vec", 24 | "map", 25 | "str", 26 | "src", 27 | "stat", 28 | "os", 29 | "test", 30 | } 31 | 32 | vm.Lock() 33 | defer vm.Unlock() 34 | 35 | for _, path := range libFiles { 36 | alias := path 37 | // everything in std.xin is assumed not to be prefixed 38 | if path == "std" { 39 | alias = "" 40 | } 41 | 42 | libFile, ferr := statikFs.Open("/" + path + ".xin") 43 | if ferr != nil { 44 | return RuntimeError{ 45 | reason: fmt.Sprintf("Stdlib error loading %s: %s", path, ferr.Error()), 46 | } 47 | } 48 | defer libFile.Close() 49 | 50 | // std import 51 | toks, err := lex(fmt.Sprintf("(std) %s", alias), libFile) 52 | if err != nil { 53 | return err 54 | } 55 | rootNode, err := parse(toks) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | libFrame := newFrame(vm.Frame) 61 | _, err = unlazyEval(libFrame, &rootNode) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | rootFrame := vm.Frame 67 | if alias == "" { 68 | for name, value := range libFrame.Scope { 69 | rootFrame.Put(name, value) 70 | } 71 | } else { 72 | for name, value := range libFrame.Scope { 73 | rootFrame.Put(alias+"::"+name, value) 74 | } 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | 81 | func importFrame(fr *Frame, importPath string) (*Frame, InterpreterError) { 82 | importFile, osErr := os.Open(importPath) 83 | if osErr != nil { 84 | return nil, RuntimeError{ 85 | reason: fmt.Sprintf("Could not open imported file %s", importPath), 86 | } 87 | } 88 | toks, err := lex(importPath, importFile) 89 | if err != nil { 90 | return nil, err 91 | } 92 | rootNode, err := parse(toks) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | // import runs in a new top-level frame in 98 | // the same VM (execution lock) 99 | importFrame := newFrame(fr.Vm.Frame) 100 | importCwd := osPath.Dir(importPath) 101 | importFrame.cwd = &importCwd 102 | _, err = unlazyEval(importFrame, &rootNode) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | return importFrame, nil 108 | } 109 | 110 | func deduplicatedImportFrame(fr *Frame, importPath string) (*Frame, InterpreterError) { 111 | importMap := fr.Vm.imports 112 | 113 | if dedupFrame, prs := importMap[importPath]; prs { 114 | return dedupFrame, nil 115 | } 116 | 117 | dedupFrame, err := importFrame(fr, importPath) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | importMap[importPath] = dedupFrame 123 | 124 | return dedupFrame, nil 125 | } 126 | 127 | func evalImportForm(fr *Frame, args []*astNode) (Value, InterpreterError) { 128 | if len(args) == 0 { 129 | return nil, InvalidImportError{nodes: args} 130 | } 131 | 132 | pathNode := args[0] 133 | path, err := unlazyEval(fr, pathNode) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | cleanPath, ok := path.(StringValue) 139 | if !ok { 140 | return nil, InvalidImportError{nodes: args} 141 | } 142 | 143 | importPath := osPath.Join(*fr.cwd, string(cleanPath)+".xin") 144 | importFramePtr, err := deduplicatedImportFrame(fr, importPath) 145 | if err != nil { 146 | return nil, err 147 | } 148 | importFrame := *importFramePtr 149 | 150 | alias := "" 151 | if len(args) > 1 { 152 | aliasNode := args[1] 153 | if aliasNode.token.kind == tkName { 154 | alias = aliasNode.token.value 155 | } 156 | } 157 | if alias == "" { 158 | for name, value := range importFrame.Scope { 159 | fr.Put(name, value) 160 | } 161 | } else { 162 | for name, value := range importFrame.Scope { 163 | fr.Put(alias+"::"+name, value) 164 | } 165 | } 166 | 167 | return zeroValue, nil 168 | } 169 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 3 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 4 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 5 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 6 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 9 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 10 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 11 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 12 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 13 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 14 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 15 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 16 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 17 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 18 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 19 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 20 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 21 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 22 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= 25 | github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= 26 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 27 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 28 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 29 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 30 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 31 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 32 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 33 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 34 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 35 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 36 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 37 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 38 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 39 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 42 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 45 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 46 | -------------------------------------------------------------------------------- /pkg/xin/math.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | ) 7 | 8 | func mathRandForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 9 | return FracValue(rand.Float64()), nil 10 | } 11 | 12 | func mathSinForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 13 | if len(args) < 1 { 14 | return nil, IncorrectNumberOfArgsError{ 15 | node: node, 16 | required: 1, 17 | given: len(args), 18 | } 19 | } 20 | 21 | first := args[0] 22 | 23 | switch cleanFirst := first.(type) { 24 | case IntValue: 25 | return FracValue(math.Sin(float64(cleanFirst))), nil 26 | case FracValue: 27 | return FracValue(math.Sin(float64(cleanFirst))), nil 28 | } 29 | 30 | return nil, MismatchedArgumentsError{ 31 | node: node, 32 | args: args, 33 | } 34 | } 35 | 36 | func mathCosForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 37 | if len(args) < 1 { 38 | return nil, IncorrectNumberOfArgsError{ 39 | node: node, 40 | required: 1, 41 | given: len(args), 42 | } 43 | } 44 | 45 | first := args[0] 46 | 47 | switch cleanFirst := first.(type) { 48 | case IntValue: 49 | return FracValue(math.Cos(float64(cleanFirst))), nil 50 | case FracValue: 51 | return FracValue(math.Cos(float64(cleanFirst))), nil 52 | } 53 | 54 | return nil, MismatchedArgumentsError{ 55 | node: node, 56 | args: args, 57 | } 58 | } 59 | 60 | func mathTanForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 61 | if len(args) < 1 { 62 | return nil, IncorrectNumberOfArgsError{ 63 | node: node, 64 | required: 1, 65 | given: len(args), 66 | } 67 | } 68 | 69 | first := args[0] 70 | 71 | switch cleanFirst := first.(type) { 72 | case IntValue: 73 | return FracValue(math.Tan(float64(cleanFirst))), nil 74 | case FracValue: 75 | return FracValue(math.Tan(float64(cleanFirst))), nil 76 | } 77 | 78 | return nil, MismatchedArgumentsError{ 79 | node: node, 80 | args: args, 81 | } 82 | } 83 | 84 | func mathAsinForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 85 | if len(args) < 1 { 86 | return nil, IncorrectNumberOfArgsError{ 87 | node: node, 88 | required: 1, 89 | given: len(args), 90 | } 91 | } 92 | 93 | first := args[0] 94 | 95 | switch cleanFirst := first.(type) { 96 | case IntValue: 97 | if cleanFirst > 1 || cleanFirst < -1 { 98 | return zeroValue, nil 99 | } 100 | 101 | return FracValue(math.Asin(float64(cleanFirst))), nil 102 | case FracValue: 103 | if cleanFirst > 1 || cleanFirst < -1 { 104 | return zeroValue, nil 105 | } 106 | 107 | return FracValue(math.Asin(float64(cleanFirst))), nil 108 | } 109 | 110 | return nil, MismatchedArgumentsError{ 111 | node: node, 112 | args: args, 113 | } 114 | } 115 | 116 | func mathAcosForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 117 | if len(args) < 1 { 118 | return nil, IncorrectNumberOfArgsError{ 119 | node: node, 120 | required: 1, 121 | given: len(args), 122 | } 123 | } 124 | 125 | first := args[0] 126 | 127 | switch cleanFirst := first.(type) { 128 | case IntValue: 129 | if cleanFirst > 1 || cleanFirst < -1 { 130 | return zeroValue, nil 131 | } 132 | 133 | return FracValue(math.Acos(float64(cleanFirst))), nil 134 | case FracValue: 135 | if cleanFirst > 1 || cleanFirst < -1 { 136 | return zeroValue, nil 137 | } 138 | 139 | return FracValue(math.Acos(float64(cleanFirst))), nil 140 | } 141 | 142 | return nil, MismatchedArgumentsError{ 143 | node: node, 144 | args: args, 145 | } 146 | } 147 | 148 | func mathAtanForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 149 | if len(args) < 1 { 150 | return nil, IncorrectNumberOfArgsError{ 151 | node: node, 152 | required: 1, 153 | given: len(args), 154 | } 155 | } 156 | 157 | first := args[0] 158 | 159 | switch cleanFirst := first.(type) { 160 | case IntValue: 161 | return FracValue(math.Atan(float64(cleanFirst))), nil 162 | case FracValue: 163 | return FracValue(math.Atan(float64(cleanFirst))), nil 164 | } 165 | 166 | return nil, MismatchedArgumentsError{ 167 | node: node, 168 | args: args, 169 | } 170 | } 171 | 172 | func mathLnForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 173 | if len(args) < 1 { 174 | return nil, IncorrectNumberOfArgsError{ 175 | node: node, 176 | required: 1, 177 | given: len(args), 178 | } 179 | } 180 | 181 | first := args[0] 182 | 183 | switch cleanFirst := first.(type) { 184 | case IntValue: 185 | return FracValue(math.Log(float64(cleanFirst))), nil 186 | case FracValue: 187 | return FracValue(math.Log(float64(cleanFirst))), nil 188 | } 189 | 190 | return nil, MismatchedArgumentsError{ 191 | node: node, 192 | args: args, 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /pkg/xin/error.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func FormatError(e InterpreterError) string { 9 | return e.Error() + "\n\t at " + e.pos().String() 10 | } 11 | 12 | type InterpreterError interface { 13 | error 14 | pos() position 15 | } 16 | 17 | type UndefinedNameError struct { 18 | name string 19 | position position 20 | } 21 | 22 | func (e UndefinedNameError) Error() string { 23 | return fmt.Sprintf("Undefined name %s", e.name) 24 | } 25 | 26 | func (e UndefinedNameError) pos() position { 27 | return e.position 28 | } 29 | 30 | type InvalidFormError struct { 31 | position position 32 | } 33 | 34 | func (e InvalidFormError) Error() string { 35 | return fmt.Sprintf("Invalid form") 36 | } 37 | 38 | func (e InvalidFormError) pos() position { 39 | return e.position 40 | } 41 | 42 | type InvalidBindError struct { 43 | nodes []*astNode 44 | position position 45 | } 46 | 47 | func (e InvalidBindError) Error() string { 48 | ss := make([]string, len(e.nodes)) 49 | for i, n := range e.nodes { 50 | ss[i] = n.String() 51 | } 52 | return fmt.Sprintf("Invalid bind error: %s", strings.Join(ss, " ")) 53 | } 54 | 55 | func (e InvalidBindError) pos() position { 56 | return e.position 57 | } 58 | 59 | type InvalidIfError struct { 60 | nodes []*astNode 61 | position position 62 | } 63 | 64 | func (e InvalidIfError) Error() string { 65 | ss := make([]string, len(e.nodes)) 66 | for i, n := range e.nodes { 67 | ss[i] = n.String() 68 | } 69 | return fmt.Sprintf("Invalid if error: %s", strings.Join(ss, " ")) 70 | } 71 | 72 | func (e InvalidIfError) pos() position { 73 | return e.position 74 | } 75 | 76 | type InvalidIfConditionError struct { 77 | cond Value 78 | position position 79 | } 80 | 81 | func (e InvalidIfConditionError) Error() string { 82 | return fmt.Sprintf("Invalid if condition: %s", e.cond) 83 | } 84 | 85 | func (e InvalidIfConditionError) pos() position { 86 | return e.position 87 | } 88 | 89 | type InvalidImportError struct { 90 | nodes []*astNode 91 | position position 92 | } 93 | 94 | func (e InvalidImportError) Error() string { 95 | ss := make([]string, len(e.nodes)) 96 | for i, n := range e.nodes { 97 | ss[i] = n.String() 98 | } 99 | return fmt.Sprintf("Invalid import: %s", strings.Join(ss, " ")) 100 | } 101 | 102 | func (e InvalidImportError) pos() position { 103 | return e.position 104 | } 105 | 106 | type UnexpectedCharacterError struct { 107 | char string 108 | position position 109 | } 110 | 111 | func (e UnexpectedCharacterError) Error() string { 112 | return fmt.Sprintf("Unexpected character %s", e.char) 113 | } 114 | 115 | func (e UnexpectedCharacterError) pos() position { 116 | return e.position 117 | } 118 | 119 | type UnexpectedTokenError struct { 120 | token token 121 | position position 122 | } 123 | 124 | func (e UnexpectedTokenError) Error() string { 125 | return fmt.Sprintf("Unexpected token %s", e.token) 126 | } 127 | 128 | func (e UnexpectedTokenError) pos() position { 129 | return e.position 130 | } 131 | 132 | type UnexpectedEndingError struct { 133 | position position 134 | } 135 | 136 | func (e UnexpectedEndingError) Error() string { 137 | return "Unexpected ending" 138 | } 139 | 140 | func (e UnexpectedEndingError) pos() position { 141 | return e.position 142 | } 143 | 144 | type RuntimeError struct { 145 | reason string 146 | position position 147 | } 148 | 149 | func (e RuntimeError) Error() string { 150 | return "Runtime error: " + e.reason 151 | } 152 | 153 | func (e RuntimeError) pos() position { 154 | return e.position 155 | } 156 | 157 | type IncorrectNumberOfArgsError struct { 158 | node *astNode 159 | required int 160 | given int 161 | } 162 | 163 | func (e IncorrectNumberOfArgsError) Error() string { 164 | return fmt.Sprintf("Incorrect number of args in %s: requires %d but got %d", 165 | e.node, e.required, e.given) 166 | } 167 | 168 | func (e IncorrectNumberOfArgsError) pos() position { 169 | return e.node.position 170 | } 171 | 172 | type MismatchedArgumentsError struct { 173 | node *astNode 174 | args []Value 175 | } 176 | 177 | func (e MismatchedArgumentsError) Error() string { 178 | ss := make([]string, len(e.args)) 179 | for i, n := range e.args { 180 | ss[i] = n.String() 181 | } 182 | return fmt.Sprintf("Mismatched arguments to %s: %s", e.node, strings.Join(ss, " ")) 183 | } 184 | 185 | func (e MismatchedArgumentsError) pos() position { 186 | return e.node.position 187 | } 188 | 189 | type InvalidStreamCallbackError struct { 190 | reason string 191 | position position 192 | } 193 | 194 | func (e InvalidStreamCallbackError) Error() string { 195 | return "Invalid stream callback: " + e.reason 196 | } 197 | 198 | func (e InvalidStreamCallbackError) pos() position { 199 | return e.position 200 | } 201 | 202 | type NetworkError struct { 203 | reason string 204 | position position 205 | } 206 | 207 | func (e NetworkError) Error() string { 208 | return "Network error: " + e.reason 209 | } 210 | 211 | func (e NetworkError) pos() position { 212 | return e.position 213 | } 214 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xin programming language 2 | 3 | [![GoDoc](https://godoc.org/github.com/thesephist/xin?status.svg)](https://godoc.org/github.com/thesephist/xin) 4 | [![Build Status](https://travis-ci.com/thesephist/xin.svg?branch=master)](https://travis-ci.com/thesephist/xin) 5 | 6 | Xin is a functional programming language inspired by Lisp and CSP. Xin aspires to be an expressive, extensible language built on a small number of simple elements that work well together. You can find a deep dive into the language design [in the spec document](SPEC.md). 7 | 8 | Xin is my second toy programming language, after [Ink](https://github.com/thesephist/ink). With Xin, I'm specifically exploring ideas around code as a data structure, lazy evaluation in an interpreter context, and streaming evented I/O. 9 | 10 | Here's the fibonacci sequence, written naively in Xin. 11 | 12 | ``` 13 | (: (fib n) 14 | (if (< n 2) 15 | 1 16 | (+ (fib (- n 1)) 17 | (fib (- n 2))))) 18 | 19 | ; 30th fibonacci number 20 | (fib 30) 21 | ``` 22 | 23 | Xin supports proper tail calls, so we can write this in a faster (`O(n)`) tail-recursive form. 24 | 25 | ``` 26 | (: (fast-fib n) 27 | ((: (f n a b) 28 | (if (< n 2) 29 | b 30 | (f (- n 1) b (+ a b)))) 31 | n 1 1)) 32 | 33 | ; 50th fibonacci number 34 | (fib 50) 35 | ``` 36 | 37 | You can find more example and real-world Xin code in the sample programs in the repository: 38 | 39 | - [the standard library](lib/std.xin) 40 | - [quicksort, and other iterator algorithms](lib/vec.xin) 41 | - [pascal's triangle generator](samples/pascal-triangle.xin) 42 | - [clone of UNIX utility xxd](samples/xxd.xin) 43 | - [counting word frequently in a file](samples/freq.xin) 44 | - [basic TCP/IP chat server](samples/chat.xin) 45 | - [basic shape drawing library outputting to .bmp files](samples/art/shape.xin) 46 | 47 | ## Goals 48 | 49 | - Expressive, readable, extensible syntax that's natural to read and suitable for defining DSLs 50 | - Programs that lend themselves to smart data structures and dumb algorithms, rather than vice versa 51 | - Great interactive Repl 52 | 53 | ## Installation and usage 54 | 55 | While Xin is under development, the best way to get it is to build it from source. If you have Go installed, clone the repository and run 56 | 57 | ``` 58 | go build ./xin.go -o ./xin 59 | ``` 60 | 61 | to build Xin as a standalone binary, or run `make install` to install Xin alongside the vim syntax highlighting / definition file. 62 | 63 | Xin can currently run as a repl, or execute from a file or standard input. To run the repl, simply run from the command line: 64 | 65 | ``` 66 | xin 67 | ``` 68 | 69 | And a prompt will appear. Each input and output line in the prompt is numbered, and you can access previous results from the repl with the corresponding number. For example, take this repl session: 70 | 71 | ``` 72 | 0 ) (+ 1 2) 73 | 0 ) 3 74 | 75 | 1 ) (vec 1 2 3) 76 | 1 ) ( 1 2 3) 77 | ``` 78 | 79 | We can access `3`, the result from line 0, as the variable `_0`. Likewise, `_1` will reference the vector from line 1. 80 | 81 | To run Xin programs from files, pass file paths to the interpreter. For example, to run `samples/list.xin` 82 | 83 | ``` 84 | xin samples/list.xin 85 | ``` 86 | 87 | You can also pass input as stdin piped into the CLI: 88 | 89 | ``` 90 | $ echo '(log (vec::sum (nat 10))) | xin' 91 | 55 # -> output 92 | ``` 93 | 94 | ## Key ideas explored 95 | 96 | While Xin is meant to be a practical general-purpose programming language, as a toy project, it explores a few key ideas that I couldn't elegantly fit into Ink, my first language. 97 | 98 | ### Lazy evaluation 99 | 100 | In Xin, the evaluation of every expression is deferred until usage. We call this lazy evaluation. Values are kept in "lazy" states until some outside action or special form coerces a lazy value to resolve to a real value. 101 | 102 | ### Code as data structure 103 | 104 | Like all Lisps, the power and flexibility of Xin comes from its syntax, and the fact that it's trivial to express Xin programs as simple nested list data structures. Xin is not a "real" Lisp, because Xin does not use linked lists internally to represent its own syntax. But the idea of runtime syntax introspection with list data structures through macros is alive in Xin, and it's much of what makes the language so extensible. 105 | 106 | Expressing all syntax as lists (auto-resizing vectors, internally) allows functions to modify or introspect their syntax at runtime, and this allows Xin programmers to build language constructs like switch cases, variadic functions, and lambdas in the userspace, as a library function, rather than having to add them to the language core, which remains tiny. 107 | 108 | ### Streaming I/O and pipes 109 | 110 | Xin expresses all I/O operations as operations to streams. 111 | 112 | ### Syntax minimalism and extensibility 113 | 114 | Xin is unusual among even toy programming languages in that there are only four special forms defined in the language spec: `:` (called "bind"), `if`, `do`, and `import`. All other language constructs, like loops, switch cases, booleans, and iteration primitives are defined in the standard library as Xin forms, not in the runtime as special cases. 115 | -------------------------------------------------------------------------------- /samples/art/shape.xin: -------------------------------------------------------------------------------- 1 | ; shape-drawing library 2 | 3 | ; in this library 4 | ; we use an x-y coordinate system as below 5 | ; 6 | ; +----------- Y -> 7 | ; | 8 | ; | 9 | ; | [ shapes! ] 10 | ; | 11 | ; X 12 | ; | 13 | ; v 14 | 15 | (import '../bmp' bmp) 16 | 17 | ; COLOR functions 18 | 19 | (: (fff) 20 | (vec 255 255 255)) 21 | (: (rgb r g b) 22 | (vec r g b)) 23 | 24 | (: (red pixel) 25 | (vec::get pixel 0)) 26 | (: (green pixel) 27 | (vec::get pixel 1)) 28 | (: (blue pixel) 29 | (vec::get pixel 2)) 30 | 31 | ; CANVAS functions 32 | 33 | (: (create-canvas width height) 34 | (vec::map (vec::of height 0) 35 | (: (f) 36 | (vec::map (vec::of width 0) fff)))) 37 | 38 | (: (canvas-width canvas) 39 | (vec::size (vec::head canvas))) 40 | (: (canvas-height canvas) 41 | (vec::size canvas)) 42 | (: (canvas-serialize canvas) 43 | (vec::flat (vec::reverse canvas))) 44 | 45 | (: (canvas-each canvas f) 46 | (vec::each canvas 47 | (: (g column x) 48 | (vec::each column (: (h pixel y) 49 | (f pixel x y)))))) 50 | 51 | ; save canvas contents to file on disk at 52 | (: (write-canvas canvas path) 53 | (do 54 | (: file (os::open path)) 55 | (: image (bmp::bmp (canvas-width canvas) 56 | (canvas-height canvas) 57 | (canvas-serialize canvas))) 58 | 59 | (<- file image 60 | (: (after success?) 61 | (if success? 62 | (log 'Saved file.') 63 | (log 'Error saving file.')))))) 64 | 65 | ; get a mutable pixel from canvas at (x, y) 66 | (: (get-pixel canvas x y) 67 | (vec::get (vec::get canvas x) y)) 68 | 69 | ; check whether a particular (x, y) falls in canvas bounds 70 | (: (in-canvas? canvas x y) 71 | (if (zero? (vec::get canvas x)) 72 | false 73 | (not (zero? (vec::get (vec::get canvas x) y))))) 74 | 75 | ; color a specific point on the canvas 76 | (: (color! pixel color) 77 | (do 78 | (vec::set! pixel 0 (red color)) 79 | (vec::set! pixel 1 (blue color)) 80 | (vec::set! pixel 2 (green color)))) 81 | 82 | ; utility needed for draw-circle! 83 | (: (distance x0 y0 x1 y1) 84 | (sqrt (+ (^ (- x0 x1) 2) 85 | (^ (- y0 y1) 2)))) 86 | 87 | ; SHAPE functions 88 | 89 | (: (draw-point! canvas color 90 | x y) 91 | (draw-circle! canvas color x y 1)) 92 | 93 | (: (draw-line! canvas color 94 | x0 y0 x1 y1) 95 | (do 96 | (: slope (if (= x0 x1) 97 | ; if the slope is infinite, we approximate 98 | ; with a slope 2x height of canvas 99 | (* 2 (canvas-height canvas)) 100 | (/ (- y1 y0) 101 | (frac (- x1 x0))))) 102 | (: (f x) 103 | (math::floor (+ y0 104 | (* (- x x0) 105 | slope)))) 106 | (vec::each 107 | (range x0 (inc x1) 1) 108 | (: (_ x) 109 | ; generate all y's for the x 110 | ; this is a pretty convoluted algorithm 111 | ; and you do not need to understand it 112 | (vec::each 113 | (vec::filter 114 | (vec::add! 115 | (range (f x) 116 | (f (inc x)) 117 | 1) 118 | (f x)) 119 | (: (_ possible-y) 120 | (& (<= y0 possible-y) 121 | (<= possible-y y1)))) 122 | (: (_ y) 123 | (if (in-canvas? canvas x y) 124 | (color! (get-pixel canvas x y) 125 | color) 126 | pass))))))) 127 | 128 | (: (draw-circle! canvas color 129 | cx cy radius) 130 | (do 131 | (: x-basis (dec (- cx radius))) 132 | (: y-basis (dec (- cy radius))) 133 | (: x-cap (inc (+ cx radius))) 134 | (: y-cap (inc (+ cy radius))) 135 | 136 | ; we only iterate through the subset of the canvas 137 | ; in which circle points will exist 138 | (: (ranged-each canvas f) 139 | (vec::each (vec::slice canvas x-basis x-cap) 140 | (: (g column x) 141 | (vec::each (vec::slice column y-basis y-cap) 142 | (: (h pixel y) 143 | (f pixel 144 | (+ (if (< x-basis 0) 145 | 0 146 | x-basis) x) 147 | (+ (if (< y-basis 0) 148 | 0 149 | y-basis) y))))))) 150 | 151 | (ranged-each canvas 152 | (: (f pixel x y) 153 | (if (= radius 154 | (math::round (distance x y cx cy))) 155 | (color! pixel color) 156 | pass))))) 157 | 158 | (: (draw-rect! canvas color 159 | x0 y0 x1 y1) 160 | (do 161 | (draw-line! canvas color 162 | x0 y0 x0 y1) 163 | (draw-line! canvas color 164 | x0 y0 x1 y0) 165 | (draw-line! canvas color 166 | x0 y1 x1 y1) 167 | (draw-line! canvas color 168 | x1 y0 x1 y1))) 169 | -------------------------------------------------------------------------------- /pkg/xin/vec.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | // vecUnderlying provides a layer of indirection 4 | // we need to allow vecs to be mutable in-place 5 | // because Go slices are not in-place mutable. 6 | // This also allows VecValue to be hashable for use 7 | // in a MapValue as a key. 8 | type vecUnderlying struct { 9 | items []Value 10 | } 11 | 12 | type VecValue struct { 13 | underlying *vecUnderlying 14 | } 15 | 16 | func NewVecValue(items []Value) VecValue { 17 | return VecValue{ 18 | underlying: &vecUnderlying{items}, 19 | } 20 | } 21 | 22 | func (v VecValue) String() string { 23 | ss := "" 24 | for _, item := range v.underlying.items { 25 | ss += " " + item.String() 26 | } 27 | return "(" + ss + ")" 28 | } 29 | 30 | func (v VecValue) Repr() string { 31 | ss := "" 32 | for _, item := range v.underlying.items { 33 | ss += " " + item.Repr() 34 | } 35 | return "(" + ss + ")" 36 | } 37 | 38 | func (v VecValue) Equal(o Value) bool { 39 | if ov, ok := o.(VecValue); ok { 40 | return v.underlying == ov.underlying 41 | } 42 | 43 | return false 44 | } 45 | 46 | func vecForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 47 | vecValues := make([]Value, len(args)) 48 | for i, a := range args { 49 | vecValues[i] = a 50 | } 51 | return NewVecValue(vecValues), nil 52 | } 53 | 54 | func vecGetForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 55 | if len(args) < 2 { 56 | return nil, IncorrectNumberOfArgsError{ 57 | node: node, 58 | required: 2, 59 | given: len(args), 60 | } 61 | } 62 | 63 | first, second := args[0], args[1] 64 | 65 | firstVec, fok := first.(VecValue) 66 | secondInt, sok := second.(IntValue) 67 | if fok && sok { 68 | max := len(firstVec.underlying.items) 69 | inRange := func(iv IntValue) bool { 70 | return int(iv) >= 0 && int(iv) < max 71 | } 72 | 73 | if max > 0 && inRange(secondInt) { 74 | return firstVec.underlying.items[secondInt], nil 75 | } 76 | 77 | return zeroValue, nil 78 | } 79 | 80 | return nil, MismatchedArgumentsError{ 81 | node: node, 82 | args: args, 83 | } 84 | } 85 | 86 | func vecSetForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 87 | if len(args) < 3 { 88 | return nil, IncorrectNumberOfArgsError{ 89 | node: node, 90 | required: 3, 91 | given: len(args), 92 | } 93 | } 94 | 95 | first, second, third := args[0], args[1], args[2] 96 | 97 | firstVec, fok := first.(VecValue) 98 | secondInt, sok := second.(IntValue) 99 | if fok && sok { 100 | max := len(firstVec.underlying.items) 101 | inRange := func(iv IntValue) bool { 102 | return int(iv) >= 0 && int(iv) < max 103 | } 104 | 105 | if max > 0 && inRange(secondInt) { 106 | firstVec.underlying.items[secondInt] = third 107 | return firstVec, nil 108 | } 109 | 110 | return zeroValue, nil 111 | } 112 | 113 | return nil, MismatchedArgumentsError{ 114 | node: node, 115 | args: args, 116 | } 117 | } 118 | 119 | func vecAddForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 120 | if len(args) < 2 { 121 | return nil, IncorrectNumberOfArgsError{ 122 | node: node, 123 | required: 2, 124 | given: len(args), 125 | } 126 | } 127 | 128 | first, second := args[0], args[1] 129 | 130 | if firstVec, fok := first.(VecValue); fok { 131 | firstVec.underlying.items = append(firstVec.underlying.items, second) 132 | return firstVec, nil 133 | } 134 | 135 | return nil, MismatchedArgumentsError{ 136 | node: node, 137 | args: args, 138 | } 139 | } 140 | 141 | func vecSizeForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 142 | if len(args) < 1 { 143 | return nil, IncorrectNumberOfArgsError{ 144 | node: node, 145 | required: 1, 146 | given: len(args), 147 | } 148 | } 149 | 150 | first := args[0] 151 | 152 | if firstVec, fok := first.(VecValue); fok { 153 | return IntValue(len(firstVec.underlying.items)), nil 154 | } 155 | 156 | return nil, MismatchedArgumentsError{ 157 | node: node, 158 | args: args, 159 | } 160 | } 161 | 162 | func vecSliceForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 163 | if len(args) < 3 { 164 | return nil, IncorrectNumberOfArgsError{ 165 | node: node, 166 | required: 3, 167 | given: len(args), 168 | } 169 | } 170 | 171 | first, second, third := args[0], args[1], args[2] 172 | 173 | firstVec, fok := first.(VecValue) 174 | secondInt, sok := second.(IntValue) 175 | thirdInt, tok := third.(IntValue) 176 | if fok && sok && tok { 177 | max := len(firstVec.underlying.items) 178 | inRange := func(iv IntValue) bool { 179 | return int(iv) >= 0 && int(iv) <= max 180 | } 181 | 182 | if int(secondInt) < 0 { 183 | secondInt = 0 184 | } 185 | if int(thirdInt) < 0 { 186 | thirdInt = 0 187 | } 188 | 189 | if int(secondInt) > max { 190 | secondInt = IntValue(max) 191 | } 192 | if int(thirdInt) > max { 193 | thirdInt = IntValue(max) 194 | } 195 | 196 | if thirdInt < secondInt { 197 | thirdInt = secondInt 198 | } 199 | 200 | if inRange(secondInt) && inRange(thirdInt) { 201 | base := make([]Value, 0, thirdInt-secondInt) 202 | items := append(base, firstVec.underlying.items[secondInt:thirdInt]...) 203 | return NewVecValue(items), nil 204 | } 205 | 206 | return zeroValue, nil 207 | } 208 | 209 | return nil, MismatchedArgumentsError{ 210 | node: node, 211 | args: args, 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /samples/freq.xin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xin 2 | 3 | ; counting the top N most frequently used words in a file 4 | ; 5 | ; usage: xin freq.xin [count] 6 | ; (count is optional, defaults to 20) 7 | 8 | ; a char is significant for our counting if 9 | ; it's a letter or _ or - or # or ' '. 10 | ; In xin, or (|) doesn't short circuit, so we 11 | ; use if forms. 12 | (: (significant-char? c) 13 | (if (str::lower? c) 14 | true 15 | (if (str::digit? c) 16 | true 17 | (if (= c ' ') 18 | true 19 | (vec::has? (vec '-' '_' '#') c))))) 20 | 21 | ; normalize string to have only spaces for whitespaces 22 | ; and be all-lowercase, for easy parsing into words 23 | (: (normalize s) 24 | (str::filter 25 | (str::downcase 26 | ; we normalize all common whitespace to ' ' 27 | (vec::reduce (vec '\t' '\n') 28 | (: (f ws acc) 29 | (str::replace acc ws ' ')) 30 | s)) 31 | significant-char?)) 32 | 33 | ; to-word-vec takes a file's contents and splits it into 34 | ; a vector of normalized words 35 | (: (to-word-vec file) 36 | (vec::filter (str::split (normalize file) ' ') 37 | (: (not-blank? s) 38 | (! (str::blank? s))))) 39 | 40 | ; produce a map of {word -> count} mappings from a vec of words 41 | (: (freq-map word-vec) 42 | (vec::reduce word-vec 43 | (: (incr-count word counter) 44 | (if (map::has? counter word) 45 | (map::set! counter word 46 | (inc (map::get counter word))) 47 | (map::set! counter word 1))) 48 | (map))) 49 | 50 | ; converts the freq-map into a sorted list of (word, count) pairs 51 | (: (freq-sorted word-vec) 52 | (vec::sort-by (map::entries (freq-map word-vec)) 53 | (: (get-neg-count pair) 54 | (neg (vec::get pair 1))))) 55 | 56 | ; given a word-vec and the number of top N words to show (row-count), 57 | ; generate and pretty-print a textual table view of frequencies 58 | (: (display-freq-table word-vec row-count) 59 | (do 60 | (: freqs (vec::slice (freq-sorted word-vec) 0 row-count)) 61 | (: frac-size (frac (vec::size word-vec))) 62 | 63 | ; shortcuts to get count / % freq from a (word, count) pair 64 | (: (get-count pair) 65 | (vec::get pair 1)) 66 | (: (get-percent pair) 67 | (* 100 (/ (get-count pair) frac-size))) 68 | 69 | ; get max widths of words / counts to be displayed, to render 70 | ; table column widths accordingly 71 | (: max-word-size (max 4 72 | (vec::max (vec::map 73 | (vec::map freqs vec::head) 74 | str::size)))) 75 | (: max-count-size (max 5 76 | (vec::max (vec::map 77 | freqs 78 | (: (f w) 79 | (str::size (str (get-count w)))))))) 80 | 81 | ; header row 82 | (logf '{} | {} | {}' 83 | (vec (str::pad-start 'WORD' max-word-size ' ') 84 | (str::pad-start 'COUNT' max-count-size ' ') 85 | 'FREQ %')) 86 | ; divider row 87 | (log (* '=' (vec::sum (vec max-word-size 88 | 3 ; divider 89 | max-count-size 90 | 3 ; divider 91 | 6)))) 92 | ; value rows 93 | (vec::each freqs 94 | (: (log-row pair) 95 | (logf '{} | {} | {}' 96 | (vec (str::pad-start (vec::head pair) 97 | max-word-size 98 | ' ') 99 | (str::pad-start (str (get-count pair)) 100 | max-count-size 101 | ' ') 102 | (+ (str::slice (str (get-percent pair)) 103 | 0 5) 104 | '%'))))))) 105 | 106 | ; read CLI args 107 | (: filepath (vec::get (os::args) 2)) 108 | (: row-count 109 | (if (< (vec::size (os::args)) 4) 110 | ; default top 10 words 111 | 20 112 | (int (vec::get (os::args) 3)))) 113 | 114 | ; main proc 115 | (if (zero? (: file 116 | (os::open filepath))) 117 | (logf 'Error: could not open "{}" for reading.' 118 | (vec filepath)) 119 | (if (zero? row-count) 120 | (logf 'Error: could not parse row count "{}".' 121 | (vec (vec::get (os::args) 3))) 122 | (do 123 | (logf 'Analyzing {}:\n' 124 | (vec filepath)) 125 | 126 | (->> file 127 | (: (f content) 128 | (do 129 | (stream::close! file) 130 | (: word-vec (to-word-vec content)) 131 | 132 | (display-freq-table 133 | word-vec 134 | row-count) 135 | 136 | (log '') 137 | (logf 'Size:\t{} bytes' 138 | (vec (str::size content))) 139 | (logf 'Total:\t{} words' 140 | (vec (vec::size word-vec))) 141 | (logf 'Uniq:\t{} words' 142 | (vec (vec::size (map::keys (freq-map word-vec))))))))))) 143 | -------------------------------------------------------------------------------- /lib/vec.xin: -------------------------------------------------------------------------------- 1 | ; vec standard library 2 | 3 | ; we use std::{min max} in this lib, 4 | ; so stash them away behind the std namespace 5 | (: std::min min) 6 | (: std::max max) 7 | 8 | (: (head v) 9 | (vec::get v 0)) 10 | 11 | (: (tail v) 12 | (vec::slice v 1 (vec::size v))) 13 | 14 | (: (last v) 15 | (vec::get v (- (vec::size v) 1))) 16 | 17 | (: (empty? v) 18 | (= (vec::size v) 0)) 19 | 20 | (: (eq? v w) 21 | (if (= (vec::size v) (vec::size w)) 22 | (reduce v 23 | (: (f x acc i) 24 | (& acc (= x (vec::get w i)))) 25 | true) 26 | false)) 27 | 28 | (: (index v x) 29 | ((: (rec i) 30 | (if (> i (vec::size v)) 31 | -1 32 | (if (= (vec::get v i) x) 33 | i 34 | (rec (+ i 1))))) 35 | 0)) 36 | 37 | (: (has? v x) 38 | (> (index v x) -1)) 39 | 40 | (: (reverse v) 41 | (if (empty? v) 42 | (vec) 43 | (vec::add! (reverse (tail v)) (head v)))) 44 | 45 | ; join mutates v 46 | (: (join! v w) 47 | (reduce w 48 | (: (f x acc) 49 | (vec::add! acc x)) 50 | v)) 51 | 52 | (: (reduce v f acc) 53 | (do 54 | (: max (vec::size v)) 55 | (: (sub i acc) 56 | (if (< i max) 57 | (sub (+ i 1) 58 | (f (vec::get v i) acc i)) 59 | acc)) 60 | (sub 0 acc))) 61 | 62 | (: (each v f) 63 | (do 64 | (: max (vec::size v)) 65 | (: (sub i) 66 | (if (< i max) 67 | (do (f (vec::get v i) i) 68 | (sub (+ i 1))) 69 | 0)) 70 | (sub 0))) 71 | 72 | (: (map v f) 73 | (reduce v 74 | (: (g x acc i) 75 | (vec::add! acc (f x i))) 76 | (vec))) 77 | 78 | ; the fastest way to clone a xin vec 79 | ; is to use the native vec::slice 80 | (: (clone v) 81 | (vec::slice v 0 (vec::size v))) 82 | 83 | (: (filter v f) 84 | (reduce v 85 | (: (g x acc i) 86 | (if (f x i) 87 | (vec::add! acc x) 88 | acc)) 89 | (vec))) 90 | 91 | (: (every v) 92 | (reduce v 93 | (: (f x acc) 94 | (& acc x)) 95 | true)) 96 | 97 | (: (some v) 98 | (reduce v 99 | (: (f x acc) 100 | (| acc x)) 101 | false)) 102 | 103 | (: (zip op v w) 104 | (do 105 | (: min-size (std::min (vec::size v) (vec::size w))) 106 | (: v (take v min-size)) 107 | (: w (take w min-size)) 108 | (: (sub i acc) 109 | (if (= i min-size) 110 | acc 111 | (sub (inc i) 112 | (vec::add! acc (op (vec::get v i) 113 | (vec::get w i)))))) 114 | (sub 0 (vec)))) 115 | 116 | ; quicksort using hoare partition 117 | (: (sort-by v pred) 118 | (do (: v-pred (map v pred)) 119 | (: (swap! i j) 120 | (do 121 | (: tmp (vec::get v i)) 122 | (: tmp-pred (vec::get v-pred i)) 123 | (vec::set! v i (vec::get v j)) 124 | (vec::set! v j tmp) 125 | (vec::set! v-pred i (vec::get v-pred j)) 126 | (vec::set! v-pred j tmp-pred))) 127 | (: (partition v lo hi) 128 | (do (: pivot (vec::get v-pred lo)) 129 | (: (lsub i) 130 | (if (>= (vec::get v-pred i) pivot) 131 | i (lsub (+ i 1)))) 132 | (: (rsub j) 133 | (if (<= (vec::get v-pred j) pivot) 134 | j (rsub (- j 1)))) 135 | (: (sub i j) 136 | (do 137 | (: i (lsub i)) 138 | (: j (rsub j)) 139 | (if (>= i j) 140 | j 141 | (do (swap! i j) 142 | (sub (+ i 1) (- j 1)))))) 143 | (sub lo hi))) 144 | (: (quicksort v lo hi) 145 | (if (empty? v) 146 | v 147 | (if (< lo hi) 148 | (do (: p (partition v lo hi)) 149 | (quicksort v lo p) 150 | (quicksort v (+ p 1) hi)) 151 | v))) 152 | (quicksort v 0 (- (vec::size v) 1)))) 153 | 154 | (: (sort! v) 155 | (sort-by v identity)) 156 | (: (sort v) 157 | (sort! (clone v))) 158 | 159 | ; naive uniq of a list 160 | ; that does not assume sortability 161 | (: (uniq v) 162 | (reduce v 163 | (: (add-if-uniq x acc) 164 | (if (has? acc x) 165 | acc 166 | (vec::add! acc x))) 167 | (vec))) 168 | 169 | ; building blocks to allow us to compose vector ops 170 | (: (compose op) 171 | (: (? v w) 172 | (reduce v 173 | (: (f x acc i) 174 | (vec::add! acc (op x (vec::get w i)))) 175 | (vec)))) 176 | 177 | ; vec of size n with elements of x 178 | (: (of n x) 179 | (* (vec x) n)) 180 | 181 | ; accumulator functions 182 | (: (max v) 183 | (if (empty? v) 184 | 0 185 | (reduce v std::max (head v)))) 186 | (: (min v) 187 | (if (empty? v) 188 | 0 189 | (reduce v std::min (head v)))) 190 | (: (sum v) 191 | (reduce v + 0)) 192 | (: (prod v) 193 | (reduce v * 1)) 194 | 195 | ; elementwise operators 196 | (: ++ (compose +)) 197 | (: -- (compose -)) 198 | (: ** (compose *)) 199 | (: // (compose /)) 200 | 201 | ; scalar operators 202 | (: (v+ v s) 203 | (++ v (of (vec::size v) s))) 204 | (: (v- v s) 205 | (-- v (of (vec::size v) s))) 206 | (: (v* v s) 207 | (** v (of (vec::size v) s))) 208 | (: (v/ v s) 209 | (// v (of (vec::size v) s))) 210 | 211 | ; dot and cross products, other combinators 212 | (: (flat v) 213 | (reduce v (: (f w acc) (join! acc w)) (vec))) 214 | (: (dot v w) 215 | (sum (** v w))) 216 | (: (combine v w op) 217 | (map (flat (of (vec::size w) v)) 218 | (: (f x i) 219 | (op x (vec::get w (/ i (vec::size w))))))) 220 | -------------------------------------------------------------------------------- /lib/str.xin: -------------------------------------------------------------------------------- 1 | ; str standard library 2 | 3 | (: (blank? s) 4 | (= s '')) 5 | 6 | (: (reverse s) 7 | (if (blank? s) 8 | '' 9 | (+ (reverse (str::slice s 1 (str::size s))) 10 | (str::get s 0)))) 11 | 12 | (: (reduce s f acc) 13 | (do 14 | (: max (str::size s)) 15 | (: (sub i acc) 16 | (if (< i max) 17 | (sub (+ i 1) 18 | (f (str::get s i) acc i)) 19 | acc)) 20 | (sub 0 acc))) 21 | 22 | ; because we'll use map as a function name here, 23 | ; alias the buitlin map function 24 | (: map::new map) 25 | (: (map s f) 26 | (reduce s 27 | (: (g c acc i) 28 | (str::add! acc (f c i))) 29 | '')) 30 | 31 | (: (filter s f) 32 | (reduce s 33 | (: (g c acc i) 34 | (if (f c i) 35 | (str::add! acc c) 36 | acc)) 37 | '')) 38 | 39 | (: (join v pad) 40 | (if (vec::empty? v) 41 | '' 42 | (vec::reduce (vec::tail v) 43 | (: (f s acc) 44 | (+ acc (+ pad s))) 45 | (vec::head v)))) 46 | 47 | (: (index s sub) 48 | ((: (rec i) 49 | (if (> i (str::size s)) 50 | -1 51 | (if (= (str::slice s i (+ i (str::size sub))) sub) 52 | i 53 | (rec (+ i 1))))) 54 | 0)) 55 | 56 | (: (has? s sub) 57 | (> (index s sub) -1)) 58 | 59 | (: (prefix? s sub) 60 | (= sub (str::slice s 61 | 0 62 | (str::size sub)))) 63 | 64 | (: (suffix? s sub) 65 | (= sub (str::slice s 66 | (- (str::size s) (str::size sub)) 67 | (str::size s)))) 68 | 69 | ; utility to check ascii ranges 70 | (: (is-in-range n min max) 71 | (& (>= n min) (<= n max))) 72 | 73 | (: (digit? s) 74 | (is-in-range (str::enc s) 48 57)) 75 | 76 | (: (upper? s) 77 | (is-in-range (str::enc s) 65 90)) 78 | 79 | (: (lower? s) 80 | (is-in-range (str::enc s) 97 122)) 81 | 82 | (: (letter? s) 83 | (| (upper? s) (lower? s))) 84 | 85 | (: (upcase s) 86 | (map s 87 | (: (f c) 88 | (if (lower? c) 89 | (str::dec (- (str::enc c) 32)) 90 | c)))) 91 | 92 | (: (downcase s) 93 | (map s 94 | (: (f c) 95 | (if (upper? c) 96 | (str::dec (+ (str::enc c) 32)) 97 | c)))) 98 | 99 | (: (pad-start s cap fill) 100 | (if (< (str::size s) cap) 101 | (do (: diff (- cap (str::size s))) 102 | (+ (str::slice (* fill (inc (/ diff (str::size fill)))) 0 diff) 103 | s)) 104 | s)) 105 | 106 | (: (pad-end s cap fill) 107 | (if (< (str::size s) cap) 108 | (do (: diff (- cap (str::size s))) 109 | (+ s 110 | (str::slice (* fill (inc (/ diff (str::size fill)))) 0 diff))) 111 | s)) 112 | 113 | ; optimized str::replace with minimal copying / O(n) searching 114 | (: (replace s old new) 115 | ((: (sub pref suff) 116 | (if (= (: idx (index suff old)) -1) 117 | (+ pref suff) 118 | (sub (+ pref 119 | (+ (str::slice suff 0 idx) 120 | new)) 121 | (str::slice suff 122 | (+ idx (str::size old)) 123 | (str::size suff))))) 124 | '' s)) 125 | 126 | (: (trim-start s part) 127 | (if (blank? part) 128 | s 129 | ((: (sub r) 130 | (if (prefix? r part) 131 | (sub (str::slice r 132 | (str::size part) 133 | (str::size r))) 134 | r)) s))) 135 | 136 | (: (trim-end s part) 137 | (if (blank? part) 138 | s 139 | ((: (sub r) 140 | (if (suffix? r part) 141 | (sub (str::slice r 142 | 0 143 | (- (str::size r) (str::size part)))) 144 | r)) s))) 145 | 146 | (: (trim s part) 147 | (trim-end (trim-start s part) part)) 148 | 149 | ; in the special case of (split s '') 150 | ; we instead split at every byte character 151 | (: (split-into-chars s) 152 | (reduce s 153 | (: (f c acc) 154 | (vec::add! acc c)) 155 | (vec))) 156 | (: (split s delim) 157 | (if (blank? delim) 158 | (split-into-chars s) 159 | ((: (sub substr sublist) 160 | (if (= (: idx (index substr delim)) -1) 161 | (vec::add! sublist substr) 162 | (sub (str::slice substr 163 | (+ idx (str::size delim)) 164 | (str::size substr)) 165 | (vec::add! sublist 166 | (str::slice substr 167 | 0 168 | idx))))) 169 | s (vec)))) 170 | 171 | ; rudimentary string formatting placeholders with just '{}' 172 | ; optimized and tail-recursive with minimal copying, 173 | ; takes after str::replace 174 | ; 175 | ; ex) (fmt 'start {} {},{} end' 176 | ; (vec 1 2 3)) 177 | ; -> 'start 1 2,3 end' 178 | (: (fmt s items) 179 | ((: (sub pref suff items) 180 | (if (| (= (: idx (index suff '{}')) -1) 181 | (vec::empty? items)) 182 | (+ pref suff) 183 | (sub (+ pref 184 | (+ (str::slice suff 0 idx) 185 | (str (vec::head items)))) 186 | (str::slice suff 187 | (+ idx 2) 188 | (str::size suff)) 189 | (vec::tail items)))) 190 | '' s items)) 191 | 192 | (do (: esc (map::new)) 193 | (map::set! esc '\n' '\\n') 194 | (map::set! esc '\r' '\\r') 195 | (map::set! esc '\t' '\\t') 196 | (map::set! esc '\\' '\\\\') 197 | (map::set! esc '\'' '\\\'')) 198 | (: (escape s) 199 | (map s (: (f c) 200 | (if (map::has? esc c) 201 | (map::get esc c) 202 | c)))) 203 | -------------------------------------------------------------------------------- /pkg/xin/string.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | ) 7 | 8 | type StringValue []byte 9 | 10 | func (v StringValue) String() string { 11 | return string(v) 12 | } 13 | 14 | func (v StringValue) Repr() string { 15 | return "'" + strings.ReplaceAll(string(v), "'", "\\'") + "'" 16 | } 17 | 18 | func (v StringValue) Equal(o Value) bool { 19 | if ov, ok := o.(StringValue); ok { 20 | return bytes.Compare(v, ov) == 0 21 | } 22 | 23 | return false 24 | } 25 | 26 | func strGetForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 27 | if len(args) < 2 { 28 | return nil, IncorrectNumberOfArgsError{ 29 | node: node, 30 | required: 2, 31 | given: len(args), 32 | } 33 | } 34 | 35 | first, second := args[0], args[1] 36 | 37 | firstStr, fok := first.(StringValue) 38 | secondInt, sok := second.(IntValue) 39 | if fok && sok { 40 | if int(secondInt) >= 0 && int(secondInt) < len(firstStr) { 41 | return firstStr[secondInt : secondInt+1], nil 42 | } 43 | 44 | return zeroValue, nil 45 | } 46 | 47 | return nil, MismatchedArgumentsError{ 48 | node: node, 49 | args: args, 50 | } 51 | } 52 | 53 | func strSetForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 54 | if len(args) < 3 { 55 | return nil, IncorrectNumberOfArgsError{ 56 | node: node, 57 | required: 3, 58 | given: len(args), 59 | } 60 | } 61 | 62 | first, second, third := args[0], args[1], args[2] 63 | 64 | firstStr, fok := first.(StringValue) 65 | secondInt, sok := second.(IntValue) 66 | thirdStr, tok := third.(StringValue) 67 | if fok && sok && tok { 68 | si := int(secondInt) 69 | if si >= 0 && si < len(firstStr) { 70 | for i, r := range thirdStr { 71 | if si+i < len(firstStr) { 72 | firstStr[si+i] = r 73 | } else { 74 | firstStr = append(firstStr, r) 75 | } 76 | } 77 | 78 | randTok := node.leaves[1].token 79 | if randTok.kind == tkName { 80 | err := fr.Up(randTok.value, firstStr, node.position) 81 | if err != nil { 82 | return nil, err 83 | } 84 | } 85 | 86 | return firstStr, nil 87 | } 88 | 89 | return zeroValue, nil 90 | } 91 | 92 | return nil, MismatchedArgumentsError{ 93 | node: node, 94 | args: args, 95 | } 96 | } 97 | 98 | func strAddForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 99 | if len(args) < 2 { 100 | return nil, IncorrectNumberOfArgsError{ 101 | node: node, 102 | required: 2, 103 | given: len(args), 104 | } 105 | } 106 | 107 | first, second := args[0], args[1] 108 | 109 | firstStr, fok := first.(StringValue) 110 | secondStr, sok := second.(StringValue) 111 | if fok && sok { 112 | firstStr = append(firstStr, secondStr...) 113 | 114 | randTok := node.leaves[1].token 115 | if randTok.kind == tkName { 116 | err := fr.Up(randTok.value, firstStr, node.position) 117 | if err != nil { 118 | return nil, err 119 | } 120 | } 121 | 122 | return firstStr, nil 123 | } 124 | 125 | return nil, MismatchedArgumentsError{ 126 | node: node, 127 | args: args, 128 | } 129 | } 130 | 131 | func strSizeForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 132 | if len(args) < 1 { 133 | return nil, IncorrectNumberOfArgsError{ 134 | node: node, 135 | required: 1, 136 | given: len(args), 137 | } 138 | } 139 | 140 | first := args[0] 141 | 142 | if firstString, ok := first.(StringValue); ok { 143 | return IntValue(len(firstString)), nil 144 | } 145 | 146 | return nil, MismatchedArgumentsError{ 147 | node: node, 148 | args: args, 149 | } 150 | } 151 | 152 | func strSliceForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 153 | if len(args) < 3 { 154 | return nil, IncorrectNumberOfArgsError{ 155 | node: node, 156 | required: 3, 157 | given: len(args), 158 | } 159 | } 160 | 161 | first, second, third := args[0], args[1], args[2] 162 | 163 | firstStr, fok := first.(StringValue) 164 | secondInt, sok := second.(IntValue) 165 | thirdInt, tok := third.(IntValue) 166 | if fok && sok && tok { 167 | max := len(firstStr) 168 | inRange := func(iv IntValue) bool { 169 | return int(iv) >= 0 && int(iv) <= max 170 | } 171 | 172 | if int(secondInt) < 0 { 173 | secondInt = 0 174 | } 175 | if int(thirdInt) < 0 { 176 | thirdInt = 0 177 | } 178 | 179 | if int(secondInt) > max { 180 | secondInt = IntValue(max) 181 | } 182 | if int(thirdInt) > max { 183 | thirdInt = IntValue(max) 184 | } 185 | 186 | if thirdInt < secondInt { 187 | thirdInt = secondInt 188 | } 189 | 190 | if inRange(secondInt) && inRange(thirdInt) && secondInt <= thirdInt { 191 | byteSlice := firstStr[secondInt:thirdInt] 192 | destSlice := make([]byte, len(byteSlice)) 193 | copy(destSlice, byteSlice) 194 | return StringValue(destSlice), nil 195 | } 196 | 197 | return zeroValue, nil 198 | } 199 | 200 | return nil, MismatchedArgumentsError{ 201 | node: node, 202 | args: args, 203 | } 204 | } 205 | 206 | func strEncForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 207 | if len(args) < 1 { 208 | return nil, IncorrectNumberOfArgsError{ 209 | node: node, 210 | required: 1, 211 | given: len(args), 212 | } 213 | } 214 | 215 | first := args[0] 216 | 217 | if firstStr, ok := first.(StringValue); ok { 218 | if len(firstStr) < 1 { 219 | return zeroValue, nil 220 | } 221 | 222 | return IntValue(firstStr[0]), nil 223 | } 224 | 225 | return nil, MismatchedArgumentsError{ 226 | node: node, 227 | args: args, 228 | } 229 | } 230 | 231 | func strDecForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 232 | if len(args) < 1 { 233 | return nil, IncorrectNumberOfArgsError{ 234 | node: node, 235 | required: 1, 236 | given: len(args), 237 | } 238 | } 239 | 240 | first := args[0] 241 | 242 | if firstInt, ok := first.(IntValue); ok { 243 | if firstInt < 0 || firstInt > 255 { 244 | return zeroValue, nil 245 | } 246 | 247 | return StringValue([]byte{byte(firstInt)}), nil 248 | } 249 | 250 | return nil, MismatchedArgumentsError{ 251 | node: node, 252 | args: args, 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /SPEC.md: -------------------------------------------------------------------------------- 1 | # Xin language specification 2 | 3 | ## Introduction 4 | 5 | Xin is a functional programming language inspired by Lisp and CSP. Xin aspires to be an expressive, extensible language built on a small number of simple elements that work well together. 6 | 7 | Xin supports proper tail calls and uses tail recursion as the only looping primitive, but provides common vocabulary like `while` and `for` / `range` as standard library forms. Xin uses lazy evaluation. 8 | 9 | ## Syntax 10 | 11 | Xin's grammar is simple. Here is the complete BNF grammar for Xin. Notably, any non-whitespace non-delimiter (`()[]{}`) character is valid in an identifier. 12 | 13 | ``` 14 | ::= [\w\-\?\!\+\*\/\:\>\<\=\%\&\|]* 15 | ::= ("-" | "") (0-9)+.(0-9)+ | 0x(0-9a-fA-F)+ 16 | ::= "'" (.*) "'" 17 | 18 | ::= | | 19 | 20 | ::= "(" ( | )+ ")" 21 | 22 | ::= * 23 | ``` 24 | 25 | ### Scope 26 | 27 | Xin names are lexically scoped. There are two ways that a new scope can be created in a program. 28 | 29 | - A new program file creates its own scope. 30 | - When a new form is defined, the body of the form creates a new lexical scope. 31 | 32 | The bind form `:` links a value to a name in the current lexical scope. 33 | 34 | A bind followed by a raw name creates a static reference to a value. 35 | 36 | ``` 37 | ; take the value `val` and assign it to the name `name` 38 | (: name val) 39 | ``` 40 | 41 | A bind followed by a parenthesized expression defines a new form. 42 | 43 | ``` 44 | ; define a form `func` with arguments `a, b, c` and define it to 45 | ; be equivalent to the expression `body-forms` 46 | (: (func a b c) 47 | body-forms) 48 | 49 | ; note that below is legal, and defines a function that returns 50 | ; a constant every time, in this case the int 42. 51 | (: (constant-func) 52 | 42) 53 | ``` 54 | 55 | ## Types and values 56 | 57 | Xin has these types: 58 | 59 | - `string`: Unicode string tha can be interpreted as a byte array / blob 60 | - `int`: 64-bit integer 61 | - `frac`: 64-bit floating point 62 | - `form`: bound expression, i.e. a function 63 | - `vec`: a heterogeneous list of values 64 | - `map`: a heteroogenous hashmap of values 65 | - `stream`: a sink / source stream of values for I/O. Stream operations are not pure. 66 | 67 | `form`, `vec`, `map`, and `stream` types are passed and equality-checked by reference, all others are passed and equality checked by value. 68 | 69 | ## Evaluation 70 | 71 | ### Special forms 72 | 73 | Xin has 4 special forms. 74 | 75 | - `:`: define a new name in the current lexical scope and set it to reference a given value 76 | - `if`: an if-else 77 | - `do`: sequentially evaluate multiple following expressions 78 | - `import`: import external files as Xin programs 79 | 80 | ### Streams 81 | 82 | Streams are the primitive for constructing concurrent programs and doing I/O in Xin. Streams are sinks and sources of values that interface with the rest of the host system, or another remote part of the Xin program. 83 | 84 | The source operator `->` pops one value out of the stream. 85 | 86 | The sink operator `<-` pushes one value into the stream. 87 | 88 | For example, the `os::stdout` stream represents the standard out file of a process. Running 89 | 90 | ``` 91 | (<- os::stdout 'hello world\n') 92 | ``` 93 | 94 | will print "hello world" to standard out. 95 | 96 | Streams can be used to read and write to files, network sockets, or to arbitrary other programs and sinks/sources of values, like OS signals. Despite the vocabulary for concurrency in Xin, Xin is a single-threaded language with an interpreter lock, and concurrent calls are ordered onto a single execution timeline, like JavaScript and CPython asyncio. 97 | 98 | ### Lazy evaluation 99 | 100 | Evaluation of all expressions and forms in Xin are deferred until they are coerced to resolve to a value by a special form, or by returning a value to the Repl. Expressions are coerced to take real values in the following cases: 101 | 102 | 1. Before being bound to a name via the bind (`:`) special form 103 | 2. As a top-level expression in a `do` special form 104 | 3. When interfacing with any runtime API that interacts with the outside world, like `os::stdout` 105 | 4. When returned as a value to the Repl. 106 | 107 | ## Packages and imports 108 | 109 | Xin includes by default a set of standard packages like `os`, `math`, `vec`, and `str` (string). Xin programmers can also define their own packages by writing and referencing files with the import name. 110 | 111 | A Xin program can import values defined in another Xin program with the `import` form. There are two ways to import. 112 | 113 | - `(import path)`: find file described by `path` and make all values defined in that file available under the current global namespace. 114 | - `(import path alias)`: find file described by `path` and make values available under _only_ the alias `alias::` namespace. 115 | 116 | ``` 117 | ; makes forms and values defined in file 'core/models/user.xin' 118 | ; available under the namespace `user-model::xxx` 119 | (import 'core/models/user' user-model) 120 | ``` 121 | ## Proposals 122 | 123 | This proposals section documents ideas about additions and changes to the language that I have considered or others have suggested, but I don't think are well aligned with the current values of the language or are too early in their specifications to be included in the implementation or specification. 124 | 125 | ### Spread syntax 126 | 127 | Spread syntax enables Xin programmers to "collect" or "spread" arbitrary segments of the arguments to a form in its invocation or definition, and refer to it as a vector. 128 | 129 | ### Composite type literals 130 | 131 | Add literal syntax for vectors and maps. 132 | 133 | Vector literals are delimited with square brackets: 134 | 135 | ``` 136 | (: my-vec [1 2 3 4 [5 6]]) 137 | 138 | ; equivalent to 139 | 140 | (: my-vec 141 | (vec 1 2 3 4 (vec 5 6))) 142 | ``` 143 | 144 | Map literals are delimited with braces and use the arrow `->`: 145 | 146 | ``` 147 | (: letter-counts 148 | {'hi' -> 2 'hello' -> 5 'bye' -> 3}) 149 | 150 | ; equivalent to 151 | 152 | (: letter-counts 153 | (do (: m (map)) 154 | (map::set! m 'hi' 2) 155 | (map::set! m 'hello' 5) 156 | (map::set! m 'bye' 3))) 157 | ``` 158 | 159 | -------------------------------------------------------------------------------- /pkg/xin/map.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // StringValue is of type []byte, which is unhashable 8 | // so we convert StringValues to hashable string type 9 | // before using as map keys 10 | type hashableStringProxy string 11 | 12 | func (v hashableStringProxy) String() string { 13 | return string(v) 14 | } 15 | 16 | func (v hashableStringProxy) Repr() string { 17 | return "'" + strings.ReplaceAll(string(v), "'", "\\'") + "'" 18 | } 19 | 20 | func (v hashableStringProxy) Equal(ov Value) bool { 21 | panic("hashableStringProxy should not be equality-checked") 22 | } 23 | 24 | // hashableNativeFormProxy is a hashable proxy for 25 | // NativeFormValue 26 | type hashableNativeFormProxy string 27 | 28 | func (v hashableNativeFormProxy) String() string { 29 | return "( " + string(v) + ")" 30 | } 31 | 32 | func (v hashableNativeFormProxy) Repr() string { 33 | return v.String() 34 | } 35 | 36 | func (v hashableNativeFormProxy) Equal(ov Value) bool { 37 | panic("hashableNativeFormProxy should not be equality-checked") 38 | } 39 | 40 | func hashable(v Value) Value { 41 | switch val := v.(type) { 42 | case StringValue: 43 | return hashableStringProxy(val) 44 | case NativeFormValue: 45 | return hashableNativeFormProxy(val.name) 46 | default: 47 | return v 48 | } 49 | } 50 | 51 | // inverse of hashable(Value) 52 | func dehash(vm *Vm, v Value) Value { 53 | switch val := v.(type) { 54 | case hashableStringProxy: 55 | return StringValue(val) 56 | case hashableNativeFormProxy: 57 | return NativeFormValue{ 58 | name: string(val), 59 | evaler: vm.evalers[string(val)], 60 | } 61 | default: 62 | return v 63 | } 64 | } 65 | 66 | type mapItems map[Value]Value 67 | 68 | type MapValue struct { 69 | // indirection used to allow MapValue to be hashable 70 | // and correctly equality-checked 71 | items *mapItems 72 | } 73 | 74 | func (v MapValue) String() string { 75 | ss := "" 76 | for k, val := range *v.items { 77 | ss += " " + k.String() + "->" + val.String() 78 | } 79 | return "(" + ss + ")" 80 | } 81 | 82 | func (v MapValue) Repr() string { 83 | ss := "" 84 | for k, val := range *v.items { 85 | ss += " " + k.Repr() + "->" + val.Repr() 86 | } 87 | return "(" + ss + ")" 88 | } 89 | 90 | func (v MapValue) Equal(o Value) bool { 91 | if ov, ok := o.(MapValue); ok { 92 | return v.items == ov.items 93 | } 94 | 95 | return false 96 | } 97 | 98 | func (m MapValue) set(k, v Value) { 99 | (*m.items)[hashable(k)] = v 100 | } 101 | 102 | func NewMapValue() MapValue { 103 | return MapValue{ 104 | items: &mapItems{}, 105 | } 106 | } 107 | 108 | func mapForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 109 | return NewMapValue(), nil 110 | } 111 | 112 | func mapGetForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 113 | if len(args) < 2 { 114 | return nil, IncorrectNumberOfArgsError{ 115 | node: node, 116 | required: 2, 117 | given: len(args), 118 | } 119 | } 120 | 121 | first, second := args[0], args[1] 122 | 123 | if firstMap, ok := first.(MapValue); ok { 124 | val, prs := (*firstMap.items)[hashable(second)] 125 | if prs { 126 | return val, nil 127 | } 128 | 129 | return zeroValue, nil 130 | } 131 | 132 | return nil, MismatchedArgumentsError{ 133 | node: node, 134 | args: args, 135 | } 136 | } 137 | 138 | func mapSetForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 139 | if len(args) < 3 { 140 | return nil, IncorrectNumberOfArgsError{ 141 | node: node, 142 | required: 3, 143 | given: len(args), 144 | } 145 | } 146 | 147 | first, second, third := args[0], args[1], args[2] 148 | 149 | if firstMap, ok := first.(MapValue); ok { 150 | firstMap.set(second, third) 151 | return firstMap, nil 152 | } 153 | 154 | return nil, MismatchedArgumentsError{ 155 | node: node, 156 | args: args, 157 | } 158 | } 159 | 160 | func mapHasForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 161 | if len(args) < 2 { 162 | return nil, IncorrectNumberOfArgsError{ 163 | node: node, 164 | required: 2, 165 | given: len(args), 166 | } 167 | } 168 | 169 | first, second := args[0], args[1] 170 | 171 | if firstMap, ok := first.(MapValue); ok { 172 | _, prs := (*firstMap.items)[hashable(second)] 173 | if prs { 174 | return trueValue, nil 175 | } 176 | 177 | return zeroValue, nil 178 | } 179 | 180 | return nil, MismatchedArgumentsError{ 181 | node: node, 182 | args: args, 183 | } 184 | } 185 | 186 | func mapDelForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 187 | if len(args) < 2 { 188 | return nil, IncorrectNumberOfArgsError{ 189 | node: node, 190 | required: 2, 191 | given: len(args), 192 | } 193 | } 194 | 195 | first, second := args[0], args[1] 196 | 197 | if firstMap, ok := first.(MapValue); ok { 198 | _, prs := (*firstMap.items)[hashable(second)] 199 | if prs { 200 | delete(*firstMap.items, second) 201 | return firstMap, nil 202 | } 203 | 204 | return zeroValue, nil 205 | } 206 | 207 | return nil, MismatchedArgumentsError{ 208 | node: node, 209 | args: args, 210 | } 211 | } 212 | 213 | func mapSizeForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 214 | if len(args) < 1 { 215 | return nil, IncorrectNumberOfArgsError{ 216 | node: node, 217 | required: 1, 218 | given: len(args), 219 | } 220 | } 221 | 222 | first := args[0] 223 | 224 | if firstMap, fok := first.(MapValue); fok { 225 | return IntValue(len(*firstMap.items)), nil 226 | } 227 | 228 | return nil, MismatchedArgumentsError{ 229 | node: node, 230 | args: args, 231 | } 232 | } 233 | 234 | func mapKeysForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 235 | if len(args) < 1 { 236 | return nil, IncorrectNumberOfArgsError{ 237 | node: node, 238 | required: 1, 239 | given: len(args), 240 | } 241 | } 242 | 243 | first := args[0] 244 | 245 | if firstMap, fok := first.(MapValue); fok { 246 | keys := make([]Value, 0, len(*firstMap.items)) 247 | for k, _ := range *firstMap.items { 248 | keys = append(keys, dehash(fr.Vm, k)) 249 | } 250 | return NewVecValue(keys), nil 251 | } 252 | 253 | return nil, MismatchedArgumentsError{ 254 | node: node, 255 | args: args, 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /pkg/xin/lex.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | "strings" 8 | "unicode" 9 | "unicode/utf8" 10 | ) 11 | 12 | const ( 13 | tkOpenParen = iota 14 | tkCloseParen 15 | 16 | tkBindForm 17 | tkIfForm 18 | tkDoForm 19 | tkImportForm 20 | 21 | tkName 22 | tkNumberLiteralInt 23 | tkNumberLiteralDecimal 24 | tkNumberLiteralHex 25 | tkStringLiteral 26 | ) 27 | 28 | type tokenKind int 29 | 30 | type token struct { 31 | kind tokenKind 32 | value string 33 | position 34 | 35 | // we compute the integer and float values of 36 | // number literals at parse time for runtime efficiency 37 | intv IntValue 38 | fracv FracValue 39 | } 40 | 41 | func (tk token) String() string { 42 | switch tk.kind { 43 | case tkOpenParen: 44 | return "(" 45 | case tkCloseParen: 46 | return ")" 47 | case tkBindForm: 48 | return ":" 49 | case tkIfForm: 50 | return "if" 51 | case tkDoForm: 52 | return "do" 53 | case tkImportForm: 54 | return "import" 55 | case tkName, tkNumberLiteralInt, tkNumberLiteralDecimal, tkNumberLiteralHex: 56 | return tk.value 57 | case tkStringLiteral: 58 | return "'" + tk.value + "'" 59 | default: 60 | return "unknown token" 61 | } 62 | } 63 | 64 | type tokenStream []token 65 | 66 | func (toks tokenStream) String() string { 67 | tokstrings := make([]string, len(toks)) 68 | for i, tk := range toks { 69 | tokstrings[i] = tk.String() 70 | } 71 | return strings.Join(tokstrings, " ") 72 | } 73 | 74 | type position struct { 75 | path string 76 | line int 77 | col int 78 | } 79 | 80 | func (p position) String() string { 81 | return fmt.Sprintf("%s:%d:%d", p.path, p.line, p.col) 82 | } 83 | 84 | func charFromEscaper(escaper byte) rune { 85 | switch escaper { 86 | case 'n': 87 | return '\n' 88 | case 'r': 89 | return '\r' 90 | case 't': 91 | return '\t' 92 | case '\\': 93 | return '\\' 94 | case '\'': 95 | return '\'' 96 | default: 97 | return rune(escaper) 98 | } 99 | } 100 | 101 | func escapeString(s string) string { 102 | builder := strings.Builder{} 103 | max := len(s) 104 | 105 | for i := 0; i < max; i++ { 106 | c := s[i] 107 | if c == '\\' { 108 | i++ 109 | 110 | if i >= len(s) { 111 | return builder.String() 112 | } 113 | next := s[i] 114 | 115 | if next == 'x' { 116 | hex := s[i+1 : i+3] 117 | i += 2 118 | 119 | codepoint, err := strconv.ParseInt(hex, 16, 32) 120 | if err != nil || !utf8.ValidRune(rune(codepoint)) { 121 | builder.WriteRune('?') 122 | continue 123 | } 124 | 125 | builder.WriteRune(rune(codepoint)) 126 | } else { 127 | builder.WriteRune(charFromEscaper(next)) 128 | } 129 | } else { 130 | builder.WriteByte(c) 131 | } 132 | } 133 | 134 | return builder.String() 135 | } 136 | 137 | func bufToToken(s string, pos position) token { 138 | if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { 139 | hexPart := s[2:] 140 | if _, err := strconv.ParseInt(hexPart, 16, 64); err == nil { 141 | v, _ := strconv.ParseInt(hexPart, 16, 64) 142 | return token{ 143 | kind: tkNumberLiteralHex, 144 | value: hexPart, 145 | position: pos, 146 | intv: IntValue(v), 147 | } 148 | } else { 149 | return token{ 150 | kind: tkNumberLiteralHex, 151 | // TODO: make this throw a parse err 152 | value: "0", 153 | position: pos, 154 | intv: IntValue(0), 155 | } 156 | } 157 | } else if _, err := strconv.ParseInt(s, 10, 64); err == nil { 158 | v, _ := strconv.ParseInt(s, 10, 64) 159 | return token{ 160 | kind: tkNumberLiteralInt, 161 | value: s, 162 | position: pos, 163 | intv: IntValue(v), 164 | } 165 | } else if _, err := strconv.ParseFloat(s, 64); err == nil { 166 | v, _ := strconv.ParseFloat(s, 64) 167 | return token{ 168 | kind: tkNumberLiteralDecimal, 169 | value: s, 170 | position: pos, 171 | fracv: FracValue(v), 172 | } 173 | } else { 174 | switch s { 175 | case ":": 176 | return token{ 177 | kind: tkBindForm, 178 | position: pos, 179 | } 180 | case "if": 181 | return token{ 182 | kind: tkIfForm, 183 | position: pos, 184 | } 185 | case "do": 186 | return token{ 187 | kind: tkDoForm, 188 | position: pos, 189 | } 190 | case "import": 191 | return token{ 192 | kind: tkImportForm, 193 | position: pos, 194 | } 195 | default: 196 | return token{ 197 | kind: tkName, 198 | value: s, 199 | position: pos, 200 | } 201 | } 202 | } 203 | } 204 | 205 | func lex(path string, r io.Reader) (tokenStream, InterpreterError) { 206 | toks := make([]token, 0) 207 | rdr, err := newReader(path, r) 208 | if err != nil { 209 | return toks, nil 210 | } 211 | 212 | buf := "" 213 | clear := func() { 214 | if buf != "" { 215 | toks = append(toks, bufToToken(buf, rdr.currPos())) 216 | buf = "" 217 | } 218 | } 219 | for !rdr.done() { 220 | peeked := rdr.peek() 221 | switch { 222 | case peeked == ";": 223 | clear() 224 | rdr.upto("\n") 225 | rdr.skip() 226 | case peeked == "'": 227 | clear() 228 | rdr.skip() 229 | 230 | content := rdr.upto("'") 231 | for rdr.lookback() == "\\" { 232 | // count preceding backslashes 233 | // if there's an even number, like \\' 234 | // or \\\\', break loop 235 | backslashesBefore := 1 236 | for i := 2; i < rdr.index; i++ { 237 | if rdr.before(i) == "\\" { 238 | backslashesBefore++ 239 | } else { 240 | break 241 | } 242 | } 243 | if backslashesBefore%2 == 0 { 244 | break 245 | } 246 | 247 | rdr.skip() 248 | content += "'" 249 | content += rdr.upto("'") 250 | } 251 | 252 | toks = append(toks, token{ 253 | kind: tkStringLiteral, 254 | value: escapeString(content), 255 | position: rdr.currPos(), 256 | }) 257 | rdr.skip() 258 | case peeked == "(": 259 | clear() 260 | toks = append(toks, token{ 261 | kind: tkOpenParen, 262 | position: rdr.currPos(), 263 | }) 264 | rdr.skip() 265 | case peeked == ")": 266 | clear() 267 | toks = append(toks, token{ 268 | kind: tkCloseParen, 269 | position: rdr.currPos(), 270 | }) 271 | rdr.skip() 272 | case unicode.IsSpace([]rune(peeked)[0]): 273 | clear() 274 | rdr.skip() 275 | default: 276 | buf += rdr.next() 277 | } 278 | } 279 | 280 | // clear out remaining items in the buffer 281 | clear() 282 | 283 | return toks, nil 284 | } 285 | -------------------------------------------------------------------------------- /pkg/xin/eval.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // these values are interned 9 | const zeroValue = IntValue(0) 10 | const trueValue = IntValue(1) 11 | const falseValue = zeroValue 12 | 13 | type Frame struct { 14 | Vm *Vm 15 | Scope map[string]Value 16 | Parent *Frame 17 | 18 | cwd *string 19 | } 20 | 21 | func newFrame(parent *Frame) *Frame { 22 | if parent == nil { 23 | return &Frame{ 24 | Scope: make(map[string]Value), 25 | } 26 | } 27 | 28 | return &Frame{ 29 | Vm: parent.Vm, 30 | Scope: make(map[string]Value), 31 | Parent: parent, 32 | cwd: parent.cwd, 33 | } 34 | } 35 | 36 | func (fr *Frame) String() string { 37 | ss := make([]string, 0, len(fr.Scope)) 38 | for name, val := range fr.Scope { 39 | ss = append(ss, name+"\t"+val.String()) 40 | } 41 | 42 | parent := "" 43 | if fr.Parent != nil { 44 | parent = fr.Parent.String() 45 | } else { 46 | parent = "()" 47 | } 48 | 49 | return fmt.Sprintf("()\n %s\n%s", 50 | strings.Join(ss, "\n "), 51 | parent, 52 | ) 53 | } 54 | 55 | func (fr *Frame) Get(name string, pos position) (Value, InterpreterError) { 56 | if val, prs := fr.Scope[name]; prs { 57 | return val, nil 58 | } else if fr.Parent != nil { 59 | return fr.Parent.Get(name, pos) 60 | } 61 | return nil, UndefinedNameError{ 62 | name: name, 63 | position: pos, 64 | } 65 | } 66 | 67 | func (fr *Frame) Put(name string, val Value) { 68 | fr.Scope[name] = val 69 | } 70 | 71 | func (fr *Frame) Up(name string, val Value, pos position) InterpreterError { 72 | if _, prs := fr.Scope[name]; prs { 73 | fr.Put(name, val) 74 | return nil 75 | } else if fr.Parent != nil { 76 | return fr.Parent.Up(name, val, pos) 77 | } 78 | return UndefinedNameError{ 79 | name: name, 80 | position: pos, 81 | } 82 | } 83 | 84 | func eval(fr *Frame, node *astNode) (Value, InterpreterError) { 85 | if node.isForm { 86 | return evalForm(fr, node) 87 | } 88 | 89 | return evalAtom(fr, node) 90 | } 91 | 92 | func evalForm(fr *Frame, node *astNode) (Value, InterpreterError) { 93 | formNode := node.leaves[0] 94 | 95 | switch formNode.token.kind { 96 | case tkBindForm: 97 | return evalBindForm(fr, node.leaves[1:]) 98 | case tkIfForm: 99 | return evalIfForm(fr, node.leaves[1:]) 100 | case tkDoForm: 101 | return evalDoForm(fr, node.leaves[1:]) 102 | case tkImportForm: 103 | return evalImportForm(fr, node.leaves[1:]) 104 | case tkName: 105 | maybeForm, err := fr.Get(formNode.token.value, formNode.position) 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | return evalFormValue(fr, node, maybeForm) 111 | default: 112 | maybeForm, err := unlazyEval(fr, formNode) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | return evalFormValue(fr, node, maybeForm) 118 | } 119 | } 120 | 121 | func evalFormValue(fr *Frame, node *astNode, maybeForm Value) (Value, InterpreterError) { 122 | args := make([]Value, len(node.leaves)-1) 123 | 124 | for i, n := range node.leaves[1:] { 125 | val, err := unlazyEval(fr, n) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | args[i] = val 131 | } 132 | 133 | return evalFormWithArgs(fr, maybeForm, args, node) 134 | } 135 | 136 | func evalFormWithArgs(fr *Frame, maybeForm Value, args []Value, node *astNode) (Value, InterpreterError) { 137 | switch form := maybeForm.(type) { 138 | case FormValue: 139 | localFrame := newFrame(form.frame) 140 | 141 | nargs := len(*form.arguments) 142 | if len(args) < nargs { 143 | nargs = len(args) 144 | } 145 | for i, v := range args[:nargs] { 146 | localFrame.Put((*form.arguments)[i], v) 147 | } 148 | 149 | return LazyValue{ 150 | frame: localFrame, 151 | node: form.definition, 152 | }, nil 153 | case NativeFormValue: 154 | return form.evaler(fr, args, node) 155 | } 156 | 157 | return nil, InvalidFormError{ 158 | position: node.position, 159 | } 160 | } 161 | 162 | // unlazyEvalFormValue is used to evaluate-and-coerce a Xin form invocation, 163 | // usually for use when calling callbacks from builtin native forms. 164 | func unlazyEvalFormValue(fr *Frame, node *astNode, maybeForm Value) (Value, InterpreterError) { 165 | val, err := evalFormValue(fr, node, maybeForm) 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | return unlazy(val) 171 | } 172 | 173 | func unlazyEvalFormWithArgs(fr *Frame, maybeForm Value, args []Value, node *astNode) (Value, InterpreterError) { 174 | val, err := evalFormWithArgs(fr, maybeForm, args, node) 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | return unlazy(val) 180 | } 181 | 182 | func evalAtom(fr *Frame, node *astNode) (Value, InterpreterError) { 183 | tok := node.token 184 | switch tok.kind { 185 | case tkName: 186 | return fr.Get(tok.value, node.position) 187 | case tkNumberLiteralInt, tkNumberLiteralHex: 188 | return tok.intv, nil 189 | case tkNumberLiteralDecimal: 190 | return tok.fracv, nil 191 | case tkStringLiteral: 192 | return StringValue(tok.value), nil 193 | default: 194 | panic(fmt.Sprintf("Unrecognized token type: %d", node.token.kind)) 195 | } 196 | } 197 | 198 | func evalBindForm(fr *Frame, args []*astNode) (Value, InterpreterError) { 199 | if len(args) != 2 { 200 | return nil, InvalidBindError{nodes: args} 201 | } 202 | 203 | specimen, body := args[0], args[1] 204 | 205 | if specimen.isForm { 206 | if len(specimen.leaves) < 1 { 207 | return nil, InvalidBindError{nodes: args} 208 | } 209 | 210 | formNameNode := specimen.leaves[0] 211 | formName := "" 212 | if !formNameNode.isForm && formNameNode.token.kind == tkName { 213 | formName = formNameNode.token.value 214 | } 215 | 216 | args := specimen.leaves[1:] 217 | argNames := make(argList, len(args)) 218 | for i, n := range args { 219 | if !n.isForm && n.token.kind == tkName { 220 | argNames[i] = n.token.value 221 | } else { 222 | return nil, InvalidBindError{nodes: args} 223 | } 224 | } 225 | 226 | form := FormValue{ 227 | frame: fr, 228 | arguments: &argNames, 229 | definition: body, 230 | } 231 | fr.Put(formName, form) 232 | 233 | return form, nil 234 | } 235 | 236 | if specimen.token.kind != tkName { 237 | return nil, InvalidBindError{nodes: args} 238 | } 239 | 240 | val, err := unlazyEval(fr, body) 241 | if err != nil { 242 | return nil, err 243 | } 244 | 245 | fr.Put(specimen.token.value, val) 246 | return val, nil 247 | } 248 | 249 | func evalIfForm(fr *Frame, args []*astNode) (Value, InterpreterError) { 250 | if len(args) != 3 { 251 | return nil, InvalidIfError{ 252 | nodes: args, 253 | } 254 | } 255 | 256 | condNode := args[0] 257 | ifTrueNode := args[1] 258 | ifFalseNode := args[2] 259 | 260 | cond, err := unlazyEval(fr, condNode) 261 | if err != nil { 262 | return nil, err 263 | } 264 | 265 | switch cond { 266 | case trueValue: 267 | return eval(fr, ifTrueNode) 268 | case falseValue: 269 | return eval(fr, ifFalseNode) 270 | default: 271 | return nil, InvalidIfConditionError{ 272 | cond: cond, 273 | position: condNode.position, 274 | } 275 | } 276 | } 277 | 278 | func evalDoForm(fr *Frame, exprs []*astNode) (Value, InterpreterError) { 279 | if len(exprs) == 0 { 280 | return zeroValue, nil 281 | } 282 | 283 | lastIndex := len(exprs) - 1 284 | 285 | for _, node := range exprs[:lastIndex] { 286 | _, err := unlazyEval(fr, node) 287 | if err != nil { 288 | return nil, err 289 | } 290 | } 291 | 292 | return eval(fr, exprs[lastIndex]) 293 | } 294 | -------------------------------------------------------------------------------- /pkg/xin/stream.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import "fmt" 4 | 5 | var streamId int64 = 0 6 | 7 | type sinkCallback func(Value, *astNode) InterpreterError 8 | 9 | type sourceCallback func() (Value, InterpreterError) 10 | 11 | type closerCallback func() InterpreterError 12 | 13 | type streamCallbacks struct { 14 | sink sinkCallback 15 | source sourceCallback 16 | closer closerCallback 17 | } 18 | 19 | type StreamValue struct { 20 | // id is used to compare and de-duplicate stream values in memory 21 | id int64 22 | callbacks *streamCallbacks 23 | } 24 | 25 | func NewStream() StreamValue { 26 | streamId++ 27 | return StreamValue{ 28 | id: streamId, 29 | callbacks: &streamCallbacks{}, 30 | } 31 | } 32 | 33 | func (v StreamValue) isSink() bool { 34 | return v.callbacks.sink != nil 35 | } 36 | 37 | func (v StreamValue) isSource() bool { 38 | return v.callbacks.source != nil 39 | } 40 | 41 | func (v StreamValue) isClose() bool { 42 | return v.callbacks.closer != nil 43 | } 44 | 45 | func (v StreamValue) String() string { 46 | streamType := "" 47 | if v.isSink() { 48 | streamType += "sink " 49 | } 50 | if v.isSource() { 51 | streamType += "source " 52 | } 53 | if v.isClose() { 54 | streamType += "close " 55 | } 56 | return fmt.Sprintf("(%s)", streamType, v.id) 57 | } 58 | 59 | func (v StreamValue) Repr() string { 60 | return v.String() 61 | } 62 | 63 | func (v StreamValue) Equal(o Value) bool { 64 | if ov, ok := o.(StreamValue); ok { 65 | return v.id == ov.id 66 | } 67 | 68 | return false 69 | } 70 | 71 | func streamForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 72 | return NewStream(), nil 73 | } 74 | 75 | func streamSetSink(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 76 | if len(args) < 2 { 77 | return nil, IncorrectNumberOfArgsError{ 78 | node: node, 79 | required: 2, 80 | given: len(args), 81 | } 82 | } 83 | 84 | first := args[0] 85 | var second Value 86 | if len(args) >= 2 { 87 | second = args[1] 88 | } else { 89 | second = Noop 90 | } 91 | 92 | var secondForm Value 93 | firstStream, fok := first.(StreamValue) 94 | secondForm, sok := second.(FormValue) 95 | if !sok { 96 | secondForm, sok = second.(NativeFormValue) 97 | } 98 | 99 | if fok && sok { 100 | firstStream.callbacks.sink = func(v Value, node *astNode) InterpreterError { 101 | fr.Vm.Lock() 102 | defer fr.Vm.Unlock() 103 | 104 | _, err := unlazyEvalFormWithArgs(fr, secondForm, []Value{v}, node) 105 | return err 106 | } 107 | 108 | return secondForm, nil 109 | } 110 | 111 | return nil, MismatchedArgumentsError{ 112 | node: node, 113 | args: args, 114 | } 115 | } 116 | 117 | func streamSetSource(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 118 | if len(args) < 2 { 119 | return nil, IncorrectNumberOfArgsError{ 120 | node: node, 121 | required: 2, 122 | given: len(args), 123 | } 124 | } 125 | 126 | first, second := args[0], args[1] 127 | 128 | if firstStream, ok := first.(StreamValue); ok { 129 | if secondForm, ok := second.(FormValue); ok { 130 | if len(*secondForm.arguments) != 0 { 131 | return nil, InvalidStreamCallbackError{ 132 | reason: "Mismatched argument count in callback", 133 | } 134 | } 135 | 136 | firstStream.callbacks.source = func() (Value, InterpreterError) { 137 | fr.Vm.Lock() 138 | defer fr.Vm.Unlock() 139 | 140 | return unlazyEvalFormWithArgs(fr, secondForm, []Value{}, node) 141 | } 142 | 143 | return secondForm, nil 144 | } 145 | } 146 | 147 | return nil, MismatchedArgumentsError{ 148 | node: node, 149 | args: args, 150 | } 151 | } 152 | 153 | func streamSetClose(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 154 | if len(args) < 2 { 155 | return nil, IncorrectNumberOfArgsError{ 156 | node: node, 157 | required: 2, 158 | given: len(args), 159 | } 160 | } 161 | 162 | first, second := args[0], args[1] 163 | 164 | if firstStream, ok := first.(StreamValue); ok { 165 | if secondForm, ok := second.(FormValue); ok { 166 | if len(*secondForm.arguments) != 0 { 167 | return nil, InvalidStreamCallbackError{ 168 | reason: "Mismatched argument count in callback", 169 | } 170 | } 171 | 172 | firstStream.callbacks.closer = func() InterpreterError { 173 | fr.Vm.Lock() 174 | defer fr.Vm.Unlock() 175 | 176 | localFrame := newFrame(fr) 177 | eval(localFrame, secondForm.definition) 178 | return nil 179 | } 180 | 181 | return secondForm, nil 182 | } 183 | } 184 | 185 | return nil, MismatchedArgumentsError{ 186 | node: node, 187 | args: args, 188 | } 189 | } 190 | 191 | func streamSourceForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 192 | if len(args) < 1 { 193 | return nil, IncorrectNumberOfArgsError{ 194 | node: node, 195 | required: 1, 196 | given: len(args), 197 | } 198 | } 199 | 200 | first := args[0] 201 | var second Value 202 | if len(args) >= 2 { 203 | second = args[1] 204 | } else { 205 | second = Noop 206 | } 207 | 208 | var secondForm Value 209 | firstStream, fok := first.(StreamValue) 210 | secondForm, sok := second.(FormValue) 211 | if !sok { 212 | secondForm, sok = second.(NativeFormValue) 213 | } 214 | 215 | if fok && sok { 216 | if !firstStream.isSource() { 217 | return nil, InvalidStreamCallbackError{ 218 | reason: "Cannot try to source from a non-source stream", 219 | } 220 | } 221 | 222 | vm := fr.Vm 223 | vm.waiter.Add(1) 224 | go func() { 225 | defer vm.waiter.Done() 226 | 227 | rv, err := firstStream.callbacks.source() 228 | if err != nil { 229 | fmt.Println(FormatError(err)) 230 | return 231 | } 232 | 233 | vm.Lock() 234 | defer vm.Unlock() 235 | 236 | _, err = unlazyEvalFormWithArgs(fr, secondForm, []Value{rv}, node) 237 | if err != nil { 238 | fmt.Println(FormatError(err)) 239 | } 240 | }() 241 | 242 | return zeroValue, nil 243 | } 244 | 245 | return nil, MismatchedArgumentsError{ 246 | node: node, 247 | args: args, 248 | } 249 | } 250 | 251 | func streamSinkForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 252 | if len(args) < 2 { 253 | return nil, IncorrectNumberOfArgsError{ 254 | node: node, 255 | required: 2, 256 | given: len(args), 257 | } 258 | } 259 | 260 | first, second := args[0], args[1] 261 | var third Value 262 | if len(args) >= 3 { 263 | third = args[2] 264 | } else { 265 | third = Noop 266 | } 267 | 268 | var thirdForm Value 269 | firstStream, fok := first.(StreamValue) 270 | thirdForm, tok := third.(FormValue) 271 | if !tok { 272 | thirdForm, tok = third.(NativeFormValue) 273 | } 274 | 275 | if fok && tok { 276 | if !firstStream.isSink() { 277 | return nil, InvalidStreamCallbackError{ 278 | reason: "Cannot try to sink to a non-sink stream", 279 | } 280 | } 281 | 282 | vm := fr.Vm 283 | vm.waiter.Add(1) 284 | go func() { 285 | defer vm.waiter.Done() 286 | 287 | success := trueValue 288 | 289 | err := firstStream.callbacks.sink(second, node) 290 | if err != nil { 291 | if _, ok := err.(RuntimeError); ok { 292 | success = falseValue 293 | } else { 294 | fmt.Println(FormatError(err)) 295 | } 296 | return 297 | } 298 | 299 | vm.Lock() 300 | defer vm.Unlock() 301 | 302 | _, err = unlazyEvalFormWithArgs(fr, thirdForm, []Value{success}, node) 303 | if err != nil { 304 | fmt.Println(FormatError(err)) 305 | } 306 | }() 307 | 308 | return zeroValue, nil 309 | } 310 | 311 | return nil, MismatchedArgumentsError{ 312 | node: node, 313 | args: args, 314 | } 315 | } 316 | 317 | func streamCloseForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 318 | if len(args) < 1 { 319 | return nil, IncorrectNumberOfArgsError{ 320 | node: node, 321 | required: 1, 322 | given: len(args), 323 | } 324 | } 325 | 326 | first := args[0] 327 | 328 | if firstStream, ok := first.(StreamValue); ok { 329 | if !firstStream.isClose() { 330 | return nil, InvalidStreamCallbackError{ 331 | reason: "Cannot try to close to a non-close stream", 332 | } 333 | } 334 | 335 | err := firstStream.callbacks.closer() 336 | if err != nil { 337 | return nil, err 338 | } 339 | return firstStream, nil 340 | } 341 | 342 | return nil, MismatchedArgumentsError{ 343 | node: node, 344 | args: args, 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /pkg/xin/os.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net" 8 | "os" 9 | "time" 10 | ) 11 | 12 | const readBufferSize = 4096 13 | 14 | func osWaitForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 15 | if len(args) < 2 { 16 | return nil, IncorrectNumberOfArgsError{ 17 | node: node, 18 | required: 2, 19 | given: len(args), 20 | } 21 | } 22 | 23 | first, second := args[0], args[1] 24 | 25 | var duration float64 26 | if firstInt, fok := first.(IntValue); fok { 27 | duration = float64(int64(firstInt)) 28 | } else if firstFrac, fok := first.(FracValue); fok { 29 | duration = float64(firstFrac) 30 | } else { 31 | return nil, MismatchedArgumentsError{ 32 | node: node, 33 | args: args, 34 | } 35 | } 36 | 37 | vm := fr.Vm 38 | vm.waiter.Add(1) 39 | go func() { 40 | defer vm.waiter.Done() 41 | 42 | time.Sleep(time.Duration( 43 | int64(duration * float64(time.Second)), 44 | )) 45 | 46 | vm.Lock() 47 | defer vm.Unlock() 48 | 49 | _, err := unlazyEvalFormValue(fr, &astNode{ 50 | // dummy function invocation astNode 51 | // to help generate proper error messages 52 | isForm: true, 53 | leaves: []*astNode{ 54 | &astNode{ 55 | isForm: false, 56 | }, 57 | }, 58 | }, second) 59 | if err != nil { 60 | fmt.Println("Eval error in os::wait:", FormatError(err)) 61 | return 62 | } 63 | }() 64 | 65 | return trueValue, nil 66 | } 67 | 68 | func newRWStream(rw io.ReadWriteCloser) StreamValue { 69 | rwStream := NewStream() 70 | reader := bufio.NewReader(rw) 71 | closed := false 72 | 73 | rwStream.callbacks.source = func() (Value, InterpreterError) { 74 | if closed { 75 | return zeroValue, nil 76 | } 77 | 78 | buffer := make([]byte, readBufferSize) 79 | readBytes, err := reader.Read(buffer) 80 | if err != nil { 81 | return zeroValue, nil 82 | } 83 | 84 | return StringValue(buffer[:readBytes]), nil 85 | } 86 | 87 | rwStream.callbacks.sink = func(v Value, node *astNode) InterpreterError { 88 | if closed { 89 | return nil 90 | } 91 | 92 | if strVal, ok := v.(StringValue); ok { 93 | _, err := rw.Write(strVal) 94 | if err != nil { 95 | return RuntimeError{ 96 | reason: err.Error(), 97 | position: node.position, 98 | } 99 | } 100 | return nil 101 | } 102 | 103 | return MismatchedArgumentsError{ 104 | node: node, 105 | args: []Value{v}, 106 | } 107 | } 108 | 109 | rwStream.callbacks.closer = func() InterpreterError { 110 | if !closed { 111 | closed = true 112 | rw.Close() 113 | } 114 | return nil 115 | } 116 | 117 | return rwStream 118 | } 119 | 120 | func osStatForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 121 | if len(args) < 1 { 122 | return nil, IncorrectNumberOfArgsError{ 123 | node: node, 124 | required: 1, 125 | given: len(args), 126 | } 127 | } 128 | 129 | first := args[0] 130 | 131 | if firstStr, ok := first.(StringValue); ok { 132 | fileStat, err := os.Stat(string(firstStr)) 133 | if err != nil { 134 | return zeroValue, nil 135 | } 136 | 137 | statMap := NewMapValue() 138 | 139 | if fileStat.IsDir() { 140 | statMap.set(StringValue("dir"), trueValue) 141 | } else { 142 | statMap.set(StringValue("dir"), falseValue) 143 | } 144 | statMap.set(StringValue("name"), StringValue(fileStat.Name())) 145 | statMap.set(StringValue("size"), IntValue(fileStat.Size())) 146 | statMap.set(StringValue("mod"), IntValue(fileStat.ModTime().Unix())) 147 | 148 | return statMap, nil 149 | } 150 | 151 | return nil, MismatchedArgumentsError{ 152 | node: node, 153 | args: args, 154 | } 155 | } 156 | 157 | func osOpenForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 158 | if len(args) < 1 { 159 | return nil, IncorrectNumberOfArgsError{ 160 | node: node, 161 | required: 1, 162 | given: len(args), 163 | } 164 | } 165 | 166 | first := args[0] 167 | 168 | if firstStr, ok := first.(StringValue); ok { 169 | flag := os.O_CREATE | os.O_RDWR 170 | file, err := os.OpenFile(string(firstStr), flag, 0644) 171 | if err != nil { 172 | return zeroValue, nil 173 | } 174 | 175 | return newRWStream(file), nil 176 | } 177 | 178 | return nil, MismatchedArgumentsError{ 179 | node: node, 180 | args: args, 181 | } 182 | } 183 | 184 | func osDeleteForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 185 | if len(args) < 1 { 186 | return nil, IncorrectNumberOfArgsError{ 187 | node: node, 188 | required: 1, 189 | given: len(args), 190 | } 191 | } 192 | 193 | first := args[0] 194 | var second Value 195 | if len(args) >= 2 { 196 | second = args[1] 197 | } else { 198 | second = Noop 199 | } 200 | 201 | var secondForm Value 202 | firstStr, fok := first.(StringValue) 203 | secondForm, sok := second.(FormValue) 204 | if !sok { 205 | secondForm, sok = second.(NativeFormValue) 206 | } 207 | 208 | if fok && sok { 209 | vm := fr.Vm 210 | vm.waiter.Add(1) 211 | go func() { 212 | defer vm.waiter.Done() 213 | 214 | err := os.RemoveAll(string(firstStr)) 215 | rv := falseValue 216 | if err != nil { 217 | rv = trueValue 218 | } 219 | 220 | vm.Lock() 221 | defer vm.Unlock() 222 | 223 | _, intErr := unlazyEvalFormWithArgs(fr, secondForm, []Value{rv}, node) 224 | if intErr != nil { 225 | fmt.Println(FormatError(intErr)) 226 | } 227 | }() 228 | 229 | return zeroValue, nil 230 | } 231 | 232 | return nil, MismatchedArgumentsError{ 233 | node: node, 234 | args: args, 235 | } 236 | } 237 | 238 | func validateNetworkArgs(args []Value, node *astNode) (string, string, InterpreterError) { 239 | first, second := args[0], args[1] 240 | 241 | firstStr, fok := first.(StringValue) 242 | secondStr, sok := second.(StringValue) 243 | 244 | if !fok || !sok { 245 | return "", "", MismatchedArgumentsError{ 246 | node: node, 247 | args: args, 248 | } 249 | } 250 | 251 | network := string(firstStr) 252 | addr := string(secondStr) 253 | 254 | if (network) != "tcp" && (network) != "udp" { 255 | return "", "", NetworkError{ 256 | reason: `Network specified to os::dial must be "tcp" or "udp"`, 257 | position: node.position, 258 | } 259 | } 260 | 261 | return network, addr, nil 262 | } 263 | 264 | func osDialForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 265 | if len(args) < 2 { 266 | return nil, IncorrectNumberOfArgsError{ 267 | node: node, 268 | required: 2, 269 | given: len(args), 270 | } 271 | } 272 | 273 | network, addr, err := validateNetworkArgs(args, node) 274 | if err != nil { 275 | return nil, err 276 | } 277 | 278 | conn, netErr := net.Dial(network, addr) 279 | if netErr != nil { 280 | return nil, NetworkError{ 281 | reason: netErr.Error(), 282 | position: node.position, 283 | } 284 | } 285 | 286 | return newRWStream(conn), nil 287 | } 288 | 289 | func osListenForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 290 | if len(args) < 3 { 291 | return nil, IncorrectNumberOfArgsError{ 292 | node: node, 293 | required: 3, 294 | given: len(args), 295 | } 296 | } 297 | 298 | network, addr, err := validateNetworkArgs(args, node) 299 | if err != nil { 300 | return nil, err 301 | } 302 | 303 | handler := args[2] 304 | signal := make(chan bool, 1) 305 | 306 | listener, netErr := net.Listen(network, addr) 307 | if netErr != nil { 308 | return nil, NetworkError{ 309 | reason: netErr.Error(), 310 | position: node.position, 311 | } 312 | } 313 | 314 | vm := fr.Vm 315 | vm.waiter.Add(1) 316 | go func(l net.Listener) { 317 | defer vm.waiter.Done() 318 | 319 | for { 320 | conn, err := l.Accept() 321 | if err != nil { 322 | select { 323 | case <-signal: 324 | return 325 | default: 326 | fmt.Println(err.Error()) 327 | } 328 | } 329 | 330 | go func(c net.Conn) { 331 | vm.Lock() 332 | defer vm.Unlock() 333 | 334 | _, err := unlazyEvalFormWithArgs(fr, handler, []Value{newRWStream(c)}, node) 335 | if err != nil { 336 | fmt.Println(FormatError(err)) 337 | } 338 | }(conn) 339 | } 340 | }(listener) 341 | 342 | return NativeFormValue{ 343 | name: "os::listen::close", 344 | evaler: func(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 345 | signal <- true 346 | listener.Close() 347 | return trueValue, nil 348 | }, 349 | }, nil 350 | } 351 | 352 | func osLogForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 353 | if len(args) < 1 { 354 | return nil, IncorrectNumberOfArgsError{ 355 | node: node, 356 | required: 1, 357 | given: len(args), 358 | } 359 | } 360 | 361 | first := args[0] 362 | fmt.Println(first.String()) 363 | 364 | return first, nil 365 | } 366 | 367 | func osArgsForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 368 | argsVec := make([]Value, len(os.Args)) 369 | for i, a := range os.Args { 370 | argsVec[i] = StringValue(a) 371 | } 372 | return NewVecValue(argsVec), nil 373 | } 374 | 375 | func osTimeForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 376 | return FracValue( 377 | float64(time.Now().UnixNano()) / 1e9, 378 | ), nil 379 | } 380 | 381 | func debugDumpForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 382 | return StringValue(fr.String()), nil 383 | } 384 | -------------------------------------------------------------------------------- /pkg/xin/runtime.go: -------------------------------------------------------------------------------- 1 | package xin 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "math" 7 | "math/rand" 8 | "os" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type formEvaler func(*Frame, []Value, *astNode) (Value, InterpreterError) 14 | 15 | type NativeFormValue struct { 16 | name string 17 | evaler formEvaler 18 | } 19 | 20 | func (v NativeFormValue) String() string { 21 | return "( " + v.name + ")" 22 | } 23 | 24 | func (v NativeFormValue) Repr() string { 25 | return v.String() 26 | } 27 | 28 | func (v NativeFormValue) Equal(o Value) bool { 29 | if ov, ok := o.(NativeFormValue); ok { 30 | return v.name == ov.name 31 | } 32 | 33 | return false 34 | } 35 | 36 | func loadAllDefaultValues(vm *Vm) { 37 | fr := vm.Frame 38 | 39 | stdoutStream := NewStream() 40 | stdoutStream.callbacks.sink = func(v Value, node *astNode) InterpreterError { 41 | os.Stdout.Write([]byte(v.String())) 42 | return nil 43 | } 44 | fr.Put("os::stdout", stdoutStream) 45 | 46 | stdinStream := NewStream() 47 | stdinStream.callbacks.source = func() (Value, InterpreterError) { 48 | reader := bufio.NewReader(os.Stdin) 49 | input, err := reader.ReadString('\n') 50 | if err == io.EOF { 51 | return StringValue(""), nil 52 | } else if err != nil { 53 | return nil, RuntimeError{ 54 | reason: "Cannot read from stdin", 55 | } 56 | } 57 | 58 | return StringValue(input[:len(input)-1]), nil 59 | } 60 | fr.Put("os::stdin", stdinStream) 61 | } 62 | 63 | func loadAllNativeForms(vm *Vm) { 64 | // seed PRNG for math::rand 65 | rand.Seed(time.Now().UTC().UnixNano()) 66 | 67 | vm.evalers = map[string]formEvaler{ 68 | "+": addForm, 69 | "-": subtractForm, 70 | "*": multiplyForm, 71 | "/": divideForm, 72 | "%": modForm, 73 | "^": powForm, 74 | 75 | ">": greaterForm, 76 | "<": lessForm, 77 | "=": equalForm, 78 | 79 | "!": notForm, 80 | "&": andForm, 81 | "|": orForm, 82 | "xor": xorForm, 83 | 84 | "int": intForm, 85 | "frac": fracForm, 86 | "str": stringForm, 87 | "type": typeForm, 88 | 89 | "str::get": strGetForm, 90 | "str::set!": strSetForm, 91 | "str::add!": strAddForm, 92 | "str::size": strSizeForm, 93 | "str::slice": strSliceForm, 94 | "str::enc": strEncForm, 95 | "str::dec": strDecForm, 96 | 97 | "vec": vecForm, 98 | "vec::get": vecGetForm, 99 | "vec::set!": vecSetForm, 100 | "vec::add!": vecAddForm, 101 | "vec::size": vecSizeForm, 102 | "vec::slice": vecSliceForm, 103 | 104 | "map": mapForm, 105 | "map::get": mapGetForm, 106 | "map::set!": mapSetForm, 107 | "map::has?": mapHasForm, 108 | "map::del!": mapDelForm, 109 | "map::size": mapSizeForm, 110 | "map::keys": mapKeysForm, 111 | 112 | "stream": streamForm, 113 | "stream::set-sink!": streamSetSink, 114 | "stream::set-source!": streamSetSource, 115 | "stream::set-close!": streamSetClose, 116 | "->": streamSourceForm, 117 | "<-": streamSinkForm, 118 | "stream::close!": streamCloseForm, 119 | 120 | "math::sin": mathSinForm, 121 | "math::cos": mathCosForm, 122 | "math::tan": mathTanForm, 123 | "math::asin": mathAsinForm, 124 | "math::acos": mathAcosForm, 125 | "math::atan": mathAtanForm, 126 | "math::ln": mathLnForm, 127 | "math::rand": mathRandForm, 128 | "crypto::rand": cryptoRandForm, 129 | 130 | "os::wait": osWaitForm, 131 | "os::stat": osStatForm, 132 | "os::open": osOpenForm, 133 | "os::delete": osDeleteForm, 134 | "os::dial": osDialForm, 135 | "os::listen": osListenForm, 136 | "os::log": osLogForm, 137 | "os::args": osArgsForm, 138 | "os::time": osTimeForm, 139 | 140 | "debug::dump": debugDumpForm, 141 | } 142 | 143 | fr := vm.Frame 144 | for name, evaler := range vm.evalers { 145 | fr.Put(name, NativeFormValue{ 146 | name: name, 147 | evaler: evaler, 148 | }) 149 | } 150 | } 151 | 152 | var Noop = NativeFormValue{ 153 | name: "noop", 154 | evaler: noopForm, 155 | } 156 | 157 | func noopForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 158 | return zeroValue, nil 159 | } 160 | 161 | func addForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 162 | if len(args) < 2 { 163 | return nil, IncorrectNumberOfArgsError{ 164 | node: node, 165 | required: 2, 166 | given: len(args), 167 | } 168 | } 169 | 170 | first, second := args[0], args[1] 171 | 172 | if firstInt, fok := first.(IntValue); fok { 173 | if _, sok := second.(FracValue); sok { 174 | first = FracValue(float64(firstInt)) 175 | } 176 | } else if _, fok := first.(FracValue); fok { 177 | if secondInt, sok := second.(IntValue); sok { 178 | second = FracValue(float64(secondInt)) 179 | } 180 | } 181 | 182 | switch cleanFirst := first.(type) { 183 | case IntValue: 184 | if cleanSecond, ok := second.(IntValue); ok { 185 | return cleanFirst + cleanSecond, nil 186 | } 187 | case FracValue: 188 | if cleanSecond, ok := second.(FracValue); ok { 189 | return cleanFirst + cleanSecond, nil 190 | } 191 | case StringValue: 192 | if cleanSecond, ok := second.(StringValue); ok { 193 | // In this context, strings are immutable. i.e. concatenating 194 | // strings should produce a completely new string whose modifications 195 | // won't be observable by the original strings. 196 | base := make([]byte, 0, len(cleanFirst)+len(cleanSecond)) 197 | return StringValue(append(append(base, cleanFirst...), cleanSecond...)), nil 198 | } 199 | case VecValue: 200 | if cleanSecond, ok := second.(VecValue); ok { 201 | base := make([]Value, 0, len(cleanFirst.underlying.items)+len(cleanSecond.underlying.items)) 202 | return NewVecValue(append(append(base, cleanFirst.underlying.items...), cleanSecond.underlying.items...)), nil 203 | } 204 | } 205 | 206 | return nil, MismatchedArgumentsError{ 207 | node: node, 208 | args: args, 209 | } 210 | } 211 | 212 | func subtractForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 213 | if len(args) < 2 { 214 | return nil, IncorrectNumberOfArgsError{ 215 | node: node, 216 | required: 2, 217 | given: len(args), 218 | } 219 | } 220 | 221 | first, second := args[0], args[1] 222 | 223 | if firstInt, fok := first.(IntValue); fok { 224 | if _, sok := second.(FracValue); sok { 225 | first = FracValue(float64(firstInt)) 226 | } 227 | } else if _, fok := first.(FracValue); fok { 228 | if secondInt, sok := second.(IntValue); sok { 229 | second = FracValue(float64(secondInt)) 230 | } 231 | } 232 | 233 | switch cleanFirst := first.(type) { 234 | case IntValue: 235 | if cleanSecond, ok := second.(IntValue); ok { 236 | return cleanFirst - cleanSecond, nil 237 | } 238 | case FracValue: 239 | if cleanSecond, ok := second.(FracValue); ok { 240 | return cleanFirst - cleanSecond, nil 241 | } 242 | } 243 | 244 | return nil, MismatchedArgumentsError{ 245 | node: node, 246 | args: args, 247 | } 248 | } 249 | 250 | func multiplyForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 251 | if len(args) < 2 { 252 | return nil, IncorrectNumberOfArgsError{ 253 | node: node, 254 | required: 2, 255 | given: len(args), 256 | } 257 | } 258 | 259 | first, second := args[0], args[1] 260 | 261 | if firstInt, fok := first.(IntValue); fok { 262 | if _, sok := second.(FracValue); sok { 263 | first = FracValue(float64(firstInt)) 264 | } 265 | } else if _, fok := first.(FracValue); fok { 266 | if secondInt, sok := second.(IntValue); sok { 267 | second = FracValue(float64(secondInt)) 268 | } 269 | } 270 | 271 | switch cleanFirst := first.(type) { 272 | case IntValue: 273 | if cleanSecond, ok := second.(IntValue); ok { 274 | return cleanFirst * cleanSecond, nil 275 | } 276 | case FracValue: 277 | if cleanSecond, ok := second.(FracValue); ok { 278 | return cleanFirst * cleanSecond, nil 279 | } 280 | case StringValue: 281 | if cleanSecond, ok := second.(IntValue); ok { 282 | max := int(cleanSecond) 283 | result, iter := "", string(cleanFirst) 284 | for i := 0; i < max; i++ { 285 | result += iter 286 | } 287 | return StringValue(result), nil 288 | } 289 | case VecValue: 290 | if cleanSecond, ok := second.(IntValue); ok { 291 | max := int(cleanSecond) 292 | result := make([]Value, 0, max*len(cleanFirst.underlying.items)) 293 | copy(result, cleanFirst.underlying.items) 294 | for i := 0; i < max; i++ { 295 | result = append(result, cleanFirst.underlying.items...) 296 | } 297 | return NewVecValue(result), nil 298 | } 299 | } 300 | 301 | return nil, MismatchedArgumentsError{ 302 | node: node, 303 | args: args, 304 | } 305 | } 306 | 307 | func divideForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 308 | if len(args) < 2 { 309 | return nil, IncorrectNumberOfArgsError{ 310 | node: node, 311 | required: 2, 312 | given: len(args), 313 | } 314 | } 315 | 316 | first, second := args[0], args[1] 317 | 318 | if firstInt, fok := first.(IntValue); fok { 319 | if _, sok := second.(FracValue); sok { 320 | first = FracValue(float64(firstInt)) 321 | } 322 | } else if _, fok := first.(FracValue); fok { 323 | if secondInt, sok := second.(IntValue); sok { 324 | second = FracValue(float64(secondInt)) 325 | } 326 | } 327 | 328 | switch cleanFirst := first.(type) { 329 | case IntValue: 330 | if cleanSecond, ok := second.(IntValue); ok { 331 | if cleanSecond == zeroValue { 332 | return zeroValue, nil 333 | } 334 | 335 | return cleanFirst / cleanSecond, nil 336 | } 337 | case FracValue: 338 | if cleanSecond, ok := second.(FracValue); ok { 339 | if cleanSecond == FracValue(0) { 340 | return zeroValue, nil 341 | } 342 | 343 | return cleanFirst / cleanSecond, nil 344 | } 345 | } 346 | 347 | return nil, MismatchedArgumentsError{ 348 | node: node, 349 | args: args, 350 | } 351 | } 352 | 353 | func modForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 354 | if len(args) < 2 { 355 | return nil, IncorrectNumberOfArgsError{ 356 | node: node, 357 | required: 2, 358 | given: len(args), 359 | } 360 | } 361 | 362 | first, second := args[0], args[1] 363 | 364 | if firstInt, fok := first.(IntValue); fok { 365 | if _, sok := second.(FracValue); sok { 366 | first = FracValue(float64(firstInt)) 367 | } 368 | } else if _, fok := first.(FracValue); fok { 369 | if secondInt, sok := second.(IntValue); sok { 370 | second = FracValue(float64(secondInt)) 371 | } 372 | } 373 | 374 | switch cleanFirst := first.(type) { 375 | case IntValue: 376 | if cleanSecond, ok := second.(IntValue); ok { 377 | if cleanSecond == zeroValue { 378 | return zeroValue, nil 379 | } 380 | 381 | return cleanFirst % cleanSecond, nil 382 | } 383 | case FracValue: 384 | if cleanSecond, ok := second.(FracValue); ok { 385 | if cleanSecond == FracValue(0) { 386 | return zeroValue, nil 387 | } 388 | 389 | modulus := math.Mod( 390 | float64(cleanFirst), 391 | float64(cleanSecond), 392 | ) 393 | return FracValue(modulus), nil 394 | } 395 | } 396 | 397 | return nil, MismatchedArgumentsError{ 398 | node: node, 399 | args: args, 400 | } 401 | } 402 | 403 | func powForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 404 | if len(args) < 2 { 405 | return nil, IncorrectNumberOfArgsError{ 406 | node: node, 407 | required: 2, 408 | given: len(args), 409 | } 410 | } 411 | 412 | first, second := args[0], args[1] 413 | 414 | if firstInt, fok := first.(IntValue); fok { 415 | if _, sok := second.(FracValue); sok { 416 | first = FracValue(float64(firstInt)) 417 | } 418 | } else if _, fok := first.(FracValue); fok { 419 | if secondInt, sok := second.(IntValue); sok { 420 | second = FracValue(float64(secondInt)) 421 | } 422 | } 423 | 424 | switch cleanFirst := first.(type) { 425 | case IntValue: 426 | if cleanSecond, ok := second.(IntValue); ok { 427 | return IntValue(math.Pow(float64(cleanFirst), float64(cleanSecond))), nil 428 | } 429 | case FracValue: 430 | if cleanSecond, ok := second.(FracValue); ok { 431 | power := math.Pow( 432 | float64(cleanFirst), 433 | float64(cleanSecond), 434 | ) 435 | return FracValue(power), nil 436 | } 437 | } 438 | 439 | return nil, MismatchedArgumentsError{ 440 | node: node, 441 | args: args, 442 | } 443 | } 444 | 445 | func notForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 446 | if len(args) < 1 { 447 | return nil, IncorrectNumberOfArgsError{ 448 | node: node, 449 | required: 1, 450 | given: len(args), 451 | } 452 | } 453 | 454 | first := args[0] 455 | 456 | if firstInt, ok := first.(IntValue); ok { 457 | if firstInt.Equal(zeroValue) { 458 | return trueValue, nil 459 | } else { 460 | return falseValue, nil 461 | } 462 | } 463 | 464 | return nil, MismatchedArgumentsError{ 465 | node: node, 466 | args: args, 467 | } 468 | } 469 | 470 | func andForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 471 | if len(args) < 2 { 472 | return nil, IncorrectNumberOfArgsError{ 473 | node: node, 474 | required: 2, 475 | given: len(args), 476 | } 477 | } 478 | 479 | first, second := args[0], args[1] 480 | 481 | switch cleanFirst := first.(type) { 482 | case IntValue: 483 | if cleanSecond, ok := second.(IntValue); ok { 484 | return cleanFirst & cleanSecond, nil 485 | } 486 | } 487 | 488 | return nil, MismatchedArgumentsError{ 489 | node: node, 490 | args: args, 491 | } 492 | } 493 | 494 | func orForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 495 | if len(args) < 2 { 496 | return nil, IncorrectNumberOfArgsError{ 497 | node: node, 498 | required: 2, 499 | given: len(args), 500 | } 501 | } 502 | 503 | first, second := args[0], args[1] 504 | 505 | switch cleanFirst := first.(type) { 506 | case IntValue: 507 | if cleanSecond, ok := second.(IntValue); ok { 508 | return cleanFirst | cleanSecond, nil 509 | } 510 | } 511 | 512 | return nil, MismatchedArgumentsError{ 513 | node: node, 514 | args: args, 515 | } 516 | } 517 | 518 | func xorForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 519 | if len(args) < 2 { 520 | return nil, IncorrectNumberOfArgsError{ 521 | node: node, 522 | required: 2, 523 | given: len(args), 524 | } 525 | } 526 | 527 | first, second := args[0], args[1] 528 | 529 | switch cleanFirst := first.(type) { 530 | case IntValue: 531 | if cleanSecond, ok := second.(IntValue); ok { 532 | return cleanFirst ^ cleanSecond, nil 533 | } 534 | } 535 | 536 | return nil, MismatchedArgumentsError{ 537 | node: node, 538 | args: args, 539 | } 540 | } 541 | 542 | func greaterForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 543 | if len(args) < 2 { 544 | return nil, IncorrectNumberOfArgsError{ 545 | node: node, 546 | required: 2, 547 | given: len(args), 548 | } 549 | } 550 | 551 | first, second := args[0], args[1] 552 | 553 | if firstInt, fok := first.(IntValue); fok { 554 | if _, sok := second.(FracValue); sok { 555 | first = FracValue(float64(firstInt)) 556 | } 557 | } else if _, fok := first.(FracValue); fok { 558 | if secondInt, sok := second.(IntValue); sok { 559 | second = FracValue(float64(secondInt)) 560 | } 561 | } 562 | 563 | switch cleanFirst := first.(type) { 564 | case IntValue: 565 | if cleanSecond, ok := second.(IntValue); ok { 566 | if cleanFirst > cleanSecond { 567 | return trueValue, nil 568 | } else { 569 | return falseValue, nil 570 | } 571 | } 572 | case FracValue: 573 | if cleanSecond, ok := second.(FracValue); ok { 574 | if cleanFirst > cleanSecond { 575 | return trueValue, nil 576 | } else { 577 | return falseValue, nil 578 | } 579 | } 580 | case StringValue: 581 | if cleanSecond, ok := second.(StringValue); ok { 582 | cmp := strings.Compare(string(cleanFirst), string(cleanSecond)) 583 | if cmp == 1 { 584 | return trueValue, nil 585 | } else { 586 | return falseValue, nil 587 | } 588 | } 589 | } 590 | 591 | return nil, MismatchedArgumentsError{ 592 | node: node, 593 | args: args, 594 | } 595 | } 596 | func lessForm(fr *Frame, args []Value, node *astNode) (Value, InterpreterError) { 597 | if len(args) < 2 { 598 | return nil, IncorrectNumberOfArgsError{ 599 | node: node, 600 | required: 2, 601 | given: len(args), 602 | } 603 | } 604 | 605 | first, second := args[0], args[1] 606 | 607 | if firstInt, fok := first.(IntValue); fok { 608 | if _, sok := second.(FracValue); sok { 609 | first = FracValue(float64(firstInt)) 610 | } 611 | } else if _, fok := first.(FracValue); fok { 612 | if secondInt, sok := second.(IntValue); sok { 613 | second = FracValue(float64(secondInt)) 614 | } 615 | } 616 | 617 | switch cleanFirst := first.(type) { 618 | case IntValue: 619 | if cleanSecond, ok := second.(IntValue); ok { 620 | if cleanFirst < cleanSecond { 621 | return trueValue, nil 622 | } else { 623 | return falseValue, nil 624 | } 625 | } 626 | case FracValue: 627 | if cleanSecond, ok := second.(FracValue); ok { 628 | if cleanFirst < cleanSecond { 629 | return trueValue, nil 630 | } else { 631 | return falseValue, nil 632 | } 633 | } 634 | case StringValue: 635 | if cleanSecond, ok := second.(StringValue); ok { 636 | cmp := strings.Compare(string(cleanFirst), string(cleanSecond)) 637 | if cmp == -1 { 638 | return trueValue, nil 639 | } else { 640 | return falseValue, nil 641 | } 642 | } 643 | } 644 | 645 | return nil, MismatchedArgumentsError{ 646 | node: node, 647 | args: args, 648 | } 649 | } 650 | -------------------------------------------------------------------------------- /statik/statik.go: -------------------------------------------------------------------------------- 1 | // Code generated by statik. DO NOT EDIT. 2 | 3 | package statik 4 | 5 | import ( 6 | "github.com/rakyll/statik/fs" 7 | ) 8 | 9 | 10 | func init() { 11 | data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x0de[P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00 \x00map.xinUT\x05\x00\x01\xbb\xb8W^\xac\x8f\xc1j\xc4 \x14E\xf7\xf9\x8a\xbb*\xba\x08tm)\xf9\x16k^K\xd0\x17\xacq\x04\xe7\xeb\x07\xa3\x19B2\x03\xb3\x98\xb7\xf3r.\xe7\xfa\x05\xd6\x1eK\xd4\xf3\xa8\xc3\x087\xfd\x04\x1dr\xd7 \x05A\xecc\x1e\xc0\xb2\x03 \xbe!X{\xa5\x96\xe9J`\x89O)\x1b\xf6?\x80\xe1*\xf5q\x06w/'W\xaa\x80\x89\x8cR\x94(\xe4\x96lY\xd9S+\x96\xf2\xd2\xec\x87+Z\xc3\xbeO\xda\x81{K\xf9\x11\xb4\x1f\xfdG\x11\xdc\xd05\xeaK\xb2\x95\xeb\xd5\xdf$\xed.\xb4i_ZTZ\xbf\xb0\xe7 {\xb5\x95\xc7V\"s\x97\xd2\x1c\xc3\xf4.k\"\x03{\x94?\xb1\xdf\x02\x00\x00\xff\xffPK\x07\x08.\xc1\xc1\xd4\xb2\x00\x00\x00\xff\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x87\xb8bP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00 \x00math.xinUT\x05\x00\x01\xee\x90]^\x84T\xcdr\xdb<\x0c\xbc\xfb)\xf6;|S1\x89\xeb$3\xbd\xd8\xf99\xb4\x9d>\x07$Q2;\xfcQH*i\xfa\xf4\x1d\x90\xfa\xb1\x14\xbb\xe5\xc13\x02\x17\x8b\xc5\x82\xc6\x01\x86\xe2Q\x1a\x8a\xaa\n\xd0\xaa\xf4\xe4\xdf7\x9bb\x8f\x82\xca\x00+6\x00\n\xd5\xa0x\x80\xc5\xad@ae\x0b+`\x85H\xb0N%\xc4\x15\xeeQ0\xd5~OAY\xdc \xbe?\xe0{_iU\x7f\n\xf8\xf1\xf5\x1bH\xb7\xce\xabx4\x89\xbf\xadj\x10\xcaT\xe1\x80\xceK/_\xf6 <\xa0L\x94\xb5\x03\xc3B_N\xb0\xe1$=\x8f \xdc\x9eF\x91\xf3f\x14g\x16\xff\xa3\x04 \x10\xeb\xc9a\xae\x91\x9a\xa3\x93P\x99C\xe5\x14\xe2\x12O\x8b\xc2\x89\x8f\xc9\x16\x01F\x0c\xbdjI!\xa2r\xc68\x0b\xd3\xeb\xa8:-\xd1\x07e[\xb4U\x9d\x9a\xd6\x95\x99H\x8b+V\xb2\xe3\xda\xa3\x17bp\xb5\xe8\xbc2\xf2y\xed\xff\xfdP\xbb!\x1d\xe4 4\xdbd\xe8\x17\ne+\xfe\x89(\xc2\x8b\x8f<\"1\x8b\x1d\xbdT\x0b\xcf\x06+\x15\x13,\xcdD\xf4\xbd\\F\x12\xb8\xa1*:\xff\x0c\xbbb\xc2R\xd7IV\x9a\x03kS,H\xe0~4,5\x89L\xa8~KX\x8a\xbd'\x0d\xdb\x9bR\xfa\xd9\x87m\x86L\xcfql\xa5k\x02\x0c\xbafn2wcpw\"\xadk\xc2\xfc\xb1ha\x91;K}\x95\xd5~Ou\xfd_*\xb0\x82d\xdc\xeecn:\xe7\xf8\x16\xf5\x87c>R\xb2A]#\xa6\x99\xb1\x0c\x91f\x9e\xbc\xba\xe8\xd2\x01\x8d\xf3\xd0\xe4[ {\x03C\xef(%\x8c\xf3\x12\xb2iT\xa5\xa4\x8d\x88\x0e\x8d\xf2!\x8e\xa6og:\xb25:\xef\xea\xbe\x92\xfcvKe)*g\x03\\\xb3\x18PH\xf3\xb80 \xed\xde\xa4G\xdfu\xd2\xc3/\xc7\xf1\xf4\xc8\x0f\x9d\xc3C\xd2\xc9\xdd\xf9+\xa0\xb8>\x9dB&\xf7k\xb73\xc2\xcbW\xe9\x83\xcc\xb5\xc5\x9a%\xa7\x9eE\xae\xb5\xcc\xef\xda\xff\xf5M\\P\x83SL6\xa2\xd8%\xb23H\x9e\xf5Z\xecd\xe3\x07xb\xbbH\xb2|/\xf9\xf7n\\#\xda\xb5()\xc8q^\xbbqIk\xcbK|\xfe`\xd0\xf0\xaf$\xad(\xa4GU4\xda9\xcf\xc0\xed\x16?\xfb\x10\xf3v\xb1\xe2fs\xc0\xdbQUG^\x12\xb6\xa2(\x03\xe2Q\xa2U\xaf\xd2\xa2\x96\x952\xa4Y@&P6\x0ez\xbc\xebm=m5\xdeT\xd7\xb0\xf8\xfce\xda{\x9el\xbd\xe5\x8bq\x1fe\xd4\xd5(\x95\xefE\xba\x1c3\xfa\x7f\xa5\xf4\x8b\x9c?\x01\x00\x00\xff\xffPK\x07\x08\xa7\x82\x8aT\x84\x02\x00\x00\xf1\x06\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x939vP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00os.xinUT\x05\x00\x01\xe7\x0fw^\xb2V\xc8/V\xc8\xcc+I-JKLNU(/J,(H-*\xe6\x02\x04\x00\x00\xff\xffPK\x07\x08\xb4v\x1d@\x1e\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00e\xbagP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00 \x00src.xinUT\x05\x00\x01\xee+d^\x94S\xcbj\xc30\x10\xbc\xfb+\xa6'i)\xa5w\xf5\xd0\x1f\xe9Eu\xd6EM\x94\x18I\x18\xdc\x90\x7f/z\xd8V\xdc\xe0\xa4\x0b!\xc8;;;\x9a]\xbd\xc1\xb33\xfa`~t0\xa7\xe3\xeb\x8e\xaf\xce8\x98O\xa7\xdd\xd84RAzv/>8\x0c\xd4\x00\x90\xcf\x10\x1fB\xc4\x7f\xe9\x83S\x8a}\xab{.\xd9*\"\x8a\x88\x16\x8e\x81\xdb\x89\xc3t\x90\x03\xb7J\xb1\xed\xc3\xf8>\x17\x8b\xf8\x95D>d\xfa\xce\x86\x898eq\xbeL\x00$\x92\xa5m.\xf8>\x99ca\xb7\xba\xc70_\x95 \x02QR-*A\x16QV\xf77DY\xddo\x88\xda\x9d \x15l*&\xfa\xaf\xbe\xdcr\xcf\xa3\x87][\xb8\x8e(\xba\xc3\xfe\x1e\xac\x96\x99\xd5+\xe59<\xc1\xe2|\xb9\xd2\xf7H$\xd3\xe5l\xe2C\xedWQUg5_\x1c0`Oy\x18\xb7J\xfeN\xca\x07\xc7\xda\x96\xa9\x08\x99\x8f$\"\"O`n\xe2|\x99E\x03T\xb7\xaf\xf3\xe6\x18\xe0\x83\xdb@tN\xb7w \xf1Q\x14e\x1b\xa8h_Y\xff\x0dT\\\x85\xb2\x8f\xdb\x1d\xa3\x07\x8b\x1dD\x93?\xc5\xdf\xb2\xcb\x8b\xcdW\xae\x841\xbdT\x8a\xbf\xe67\x00\x00\xff\xffPK\x07\x08\xc1]~\x023\x01\x00\x00\x05\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xdd1\x159\xe7\x89k@o\x91BK\x95K\x0d\xdbG}9\x05\xaf\x0b\xdd\x0e\xf6\xff\xdf\xd6\xf8\x8a\x13m\x9a\xd7\xe6\x0c\x0c\xaf\xe8\x8e?\xf1\xa9\x0b\xc0\xb4\x07&`\x94\x97s\xe3\xbbb\xe0}\x81A\x9d\xb0\xf6/F\xe1r\xf7\x81\x16\xb1\xf5\xc34\xaa,\xb0\x851\xaeb\xf8\xc3Y\xa6\x1f\x90~\x11Zs\x0d\xfb\x1a\xaf\xf3\xae\xd7H\x18\xacqy\x91`\xbd\xc6\x92\x93\xd7\xf8HI\xfb**@\xf9\xd9Q|\xa8\x14\xee\x80zV \xbe>B\xe2\x9b\xe68\xc2\x0d\xa4R\xaf\xe7\xcc\xdb\xb0r\x12b\x94\xf1\x98\xb0p\xdbH\xa5b#\xd2\xbf\x82-9\x95jJ\xa6\x10\xf0?Q\xec_\xe2\x13\xf4\x19\x7f\xfe\x89\xd4;\x1c\xfa\x1c L\xd2\x84\xe7K\xe6phv\x94\x9a\x0dui\x8c(us]Q}\xae\xb6?\xdd\xabWt\x14\x0c\xc6\xba\x88\xad\xd9\xca\xf9\xe6\x92\xe7\x0f\xf4\x15\x00\x00\xff\xffPK\x07\x08\x1c\x9ea|F\x01\x00\x00\xba\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xd4\x8cvP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00 \x00std.xinUT\x05\x00\x01\xa0\xa2w^|U\xdb\x8e\xdb6\x10}\xf7W\x1c\x07H)5uc\xe5\xd6V{\xf1\x9f\x14\xa0\xa4\x91LD\"\x1d\x92v\xbdA?\xbe\x18\x92\xb2\xe8\x8d\xd2}X\xd0g\xce\x1c\xce\x8d\xa3\x078/u'm\x87Q5V\xda\x97\xcd\xe6\x01\x8d1#I\x0d9*\xe9\xc8m\x8a\x1a\xde\x9e U\xc9\xc7^\x8e\x8e\xb0/\x99\xf9\x8f\xd4\x1e\xde\xa0!\xc8f$>:\xf9\x827'\xe9\xdc\x9b\xcd\x03\x8c\xc6\xd9\xd1H\xceA\xf5;b\xc7\x96%\x7fCo,\xe8*\xa7\xd3H,\xca\x0e\xacY\xd4\x90\xba\xc3/\xe1d,\xfe\x0d\x07m<\xb6\xe1Tpl\xb8\x96\x1b\x00\x85\xeaq\x85\xe0\xd0\x04D\x08K\x94!,\xffr\"H\xe7\xc8zetH\xa0P\xda\x1ff\xc7'\x14\x81r-\xa1\xb4/\xa3roe\xbb\xc6`\xfd\xd8\xc7~8\xf2\x90\x8d\xf3V\x86\x87\ns!\xcb{\"e\xedc \xd9\xe2 \x9f\n\xe8p\xb9\x19\xeb\xda\x91\x0f\xd0\x0b\x88/\x02\xe2\x0f\x01\xf1\xa7\x80\xf8K@H\x01\xd1\x08\x88V@t\x02\",}\x91\xb2?\xd2\xb5\xae)t6\x9bp\x8d\xeaK\xaapl\xec@\xfe>\x8c\xf0]\x08]\x8e\xacw\x99T\xf1>\n,=\xfa_\x91\xe2m\xa2\x97\xf3pz\xb3\xeb\xa8\xdd\xe9\xf3\xd4\x90\xddq\xef\xe6\x99\xa81\xcd\xbd\x8b\xc2K\xab\xa6\x90i\xb5_\xb54\x02U\xb5ji\x05\xaa\x0f\xab\x96N\xa0\xfa\xb8j!\x81\xea\xd3\xaa\xa5\x17\xa8>\xcf\xcb\xe5.\x0d\xb8\xa5\xc0a\xbd\x87\x12\x1c\x12\x8cX\nw'\x9a\xeau_\n\xb82o\x1d\xaf\xceL\xf9)\x89\x87\xb9s\xfcX\x93\xe0Z,\xb1m\xbffJ\xc99\xbcb\x87}\xdc\xccw\x8aw/\x0f\xcb\x98\xfcxGt\xe3\x1c\xdc\xbaN\xfa\xa8uV\x86\x15\xeb\xcc\xd9\xf2\xb5\xe1k\x1d\x12\xdc=?\xa3W#\xa1\xcd\xd6o\xfc\xaa0\x9cn\xde\xa7T\xe6\xd5\x92\xafT\xb0H\xd4\x08\xdf\x144\xe7\xfe.\xfe\xe5/\x13\xff9 (\xda\xd77\xbc\xb2\x87%\x152\xbd-e\xd6+\xb3\xca \xc1\xe7\xff\x02\x00\x00\xff\xffPK\x07\x08z\x95\xd7\xaf\xd1\x03\x00\x00C\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaf\x8evP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00 \x00str.xinUT\x05\x00\x01\x1a\xa6w^\xbcX\xd9v\xdb6\x10}\xd7W\xdc\xbc\x94d\x1d\x9dXN\xd34R\xec|B\x7f\xc0/\x10\x08JHH\x90\x07\x00\xb5\xc4\xf1\xbf\xf7\x0c\xc0\x05\\\xa4,n\x8b\x17S\x98\xed\xce`\x16\xc0\x1b\x18\xaba,S)\xd3)r\xb9\xd5L\x9f\x17\x8bx\x8dx\x9b3\xf5\xe5\x13L\xb2\x00\x10\xdf\xc3 \x8a\x92\xc4\xd3\xb48\x08mDK\x94\xd9\x88\x1d\x88\"\xff7\xbe\xe9\xb9cc\xf5zmr\xc9\x05\x0cV\xedo\xf9\x95\x14%\x8d \xc9\xb8\xfd\x9d\xb00\xb8M\x92\xdehZ;\xc9\x0c\x8cso9-\x1b3k\x14\xec4\xd2\xd8\x91bSo!;)\xbfM\xa0?B\x92\\\xbf\x0b\xcf\x1b\xdf@b\x15n{Z6\xc0&\x13R \x99\x84\x8cd\xa4\xb5L\xaan\xfdN\xb2Xl\xb0\x15\x9c\xd5F\xe0(\xa2<\x07}\x15\xac\x023`\xc8j\xc5\xad,\x15\x14+\x04\xf6B\x8b\xd7\x8b\x0dX.\x99\x81\xdd\x0blkis\xa9\x9c@\xcb\xbbp^W\xeb\xb5\x12G\xfaH\\\x98\x88\xc3 \xf3\xf1ic\x16zBL;\xf0\x06\xfb\xc8G\xef\x1fK\xd3W\x8e\x1egp\x0e\x0e\xd8\xfa<\xc8dn\x85~\x899:\x05oc\x1c\xec1\x14>\xe5\x08b\xedW\x8f\xecs)\x15\x0e\xa8X\xda\xa7\xe8A\xf0\xf5Z\x14\x95=\x7f\xc2a\x9c\xa7\x8e\xd8\xe0\xf7?,\x93y\xc77X\xceu\x98aB\x0d9n|\xf8n\x08\xc10\xbb\x03&gf/X\x8aC\x9f\xe6R\xa5\xe2\x04\x03So=t\x9f\xfba\x88\x9c7\x0f\x90\xb3\xf9Nk\xb9\xea\xbf\x1d\xf3\xfd\xa8\xf8\xa4O\xf1P\xbe\xde&I\xd2Ym\x97\x1c\x1c%\xc1hj#i}\xbam\x81\xef\x99\xf9\x14\xe2~\x18\xfa\x82\xe5\xaa\xe5\xac\xb4\xc8\xe4i\xc0|\x0fWw!\xc8\xf9\xc8\xe2\xf6\xc2\xfe\xc4\x97\xc6\x98\xa9\xb3_7\x16/\x871\x9eX\xf9\x010IS\xfd\xb5\x95\xb9\xb4g\xd8\x12|/\xf8\x170\xc3\xa5\x84fj'\x8c?z\xb3\x94j\xe96\xa0P\xb8r\xf7\xcd)\xfe\x0d\xf1\xc3\xbd\xdfL\x10\x7ft\x9f\xec\xd49\x99\xca\x9d\xb4]\xb3\x0e\xf5x$BqB\xff\xc7_x\xf7\xbe\x95\xa9\xabJ\xe8\xef\xcb\xfc\xf9\x0e\x1f\xba3\xce\xcb\xe3\x8f\xc8|x\x8f\xd5\xdd]'$\xac\x0d\xa4\xbe\x05\xa6\x03\x8d=,\xce\xfa\xd1\xe2\xbaY\x17d_x\xc3\xa2s\xf9\xddh\x19\x97\xa3\x07\x95R\xd6.\x03\x84<\xc1\xdb\xbb\xf1\xd9\xf1>e\xd2\xf2\xa8~\x01D\xe3\xd5\x15\x107?\x01\xa2b\xe9\xd2X\xa6i\xd2p\xea\xfa2\xcf\xfb^\xf6q\x94\x97\x9c\xda\xbf7\x97\x96\x041\x95YF^\x93\xe8\x85A\xdb\xc1\xf15\x10\xff\xeelP\xd5r\xc4o\x1a\x0d\xbd\xa8\x03@=\xe2\xd6\x91B\xe4]\xf31!z\xa1\xd2\xff\x12{X\xb3/\xf1c\x80}\x83\xb2\xb2\xb2\x90_E\n'\xa2E\x953.p\x94vO\xd5'\x0b\x96\x83\x97\xd5Y\xaa\x1d\xde\xe0\xefX%0\x82i\xbe\x97j\xd7\xdcS\xbc\x84A\x99\xa7P\xe2\xd8\xb7qj;\xd4\xfc@Mi\xd8\xce\xef\xc9q\x99\x9e\xba\xa6Yg\x19)H\\\xe3\xec]\xa5\x912\xd1\xd0_[\x886\xedI\xa3\x93v\xbao\xc9\xda|\xff\"\xccc\xcaX~V\xb0a\xbd\xf1\x8e\xf4!w~\\\x93\x08\xbb\xaa;\x92\x86=\x8a\xfa\x94\xb2Z\x16]ETL\xdb>\xa3\x9a\xcbg\xb7\x8969\xba\xa8\xeb0V$\xd2\xce \x1dJ\xb5hF\xd3A_\x81>\x00?Qu\x8dY\x8fn\x05\x9a\xc6o2\xf0\xd6W\xd0\x8b}mG\xe0\xcb}\xbd4|\xfd\x1aNK\x9dLB\xf3]\x8f\x07\xdev!\x989\xfaF\x1f\x15\xacT\xee\x86l*\xc1%\xd5&5\xee\x92\xbc\xaeri\xfd\xb3e\xb1\xc1Q@*c\xe9\xb2\xe5 \xcc\x82\xde%gl\xcfV\x80\xef\x99f\xdc\n\xed\xef\x0c\xc4\xb1\x94\xca\x96K\"\x98v\x10\\\xba\xdff\xfe~;\xa9\x19w\xc1\x0bn\xb0\xc3\xd0\x1f\x84{\x18t\x06a\x90\x8a\\\x16\x93\xb3\xeewq\x11[p\xfe\xa6\xde\xbag]\xbd\xcd\xa5\xb1\xe3d\x986\x1a\xc7\xedm\x8c\x9a\xcd\xc0\x85F_#p=\x8b<\xcf\xf5d\x99t\x89\x06\xc2\x8f\xd6O\x83c\xca?\xc5|M\xe5O\xc2\x0e\xd6\xf5b\x08\x165\xda \xf7M{\xf4.}u\x9d\xcaB(\xcb\xf4\x99&\x0e\x8d\x95\xac\xd4\x05\xb3\x96>\xdd(\xd9\x97y*\xb4\xf13\xe8sm,\xa2\xa7\xe7h0\xab\x98JA\x0f\x96\xa5\x16\xbc\xd6F\x1e\xe6'\x16=*-\xfb\"\x0cX\xe6^n\xc1\x88[l\x16\x1b\x88S\x828+,\"_nO\xcfxz~\xfd\xf4\x0c\xa1R\xb2\xd8-r\x01+\xdc\xe1mB\xe5\xb5|@+\xb2\xc2\xdd\xeb\xb7\x9e\xdf\xd5F\xe1\xde\xcaV\x14\xe6\xc2 \x0c\x88h\xb3\xf4\xdb\x85\x89H\x8eO\xb3\xb4;\xf4\xe6u\xe7\x15\x86\x99\xff?LM\xe2\x0d\xdft\x0d\x88\x7fa\x92\xde]\xab\x89\xc9\xec\x9c\x0d\x8c{\xcc\x0e\x11\xd1l\xed\"\xb5h\xae^\xc2pw\xdfu\xffTp\x8c\xfe\x97\x11\xf6\x95#F\x8f*B\xf4\xf8\xa8\xa2Y\xa2vD=O\xb4\x8eh\xe7\x89\x8f\x8e\xf8\xf88O\x8d<\xd5=\xf0)\x81\x84\xe1\xac\x1a^\xd0g/\xe6M:9u\xee\x81J\xea\xa6\xafv\xcf\xb0\x13\xf6\x02\xddW\xeb?\x01\x00\x00\xff\xffPK\x07\x08+W\x1b\x9a%\x05\x00\x00/\x13\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xc5\xb2bP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00 \x00test.xinUT\x05\x00\x01\x13\x87]^\xa4U\xcbn\xdb0\x10\xbc\xeb+\xb6'S@\x95:W\xa5\xa9O\xfd\x0b_hj\xe9\x08\xa0E\x86\xa4\x82\xa4\x82\xfe\xbd\xe0\xc3zR\xb5\xe1\xf2\x90\x08\xda\xd9\x99\xd9Y\xda~\x01\x8b\xc6\x82n\x9b\x06u\x96\x91\x12\x88aR!\x08zB\x01\x8c\x1a4y\x06\x00\xa4\x92\xee\x1f\x00)\x81\nQ\x98\x961\xc4\n+ \x1f\xc8\xca\x12?P\x7f\xc5\xe7\x0bU\xa1\xd3\xff\x1d\xa1\x87<\xcf\x07\x12&\xdb\xc6\xaehL\xfd\x07\xe3#\xaf\x85E}\x8bH\xc83\x87]\xd7\xff\xe8\xfa\xa3\xed\xfa]x\x1d\x8e\xe3Y\xeaL\xeb\x03(\xea\x8e\xd3\xce\x8e\x8fb\x10\xac\xf9|\xfe+\\Qc\xb2\x19'R\xf6\x168W\x94>\x01\xc2\x81\xad\xd5B\xb5\xe6@\xfc\xcc\x9c\xd6\x02\xab\xc3&r\x88\xe0h\x0b\xe8\xfacs\xb4\x00\x8b\x1c\x12=.\x19R\xa1a\x85\xe4\x85\x13\xfa\x87\xc0\xb4\x8f\x1a\x83\xda\xd6\xb2\x99\xf4\xe5\x9b\x9d.\x93\xdc\x9fp\xb5|\x83S\xd5\xb5r$0\xd0\x85;\xe6\\\xa5\xcb\xb9\xefO\x18\x0eQ\x9f\xd1\x02\x83}\x84\xa5L.\xb1\xcf\x11\xbb\xb8XW\xe4k\x9ae\x90H\xec\x86|K\xb1\xb9\xd1_\xc6AL6q\x08\x1aM+l\xe8v\x1b\x7f\x8do\xc0\xea\x16c\xaa\xe3\xbd\"\xc6\xea\xb2\xe4\x17\x0b\xbb\xdf\x9f\n\x99\xc5\xca\x03\xe1\xd4Z8K\x9bZ\xbb\x8f4\xca\x0ck\x08\xea\x05\xa7\xc2\xe0\xb6\x07_\xbe\xcbD z\xd0\x05\xbe_\x051\xf2%\xbc\xccJ\xb7\xect\xfdM/W\xbe\xa5)\xaa\x94\x96\x9f\x05\xa3\n\xf6O{w\x9e\xf3\xb9\xd9\"@\xb6=\xff\x04r\xa1\xf6\xad,\xe9\xc9\x00)V\xc0|\"r\xd78\x01\xfe4\x1f\xeb;H\xce\xe1\xf4\x05\xbfn\x8d\x98\xfcl\x06S\xc9\xd2\xc4\xddzU\xc5\xb8\xc8\xc4\xe8\xe1[\xef\xfd\xf0\xd0\xd6\x1c\xf3\x7fln\xe2\xd1\xfd\xfclz\xbcP\xf5\xb8G\xc7\xfc\xa0\xc7\xbf\x01\x00\x00\xff\xffPK\x07\x08w\xe4\xa1\xb3\xdd\x01\x00\x00i\x07\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xcf\x98\x83P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00 \x00vec.xinUT\x05\x00\x017\x89\x87^\xa4X\xddv\x9b8\x10\xbe\xf7SLnz\xc0.\xc7\xce\xad\xdd4\xcf\"\x83\x88\xc7\x01\x89 \x01vw\xf7\xdd\xf7\x8c\x84\x90\x108\xd9n\xb9\x8aG\x9f\xe6\xffO9A\xcfsP\x9a\x89\x82\xb5\x05TxnY{\xdflN0p\xe8\x14\x07\xa5\x8b\xe3\xf1\xaf\x1a\x05\xd4\xec\xf6\x0f\xa0\x00}AE\xc0\xef\x9b\x13(Iw\xd5\x05\xf4\x85\xd7\xc0\x06v\x873\xbf\xa0(\x88@wA\xb0\x9a\xab\x86\xe5|\x93\x1c-3\xc3\x0bE\xea \xecF\xcc\xd3\x0dQ\x92\x0bg\x05\xf4\xe9\x06\x00\x92\x9e\xe7\xc7\xe3\x1b\xd7\xd0\xc3!\x1d\xcf5\xc3jv\xae*\xcc9\xf4\xf0\xec~\xe3/\x0e}\xea\xf0\x15Sz\x85_\x92\xcd\xe1\xf0<\xdd\xe0u\xa3\xef\xaf\xee\xceK\x84\x9b\x14\xe1\x1f\xaf\xd0\xc3`QX.\x91\xc1\xaf!50\x80\xa4\xe5EG\xea\xda\x9f\xee#~%\xdc\x80\xe59`:?\xa3\xe3o\xe6$y\x81[`\xc4\x00\x98\xa6i\x84\xd6m\xc7GR\xc9*\xc5\x9d\xb6(\n~\x83\x1enV_\"\xb5<\x14fL\xf8 \x18y\xd1s\xcf\x9e\xfd\xdf3{\xadC1\x1dy\xbb\x0f\xc3\x1fFX\xb2\x034~\x1e\xd9N\xae\xbc0\xf5\xeau\xfb\x19j\x0b\xd9\xb3C\xb5\xbc\xe7\xad\xe2.0\xa4\xc2,T6\xc0\xc1\x9f\xc7#+\x8a'\x7f\xd1\xe5N:e\x99\x89\xf9 \xae\x92r\xb2\xd3Ls\x05\xbd\x11F\xa4'\x1f\xdf1l\xc3\xcc&\x1f\xb48d\x81t\n\xdcm\x1e\xa5\xde[d\x93\x01\xca\x89IR\xc8\x8d\xe3N\x85\xb1\x16\x0d\xba\xaa\xba3\xe0\\\xb4\xf1\xc8\x0f@[L\x812\x84\x1d}\x1f\xa7VR\xc6A\xb4)\x18\x02I\x88\x93L\xac\x0e\x96\xe2\xca\x80\xe5\x17\xb2\xe0\xf7\xb5\xff\x0f\x9a\x17rE\xc3\xa8@B\xf3fz\x1f\xe6ZO\x1a\xd7\xac\xf1\n\xaf\xd5#\x81\xde\x1eUc\x14Z\x93\x01\x18\x95\xa1\xc9C\x9bZ\xd4\x07K\xa64W\x1a\xa8=j y%\x05\x07\x067\x14\xd4}7'@Etj\xb7\x04\x17Lc\xcf\xc177\xa3\xb5\xbd\xb5\xd6\xf8\x0e\xeb\x8d\xaf\xc4J\xf3\xf6O,\xa5\xa8X\xf3\xe2\xb4Y&\xf8\x02\x11d\x8d\xbf\xe2\xb3\xa6\xe7\xed\xddY\xf3H\xb3G\xc5\xf5m\xad\xa8L\xe3\x1b\xb9+Y\xf3\xff\xcb\xfc\xef5\xe6\xb3V\xfa\x0b\x1b\x90\x8d\xef\x0da\xce\xa3\xc8L\x18\x92i\xd4}6\x13|E\xf4\xd4\x9c\xde)\x9a\x8eEp8\x8c\x87\xc3\xda\xe1\xc3N\xf0B\xf5\xe4\xf0\x811,\xcf\xc3\x123\xd5\x83bm\xee\xc4\xa9.\x9b\xa8\x14\x17A_~\x8bq\x15\xb5\x921+L\xad|t\x98\xbf+\xd9j\xe8\x14\x8a7\xb8H\xd6rhX\xabQ\xa3\x14ch[\x9d\x9d\xef\xd0C\xd3\xf2\xc2\x05\xc0\xb80#\x8a+os\x1azi`\xcd\x13 \\\x03/\xb9\xc09\x90\xaec\x03\xd3\x05`\x14\xe2Q\x96\x10Am\x94\xb9\xa6 \x823\x9e\xd7\xc7\xc0+\xf1\x7ft:\x8aYJ~\xccp<\x9f\xb4\x0e\xf3-\x99\xbc\n=T\x12.8\xf3\x0bA\x1a\xec\xa5^\xca\xabd\xec\x95\xa4\x8a\xda\xb9=0\xfb\xc4\xcb\x9a\xab,\xebE\xf6\xe0\xc8\xc9\xaf \x91\x9c\x96N\xafkr~\xac\xc8\xb9>\x92s\x1d9%\x19\\W\xe5\xd8\x8aZ\x08\x9a\xa5\x8b\x03\xe3d\xfe\xb2|\x8e\x93\xa8\xeb\xca\xe9\xe8\x9f\xa5 \xfa\xaeK\x92 \xcbJ\x1a\x87\x90\xc0}\x81ys\x03 c#\x1e\xe6\x83\xaf\xbde>\xaclZ\xe6\x0b{\xea8\xc2\xa3\xab\x10\xa4\xd3Z\xce\xc5V\xc4j4\x9f\x02\x92\x1d4d\xe9\x82Q\xefM\x0b\xf1\x87\xf5\xad\x7f\x1a\x1a-U\x8dm)\xbe\xcd`\xc1\x85F}O\xd3 \x15\x82\x9e\xfcd\xb6ML0\x1a\xde\x9d\xc0\x0f\x90%0\xa8Pi\xb3\x070\x0d\x85\xe4\n\x84\xd4\xc0\x94\xeaj\x0e\xc4\x80\x9d\xb1B}7\xcc\xcd\xad/\xe6\x16+\x8a\x0c\xcb\xcc@\xd7'\x18E\xc2l\xd4\x8f'\xf3\x82\xb6\\Xg\xf7\x82\x85\xe6\xdcaUPw>W2\x7f7\x8b\x0b\xab*9@g\xfe\xcee\xddHe\xb6\x17-[\x90\x8d\xb2\xdb\xcbH\x966\xa2D\xf2\xaf\xa7G\xe6\xc2\x97\x0f\xa3\xb5\x11\xf5\xe5\x13i6s\xe8\xf9+K0\xf9 `@}\x01^\xf1\x9a\x0b\xad\x88~3\xda\xcb\x12\x84{\x9dl\xcduz\x99\x08\xcb\x81\xe5yWw\x15#s\xcbN\xe4\x94\xe0j\\4o\x9f 0' 123 | (eq (vec::index row 65) 4)) 124 | (case 'vec::index -1' 125 | (eq (vec::index row 100) -1)) 126 | (case 'vec::index long vec' 127 | (eq (vec::index (seq 10000) 9876) 9876)) 128 | (case 'vec::index long vec, -1' 129 | (eq (vec::index (seq 1000) -99) -1)) 130 | (case 'vec::has? true' 131 | (assert (vec::has? row 15))) 132 | (case 'vec::has? false' 133 | (assert-false (vec::has? row 1000))) 134 | (case 'vec::reverse' 135 | (eq-vec (vec::reverse (seq 10)) 136 | (vec 9 8 7 6 5 4 3 2 1 0))) 137 | (case 'vec::join!' 138 | (eq-vec (vec::join! (nat 4) (nat 5)) 139 | (vec 1 2 3 4 1 2 3 4 5))) 140 | (case 'vec::reduce - number sum' 141 | (eq (vec::reduce (seq 11) + 3) 58)) 142 | (case 'vec::reduce - string concatenation' 143 | (eq (vec::reduce (vec 'hello' 'world' 'foo' 'bar') + 'start_') 144 | 'barfooworldhellostart_')) 145 | (case 'vec::reverse' 146 | (eq-vec (vec::reverse row) (vec 897 35 65 92 15 14 3))) 147 | (case 'vec::map' 148 | (eq-vec (vec::map row odd?) (vec 1 0 1 0 1 1 1))) 149 | (case 'vec::filter' 150 | (eq-vec (vec::filter row odd?) (vec 3 15 65 35 897))) 151 | (case 'vec::every true' 152 | (eq (vec::every (vec 1 1 1 1 1)) true)) 153 | (case 'vec::every false' 154 | (eq (vec::every (vec 1 1 0 1 0)) false)) 155 | (case 'vec::some true' 156 | (eq (vec::some (vec 1 1 0 1 1)) true)) 157 | (case 'vec::some false' 158 | (eq (vec::some (vec 0 0 0 0 0)) false)) 159 | (case 'vec::zip equal length vecs' 160 | (eq-vec (vec::zip + (nat 10) (range -5 30 1)) 161 | (range -4 16 2))) 162 | (case 'vec::zip with custom func' 163 | (eq (str (vec::zip vec 164 | (vec 'a' 'b' 'c' 'd') 165 | (vec 1 2 3))) 166 | (str (vec (vec 'a' 1) 167 | (vec 'b' 2) 168 | (vec 'c' 3))))) 169 | (case 'vec::sort' 170 | (eq-vec (vec::sort (vec::reverse row)) (vec 3 14 15 35 65 92 897))) 171 | (case 'vec::uniq - already unique' 172 | (eq-vec (vec::uniq (nat 10)) 173 | (nat 10))) 174 | (case 'vec::uniq - all the same' 175 | (eq-vec (vec::uniq (vec 'hi' 'hi' 'hi')) 176 | (vec 'hi'))) 177 | (case 'vec::uniq - sorted' 178 | (eq-vec (vec::uniq (vec 1 1 1 2 2 3 4 5 5 5 6 6 7 7 7 8 9)) 179 | (nat 9))) 180 | (case 'vec::uniq - unsorted' 181 | (eq-vec (vec::sort! (vec::uniq (vec 1 5 9 2 7 2 4 2 5 8 4 6 3 4 2 5 1))) 182 | (nat 9))) 183 | (case 'vec::of' 184 | (eq-vec (vec::of 5 10) (vec 10 10 10 10 10))) 185 | (case 'vec::max' 186 | (eq (vec::max row) 897)) 187 | (case 'vec::min' 188 | (eq (vec::min row) 3)) 189 | (case 'vec::sum' 190 | (eq (vec::sum row) 1121)) 191 | (case 'vec::prod' 192 | (eq (vec::prod (vec::slice row 0 4)) 57960)) 193 | (case 'vec::++' 194 | (eq-vec (vec::++ (vec 1 2 3) (vec 90 80 70)) 195 | (vec 91 82 73))) 196 | (case 'vec::v+' 197 | (eq-vec (vec::v+ row 100) 198 | (vec 103 114 115 192 165 135 997))) 199 | (case 'vec::flat' 200 | (eq-vec (vec::flat (vec row row row)) 201 | (+ (+ row row) row))))) 202 | 203 | (: hello 'Hello, World!\n') 204 | (scope 205 | 'String' 206 | (vec 207 | (case 'str::size of blank chars' 208 | (eq (str::size ' \n \t') 4)) 209 | (case 'str::size' 210 | (eq (str::size hello) 14)) 211 | (case 'str::slice in bounds' 212 | (eq (str::slice hello 4 10) 'o, Wor')) 213 | (case 'str::slice OOB left' 214 | (eq (str::slice hello -1 3) 'Hel')) 215 | (case 'str::slice OOB right' 216 | (eq (str::slice hello 8 200) 'orld!\n')) 217 | (case 'str::slice OOB both sides' 218 | (eq (str::slice hello -20 30) hello)) 219 | (case 'str::slice OOB flush left' 220 | (eq (str::slice hello -5 -1) '')) 221 | (case 'str::slice OOB flush right' 222 | (eq (str::slice hello 20 30) '')) 223 | (case 'str::slice backwards bounds' 224 | (eq (str::slice hello 3 2) '')) 225 | (case 'str::get' 226 | (eq (str::get hello 5) ',')) 227 | (case 'str::get OOB left' 228 | (eq (str::get hello -5) false)) 229 | (case 'str::get OOB right' 230 | (eq (str::get hello 100) false)) 231 | (case 'str::get length' 232 | (eq (str::get hello (str::size hello)) false)) 233 | (case 'str::set! blank' 234 | (do 235 | (: s 'hello') 236 | (str::set! s 2 '') 237 | (eq s 'hello'))) 238 | (case 'str::set! one char' 239 | (do 240 | (: s 'hello') 241 | (str::set! s 2 'i') 242 | (eq s 'heilo'))) 243 | (case 'str::set! multi char' 244 | (do 245 | (: s 'hello') 246 | (str::set! s 2 'xi') 247 | (eq s 'hexio'))) 248 | (case 'str::set! overflow' 249 | (do 250 | (: s 'hello') 251 | (str::set! s 4 'xxx') 252 | (eq s 'hellxxx'))) 253 | (case 'str::set! operates on literals' 254 | (do 255 | (eq (str::set! 'hello' 2 'xi') 256 | 'hexio'))) 257 | (case 'str::add!' 258 | (do 259 | (: s 'hello') 260 | (str::add! s (str::add! ' ' 261 | ; check: operates on literals 262 | 'world')) 263 | (eq s 'hello world'))) 264 | (case 'str::blank? on non-blank' 265 | (eq (str::blank? hello) false)) 266 | (case 'str::blank? on blank' 267 | (eq (str::blank? '') true)) 268 | (case 'str::enc' 269 | (eq (str::enc 'abc') 97)) 270 | (case 'str::dec' 271 | (eq (str::dec 65) 'A')) 272 | (case 'str::map' 273 | (eq (str::map 'TeST sTrINg' (: (f c) 274 | (if (str::upper? c) 275 | (str::downcase c) 276 | '0'))) 277 | 't0st00t0in0')) 278 | (case 'str::filter' 279 | (eq (str::filter 'TeST sTrINg' str::upper?) 280 | 'TSTTIN')) 281 | (case 'str::split' 282 | (eq-vec (str::split ', scale, by, the,bay, 2020' ', ') 283 | (vec '' 'scale' 'by' 'the,bay' '2020'))) 284 | (case 'str::replace' 285 | (eq (str::replace ', scale, by, , ,the,bay, 2020' ', ' '-') 286 | '-scale-by--,the,bay-2020')) 287 | (case 'str::upcase' 288 | (eq (str::upcase 'Scale by the Bay!') 289 | 'SCALE BY THE BAY!')) 290 | (case 'str::downcase' 291 | (eq (str::downcase 'Scale by the Bay!') 292 | 'scale by the bay!')) 293 | (case 'str::pad-start single letter' 294 | (eq (str::pad-start 'hello' 10 '0') 295 | '00000hello')) 296 | (case 'str::pad-start multi letter' 297 | (eq (str::pad-start 'bye' 10 '123') 298 | '1231231bye')) 299 | (case 'str::pad-start too long' 300 | (eq (str::pad-start 'this is a long sentence' 10 ' ') 301 | 'this is a long sentence')) 302 | (case 'str::pad-end single letter' 303 | (eq (str::pad-end'hello' 10 '0') 304 | 'hello00000')) 305 | (case 'str::pad-end multi letter' 306 | (eq (str::pad-end 'bye' 10 '123') 307 | 'bye1231231')) 308 | (case 'str::pad-end too long' 309 | (eq (str::pad-start 'this is a long sentence' 10 ' ') 310 | 'this is a long sentence')) 311 | (case 'str::trim-start' 312 | (eq (str::trim-start ' test ' ' ') 313 | 'test ')) 314 | (case 'str::trim-end' 315 | (eq (str::trim-end ' test ' ' ') 316 | ' test')) 317 | (case 'str::trim' 318 | (eq (str::trim ' test ' ' ') 319 | 'test')) 320 | (case 'str::trim - does not hang on blank part' 321 | (eq (str::trim ' test ' '') 322 | ' test ')) 323 | (case 'str::escape' 324 | (eq (str::escape '\n\thacker \'town\\\' \n') 325 | '\\n\\thacker \\\'town\\\\\\\' \\n')) 326 | (case 'str::fmt - basic' 327 | (eq (str::fmt 'a {}{} b {} 3 {}' 328 | (vec 1 'test' 3.141592 (vec 1 2))) 329 | 'a 1test b 3.14159200 3 ( 1 2)')) 330 | (case 'str::fmt - empty items' 331 | (eq (str::fmt 'start{}end' (vec)) 332 | 'start{}end')) 333 | (case 'str::fmt - items vec too long' 334 | (eq (str::fmt '{}, {}' (vec 12 13 14 15 16)) 335 | '12, 13')) 336 | (case 'str::fmt - items vec too short' 337 | (eq (str::fmt '{}, {}, {}' (vec -3 -2)) 338 | '-3, -2, {}')) 339 | (case 'str::fmt - nested & escaped placeholders' 340 | (eq (str::fmt '{{}} {\\} [{]} {{} end' 341 | (vec 1 2 3 4 5)) 342 | '{1} {\\} [{]} {2 end')) 343 | (case 'str::fmt - no recursive replacement' 344 | (eq (str::fmt 'a: {}, b: {}' 345 | (vec '{} {}' 42)) 346 | 'a: {} {}, b: 42')))) 347 | 348 | (do (: simple-map (map)) 349 | (map::set! simple-map 1 'first') 350 | (map::set! simple-map 2 'second') 351 | (map::set! simple-map 3 'third')) 352 | (: mixed-vec-key 353 | (vec 5 3 1)) 354 | (do (: mixed-map (map)) 355 | (map::set! mixed-map 'words' (vec 'hi' 'bye' 'aloha')) 356 | (map::set! mixed-map 'maps' simple-map) 357 | (map::set! mixed-map scope 'scope func') 358 | (map::set! mixed-map mixed-vec-key 'keyed by vec')) 359 | (scope 360 | 'Map' 361 | (vec 362 | (case 'map::size 0' 363 | (eq (map::size (map)) 0)) 364 | (case 'map::size > 0' 365 | (eq (map::size mixed-map) 4)) 366 | (case 'map::empty?' 367 | (assert (map::empty? (map)))) 368 | (case 'map::empty? - false' 369 | (assert-false (map::empty? simple-map))) 370 | (case 'map::get - simple' 371 | (eq (map::get simple-map 3) 'third')) 372 | (case 'map::get - mixed' 373 | (eq-vec (map::get mixed-map 'words') (vec 'hi' 'bye' 'aloha'))) 374 | (case 'map::get - vec key' 375 | (eq (map::get mixed-map mixed-vec-key) 376 | 'keyed by vec')) 377 | (case 'map::get - func key' 378 | (eq (map::get mixed-map scope) 379 | 'scope func')) 380 | (case 'map::has? - true' 381 | (assert (map::has? simple-map 3))) 382 | (case 'map::has? - false' 383 | (assert-false (map::has? mixed-map (vec 5 3 1)))) 384 | (case 'map::keys' 385 | (assert 386 | (do (: keys (map::keys mixed-map)) 387 | (& (= (vec::size keys) 4) 388 | (vec::every (vec::map (vec 'words' 389 | 'maps' 390 | scope 391 | mixed-vec-key) 392 | (: (f k) 393 | (vec::has? keys k)))))))) 394 | (case 'map::values' 395 | (assert 396 | (do (: vals (map::values simple-map)) 397 | (& (= (vec::size vals) 3) 398 | (vec::every (vec::map (vec 'first' 399 | 'second' 400 | 'third') 401 | (: (f k) 402 | (vec::has? vals k)))))))) 403 | (case 'map::entries' 404 | (assert 405 | (do (: ents (map::entries simple-map)) 406 | (& (= (vec::size ents) 3) 407 | (vec::every (vec::map ents 408 | (: (f pair) 409 | (= (map::get simple-map (vec::head pair)) 410 | (vec::get pair 1))))))))))) 411 | 412 | (do (: simple-set (set)) 413 | (set::add! simple-set 'one') 414 | (set::add! simple-set 'two') 415 | (set::add! simple-set 'three') 416 | (set::add! simple-set 'four')) 417 | (scope 418 | 'Set' 419 | (vec 420 | (case 'set::size 0' 421 | (eq (set::size (set)) 0)) 422 | (case 'set::size > 0' 423 | (eq (set::size simple-set) 4)))) 424 | 425 | (scope 426 | 'Math' 427 | (vec 428 | (case 'math::abs positive' 429 | (eq (math::abs 12) 12)) 430 | (case 'math::abs negative' 431 | (eq (math::abs -34) 34)) 432 | (case 'math::abs zero' 433 | (eq (math::abs 0) 0)) 434 | (case 'math::round' 435 | (eq-vec (vec::map (vec 2.01 2.2 2.45 2.49 2.5 2.51 2.8 2.99 3.1) math::round) 436 | (vec 2 2 2 2 3 3 3 3 3))) 437 | (case 'math::gcd coprime' 438 | (eq (math::gcd 3 7) 1)) 439 | (case 'math::gcd a > b' 440 | (eq (math::gcd 49 14) 7)) 441 | (case 'math::gcd negatives' 442 | (eq (math::gcd -42 14) 14)) 443 | (case 'math::gcd including 1' 444 | (eq (math::gcd 1 12) 1)) 445 | (case 'math::lcm' 446 | (eq (math::lcm 14 49) 98)) 447 | (case 'math::lcm coprime' 448 | (eq (math::lcm 7 39) 273)) 449 | (case 'math::{prime? prime-factors factors}' 450 | ; validate that all primes in factors is prime factors 451 | (assert 452 | (vec::every 453 | (vec::map (nat 500) 454 | (: (f n) 455 | (vec::eq? (vec::uniq (math::prime-factors n)) 456 | (vec::filter (math::factors n) 457 | math::prime?))))))))) 458 | 459 | (scope 460 | 'Data serialization' 461 | (vec 462 | (case 'simple int' 463 | (eq (src::serialize 42) '42')) 464 | (case 'simple frac' 465 | ; (str ) will return to 8 digits, so 466 | ; src::serialize also does. This is fine for now. 467 | (eq (src::serialize -3.141592) '-3.14159200')) 468 | (case 'simple string' 469 | (eq (src::serialize 'hello world') '\'hello world\'')) 470 | (case 'escaped string' 471 | (eq (src::serialize 'hello\tworld\r\n') 472 | '\'hello\\tworld\\r\\n\'')) 473 | (case 'simple vec' 474 | (eq (src::serialize (vec 1 2 'three' 4 -5)) 475 | '(vec 1 2 \'three\' 4 -5)')) 476 | (case 'nested vec' 477 | (eq (src::serialize (vec 'hi' 'hello' (vec 1 2.42 3.14 'four'))) 478 | '(vec \'hi\' \'hello\' (vec 1 2.42000000 3.14000000 \'four\'))')) 479 | (case 'simple map' 480 | (assert (vec::has? (vec 481 | '(do (: m (map)) (map::set! m \'hi\' 2) (map::set! m (vec 1 2) \'is vec\'))' 482 | '(do (: m (map)) (map::set! m (vec 1 2) \'is vec\') (map::set! m \'hi\' 2))') 483 | (src::serialize (do (: m (map)) 484 | (map::set! m 'hi' 2) 485 | (map::set! m (vec 1 2) 'is vec')))))) 486 | (case 'stream' 487 | (eq (src::serialize (stream)) '(stream)')))) 488 | 489 | (: stat-test-list (vec 1 3 24 0 4 2 2 2 2 4 3 1 2 442 235 3 23 315)) 490 | (scope 491 | 'Statistics' 492 | (vec 493 | (case 'mean' 494 | (eq (stat::mean (vec 0 3 1 5 639 24)) 112)) 495 | (case 'geomean' 496 | (eq-approx (stat::geomean (vec 3 1 3 3 9 1 1 27)) 3)) 497 | (case 'median of odd list' 498 | (eq (stat::median (vec 13 254 2 24 0 0 3 1 2)) 2)) 499 | (case 'median of even list' 500 | (eq (stat::median (vec 1 2 3 4 5 6)) 3.5)) 501 | (case 'mode' 502 | (eq (stat::mode stat-test-list) 2)))) 503 | --------------------------------------------------------------------------------