├── .gitattributes ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── .gitattributes ├── README.md ├── dohelp.fnl ├── fizzbuzz.fnl ├── hello.fnl ├── inspace.fnl ├── simpleserver.fnl ├── tictac.fnl ├── todo_client.fnl ├── todo_common.fnl └── todo_server.fnl ├── extensions └── ext.go ├── funl ├── ast.go ├── evaluator.go ├── fiber.go ├── fni.go ├── fumap.go ├── fumap_test.go ├── funl.go ├── importer.go ├── lexer.go ├── lexer_test.go ├── list.go ├── list_test.go ├── operators.go ├── operimpl.go ├── parser.go └── stdfunfiles.go ├── funl_logo.png ├── go.mod ├── hellow.png ├── main.go ├── pmap ├── pmap.go └── pmap_test.go ├── std ├── std.go ├── stdast.go ├── stdbase64.go ├── stdbytes.go ├── stdfiles.go ├── stdhttp.go ├── stdio.go ├── stdjson.go ├── stdjson_test.go ├── stdlex.go ├── stdlog.go ├── stdmath.go ├── stdos.go ├── stdrpc.go ├── stdrun.go ├── stdstr.go ├── stdtime.go └── stdvar.go ├── stdfun ├── .gitattributes ├── repl.fnl ├── stddbc.fnl ├── stdfilu.fnl ├── stdfu.fnl ├── stdfun_generator.go ├── stdmeta.fnl ├── stdpp.fnl ├── stdpr.fnl ├── stdser.fnl ├── stdset.fnl └── stdsort.fnl ├── tester.fnl ├── tst ├── .gitattributes ├── arithm_test.fnl ├── comparison_test.fnl ├── expansion_test.fnl ├── func_proc_call_test.fnl ├── impure_test.fnl ├── list_test.fnl ├── logic_oper_test.fnl ├── map_test.fnl ├── math_test.fnl ├── mod_level_test.fnl ├── other_test.fnl ├── stdbase64_test.fnl ├── stdlex_test.fnl ├── stdmeta_test.fnl ├── stdser_test.fnl ├── stdsort_test.fnl ├── stdvar_test.fnl └── string_test.fnl └── tstfwk ├── .gitattributes ├── common_test_util.fnl └── ut_fwk.fnl /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.fnl linguist-language=funl 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Possible contributions 3 | There are many ways to contribute, for example: 4 | 5 | * Syntax highlighting for IDE's (VSC, Atom etc.) 6 | * Extending standard libraries (for example, networking/sockets, HTTP extending etc.) 7 | * Make projects based on FunL, applications or libraries 8 | * Improve (or make better) testing framework 9 | * Make automated tests for standard libraries 10 | * More tooling (code formatter, code analyzers etc.) 11 | 12 | Possible new (standard) libraries: 13 | 14 | * support for compression (gzip etc.) 15 | * cryptography support 16 | * data formats: csv, excel, base64, yaml etc. 17 | * sockets (TCP/UDP), websockets, Server-Sent Events, SMTP etc. 18 | * regular expressions 19 | * more advanced command line options parser 20 | * random number generation 21 | * bit operations 22 | 23 | # Asking for more information 24 | I'm happy to answer questions if needed. 25 | You can use my email for that: anssi.halmeaho@hotmail.com 26 | 27 | # Technical details of developing FunL 28 | Here are some details of developing FunL. 29 | 30 | ## Developing FunL native (in FunL itself) standard libraries 31 | Some of FunL standard libraries are implemented in FunL itself (like **stdfu**). 32 | As those are embedded into single executable (_funla_) source code of native libraries 33 | are included as Go raw strings (FunL executable contains all standard libraries). 34 | Source files of those libraries are placed in stdfun directory and following 35 | command will update **stdfunfiles.go** file: 36 | 37 | ``` 38 | go generate 39 | ``` 40 | 41 | This needs to be done when adding or modifying some standard libraries written in FunL itself. 42 | 43 | In _main.go_ there's line ```//go:generate go run ./stdfun/stdfun_generator.go``` which causes 44 | execution of ```/stdfun/stdfun_generator.go``` in ```go generate``` which produces _stdfunfiles.go_. 45 | 46 | In addition name of new module needs to be added to __funmodNames__ table in __initFunSourceSTD__ function(__funl.go__) 47 | so that module is included in __funla__ binary. 48 | 49 | ## Testing 50 | There is testing tool and tests implemented in FunL. Run those tests when doing changes: 51 | 52 | ### Testing with tester.fnl 53 | Most language functionality is tested with **tester.fnl**. 54 | 55 | Short status printed: 56 | 57 | ``` 58 | ./funla -args="'tst'" tester.fnl 59 | ``` 60 | 61 | Longer status printed: 62 | 63 | ``` 64 | ./funla -args="'tst'" -name=all tester.fnl 65 | ``` 66 | 67 | Note. this tool can be used as unit testing tool also otherwise. 68 | 69 | ### Unit testing Go code 70 | For some parts of code there are also Go unit tests: 71 | 72 | ``` 73 | go test ./... 74 | ``` 75 | 76 | ## Debugging runtime errors 77 | See [guide to read runtime error printouts](https://github.com/anssihalmeaho/funl/wiki/rteprint) 78 | 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anssi Halmeaho 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOCMD=go 2 | GOBUILD=$(GOCMD) build 3 | GOCLEAN=$(GOCMD) clean 4 | GOTEST=$(GOCMD) test 5 | 6 | BINARY_NAME=funla 7 | 8 | all: build 9 | 10 | race: 11 | $(GOBUILD) -race -trimpath -o $(BINARY_NAME) -v . 12 | 13 | build: 14 | $(GOBUILD) -trimpath -o $(BINARY_NAME) -v . 15 | 16 | clean: 17 | $(GOCLEAN) 18 | rm -f $(BINARY_NAME) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/anssihalmeaho/funl/blob/master/funl_logo.png) 2 | 3 | # FunL 4 | FunL is simple dynamically typed, functional programming language. 5 | It's interpreted language with support for concurrency and immutable data. 6 | FunL is implemented with Go. 7 | 8 | ## Get started 9 | ### Install 10 | 11 | Prerequisite is to have Go language environment available. 12 | 13 | #### Install in Linux (or Cygwin, Mac) 14 | git clone https://github.com/anssihalmeaho/funl.git 15 | cd funl 16 | make 17 | 18 | Run hello world example: 19 | 20 | ./funla -silent examples/hello.fnl 21 | Hello World 22 | 23 | #### Install in Windows 24 | git clone https://github.com/anssihalmeaho/funl.git 25 | cd funl 26 | go build -trimpath -o funla.exe -v . 27 | 28 | Run hello world example: 29 | 30 | funla.exe -silent examples\hello.fnl 31 | Hello World 32 | 33 | There are also other examples in examples folder. 34 | 35 | ## Getting help and try expressions 36 | 37 | ### Options: -help, -h 38 | #### In Linux (or Cygwin, Mac) 39 | ./funla -help 40 | 41 | #### In Windows 42 | funla.exe -help 43 | 44 | ### REPL (Read-Eval-Print-Loop) 45 | #### In Linux (or Cygwin, Mac) 46 | ./funla -repl 47 | 48 | #### In Windows 49 | funla.exe -repl 50 | 51 | In REPL type help for more information. 52 | 53 | Check [REPL usage](https://github.com/anssihalmeaho/funl/wiki/REPL-usage) for more hints. 54 | 55 | ### Options: -eval 56 | ./funla -eval "plus(1 2)" 57 | 3 58 | 59 | With _-eval_ option any FunL expression can be given as argument. 60 | Expression is evaluated and result is printed. 61 | 62 | ### help operator 63 | 64 | help operator can be used to get list of operators: 65 | 66 | ./funla -eval "help('operators')" 67 | 68 | help operator provides description for each operator: 69 | 70 | ./funla -eval "help('if')" 71 | 72 | in REPL: 73 | 74 | ./funla -repl 75 | Welcome to FunL REPL (interactive command shell) 76 | funl> help('if') 77 | 78 | ## Language and Standard library descriptions 79 | * [General structure](https://github.com/anssihalmeaho/funl/wiki/General-Structure) 80 | * [Syntax and Concepts](https://github.com/anssihalmeaho/funl/wiki/Syntax-and-concepts) 81 | * [Concurrency and impure operations](https://github.com/anssihalmeaho/funl/wiki/Concurrency-and-impure-operations) 82 | * [Importing modules](https://github.com/anssihalmeaho/funl/wiki/Importing-modules) 83 | * [Packages](https://github.com/anssihalmeaho/funl/wiki/packages) 84 | * [Operators explained](https://github.com/anssihalmeaho/funl/wiki/Operators-explained) 85 | * [External Modules](https://github.com/anssihalmeaho/funl/wiki/External-Modules) 86 | * [Usage as embedded language](https://github.com/anssihalmeaho/funl/wiki/Using-FunL-as-embedded-language) 87 | * [REPL usage](https://github.com/anssihalmeaho/funl/wiki/REPL-usage) 88 | * [Reading Runtime Error Printout](https://github.com/anssihalmeaho/funl/wiki/rteprint) 89 | * [Standard Libraries](https://github.com/anssihalmeaho/funl/wiki/Standard-Libraries) 90 | 91 | ## Contribution and development guidelines 92 | Guide for [contributing and developing FunL](https://github.com/anssihalmeaho/funl/blob/master/CONTRIBUTING.md) 93 | 94 | ## Blog 95 | There is Blog for FunL related topics: https://programmingfunl.wordpress.com 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.fnl linguist-language=funl 3 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/anssihalmeaho/funl/blob/master/hellow.png) 2 | 3 | # FunL examples 4 | 5 | ## hello.fnl 6 | Hello world example for FunL. 7 | 8 | ## dohelp.fnl 9 | Demonstrates how to create some files consisting FunL operator helps. 10 | 11 | ## inspace.fnl 12 | HTTP client example for inquiring people currently in space. 13 | 14 | ## tictac.fnl 15 | Tic-tac-toe game (program against user). 16 | 17 | ## fizzbuzz.fnl 18 | FizzBuzz implementation in FunL. 19 | 20 | ## simpleserver.fnl 21 | Really simple HTTP server. 22 | Provides GET /hello API for which it replies with "Hi There !". 23 | Start server (port number is given as argument): 24 | 25 | ``` 26 | make 27 | ./funla -args="8003" ./examples/simpleserver.fnl 28 | ``` 29 | 30 | Then test for example with curl: 31 | 32 | ``` 33 | curl http://localhost:8003/hello 34 | 35 | Hi There ! 36 | ``` 37 | 38 | ## ToDo Application (todo_client.fnl / todo_server.fnl / todo_common.fnl) 39 | Implementation of HTTP client and server for simple todo-application. 40 | There is also common module (todo_common) for client and server to use. 41 | It demonstrates how to create HTTP (micro)services, using JSON, logging, 42 | environment variables and shutting down HTTP server. 43 | 44 | Client and server use **localhost** address with default port number _8003_. 45 | Port number can be redefined with **TODO_SRV_PORT** environment variable. 46 | 47 | For example: 48 | 49 | export TODO_SRV_PORT=8009 50 | 51 | Server (todo_server.fnl) needs to started first and after that 52 | client(s) (todo_client.fnl) can be started to use server. 53 | 54 | Todo-items are JSON objects/maps with one mandatory field (**id**). 55 | Value for **id** is allocated by server when item is added. 56 | 57 | ### todo_server.fnl 58 | Server implementation for todo-application. Server maintains 59 | (todo-)items in its memory. Current implementation doesn't store those 60 | any permanent storage so those are lost when server is restarted. 61 | 62 | #### Service paths provided by server 63 | Inquire all items: 64 | 65 | ``` 66 | GET /items 67 | 68 | response body -> JSON: array of JSON objects (object corresponding to todo-item) 69 | ``` 70 | 71 | Put new item: 72 | 73 | ``` 74 | POST /items 75 | request body: JSON object (item) 76 | ``` 77 | 78 | Remove item: 79 | 80 | ``` 81 | DELETE /items/id/ 82 | ``` 83 | 84 | #### Starting server 85 | Starting todo server: 86 | 87 | ``` 88 | ./funla ./examples/todo_server.fnl 89 | todo-server: 2020/05/10 21:11:40 :'...listening...' 90 | ``` 91 | 92 | 93 | ### todo_client.fnl 94 | Client implements command-line interface which receives 95 | commands from user and uses todo-server via HTTP API 96 | to implement those commands. 97 | 98 | #### Starting client 99 | Starting client: 100 | 101 | ``` 102 | ./funla ./examples/todo_client.fnl 103 | Welcome to Todo application client 104 | todo> help 105 | Input can be: 106 | help -> prints this help 107 | ? -> prints this help 108 | quit -> exits repl 109 | exit -> exits repl 110 | 111 | put -> adds item 112 | get -> prints all items 113 | del -> removes item 114 | 115 | todo> 116 | ``` 117 | 118 | #### put -command 119 | Item is added with **put** -command. 120 | Item data is given directly as JSON object. 121 | 122 | ``` 123 | todo> put {"name": "buy food", "tag": "home"} 124 | ok 125 | todo> put {"name": "clean the house", "tag": "home"} 126 | ok 127 | ``` 128 | 129 | #### get -command 130 | With **get** -command user can list all items. 131 | 132 | ``` 133 | todo> get 134 | 135 | item: 136 | - id : 100 137 | - tag : home 138 | - name : buy food 139 | 140 | item: 141 | - id : 101 142 | - tag : home 143 | - name : clean the house 144 | ``` 145 | 146 | #### del -command 147 | Item is removed with **del** -command. 148 | Item **id** needs to be given as argument for the command. 149 | 150 | ``` 151 | todo> del 100 152 | ok 153 | todo> get 154 | 155 | item: 156 | - id : 101 157 | - tag : home 158 | - name : clean the house 159 | 160 | todo> 161 | ``` 162 | 163 | ### todo_common.fnl 164 | Common module **todo_common** provides procedure (_get-port_) which provides 165 | port number for clöient and server. It's by default 8003 but can redefined 166 | with **TODO_SRV_PORT** environment value. 167 | 168 | -------------------------------------------------------------------------------- /examples/dohelp.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | # reads help for all operators to list 5 | get-all-helps = func() 6 | looper = func(opelist result) 7 | while( not(empty(opelist)) 8 | rest(opelist) 9 | append(result help(head(opelist))) 10 | result 11 | ) 12 | end 13 | 14 | call(looper help('operators') list()) 15 | end 16 | 17 | # writes list items to file 18 | write-to-file = proc(help-list) 19 | looper = proc(lst) 20 | while( not(empty(lst)) 21 | call(proc() 22 | _ = call(stdfiles.writeln fh head(lst)) 23 | rest(lst) 24 | end) 25 | true 26 | ) 27 | end 28 | 29 | import stdfiles 30 | fh = call(stdfiles.create 'helpit.txt') 31 | _ = call(looper help-list) 32 | call(stdfiles.close fh) 33 | end 34 | 35 | # main 36 | main = proc() 37 | just-oper-names = if( not(empty(argslist())) 38 | eq(head(argslist()) 'opnames') 39 | false 40 | ) 41 | list-to-write = if( just-oper-names 42 | help('operators') 43 | call(get-all-helps) 44 | ) 45 | call(write-to-file list-to-write) 46 | 47 | #call(write-to-file call(get-all-helps)) 48 | end 49 | 50 | endns 51 | -------------------------------------------------------------------------------- /examples/fizzbuzz.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | main = proc() 5 | import stdfu 6 | 7 | fizzbuzz = func(int-list) 8 | is-divisible-by = func(dividend divisor) 9 | eq(mod(dividend divisor) 0) 10 | end 11 | 12 | choose-string = func(i result-str) 13 | is-divisible-by-5 = call(is-divisible-by i 5) 14 | is-divisible-by-3 = call(is-divisible-by i 3) 15 | plus(result-str '\n' cond( 16 | and(is-divisible-by-5 is-divisible-by-3) 'FizzBuzz' 17 | is-divisible-by-5 'Buzz' 18 | is-divisible-by-3 'Fizz' 19 | str(i) 20 | )) 21 | end 22 | 23 | call(stdfu.loop choose-string int-list '') 24 | end 25 | 26 | call(fizzbuzz call(stdfu.generate 1 100 func(n) n end)) 27 | end 28 | 29 | endns 30 | -------------------------------------------------------------------------------- /examples/hello.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | main = proc() 5 | import stdio 6 | 7 | call(stdio.printline 'Hello World') 8 | end 9 | 10 | endns 11 | -------------------------------------------------------------------------------- /examples/inspace.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | # https://pragprog.com/book/bhwb/exercises-for-programmers 5 | # http://api.open-notify.org/astros.json 6 | 7 | get-astros = proc() 8 | import stdhttp 9 | import stdjson 10 | 11 | response = call(stdhttp.do 'GET' 'http://api.open-notify.org/astros.json' map()) 12 | _ _ val = call(stdjson.decode get(response 'body')): 13 | val 14 | end 15 | 16 | by-craft = func(astro-list) 17 | import stdfu 18 | f = func(item) 19 | craft = get(item 'craft') 20 | astro-name = get(item 'name') 21 | list(craft astro-name) 22 | end 23 | call(stdfu.group-by astro-list f) 24 | end 25 | 26 | main = proc() 27 | result = call(get-astros) 28 | astro-list = get(result 'people') 29 | call(by-craft astro-list) 30 | end 31 | 32 | endns 33 | -------------------------------------------------------------------------------- /examples/simpleserver.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | import stdhttp 5 | 6 | main = proc(port) 7 | hello-handler = proc(w r) 8 | import stdbytes 9 | call(stdhttp.write-response w 200 call(stdbytes.str-to-bytes 'Hi There !')) 10 | end 11 | 12 | mux = call(stdhttp.mux) 13 | _ = call(stdhttp.reg-handler mux '/hello' hello-handler) 14 | address = plus(':' str(port)) 15 | call(stdhttp.listen-and-serve mux address) 16 | end 17 | 18 | endns 19 | 20 | -------------------------------------------------------------------------------- /examples/tictac.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | import stdio 5 | import stdfu 6 | 7 | None = 1 8 | X = 2 9 | O = 3 10 | 11 | machine = 1 12 | player = 2 13 | 14 | #--- gets empty grid as basis 15 | get-initial-grid = func() 16 | map() 17 | end 18 | 19 | symbol-map = map( 20 | None ' ' 21 | X 'X' 22 | O 'O' 23 | ) 24 | 25 | #--- prints grid to screen 26 | print-grid = proc(grid) 27 | print-line = proc(start-ind) 28 | v1 = if(in(grid start-ind) get(grid start-ind) None) 29 | v2 = if(in(grid plus(start-ind 1)) get(grid plus(start-ind 1)) None) 30 | v3 = if(in(grid plus(start-ind 2)) get(grid plus(start-ind 2)) None) 31 | 32 | _ = call(stdio.printf ' %s | %s | %s\n' 33 | get(symbol-map v1) 34 | get(symbol-map v2) 35 | get(symbol-map v3) 36 | ) 37 | _ = if(not(eq(start-ind 7)) call(stdio.printf ' --+---+---\n') '') 38 | true 39 | end 40 | 41 | print-it = proc(line-count) 42 | _ = call(stdio.printf '\n') 43 | _ = call(print-line 1) 44 | _ = call(print-line 4) 45 | _ = call(print-line 7) 46 | _ = call(stdio.printf '\n') 47 | true 48 | end 49 | 50 | call(print-it 0) 51 | end 52 | 53 | #--- has-game-ended 54 | has-game-ended = call(func() 55 | import stdset 56 | import stdfu 57 | 58 | winning-list = list( 59 | list(1 2 3) 60 | list(4 5 6) 61 | list(7 8 9) 62 | 63 | list(1 4 7) 64 | list(2 5 8) 65 | list(3 6 9) 66 | 67 | list(1 5 9) 68 | list(3 5 7) 69 | ) 70 | 71 | check-one-line = func(line marked) 72 | set1 = call(stdset.list-to-set call(stdset.newset) line) 73 | set2 = call(stdset.list-to-set call(stdset.newset) marked) 74 | #_ = print('marked: ' marked) 75 | call(stdset.is-subset set2 set1) 76 | end 77 | 78 | func(grid) 79 | is-winner = func(mark) 80 | marked = keys(call(stdfu.filter grid func(_ v) eq(v mark) end)) 81 | 82 | checkloop = func(winlist found) 83 | while( and( not(found) not(empty(winlist)) ) 84 | rest(winlist) 85 | call(check-one-line head(winlist) marked) 86 | found 87 | ) 88 | end 89 | 90 | call(checkloop winning-list false) 91 | end 92 | 93 | cond( 94 | call(is-winner X) list(true true player) 95 | call(is-winner O) list(true true machine) 96 | gt(len(grid) 8) list(true false 0) 97 | list(false false 0) 98 | ) 99 | end 100 | end) 101 | 102 | minmax = func(grid turn depth) 103 | swap = func(whos-turn) 104 | if( eq(whos-turn machine) 105 | player 106 | machine 107 | ) 108 | end 109 | 110 | min = func(idx1 idx2) 111 | if( gt(idx1 idx2) 112 | idx2 113 | idx1 114 | ) 115 | end 116 | 117 | max = func(idx1 idx2) 118 | if( gt(idx1 idx2) 119 | idx1 120 | idx2 121 | ) 122 | end 123 | 124 | get-mark = func(whos-turn) 125 | if( eq(whos-turn machine) 126 | O 127 | X 128 | ) 129 | end 130 | 131 | get-one-score = func(idx current-score) 132 | next-grid = put(grid idx call(get-mark turn)) 133 | is-end has-winner winner = call(has-game-ended next-grid): 134 | score = if( is-end 135 | if( has-winner 136 | case( turn 137 | player minus(0 10) 138 | machine 10 139 | ) 140 | 0 # no winner, even game 141 | ) 142 | call(minmax next-grid call(swap turn) plus(depth 1)) 143 | ) 144 | if( eq(current-score 'no-score') 145 | score 146 | if( eq(turn machine) 147 | call(max score current-score) 148 | call(min score current-score) 149 | ) 150 | ) 151 | end 152 | 153 | looper = func(avail current-score) 154 | while( not(empty(avail)) 155 | rest(avail) 156 | call(get-one-score head(avail) current-score 1) 157 | current-score 158 | ) 159 | end 160 | 161 | all-idxs = list(1 2 3 4 5 6 7 8 9) 162 | used-idxs = keys(grid) 163 | available-spots = call(stdfu.filter all-idxs func(idx) not(in(used-idxs idx)) end) 164 | 165 | score = call(looper available-spots 'no-score') 166 | score 167 | end 168 | 169 | scores-for-availables = func(grid) 170 | proxy = func(item) 171 | ngrid = put(grid item O) 172 | is-end has-winner winner = call(has-game-ended ngrid): 173 | if( is-end 174 | if( has-winner 175 | if( eq(winner machine) 176 | 10 177 | minus(0 10) 178 | ) 179 | 0 180 | ) 181 | call(minmax ngrid player 1) 182 | ) 183 | end 184 | 185 | looper = func(avail result) 186 | while( not(empty(avail)) 187 | rest(avail) 188 | put(result head(avail) call(proxy head(avail))) 189 | result 190 | ) 191 | end 192 | 193 | all-idxs = list(1 2 3 4 5 6 7 8 9) 194 | used-idxs = keys(grid) 195 | available-spots = call(stdfu.filter all-idxs func(idx) not(in(used-idxs idx)) end) 196 | call(looper available-spots map()) 197 | end 198 | 199 | #--- players-move 200 | players-move = proc(grid) 201 | mark-move = func(index-value) 202 | is-valid-index = and( 203 | lt(index-value 10) 204 | gt(index-value 0) 205 | ) 206 | cond( 207 | not(is-valid-index) list(grid false) 208 | in(grid index-value) list(grid false) 209 | list(put(grid index-value X) true) 210 | ) 211 | end 212 | 213 | ask-move = proc() 214 | _ = call(print-grid grid) 215 | _ = call(stdio.printline ' What is your move ? ') 216 | index = conv(call(stdio.readinput) 'int') 217 | if( eq(type(index) 'string') 218 | list(grid false) 219 | call(mark-move index) 220 | ) 221 | end 222 | 223 | if( eq(len(grid) 8) # there's only one possibility 224 | call(proc() 225 | used-spots = keys(grid) 226 | missing-spot = call(stdfu.filter list(1 2 3 4 5 6 7 8 9) func(idx) not(in(used-spots idx)) end): 227 | call(mark-move missing-spot) 228 | end) 229 | 230 | call(proc() 231 | ngrid is-valid-move = call(ask-move): 232 | _ = if(not(is-valid-move) call(stdio.printf '\n...invalid input, please retry...\n') true) 233 | while( not(is-valid-move) list(ngrid true) ) 234 | end) 235 | ) 236 | end 237 | 238 | #--- machines-move 239 | machines-move = proc(source-grid machine-mark) 240 | decide-move = func() 241 | score-map = call(scores-for-availables source-grid) 242 | grouped = call(stdfu.group-by score-map func(k v) list(v k) end) 243 | cond( 244 | in(grouped 10) head(get(grouped 10)) 245 | in(grouped 0) head(get(grouped 0)) 246 | in(grouped minus(0 10)) head(get(grouped minus(0 10))) 247 | error('something wrong...' grouped) 248 | ) 249 | end 250 | 251 | decide-first = func() 252 | cond( 253 | not(in(source-grid 5)) 5 254 | 1 255 | ) 256 | end 257 | 258 | machines-index = if( eq(len(source-grid) 1) 259 | call(decide-first) 260 | call(decide-move) 261 | ) 262 | next-grid = put(source-grid machines-index machine-mark) 263 | list(next-grid true) 264 | end 265 | 266 | #--- print-error 267 | print-error = proc() 268 | call(stdio.printline 'Error, game end.') 269 | end 270 | 271 | #--- play 272 | play = proc(grid next-to-play) 273 | 274 | #--- get-result-printout 275 | get-result-printout = func(has-winner winner) 276 | winner-name = case( winner 277 | player 'You' 278 | machine 'Me' 279 | 'No winner' 280 | ) 281 | if( has-winner 282 | sprintf('\n Winner is %s ! \n' winner-name) 283 | sprintf('\n Game even. \n') 284 | ) 285 | end 286 | 287 | next-grid ok = case( next-to-play 288 | player call(players-move grid) 289 | machine call(machines-move grid O) 290 | ): 291 | other-in-turn = case( next-to-play 292 | player machine 293 | machine player 294 | ) 295 | if( ok 296 | call(proc() 297 | is-end has-winner winner = call(has-game-ended next-grid): 298 | if( is-end 299 | call(proc() 300 | _ = call(print-grid next-grid) 301 | call(get-result-printout has-winner winner) 302 | end) 303 | call(play next-grid other-in-turn) 304 | ) 305 | end) 306 | call(print-error) 307 | ) 308 | end 309 | 310 | #--- main 311 | main = proc() 312 | _ = call(stdio.printf ' Moves are given with numbers in range 1-9\n') 313 | grid = call(get-initial-grid) 314 | call(play grid player) 315 | end 316 | 317 | endns 318 | -------------------------------------------------------------------------------- /examples/todo_client.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | import stdio 5 | import stdhttp 6 | import stdjson 7 | import stdbytes 8 | 9 | import todo_common 10 | 11 | # HTTP status codes 12 | Status-OK = 200 # OK HTTP status code 13 | Status-Bad-Request = 400 # Bad Request HTTP status code 14 | Status-Internal-Server-Error = 500 # Internal Server Error HTTP status code 15 | 16 | # none is value which content does not matter 17 | none = 'whatever' 18 | 19 | # Server URL 20 | server-endpoint = sprintf('http://localhost%s/items' call(todo_common.get-port)) 21 | 22 | # Printing put/del result 23 | print-result = proc(response) 24 | code = get(response 'status-code') 25 | text = get(response 'status-text') 26 | case( code 27 | Status-OK call(stdio.printline 'ok') 28 | call(stdio.printline sprintf('error: %d, %s: %s' code text call(stdbytes.string get(response 'body')))) 29 | ) 30 | end 31 | 32 | # Inquires items from server 33 | get-items = proc() 34 | print-inq-result = proc(data) 35 | import stdfu 36 | 37 | item-form = func(item) 38 | item-formatter = func(item-as-map out) 39 | key val = item-as-map: 40 | sprintf('%s\n - %s : %v' out key val) 41 | end 42 | call(stdfu.foreach keyvals(item) item-formatter '') 43 | end 44 | 45 | out = call(stdfu.foreach data func(item output) plus(output '\n item: ' call(item-form item) '\n') end '') 46 | call(stdio.printline out) 47 | end 48 | 49 | result = call(stdhttp.do 'GET' server-endpoint map()) 50 | statuscode = get(result 'status-code') 51 | case( statuscode 52 | Status-OK call(print-inq-result last(call(stdjson.decode get(result 'body'))) ) 53 | sprintf('error from server: %d : %s' statuscode get(result 'status-text')) 54 | ) 55 | end 56 | 57 | # Sends new item to server 58 | put-item = proc(cmd input) 59 | send-item-to-server = proc(body-content) 60 | header = map('Content-Type' 'application/json') 61 | response = call(stdhttp.do 'POST' server-endpoint header body-content) 62 | response 63 | end 64 | 65 | if( eq(cmd input) 66 | 'no item given' 67 | call(proc() 68 | json-item = slice(input len(cmd)) # remove command part from front of string 69 | response = call(send-item-to-server call(stdbytes.str-to-bytes json-item)) 70 | call(print-result response) 71 | end) 72 | ) 73 | end 74 | 75 | # Removes item from server 76 | del-item = proc(cmd input) 77 | import stdstr 78 | 79 | remove-item-from-server = proc(id) 80 | delete-endpoint = plus(server-endpoint '/id/' id) 81 | response = call(stdhttp.do 'DELETE' delete-endpoint map()) 82 | response 83 | end 84 | 85 | if( eq(cmd input) 86 | 'no id given' 87 | call(proc() 88 | param = slice(input len(cmd)) # remove command part from front of string 89 | id = call(stdstr.strip param) 90 | if( and( not(eq(id '')) call(stdstr.is-digit id) ) 91 | call(proc() 92 | response = call(remove-item-from-server id) 93 | call(print-result response) 94 | end) 95 | call(stdio.printline 'invalid id: ' id) 96 | ) 97 | end) 98 | ) 99 | end 100 | 101 | # Executes commands 102 | exec-cmd = proc(input) 103 | cmd = head(split(input)) 104 | _ = case( cmd 105 | 'get' call(get-items) 106 | 'put' call(put-item cmd input) 107 | 'del' call(del-item cmd input) 108 | none 109 | ) 110 | none 111 | end 112 | 113 | # Printing help for user 114 | print-help = proc() 115 | _ = call(stdio.printline 'Input can be:') 116 | _ = call(stdio.printline ' help -> prints this help') 117 | _ = call(stdio.printline ' ? -> prints this help') 118 | _ = call(stdio.printline ' quit -> exits repl') 119 | _ = call(stdio.printline ' exit -> exits repl') 120 | _ = call(stdio.printline '') 121 | _ = call(stdio.printline ' put -> adds item') 122 | _ = call(stdio.printline ' get -> prints all items') 123 | _ = call(stdio.printline ' del -> removes item') 124 | _ = call(stdio.printline '') 125 | true 126 | end 127 | 128 | # Main "loop" of program 129 | exec-commands = proc() 130 | _ = call(stdio.printout 'todo> ') 131 | input = call(stdio.readinput) 132 | continue = not(in(list('quit' 'exit') input)) 133 | 134 | result = case( input 135 | '' true 136 | 'quit' false 137 | 'exit' false 138 | 'help' call(print-help) 139 | '?' call(print-help) 140 | call(exec-cmd input) 141 | ) 142 | 143 | while(continue 'bye') 144 | end 145 | 146 | # main (entrypoint) 147 | main = proc() 148 | _ = call(stdio.printline 'Welcome to Todo application client') 149 | call(exec-commands) 150 | end 151 | 152 | endns 153 | -------------------------------------------------------------------------------- /examples/todo_common.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns todo_common 3 | 4 | # returns TCP port which is listened 5 | get-port = proc() 6 | import stdos 7 | 8 | found port = call(stdos.getenv 'TODO_SRV_PORT'): 9 | if(not(found) ':8003' plus(':' port)) 10 | end 11 | 12 | endns 13 | 14 | -------------------------------------------------------------------------------- /examples/todo_server.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | import stdhttp 5 | import stdlog 6 | 7 | # HTTP status codes 8 | Status-OK = 200 # OK HTTP status code 9 | Status-Bad-Request = 400 # Bad Request HTTP status code 10 | Status-Internal-Server-Error = 500 # Internal Server Error HTTP status code 11 | 12 | # Logger to use in this server 13 | log = call(stdlog.get-default-logger map('prefix' 'todo-server: ' 'date' true 'time' true)) 14 | 15 | # none is value which content does not matter 16 | none = 'whatever' 17 | 18 | # Starts server fiber, returns channel with which item server is accessed 19 | run-server = proc() 20 | import stdjson 21 | 22 | # helper for writing OK HTTP response 23 | write-success-response = proc(w data) 24 | _ = call(stdhttp.set-response-header w map('Content-Type' 'application/json')) 25 | status = call(stdhttp.write-response w Status-OK data) 26 | 27 | if( not(eq(status '')) 28 | call(log 'error: ' status) 29 | none 30 | ) 31 | end 32 | 33 | # helper for writing Error HTTP response 34 | write-error-response = proc(w error-text error-code) 35 | _ = call(stdhttp.set-response-header w map('Content-Type' 'application/json')) 36 | _ _ error-body = call(stdjson.encode error-text): 37 | call(stdhttp.write-response w error-code error-body) 38 | end 39 | 40 | # handles GET /items 41 | get-items = proc(w r items) 42 | ok err data = call(stdjson.encode items): 43 | _ = if( ok 44 | call(write-success-response w data) 45 | call(proc() 46 | _ = call(log 'error in encoding items: ' err) 47 | call(write-error-response w 'error in encoding items' Status-Internal-Server-Error) 48 | end) 49 | ) 50 | items 51 | end 52 | 53 | # handles POST /items 54 | put-item = proc(w r items id) 55 | ok err item = call(stdjson.decode get(r 'body')): 56 | _ = if( not(ok) 57 | call(proc() 58 | _ = call(log 'error in decoding: ' err) 59 | call(write-error-response w 'error in decoding request' Status-Bad-Request) 60 | end) 61 | none 62 | ) 63 | if(ok append(items put(item 'id' id)) items) 64 | end 65 | 66 | # handles DELETE /items/id/ 67 | del-item = proc(w r items) 68 | import stdfu 69 | 70 | item-id = conv(last(split(get(r 'URI') '/')) 'int') 71 | call(stdfu.filter items func(item) not( eq(get(item 'id') item-id) ) end) 72 | end 73 | 74 | # server fiber implementation 75 | item-server = proc(server-ch) 76 | serving = proc(items id-counter) 77 | while( true 78 | call(proc() 79 | w r replych = recv(server-ch): 80 | new-items = case( get(r 'method') 81 | 'GET' call(get-items w r items) 82 | 'POST' call(put-item w r items id-counter) 83 | 'DELETE' call(del-item w r items) 84 | items 85 | ) 86 | _ = send(replych none) 87 | new-items 88 | end) 89 | plus(id-counter 1) 90 | none 91 | ) 92 | end 93 | 94 | call(serving list() 100) 95 | end 96 | 97 | # create channel, start server fiber and return channel 98 | ch = chan() 99 | _ = spawn(call(item-server ch)) 100 | ch 101 | end 102 | 103 | # returns handler for /items (POST, GET, DELETE) 104 | get-item-handler = func(ch supported-methods) 105 | send-and-wait = proc(w r) 106 | replych = chan() 107 | _ = send(ch list(w r replych)) 108 | recv(replych) # need to wait reply, otherwise connection will be lost 109 | end 110 | 111 | proc(w r) 112 | if( in(supported-methods get(r 'method')) 113 | call(send-and-wait w r) 114 | none 115 | ) 116 | end 117 | end 118 | 119 | # HTTP handlers and server 120 | server-main = proc(mux item-server-ch) 121 | import todo_common 122 | 123 | _ = call(stdhttp.reg-handler mux '/items' call(get-item-handler item-server-ch list('GET' 'POST'))) 124 | _ = call(stdhttp.reg-handler mux '/items/id/' call(get-item-handler item-server-ch list('DELETE'))) 125 | _ = call(log '...listening...') 126 | retv = call(stdhttp.listen-and-serve mux call(todo_common.get-port)) 127 | plus('Quit serving: ' retv) 128 | end 129 | 130 | # main sets up item-server fiber and HTTP server and signal handler 131 | main = proc() 132 | import stdos 133 | 134 | mux = call(stdhttp.mux) 135 | sig-handler = proc(signum sigtext) 136 | _ = call(log 'signal received' signum sigtext) 137 | call(stdhttp.shutdown mux) 138 | end 139 | _ = call(stdos.reg-signal-handler sig-handler 2) 140 | 141 | call(server-main mux call(run-server)) 142 | end 143 | 144 | endns 145 | 146 | -------------------------------------------------------------------------------- /extensions/ext.go: -------------------------------------------------------------------------------- 1 | package extensions 2 | 3 | // CallMe is here just so that this package is included in FunL 4 | func CallMe() {} 5 | 6 | // Extension files (.go) can be added and have initializations in init -functions 7 | -------------------------------------------------------------------------------- /funl/fni.go: -------------------------------------------------------------------------------- 1 | package funl 2 | 3 | import ( 4 | "fmt" 5 | "plugin" 6 | ) 7 | 8 | type FNIApi interface { 9 | RegExtProc(ExtProcType, string) error 10 | } 11 | 12 | type FNIHandler struct { 13 | topFrame *Frame 14 | } 15 | 16 | func (fni *FNIHandler) RegExtProc(extProc ExtProcType, extProcName string) (err error) { 17 | nsSid := SymIDMap.Add(extProcName) 18 | fni.topFrame.Interpreter.NsDir.Put(nsSid, fni.topFrame) 19 | 20 | epVal := Value{Kind: ExtProcValue, Data: extProc} 21 | item := &Item{Type: ValueItem, Data: epVal} 22 | err = fni.topFrame.Syms.Add(extProcName, item) 23 | return 24 | } 25 | 26 | type ExtSetupHandler func(FNIApi) error 27 | 28 | func SetupExtModule(targetPath string, interpreter *Interpreter) (topFrame *Frame, err error) { 29 | var plug *plugin.Plugin 30 | plug, err = plugin.Open(targetPath) 31 | if err != nil { 32 | err = fmt.Errorf("Plugin file could not be read: %s: %v", targetPath, err) 33 | return 34 | } 35 | v, err := plug.Lookup("Setup") 36 | if err != nil { 37 | err = fmt.Errorf("Setup function not found in plugin: %s: %v", targetPath, err) 38 | return 39 | } 40 | setupHandler := v.(func(FNIApi) error) 41 | 42 | topFrame = &Frame{ 43 | Syms: NewSymt(), 44 | OtherNS: make(map[SymID]ImportInfo), 45 | Imported: make(map[SymID]*Frame), 46 | Interpreter: interpreter, 47 | } 48 | napi := &FNIHandler{topFrame: topFrame} 49 | err = setupHandler(napi) 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /funl/lexer_test.go: -------------------------------------------------------------------------------- 1 | package funl 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func lextester(t *testing.T, text string, ttokens []token) { 9 | lexer := newTokenizer(NewDefaultOperators()) 10 | tokens, err := lexer.scan(text) 11 | if err != nil { 12 | t.Fatalf("error : %v", err) 13 | } 14 | if l1, l2 := len(tokens), len(ttokens); l1 != l2 { 15 | t.Log(tokens) 16 | t.Fatalf("wrong (%v)(%v)", l1, l2) 17 | return 18 | } 19 | for i, token := range ttokens { 20 | if tokens[i].Type != token.Type { 21 | t.Logf("Tokens: %#v", tokens) 22 | t.Fatalf("Unexpected type (%d): %v, %v (%s)(%s)", i, tokens[i].Type, token.Type, tokens[i].Value, token.Value) 23 | } 24 | if tokens[i].Value != token.Value { 25 | t.Logf("Tokens: %#v", tokens) 26 | t.Fatalf("Unexpected value (%d): %s, %s", i, tokens[i].Value, token.Value) 27 | } 28 | } 29 | } 30 | 31 | func TestJustSymbols(t *testing.T) { 32 | text := "2010" 33 | tokens := []token{ 34 | token{ 35 | Type: tokenNumber, 36 | Value: "2010", 37 | }, 38 | } 39 | lextester(t, text, tokens) 40 | text = "dum_name" 41 | tokens = []token{ 42 | token{ 43 | Type: tokenSymbol, 44 | Value: "dum_name", 45 | }, 46 | } 47 | lextester(t, text, tokens) 48 | } 49 | 50 | func TestEmptyInput(t *testing.T) { 51 | lexer := newTokenizer(NewDefaultOperators()) 52 | tokens, err := lexer.scan("") 53 | if err != nil { 54 | t.Fatalf("error : %v", err) 55 | } 56 | if l := len(tokens); l != 0 { 57 | t.Fatalf("should be empty (%d)", l) 58 | } 59 | } 60 | 61 | func TestNonAllowedChar(t *testing.T) { 62 | lexer := newTokenizer(NewDefaultOperators()) 63 | _, err := lexer.scan("eq(dum.xyz, ? 'any text')") 64 | if err == nil { 65 | t.Fatalf("should fail") 66 | } 67 | } 68 | 69 | func TestLexBasic(t *testing.T) { 70 | text := "not(dum.sub, 'aabbcc text'any_more'what text')" 71 | tokens := []token{ 72 | token{ 73 | Type: tokenSymbol, 74 | Value: "not", 75 | }, 76 | token{ 77 | Type: tokenOpenBracket, 78 | Value: "(", 79 | }, 80 | token{ 81 | Type: tokenSymbol, 82 | Value: "dum", 83 | }, 84 | token{ 85 | Type: tokenDot, 86 | Value: ".", 87 | }, 88 | token{ 89 | Type: tokenSymbol, 90 | Value: "sub", 91 | }, 92 | token{ 93 | Type: tokenComma, 94 | Value: ",", 95 | }, 96 | token{ 97 | Type: tokenString, 98 | Value: "aabbcc text", 99 | }, 100 | token{ 101 | Type: tokenSymbol, 102 | Value: "any_more", 103 | }, 104 | token{ 105 | Type: tokenString, 106 | Value: "what text", 107 | }, 108 | token{ 109 | Type: tokenClosingBracket, 110 | Value: ")", 111 | }, 112 | } 113 | lextester(t, text, tokens) 114 | } 115 | 116 | func TestLexNumbers(t *testing.T) { 117 | text := "not(2007, 'dum, 2010 text', value1, value2)" 118 | tokens := []token{ 119 | token{ 120 | Type: tokenSymbol, 121 | Value: "not", 122 | }, 123 | token{ 124 | Type: tokenOpenBracket, 125 | Value: "(", 126 | }, 127 | token{ 128 | Type: tokenNumber, 129 | Value: "2007", 130 | }, 131 | token{ 132 | Type: tokenComma, 133 | Value: ",", 134 | }, 135 | token{ 136 | Type: tokenString, 137 | Value: "dum, 2010 text", 138 | }, 139 | token{ 140 | Type: tokenComma, 141 | Value: ",", 142 | }, 143 | token{ 144 | Type: tokenSymbol, 145 | Value: "value1", 146 | }, 147 | token{ 148 | Type: tokenComma, 149 | Value: ",", 150 | }, 151 | token{ 152 | Type: tokenSymbol, 153 | Value: "value2", 154 | }, 155 | token{ 156 | Type: tokenClosingBracket, 157 | Value: ")", 158 | }, 159 | } 160 | lextester(t, text, tokens) 161 | } 162 | 163 | func TestVariousTokens(t *testing.T) { 164 | text := " gt ( or(.dum.xyz.sub, or( ' any (( ) and not, textTEXT ' , 2010, xy_z-201)) , not('xxx aa__-c-- zz ') " 165 | tokens := []token{ 166 | token{ 167 | Type: tokenSymbol, 168 | Value: "gt", 169 | }, 170 | token{ 171 | Type: tokenOpenBracket, 172 | Value: "(", 173 | }, 174 | token{ 175 | Type: tokenSymbol, 176 | Value: "or", 177 | }, 178 | token{ 179 | Type: tokenOpenBracket, 180 | Value: "(", 181 | }, 182 | token{ 183 | Type: tokenDot, 184 | Value: ".", 185 | }, 186 | token{ 187 | Type: tokenSymbol, 188 | Value: "dum", 189 | }, 190 | token{ 191 | Type: tokenDot, 192 | Value: ".", 193 | }, 194 | token{ 195 | Type: tokenSymbol, 196 | Value: "xyz", 197 | }, 198 | token{ 199 | Type: tokenDot, 200 | Value: ".", 201 | }, 202 | token{ 203 | Type: tokenSymbol, 204 | Value: "sub", 205 | }, 206 | token{ 207 | Type: tokenComma, 208 | Value: ",", 209 | }, 210 | token{ 211 | Type: tokenSymbol, 212 | Value: "or", 213 | }, 214 | token{ 215 | Type: tokenOpenBracket, 216 | Value: "(", 217 | }, 218 | token{ 219 | Type: tokenString, 220 | Value: " any (( ) and not, textTEXT ", 221 | }, 222 | token{ 223 | Type: tokenComma, 224 | Value: ",", 225 | }, 226 | token{ 227 | Type: tokenNumber, 228 | Value: "2010", 229 | }, 230 | token{ 231 | Type: tokenComma, 232 | Value: ",", 233 | }, 234 | token{ 235 | Type: tokenSymbol, 236 | Value: "xy_z-201", 237 | }, 238 | token{ 239 | Type: tokenClosingBracket, 240 | Value: ")", 241 | }, 242 | token{ 243 | Type: tokenClosingBracket, 244 | Value: ")", 245 | }, 246 | token{ 247 | Type: tokenComma, 248 | Value: ",", 249 | }, 250 | token{ 251 | Type: tokenSymbol, 252 | Value: "not", 253 | }, 254 | token{ 255 | Type: tokenOpenBracket, 256 | Value: "(", 257 | }, 258 | token{ 259 | Type: tokenString, 260 | Value: "xxx aa__-c-- zz ", 261 | }, 262 | token{ 263 | Type: tokenClosingBracket, 264 | Value: ")", 265 | }, 266 | } 267 | lextester(t, text, tokens) 268 | } 269 | 270 | func TestLexBool(t *testing.T) { 271 | text := " not( len(val1.sub, true), eq(false, val2.f2) ) " 272 | tokens := []token{ 273 | token{ 274 | Type: tokenSymbol, 275 | Value: "not", 276 | }, 277 | token{ 278 | Type: tokenOpenBracket, 279 | Value: "(", 280 | }, 281 | token{ 282 | Type: tokenSymbol, 283 | Value: "len", 284 | }, 285 | token{ 286 | Type: tokenOpenBracket, 287 | Value: "(", 288 | }, 289 | token{ 290 | Type: tokenSymbol, 291 | Value: "val1", 292 | }, 293 | token{ 294 | Type: tokenDot, 295 | Value: ".", 296 | }, 297 | token{ 298 | Type: tokenSymbol, 299 | Value: "sub", 300 | }, 301 | token{ 302 | Type: tokenComma, 303 | Value: ",", 304 | }, 305 | token{ 306 | Type: tokenTrue, 307 | Value: "true", 308 | }, 309 | token{ 310 | Type: tokenClosingBracket, 311 | Value: ")", 312 | }, 313 | token{ 314 | Type: tokenComma, 315 | Value: ",", 316 | }, 317 | token{ 318 | Type: tokenSymbol, 319 | Value: "eq", 320 | }, 321 | token{ 322 | Type: tokenOpenBracket, 323 | Value: "(", 324 | }, 325 | token{ 326 | Type: tokenFalse, 327 | Value: "false", 328 | }, 329 | token{ 330 | Type: tokenComma, 331 | Value: ",", 332 | }, 333 | token{ 334 | Type: tokenSymbol, 335 | Value: "val2", 336 | }, 337 | token{ 338 | Type: tokenDot, 339 | Value: ".", 340 | }, 341 | token{ 342 | Type: tokenSymbol, 343 | Value: "f2", 344 | }, 345 | token{ 346 | Type: tokenClosingBracket, 347 | Value: ")", 348 | }, 349 | token{ 350 | Type: tokenClosingBracket, 351 | Value: ")", 352 | }, 353 | } 354 | lextester(t, text, tokens) 355 | } 356 | 357 | func TestTokenConvToStr(t *testing.T) { 358 | if tokStr := tokenAsStr(tokenSymbol); tokStr != "tokenSymbol" { 359 | t.Fatalf("Unexpected (%s)", tokStr) 360 | } 361 | if tokStr := tokenAsStr(tokenType(100)); tokStr != "Unknown (100)" { 362 | t.Fatalf("Unexpected (%s)", tokStr) 363 | } 364 | if s := fmt.Sprintf("%s", tokenSymbol); s != "tokenSymbol" { 365 | t.Fatalf("Unexpected (%s)", s) 366 | } 367 | if s := fmt.Sprintf("%#v", tokenSymbol); s != "tokenSymbol" { 368 | t.Fatalf("Unexpected (%s)", s) 369 | } 370 | } 371 | 372 | func TestLexStringNotComplete(t *testing.T) { 373 | lexer := newTokenizer(NewDefaultOperators()) 374 | _, err := lexer.scan("'string not completed ok") 375 | if err == nil { 376 | t.Fatalf("Should not be ok") 377 | } 378 | } 379 | 380 | func TestEscapingSubStr(t *testing.T) { 381 | bq := `\` 382 | text := "'any " + bq + "'sub dum" + bq + "'text'" 383 | tokens := []token{ 384 | token{ 385 | Type: tokenString, 386 | Value: "any 'sub dum'text", 387 | }, 388 | } 389 | lextester(t, text, tokens) 390 | text = "'any " + bq + bq + "sub dum" + bq + bq + "text'" 391 | tokens = []token{ 392 | token{ 393 | Type: tokenString, 394 | Value: "any " + bq + "sub dum" + bq + "text", 395 | }, 396 | } 397 | lextester(t, text, tokens) 398 | } 399 | 400 | func TestExpander(t *testing.T) { 401 | text := "xyz: dum" 402 | tokens := []token{ 403 | token{ 404 | Type: tokenSymbol, 405 | Value: "xyz", 406 | }, 407 | token{ 408 | Type: tokenExpander, 409 | Value: ":", 410 | }, 411 | token{ 412 | Type: tokenSymbol, 413 | Value: "dum", 414 | }, 415 | } 416 | lextester(t, text, tokens) 417 | } 418 | 419 | func TestLineComments(t *testing.T) { 420 | text := "abc efg # this is line comment\n hmm" 421 | tokens := []token{ 422 | token{ 423 | Type: tokenSymbol, 424 | Value: "abc", 425 | }, 426 | token{ 427 | Type: tokenSymbol, 428 | Value: "efg", 429 | }, 430 | token{ 431 | Type: tokenLineComment, 432 | Value: " this is line comment ", 433 | }, 434 | token{ 435 | Type: tokenSymbol, 436 | Value: "hmm", 437 | }, 438 | } 439 | lextester(t, text, tokens) 440 | 441 | text = "abc efg # this is line comment" 442 | tokens = []token{ 443 | token{ 444 | Type: tokenSymbol, 445 | Value: "abc", 446 | }, 447 | token{ 448 | Type: tokenSymbol, 449 | Value: "efg", 450 | }, 451 | token{ 452 | Type: tokenLineComment, 453 | Value: " this is line comment", 454 | }, 455 | } 456 | lextester(t, text, tokens) 457 | 458 | text = "# this is line comment" 459 | tokens = []token{ 460 | token{ 461 | Type: tokenLineComment, 462 | Value: " this is line comment", 463 | }, 464 | } 465 | lextester(t, text, tokens) 466 | } 467 | 468 | func TestMultiLineComments(t *testing.T) { 469 | text := "abc efg /* multi line\ncomment is \nhere */ hmm" 470 | 471 | lexer := newTokenizer(NewDefaultOperators()) 472 | tokens, err := lexer.scan(text) 473 | if err != nil { 474 | t.Fatalf("error : %v", err) 475 | } 476 | t.Logf("Tokens: %#v", tokens) 477 | } 478 | -------------------------------------------------------------------------------- /funl_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anssihalmeaho/funl/1533584cbea85552815144897fd3f9f13f15ad08/funl_logo.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/anssihalmeaho/funl 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /hellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anssihalmeaho/funl/1533584cbea85552815144897fd3f9f13f15ad08/hellow.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "runtime" 9 | "runtime/debug" 10 | "runtime/pprof" 11 | 12 | "github.com/anssihalmeaho/funl/extensions" 13 | "github.com/anssihalmeaho/funl/funl" 14 | "github.com/anssihalmeaho/funl/std" 15 | ) 16 | 17 | //go:generate go run ./stdfun/stdfun_generator.go 18 | 19 | const doProfiling = false 20 | const doStackMeas = false 21 | const doMemMeas = false 22 | 23 | func main() { 24 | extensions.CallMe() 25 | 26 | if doProfiling { 27 | f, err := os.Create("fup.prof") 28 | if err != nil { 29 | fmt.Println("Error in profile setup: " + err.Error()) 30 | } 31 | pprof.StartCPUProfile(f) 32 | defer pprof.StopCPUProfile() 33 | } 34 | 35 | _, goBTset := os.LookupEnv("FUNLGOBACKTRACE") 36 | defer func() { 37 | if r := recover(); r != nil { 38 | fmt.Println("Runtime error: ", r) 39 | if goBTset { 40 | debug.PrintStack() 41 | } 42 | } 43 | }() 44 | 45 | var err error 46 | var fargs string 47 | flag.StringVar(&fargs, "args", "", fmt.Sprintf("arguments for %s", os.Args[0])) 48 | var name string 49 | flag.StringVar(&name, "name", "main", "function or procedure to be evaluated (main is default)") 50 | var modName string 51 | flag.StringVar(&modName, "mod", "main", "module (namespace) to be evaluated (main is default)") 52 | replPtr := flag.Bool("repl", false, "starts REPL") 53 | silentPtr := flag.Bool("silent", false, "does not print result of evaluation when returning from program (not silent is default)") 54 | noPrintPtr := flag.Bool("noprint", false, "prevents printing from functions (by print-operator)") 55 | doRTEPrintPtr := flag.Bool("rteprint", false, "enables printing RTE location and scope") 56 | packagePtr := flag.Bool("package", false, "source file is package") 57 | var evalStr string 58 | flag.StringVar(&evalStr, "eval", "", "evaluate expression") 59 | var importPackageName string 60 | flag.StringVar(&importPackageName, "import", "", "package from which imports are done") 61 | flag.Parse() 62 | 63 | if *noPrintPtr { 64 | funl.PrintingDisabledInFunctions = true 65 | } 66 | if *doRTEPrintPtr { 67 | funl.PrintingRTElocationAndScopeEnabled = true 68 | } 69 | 70 | var parsedArgs []*funl.Item 71 | if fargs != "" { 72 | parsedArgs, err = funl.GetArgs(fargs) 73 | if err != nil { 74 | fmt.Println("Error in parsing arguments: ", err) 75 | return 76 | } 77 | } 78 | 79 | var skipSrcFile bool 80 | if modName != "main" { 81 | skipSrcFile = true 82 | } 83 | 84 | var content []byte 85 | var srcFileName string 86 | if !skipSrcFile { 87 | if *replPtr { 88 | srcFileName = "repl.fnl" 89 | } 90 | 91 | if evalStr != "" { 92 | content = []byte(fmt.Sprintf("ns main main = proc() %s end endns", evalStr)) 93 | name = "main" 94 | } else { 95 | others := flag.Args() 96 | if srcFileName == "" { 97 | if len(others) != 1 { 98 | fmt.Println("Source file not given correctly") 99 | return 100 | } 101 | srcFileName = others[0] 102 | } 103 | if srcFileName == "repl.fnl" { 104 | content = []byte(funl.GetReplCode()) 105 | } else if *packagePtr { 106 | // none 107 | } else { 108 | content, err = ioutil.ReadFile(srcFileName) 109 | if err != nil { 110 | fmt.Println(fmt.Sprintf("Source file reading failed: %v", err)) 111 | return 112 | } 113 | } 114 | } 115 | } else { 116 | srcFileName = modName 117 | var argStr string 118 | if fargs != "" { 119 | argStr = "," + fargs 120 | } 121 | content = []byte(fmt.Sprintf("ns main import %s main = proc() call(%s.%s%s) end endns", modName, modName, name, argStr)) 122 | name = "main" 123 | } 124 | 125 | var retValue funl.Value 126 | if *packagePtr { 127 | retValue, err = funl.FunlMainWithPackage(parsedArgs, name, srcFileName, std.InitSTD) 128 | } else if importPackageName != "" { 129 | retValue, err = funl.FunlMainWithPackImport(importPackageName, string(content), parsedArgs, name, srcFileName, std.InitSTD) 130 | } else { 131 | retValue, err = funl.FunlMainWithArgs(string(content), parsedArgs, name, srcFileName, std.InitSTD) 132 | } 133 | if err != nil { 134 | fmt.Println(fmt.Sprintf("Error: %v", err)) 135 | return 136 | } 137 | if !*replPtr { 138 | if !*silentPtr { 139 | fmt.Println(fmt.Sprintf("%#v", retValue)) 140 | } 141 | } 142 | 143 | if doStackMeas { 144 | var mems runtime.MemStats 145 | runtime.ReadMemStats(&mems) 146 | fmt.Println("in use : ", mems.StackInuse) 147 | fmt.Println("stack sys: ", mems.StackSys) 148 | } 149 | if doMemMeas { 150 | var m runtime.MemStats 151 | runtime.ReadMemStats(&m) 152 | fmt.Printf("\nAlloc = %v MB", transformBytesToMegaBytes(m.Alloc)) 153 | fmt.Printf("\tTotalAlloc = %v MB", transformBytesToMegaBytes(m.TotalAlloc)) 154 | fmt.Printf("\tSys = %v MB", transformBytesToMegaBytes(m.Sys)) 155 | fmt.Printf("\tNumGC = %v\n", m.NumGC) 156 | } 157 | } 158 | 159 | func transformBytesToMegaBytes(b uint64) uint64 { 160 | return b / 1024 / 1024 161 | } 162 | -------------------------------------------------------------------------------- /pmap/pmap.go: -------------------------------------------------------------------------------- 1 | package pmap 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | //RED represents red color of node 10 | RED = 0 11 | //BLACK represents black color of node 12 | BLACK = 1 13 | ) 14 | 15 | //MKey is int 16 | type MKey int 17 | 18 | //MValue is map value 19 | type MValue interface{} 20 | 21 | //Node is node of tree 22 | type Node struct { 23 | Left *Node 24 | Right *Node 25 | Color int 26 | Key MKey 27 | Val MValue 28 | } 29 | 30 | //RBMap is Red-Black tree 31 | type RBMap struct { 32 | Root *Node 33 | itemCount int 34 | Handler NodeHandler 35 | } 36 | 37 | //Get finds item from map 38 | func (rbm *RBMap) Get(key MKey) (MValue, bool) { 39 | if rbm.IsEmpty() { 40 | return nil, false 41 | } 42 | return find(rbm.Root, key) 43 | } 44 | 45 | //IsEmpty is true if map is empty, otherwise true 46 | func (rbm *RBMap) IsEmpty() bool { 47 | if rbm.Root == nil { 48 | return true 49 | } 50 | return false 51 | } 52 | 53 | func printNext(node *Node, depth int, direction string) { 54 | indent := strings.Repeat("..", depth) 55 | color := "RED" 56 | if node.Color == BLACK { 57 | color = "BLACK" 58 | } 59 | fmt.Println(fmt.Sprintf("%s%s %p: key=%d, val=%v, colour=%s", indent, direction, node, node.Key, node.Val, color)) 60 | if node.Left != nil { 61 | printNext(node.Left, depth+1, "LEFT ") 62 | } 63 | if node.Right != nil { 64 | printNext(node.Right, depth+1, "RIGHT") 65 | } 66 | } 67 | 68 | //Count return number of items in RBMap 69 | func (rbm *RBMap) Count() int { 70 | return rbm.itemCount 71 | } 72 | 73 | //Print prints map 74 | func (rbm *RBMap) Print() { 75 | if rbm.IsEmpty() { 76 | fmt.Println("Empty") 77 | return 78 | } 79 | printNext(rbm.Root, 0, "ROOT ") 80 | } 81 | 82 | //Equals return true if maps are equal, false otherwise 83 | func (rbm *RBMap) Equals(other *RBMap) bool { 84 | if rbm == other { 85 | return true 86 | } 87 | if rbm.IsEmpty() && other.IsEmpty() { 88 | return true 89 | } 90 | if rbm.IsEmpty() || other.IsEmpty() { 91 | return false 92 | } 93 | if rbm.Count() != other.Count() { 94 | return false 95 | } 96 | m1 := make(map[MKey]MValue) 97 | m2 := make(map[MKey]MValue) 98 | getKVs(rbm.Root, &m1) 99 | getKVs(other.Root, &m2) 100 | 101 | isSubsetOf := func(set1, set2 map[MKey]MValue) bool { 102 | for k, v := range set1 { 103 | if v2, found := set2[k]; !found { 104 | return false 105 | } else if v != v2 { // equality check might depend... 106 | return false 107 | } 108 | } 109 | return true 110 | } 111 | return isSubsetOf(m1, m2) && isSubsetOf(m2, m1) 112 | } 113 | 114 | func getKVs(node *Node, m *map[MKey]MValue) { 115 | (*m)[node.Key] = node.Val 116 | if node.Right != nil { 117 | getKVs(node.Right, m) 118 | } 119 | if node.Left != nil { 120 | getKVs(node.Left, m) 121 | } 122 | } 123 | 124 | func getNextKey(node *Node, keys *[]MKey) { 125 | *keys = append(*keys, node.Key) 126 | if node.Right != nil { 127 | getNextKey(node.Right, keys) 128 | } 129 | if node.Left != nil { 130 | getNextKey(node.Left, keys) 131 | } 132 | } 133 | 134 | //Keys returns list of all keys in map 135 | func (rbm *RBMap) Keys() (keys []MKey) { 136 | if rbm.IsEmpty() { 137 | return 138 | } 139 | getNextKey(rbm.Root, &keys) 140 | return 141 | } 142 | 143 | func getNextValue(node *Node, values *[]MValue) { 144 | *values = append(*values, node.Val) 145 | if node.Right != nil { 146 | getNextValue(node.Right, values) 147 | } 148 | if node.Left != nil { 149 | getNextValue(node.Left, values) 150 | } 151 | } 152 | 153 | //Values returns list of all values in map 154 | func (rbm *RBMap) Values() (values []MValue) { 155 | if rbm.IsEmpty() { 156 | return 157 | } 158 | getNextValue(rbm.Root, &values) 159 | return 160 | } 161 | 162 | func visitNext(node *Node, visitor func(*Node)) { 163 | visitor(node) 164 | if node.Right != nil { 165 | visitNext(node.Right, visitor) 166 | } 167 | if node.Left != nil { 168 | visitNext(node.Left, visitor) 169 | } 170 | } 171 | 172 | //VisitAll visits all nodes and calls handler 173 | func (rbm *RBMap) VisitAll(visitor func(*Node)) { 174 | if rbm.IsEmpty() { 175 | return 176 | } 177 | visitNext(rbm.Root, visitor) 178 | } 179 | 180 | /* 181 | func visitNextUntil(node *Node, visitor func(*Node) bool) bool { 182 | ret := visitor(node) 183 | if !ret { 184 | return false 185 | } 186 | if node.Right != nil { 187 | if enough := visitNextUntil(node.Right, visitor); enough { 188 | return false 189 | } 190 | } 191 | if node.Left != nil { 192 | if enough := visitNextUntil(node.Left, visitor); enough { 193 | return false 194 | } 195 | } 196 | return true 197 | } 198 | 199 | //VisitAllUntil visits all nodes and calls handler until handler returns false 200 | func (rbm *RBMap) VisitAllUntil(visitor func(*Node) bool) { 201 | if rbm.IsEmpty() { 202 | return 203 | } 204 | visitNextUntil(rbm.Root, visitor) 205 | } 206 | */ 207 | 208 | func find(node *Node, key MKey) (MValue, bool) { 209 | if node == nil { 210 | return nil, false 211 | } 212 | if key > node.Key { 213 | return find(node.Right, key) 214 | } else if key < node.Key { 215 | return find(node.Left, key) 216 | } else { 217 | if node.Key == key { 218 | return node.Val, true 219 | } 220 | return nil, false 221 | } 222 | } 223 | 224 | /* 225 | This is version of find which is optimized so that 226 | recursive function calls are replaced with 227 | "tail call recursion" -handling (just looping instead) 228 | (Go compiler doesn't implement tail call optimization at the moment). 229 | 230 | Anyway, this was not taken into use as measurements of execution times 231 | showed that no real improvement for performance was achieved... 232 | 233 | func find(node *Node, key MKey) (MValue, bool) { 234 | nextnode := node 235 | for { 236 | if nextnode == nil { 237 | return nil, false 238 | } 239 | if key > nextnode.Key { 240 | nextnode = nextnode.Right 241 | } else if key < nextnode.Key { 242 | nextnode = nextnode.Left 243 | } else { 244 | if nextnode.Key == key { 245 | return nextnode.Val, true 246 | } 247 | return nil, false 248 | } 249 | } 250 | } 251 | */ 252 | 253 | func balance(node *Node) *Node { 254 | if node.Color == RED { 255 | return node 256 | } 257 | if node.Right != nil && node.Right.Color == RED { 258 | rchild := node.Right 259 | if rchild.Right != nil && rchild.Right.Color == RED { 260 | moved := rchild.Left 261 | rchild.Right.Color = BLACK 262 | 263 | rchild.Left = node 264 | node.Right = moved 265 | return rchild 266 | } 267 | if rchild.Left != nil && rchild.Left.Color == RED { 268 | moved1 := rchild.Left.Right 269 | moved2 := rchild.Left.Left 270 | newtop := rchild.Left 271 | rchild.Color = BLACK 272 | 273 | newtop.Right = rchild 274 | newtop.Left = node 275 | node.Right = moved2 276 | rchild.Left = moved1 277 | return newtop 278 | } 279 | } 280 | if node.Left != nil && node.Left.Color == RED { 281 | lchild := node.Left 282 | if lchild.Left != nil && lchild.Left.Color == RED { 283 | moved := lchild.Right 284 | lchild.Left.Color = BLACK 285 | 286 | lchild.Right = node 287 | node.Left = moved 288 | return lchild 289 | } 290 | if lchild.Right != nil && lchild.Right.Color == RED { 291 | moved1 := lchild.Right.Right 292 | moved2 := lchild.Right.Left 293 | newtop := lchild.Right 294 | lchild.Color = BLACK 295 | 296 | newtop.Right = node 297 | newtop.Left = lchild 298 | node.Left = moved1 299 | lchild.Right = moved2 300 | return newtop 301 | } 302 | } 303 | return node 304 | } 305 | 306 | func insert(node *Node, key MKey, val MValue, nhandler NodeHandler) *Node { 307 | if node == nil { 308 | return &Node{ 309 | Key: key, 310 | Val: val, 311 | Color: RED, 312 | } 313 | } 314 | if key > node.Key { 315 | nodecopy := *node 316 | newnode := insert(nodecopy.Right, key, val, nhandler) 317 | nodecopy.Right = newnode 318 | return balance(&nodecopy) 319 | } else if key < node.Key { 320 | nodecopy := *node 321 | newnode := insert(nodecopy.Left, key, val, nhandler) 322 | nodecopy.Left = newnode 323 | return balance(&nodecopy) 324 | } else { 325 | nodecopy := nhandler.HandleSameKey(node, key, val) 326 | return nodecopy 327 | } 328 | } 329 | 330 | //Put puts value to map 331 | func (rbm *RBMap) Put(key MKey, val MValue) *RBMap { 332 | if rbm.IsEmpty() { 333 | rootNode := &Node{ 334 | Key: key, 335 | Val: val, 336 | Color: RED, 337 | } 338 | return &RBMap{Root: rootNode, itemCount: 1, Handler: rbm.Handler} 339 | } 340 | nodeCopy := *rbm.Root 341 | newnode := balance(insert(&nodeCopy, key, val, rbm.Handler)) 342 | newnode.Color = BLACK 343 | return &RBMap{Root: newnode, itemCount: rbm.itemCount + 1, Handler: rbm.Handler} 344 | } 345 | 346 | func copyPath(node *Node, key MKey, val MValue, nhandler NodeHandler) (*Node, bool) { 347 | if node == nil { 348 | return nil, false 349 | } 350 | if key > node.Key { 351 | nodecopy := *node 352 | newnode, found := copyPath(nodecopy.Right, key, val, nhandler) 353 | nodecopy.Right = newnode 354 | return &nodecopy, found 355 | } else if key < node.Key { 356 | nodecopy := *node 357 | newnode, found := copyPath(nodecopy.Left, key, val, nhandler) 358 | nodecopy.Left = newnode 359 | return &nodecopy, found 360 | } else { 361 | return nhandler.MarkDeletion(node, key, val) 362 | } 363 | } 364 | 365 | //Modify copies path with modified value 366 | func (rbm *RBMap) Modify(key MKey, val MValue) (*RBMap, bool) { 367 | if rbm.IsEmpty() { 368 | return rbm, false 369 | } 370 | nodeCopy := *rbm.Root 371 | newnode, found := copyPath(&nodeCopy, key, val, rbm.Handler) 372 | return &RBMap{Root: newnode, itemCount: rbm.itemCount, Handler: rbm.Handler}, found 373 | } 374 | 375 | //NodeHandler is interface 376 | type NodeHandler interface { 377 | HandleSameKey(*Node, MKey, MValue) *Node 378 | MarkDeletion(*Node, MKey, MValue) (*Node, bool) 379 | } 380 | 381 | //NewRBMapWithHandler returns new map with handler 382 | func NewRBMapWithHandler(nhandler NodeHandler) *RBMap { 383 | return &RBMap{Handler: nhandler} 384 | } 385 | 386 | //NewRBMap returns new map 387 | func NewRBMap() *RBMap { 388 | return &RBMap{} 389 | } 390 | -------------------------------------------------------------------------------- /std/std.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "github.com/anssihalmeaho/funl/funl" 5 | ) 6 | 7 | type stdFuncType func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) 8 | 9 | type stdFuncInfo struct { 10 | Name string 11 | Getter func(name string) stdFuncType 12 | IsFunction bool 13 | } 14 | 15 | func setSTDFunctions(topFrame *funl.Frame, stdModuleName string, stdFuncs []stdFuncInfo, interpreter *funl.Interpreter) (err error) { 16 | nsSid := funl.SymIDMap.Add(stdModuleName) 17 | interpreter.NsDir.Put(nsSid, topFrame) 18 | 19 | for _, v := range stdFuncs { 20 | extProc := funl.ExtProcType{ 21 | Impl: v.Getter(stdModuleName + ":" + v.Name), 22 | IsFunction: v.IsFunction, 23 | } 24 | epVal := funl.Value{Kind: funl.ExtProcValue, Data: extProc} 25 | item := &funl.Item{Type: funl.ValueItem, Data: epVal} 26 | err = topFrame.Syms.Add(v.Name, item) 27 | if err != nil { 28 | return 29 | } 30 | } 31 | return 32 | } 33 | 34 | // StdFuncType exposed 35 | type StdFuncType func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) 36 | 37 | // StdFuncInfo exposed 38 | type StdFuncInfo struct { 39 | Name string 40 | Getter func(name string) StdFuncType 41 | IsFunction bool 42 | } 43 | 44 | // SetSTDFunctions exposed 45 | func SetSTDFunctions(topFrame *funl.Frame, stdModuleName string, stdFuncs []StdFuncInfo, interpreter *funl.Interpreter) (err error) { 46 | nsSid := funl.SymIDMap.Add(stdModuleName) 47 | interpreter.NsDir.Put(nsSid, topFrame) 48 | 49 | for _, v := range stdFuncs { 50 | extProc := funl.ExtProcType{ 51 | Impl: v.Getter(stdModuleName + ":" + v.Name), 52 | IsFunction: v.IsFunction, 53 | } 54 | epVal := funl.Value{Kind: funl.ExtProcValue, Data: extProc} 55 | item := &funl.Item{Type: funl.ValueItem, Data: epVal} 56 | err = topFrame.Syms.Add(v.Name, item) 57 | if err != nil { 58 | return 59 | } 60 | } 61 | return 62 | } 63 | 64 | //InitSTD is used for initializing standard library 65 | func InitSTD(interpreter *funl.Interpreter) (err error) { 66 | inits := []func(*funl.Interpreter) error{ 67 | initSTDIO, 68 | initSTDTIME, 69 | initSTDBytes, 70 | initSTDFiles, 71 | initSTDJson, 72 | initSTDHttp, 73 | initSTDos, 74 | initSTDlog, 75 | initSTDStr, 76 | initSTDMath, 77 | initSTDAst, 78 | initSTDRPC, 79 | initSTDbase64, 80 | initSTDVar, 81 | initSTDRun, 82 | initSTDLex, 83 | } 84 | for _, initf := range inits { 85 | err = initf(interpreter) 86 | if err != nil { 87 | return 88 | } 89 | } 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /std/stdbase64.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/anssihalmeaho/funl/funl" 8 | ) 9 | 10 | func initSTDbase64(interpreter *funl.Interpreter) (err error) { 11 | stdModuleName := "stdbase64" 12 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 13 | stdFuncs := []stdFuncInfo{ 14 | { 15 | Name: "encode", 16 | Getter: getStdBase64encode, 17 | IsFunction: true, 18 | }, 19 | { 20 | Name: "decode", 21 | Getter: getStdBase64decode, 22 | IsFunction: true, 23 | }, 24 | } 25 | err = setSTDFunctions(topFrame, stdModuleName, stdFuncs, interpreter) 26 | return 27 | } 28 | 29 | // call(stdbase64.encode ) -> list(ok err ) 30 | func getStdBase64encode(name string) stdFuncType { 31 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 32 | if l := len(arguments); l != 1 { 33 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 34 | } 35 | if arguments[0].Kind != funl.OpaqueValue { 36 | funl.RunTimeError2(frame, "%s: assuming opaque bytearray", name) 37 | } 38 | bytearray, convOK := arguments[0].Data.(*OpaqueByteArray) 39 | var errStr string 40 | var str string 41 | if convOK { 42 | str = base64.StdEncoding.EncodeToString(bytearray.data) 43 | } else { 44 | errStr = "assuming bytearray" 45 | } 46 | values := []funl.Value{ 47 | { 48 | Kind: funl.BoolValue, 49 | Data: errStr == "", 50 | }, 51 | { 52 | Kind: funl.StringValue, 53 | Data: errStr, 54 | }, 55 | { 56 | Kind: funl.StringValue, 57 | Data: str, 58 | }, 59 | } 60 | retVal = funl.MakeListOfValues(frame, values) 61 | return 62 | } 63 | } 64 | 65 | // call(stdbase64.decode ) -> list(ok err ) 66 | func getStdBase64decode(name string) stdFuncType { 67 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 68 | if l := len(arguments); l != 1 { 69 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 70 | } 71 | if arguments[0].Kind != funl.StringValue { 72 | funl.RunTimeError2(frame, "%s: assuming string", name) 73 | } 74 | bytedata, err := base64.StdEncoding.DecodeString(arguments[0].Data.(string)) 75 | var errStr string 76 | var bytearray *OpaqueByteArray 77 | if err != nil { 78 | errStr = fmt.Sprintf("Error in decoding: %v", err) 79 | bytearray = &OpaqueByteArray{data: []byte{}} 80 | } else { 81 | bytearray = &OpaqueByteArray{data: bytedata} 82 | } 83 | values := []funl.Value{ 84 | { 85 | Kind: funl.BoolValue, 86 | Data: err == nil, 87 | }, 88 | { 89 | Kind: funl.StringValue, 90 | Data: errStr, 91 | }, 92 | { 93 | Kind: funl.OpaqueValue, 94 | Data: bytearray, 95 | }, 96 | } 97 | retVal = funl.MakeListOfValues(frame, values) 98 | return 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /std/stdbytes.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/anssihalmeaho/funl/funl" 8 | ) 9 | 10 | func initSTDBytes(interpreter *funl.Interpreter) (err error) { 11 | stdModuleName := "stdbytes" 12 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 13 | stdBytesFuncs := []stdFuncInfo{ 14 | { 15 | Name: "str-to-bytes", 16 | Getter: getStdBytesStrToBytes, 17 | IsFunction: true, 18 | }, 19 | { 20 | Name: "string", 21 | Getter: getStdBytesString, 22 | IsFunction: true, 23 | }, 24 | { 25 | Name: "count", 26 | Getter: getStdBytesCount, 27 | IsFunction: true, 28 | }, 29 | { 30 | Name: "new", 31 | Getter: getStdBytesNew, 32 | IsFunction: true, 33 | }, 34 | { 35 | Name: "split-by", 36 | Getter: getStdBytesSplitBy, 37 | IsFunction: true, 38 | }, 39 | { 40 | Name: "as-list", 41 | Getter: getStdBytesAsList, 42 | IsFunction: true, 43 | }, 44 | } 45 | err = setSTDFunctions(topFrame, stdModuleName, stdBytesFuncs, interpreter) 46 | 47 | nlVal := &OpaqueByteArray{data: []byte("\n")} 48 | item := &funl.Item{Type: funl.ValueItem, Data: funl.Value{Kind: funl.OpaqueValue, Data: nlVal}} 49 | err = topFrame.Syms.Add("nl", item) 50 | if err != nil { 51 | return 52 | } 53 | 54 | return 55 | } 56 | 57 | type OpaqueByteArray struct { 58 | data []byte 59 | } 60 | 61 | // Creates new OpaqueByteArray 62 | func NewOpaqueByteArray(data []byte) *OpaqueByteArray { 63 | return &OpaqueByteArray{data: data} 64 | } 65 | 66 | // GetBytes provides Go API for reading byte slice 67 | func (ob *OpaqueByteArray) GetBytes() []byte { 68 | return ob.data 69 | } 70 | 71 | func (ob *OpaqueByteArray) TypeName() string { 72 | return "bytearray" 73 | } 74 | 75 | func (ob *OpaqueByteArray) Str() string { 76 | s := "" 77 | for _, v := range ob.data { 78 | s += fmt.Sprintf("%x ", v) 79 | } 80 | if s != "" { 81 | s = s[:len(s)-1] 82 | } 83 | return fmt.Sprintf("bytearray(%s)", s) 84 | } 85 | 86 | func (ob *OpaqueByteArray) Equals(with funl.OpaqueAPI) bool { 87 | other, ok := with.(*OpaqueByteArray) 88 | if !ok { 89 | return false 90 | } 91 | return bytes.Equal(ob.data, other.data) 92 | } 93 | 94 | func getStdBytesAsList(name string) stdFuncType { 95 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 96 | if l := len(arguments); l != 1 { 97 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 98 | } 99 | if arguments[0].Kind != funl.OpaqueValue { 100 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 101 | } 102 | byteArray, ok := arguments[0].Data.(*OpaqueByteArray) 103 | if !ok { 104 | funl.RunTimeError2(frame, "%s: argument is not bytearray value", name) 105 | } 106 | 107 | resultList := []funl.Value{} 108 | for _, byteVal := range byteArray.data { 109 | resultList = append(resultList, funl.Value{Kind: funl.IntValue, Data: int(byteVal)}) 110 | } 111 | retVal = funl.MakeListOfValues(frame, resultList) 112 | return 113 | } 114 | } 115 | 116 | func getStdBytesSplitBy(name string) stdFuncType { 117 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 118 | if l := len(arguments); l != 2 { 119 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need two", name, l) 120 | } 121 | if arguments[0].Kind != funl.OpaqueValue { 122 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 123 | } 124 | byteArray, ok := arguments[0].Data.(*OpaqueByteArray) 125 | if !ok { 126 | funl.RunTimeError2(frame, "%s: argument is not bytearray value", name) 127 | } 128 | if arguments[1].Kind != funl.OpaqueValue { 129 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 130 | } 131 | separatorByteArray, ok := arguments[1].Data.(*OpaqueByteArray) 132 | if !ok { 133 | funl.RunTimeError2(frame, "%s: argument is not bytearray value", name) 134 | } 135 | 136 | var resultList []funl.Value 137 | for _, bslice := range bytes.Split(byteArray.data, separatorByteArray.data) { 138 | resultList = append(resultList, funl.Value{Kind: funl.OpaqueValue, Data: &OpaqueByteArray{data: bslice}}) 139 | } 140 | retVal = funl.MakeListOfValues(frame, resultList) 141 | return 142 | } 143 | } 144 | 145 | func getStdBytesNew(name string) stdFuncType { 146 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 147 | if l := len(arguments); l != 1 { 148 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 149 | } 150 | if arguments[0].Kind != funl.ListValue { 151 | funl.RunTimeError2(frame, "%s: requires list as argument", name) 152 | } 153 | byteArray := &OpaqueByteArray{} 154 | ite := funl.NewListIterator(arguments[0]) 155 | for { 156 | val := ite.Next() 157 | if val == nil { 158 | break 159 | } 160 | switch val.Kind { 161 | case funl.IntValue: 162 | if num := val.Data.(int); num > 255 { 163 | funl.RunTimeError2(frame, "%s: expects int value less than 256 (%d)", name, num) 164 | } else if num < 0 { 165 | funl.RunTimeError2(frame, "%s: expects int value more than 0 (%d)", name, num) 166 | } else { 167 | byteVal := byte(num) 168 | byteArray.data = append(byteArray.data, byteVal) 169 | } 170 | case funl.StringValue: 171 | s := val.Data.(string) 172 | for _, byteVal := range []byte(s) { 173 | byteArray.data = append(byteArray.data, byteVal) 174 | } 175 | case funl.OpaqueValue: 176 | if byteArr, ok := val.Data.(*OpaqueByteArray); !ok { 177 | funl.RunTimeError2(frame, "%s: unsupported type: %s", name, val.Data) 178 | } else { 179 | for _, byteVal := range byteArr.data { 180 | byteArray.data = append(byteArray.data, byteVal) 181 | } 182 | } 183 | default: 184 | funl.RunTimeError2(frame, "%s: unsupported type", name) 185 | } 186 | } 187 | retVal = funl.Value{Kind: funl.OpaqueValue, Data: byteArray} 188 | return 189 | } 190 | } 191 | 192 | func getStdBytesStrToBytes(name string) stdFuncType { 193 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 194 | if l := len(arguments); l != 1 { 195 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 196 | } 197 | if arguments[0].Kind != funl.StringValue { 198 | funl.RunTimeError2(frame, "%s: requires string value", name) 199 | } 200 | strVal, ok := arguments[0].Data.(string) 201 | if !ok { 202 | funl.RunTimeError2(frame, "%s: argument is not string value", name) 203 | } 204 | buf := bytes.NewBufferString(strVal) 205 | retVal = funl.Value{Kind: funl.OpaqueValue, Data: &OpaqueByteArray{data: buf.Bytes()}} 206 | return 207 | } 208 | } 209 | 210 | func getStdBytesString(name string) stdFuncType { 211 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 212 | if l := len(arguments); l != 1 { 213 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 214 | } 215 | if arguments[0].Kind != funl.OpaqueValue { 216 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 217 | } 218 | byteArray, ok := arguments[0].Data.(*OpaqueByteArray) 219 | if !ok { 220 | funl.RunTimeError2(frame, "%s: argument is not bytearray value", name) 221 | } 222 | buf := bytes.NewBuffer(byteArray.data) 223 | str := buf.String() 224 | if str == "" { 225 | funl.RunTimeError2(frame, "%s: could not convert bytearray to string", name) 226 | } 227 | retVal = funl.Value{Kind: funl.StringValue, Data: str} 228 | return 229 | } 230 | } 231 | 232 | func getStdBytesCount(name string) stdFuncType { 233 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 234 | if l := len(arguments); l != 1 { 235 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 236 | } 237 | if arguments[0].Kind != funl.OpaqueValue { 238 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 239 | } 240 | byteArray, ok := arguments[0].Data.(*OpaqueByteArray) 241 | if !ok { 242 | funl.RunTimeError2(frame, "%s: argument is not bytearray value", name) 243 | } 244 | retVal = funl.Value{Kind: funl.IntValue, Data: len(byteArray.data)} 245 | return 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /std/stdio.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/anssihalmeaho/funl/funl" 9 | ) 10 | 11 | func initSTDIO(interpreter *funl.Interpreter) (err error) { 12 | stdModuleName := "stdio" 13 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 14 | stdIOFuncs := []stdFuncInfo{ 15 | { 16 | Name: "printf", 17 | Getter: getStdIOPrintf, 18 | }, 19 | { 20 | Name: "printout", 21 | Getter: getStdIOPrintout, 22 | }, 23 | { 24 | Name: "printline", 25 | Getter: getStdIOPrintline, 26 | }, 27 | { 28 | Name: "readinput", 29 | Getter: getStdIOReadinput, 30 | }, 31 | { 32 | Name: "printfline", 33 | Getter: getStdIOPrintfline, 34 | }, 35 | } 36 | err = setSTDFunctions(topFrame, stdModuleName, stdIOFuncs, interpreter) 37 | return 38 | } 39 | 40 | func getStdIOPrintf(name string) stdFuncType { 41 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 42 | var operands []*funl.Item 43 | for _, v := range arguments { 44 | operands = append(operands, &funl.Item{Type: funl.ValueItem, Data: v}) 45 | } 46 | formattedStrVal := funl.HandleSprintfOP(frame, operands) 47 | if formattedStrVal.Kind != funl.StringValue { 48 | funl.RunTimeError2(frame, "%s: assuming string (%#v)", name, formattedStrVal) 49 | } 50 | 51 | fmt.Printf(formattedStrVal.Data.(string)) 52 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 53 | return 54 | } 55 | } 56 | 57 | func getStdIOPrintfline(name string) stdFuncType { 58 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 59 | var operands []*funl.Item 60 | for _, v := range arguments { 61 | operands = append(operands, &funl.Item{Type: funl.ValueItem, Data: v}) 62 | } 63 | formattedStrVal := funl.HandleSprintfOP(frame, operands) 64 | if formattedStrVal.Kind != funl.StringValue { 65 | funl.RunTimeError2(frame, "%s: assuming string (%#v)", name, formattedStrVal) 66 | } 67 | 68 | fmt.Printf(formattedStrVal.Data.(string) + "\n") 69 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 70 | return 71 | } 72 | } 73 | 74 | func getStdIOPrintline(name string) stdFuncType { 75 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 76 | var s string 77 | for _, v := range arguments { 78 | var sval string 79 | switch v.Kind { 80 | case funl.StringValue: 81 | sval = v.Data.(string) 82 | default: 83 | sval = fmt.Sprintf("%v", v) 84 | } 85 | s += sval 86 | } 87 | fmt.Printf("%s\n", s) 88 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 89 | return 90 | } 91 | } 92 | 93 | func getStdIOPrintout(name string) stdFuncType { 94 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 95 | var s string 96 | for _, v := range arguments { 97 | var sval string 98 | switch v.Kind { 99 | case funl.StringValue: 100 | sval = v.Data.(string) 101 | default: 102 | sval = fmt.Sprintf("%v", v) 103 | } 104 | s += sval 105 | } 106 | fmt.Printf("%s", s) 107 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 108 | return 109 | } 110 | } 111 | 112 | func getStdIOReadinput(name string) stdFuncType { 113 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 114 | scanner := bufio.NewScanner(os.Stdin) 115 | scanner.Scan() 116 | retVal = funl.Value{Kind: funl.StringValue, Data: scanner.Text()} 117 | return 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /std/stdjson.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "math" 8 | "reflect" 9 | "strconv" 10 | 11 | "github.com/anssihalmeaho/funl/funl" 12 | ) 13 | 14 | func initSTDJson(interpreter *funl.Interpreter) (err error) { 15 | stdModuleName := "stdjson" 16 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 17 | stdFuncs := []stdFuncInfo{ 18 | { 19 | Name: "encode", 20 | Getter: getStdJSONencode, 21 | IsFunction: true, 22 | }, 23 | { 24 | Name: "decode", 25 | Getter: getStdJSONdecode, 26 | IsFunction: true, 27 | }, 28 | } 29 | err = setSTDFunctions(topFrame, stdModuleName, stdFuncs, interpreter) 30 | 31 | item := &funl.Item{Type: funl.ValueItem, Data: funl.Value{Kind: funl.OpaqueValue, Data: &OpaqueJSONnull{}}} 32 | err = topFrame.Syms.Add("null", item) 33 | if err != nil { 34 | return 35 | } 36 | 37 | return 38 | } 39 | 40 | type OpaqueJSONnull struct{} 41 | 42 | func (opa *OpaqueJSONnull) TypeName() string { 43 | return "json-null" 44 | } 45 | 46 | func (opa *OpaqueJSONnull) Str() string { 47 | return "json-null" 48 | } 49 | 50 | func (opa *OpaqueJSONnull) Equals(with funl.OpaqueAPI) bool { 51 | _, ok := with.(*OpaqueJSONnull) 52 | return ok 53 | } 54 | 55 | // encode() -> list(bool, string, opaque:bytearray) 56 | func getStdJSONencode(name string) stdFuncType { 57 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 58 | if l := len(arguments); l != 1 { 59 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 60 | } 61 | 62 | ok, errText, val := encodeJSON(name, frame, arguments[0]) 63 | if !ok { 64 | val = funl.Value{Kind: funl.StringValue, Data: ""} 65 | } 66 | values := []funl.Value{ 67 | { 68 | Kind: funl.BoolValue, 69 | Data: ok, 70 | }, 71 | { 72 | Kind: funl.StringValue, 73 | Data: errText, 74 | }, 75 | val, 76 | } 77 | retVal = funl.MakeListOfValues(frame, values) 78 | return 79 | } 80 | } 81 | 82 | // decode(opaque:bytearray) -> list(bool, string, ) 83 | func getStdJSONdecode(name string) stdFuncType { 84 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 85 | if l := len(arguments); l != 1 { 86 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 87 | } 88 | if arguments[0].Kind != funl.OpaqueValue { 89 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 90 | } 91 | byteArray, ok := arguments[0].Data.(*OpaqueByteArray) 92 | if !ok { 93 | funl.RunTimeError2(frame, "%s: argument is not bytearray value", name) 94 | } 95 | 96 | ok, errText, val := decodeJSON(name, frame, byteArray.data) 97 | if !ok { 98 | val = funl.Value{Kind: funl.StringValue, Data: ""} 99 | } 100 | values := []funl.Value{ 101 | { 102 | Kind: funl.BoolValue, 103 | Data: ok, 104 | }, 105 | { 106 | Kind: funl.StringValue, 107 | Data: errText, 108 | }, 109 | val, 110 | } 111 | retVal = funl.MakeListOfValues(frame, values) 112 | return 113 | } 114 | } 115 | 116 | func encodeJSON(name string, frame *funl.Frame, inValue funl.Value) (ok bool, errText string, val funl.Value) { 117 | defer func() { 118 | if r := recover(); r != nil { 119 | err, _ := r.(error) 120 | ok, errText = false, fmt.Sprintf("%s: %v", name, err) 121 | } 122 | }() 123 | 124 | resultAsBytes := traverseValuesEncode(frame, inValue, make([]byte, 0)) 125 | val = funl.Value{Kind: funl.OpaqueValue, Data: &OpaqueByteArray{data: resultAsBytes}} 126 | ok = true 127 | return 128 | } 129 | 130 | func decodeJSON(name string, frame *funl.Frame, indata []byte) (ok bool, errText string, val funl.Value) { 131 | defer func() { 132 | if r := recover(); r != nil { 133 | err, _ := r.(error) 134 | ok, errText = false, fmt.Sprintf("%s: %v", name, err) 135 | } 136 | }() 137 | 138 | var res interface{} 139 | d := json.NewDecoder(bytes.NewBuffer(indata)) 140 | d.UseNumber() 141 | if err := d.Decode(&res); err != nil { 142 | ok, errText = false, fmt.Sprintf("%s: error in unmarshal: %v", name, err) 143 | return 144 | } 145 | val = traverseValues(frame, res) 146 | ok = true 147 | return 148 | } 149 | 150 | func traverseValuesEncode(frame *funl.Frame, inValue funl.Value, prevsl []byte) (nextsl []byte) { 151 | if inValue.Kind == funl.OpaqueValue { 152 | _, isNullV := inValue.Data.(*OpaqueJSONnull) 153 | if isNullV { 154 | nextsl = append(prevsl, []byte("null")...) 155 | return 156 | } 157 | } 158 | switch inValue.Kind { 159 | 160 | case funl.BoolValue: 161 | if inValue.Data.(bool) { 162 | nextsl = append(prevsl, []byte("true")...) 163 | return 164 | } 165 | nextsl = append(prevsl, []byte("false")...) 166 | return 167 | 168 | case funl.StringValue: 169 | strVal := inValue.Data.(string) 170 | strAsBytes, err := json.Marshal(strVal) 171 | if err != nil { 172 | panic(err) 173 | } 174 | nextsl = append(prevsl, strAsBytes...) 175 | return 176 | 177 | case funl.IntValue: 178 | intVal := inValue.Data.(int) 179 | intAsBytes, err := json.Marshal(intVal) 180 | if err != nil { 181 | panic(err) 182 | } 183 | nextsl = append(prevsl, intAsBytes...) 184 | return 185 | 186 | case funl.FloatValue: 187 | floatVal := inValue.Data.(float64) 188 | var floatAsBytes []byte 189 | if math.Trunc(floatVal) == floatVal { 190 | // its whole number 191 | floatAsBytes = []byte(fmt.Sprintf("%.1f", floatVal)) 192 | } else { 193 | floatAsBytes = []byte(strconv.FormatFloat(floatVal, 'f', -1, 64)) 194 | } 195 | nextsl = append(prevsl, floatAsBytes...) 196 | return 197 | 198 | case funl.ListValue: 199 | var listAsBytes []byte 200 | listAsBytes = append(listAsBytes, []byte("[")...) 201 | listIter := funl.NewListIterator(inValue) 202 | isFirstRound := true 203 | for { 204 | nextItem := listIter.Next() 205 | if nextItem == nil { 206 | break 207 | } 208 | if !isFirstRound { 209 | listAsBytes = append(listAsBytes, []byte(", ")...) 210 | } 211 | isFirstRound = false 212 | listAsBytes = traverseValuesEncode(frame, *nextItem, listAsBytes) 213 | } 214 | listAsBytes = append(listAsBytes, []byte("]")...) 215 | nextsl = append(prevsl, listAsBytes...) 216 | return 217 | 218 | case funl.MapValue: 219 | var mapAsBytes []byte 220 | mapAsBytes = append(mapAsBytes, []byte("{")...) 221 | keyvals := funl.HandleKeyvalsOP(frame, []*funl.Item{&funl.Item{Type: funl.ValueItem, Data: inValue}}) 222 | kvListIter := funl.NewListIterator(keyvals) 223 | isFirstRound := true 224 | for { 225 | nextKV := kvListIter.Next() 226 | if nextKV == nil { 227 | break 228 | } 229 | if !isFirstRound { 230 | mapAsBytes = append(mapAsBytes, []byte(", ")...) 231 | } 232 | isFirstRound = false 233 | kvIter := funl.NewListIterator(*nextKV) 234 | keyv := *(kvIter.Next()) 235 | valv := *(kvIter.Next()) 236 | if keyv.Kind != funl.StringValue { 237 | panic(fmt.Errorf("JSON object key not a string: %v", keyv)) 238 | } 239 | mapAsBytes = traverseValuesEncode(frame, keyv, mapAsBytes) 240 | mapAsBytes = append(mapAsBytes, []byte(": ")...) 241 | mapAsBytes = traverseValuesEncode(frame, valv, mapAsBytes) 242 | } 243 | mapAsBytes = append(mapAsBytes, []byte("}")...) 244 | nextsl = append(prevsl, mapAsBytes...) 245 | return 246 | } 247 | panic(fmt.Errorf("Unexpected type: %v", inValue)) 248 | } 249 | 250 | func traverseValues(frame *funl.Frame, intf interface{}) funl.Value { 251 | val := reflect.ValueOf(intf) 252 | if intf == nil { 253 | return funl.Value{Kind: funl.OpaqueValue, Data: &OpaqueJSONnull{}} 254 | } 255 | switch val.Kind() { 256 | case reflect.Bool: 257 | return funl.Value{Kind: funl.BoolValue, Data: val.Bool()} 258 | case reflect.String: 259 | if num, convOK := val.Interface().(json.Number); convOK { 260 | if i64, err := num.Int64(); err == nil { 261 | return funl.Value{Kind: funl.IntValue, Data: int(i64)} 262 | } 263 | if f64, err := num.Float64(); err == nil { 264 | return funl.Value{Kind: funl.FloatValue, Data: f64} 265 | } 266 | } 267 | return funl.Value{Kind: funl.StringValue, Data: val.String()} 268 | case reflect.Int: 269 | return funl.Value{Kind: funl.IntValue, Data: val.Int()} 270 | case reflect.Float64: 271 | return funl.Value{Kind: funl.FloatValue, Data: val.Float()} 272 | case reflect.Slice: 273 | var values []funl.Value 274 | for i := 0; i < val.Len(); i++ { 275 | item := val.Index(i) 276 | listItem := traverseValues(frame, item.Interface()) 277 | values = append(values, listItem) 278 | } 279 | return funl.MakeListOfValues(frame, values) 280 | case reflect.Map: 281 | mapval := funl.HandleMapOP(frame, []*funl.Item{}) 282 | for _, k := range val.MapKeys() { 283 | v := val.MapIndex(k) 284 | keyv := funl.Value{Kind: funl.StringValue, Data: k.String()} 285 | valv := traverseValues(frame, v.Interface()) 286 | putArgs := []*funl.Item{ 287 | &funl.Item{Type: funl.ValueItem, Data: mapval}, 288 | &funl.Item{Type: funl.ValueItem, Data: keyv}, 289 | &funl.Item{Type: funl.ValueItem, Data: valv}, 290 | } 291 | mapval = funl.HandlePutOP(frame, putArgs) 292 | if mapval.Kind != funl.MapValue { 293 | panic(fmt.Errorf("failed to put key-value to map")) 294 | } 295 | } 296 | return mapval 297 | } 298 | panic(fmt.Errorf("Unexpected type: %v (%v)", val, val.Kind())) 299 | } 300 | -------------------------------------------------------------------------------- /std/stdjson_test.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "github.com/anssihalmeaho/funl/funl" 5 | "testing" 6 | ) 7 | 8 | var jsonBlob = []byte(`[ 9 | {"Name": "Platypus", "Order": "Monotremata"}, 10 | {"Name": "Quoll", "Order": "Dasyuromorphia"} 11 | ]`) 12 | 13 | var jsonBlobFail = []byte(`[ 14 | {"Name": Platypus", "Order": "Monotremata"}, 15 | {"Name": "Quoll", "Order": "Dasyuromorphia"} 16 | ]`) 17 | 18 | var jsonX = []byte(` [true, false, ["tjaa", 123]] `) 19 | 20 | func TestEncodeOK(t *testing.T) { 21 | //inValue := funl.Value{Kind: funl.StringValue, Data: "some stuff"} 22 | //inValue := funl.Value{Kind: funl.IntValue, Data: 123} 23 | //inValue := funl.Value{Kind: funl.BoolValue, Data: true} 24 | //inValue := funl.Value{Kind: funl.BoolValue, Data: true} 25 | //inValue := funl.Value{Kind: funl.OpaqueValue, Data: &OpaqueJSONnull{}} 26 | inValue := funl.Value{Kind: funl.FloatValue, Data: 0.5} 27 | ok, errText, val := encodeJSON("dumname", nil, inValue) 28 | 29 | t.Logf("data = %s", val.Data.(*OpaqueByteArray).data) 30 | if !ok { 31 | t.Errorf("Not ok: %s", errText) 32 | } 33 | } 34 | 35 | func TestDecodeOK(t *testing.T) { 36 | ok, errText, val := decodeJSON("dumname", nil, jsonBlob) 37 | if !ok { 38 | t.Logf("error text = %s", errText) 39 | t.Errorf("Should be ok") 40 | } 41 | if !ok { 42 | t.Errorf("Not ok: %s", errText) 43 | } 44 | if val.Kind != funl.ListValue { 45 | t.Errorf("Not list") 46 | } 47 | } 48 | 49 | func TestDecodeFail(t *testing.T) { 50 | ok, errText, _ := decodeJSON("dumname", nil, jsonBlobFail) 51 | if ok { 52 | t.Errorf("Should fail") 53 | } 54 | if errText == "" { 55 | t.Errorf("No error text") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /std/stdlex.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/anssihalmeaho/funl/funl" 7 | ) 8 | 9 | func initSTDLex(interpreter *funl.Interpreter) (err error) { 10 | stdModuleName := "stdlex" 11 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 12 | stdAstFuncs := []stdFuncInfo{ 13 | { 14 | Name: "tokenize", 15 | Getter: getTokenize, 16 | IsFunction: true, 17 | }, 18 | } 19 | err = setSTDFunctions(topFrame, stdModuleName, stdAstFuncs, interpreter) 20 | return 21 | } 22 | 23 | func getTokenize(name string) stdFuncType { 24 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 25 | if l := len(arguments); l != 1 { 26 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), needs one", name, l) 27 | } 28 | if arguments[0].Kind != funl.StringValue { 29 | funl.RunTimeError2(frame, "%s: assuming string", name) 30 | } 31 | tokenizer := funl.NewTokenizer(funl.NewDefaultOperators()) 32 | tokens, err := tokenizer.Scan(arguments[0].Data.(string)) 33 | if err != nil { 34 | values := []funl.Value{ 35 | { 36 | Kind: funl.BoolValue, 37 | Data: false, 38 | }, 39 | { 40 | Kind: funl.StringValue, 41 | Data: err.Error(), 42 | }, 43 | { 44 | Kind: funl.StringValue, 45 | Data: "", 46 | }, 47 | } 48 | retVal = funl.MakeListOfValues(frame, values) 49 | return 50 | } 51 | 52 | tokenValSlice := []funl.Value{} 53 | for _, token := range tokens { 54 | mapval := funl.HandleMapOP(frame, []*funl.Item{}) 55 | 56 | putArgs := []*funl.Item{ 57 | &funl.Item{Type: funl.ValueItem, Data: mapval}, 58 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{Kind: funl.StringValue, Data: "value"}}, 59 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{Kind: funl.StringValue, Data: token.Value}}, 60 | } 61 | mapval = funl.HandlePutOP(frame, putArgs) 62 | 63 | putArgs = []*funl.Item{ 64 | &funl.Item{Type: funl.ValueItem, Data: mapval}, 65 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{Kind: funl.StringValue, Data: "type"}}, 66 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{ 67 | Kind: funl.StringValue, 68 | Data: fmt.Sprintf("%s", token.Type), 69 | }}, 70 | } 71 | mapval = funl.HandlePutOP(frame, putArgs) 72 | 73 | putArgs = []*funl.Item{ 74 | &funl.Item{Type: funl.ValueItem, Data: mapval}, 75 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{Kind: funl.StringValue, Data: "line"}}, 76 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{ 77 | Kind: funl.IntValue, 78 | Data: token.Lineno, 79 | }}, 80 | } 81 | mapval = funl.HandlePutOP(frame, putArgs) 82 | 83 | putArgs = []*funl.Item{ 84 | &funl.Item{Type: funl.ValueItem, Data: mapval}, 85 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{Kind: funl.StringValue, Data: "pos"}}, 86 | &funl.Item{Type: funl.ValueItem, Data: funl.Value{ 87 | Kind: funl.IntValue, 88 | Data: token.Pos, 89 | }}, 90 | } 91 | mapval = funl.HandlePutOP(frame, putArgs) 92 | 93 | tokenValSlice = append(tokenValSlice, mapval) 94 | } 95 | values := []funl.Value{ 96 | { 97 | Kind: funl.BoolValue, 98 | Data: true, 99 | }, 100 | { 101 | Kind: funl.StringValue, 102 | Data: "", 103 | }, 104 | funl.MakeListOfValues(frame, tokenValSlice), 105 | } 106 | retVal = funl.MakeListOfValues(frame, values) 107 | return 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /std/stdlog.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/anssihalmeaho/funl/funl" 9 | ) 10 | 11 | func initSTDlog(interpreter *funl.Interpreter) (err error) { 12 | stdModuleName := "stdlog" 13 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 14 | stdLogFuncs := []stdFuncInfo{ 15 | { 16 | Name: "get-logger", 17 | Getter: getStdLogGetLogger, 18 | }, 19 | { 20 | Name: "get-default-logger", 21 | Getter: getStdLogGetDefaultLogger, 22 | }, 23 | } 24 | err = setSTDFunctions(topFrame, stdModuleName, stdLogFuncs, interpreter) 25 | return 26 | } 27 | 28 | func getStdLogGetDefaultLogger(name string) stdFuncType { 29 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 30 | l := len(arguments) 31 | if (l != 0) && (l != 1) { 32 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), needs one or two", name, l) 33 | } 34 | 35 | var prefix string 36 | var flag int 37 | var separator = ":" 38 | // lets check config map if there is such 39 | if l == 1 { 40 | if arguments[0].Kind != funl.MapValue { 41 | funl.RunTimeError2(frame, "%s: assuming map as 1st argument", name) 42 | } 43 | 44 | keyvals := funl.HandleKeyvalsOP(frame, []*funl.Item{&funl.Item{Type: funl.ValueItem, Data: arguments[0]}}) 45 | kvListIter := funl.NewListIterator(keyvals) 46 | for { 47 | nextKV := kvListIter.Next() 48 | if nextKV == nil { 49 | break 50 | } 51 | kvIter := funl.NewListIterator(*nextKV) 52 | keyv := *(kvIter.Next()) 53 | valv := *(kvIter.Next()) 54 | if keyv.Kind != funl.StringValue { 55 | funl.RunTimeError2(frame, "%s:config key not a string: %v", name, keyv) 56 | } 57 | switch keyStr := keyv.Data.(string); keyStr { 58 | case "prefix": 59 | if valv.Kind != funl.StringValue { 60 | funl.RunTimeError2(frame, "%s: %s value not string: %v", name, keyStr, keyv) 61 | } 62 | prefix = valv.Data.(string) 63 | case "separator": 64 | if valv.Kind != funl.StringValue { 65 | funl.RunTimeError2(frame, "%s: %s value not string: %v", name, keyStr, keyv) 66 | } 67 | separator = valv.Data.(string) 68 | case "date": 69 | if valv.Kind != funl.BoolValue { 70 | funl.RunTimeError2(frame, "%s: %s value not bool value: %v", name, keyStr, keyv) 71 | } 72 | if valv.Data.(bool) { 73 | flag |= log.Ldate 74 | } 75 | case "time": 76 | if valv.Kind != funl.BoolValue { 77 | funl.RunTimeError2(frame, "%s: %s value not bool value: %v", name, keyStr, keyv) 78 | } 79 | if valv.Data.(bool) { 80 | flag |= log.Ltime 81 | } 82 | case "microseconds": 83 | if valv.Kind != funl.BoolValue { 84 | funl.RunTimeError2(frame, "%s: %s value not bool value: %v", name, keyStr, keyv) 85 | } 86 | if valv.Data.(bool) { 87 | flag |= log.Lmicroseconds 88 | } 89 | case "UTC": 90 | if valv.Kind != funl.BoolValue { 91 | funl.RunTimeError2(frame, "%s: %s value not bool value: %v", name, keyStr, keyv) 92 | } 93 | if valv.Data.(bool) { 94 | flag |= log.LUTC 95 | } 96 | } 97 | } 98 | } 99 | 100 | logger := log.New(os.Stdout, prefix, flag) 101 | 102 | logWrapper := func(wFrame *funl.Frame, wArguments []funl.Value) funl.Value { 103 | if l := len(wArguments); l == 0 { 104 | funl.RunTimeError2(frame, "Logger assumes at least one argument") 105 | } 106 | var textOutput string 107 | for _, argval := range wArguments { 108 | textOutput += fmt.Sprintf("%s%s", separator, argval) 109 | } 110 | logger.Println(textOutput) 111 | return funl.Value{Kind: funl.BoolValue, Data: true} 112 | } 113 | 114 | extProc := funl.ExtProcType{ 115 | Impl: logWrapper, 116 | IsFunction: false, 117 | } 118 | retVal = funl.Value{Kind: funl.ExtProcValue, Data: extProc} 119 | return 120 | } 121 | } 122 | 123 | //get-logger () -> ext-proc (stdlog handler which serailizes and calls handler) 124 | func getStdLogGetLogger(name string) stdFuncType { 125 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 126 | l := len(arguments) 127 | if (l != 1) && (l != 2) { 128 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), needs one or two", name, l) 129 | } 130 | 131 | switch arguments[0].Kind { 132 | case funl.FunctionValue, funl.ExtProcValue: 133 | default: 134 | funl.RunTimeError2(frame, "%s: assuming procedure as argument", name) 135 | } 136 | 137 | const defaultLogBufferSize = 1024 138 | bufSize := defaultLogBufferSize 139 | // lets check config map if there is such 140 | if l == 2 { 141 | if arguments[1].Kind != funl.MapValue { 142 | funl.RunTimeError2(frame, "%s: assuming map as 2nd argument", name) 143 | } 144 | 145 | keyvals := funl.HandleKeyvalsOP(frame, []*funl.Item{&funl.Item{Type: funl.ValueItem, Data: arguments[1]}}) 146 | kvListIter := funl.NewListIterator(keyvals) 147 | for { 148 | nextKV := kvListIter.Next() 149 | if nextKV == nil { 150 | break 151 | } 152 | kvIter := funl.NewListIterator(*nextKV) 153 | keyv := *(kvIter.Next()) 154 | valv := *(kvIter.Next()) 155 | if keyv.Kind != funl.StringValue { 156 | funl.RunTimeError2(frame, "%s:config key not a string: %v", name, keyv) 157 | } 158 | switch keyStr := keyv.Data.(string); keyStr { 159 | case "buffer-size": 160 | if valv.Kind != funl.IntValue { 161 | funl.RunTimeError2(frame, "%s: %s value not int: %v", name, keyStr, keyv) 162 | } 163 | bufSize = valv.Data.(int) 164 | } 165 | } 166 | } 167 | 168 | logCh := make(chan []*funl.Item, bufSize) 169 | 170 | go func() { 171 | for argsForCall := range logCh { 172 | funl.HandleCallOP(frame, argsForCall) 173 | } 174 | }() 175 | 176 | logWrapper := func(wFrame *funl.Frame, wArguments []funl.Value) funl.Value { 177 | if l := len(wArguments); l == 0 { 178 | funl.RunTimeError2(frame, "Logger assumes at least one argument") 179 | } 180 | argsForCall := []*funl.Item{ 181 | &funl.Item{ 182 | Type: funl.ValueItem, 183 | Data: arguments[0], 184 | }, 185 | &funl.Item{ 186 | Type: funl.ValueItem, 187 | Data: funl.MakeListOfValues(wFrame, wArguments), 188 | }, 189 | } 190 | logCh <- argsForCall 191 | return funl.Value{Kind: funl.BoolValue, Data: true} 192 | } 193 | 194 | extProc := funl.ExtProcType{ 195 | Impl: logWrapper, 196 | IsFunction: false, 197 | } 198 | retVal = funl.Value{Kind: funl.ExtProcValue, Data: extProc} 199 | return 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /std/stdos.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "os/exec" 7 | "os/signal" 8 | "strings" 9 | "syscall" 10 | 11 | "github.com/anssihalmeaho/funl/funl" 12 | ) 13 | 14 | func initSTDos(interpreter *funl.Interpreter) (err error) { 15 | stdModuleName := "stdos" 16 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 17 | stdOSFuncs := []stdFuncInfo{ 18 | { 19 | Name: "reg-signal-handler", 20 | Getter: getStdOSregSignalHandler, 21 | }, 22 | { 23 | Name: "exit", 24 | Getter: getStdOSexit, 25 | }, 26 | { 27 | Name: "getenv", 28 | Getter: getStdOSGetEnv, 29 | }, 30 | { 31 | Name: "setenv", 32 | Getter: getStdOSSetEnv, 33 | }, 34 | { 35 | Name: "unsetenv", 36 | Getter: getStdOSUnSetEnv, 37 | }, 38 | { 39 | Name: "exec", 40 | Getter: getStdOSExec, 41 | }, 42 | { 43 | Name: "exec-with", 44 | Getter: getStdOSExecWith, 45 | }, 46 | } 47 | err = setSTDFunctions(topFrame, stdModuleName, stdOSFuncs, interpreter) 48 | return 49 | } 50 | 51 | func getStdOSExecWith(name string) stdFuncType { 52 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 53 | l := len(arguments) 54 | if l < 1 { 55 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), needs at least one", name, l) 56 | } 57 | 58 | var stdinBytearr *OpaqueByteArray 59 | if arguments[0].Kind != funl.MapValue { 60 | funl.RunTimeError2(frame, "%s: requires map value", name) 61 | } 62 | keyvals := funl.HandleKeyvalsOP(frame, []*funl.Item{&funl.Item{Type: funl.ValueItem, Data: arguments[0]}}) 63 | kvListIter := funl.NewListIterator(keyvals) 64 | for { 65 | nextKV := kvListIter.Next() 66 | if nextKV == nil { 67 | break 68 | } 69 | kvIter := funl.NewListIterator(*nextKV) 70 | keyv := *(kvIter.Next()) 71 | valv := *(kvIter.Next()) 72 | if keyv.Kind != funl.StringValue { 73 | funl.RunTimeError2(frame, "%s: info key not a string: %v", name, keyv) 74 | } 75 | switch keyStr := keyv.Data.(string); keyStr { 76 | case "stdin": 77 | if valv.Kind != funl.OpaqueValue { 78 | funl.RunTimeError2(frame, "%s: %s value not opaque value: %v", name, keyStr, keyv) 79 | } 80 | var convOK bool 81 | stdinBytearr, convOK = valv.Data.(*OpaqueByteArray) 82 | if !convOK { 83 | funl.RunTimeError2(frame, "%s: %s value not opaque: %v", name, keyStr, keyv) 84 | } 85 | } 86 | } 87 | 88 | var argStrs []string 89 | for i, argval := range arguments[1:] { 90 | if argval.Kind != funl.StringValue { 91 | funl.RunTimeError2(frame, "%s: assuming string as key (%d)", name, i) 92 | } 93 | argStrs = append(argStrs, argval.Data.(string)) 94 | } 95 | 96 | var outStd bytes.Buffer 97 | var outErr bytes.Buffer 98 | cmd := exec.Command(argStrs[0], argStrs[1:]...) 99 | cmd.Stdout = &outStd 100 | cmd.Stderr = &outErr 101 | 102 | if stdinBytearr != nil { 103 | cmd.Stdin = bytes.NewBuffer(stdinBytearr.data) 104 | } 105 | 106 | err := cmd.Run() 107 | 108 | var errText string 109 | if err != nil { 110 | errText = err.Error() 111 | } 112 | values := []funl.Value{ 113 | { 114 | Kind: funl.BoolValue, 115 | Data: err == nil, 116 | }, 117 | { 118 | Kind: funl.StringValue, 119 | Data: errText, 120 | }, 121 | { 122 | Kind: funl.OpaqueValue, 123 | Data: &OpaqueByteArray{data: outStd.Bytes()}, 124 | }, 125 | { 126 | Kind: funl.OpaqueValue, 127 | Data: &OpaqueByteArray{data: outErr.Bytes()}, 128 | }, 129 | } 130 | retVal = funl.MakeListOfValues(frame, values) 131 | return 132 | } 133 | } 134 | 135 | func getStdOSExec(name string) stdFuncType { 136 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 137 | l := len(arguments) 138 | if l < 1 { 139 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), needs at least one", name, l) 140 | } 141 | var argStrs []string 142 | for i, argval := range arguments { 143 | if argval.Kind != funl.StringValue { 144 | funl.RunTimeError2(frame, "%s: assuming string as key (%d)", name, i) 145 | } 146 | argStrs = append(argStrs, argval.Data.(string)) 147 | } 148 | 149 | var outStd bytes.Buffer 150 | var outErr bytes.Buffer 151 | cmd := exec.Command(argStrs[0], argStrs[1:]...) 152 | cmd.Stdout = &outStd 153 | cmd.Stderr = &outErr 154 | err := cmd.Run() 155 | 156 | var errText string 157 | if err != nil { 158 | errText = err.Error() 159 | } 160 | values := []funl.Value{ 161 | { 162 | Kind: funl.BoolValue, 163 | Data: err == nil, 164 | }, 165 | { 166 | Kind: funl.StringValue, 167 | Data: errText, 168 | }, 169 | { 170 | Kind: funl.OpaqueValue, 171 | Data: &OpaqueByteArray{data: outStd.Bytes()}, 172 | }, 173 | { 174 | Kind: funl.OpaqueValue, 175 | Data: &OpaqueByteArray{data: outErr.Bytes()}, 176 | }, 177 | } 178 | retVal = funl.MakeListOfValues(frame, values) 179 | return 180 | } 181 | } 182 | 183 | func getStdOSUnSetEnv(name string) stdFuncType { 184 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 185 | l := len(arguments) 186 | if l != 1 { 187 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), needs one", name, l) 188 | } 189 | 190 | if arguments[0].Kind != funl.StringValue { 191 | funl.RunTimeError2(frame, "%s: assuming string as key", name) 192 | } 193 | err := os.Unsetenv(arguments[0].Data.(string)) 194 | var errText string 195 | if err != nil { 196 | errText = err.Error() 197 | } 198 | retVal = funl.Value{Kind: funl.StringValue, Data: errText} 199 | return 200 | } 201 | } 202 | 203 | func getStdOSSetEnv(name string) stdFuncType { 204 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 205 | l := len(arguments) 206 | if l != 2 { 207 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), needs two", name, l) 208 | } 209 | 210 | if arguments[0].Kind != funl.StringValue { 211 | funl.RunTimeError2(frame, "%s: assuming string as key", name) 212 | } 213 | if arguments[1].Kind != funl.StringValue { 214 | funl.RunTimeError2(frame, "%s: assuming string as value", name) 215 | } 216 | err := os.Setenv(arguments[0].Data.(string), arguments[1].Data.(string)) 217 | var errText string 218 | if err != nil { 219 | errText = err.Error() 220 | } 221 | retVal = funl.Value{Kind: funl.StringValue, Data: errText} 222 | return 223 | } 224 | } 225 | 226 | func getStdOSGetEnv(name string) stdFuncType { 227 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 228 | l := len(arguments) 229 | if l > 1 { 230 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need at most one", name, l) 231 | } 232 | 233 | // if no arguments are given then returns map with all key-values 234 | if l == 0 { 235 | envMapval := funl.HandleMapOP(frame, []*funl.Item{}) 236 | for _, e := range os.Environ() { 237 | pair := strings.Split(e, "=") 238 | if len(pair) != 2 { 239 | continue 240 | } 241 | 242 | keyv := funl.Value{Kind: funl.StringValue, Data: pair[0]} 243 | valv := funl.Value{Kind: funl.StringValue, Data: pair[1]} 244 | putArgs := []*funl.Item{ 245 | &funl.Item{Type: funl.ValueItem, Data: envMapval}, 246 | &funl.Item{Type: funl.ValueItem, Data: keyv}, 247 | &funl.Item{Type: funl.ValueItem, Data: valv}, 248 | } 249 | envMapval = funl.HandlePutOP(frame, putArgs) 250 | if envMapval.Kind != funl.MapValue { 251 | funl.RunTimeError2(frame, "%s: failed to put key-value to map", name) 252 | } 253 | 254 | } 255 | retVal = envMapval 256 | return 257 | } 258 | // otherwise, lets find value for key given as argument 259 | if arguments[0].Kind != funl.StringValue { 260 | funl.RunTimeError2(frame, "%s: assuming string as argument", name) 261 | } 262 | envVal, found := os.LookupEnv(arguments[0].Data.(string)) 263 | retValues := []funl.Value{ 264 | { 265 | Kind: funl.BoolValue, 266 | Data: found, 267 | }, 268 | { 269 | Kind: funl.StringValue, 270 | Data: envVal, 271 | }, 272 | } 273 | retVal = funl.MakeListOfValues(frame, retValues) 274 | return 275 | } 276 | } 277 | 278 | // call(stdos.reg-signal-handler, proc() end, , ,...) 279 | // -> ext-proc : canceller 280 | func getStdOSregSignalHandler(name string) stdFuncType { 281 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 282 | l := len(arguments) 283 | if l < 1 { 284 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need at least one", name, l) 285 | } 286 | if arguments[0].Kind != funl.FunctionValue { 287 | // what about ext-procs...? 288 | funl.RunTimeError2(frame, "%s: requires func/proc value", name) 289 | } 290 | var signums []os.Signal 291 | if l > 1 { 292 | for _, signalNumVal := range arguments[1:] { 293 | if signalNumVal.Kind != funl.IntValue { 294 | funl.RunTimeError2(frame, "%s: requires int value for signal number", name) 295 | } 296 | signalNumAsInt, ok := signalNumVal.Data.(int) 297 | if !ok { 298 | funl.RunTimeError2(frame, "%s: signal number could not be read", name) 299 | } 300 | signalNum := syscall.Signal(signalNumAsInt) 301 | signums = append(signums, signalNum) 302 | } 303 | } 304 | c := make(chan os.Signal, 1) 305 | if l > 1 { 306 | signal.Notify(c, signums...) 307 | } else { 308 | signal.Notify(c) 309 | } 310 | go func() { 311 | for { 312 | sig := <-c 313 | sigNum, _ := sig.(syscall.Signal) 314 | 315 | argsForCall := []*funl.Item{ 316 | &funl.Item{ 317 | Type: funl.ValueItem, 318 | Data: arguments[0], 319 | }, 320 | &funl.Item{ 321 | Type: funl.ValueItem, 322 | Data: funl.Value{ 323 | Kind: funl.IntValue, 324 | Data: int(sigNum), 325 | }, 326 | }, 327 | &funl.Item{ 328 | Type: funl.ValueItem, 329 | Data: funl.Value{ 330 | Kind: funl.StringValue, 331 | Data: sig.String(), 332 | }, 333 | }, 334 | } 335 | funl.HandleCallOP(frame, argsForCall) 336 | } 337 | }() 338 | 339 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} // temporary 340 | return 341 | } 342 | } 343 | 344 | func getStdOSexit(name string) stdFuncType { 345 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 346 | l := len(arguments) 347 | if l == 1 { 348 | if arguments[0].Kind != funl.IntValue { 349 | exitCode, ok := arguments[0].Data.(int) 350 | if ok { 351 | os.Exit(exitCode) 352 | } 353 | } 354 | } 355 | os.Exit(0) 356 | return 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /std/stdrun.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "github.com/anssihalmeaho/funl/funl" 5 | ) 6 | 7 | func initSTDRun(interpreter *funl.Interpreter) (err error) { 8 | stdModuleName := "stdrun" 9 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 10 | stdAstFuncs := []stdFuncInfo{ 11 | { 12 | Name: "add-to-mod-cache", 13 | Getter: getAddToModCache, 14 | //IsFunction: true, 15 | }, 16 | { 17 | Name: "backtrace", 18 | Getter: getBacktrace, 19 | IsFunction: true, 20 | }, 21 | } 22 | err = setSTDFunctions(topFrame, stdModuleName, stdAstFuncs, interpreter) 23 | return 24 | } 25 | 26 | func getBacktrace(name string) stdFuncType { 27 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 28 | stack := []funl.Value{} 29 | 30 | // if debug printing disabled in pure functions return just empty list 31 | if funl.PrintingDisabledInFunctions { 32 | retVal = funl.MakeListOfValues(frame, stack) 33 | return 34 | } 35 | 36 | prevFrame := frame 37 | for { 38 | if prevFrame == nil { 39 | break 40 | } 41 | mapv := funl.HandleMapOP(frame, []*funl.Item{}) 42 | if prevFrame.FuncProto == nil { 43 | mapv = putToMap(frame, mapv, "line", funl.Value{Kind: funl.IntValue, Data: 0}) 44 | mapv = putToMap(frame, mapv, "file", funl.Value{Kind: funl.StringValue, Data: "-"}) 45 | mapv = putToMap(frame, mapv, "args", funl.MakeListOfValues(frame, []funl.Value{})) 46 | } else { 47 | mapv = putToMap(frame, mapv, "line", funl.Value{Kind: funl.IntValue, Data: prevFrame.FuncProto.Lineno}) 48 | mapv = putToMap(frame, mapv, "file", funl.Value{Kind: funl.StringValue, Data: prevFrame.FuncProto.SrcFileName}) 49 | mapv = putToMap(frame, mapv, "args", funl.MakeListOfValues(frame, prevFrame.EvaluatedArgs)) 50 | } 51 | 52 | stack = append(stack, mapv) 53 | prevFrame = prevFrame.Previous 54 | } 55 | retVal = funl.MakeListOfValues(frame, stack) 56 | return 57 | } 58 | } 59 | 60 | func getAddToModCache(name string) stdFuncType { 61 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 62 | if l := len(arguments); l != 2 { 63 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need two", name, l) 64 | } 65 | if arguments[0].Kind != funl.StringValue { 66 | funl.RunTimeError2(frame, "%s: assuming string", name) 67 | } 68 | if arguments[1].Kind != funl.MapValue { 69 | funl.RunTimeError2(frame, "%s: assuming map", name) 70 | } 71 | 72 | importModName := arguments[0].Data.(string) 73 | nspace := &funl.NSpace{OtherNS: make(map[funl.SymID]funl.ImportInfo), Syms: funl.NewSymt()} 74 | 75 | // loop symbol to value mappings 76 | keyvals := funl.HandleKeyvalsOP(frame, []*funl.Item{&funl.Item{Type: funl.ValueItem, Data: arguments[1]}}) 77 | kvListIter := funl.NewListIterator(keyvals) 78 | for { 79 | nextKV := kvListIter.Next() 80 | if nextKV == nil { 81 | break 82 | } 83 | kvIter := funl.NewListIterator(*nextKV) 84 | keyv := *(kvIter.Next()) 85 | valv := *(kvIter.Next()) 86 | if keyv.Kind != funl.StringValue { 87 | continue // just skip... 88 | } 89 | err := nspace.Syms.Add(keyv.Data.(string), &funl.Item{Type: funl.ValueItem, Data: valv}) 90 | if err != nil { 91 | funl.RunTimeError2(frame, "%s: error in adding symbol: %v", name, err) 92 | } 93 | } 94 | 95 | // then add to module cache 96 | interpreter := frame.GetTopFrame().Interpreter 97 | funl.AddNStoCache(true, importModName, nspace, interpreter) 98 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 99 | return 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /std/stdtime.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/anssihalmeaho/funl/funl" 8 | ) 9 | 10 | func initSTDTIME(interpreter *funl.Interpreter) (err error) { 11 | stdModuleName := "stdtime" 12 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 13 | stdTimeFuncs := []stdFuncInfo{ 14 | { 15 | Name: "sleep", 16 | Getter: getStdTimeSleep, 17 | }, 18 | { 19 | Name: "nanosleep", 20 | Getter: getStdTimeNanoSleep, 21 | }, 22 | { 23 | Name: "newtimer", 24 | Getter: getStdNewTimer, 25 | }, 26 | { 27 | Name: "stoptimer", 28 | Getter: getStdStopTimer, 29 | }, 30 | { 31 | Name: "callafter", 32 | Getter: getStdCallAfter, 33 | }, 34 | { 35 | Name: "newticker", 36 | Getter: getStdNewTicker, 37 | }, 38 | { 39 | Name: "stopticker", 40 | Getter: getStdStopTicker, 41 | }, 42 | } 43 | err = setSTDFunctions(topFrame, stdModuleName, stdTimeFuncs, interpreter) 44 | return 45 | } 46 | 47 | func getStdTimeNanoSleep(name string) stdFuncType { 48 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 49 | if l := len(arguments); l != 1 { 50 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d)", name, l) 51 | } 52 | if arguments[0].Kind != funl.IntValue { 53 | funl.RunTimeError2(frame, "%s: requires integer as input", name) 54 | } 55 | sleepTime := arguments[0].Data.(int) 56 | time.Sleep(time.Duration(sleepTime)) 57 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 58 | return 59 | } 60 | } 61 | 62 | func getStdTimeSleep(name string) stdFuncType { 63 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 64 | if l := len(arguments); l != 1 { 65 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d)", name, l) 66 | } 67 | if arguments[0].Kind != funl.IntValue { 68 | funl.RunTimeError2(frame, "%s: requires integer as input", name) 69 | } 70 | sleepTime := arguments[0].Data.(int) 71 | time.Sleep(time.Duration(sleepTime) * time.Second) 72 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 73 | return 74 | } 75 | } 76 | 77 | type opaqueTimer struct { 78 | t *time.Timer 79 | } 80 | 81 | func (ot *opaqueTimer) TypeName() string { 82 | return "timer" 83 | } 84 | 85 | func (ot *opaqueTimer) Str() string { 86 | return fmt.Sprintf("timer(%#v)", *ot) 87 | } 88 | 89 | func (ot *opaqueTimer) Equals(with funl.OpaqueAPI) bool { 90 | timerVal, ok := with.(*opaqueTimer) 91 | if !ok { 92 | return false 93 | } 94 | return *ot == *timerVal 95 | } 96 | 97 | func getStdStopTimer(name string) stdFuncType { 98 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 99 | if l := len(arguments); l != 1 { 100 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d)", name, l) 101 | } 102 | if arguments[0].Kind != funl.OpaqueValue { 103 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 104 | } 105 | timVal, ok := arguments[0].Data.(*opaqueTimer) 106 | if !ok { 107 | funl.RunTimeError2(frame, "%s: requires timer value", name) 108 | } 109 | retVal = funl.Value{Kind: funl.BoolValue, Data: timVal.t.Stop()} 110 | return 111 | } 112 | } 113 | 114 | func getStdCallAfter(name string) stdFuncType { 115 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 116 | if l := len(arguments); l != 2 { 117 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d)", name, l) 118 | } 119 | if arguments[0].Kind != funl.IntValue { 120 | funl.RunTimeError2(frame, "%s: requires int value", name) 121 | } 122 | durationInt, ok := arguments[0].Data.(int) 123 | if !ok { 124 | funl.RunTimeError2(frame, "%s: argument is not int value", name) 125 | } 126 | if arguments[1].Kind != funl.FunctionValue { 127 | funl.RunTimeError2(frame, "%s: requires func/proc as 2nd argument", name) 128 | } 129 | _, ok = arguments[1].Data.(funl.FuncValue) 130 | if !ok { 131 | funl.RunTimeError2(frame, "%s: not func/proc as 2nd argument", name) 132 | } 133 | wrapperFunc := func() { 134 | argsForCall := []*funl.Item{&funl.Item{Type: funl.ValueItem, Data: arguments[1]}} 135 | funl.HandleCallOP(frame, argsForCall) 136 | } 137 | timer := time.AfterFunc(time.Duration(durationInt), wrapperFunc) 138 | retVal = funl.Value{Kind: funl.OpaqueValue, Data: &opaqueTimer{t: timer}} 139 | return 140 | } 141 | } 142 | 143 | func getStdNewTimer(name string) stdFuncType { 144 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 145 | if l := len(arguments); l != 3 { 146 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d)", name, l) 147 | } 148 | if arguments[0].Kind != funl.IntValue { 149 | funl.RunTimeError2(frame, "%s: requires int value", name) 150 | } 151 | durationInt, ok := arguments[0].Data.(int) 152 | if !ok { 153 | funl.RunTimeError2(frame, "%s: argument is not int value", name) 154 | } 155 | if arguments[1].Kind != funl.ChanValue { 156 | funl.RunTimeError2(frame, "%s: requires channel as 2nd argument", name) 157 | } 158 | timer := time.NewTimer(time.Duration(durationInt)) 159 | retVal = funl.Value{Kind: funl.OpaqueValue, Data: &opaqueTimer{t: timer}} 160 | go func() { 161 | <-timer.C 162 | ch := arguments[1].Data.(chan funl.Value) 163 | ch <- arguments[2] 164 | }() 165 | return 166 | } 167 | } 168 | 169 | type opaqueTicker struct { 170 | t *time.Ticker 171 | done chan bool 172 | } 173 | 174 | func (ot *opaqueTicker) TypeName() string { 175 | return "ticker" 176 | } 177 | 178 | func (ot *opaqueTicker) Str() string { 179 | return fmt.Sprintf("ticker(%#v)", *ot) 180 | } 181 | 182 | func (ot *opaqueTicker) Equals(with funl.OpaqueAPI) bool { 183 | tickerVal, ok := with.(*opaqueTicker) 184 | if !ok { 185 | return false 186 | } 187 | return *ot == *tickerVal 188 | } 189 | 190 | func getStdStopTicker(name string) stdFuncType { 191 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 192 | if l := len(arguments); l != 1 { 193 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d)", name, l) 194 | } 195 | if arguments[0].Kind != funl.OpaqueValue { 196 | funl.RunTimeError2(frame, "%s: requires opaque value", name) 197 | } 198 | tickVal, ok := arguments[0].Data.(*opaqueTicker) 199 | if !ok { 200 | funl.RunTimeError2(frame, "%s: requires ticker value", name) 201 | } 202 | tickVal.t.Stop() 203 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 204 | tickVal.done <- true 205 | return 206 | } 207 | } 208 | 209 | func getStdNewTicker(name string) stdFuncType { 210 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 211 | if l := len(arguments); l != 3 { 212 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d)", name, l) 213 | } 214 | if arguments[0].Kind != funl.IntValue { 215 | funl.RunTimeError2(frame, "%s: requires int value", name) 216 | } 217 | durationInt, ok := arguments[0].Data.(int) 218 | if !ok { 219 | funl.RunTimeError2(frame, "%s: argument is not int value", name) 220 | } 221 | if arguments[1].Kind != funl.ChanValue { 222 | funl.RunTimeError2(frame, "%s: requires channel as 2nd argument", name) 223 | } 224 | ticker := time.NewTicker(time.Duration(durationInt)) 225 | done := make(chan bool) 226 | retVal = funl.Value{Kind: funl.OpaqueValue, Data: &opaqueTicker{t: ticker, done: done}} 227 | go func() { 228 | for { 229 | select { 230 | case <-done: 231 | return 232 | case <-ticker.C: 233 | ch := arguments[1].Data.(chan funl.Value) 234 | ch <- arguments[2] 235 | } 236 | } 237 | }() 238 | return 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /std/stdvar.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/anssihalmeaho/funl/funl" 8 | ) 9 | 10 | func initSTDVar(interpreter *funl.Interpreter) (err error) { 11 | stdModuleName := "stdvar" 12 | topFrame := funl.NewTopFrameWithInterpreter(interpreter) 13 | stdFuncs := []stdFuncInfo{ 14 | { 15 | Name: "new", 16 | Getter: getStdVarNew, 17 | }, 18 | { 19 | Name: "value", 20 | Getter: getStdVarValue, 21 | }, 22 | { 23 | Name: "set", 24 | Getter: getStdVarSet, 25 | }, 26 | { 27 | Name: "change", 28 | Getter: getStdVarChange, 29 | }, 30 | { 31 | Name: "change-v2", 32 | Getter: getStdVarChangeV2, 33 | }, 34 | } 35 | err = setSTDFunctions(topFrame, stdModuleName, stdFuncs, interpreter) 36 | return 37 | } 38 | 39 | // OpaqueVarRef ... 40 | type OpaqueVarRef struct { 41 | ValRef *funl.Value 42 | sync.RWMutex 43 | } 44 | 45 | // TypeName ... 46 | func (ref *OpaqueVarRef) TypeName() string { 47 | return "var-ref" 48 | } 49 | 50 | // Str ... 51 | func (ref *OpaqueVarRef) Str() string { 52 | ref.RLock() 53 | val := *(ref.ValRef) 54 | ref.RUnlock() 55 | return fmt.Sprintf("var-ref(%v)", val) 56 | } 57 | 58 | // Equals ... 59 | func (ref *OpaqueVarRef) Equals(with funl.OpaqueAPI) bool { 60 | other, ok := with.(*OpaqueVarRef) 61 | if !ok { 62 | return false 63 | } 64 | other.RLock() 65 | ref.RLock() 66 | isSame := (ref.ValRef == other.ValRef) 67 | ref.RUnlock() 68 | other.RUnlock() 69 | return isSame 70 | } 71 | 72 | func getStdVarChangeV2(name string) stdFuncType { 73 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 74 | if l := len(arguments); l < 2 { 75 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need two", name, l) 76 | } 77 | if arguments[0].Kind != funl.OpaqueValue { 78 | funl.RunTimeError2(frame, "%s: assuming opaque var-ref", name) 79 | } 80 | varref, convOK := arguments[0].Data.(*OpaqueVarRef) 81 | if !convOK { 82 | funl.RunTimeError2(frame, "%s: assuming var-ref", name) 83 | } 84 | 85 | switch arguments[1].Kind { 86 | case funl.FunctionValue: 87 | funcVal, funcOK := arguments[1].Data.(funl.FuncValue) 88 | if !funcOK { 89 | funl.RunTimeError2(frame, "%s: not func as 2nd argument", name) 90 | } 91 | if funcVal.FuncProto.IsProc { 92 | funl.RunTimeError2(frame, "%s: proc not allowed", name) 93 | } 94 | case funl.ExtProcValue: 95 | extFuncVal, extfuncOK := arguments[1].Data.(funl.ExtProcType) 96 | if !extfuncOK { 97 | funl.RunTimeError2(frame, "%s: not ext-func as 2nd argument", name) 98 | } 99 | if !extFuncVal.IsFunction { 100 | funl.RunTimeError2(frame, "%s: ext-proc not allowed", name) 101 | } 102 | default: 103 | funl.RunTimeError2(frame, "%s: assuming function as argument", name) 104 | } 105 | 106 | varref.Lock() 107 | defer varref.Unlock() 108 | 109 | oldVal := *(varref.ValRef) 110 | 111 | argsForCall := []*funl.Item{ 112 | { 113 | Type: funl.ValueItem, 114 | Data: arguments[1], 115 | }, 116 | { 117 | Type: funl.ValueItem, 118 | Data: oldVal, 119 | }, 120 | } 121 | if len(arguments) == 3 { 122 | argsForCall = append(argsForCall, &funl.Item{Type: funl.ValueItem, Data: arguments[2]}) 123 | } 124 | retListVal, callErr := func() (rv funl.Value, errDesc error) { 125 | defer func() { 126 | if r := recover(); r != nil { 127 | var rtestr string 128 | if err, isError := r.(error); isError { 129 | rtestr = err.Error() 130 | } 131 | errDesc = fmt.Errorf("%s", rtestr) 132 | } 133 | }() 134 | return funl.HandleCallOP(frame, argsForCall), nil 135 | }() 136 | 137 | var rval funl.Value 138 | var addRetVal funl.Value 139 | var errtext string 140 | if callErr == nil && retListVal.Kind != funl.ListValue { 141 | callErr = fmt.Errorf("List value expected") 142 | } 143 | if callErr == nil { 144 | lit := funl.NewListIterator(retListVal) 145 | newVal := lit.Next() 146 | if newVal == nil { 147 | callErr = fmt.Errorf("Too short list received (empty)") 148 | errtext = callErr.Error() 149 | rval = funl.Value{Kind: funl.StringValue, Data: ""} 150 | addRetVal = funl.Value{Kind: funl.StringValue, Data: ""} 151 | } else { 152 | nextVal := lit.Next() 153 | if nextVal == nil { 154 | callErr = fmt.Errorf("Too short list received (one item)") 155 | errtext = callErr.Error() 156 | rval = funl.Value{Kind: funl.StringValue, Data: ""} 157 | addRetVal = funl.Value{Kind: funl.StringValue, Data: ""} 158 | } else { 159 | addRetVal = *nextVal 160 | rval = *newVal 161 | varref.ValRef = newVal 162 | } 163 | } 164 | } else { 165 | errtext = callErr.Error() 166 | rval = funl.Value{Kind: funl.StringValue, Data: ""} 167 | addRetVal = funl.Value{Kind: funl.StringValue, Data: ""} 168 | } 169 | 170 | values := []funl.Value{ 171 | { 172 | Kind: funl.BoolValue, 173 | Data: callErr == nil, 174 | }, 175 | { 176 | Kind: funl.StringValue, 177 | Data: errtext, 178 | }, 179 | rval, 180 | addRetVal, 181 | } 182 | retVal = funl.MakeListOfValues(frame, values) 183 | return 184 | } 185 | } 186 | 187 | func getStdVarChange(name string) stdFuncType { 188 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 189 | if l := len(arguments); l != 2 { 190 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need two", name, l) 191 | } 192 | if arguments[0].Kind != funl.OpaqueValue { 193 | funl.RunTimeError2(frame, "%s: assuming opaque var-ref", name) 194 | } 195 | varref, convOK := arguments[0].Data.(*OpaqueVarRef) 196 | if !convOK { 197 | funl.RunTimeError2(frame, "%s: assuming var-ref", name) 198 | } 199 | 200 | switch arguments[1].Kind { 201 | case funl.FunctionValue: 202 | funcVal, funcOK := arguments[1].Data.(funl.FuncValue) 203 | if !funcOK { 204 | funl.RunTimeError2(frame, "%s: not func as 2nd argument", name) 205 | } 206 | if funcVal.FuncProto.IsProc { 207 | funl.RunTimeError2(frame, "%s: proc not allowed", name) 208 | } 209 | case funl.ExtProcValue: 210 | extFuncVal, extfuncOK := arguments[1].Data.(funl.ExtProcType) 211 | if !extfuncOK { 212 | funl.RunTimeError2(frame, "%s: not ext-func as 2nd argument", name) 213 | } 214 | if !extFuncVal.IsFunction { 215 | funl.RunTimeError2(frame, "%s: ext-proc not allowed", name) 216 | } 217 | default: 218 | funl.RunTimeError2(frame, "%s: assuming function as argument", name) 219 | } 220 | 221 | varref.Lock() 222 | defer varref.Unlock() 223 | 224 | oldVal := *(varref.ValRef) 225 | 226 | argsForCall := []*funl.Item{ 227 | { 228 | Type: funl.ValueItem, 229 | Data: arguments[1], 230 | }, 231 | { 232 | Type: funl.ValueItem, 233 | Data: oldVal, 234 | }, 235 | } 236 | newVal, callErr := func() (rv funl.Value, errDesc error) { 237 | defer func() { 238 | if r := recover(); r != nil { 239 | var rtestr string 240 | if err, isError := r.(error); isError { 241 | rtestr = err.Error() 242 | } 243 | errDesc = fmt.Errorf("%s", rtestr) 244 | } 245 | }() 246 | return funl.HandleCallOP(frame, argsForCall), nil 247 | }() 248 | var rval funl.Value 249 | var errtext string 250 | if callErr == nil { 251 | rval = newVal 252 | varref.ValRef = &newVal 253 | } else { 254 | errtext = callErr.Error() 255 | rval = funl.Value{Kind: funl.StringValue, Data: ""} 256 | } 257 | 258 | values := []funl.Value{ 259 | { 260 | Kind: funl.BoolValue, 261 | Data: callErr == nil, 262 | }, 263 | { 264 | Kind: funl.StringValue, 265 | Data: errtext, 266 | }, 267 | rval, 268 | } 269 | retVal = funl.MakeListOfValues(frame, values) 270 | return 271 | } 272 | } 273 | 274 | func getStdVarSet(name string) stdFuncType { 275 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 276 | if l := len(arguments); l != 2 { 277 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need two", name, l) 278 | } 279 | if arguments[0].Kind != funl.OpaqueValue { 280 | funl.RunTimeError2(frame, "%s: assuming opaque var-ref", name) 281 | } 282 | varref, convOK := arguments[0].Data.(*OpaqueVarRef) 283 | if !convOK { 284 | funl.RunTimeError2(frame, "%s: assuming var-ref", name) 285 | } 286 | newval := arguments[1] 287 | varref.Lock() 288 | varref.ValRef = &newval 289 | varref.Unlock() 290 | retVal = funl.Value{Kind: funl.BoolValue, Data: true} 291 | return 292 | } 293 | } 294 | 295 | func getStdVarValue(name string) stdFuncType { 296 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 297 | if l := len(arguments); l != 1 { 298 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 299 | } 300 | if arguments[0].Kind != funl.OpaqueValue { 301 | funl.RunTimeError2(frame, "%s: assuming opaque var-ref", name) 302 | } 303 | varref, convOK := arguments[0].Data.(*OpaqueVarRef) 304 | if !convOK { 305 | funl.RunTimeError2(frame, "%s: assuming var-ref", name) 306 | } 307 | varref.RLock() 308 | retVal = *(varref.ValRef) 309 | varref.RUnlock() 310 | return 311 | } 312 | } 313 | 314 | func getStdVarNew(name string) stdFuncType { 315 | return func(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) { 316 | if l := len(arguments); l != 1 { 317 | funl.RunTimeError2(frame, "%s: wrong amount of arguments (%d), need one", name, l) 318 | } 319 | val := arguments[0] 320 | varref := &OpaqueVarRef{ValRef: &val} 321 | retVal = funl.Value{Kind: funl.OpaqueValue, Data: varref} 322 | return 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /stdfun/.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.fnl linguist-language=funl 3 | -------------------------------------------------------------------------------- /stdfun/repl.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | import stdio 5 | import stdstr 6 | 7 | # Note. some symbols are named with prefix ___ 8 | # so that clash between user let-definitions would be less probable 9 | 10 | ___handleHelp = proc() 11 | _ = call(stdio.printline 'Input can be:') 12 | _ = call(stdio.printline ' help -> prints this help') 13 | _ = call(stdio.printline ' ? -> prints this help') 14 | _ = call(stdio.printline ' quit -> exits repl') 15 | _ = call(stdio.printline ' exit -> exits repl') 16 | _ = call(stdio.printline ' -> evaluates expression and prints result') 17 | _ = call(stdio.printline '') 18 | _ = call(stdio.printline 'Adding < to end of line causes repl to gather more input before evaluation') 19 | _ = call(stdio.printline '') 20 | true 21 | end 22 | 23 | ___strip-last = func(prev) 24 | case( len(prev) 25 | 0 error('odd input') 26 | 1 '' 27 | slice(prev 0 minus(len(prev) 2)) 28 | ) 29 | end 30 | 31 | ___getmore = proc(prev-input) 32 | _ = call(stdio.printout 'funl>... ') 33 | more-input = plus(prev-input call(stdio.readinput)) 34 | 35 | if( call(stdstr.endswith more-input '<') 36 | call(___getmore call(___strip-last more-input)) 37 | if( call(stdstr.endswith more-input '<') 38 | call(___strip-last more-input) 39 | more-input 40 | ) 41 | ) 42 | end 43 | 44 | ___repl = proc() 45 | _ = call(stdio.printout 'funl> ') 46 | ___input = call(stdio.readinput) 47 | ___continue = not(in(list('quit' 'exit') ___input)) 48 | 49 | ___real-input = if( call(stdstr.endswith ___input '<') 50 | call(___getmore call(___strip-last ___input)) 51 | ___input 52 | ) 53 | #_ = print(':' real-input ':') 54 | 55 | result = case( ___input 56 | '' true 57 | 'quit' false 58 | 'exit' false 59 | 'help' call(___handleHelp) 60 | '?' call(___handleHelp) 61 | call(stdio.printline try(eval(___real-input))) 62 | ) 63 | 64 | while(___continue 'done') 65 | end 66 | 67 | main = proc() 68 | _ = call(stdio.printline 'Welcome to FunL REPL (interactive command shell)') 69 | call(___repl) 70 | end 71 | 72 | endns 73 | -------------------------------------------------------------------------------- /stdfun/stddbc.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stddbc 3 | 4 | bt-to-str = func(callchain) 5 | loopy = func(rest-of result) 6 | if(empty(rest-of) 7 | result 8 | call(func() 9 | next-str = call(print-citem head(rest-of)) 10 | call(loopy rest(rest-of) plus(result next-str)) 11 | end) 12 | ) 13 | end 14 | 15 | call(loopy callchain '') 16 | end 17 | 18 | print-citem = func(citem) 19 | sprintf( 20 | ' file: %s line: %d args: %v\n' 21 | get(citem 'file') 22 | get(citem 'line') 23 | get(citem 'args') 24 | ) 25 | end 26 | 27 | assert = call(proc() 28 | import stdos 29 | import stdrun 30 | 31 | _ env-value = call(stdos.getenv 'FUNL_DBC_CC'): 32 | 33 | func(condition errtext) 34 | condtype = type(condition) 35 | condval = case( condtype 36 | 'bool' condition 37 | 'function' call(condition) 38 | true 39 | ) 40 | bt = call(stdrun.backtrace) 41 | prev = if( lt(len(bt) 2) 42 | map('file' '-' 'line' 0 'args' list()) 43 | head(rest(bt)) 44 | ) 45 | printing = case(env-value 46 | 'nocc' 47 | errtext 48 | 49 | 'prev' 50 | sprintf('%s:\nfrom: %s' errtext call(print-citem prev)) 51 | 52 | sprintf('%s:\ncall chain:\n%s' errtext call(bt-to-str bt)) 53 | ) 54 | if(condval true error(printing)) 55 | end 56 | end) 57 | 58 | endns 59 | -------------------------------------------------------------------------------- /stdfun/stdfilu.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdfilu 3 | 4 | import stdfiles 5 | import stdfu 6 | 7 | get-files-by-ext = proc(path, extension) 8 | matcher = func(filename) 9 | in(filename, plus('.', extension)) 10 | end 11 | 12 | result = call(stdfiles.read-dir, path) 13 | if( eq(type(result), 'string'), 14 | result, 15 | call(stdfu.filter, keys(result), matcher) 16 | ) 17 | end 18 | 19 | get-subdirs = proc(path) 20 | matcher = proc(v) 21 | finfo = call(stdfiles.finfo-map v) 22 | and( 23 | in(finfo 'is-dir') 24 | get(finfo 'is-dir') 25 | ) 26 | end 27 | 28 | looper = proc(kvs results) 29 | while( not(empty(kvs)) 30 | rest(kvs) 31 | call(proc() 32 | finfo = head(kvs) 33 | fname fm = finfo: 34 | if( call(matcher fm) 35 | append(results fname) 36 | results 37 | ) 38 | end) 39 | results 40 | ) 41 | end 42 | 43 | fmap = call(stdfiles.read-dir path) 44 | call(looper keyvals(fmap) list()) 45 | end 46 | 47 | get-nondirs = proc(path) 48 | matcher = proc(v) 49 | finfo = call(stdfiles.finfo-map v) 50 | and( 51 | in(finfo 'is-dir') 52 | not(get(finfo 'is-dir')) 53 | ) 54 | end 55 | 56 | looper = proc(kvs results) 57 | while( not(empty(kvs)) 58 | rest(kvs) 59 | call(proc() 60 | finfo = head(kvs) 61 | fname fm = finfo: 62 | if( call(matcher fm) 63 | append(results fname) 64 | results 65 | ) 66 | end) 67 | results 68 | ) 69 | end 70 | 71 | fmap = call(stdfiles.read-dir path) 72 | call(looper keyvals(fmap) list()) 73 | end 74 | 75 | endns 76 | -------------------------------------------------------------------------------- /stdfun/stdfu.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdfu 3 | 4 | select-keys = func(m keylist) 5 | keylooper = func(remaining-keys result) 6 | if( empty(remaining-keys) 7 | result 8 | call(func() 9 | nextkey = head(remaining-keys) 10 | found val = getl(m nextkey): 11 | next-result = if(found put(result nextkey val) result) 12 | call(keylooper rest(remaining-keys) next-result) 13 | end) 14 | ) 15 | end 16 | 17 | call(keylooper keylist map()) 18 | end 19 | 20 | pre-decorate = func(pre-handler handler) 21 | func() 22 | new-args = call(pre-handler argslist():) 23 | call(handler new-args:) 24 | end 25 | end 26 | 27 | post-decorate = func(handler post-handler) 28 | func() 29 | retval = call(handler argslist():) 30 | new-retval = call(post-handler retval argslist():) 31 | new-retval 32 | end 33 | end 34 | 35 | p-pre-decorate = func(pre-handler handler) 36 | proc() 37 | new-args = call(pre-handler argslist():) 38 | call(handler new-args:) 39 | end 40 | end 41 | 42 | p-post-decorate = func(handler post-handler) 43 | proc() 44 | retval = call(handler argslist():) 45 | new-retval = call(post-handler retval argslist():) 46 | new-retval 47 | end 48 | end 49 | 50 | loop = func(handler inlist result) 51 | while( not(empty(inlist)) 52 | handler 53 | rest(inlist) 54 | call(handler head(inlist) result) 55 | result 56 | ) 57 | end 58 | 59 | ploop = proc(handler inlist result) 60 | while( not(empty(inlist)) 61 | handler 62 | rest(inlist) 63 | call(handler head(inlist) result) 64 | result 65 | ) 66 | end 67 | 68 | chain = func(input, handler-list) 69 | applyHandler = func(handler-chain, output) 70 | while( not(empty(handler-chain)), 71 | rest(handler-chain), 72 | call(head(handler-chain), output), 73 | output 74 | ) 75 | end 76 | 77 | call(applyHandler, handler-list, input) 78 | end 79 | 80 | foreach = func(lst, handler, initial) 81 | looper = func(l, cum) 82 | while( not(empty(l)), 83 | rest(l), 84 | call(handler, head(l), cum), 85 | cum 86 | ) 87 | end 88 | 89 | if( eq(type(lst), 'list'), 90 | call(looper, lst, initial), 91 | error('foreach needs list as argument, not ', type(lst)) 92 | ) 93 | end 94 | 95 | max = func(lst greater-than-func) 96 | comparator = func(item-1 item-2) 97 | call(greater-than-func item-1 item-2) 98 | end 99 | 100 | if( eq(type(lst) 'list') 101 | case( len(lst) 102 | 0 error('empty list') 103 | 1 head(lst) 104 | call(foreach rest(lst) comparator head(lst)) 105 | ) 106 | error(sprintf('argument not a list (%s)' type(lst))) 107 | ) 108 | end 109 | 110 | zip = func(keylist, valuelist) 111 | looper = func(keyl, values, m) 112 | while( not(empty(keyl)), 113 | rest(keyl), 114 | rest(values), 115 | if( in(m, head(keyl)), 116 | m, 117 | put(m, head(keyl), head(values)) 118 | ), 119 | m 120 | ) 121 | end 122 | 123 | if( eq(len(keylist), len(valuelist)), 124 | call(looper, keylist, valuelist, map()), 125 | error('zip needs list to be same length') 126 | ) 127 | end 128 | 129 | generate = func(startv, stopv, generFunc) 130 | looper = func(i, targetlist) 131 | while(le(i, stopv), 132 | plus(i, 1), 133 | call(func(idx) 134 | newv = call(generFunc, idx) 135 | append(targetlist, newv) 136 | end, i), 137 | targetlist 138 | ) 139 | end 140 | 141 | ok1 = eq(type(startv), 'int') 142 | ok2 = eq(type(stopv), 'int') 143 | case(list(ok1, ok2), 144 | list(true, false), error('2nd argument not int'), 145 | list(false, true), error('1st argument not int'), 146 | list(false, false), error('1st and 2nd argument are not int'), 147 | list(true, true), call(looper, startv, list()) 148 | ) 149 | end 150 | 151 | apply = func(srclist, converter) 152 | applyConv = func(l, newl, cnt) 153 | if( empty(l), 154 | newl, 155 | call( 156 | func() 157 | remainingList = rest(l) 158 | item = head(l) 159 | convertedItem = call(converter, item) 160 | call(applyConv, remainingList, append(newl, convertedItem), plus(cnt,1)) 161 | end 162 | ) 163 | ) 164 | end 165 | 166 | call(applyConv, srclist, list(), 1) 167 | end 168 | 169 | proc-apply = proc(srclist, converter) 170 | applyConv = proc(l, newl, cnt) 171 | if( empty(l), 172 | newl, 173 | call( 174 | proc() 175 | remainingList = rest(l) 176 | item = head(l) 177 | convertedItem = call(converter, item) 178 | call(applyConv, remainingList, append(newl, convertedItem), plus(cnt,1)) 179 | end 180 | ) 181 | ) 182 | end 183 | 184 | call(applyConv, srclist, list(), 1) 185 | end 186 | 187 | filter = func(srcdata, condition) 188 | map-filter = func(src-map) 189 | looper = func(kvs, resultm) 190 | next-kv = if(not(empty(kvs)), head(kvs), 'whatever') 191 | 192 | while( not(empty(kvs)), 193 | rest(kvs), 194 | if( call(condition, head(next-kv), last(next-kv)), 195 | put(resultm, head(next-kv), last(next-kv)), 196 | resultm 197 | ), 198 | resultm 199 | ) 200 | end 201 | 202 | call(looper, keyvals(src-map), map()) 203 | end 204 | 205 | list-filter = func(srclist) 206 | handleNext = func(l, newl) 207 | applyCond = func() 208 | item = head(l) 209 | remainingList = rest(l) 210 | 211 | if( call(condition, item), 212 | call(handleNext, remainingList, append(newl, item)), 213 | call(handleNext, remainingList, newl) 214 | ) 215 | end 216 | 217 | if( empty(l), 218 | newl, 219 | call(applyCond) 220 | ) 221 | end 222 | 223 | call(handleNext, srclist, list()) 224 | end 225 | 226 | case( type(srcdata), 227 | 'list', call(list-filter, srcdata), 228 | 'map', call(map-filter, srcdata), 229 | error('non-supported type: ', type(srcdata)) 230 | ) 231 | end 232 | 233 | # true if condition(item) is true for all, false otherwise 234 | applies-for-all = func(srclist, condition) 235 | result-list = call(filter, srclist, condition) 236 | eq(len(result-list), len(srclist)) 237 | end 238 | 239 | # true if condition(item) is true for any, false otherwise 240 | applies-for-any = func(srclist, condition) 241 | result-list = call(filter, srclist, condition) 242 | not(eq(result-list, list())) 243 | end 244 | 245 | group-by = func(srcdata, grouper) 246 | append-to-list = func(resm akey aval) 247 | prevl = get(resm akey) 248 | newl = append(prevl aval) 249 | newm = del(resm akey) 250 | put(newm akey newl) 251 | end 252 | 253 | map-group-by = func(src-map) 254 | looper = func(kvs result) 255 | next-kv = if(not(empty(kvs)) head(kvs) 'whatever') 256 | 257 | while( not(empty(kvs)) 258 | rest(kvs) 259 | call(func() 260 | kv vv = head(kvs): 261 | key value = call(grouper kv vv): 262 | if( in(result key) 263 | call(append-to-list result key value) 264 | put(result key list(value)) 265 | ) 266 | end) 267 | result 268 | ) 269 | end 270 | 271 | call(looper, keyvals(src-map), map()) 272 | end 273 | 274 | list-group-by = func(srclist) 275 | looper = func(kvl result) 276 | while( not(empty(kvl)) 277 | rest(kvl) 278 | call(func() 279 | key value = call(grouper head(kvl)): 280 | if( in(result key) 281 | call(append-to-list result key value) 282 | put(result key list(value)) 283 | ) 284 | end) 285 | result 286 | ) 287 | end 288 | 289 | call(looper srclist map()) 290 | end 291 | 292 | case( type(srcdata), 293 | 'list', call(list-group-by, srcdata), 294 | 'map', call(map-group-by, srcdata), 295 | error('non-supported type: ', type(srcdata)) 296 | ) 297 | end 298 | 299 | merge = func() 300 | args = argslist() 301 | is-conflict-handler-given = eq(type(head(args)) 'function') 302 | offset = if(is-conflict-handler-given 1 0) 303 | 304 | conflict-handler = if( is-conflict-handler-given 305 | head(args) 306 | func(key val1 val2) 307 | list(false val2) 308 | end 309 | ) 310 | 311 | looper = func(kvs result) 312 | while( not(empty(kvs)) 313 | rest(kvs) 314 | call(func() 315 | key value = head(kvs): 316 | is-key-in = in(result key) 317 | do-add chosen-val = if( is-key-in 318 | call(conflict-handler key get(result key) value) 319 | list(true value) 320 | ): 321 | if(do-add 322 | if(is-key-in 323 | put(del(result key) key chosen-val) 324 | put(result key chosen-val) 325 | ) 326 | result 327 | ) 328 | end) 329 | result 330 | ) 331 | end 332 | 333 | map-looper = func(mlist result) 334 | while( not(empty(mlist)) 335 | rrest(mlist) 336 | call(looper keyvals(last(mlist)) result) 337 | result 338 | ) 339 | end 340 | 341 | call(map-looper slice(args offset) map()) 342 | end 343 | 344 | pipe = func(func-list) 345 | func(input) 346 | looper = func(flist inp) 347 | while( not(empty(flist)) 348 | rest(flist) 349 | call(head(flist) inp) 350 | inp 351 | ) 352 | end 353 | 354 | call(looper func-list input) 355 | end 356 | end 357 | 358 | proc-pipe = func(proc-list) 359 | proc(input) 360 | looper = proc(flist inp) 361 | while( not(empty(flist)) 362 | rest(flist) 363 | call(head(flist) inp) 364 | inp 365 | ) 366 | end 367 | 368 | call(looper proc-list input) 369 | end 370 | end 371 | 372 | pairs-to-map = func(kv-list) 373 | pair-to-map = func(item result) 374 | put(result head(item) last(item)) 375 | end 376 | 377 | call(loop pair-to-map kv-list map()) 378 | end 379 | 380 | # overwrites key-value if found, ignored if not found 381 | write-if-found = func(src delta) 382 | add-to = func(result kvs) 383 | if(empty(kvs) 384 | result 385 | call(func() 386 | key val = head(kvs): 387 | next = if(in(result key) 388 | put(del(result key) key val) 389 | result 390 | ) 391 | call(add-to next rest(kvs)) 392 | end) 393 | ) 394 | end 395 | 396 | call(add-to src keyvals(delta)) 397 | end 398 | 399 | # writes key-values regardless is it found or not 400 | write-all = func(src delta) 401 | add-to = func(result kvs) 402 | if(empty(kvs) 403 | result 404 | call(func() 405 | key val = head(kvs): 406 | next = if(in(result key) 407 | put(del(result key) key val) 408 | put(result key val) 409 | ) 410 | call(add-to next rest(kvs)) 411 | end) 412 | ) 413 | end 414 | 415 | call(add-to src keyvals(delta)) 416 | end 417 | 418 | # write key-values which are not yet found 419 | write-if-not-found = func(src delta) 420 | add-to = func(result kvs) 421 | if(empty(kvs) 422 | result 423 | call(func() 424 | key val = head(kvs): 425 | next = if(in(result key) 426 | result 427 | put(result key val) 428 | ) 429 | call(add-to next rest(kvs)) 430 | end) 431 | ) 432 | end 433 | 434 | call(add-to src keyvals(delta)) 435 | end 436 | 437 | endns 438 | -------------------------------------------------------------------------------- /stdfun/stdfun_generator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | fs, _ := ioutil.ReadDir("./stdfun/") 13 | out, err := os.Create("./funl/stdfunfiles.go") 14 | if err != nil { 15 | fmt.Println(fmt.Sprintf("Error in generating std .fun files (%v)", err)) 16 | return 17 | } 18 | 19 | out.Write([]byte("package funl \n\nfunc init() {\n")) 20 | for _, f := range fs { 21 | if strings.HasSuffix(f.Name(), ".fnl") { 22 | keyStr := `"` + strings.TrimSuffix(f.Name(), ".fnl") + `"` 23 | out.Write([]byte("\n\tstdfunMap[" + keyStr + "] = `")) 24 | f, err := os.Open("./stdfun/" + f.Name()) 25 | if err != nil { 26 | fmt.Println(fmt.Sprintf("Error in reading std .fun files (%v)", err)) 27 | f.Close() 28 | break 29 | } 30 | io.Copy(out, f) 31 | out.Write([]byte("`\n")) 32 | f.Close() 33 | } 34 | } 35 | out.Write([]byte("}\n")) 36 | out.Close() 37 | } 38 | -------------------------------------------------------------------------------- /stdfun/stdmeta.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdmeta 3 | 4 | /* 5 | todo 6 | 7 | - list('list' list-schema) 8 | -> validates with list schema 9 | 10 | - list('exact-keys' 'f1' 'f2') 11 | -> should have exactly following keys (not less, not more) 12 | 13 | - list('len' 5) 14 | -> length of list or map (key-values) checked 15 | -> ranges too ? 16 | 17 | - list('all-in-list' list('type' 'int')) 18 | -> requirement(s) for all items in list 19 | -> could it be also for all key-values in map ? 20 | 21 | - validation with customized function(s)...? 22 | 23 | */ 24 | 25 | # gathers documentation from schema 26 | get-doc = func(schema) 27 | import stdfu 28 | 29 | visit-map = func(map-schema indent result-str) 30 | map-fields-visit = func(kvitem output) 31 | get-field-visitor = func(mkey) 32 | func(check out-str) 33 | fout = case( head(check) 34 | 'required' 35 | 'required, ' 36 | 'type' 37 | plus('type: ' head(rest(check)) ', ') 38 | 'map' 39 | call(visit-map head(rest(check)) plus(indent ' ') 'map: ') 40 | 'doc' 41 | call(func() 42 | doc-lines = call(stdfu.apply rest(check) func(item) plus('\n' indent ' -> ' item) end) 43 | plus(doc-lines: ', ') 44 | end) 45 | 'in' 46 | plus('allowed: [ ' call(stdfu.apply rest(check) func(x) plus(str(x) ' ') end): '], ') 47 | error('illegal tag: ' str(head(check))) 48 | ) 49 | plus(out-str fout) 50 | end 51 | end 52 | 53 | keyv checklist = kvitem: 54 | field-output = call(stdfu.loop call(get-field-visitor keyv) checklist '') 55 | plus(output '\n' indent str(keyv) ' : ' field-output) 56 | end 57 | 58 | if( eq(type(map-schema) 'map') 59 | call(func() 60 | map-output = call(stdfu.loop map-fields-visit keyvals(map-schema) '') 61 | plus('\n' indent 'map: ' map-output) 62 | end) 63 | 'requires map' 64 | ) 65 | end 66 | 67 | if( and( eq(type(schema) 'list') not(empty(schema))) 68 | case( head(schema) 69 | 'map' call(visit-map head(rest(schema)) '' '') 70 | 'none' 71 | ) 72 | 'requires non-empty list' 73 | ) 74 | end 75 | 76 | # validates data against schema 77 | validate = func(schema srcdata) 78 | import stdfu 79 | 80 | validate-map = func(map-schema map-data field-path) 81 | map-fields-checker = func(kvitem results) 82 | get-field-checker = func(mkey) 83 | func(check result-list) 84 | _ msg-list = result-list: 85 | passed message = case( head(check) 86 | 'required' 87 | if( in(map-data mkey) 88 | result-list 89 | list(false append(msg-list sprintf('required field %v not found (%s)' mkey field-path))) 90 | ) 91 | 92 | 'in' 93 | call(func() 94 | key-found val = getl(map-data mkey): 95 | allowed = rest(check) 96 | cond( 97 | not(key-found) 98 | result-list 99 | not(in(allowed val)) 100 | list(false append(msg-list sprintf('field %v is not in allowed set (%v not in: %v)(%s)' mkey val allowed field-path))) 101 | result-list 102 | ) 103 | end) 104 | 105 | 'type' 106 | call(func() 107 | key-found val = getl(map-data mkey): 108 | required-type = head(rest(check)) 109 | cond( 110 | not(key-found) 111 | result-list 112 | not(eq(type(val) required-type)) 113 | list(false append(msg-list sprintf('field %v is not required type (got: %v, expected: %v)(%s)' mkey type(val) required-type field-path))) 114 | result-list 115 | ) 116 | end) 117 | 118 | 'map' 119 | call(func() 120 | submap-found submap-data = getl(map-data mkey): 121 | cond( 122 | not(submap-found) 123 | result-list 124 | not(eq(type(submap-data) 'map')) 125 | list(false append(msg-list sprintf('field %v is not map (%s)' mkey field-path))) 126 | call(validate-map head(rest(check)) submap-data plus(field-path ' -> ' str(mkey))) 127 | ) 128 | end) 129 | 130 | 'doc' 131 | result-list 132 | 133 | list(false append(msg-list sprintf('unknown validator: %s' str(head(check))))) 134 | ): 135 | list(passed message) 136 | end 137 | end 138 | 139 | keyv checklist = kvitem: 140 | call(stdfu.loop call(get-field-checker keyv) checklist results) 141 | end 142 | 143 | if( eq(type(map-schema) 'map') 144 | call(stdfu.loop map-fields-checker keyvals(map-schema) list(true list())) 145 | list(false list('requires map')) 146 | ) 147 | end 148 | 149 | if( and( eq(type(schema) 'list') not(empty(schema))) 150 | case( head(schema) 151 | 'map' call(validate-map head(rest(schema)) srcdata '') 152 | list(false list(sprintf('unknown validator: %s' str(head(schema)) ))) 153 | ) 154 | list(false list('requires non-empty list')) 155 | ) 156 | end 157 | 158 | endns 159 | 160 | -------------------------------------------------------------------------------- /stdfun/stdpp.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdpp 3 | 4 | pprint = proc(val) 5 | import stdio 6 | 7 | value-str = call(pform val) 8 | _ = call(stdio.printline value-str) 9 | val 10 | end 11 | 12 | form = func(val) 13 | import stdfu 14 | 15 | indent-mark = if( gt(len(argslist()) 1) 16 | call(func() 17 | _ marker = argslist(): 18 | if( eq(type(marker) 'string') marker '\t') 19 | end argslist():) 20 | '\t' # tab is default 21 | ) 22 | 23 | print-list = func(v indent) 24 | call(stdfu.foreach v func(x cum) call(print-value x plus(indent indent-mark) cum) end '') 25 | end 26 | 27 | print-map = func(v indent) 28 | call(stdfu.foreach keyvals(v) func(kv cum) 29 | mkey mvalue = kv: 30 | cum2 = call(print-value mkey plus(indent indent-mark) cum) 31 | call(print-value mvalue plus(indent indent-mark) cum2) 32 | end '') 33 | end 34 | 35 | print-value = func(v indent res) 36 | item-str = case( type(v) 37 | 'string' plus('\n' indent '\'' v '\'') 38 | 'list' plus('\n' indent plus('list(' call(print-list v indent) '\n' indent ')') ) 39 | 'map' plus('\n' indent plus('map(' call(print-map v indent) '\n' indent ')') ) 40 | plus('\n' indent str(v)) 41 | ) 42 | plus(res item-str) 43 | end 44 | 45 | call(print-value val '' '') 46 | end 47 | 48 | pform = proc(val) 49 | import stdfu 50 | 51 | indent-mark = if( gt(len(argslist()) 1) 52 | call(proc() 53 | _ marker = argslist(): 54 | if( eq(type(marker) 'string') marker '\t') 55 | end argslist():) 56 | '\t' # tab is default 57 | ) 58 | 59 | print-list = proc(v indent) 60 | call(stdfu.ploop proc(x cum) call(print-value x plus(indent indent-mark) cum) end v '') 61 | end 62 | 63 | print-map = proc(v indent) 64 | call(stdfu.ploop proc(kv cum) 65 | mkey mvalue = kv: 66 | cum2 = call(print-value mkey plus(indent indent-mark) cum) 67 | call(print-value mvalue plus(indent indent-mark) cum2) 68 | end keyvals(v) '') 69 | end 70 | 71 | print-value = proc(v indent res) 72 | item-str = case( type(v) 73 | 'string' plus('\n' indent '\'' v '\'') 74 | 'list' plus('\n' indent plus('list(' call(print-list v indent) '\n' indent ')') ) 75 | 'map' plus('\n' indent plus('map(' call(print-map v indent) '\n' indent ')') ) 76 | plus('\n' indent str(v)) 77 | ) 78 | plus(res item-str) 79 | end 80 | 81 | call(print-value val '' '') 82 | end 83 | 84 | endns 85 | 86 | -------------------------------------------------------------------------------- /stdfun/stdpr.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdpr 3 | 4 | get-pr = func(do-print) 5 | if( do-print 6 | func(text value) 7 | _ = print(text value) 8 | value 9 | end 10 | 11 | func(_ value) value end 12 | ) 13 | end 14 | 15 | get-pp-pr = func(do-print) 16 | if( do-print 17 | func(text value) 18 | import stdpp 19 | 20 | _ = print(text call(stdpp.form value)) 21 | value 22 | end 23 | 24 | func(_ value) value end 25 | ) 26 | end 27 | 28 | endns 29 | -------------------------------------------------------------------------------- /stdfun/stdser.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdser 3 | 4 | import stdjson 5 | import stdbytes 6 | import stdfu 7 | import stdbase64 8 | 9 | tags = list('int' 'float' 'bool' 'string' 'list' 'map' 'bytearray') 10 | 11 | encode = func(val) 12 | enc-bytearray = func(inval) 13 | enc-ok enc-err retv = call(stdbase64.encode inval): 14 | _ = if(enc-ok '' error(enc-err)) 15 | retv 16 | end 17 | 18 | handle-item = func(inval) 19 | vtype = type(inval) 20 | case( vtype 21 | 'int' list('int' inval) 22 | 'float' list('float' inval) 23 | 'bool' list('bool' inval) 24 | 'string' list('string' inval) 25 | 'opaque:bytearray' list('bytearray' call(enc-bytearray inval)) 26 | 'list' list('list' call(stdfu.apply inval func(item) call(handle-item item) end)) 27 | 'map' list('map' call(stdfu.apply keyvals(inval) func(item) k v = item: list(call(handle-item k) call(handle-item v)) end)) 28 | error('unsupported type: ' vtype) 29 | ) 30 | end 31 | 32 | call(stdjson.encode call(handle-item val)) 33 | end 34 | 35 | decode = func(val) 36 | handle-map = func(ml) 37 | mapper = func(pair resultm) 38 | kpair vpair = pair: 39 | put(resultm call(handle-pair kpair) call(handle-pair vpair)) 40 | end 41 | 42 | call(stdfu.loop mapper ml map()) 43 | end 44 | 45 | dec-bytearray = func(inval) 46 | dec-ok dec-err retv = call(stdbase64.decode inval): 47 | _ = if(dec-ok '' error(dec-err)) 48 | retv 49 | end 50 | 51 | handle-pair = func(pairval) 52 | tag value = pairval: 53 | case( tag 54 | 'int' value 55 | 'float' value 56 | 'bool' value 57 | 'string' value 58 | 'bytearray' call(dec-bytearray value) 59 | 'list' call(stdfu.apply value func(pair) call(handle-pair pair) end) 60 | 'map' call(handle-map value) 61 | error('unsupported tag: ' tag) 62 | ) 63 | end 64 | 65 | ok err fuval = call(stdjson.decode val): 66 | if( ok 67 | list(true '' call(handle-pair fuval)) 68 | list(false err fuval) 69 | ) 70 | end 71 | 72 | endns 73 | 74 | -------------------------------------------------------------------------------- /stdfun/stdset.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdset 3 | 4 | # newset creates new set 5 | newset = func() 6 | map() 7 | end 8 | 9 | # is-empty returns true if there is no items in set, otherwise true 10 | is-empty = func(set) 11 | eq(len(set), 0) 12 | end 13 | 14 | # setlen returns number of items in set 15 | setlen = func(set) 16 | len(set) 17 | end 18 | 19 | # as-list returns set items in list 20 | as-list = func(set) 21 | keys(set) 22 | end 23 | 24 | # has-item returns true if item is in set, false otherwise 25 | has-item = func(set, item) 26 | in(set, item) 27 | end 28 | 29 | # removes item from set (if it is in it) 30 | remove-from-set = func(set item) 31 | if( in(set item) 32 | del(set item) 33 | set 34 | ) 35 | end 36 | 37 | # add-to-set adds one item to set 38 | add-to-set = func(set, item) 39 | if( in(set, item), 40 | set, 41 | put(set, item, true) 42 | ) 43 | end 44 | 45 | # list-to-set adds one item to set 46 | list-to-set = func(set, itemlist) 47 | looper = func(iteml, setv) 48 | while( not(empty(iteml)), 49 | rest(iteml), 50 | call(add-to-set, setv, head(iteml)), 51 | setv 52 | ) 53 | end 54 | 55 | call(looper, itemlist, set) 56 | end 57 | 58 | # union creates union of two sets given as arguments 59 | union = func(set1, set2) 60 | set-to-add = if( gt(len(set1), len(set2)), 61 | set2, 62 | set1 63 | ) 64 | target-set = if( gt(len(set1), len(set2)), 65 | set1, 66 | set2 67 | ) 68 | call(list-to-set, target-set, keys(set-to-add)) 69 | end 70 | 71 | # helper function 72 | get-matching-subset = func(source-list, condition) 73 | looper = func(iteml, resultl) 74 | while( not(empty(iteml)), 75 | rest(iteml), 76 | call(func() 77 | item = head(iteml) 78 | if( call(condition, item), 79 | append(resultl, item), 80 | resultl 81 | ) 82 | end), 83 | resultl 84 | ) 85 | end 86 | 87 | itemlist = call(looper, source-list, list()) 88 | call(list-to-set, call(newset), itemlist) 89 | end 90 | 91 | # intersection creates intersection set of two sets given as arguments 92 | intersection = func(set1, set2) 93 | keys1 = keys(set1) 94 | keys2 = keys(set2) 95 | 96 | condition = func(item) and( in(keys1, item), in(keys2, item) ) end 97 | call(get-matching-subset, extend(keys1, keys2), condition) 98 | end 99 | 100 | # difference returns set with elements in set1 but not in set2 101 | difference = func(set1, set2) 102 | keys1 = keys(set1) 103 | keys2 = keys(set2) 104 | 105 | condition = func(item) and( in(keys1, item), not(in(keys2, item)) ) end 106 | call(get-matching-subset, extend(keys1, keys2), condition) 107 | end 108 | 109 | # is-subset return true if subset -argument is subset of set -argument 110 | is-subset = func(set, subset) 111 | looper = func(iteml, result) 112 | while( not(empty(iteml)), 113 | rest(iteml), 114 | and(result, in(set, head(iteml))), 115 | result 116 | ) 117 | end 118 | 119 | call(looper, keys(subset), true) 120 | end 121 | 122 | # equal returns true if two sets given as arguments are having same items, false otherwise 123 | equal = func(set1, set2) 124 | len1 = call(setlen, set1) 125 | len2 = call(setlen, set2) 126 | 127 | if( eq(len1, len2), 128 | call(is-subset, set1, set2), 129 | false 130 | ) 131 | end 132 | 133 | endns 134 | -------------------------------------------------------------------------------- /stdfun/stdsort.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdsort 3 | 4 | # merges two sorted lists to one 5 | merge = func(lst1 lst2) 6 | do-merge = func(rl1 rl2 res) 7 | ready nrl1 nrl2 nres = cond( 8 | empty(rl1) cond( 9 | empty(rl2) list(true rl1 rl2 res) 10 | list(false rl1 rest(rl2) append(res head(rl2))) 11 | ) 12 | 13 | empty(rl2) cond( 14 | empty(rl1) list(true rl1 rl2 res) 15 | list(false rest(rl1) rl2 append(res head(rl1))) 16 | ) 17 | 18 | if( lt(head(rl1) head(rl2)) 19 | list(false rest(rl1) rl2 append(res head(rl1))) 20 | list(false rl1 rest(rl2) append(res head(rl2))) 21 | ) 22 | ): 23 | 24 | while( not(ready) 25 | nrl1 26 | nrl2 27 | nres 28 | nres 29 | ) 30 | end 31 | 32 | call(do-merge lst1 lst2 list()) 33 | end 34 | 35 | # merges two sorted lists to one 36 | merge-with-func = func(lst1 lst2 compa-func) 37 | do-merge = func(rl1 rl2 res) 38 | ready nrl1 nrl2 nres = cond( 39 | empty(rl1) cond( 40 | empty(rl2) list(true rl1 rl2 res) 41 | list(false rl1 rest(rl2) append(res head(rl2))) 42 | ) 43 | 44 | empty(rl2) cond( 45 | empty(rl1) list(true rl1 rl2 res) 46 | list(false rest(rl1) rl2 append(res head(rl1))) 47 | ) 48 | 49 | if( call(compa-func head(rl1) head(rl2)) 50 | list(false rest(rl1) rl2 append(res head(rl1))) 51 | list(false rl1 rest(rl2) append(res head(rl2))) 52 | ) 53 | ): 54 | 55 | while( not(ready) 56 | nrl1 57 | nrl2 58 | nres 59 | nres 60 | ) 61 | end 62 | 63 | call(do-merge lst1 lst2 list()) 64 | end 65 | 66 | # implements sort by using mergesort 67 | sort = func(src-lst) 68 | use-func compa-func = if( eq(len(argslist()) 2) 69 | list(true last(argslist())) 70 | list(false func() 'not used' end) 71 | ): 72 | 73 | real-sort = func(lst) 74 | l = len(lst) 75 | 76 | get-slices = func() 77 | middle = div(l 2) 78 | left = slice(lst 0 middle) 79 | right = slice(lst plus(middle 1) l) 80 | list(left right) 81 | end 82 | 83 | left-lst right-lst = call(get-slices): 84 | case( l 85 | 0 error('unexpected empty list') 86 | 1 lst 87 | 2 if( use-func 88 | call(merge-with-func list(head(lst)) list(last(lst)) compa-func) 89 | call(merge list(head(lst)) list(last(lst)) ) 90 | ) 91 | if( use-func 92 | call(merge-with-func call(real-sort left-lst) call(real-sort right-lst) compa-func) 93 | call(merge call(real-sort left-lst) call(real-sort right-lst)) 94 | ) 95 | ) 96 | end 97 | 98 | if( empty(src-lst) 99 | list() 100 | call(real-sort src-lst) 101 | ) 102 | end 103 | 104 | endns 105 | 106 | -------------------------------------------------------------------------------- /tester.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns main 3 | 4 | PRINT-OVERALL = 1 5 | PRINT-MOD = 2 6 | PRINT-ALL = 3 7 | 8 | import stdio 9 | import stdfilu 10 | import stdfu 11 | import stdstr 12 | 13 | get-files = proc(target-dir) 14 | result = call(stdfilu.get-files-by-ext target-dir 'fnl') 15 | is-error = eq(type(result) 'string') 16 | _ = if(is-error call(stdio.printf 'error in reading: %s\n' result) '') 17 | if( is-error 18 | list(false list()) 19 | list(true result) 20 | ) 21 | end 22 | 23 | get-list-of-mods = func(file-list) 24 | fl1 = call(stdfu.apply file-list func(fit) head(split(fit '.fnl')) end) 25 | fl2 = call(stdfu.filter fl1 func(fit) call(stdstr.endswith fit '_test') end) 26 | list(true fl2) 27 | end 28 | 29 | get-tests-from-mod = proc(testmod) 30 | modmap = try(eval(sprintf('imp(%s)' testmod))) 31 | import-nok = if( eq(type(modmap) 'string') print('Import failed: ' modmap) false) 32 | if( import-nok 33 | map() 34 | call(proc() 35 | test-proc-filter = func(proc-name _) 36 | or( 37 | call(stdstr.startswith proc-name 'test') 38 | call(stdstr.startswith proc-name 'Test') 39 | ) 40 | end 41 | call(stdfu.filter modmap test-proc-filter) 42 | end) 43 | ) 44 | end 45 | 46 | get-test-list-for-mod = proc(target-dir mod-name) 47 | ok file-list = call(get-files target-dir): 48 | 49 | _ modlist = call(get-list-of-mods file-list): 50 | target-mod-list = call(stdfu.filter modlist func(mname) eq(mname mod-name) end) 51 | mod-found = not(empty(target-mod-list)) 52 | tests-from-mod = if( mod-found 53 | call(get-tests-from-mod head(target-mod-list)) 54 | map() 55 | ) 56 | list(mod-found tests-from-mod) 57 | end 58 | 59 | get-all-tests = proc(target-dir) 60 | ok file-list = call(get-files target-dir): 61 | #_ = print(file-list) 62 | 63 | gather-from-one-mod = proc(onemod result-map) 64 | looper = func(kvlist resm) 65 | while( not(empty(kvlist)) 66 | rest(kvlist) 67 | put(resm plus(onemod ' : ' head(head(kvlist))) last(head(kvlist))) 68 | resm 69 | ) 70 | end 71 | 72 | testmap = call(get-tests-from-mod onemod) 73 | call(looper keyvals(testmap) result-map) 74 | end 75 | 76 | gather-from-mods = proc(modlist) 77 | looper = proc(mlist result-map) 78 | while( not(empty(mlist)) 79 | rest(mlist) 80 | call(gather-from-one-mod head(mlist) result-map) 81 | result-map 82 | ) 83 | end 84 | 85 | list(true call(looper modlist map())) 86 | end 87 | 88 | get-list-of-tests = proc() 89 | mod-ok modlist = call(get-list-of-mods file-list): 90 | if( mod-ok 91 | call(gather-from-mods modlist) 92 | list(false list()) 93 | ) 94 | end 95 | 96 | if( ok 97 | call(get-list-of-tests) 98 | list(false list()) 99 | ) 100 | end 101 | 102 | get-test-list-for-test = proc(target-dir tcase) 103 | ok test-case-map = call(get-all-tests target-dir): 104 | matching-map = call(stdfu.filter test-case-map 105 | func(tc-name _) 106 | tc-part = call(stdstr.strip last(split(tc-name ':'))) 107 | eq(tc-part tcase) 108 | end 109 | ) 110 | list( 111 | and(ok not(empty(matching-map))) 112 | matching-map 113 | ) 114 | end 115 | 116 | main = proc() 117 | args = argslist() 118 | target-defined target-dir = if( and( not(empty(args)) eq(type(head(args)) 'string') ) 119 | list(true head(args)) 120 | list(true '.') 121 | ): 122 | options-map = if( and( gt(len(args) 1) eq(type(ind(args 1)) 'map')) 123 | ind(args 1) 124 | map() 125 | ) 126 | is-mod-defined = in(options-map 'mod') 127 | is-test-defined = in(options-map 'test') 128 | are-all-executed = not(or(is-mod-defined is-test-defined)) 129 | found print-val = getl(options-map 'print'): 130 | print-opt = case( if(found print-val 'overall') 131 | 'all' PRINT-ALL 132 | 'mod' PRINT-MOD 133 | 'overall' PRINT-OVERALL 134 | PRINT-OVERALL 135 | ) 136 | 137 | run-tests = proc() 138 | ok test-case-map = cond( 139 | and(is-test-defined is-mod-defined) 140 | call(get-test-list-for-test-and-mod target-dir get(options-map 'test') get(options-map 'mod')) 141 | 142 | is-test-defined 143 | call(proc() 144 | tcase = get(options-map 'test') 145 | tc-found tcm = call(get-test-list-for-test target-dir tcase): 146 | _ = if(not(tc-found) 147 | call(stdio.printf 'Test case not found (%s)\n' tcase) 148 | '') 149 | list(tc-found tcm) 150 | end) 151 | 152 | is-mod-defined 153 | call(proc() 154 | tmod = get(options-map 'mod') 155 | mod-found tcm = call(get-test-list-for-mod target-dir tmod): 156 | _ = if(not(mod-found) 157 | call(stdio.printf 'Test module not found (%s)\n' tmod) 158 | '') 159 | list(mod-found tcm) 160 | end) 161 | 162 | call(get-all-tests target-dir) 163 | ): 164 | 165 | run-one-test = proc(test-name test-proc) 166 | retv = try(call(test-proc)) 167 | rte-text = if( eq(type(retv) 'string') sprintf('(%s)' retv) '') 168 | tc-result = cond( 169 | eq(type(retv) 'string') false 170 | retv true 171 | false 172 | ) 173 | _ = if( in(list(PRINT-ALL PRINT-MOD) print-opt) 174 | call(stdio.printf '%s %s : %s %s\n' if(tc-result '---' '<<<') if(tc-result 'PASS' 'FAIL') test-name rte-text) 175 | '' 176 | ) 177 | tc-result 178 | end 179 | 180 | run-cases = proc(tc-kvs) 181 | looper = proc(tckvs cnt-list) 182 | while( not(empty(tckvs)) 183 | rest(tckvs) 184 | call(proc() 185 | tcname tcproc = head(tckvs): 186 | tc-passed = call(run-one-test tcname tcproc) 187 | pass-count fail-count = cnt-list: 188 | if( tc-passed 189 | list(plus(pass-count 1) fail-count) 190 | list(pass-count plus(fail-count 1)) 191 | ) 192 | end) 193 | cnt-list 194 | ) 195 | end 196 | 197 | passed failed = call(looper tc-kvs list(0 0)): 198 | all-passed = eq(failed 0) 199 | _ = if( all-passed 200 | call(stdio.printf 'PASSED (%d)\n' passed) 201 | call(stdio.printf 'FAILED (passed=%d)(failed=%d)\n' passed failed) 202 | ) 203 | all-passed 204 | end 205 | 206 | if( ok 207 | call(run-cases keyvals(test-case-map)) 208 | false 209 | ) 210 | end 211 | 212 | if( target-defined 213 | call(run-tests) 214 | call(proc() _ = call(stdio.printf 'no target directory defined\n') false end) 215 | ) 216 | end 217 | 218 | all = proc() 219 | call(main list(argslist(): map('print' 'all')):) 220 | end 221 | 222 | endns 223 | -------------------------------------------------------------------------------- /tst/.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.fnl linguist-language=funl 3 | -------------------------------------------------------------------------------- /tst/arithm_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns arithm_test 3 | 4 | testPlusWithSingleArgument = func() 5 | and( 6 | eq(plus(10) 10) 7 | eq(plus(10.25) 10.25) 8 | eq(plus('ok') 'ok') 9 | ) 10 | end 11 | 12 | testMulWithSingleArgument = func() 13 | and( 14 | eq(mul(10) 10) 15 | eq(mul(0.5) 0.5) 16 | ) 17 | end 18 | 19 | testSumOfIntegers = func() 20 | result = plus(1 ,2, 3) 21 | eq(result, 6) 22 | end 23 | 24 | testDivisionByZeroForFloat = proc() 25 | text = 'assumed' 26 | result = try(div(10, float(0)), text) 27 | result2 = try(div(10, 0.0), text) 28 | and(eq(result, text), eq(result2, text)) 29 | end 30 | 31 | testSumOfFloats = func() 32 | fval1 = float(10) 33 | fval2 = div(float(3), float(10)) 34 | fval3 = div(float(2), float(10)) 35 | 36 | expected = div(float(105), float(10)) # 10.5 37 | 38 | result = plus(fval1, fval2, fval3, 0.0) 39 | eq(result, expected) 40 | end 41 | 42 | testDiffOfFloats = func() 43 | x = div(float(5), float(10)) 44 | y = div(float(2), float(10)) 45 | result = minus(x, y) 46 | eq(result, div(float(3), float(10))) 47 | end 48 | 49 | testDiffOfIntegers = func() 50 | result = minus(6, 2) 51 | eq(result, 4) 52 | end 53 | 54 | testFloatAndIntDivisions = func() 55 | result = list( 56 | div(10, 2), 57 | type(div(10, 2)), 58 | div(float(10), 2), 59 | type(div(float(10), 2)), 60 | div(10, float(2)), 61 | type(div(10, float(2))), 62 | div(float(10), float(2)), 63 | type(div(float(10), float(2))) 64 | ) 65 | expected = list( 66 | 5, 67 | 'int', 68 | float(5), 69 | 'float', 70 | float(5), 71 | 'float', 72 | float(5), 73 | 'float' 74 | ) 75 | eq(result, expected) 76 | end 77 | 78 | testMultiplyOfFloats = func() 79 | x = div(float(5), float(100)) # 0.05 80 | y = float(10) 81 | expected = div(float(5), float(10)) # 0.5 82 | result = mul(x, y) 83 | and(eq(result, expected), eq(type(result), 'float')) 84 | end 85 | 86 | testMultiplyOfFloats2 = func() 87 | x = 0.05 88 | y = 10.0 89 | expected = 0.5 90 | result = mul(x, y) 91 | and(eq(result, expected), eq(type(result), 'float')) 92 | end 93 | 94 | testMultiplyOfIntsAndFloats = func() 95 | x = div(float(5), float(100)) # 0.05 96 | y = 10 97 | expected = float(2) 98 | result = mul(x, y, 4) 99 | and(eq(result, expected), eq(type(result), 'float')) 100 | end 101 | 102 | testMultiplyOfIntegers = func() 103 | result = mul(6, 2, 10) 104 | eq(result, 120) 105 | end 106 | 107 | testDivisionOfIntegers = func() 108 | result = list( 109 | div(20, 3), 110 | div(20, 10), 111 | div(2, 6), 112 | mod(20, 3), 113 | mod(20, 10), 114 | mod(2, 6) 115 | ) 116 | expected = list(6, 2, 0, 2, 0, 2) 117 | eq(result, expected) 118 | end 119 | 120 | testCombinations = func() 121 | result = list( 122 | plus( mul(mul(minus(9,1), 2), div(4, 2)), 8), 123 | plus( minus(1, 1), minus(1, 5), div(4, 1)) 124 | ) 125 | expected = list(40, 0) 126 | eq(result, expected) 127 | end 128 | 129 | testDivisionByZero = proc() 130 | text = 'assumed' 131 | result = try(div(10, 0), text) 132 | eq(result, text) 133 | end 134 | 135 | testInvalidTypes = proc() 136 | text = 'assumed' 137 | result = list( 138 | try(div('abc', 1), text), 139 | try(mod(true, 1), text), 140 | try(plus(10, list()), text), 141 | try(minus('abc', 0), text), 142 | try(mul('abc', 1), text) 143 | ) 144 | 145 | eq(result, list(text,text,text,text,text)) 146 | end 147 | 148 | endns 149 | -------------------------------------------------------------------------------- /tst/comparison_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns comparison_test 3 | 4 | import ut_fwk 5 | 6 | testIFcases = func() 7 | result = list( 8 | if(true, 'a', 'b'), 9 | if(not(true), 'a', 'b') 10 | ) 11 | expected = list( 12 | 'a', 13 | 'b' 14 | ) 15 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 16 | end 17 | 18 | testEQcases = func() 19 | l1 = list('aa', 11, 22) 20 | 21 | result = list( 22 | eq('abc', plus('a', 'bc')), 23 | eq(100, plus(50,50)), 24 | eq(true, not(false)), 25 | eq( list('aa', 11, mul(11, 2)), l1), 26 | eq(1, '1'), 27 | eq(1, 1, 2), 28 | eq(1, 1, 1), 29 | eq(float(1), float(1), float(1)), 30 | eq(float(1), float(1), float(2)) 31 | ) 32 | expected = list( 33 | true, 34 | true, 35 | true, 36 | true, 37 | false, 38 | false, 39 | true, 40 | true, 41 | false 42 | ) 43 | 44 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 45 | end 46 | 47 | testGTcases = proc() 48 | result = list( 49 | gt(2, 1), 50 | gt(1, 2), 51 | gt(1, 1), 52 | try(gt(1, '1'), true), 53 | try(gt(1, 1, 2), true), 54 | try(gt(1), true), 55 | 56 | gt(float(2), float(1)), 57 | gt(float(2), 1), 58 | gt(2, float(1)), 59 | gt(float(1), float(1)), 60 | gt(float(1), 1), 61 | gt(1, float(1)), 62 | gt(float(1), float(2)), 63 | gt(float(1), 2), 64 | gt(1, float(2)), 65 | 66 | true 67 | ) 68 | expected = list( 69 | true, 70 | false, 71 | false, 72 | true, 73 | true, 74 | true, 75 | 76 | true, 77 | true, 78 | true, 79 | false, 80 | false, 81 | false, 82 | false, 83 | false, 84 | false, 85 | 86 | true 87 | ) 88 | 89 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 90 | end 91 | 92 | testGEcases = proc() 93 | result = list( 94 | ge(2, 1), 95 | ge(1, 2), 96 | ge(1, 1), 97 | try(ge(1, '1'), true), 98 | try(ge(1, 1, 2), true), 99 | try(ge(1), true), 100 | 101 | ge(float(2), float(1)), 102 | ge(float(2), 1), 103 | ge(2, float(1)), 104 | ge(float(1), float(1)), 105 | ge(float(1), 1), 106 | ge(1, float(1)), 107 | ge(float(1), float(2)), 108 | ge(float(1), 2), 109 | ge(1, float(2)), 110 | 111 | true 112 | ) 113 | expected = list( 114 | true, 115 | false, 116 | true, 117 | true, 118 | true, 119 | true, 120 | 121 | true, 122 | true, 123 | true, 124 | true, 125 | true, 126 | true, 127 | false, 128 | false, 129 | false, 130 | 131 | true 132 | ) 133 | 134 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 135 | end 136 | 137 | testLTcases = proc() 138 | result = list( 139 | lt(1, 2), 140 | lt(2, 1), 141 | lt(1, 1), 142 | try(lt(1, '1'), true), 143 | try(lt(1, 1, 2), true), 144 | try(lt(1), true), 145 | 146 | lt(float(1), float(2)), 147 | lt(float(1), 2), 148 | lt(1, float(2)), 149 | lt(float(1), float(1)), 150 | lt(float(1), 1), 151 | lt(1, float(1)), 152 | lt(float(2), float(1)), 153 | lt(float(2), 1), 154 | lt(2, float(1)), 155 | 156 | true 157 | ) 158 | expected = list( 159 | true, 160 | false, 161 | false, 162 | true, 163 | true, 164 | true, 165 | 166 | true, 167 | true, 168 | true, 169 | false, 170 | false, 171 | false, 172 | false, 173 | false, 174 | false, 175 | 176 | true 177 | ) 178 | 179 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 180 | end 181 | 182 | testLEcases = proc() 183 | result = list( 184 | le(1, 2), 185 | le(2, 1), 186 | le(1, 1), 187 | try(le(1, '1'), true), 188 | try(le(1, 1, 2), true), 189 | try(le(1), true), 190 | 191 | le(float(1), float(2)), 192 | le(float(1), 2), 193 | le(1, float(2)), 194 | le(float(1), float(1)), 195 | le(float(1), 1), 196 | le(1, float(1)), 197 | le(float(2), float(1)), 198 | le(float(2), 1), 199 | le(2, float(1)), 200 | 201 | true 202 | ) 203 | expected = list( 204 | true, 205 | false, 206 | true, 207 | true, 208 | true, 209 | true, 210 | 211 | true, 212 | true, 213 | true, 214 | true, 215 | true, 216 | true, 217 | false, 218 | false, 219 | false, 220 | 221 | true 222 | ) 223 | 224 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 225 | end 226 | 227 | testCondCase = proc() 228 | yes = true 229 | result = list( 230 | try(cond( 231 | eq(10 11) '1' 232 | not(true) '2' 233 | yes '3' 234 | 'default' 235 | )) 236 | try(cond( 237 | eq(10 11) '1' 238 | true '2' 239 | yes '3' 240 | 'default' 241 | )) 242 | try(cond( 243 | eq(10 11) '1' 244 | not(true) '2' 245 | false '3' 246 | 'default' 247 | )) 248 | try(cond( 249 | eq(10 11) '1' 250 | 'default' 251 | )) 252 | try(cond( 253 | eq(10 10) '1' 254 | 'default' 255 | )) 256 | try(cond( 257 | eq(10 11) '1' 258 | true '2' 259 | not(false) '3' 260 | 'default' 261 | )) 262 | try(cond( 263 | eq(11 11) '1' 264 | true '2' 265 | not(false) '3' 266 | 'default' 267 | )) 268 | ) 269 | 270 | expected = list( 271 | '3' '2' 'default' 'default' '1' '2' '1' 272 | ) 273 | 274 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 275 | end 276 | 277 | testCaseCase = func() 278 | numberAsString = func(d) str(d) end 279 | result = list( 280 | case( 281 | call(numberAsString, 1), 282 | '1', 1, 283 | '2', 2, 284 | 'default' 285 | ), 286 | case( 287 | call(numberAsString, 2), 288 | '1', 1, 289 | '2', 2, 290 | 'default' 291 | ), 292 | case( 293 | call(numberAsString, 3), 294 | '1', 1, 295 | '2', 2, 296 | 'default' 297 | ), 298 | case( 299 | call(numberAsString, 2), 300 | '1', 1, 301 | '2', 2 302 | ) 303 | ) 304 | expected = list( 305 | 1, 306 | 2, 307 | 'default', 308 | 2 309 | ) 310 | 311 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 312 | end 313 | 314 | testEQMismatchedTypes = proc() 315 | text = 'got error' 316 | result = list( 317 | try(eq(), text) 318 | ) 319 | expected = list( 320 | text 321 | ) 322 | 323 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 324 | end 325 | 326 | endns 327 | -------------------------------------------------------------------------------- /tst/expansion_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns expansion_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | 10 | returnArguments = func(p1 _ p3) 11 | list( 12 | list(p1 p3) 13 | argslist() 14 | ) 15 | end 16 | 17 | returnArguments2 = func() 18 | argslist() 19 | end 20 | 21 | variadicF = func() 22 | list(argslist():) 23 | end 24 | 25 | returnArguments3 = func() 26 | call(variadicF, argslist():) 27 | end 28 | 29 | testArgslist = proc() 30 | arg-val-1 = 'some test value 1' 31 | dum-val = 'dum' 32 | arg-val-2 = 'some test value 2' 33 | 34 | l1 l2 = call(returnArguments arg-val-1 dum-val arg-val-2): 35 | p1 p2 = l1: 36 | 37 | and( 38 | eq(p1 arg-val-1) 39 | eq(p2 arg-val-2) 40 | ) 41 | end 42 | 43 | testArgslistWithSubFunction = proc() 44 | arg-val-1 = 'some test value 1' 45 | dum-val = 'dum' 46 | arg-val-2 = 'some test value 2' 47 | 48 | subReturnArguments = func(p1 _ p3) 49 | list( 50 | list(p1 p3) 51 | argslist() 52 | ) 53 | end 54 | 55 | l1 l2 = call(subReturnArguments arg-val-1 dum-val arg-val-2): 56 | p1 p2 = l1: 57 | 58 | and( 59 | eq(p1 arg-val-1) 60 | eq(p2 arg-val-2) 61 | ) 62 | end 63 | 64 | testArgslistAsVariadicFunction = proc() 65 | argl = list(1 2 3) 66 | l = call(returnArguments2 argl:) 67 | eq(l argl) 68 | end 69 | 70 | testArgslistAsVariadicFunction2 = proc() 71 | argl = list(1 2 3) 72 | l = call(returnArguments3 argl:) 73 | eq(l argl) 74 | end 75 | 76 | testArgslistWithNoArguments = proc() 77 | l = call(returnArguments2) 78 | eq(l list()) 79 | end 80 | 81 | testExpandOperCall = proc() 82 | l1 = list(2 4 1 3) 83 | l2 = list('a' 'b' 'c' 'd') 84 | l3 = list('e' 'f' 'g' 'h') 85 | l4 = list() 86 | l5 = list(10) 87 | and( 88 | eq(plus(l1:) 10) 89 | eq(plus(l2:) 'abcd') 90 | eq(plus(l2: l3:) 'abcdefgh') 91 | eq(plus('<' l2: '_' l3: '>') '') 92 | eq(plus('<' l2: '_' l3: l4: '>') '') 93 | eq( list(list():) list() ) 94 | eq( str(l5:) '10' ) 95 | ) 96 | end 97 | 98 | testExpandLetDefInFunction = proc() 99 | l = list(1 2 3) 100 | a b c = l: 101 | x _ z = l: 102 | v = list(10): 103 | 104 | and( 105 | eq(a 1) 106 | eq(b 2) 107 | eq(c 3) 108 | eq(x 1) 109 | eq(z 3) 110 | eq(v 10) 111 | ) 112 | end 113 | 114 | G_l = list(1 2 3) 115 | G_a G_b G_c = G_l: 116 | G_x _ G_z = G_l: 117 | G_v = list(10): 118 | 119 | testExpandLetDefInNamespace = proc() 120 | and( 121 | eq(G_a 1) 122 | eq(G_b 2) 123 | eq(G_c 3) 124 | eq(G_x 1) 125 | eq(G_z 3) 126 | eq(G_v 10) 127 | ) 128 | end 129 | 130 | endns 131 | -------------------------------------------------------------------------------- /tst/func_proc_call_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns func_proc_call_test 3 | 4 | import ut_fwk 5 | 6 | defaultText = 'ok' 7 | p2 = 'xxx' 8 | 9 | functionToTest = func(p1, p2x, p3) 10 | retval = defaultText 11 | plus(retval, ':', str(p1), ':', p2x, ':', str(p3)) 12 | end 13 | 14 | testFunctionCall = func() 15 | result = call(functionToTest, 1, '2', true) 16 | call(ut_fwk.VERIFY, eq(result, 'ok:1:2:true'), plus('Unexpected result = ', str(result))) 17 | end 18 | 19 | testAnonymousFuncCall = func() 20 | p3 = 'yep' 21 | result = call(func() v = p3 v end) 22 | call(ut_fwk.VERIFY, eq(result, 'yep'), plus('Unexpected result = ', str(result))) 23 | end 24 | 25 | testAnonymousFuncCall2 = func() 26 | p3 = 'yep' 27 | retf = func(p1) 28 | func() v = plus(p1, p3) v end 29 | end 30 | f = call(retf, '...') 31 | result = call(f) 32 | call(ut_fwk.VERIFY, eq(result, '...yep'), plus('Unexpected result = ', str(result))) 33 | end 34 | 35 | testClosureAndSubfunction = func() 36 | v11 = 'should not be used' 37 | subf = func(p11) 38 | v111 = p11 39 | subsubf = func(p111) 40 | call(functionToTest, v111, plus(p111, p2), not(true)) 41 | end 42 | 43 | subsubf 44 | end 45 | 46 | clos = call(subf, 100) 47 | result = call(clos, 'any') 48 | call(ut_fwk.VERIFY, eq(result, 'ok:100:anyxxx:false'), plus('Unexpected result = ', str(result))) 49 | end 50 | 51 | testNotEnoughArguments = proc() 52 | assumed = 'expected error' 53 | result = try(call(functionToTest, 1, '2'), assumed) 54 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 55 | end 56 | 57 | testNonExistingFunctionCalled = proc() 58 | assumed = 'expected error' 59 | result = try(call(nonExistFunction), assumed) 60 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 61 | end 62 | 63 | testNonFunctionCalled = proc() 64 | assumed = 'expected error' 65 | result = try(call('this is not function'), assumed) 66 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 67 | end 68 | 69 | testFunctionCallingProcedure = proc() 70 | someProcedure = proc() 'hello' end 71 | middleFunction = func() call(someProcedure) end 72 | 73 | assumed = 'expected error' 74 | result = try(call(middleFunction), assumed) 75 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 76 | end 77 | 78 | /* 79 | testImportFailing = proc() 80 | testf = func() 81 | import non_existent_module 82 | 'you shouldnt see this' 83 | end 84 | 85 | assumed = 'expected error' 86 | result = try(call(testf), assumed) 87 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 88 | end 89 | */ 90 | 91 | testWhile = func() 92 | subf = func(n, l) 93 | nexti = minus(n, 1) 94 | 95 | while( 96 | not(eq(n, 0)), 97 | nexti, 98 | append(l, n), 99 | l 100 | ) 101 | end 102 | 103 | result = call(subf, 5, list()) 104 | assumed = list(5, 4, 3, 2, 1) 105 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 106 | end 107 | 108 | testWhile2 = func() 109 | getInnerF = func() 110 | func(p) minus(p, 1) end 111 | end 112 | 113 | subf = func(n, l) 114 | innerF = call(getInnerF) 115 | nexti = call(innerF, n) 116 | 117 | while( 118 | not(eq(n, 0)), 119 | nexti, 120 | append(l, n), 121 | l 122 | ) 123 | end 124 | 125 | result = call(subf, 5, list()) 126 | assumed = list(5, 4, 3, 2, 1) 127 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 128 | end 129 | 130 | testWhile3 = func() 131 | repeat = func(x, n, l) 132 | counter = minus(n, 1) 133 | while( 134 | not(eq(counter, 0)), 135 | x, 136 | counter, 137 | append(l, mul(x, 10)), 138 | l 139 | ) 140 | end 141 | 142 | subf = func(n, l) 143 | nexti = minus(n, 1) 144 | 145 | while( 146 | not(eq(n, 0)), 147 | nexti, 148 | call(repeat, n, 4, l), 149 | l 150 | ) 151 | end 152 | 153 | result = call(subf, 5, list()) 154 | assumed = list(50, 50, 50, 40, 40, 40, 30, 30, 30, 20, 20, 20, 10, 10, 10) 155 | call(ut_fwk.VERIFY, eq(result, assumed), plus('Unexpected result = ', str(result))) 156 | end 157 | 158 | endns 159 | -------------------------------------------------------------------------------- /tst/impure_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns impure_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | testBufferedChannel = proc() 9 | ch = chan(2) 10 | 11 | ret-1 = recwith(ch map('wait' false)) 12 | tst-result-1 = call(ASSURE eq(ret-1 list(false '')) plus('Unexpected value from recwith: ' str(ret-1))) 13 | 14 | ret-2 = list( 15 | send(ch 'any value' map('wait' false)) 16 | send(ch 'any value' map('wait' false)) 17 | send(ch 'any value' map('wait' false)) 18 | ) 19 | tst-result-2 = call(ASSURE eq(ret-2 list(true true false)) plus('Unexpected value from send: ' str(ret-2))) 20 | 21 | and(tst-result-1 tst-result-2) 22 | end 23 | 24 | testOneFiberCreateAndReply = proc() 25 | replyCh = chan() 26 | testMsg = 'this is test message' 27 | fib = proc() 28 | send(replyCh, testMsg) 29 | end 30 | _ = spawn(call(fib)) 31 | reply = recv(replyCh) 32 | 33 | call(ASSURE, eq(testMsg, reply), plus('Unexpected reply = ', str(reply))) 34 | end 35 | 36 | testOneFiberCreateAndReply2 = proc() 37 | replyCh = chan() 38 | testMsg = 'this is test message' 39 | _ = spawn(send(replyCh, testMsg)) 40 | reply = recv(replyCh) 41 | 42 | call(ASSURE, eq(testMsg, reply), plus('Unexpected reply = ', str(reply))) 43 | end 44 | 45 | testTwoFiberCommAsRing = proc() 46 | fib = proc(ch, nextch) 47 | send(nextch, plus(recv(ch), '.')) 48 | end 49 | 50 | testMsg = 'this is test message' 51 | ch1 = chan() 52 | ch2 = chan() 53 | ch3 = chan() 54 | _ = spawn(call(fib, ch2, ch3)) 55 | _ = spawn(call(fib, ch1, ch2)) 56 | _ = send(ch1, testMsg) 57 | reply = recv(ch3) 58 | call(ASSURE, eq(plus(testMsg, '..'), reply), plus('Unexpected reply = ', str(reply))) 59 | end 60 | 61 | testStackedFibers = proc() 62 | amount = 10 63 | 64 | fib = proc(count, replych) 65 | nextCount = plus(count, 1) 66 | 67 | if( eq(nextCount, amount), 68 | send(replych, list(minus(nextCount, 1), str(nextCount))), 69 | call(proc(repch) 70 | wchan = chan() 71 | _ = spawn(call(fib, nextCount, wchan)) 72 | val = recv(wchan) 73 | ncnt = minus(head(val), 1) 74 | nstr = plus(last(val), str(ncnt)) 75 | send(repch, list(ncnt, nstr)) 76 | end, 77 | replych 78 | ) 79 | ) 80 | end 81 | 82 | resultch = chan() 83 | _ = spawn(call(fib, 1, resultch)) 84 | resultVal = recv(resultch) 85 | 86 | call(ASSURE, eq(resultVal, list(1, '1087654321')), plus('Unexpected resultVal = ', str(resultVal))) 87 | end 88 | 89 | testSelectReceive = proc() 90 | resultCh = chan() 91 | channels = list(chan(), chan(), chan()) 92 | quitch = chan() 93 | controlCh = chan() 94 | 95 | sender = proc(num) 96 | send(ind(channels, num), num) 97 | end 98 | 99 | receiver = proc(count, s) 100 | getMsgHandler = func(char) 101 | proc(message) 102 | _ = send(controlCh, true) 103 | plus(char, str(message), ':') 104 | end 105 | end 106 | 107 | msg = select( 108 | ind(channels, 0), call(getMsgHandler, 'A'), 109 | ind(channels, 1), call(getMsgHandler, 'B'), 110 | ind(channels, 2), call(getMsgHandler, 'C'), 111 | quitch, func(m) s end 112 | ) 113 | 114 | while( lt(count, 3), 115 | plus(count, 1), 116 | plus(s, msg), 117 | send(resultCh, msg) 118 | ) 119 | end 120 | 121 | _ = spawn(call(receiver, 0, '')) 122 | 123 | _ = spawn(call(sender, 0)) 124 | _ = spawn(call(sender, 1)) 125 | _ = spawn(call(sender, 2)) 126 | 127 | wait = proc(n) 128 | while( lt(n, 3), 129 | call( 130 | proc(nn) 131 | _ = recv(controlCh) 132 | plus(nn, 1) 133 | end, 134 | n 135 | ), 136 | true 137 | ) 138 | end 139 | 140 | _ = call(wait, 0) 141 | _ = send(quitch, 'quit') 142 | result = split(recv(resultCh), ':') 143 | 144 | allOk = and( 145 | in(result, 'A0'), 146 | in(result, 'B1'), 147 | in(result, 'C2'), 148 | in(result, ''), 149 | eq(len(result), 4) 150 | ) 151 | call(ASSURE, allOk, plus('Unexpected result = ', str(result))) 152 | end 153 | 154 | testSelectReceiveWithListOfHandlers = proc() 155 | resultCh = chan() 156 | channels = list(chan(), chan(), chan()) 157 | quitch = chan() 158 | controlCh = chan() 159 | 160 | sender = proc(num) 161 | send(ind(channels, num), num) 162 | end 163 | 164 | receiver = proc(count, s) 165 | getMsgHandler = func(char) 166 | proc(message) 167 | _ = send(controlCh, true) 168 | plus(char, str(message), ':') 169 | end 170 | end 171 | 172 | handlers = list( 173 | call(getMsgHandler, 'A'), 174 | call(getMsgHandler, 'B'), 175 | call(getMsgHandler, 'C'), 176 | func(m) s end 177 | ) 178 | 179 | msg = select(append(channels, quitch), handlers) 180 | 181 | while( lt(count, 3), 182 | plus(count, 1), 183 | plus(s, msg), 184 | send(resultCh, msg) 185 | ) 186 | end 187 | 188 | _ = spawn(call(receiver, 0, '')) 189 | 190 | _ = spawn(call(sender, 0)) 191 | _ = spawn(call(sender, 1)) 192 | _ = spawn(call(sender, 2)) 193 | 194 | wait = proc(n) 195 | while( lt(n, 3), 196 | call( 197 | proc(nn) 198 | _ = recv(controlCh) 199 | plus(nn, 1) 200 | end, 201 | n 202 | ), 203 | true 204 | ) 205 | end 206 | 207 | _ = call(wait, 0) 208 | _ = send(quitch, 'quit') 209 | result = split(recv(resultCh), ':') 210 | 211 | allOk = and( 212 | in(result, 'A0'), 213 | in(result, 'B1'), 214 | in(result, 'C2'), 215 | in(result, ''), 216 | eq(len(result), 4) 217 | ) 218 | call(ASSURE, allOk, plus('Unexpected result = ', str(result))) 219 | end 220 | 221 | endns 222 | -------------------------------------------------------------------------------- /tst/list_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns list_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | 10 | testListBasics = proc() 11 | otherlist = list(1, 2, 3) 12 | testlist = list('abc', 123, true, list(), otherlist) 13 | 14 | result = list( 15 | eq(head(testlist), 'abc'), 16 | eq(head(rest(testlist)), 123), 17 | eq(head(rest(rest(rest(testlist)))), list()), 18 | eq(head(rest(rest(rest(rest(testlist))))), otherlist), 19 | eq(head(rest(rest(rest(rest(testlist))))), last(testlist)), 20 | eq(last(testlist), otherlist), 21 | eq(len(otherlist), 3), 22 | eq(len(testlist), 5), 23 | eq(len(extend(otherlist, testlist)), 8), 24 | eq(extend(otherlist, testlist, list(10, 20)), list(1, 2, 3, 'abc', 123, true, list(), otherlist, 10, 20)), 25 | eq(append(otherlist, 'new'), list(1, 2, 3, 'new')), 26 | eq(append(otherlist, 'new', 'new2'), list(1, 2, 3, 'new', 'new2')), 27 | eq(add(otherlist, 'new'), list('new', 1, 2, 3)), 28 | eq(add(otherlist, 'new', 'new2'), list('new', 'new2', 1, 2, 3)), 29 | eq(empty(list()), true), 30 | eq(empty(list(list())), false), 31 | eq(empty(testlist), false), 32 | eq(rest(otherlist), list(2, 3)), 33 | eq(rrest(otherlist), list(1, 2)), 34 | eq(reverse(otherlist), list(3, 2, 1)), 35 | eq(reverse(list()), list()), 36 | eq(in(otherlist, 2), true), 37 | eq(in(otherlist, 4), false), 38 | eq(ind(testlist, 2), true), 39 | eq(ind(testlist, 0), 'abc'), 40 | try(ind(testlist, 100), true), 41 | eq(find(otherlist, 3), list(2)), 42 | eq(find(extend(otherlist, list(1, 3, 3)), 3), list(2, 4, 5)), 43 | eq(find(list(1,2,3), 4), list()), 44 | eq(slice(testlist, 1, 3), list(123, true, list())), 45 | eq(slice(testlist, 1, 1), list(123)), 46 | eq(slice(testlist, 2), list(true, list(), otherlist)), 47 | eq(slice(otherlist, 100), list()), 48 | eq(slice(otherlist, 1, 100), list(2, 3)) 49 | 50 | eq( extend(list(1 2 3) list(4 5 6) list(7 8 9)) list(1 2 3 4 5 6 7 8 9)) 51 | eq( extend(list() list() list()) list()) 52 | eq( extend(list(1 2)) list(1 2)) 53 | eq( extend(list()) list()) 54 | eq( extend(list(1 2 3) list()) list(1 2 3)) 55 | 56 | eq( extend(list() list() list()) list()) 57 | eq( extend(list() list(4 5 6)) list(4 5 6)) 58 | 59 | eq( extend(list(1 2) append(list(3) 4) ) list(1 2 3 4)) 60 | eq( extend(list() append(list(3) 4) ) list(3 4)) 61 | eq( extend(list() append(list() 4) ) list(4)) 62 | eq( extend(list(1 2) append(list(3) 4) append(list(5) 6) ) list(1 2 3 4 5 6)) 63 | eq( extend(list(1 2) append(list(3) 4) list(5 6)) list(1 2 3 4 5 6)) 64 | ) 65 | allRight = call(common_test_util.isAllTrueInList, result) 66 | call(ASSURE, allRight, plus('Unexpected result = ', str(result))) 67 | end 68 | 69 | testListTypeErrors = proc() 70 | notlist = 'this is not list' 71 | result = list( 72 | try(head(notlist), true), 73 | try(last(notlist), true), 74 | try(extend(notlist, list(1,2)), true), 75 | try(add(notlist, 1), true), 76 | try(append(notlist, 1), true), 77 | try(empty(notlist), true), 78 | try(rest(notlist), true), 79 | try(rrest(notlist), true), 80 | try(reverse(notlist), true), 81 | true 82 | ) 83 | allRight = call(common_test_util.isAllTrueInList, result) 84 | call(ASSURE, allRight, plus('Unexpected result = ', str(result))) 85 | end 86 | 87 | endns 88 | 89 | -------------------------------------------------------------------------------- /tst/logic_oper_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns logic_oper_test 3 | 4 | testAndOperBasics = func() 5 | result = and(true, true) 6 | eq(result, true) 7 | end 8 | 9 | testOrOperBasics = func() 10 | result = or(true, true) 11 | eq(result, true) 12 | end 13 | 14 | testAndOperWithIllegalValue = proc() 15 | expectText = 'error as expected' 16 | result = try(and(true, 'this is illegal'), expectText) 17 | eq(result, expectText) 18 | end 19 | 20 | testOrOperWithIllegalValue = proc() 21 | expectText = 'error as expected' 22 | result = try(or('this is illegal', true), expectText) 23 | eq(result, expectText) 24 | end 25 | 26 | testNotOperWithIllegalValue = proc() 27 | expectText = 'error as expected' 28 | result = try(not('this is illegal'), expectText) 29 | eq(result, expectText) 30 | end 31 | 32 | testNotOperBasics = func() 33 | result = list( 34 | not(true), 35 | not(not(true)), 36 | not(not(not(true))) 37 | ) 38 | expected = list(false, true, false) 39 | 40 | eq(result, expected) 41 | end 42 | 43 | testCombinations = func() 44 | ttrue = true 45 | tfalse = false 46 | 47 | result = list( 48 | and( or(ttrue, tfalse), not(tfalse) ), 49 | and( not(and(tfalse, tfalse)), or(tfalse, ttrue), and(not(false), true) , false), 50 | or( and(not(false), ttrue), false, not(true), and(ttrue, ttrue, false)), 51 | 52 | not( and( or(ttrue, tfalse), not(tfalse) ) ), 53 | not( and( not(and(tfalse, tfalse)), or(tfalse, ttrue), and(not(false), true) , false) ), 54 | not( or( and(not(false), ttrue), false, not(true), and(ttrue, ttrue, false)) ) 55 | 56 | and(true) 57 | and(false) 58 | 59 | or(true) 60 | or(false) 61 | ) 62 | 63 | expected = list(true, false, true, false, true, false true false true false) 64 | 65 | eq(result, expected) 66 | end 67 | 68 | endns 69 | -------------------------------------------------------------------------------- /tst/math_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns math_test 3 | 4 | import ut_fwk 5 | 6 | testAbsCases = func() 7 | import stdmath 8 | 9 | result = list( 10 | call(stdmath.abs float(2)) 11 | call(stdmath.abs float(minus(0 2))) 12 | ) 13 | expected = list( 14 | 2.0 15 | 2.0 16 | ) 17 | call(ut_fwk.VERIFY eq(result expected) plus('Unexpected result = ' str(result))) 18 | end 19 | 20 | testPowLogCases = func() 21 | import stdmath 22 | 23 | result = list( 24 | call(stdmath.exp 0.0) 25 | call(stdmath.exp2 2.0) 26 | call(stdmath.expm1 0.0) 27 | call(stdmath.log 1.0) 28 | call(stdmath.log10 100.0) 29 | call(stdmath.log1p 0.0) 30 | call(stdmath.log2 256.0) 31 | call(stdmath.logb 8.0) 32 | call(stdmath.pow 2.0 3.0) 33 | call(stdmath.pow 1.5 3.0) 34 | call(stdmath.pow 10.0 3.0) 35 | call(stdmath.sqrt 16.0) 36 | call(stdmath.cbrt 27.0) 37 | ) 38 | expected = list( 39 | 1.0 40 | 4.0 41 | 0.0 42 | 0.0 43 | 2.0 44 | 0.0 45 | 8.0 46 | 3.0 47 | 8.0 48 | 3.375 49 | 1000.0 50 | 4.0 51 | 3.0 52 | ) 53 | call(ut_fwk.VERIFY eq(result expected) plus('Unexpected result = ' str(result))) 54 | end 55 | 56 | testLimitCases = func() 57 | import stdmath 58 | 59 | result = list( 60 | call(stdmath.ceil 1.49) 61 | call(stdmath.ceil 0.0) 62 | call(stdmath.is-nan call(stdmath.ceil call(stdmath.acos 2.0))) 63 | 64 | call(stdmath.floor 1.51) 65 | 66 | call(stdmath.trunc 10.5) 67 | 68 | call(stdmath.frexp 10.0) 69 | 70 | call(stdmath.ldexp 0.625 4) 71 | call(stdmath.ldexp call(stdmath.frexp 10.0):) 72 | 73 | call(stdmath.modf 10.75) 74 | 75 | call(stdmath.remainder 7.5 2.0) 76 | ) 77 | expected = list( 78 | 2.0 79 | 0.0 80 | true 81 | 82 | 1.0 83 | 84 | 10.0 85 | 86 | list(0.625 4) 87 | 88 | 10.0 89 | 10.0 90 | 91 | list(10.0 0.75) 92 | 93 | 1.5 94 | ) 95 | call(ut_fwk.VERIFY eq(result expected) plus('Unexpected result = ' str(result))) 96 | end 97 | 98 | testTrigoCases = func() 99 | import stdmath 100 | 101 | result = list( 102 | call(stdmath.acos 1.0) 103 | call(stdmath.is-nan call(stdmath.acos 2.0)) 104 | 105 | call(stdmath.acosh 1.0) 106 | call(stdmath.is-nan call(stdmath.acosh 0.5)) 107 | 108 | call(stdmath.asin 0.0) 109 | call(stdmath.is-nan call(stdmath.asin 2.0)) 110 | 111 | call(stdmath.asin 0.0) 112 | 113 | call(stdmath.atan 0.0) 114 | 115 | call(stdmath.atanh 0.0) 116 | call(stdmath.is-inf call(stdmath.atanh 1.0) '+') 117 | 118 | call(stdmath.cos stdmath.pi) 119 | sprintf('%.2f' call(stdmath.cos div(stdmath.pi 2))) 120 | 121 | call(stdmath.cosh 0.0) 122 | 123 | call(stdmath.sin 0.0) 124 | sprintf('%.2f' call(stdmath.sin stdmath.pi)) 125 | 126 | call(stdmath.sinh 0.0) 127 | 128 | call(stdmath.tan 0.0) 129 | 130 | call(stdmath.tanh 0.0) 131 | ) 132 | expected = list( 133 | 0.0 134 | true 135 | 136 | 0.0 137 | true 138 | 139 | 0.0 140 | true 141 | 142 | 0.0 143 | 144 | 0.0 145 | 146 | 0.0 147 | true 148 | 149 | float(minus(0 1)) 150 | '0.00' 151 | 152 | 1.0 153 | 154 | 0.0 155 | '0.00' 156 | 157 | 0.0 158 | 159 | 0.0 160 | 161 | 0.0 162 | ) 163 | call(ut_fwk.VERIFY eq(result expected) plus('Unexpected result = ' str(result))) 164 | end 165 | 166 | endns 167 | 168 | -------------------------------------------------------------------------------- /tst/mod_level_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns mod_level_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | 10 | someTestInt = 1 11 | someTestStr = 'this is test' 12 | someTestFunc = func() 123 end 13 | someTestChan = chan() 14 | someTestFloat = div(float(5), float(10)) 15 | 16 | testModLevelSymbol = func() 17 | result = list( 18 | someTestInt, 19 | type(someTestStr), 20 | call(someTestFunc), 21 | type(someTestChan), 22 | type(someTestFloat), 23 | true 24 | ) 25 | expected = list( 26 | 1, 27 | 'string', 28 | 123, 29 | 'channel', 30 | 'float', 31 | true 32 | ) 33 | call(ASSURE, eq(result, expected), plus('Unexpected result = ', str(result))) 34 | end 35 | 36 | endns 37 | -------------------------------------------------------------------------------- /tst/other_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns other_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | 10 | # note. here's expression without let definition 11 | # this is just to test that interpreter accepts it 12 | plus(1 2 3) 13 | 14 | test-several-expressions-in-func = proc() 15 | result = list( 16 | eq( call(proc() plus(1 2) plus(3 4) plus(5 6) end) 11) 17 | 18 | eq( call(proc() _ = plus(1 2) _ = plus(3 4) plus(5 6) end) 11) 19 | eq( call(proc() _ = plus(1 2) _ _ = list(3 4): plus(5 6) end) 11) 20 | 21 | not(head(tryl(eval('call(proc() 10 20 30 end)')))) 22 | not(head(tryl(eval('call(proc() x=1 y=2 y x end)')))) 23 | ) 24 | allRight = call(common_test_util.isAllTrueInList result) 25 | call(ASSURE allRight plus('Unexpected result = ' str(result))) 26 | end 27 | 28 | top-thunk = defer(call(proc() 'top level' end)) 29 | 30 | test-thunk-error = proc() 31 | case-1 = proc() 32 | get-thunk = proc() 33 | defer(call(proc() 'ok' end)) 34 | end 35 | 36 | pure-f = func(tunkki) 37 | force(tunkki) 38 | end 39 | 40 | tryl(call(pure-f call(get-thunk))) 41 | end 42 | 43 | case-2 = proc() 44 | pure-f = func(tunkki) 45 | force(tunkki) 46 | end 47 | 48 | tryl(call(pure-f top-thunk)) 49 | end 50 | 51 | case-3 = proc() 52 | get-thunk = proc() 53 | import stdio 54 | defer(call(stdio.printfline '%s %s %s' 'in' 'ext' 'proc')) 55 | end 56 | 57 | pure-f = func(tunkki) 58 | force(tunkki) 59 | end 60 | 61 | tryl(call(pure-f call(get-thunk))) 62 | end 63 | 64 | case-4 = proc() 65 | get-thunk = proc() 66 | import stdstr 67 | defer(call(stdstr.uppercase 'to upper')) 68 | end 69 | 70 | pure-f = func(tunkki) 71 | force(tunkki) 72 | end 73 | 74 | tryl(call(pure-f call(get-thunk))) 75 | end 76 | 77 | result = list( 78 | not(head(call(case-1))) 79 | not(head(call(case-2))) 80 | not(head(call(case-3))) 81 | not(head(call(case-4))) 82 | ) 83 | allRight = call(common_test_util.isAllTrueInList result) 84 | call(ASSURE allRight plus('Unexpected result = ' str(result))) 85 | end 86 | 87 | test-thunk = func() 88 | make-thunk = func(a b) 89 | defer(mul(a b)) 90 | end 91 | 92 | x = 2 93 | result = list( 94 | eq( force(defer(plus(1 2))) 3 ) 95 | eq( force(defer(plus(1 x))) 3 ) 96 | eq( force(force(defer(plus(1 x)))) 3 ) 97 | eq( force(force(defer(defer(plus(1 2))))) 3 ) 98 | eq( force(plus(1 2)) 3 ) 99 | eq( force(1) 1 ) 100 | eq( force(call(make-thunk 2 3)) 6 ) 101 | ) 102 | allRight = call(common_test_util.isAllTrueInList result) 103 | call(ASSURE allRight plus('Unexpected result = ' str(result))) 104 | end 105 | 106 | testTypeOper = func() 107 | result = list( 108 | eq(type('some text') 'string') 109 | eq(type(123) 'int') 110 | eq(type(true) 'bool') 111 | eq(type(func() 1 end) 'function') 112 | eq(type(proc() 1 end) 'function') # should it be something else..? 113 | eq(type(chan()) 'channel') 114 | eq(type(list(1 2 3)) 'list') 115 | eq(type(defer(1)) 'thunk') 116 | ) 117 | allRight = call(common_test_util.isAllTrueInList result) 118 | call(ASSURE allRight plus('Unexpected result = ' str(result))) 119 | end 120 | 121 | testStrOper = func() 122 | import stdstr 123 | result = list( 124 | eq(str(123) '123') 125 | eq(str(true) 'true') 126 | eq(str(not(true)) 'false') 127 | eq(str('abc def') 'abc def') 128 | eq(str(list(1 2 3)) 'list(1, 2, 3)') 129 | call(stdstr.startswith str(func() 1 end) 'func-value') # maybe should be something else... 130 | eq(str(chan()) 'chan-value') # maybe should be something else... 131 | eq(str( div(float(5) float(10))) '0.5') 132 | eq(str(float(50)), '50.0') 133 | eq(str(defer(1)) 'thunk') 134 | ) 135 | allRight = call(common_test_util.isAllTrueInList result) 136 | call(ASSURE allRight plus('Unexpected result = ' str(result))) 137 | end 138 | 139 | testTypeConversion = func() 140 | floatVal1 = div(float(5), float(10)) 141 | floatVal2 = div(float(55), float(10)) 142 | 143 | result = list( 144 | eq(conv(floatVal1, 'float'), floatVal1), 145 | eq(conv(123, 'float'), float(123)), 146 | 147 | eq(conv(floatVal1, 'string'), '0.5'), 148 | eq(conv(floatVal2, 'string'), '5.5'), 149 | eq(conv(float(123), 'int'), 123), 150 | eq(conv(floatVal1, 'int'), 0), 151 | eq(conv(floatVal2, 'int'), 5), 152 | 153 | eq(conv('123', 'int'), 123), 154 | eq(conv(list(1,2,3), 'string'), 'list(1, 2, 3)'), 155 | eq(conv(123, 'string'), '123'), 156 | eq(conv(123, 'int'), 123), 157 | eq(conv(false, 'bool'), false), 158 | eq(conv('123', 'string'), '123'), 159 | eq(conv(123, 'int'), 123), 160 | eq(conv(list(1,2,3), 'list'), list(1, 2, 3)), 161 | ) 162 | allRight = call(common_test_util.isAllTrueInList, result) 163 | call(ASSURE, allRight, plus('Unexpected result = ', str(result))) 164 | end 165 | 166 | testNameOper = func() 167 | some_symbol = 123 168 | result = eq(name(some_symbol), 'some_symbol') 169 | call(ASSURE, result, plus('Unexpected result = ', str(result))) 170 | end 171 | 172 | testErrorOper = proc() 173 | result = list( 174 | eq(try(error('some explanation')), 'RTE:some explanation'), 175 | eq(try(error()), 'RTE:'), 176 | eq(try(error('some explanation', '...and more')), 'RTE:some explanation...and more'), 177 | eq(try(error('some explanation:', 123)), 'RTE:some explanation:123'), 178 | ) 179 | allRight = call(common_test_util.isAllTrueInList, result) 180 | call(ASSURE, allRight, plus('Unexpected result = ', str(result))) 181 | end 182 | 183 | testSymvalOper = proc() 184 | some_symbol = 123 185 | some_other_symbol = 456 186 | result = list( 187 | try(symval('nonexisting'), true), 188 | try(symval('other_test'), true), 189 | eq(symval('some_symbol'), 123), 190 | not(eq(symval('some_other_symbol'), 123)), 191 | eq(symval(name(some_symbol)), 123), 192 | ) 193 | allRight = call(common_test_util.isAllTrueInList, result) 194 | call(ASSURE, allRight, plus('Unexpected result = ', str(result))) 195 | end 196 | 197 | testTrylOper = proc() 198 | result = list( 199 | eq(tryl(mul(2 3 2)) list(true '' 12)) 200 | eq(tryl(mul(2 3 'X')) list(false 'Invalid type for mul' '')) 201 | eq(tryl(10) list(true '' 10)) 202 | ) 203 | allRight = call(common_test_util.isAllTrueInList, result) 204 | call(ASSURE, allRight, plus('Unexpected result = ', str(result))) 205 | end 206 | 207 | endns 208 | -------------------------------------------------------------------------------- /tst/stdbase64_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdbase64_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | import stdbase64 10 | import stdbytes 11 | 12 | test-encode-decode = func() 13 | data = call(stdbytes.str-to-bytes 'abcde') 14 | 15 | enc-ok enc-err encoded = call(stdbase64.encode data): 16 | _ = call(ASSURE enc-ok enc-err) 17 | 18 | dec-ok dec-err decoded = call(stdbase64.decode encoded): 19 | _ = call(ASSURE dec-ok dec-err) 20 | 21 | s = call(stdbytes.string decoded) 22 | call(ASSURE eq(s 'abcde') plus('unexpected result: ' s)) 23 | end 24 | 25 | endns 26 | 27 | -------------------------------------------------------------------------------- /tst/stdlex_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdlex_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | import stdlex 10 | 11 | test-tokenize-ok = proc() 12 | ok err tokens = call(stdlex.tokenize 'symbol 123 #line comment\ntrue'): 13 | assumed-tokens = list( 14 | map( 15 | 'line' 1 16 | 'value' 'symbol' 17 | 'pos' 7 18 | 'type' 'tokenSymbol' 19 | ) 20 | map( 21 | 'line' 1 22 | 'value' '123' 23 | 'pos' 11 24 | 'type' 'tokenNumber' 25 | ) 26 | map( 27 | 'line' 1 28 | 'value' 'line comment ' 29 | 'pos' 26 30 | 'type' 'tokenLineComment' 31 | ) 32 | map( 33 | 'line' 2 34 | 'value' 'true' 35 | 'pos' 4 36 | 'type' 'tokenTrue' 37 | ) 38 | ) 39 | and( 40 | call(ASSURE ok err) 41 | call(ASSURE eq(tokens assumed-tokens ) sprintf('unexpected tokens: %v' tokens)) 42 | ) 43 | end 44 | 45 | test-tokenize-nok = proc() 46 | ok err tokens = call(stdlex.tokenize 'symbol 123 ? #line comment\ntrue'): 47 | and( 48 | call(ASSURE not(ok) 'Unexpected success') 49 | ) 50 | end 51 | 52 | endns 53 | 54 | -------------------------------------------------------------------------------- /tst/stdmeta_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdmeta_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | import stdmeta 10 | 11 | test-schema-check-fails = func() 12 | # define schemas 13 | subsubschema = map( 14 | 10.5 list(list('type' 'int') list('in' 10 15 25)) 15 | ) 16 | subchema = map( 17 | 'subf1' list(list('required') list('doc' 'this is subfield' '...of sub-map')) 18 | 'subf2' list(list('required') list('type' 'int')) 19 | 'subf3' list(list('map' subsubschema)) 20 | ) 21 | schema = list('map' map( 22 | 'field-1' list(list('required') list('type' 'string')) 23 | 'field-2' list(list('required') list('type' 'list')) 24 | 'field-3' list(list('map' subchema)) 25 | )) 26 | 27 | # define data 28 | subdata = map( 29 | 'subf2' 'not supposed to be string' 30 | 'subf3' map(10.5 10.0 'b' 20) 31 | ) 32 | data = map( 33 | 'field-1' 'some text' 34 | 'field-2' list(1 2 3) 35 | 'field-3' subdata 36 | ) 37 | 38 | # and validate 39 | ok msglist = call(stdmeta.validate schema data): 40 | 41 | expected-msg-list = list( 42 | 'required field subf1 not found ( -> field-3)' 43 | 'field subf2 is not required type (got: string, expected: int)( -> field-3)' 44 | 'field 10.5 is not in allowed set (10 not in: list(10, 15, 25))( -> field-3 -> subf3)' 45 | 'field 10.5 is not required type (got: float, expected: int)( -> field-3 -> subf3)' 46 | ) 47 | 48 | import stdfu 49 | messages-ok = and( 50 | eq(len(msglist) len(expected-msg-list)) 51 | call(stdfu.foreach msglist func(msg result) and(result in(expected-msg-list msg)) end true) 52 | call(stdfu.foreach expected-msg-list func(msg result) and(result in(msglist msg)) end true) 53 | ) 54 | 55 | call(ASSURE and(not(ok) messages-ok) plus('Unexpected result = ' str(msglist))) 56 | end 57 | 58 | test-schema-ok = func() 59 | # define schemas 60 | subsubschema = map( 61 | 10.5 list(list('type' 'int') list('in' 10 15 25)) 62 | ) 63 | subchema = map( 64 | 'subf1' list(list('required') list('doc' 'this is subfield' '...of sub-map')) 65 | 'subf2' list(list('required') list('type' 'int')) 66 | 'subf3' list(list('map' subsubschema)) 67 | ) 68 | schema = list('map' map( 69 | 'field-1' list(list('required') list('type' 'string')) 70 | 'field-2' list(list('required') list('type' 'list')) 71 | 'field-3' list(list('map' subchema)) 72 | )) 73 | 74 | # define data 75 | subdata = map( 76 | 'subf1' 100 77 | 'subf2' 200 78 | 'subf3' map(10.5 10 'b' 20) 79 | ) 80 | data = map( 81 | 'field-1' 'some text' 82 | 'field-2' list(1 2 3) 83 | 'field-3' subdata 84 | ) 85 | 86 | # and validate 87 | ok msglist = call(stdmeta.validate schema data): 88 | call(ASSURE and(ok empty(msglist)) plus('Unexpected result = ' str(msglist))) 89 | end 90 | 91 | endns 92 | 93 | -------------------------------------------------------------------------------- /tst/stdser_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdser_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | import stdbytes 10 | import stdser 11 | 12 | test-bytearray-encode-decode = func() 13 | data = call(stdbytes.str-to-bytes 'abcde') 14 | 15 | enc-ok enc-err encoded = call(stdser.encode data): 16 | _ = call(ASSURE enc-ok enc-err) 17 | 18 | dec-ok dec-err decoded = call(stdser.decode encoded): 19 | _ = call(ASSURE dec-ok dec-err) 20 | 21 | s = call(stdbytes.string decoded) 22 | call(ASSURE eq(s 'abcde') plus('unexpected result: ' s)) 23 | end 24 | 25 | endns 26 | 27 | -------------------------------------------------------------------------------- /tst/stdsort_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdsort_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | 10 | should-be = func(input expected-output) 11 | import stdsort 12 | 13 | out = call(stdsort.sort input) 14 | call(ASSURE eq(out expected-output) sprintf('Result: %v, \nExpected: %v' out expected-output)) 15 | end 16 | 17 | get-should-be-with-func = func(comparator) 18 | func(input expected-output) 19 | import stdsort 20 | 21 | out = call(stdsort.sort input comparator) 22 | call(ASSURE eq(out expected-output) sprintf('Result: %v, \nExpected: %v' out expected-output)) 23 | end 24 | end 25 | 26 | test-list-sortings = func() 27 | and(list( 28 | call(should-be list(1) list(1)) 29 | call(should-be list() list()) 30 | call(should-be list(10 5 20) list(5 10 20)) 31 | call(should-be list(1 1) list(1 1)) 32 | call(should-be list(1 1 1) list(1 1 1)) 33 | call(should-be list(10 minus(0 5) 20 0) list(minus(0 5) 0 10 20)) 34 | call(should-be list(minus(0 5) 1 minus(0.0 2.5)) list(minus(0 5) minus(0.0 2.5) 1)) 35 | call(should-be list(10 100 5 50 20) list(5 10 20 50 100)) 36 | call(should-be list(10.5 100 5.5 50.5 20) list(5.5 10.5 20 50.5 100)) 37 | ):) 38 | end 39 | 40 | test-list-sortings-with-comparison-func = func() 41 | len-comparator = func(a b) 42 | lt(len(a) len(b)) 43 | end 44 | 45 | checker = call(get-should-be-with-func len-comparator) 46 | 47 | and(list( 48 | call(checker list() list()) 49 | call(checker list('a') list('a')) 50 | call(checker list('ab' 'a') list('a' 'ab')) 51 | call(checker list('ab' 'abcd' 'a' 'ab' 'abc') list('a' 'ab' 'ab' 'abc' 'abcd')) 52 | ):) 53 | end 54 | 55 | endns 56 | 57 | -------------------------------------------------------------------------------- /tst/stdvar_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns stdvar_test 3 | 4 | import ut_fwk 5 | 6 | ASSURE = ut_fwk.VERIFY 7 | 8 | import common_test_util 9 | import stdvar 10 | 11 | test-change-v2-ok = proc() 12 | var = call(stdvar.new 50) 13 | retv = call(stdvar.change-v2 var func(prev inp) list(plus(prev inp) list(prev inp)) end 10) 14 | _ = call(ASSURE eq(retv list(true '' 60 list(50 10))) plus('Unexpected result = ' str(retv))) 15 | call(ASSURE eq(call(stdvar.value var) 60) 'unexpected value') 16 | end 17 | 18 | test-change-v2-ok-no-extra-parameter = proc() 19 | var = call(stdvar.new 50) 20 | retv = call(stdvar.change-v2 var func(prev) list(plus(prev 1) list(prev 1)) end) 21 | _ = call(ASSURE eq(retv list(true '' 51 list(50 1))) plus('Unexpected result = ' str(retv))) 22 | call(ASSURE eq(call(stdvar.value var) 51) 'unexpected value') 23 | end 24 | 25 | test-change-v2-RTE-in-func = proc() 26 | var = call(stdvar.new 50) 27 | retv = call(stdvar.change-v2 var func(prev inp) list(plus(prev inp) list(prev inp)) end true) 28 | _ = call(ASSURE eq(retv list(false 'mismatching types as arguments' '' '')) plus('Unexpected result = ' str(retv))) 29 | call(ASSURE eq(call(stdvar.value var) 50) 'unexpected value') 30 | end 31 | 32 | test-change-v2-ret-value-not-list = proc() 33 | var = call(stdvar.new 50) 34 | retv = call(stdvar.change-v2 var func(prev inp) 'crappy return value' end true) 35 | _ = call(ASSURE eq(retv list(false 'List value expected' '' '')) plus('Unexpected result = ' str(retv))) 36 | call(ASSURE eq(call(stdvar.value var) 50) 'unexpected value') 37 | end 38 | 39 | test-change-v2-ret-value-is-empty-list = proc() 40 | var = call(stdvar.new 50) 41 | retv = call(stdvar.change-v2 var func(prev inp) list() end true) 42 | _ = call(ASSURE eq(retv list(false 'Too short list received (empty)' '' '')) plus('Unexpected result = ' str(retv))) 43 | call(ASSURE eq(call(stdvar.value var) 50) 'unexpected value') 44 | end 45 | 46 | test-change-v2-ret-value-list-too-short = proc() 47 | var = call(stdvar.new 50) 48 | retv = call(stdvar.change-v2 var func(prev inp) list(1) end true) 49 | _ = call(ASSURE eq(retv list(false 'Too short list received (one item)' '' '')) plus('Unexpected result = ' str(retv))) 50 | call(ASSURE eq(call(stdvar.value var) 50) 'unexpected value') 51 | end 52 | 53 | endns 54 | -------------------------------------------------------------------------------- /tst/string_test.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns string_test 3 | 4 | import ut_fwk 5 | 6 | testSprintf = func() 7 | result = list( 8 | sprintf('%s:%d:%s' 'blaa' 100 plus('aa' 'bb')) 9 | sprintf('%.2f:%v:%x' 0.25 false 'abc') 10 | sprintf('') 11 | ) 12 | expected = list( 13 | 'blaa:100:aabb' 14 | '0.25:false:616263' 15 | '' 16 | ) 17 | call(ut_fwk.VERIFY eq(result expected) plus('Unexpected result = ' str(result))) 18 | end 19 | 20 | testStringCatenation = func() 21 | str1 = 'aaa' 22 | str2 = 'bbb' 23 | getstr = func() 'ddd' end 24 | 25 | result = plus(str1, str2, 'ccc', call(getstr)) 26 | expected = 'aaabbbcccddd' 27 | cond1 = call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 28 | cond2 = and( eq(len(str1), 3), eq(len(result), 12) ) 29 | and(cond1, cond2) 30 | end 31 | 32 | testStringLen = func() 33 | str1 = '' 34 | str2 = 'abc' 35 | result = list( 36 | len(str1), 37 | len(str2) 38 | ) 39 | expected = list( 40 | 0, 41 | 3 42 | ) 43 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 44 | end 45 | 46 | testSubstringQuery = func() 47 | mainstr = 'abc def ghi' 48 | substr1 = ' def' 49 | substr2 = 'chi' 50 | substr3 = 'ghi' 51 | substr4 = 'abc' 52 | 53 | result = list( 54 | in(mainstr, ' def'), 55 | in(mainstr, substr2), 56 | in(mainstr, 'ghi'), 57 | in(mainstr, substr4) 58 | ) 59 | expected = list( 60 | true, 61 | false, 62 | true, 63 | true 64 | ) 65 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 66 | end 67 | 68 | testConvToStr = func() 69 | x = 100 70 | result = list( 71 | str(10), 72 | str(true), 73 | str(x), 74 | str('text'), 75 | str(list(1, 2, 3)) 76 | ) 77 | expected = list( 78 | '10', 79 | 'true', 80 | '100', 81 | 'text', 82 | 'list(1, 2, 3)' 83 | ) 84 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 85 | end 86 | 87 | testStartsWith = func() 88 | import stdstr 89 | startswith = stdstr.startswith 90 | 91 | s1 = 'begins blaa' 92 | s2 = 'begins' 93 | s3 = 'not this' 94 | result = list( 95 | call(startswith, 'abc def ghi', 'a'), 96 | call(startswith, 'abc def ghi', 'abc d'), 97 | call(startswith, 'abc def ghi', 'b'), 98 | call(startswith, 'abc def ghi', 'bc'), 99 | call(startswith, 'abc def ghi', 'ghi'), 100 | call(startswith, 'abc def ghi', 'abc def ghi'), 101 | call(startswith, s1, s2), 102 | call(startswith, s1, s3), 103 | call(startswith, 'zzz', s2) 104 | ) 105 | expected = list( 106 | true, 107 | true, 108 | false, 109 | false, 110 | false, 111 | true, 112 | true, 113 | false, 114 | false 115 | ) 116 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 117 | end 118 | 119 | testEndsWith = func() 120 | import stdstr 121 | endswith = stdstr.endswith 122 | 123 | s1 = 'begins blaa' 124 | s2 = 'blaa' 125 | s3 = 'not this' 126 | result = list( 127 | call(endswith, 'abc def ghi', 'a'), 128 | call(endswith, 'abc def ghi', 'abc d'), 129 | call(endswith, 'abc def ghi', 'i'), 130 | call(endswith, 'abc def ghi', 'hi'), 131 | call(endswith, 'abc def ghi', 'f ghi'), 132 | call(endswith, 'abc def ghi', 'abc def ghi'), 133 | call(endswith, s1, s2), 134 | call(endswith, s1, s3), 135 | call(endswith, 'zzz', s2) 136 | ) 137 | expected = list( 138 | false, 139 | false, 140 | true, 141 | true, 142 | true, 143 | true, 144 | true, 145 | false, 146 | false 147 | ) 148 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 149 | end 150 | 151 | testSplitString = func() 152 | result = list( 153 | split('abcdabcdabcd', 'bc'), 154 | split('abcdabcdabcd', 'b'), 155 | split('abcd abcd abcd'), 156 | split('abcd 1234 abcd', ' '), 157 | 158 | split('abcdefgh', '') 159 | ) 160 | 161 | expected = list( 162 | list('a', 'da', 'da', 'd'), 163 | list('a', 'cda', 'cda', 'cd'), 164 | list('abcd', 'abcd', 'abcd'), 165 | list('abcd', '1234', 'abcd'), 166 | 167 | list('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') 168 | ) 169 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 170 | end 171 | 172 | testSliceOfString = func() 173 | result = list( 174 | slice('some nonsense sentence', 5), 175 | slice('some nonsense sentence', 5, 12) 176 | ) 177 | 178 | expected = list( 179 | 'nonsense sentence', 180 | 'nonsense' 181 | ) 182 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 183 | end 184 | 185 | testFindSubstring = func() 186 | result = list( 187 | find('there is substring here', 'substring'), 188 | find('there is substring here and here substring again', 'substring'), 189 | find('there is substring here', 'xxx') 190 | ) 191 | expected = list( 192 | list(9), 193 | list(9, 33), 194 | list() 195 | ) 196 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 197 | end 198 | 199 | testIndForString = proc() 200 | result = list( 201 | ind('0123456789', 6), 202 | ind('0123456789', 4), 203 | ind('0123456789', 0), 204 | ind('0123456789', 9), 205 | try(ind('0123456789', 10), 'index over') 206 | ) 207 | expected = list( 208 | '6', 209 | '4', 210 | '0', 211 | '9', 212 | 'index over' 213 | ) 214 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 215 | end 216 | 217 | testLenOfString = func() 218 | result = list( 219 | len('123456789'), 220 | len('') 221 | ) 222 | expected = list( 223 | 9, 224 | 0 225 | ) 226 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 227 | end 228 | 229 | testStringEscapeCaharacters = func() 230 | result = list( 231 | split('123\'456\'789', '\''), 232 | split('123\'\'456\'789', '\''), 233 | split('123\\456\\789', '\\'), 234 | split('123\\\\456\\\\789', '\\\\'), 235 | split('123\\\\456\\\\789', '\\'), 236 | split('123\\\'\\789', '\''), 237 | split('123\'\\\'789', '\'') 238 | ) 239 | expected = list( 240 | list('123', '456', '789'), 241 | list('123', '', '456', '789'), 242 | list('123', '456', '789'), 243 | list('123', '456', '789'), 244 | list('123', '', '456', '', '789'), 245 | list('123\\', '\\789'), 246 | list('123', '\\', '789') 247 | ) 248 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 249 | end 250 | 251 | testStrForTypes = func() 252 | import stdstr 253 | 254 | result = list( 255 | str('123456789'), 256 | str(123), 257 | str(0.5), 258 | str(list(1,2,3)), 259 | str(map(1, 10, 2, 20)), 260 | str(true), 261 | str(not(true)), 262 | call(stdstr.startswith str(func() 1 end) 'func-value:'), 263 | call(stdstr.startswith str(proc() 1 end) 'func-value:'), 264 | str(chan()) 265 | # missing: opaque type 266 | # missing: external proc 267 | ) 268 | expected = list( 269 | '123456789', 270 | '123', 271 | '0.5', 272 | 'list(1, 2, 3)', 273 | 'map(1 : 10, 2 : 20)', 274 | 'true', 275 | 'false', 276 | true 277 | true 278 | 'chan-value' 279 | ) 280 | call(ut_fwk.VERIFY, eq(result, expected), plus('Unexpected result = ', str(result))) 281 | end 282 | 283 | endns 284 | 285 | -------------------------------------------------------------------------------- /tstfwk/.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.fnl linguist-language=funl 3 | -------------------------------------------------------------------------------- /tstfwk/common_test_util.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns common_test_util 3 | 4 | isAllTrueInList = func(l) 5 | checker = func(li) 6 | if(empty(li), 7 | true, 8 | if(head(li), 9 | call(checker, rest(li)), 10 | false 11 | ) 12 | ) 13 | end 14 | 15 | call(checker, l) 16 | end 17 | 18 | endns 19 | 20 | -------------------------------------------------------------------------------- /tstfwk/ut_fwk.fnl: -------------------------------------------------------------------------------- 1 | 2 | ns ut_fwk 3 | 4 | VERIFY = func(condition, errText) 5 | if(condition, 6 | true, 7 | call(func() 8 | _ = print(plus(name(VERIFY), ' fail: '), errText) 9 | false 10 | end) 11 | ) 12 | end 13 | 14 | endns 15 | --------------------------------------------------------------------------------