├── .codeclimate.yml ├── .cspell.json ├── .github ├── dependabot.yml └── workflows │ ├── dependabot.yaml │ └── test.yaml ├── .gitignore ├── .mdlrc ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── examples ├── aruba.rb ├── clutil.feature ├── collections.feature ├── comparison.feature ├── data_types.feature ├── effects.feature ├── errors.feature ├── functions.feature ├── import.feature ├── io.feature ├── match.feature ├── math.feature ├── memory_leak.feature ├── miscellaneous_functions.feature ├── modules │ ├── fs.feature │ ├── http.feature │ ├── json.feature │ ├── os.feature │ ├── random.feature │ └── re.feature ├── others.feature ├── parallelism.feature └── recursion.feature ├── go.mod ├── go.sum ├── rakefile.rb └── src ├── cmd ├── cloe │ └── main.go └── clutil │ ├── clean.go │ ├── git.go │ ├── install.go │ ├── installer.go │ ├── main.go │ └── update.go └── lib ├── ast ├── anonymous_function.go ├── app.go ├── arguments.go ├── def_function.go ├── effect.go ├── import.go ├── keyword_argument.go ├── keyword_parameters.go ├── let_expression.go ├── let_match.go ├── let_var.go ├── match.go ├── match_case.go ├── mutual_recursion.go ├── optional_parameter.go ├── positional_argument.go ├── positional_parameters.go ├── signature.go ├── switch.go ├── switch_case.go └── util.go ├── builtins ├── compare.go ├── compare_test.go ├── par.go ├── par_test.go ├── performance_test.go ├── print.go ├── print_test.go ├── rally.go ├── rally_test.go ├── read.go ├── read_test.go ├── seq.go ├── seq_test.go ├── util.go ├── util_test.go ├── y.go ├── y_star.go ├── y_star_test.go └── y_test.go ├── compile ├── builtins.go ├── builtins_test.go ├── cache.go ├── cache_test.go ├── compile.go ├── compile_test.go ├── compiler.go ├── effect.go ├── effect_test.go ├── environment.go ├── environment_test.go ├── module.go └── performance_test.go ├── consts ├── language_path.go └── names.go ├── core ├── arguments.go ├── arguments_test.go ├── boolean.go ├── boolean_test.go ├── collection.go ├── collection_test.go ├── dictionary.go ├── dictionary_test.go ├── effect.go ├── effect_test.go ├── error.go ├── error_test.go ├── function.go ├── function_test.go ├── init.go ├── interfaces.go ├── interfaces_test.go ├── keyword_argument.go ├── keyword_parameters.go ├── list.go ├── list_test.go ├── nil.go ├── nil_test.go ├── number.go ├── number_test.go ├── optional_parameter.go ├── positional_argument.go ├── positional_parameters.go ├── sequence.go ├── signature.go ├── signature_test.go ├── string.go ├── string_test.go ├── thunk.go ├── thunk_test.go ├── util.go ├── util_test.go ├── value.go └── value_test.go ├── debug ├── debug.go ├── info.go └── info_test.go ├── desugar ├── anonymous_function.go ├── anonymous_function_test.go ├── desugar.go ├── desugar_test.go ├── dictionary_expansion.go ├── dictionary_expansion_test.go ├── empty_collections.go ├── empty_collections_test.go ├── flatten.go ├── let_expression.go ├── let_match.go ├── match │ ├── cases_desugarer.go │ ├── cases_desugarer_test.go │ ├── desugar.go │ ├── desugar_test.go │ ├── pattern_renamer.go │ ├── pattern_renamer_test.go │ ├── pattern_type.go │ ├── util.go │ ├── value_renamer.go │ └── value_renamer_test.go ├── mutual_recursion.go ├── mutual_recursion_test.go ├── names.go ├── names_test.go ├── remove_aliases.go ├── remove_unused_variables.go ├── self_recursion.go └── util.go ├── gensym ├── gensym.go └── gensym_test.go ├── ir ├── app.go ├── app_test.go ├── arguments.go ├── case.go ├── keyword_argument.go ├── keyword_argument_test.go ├── positional_argument.go ├── switch.go ├── switch_test.go ├── util.go └── util_test.go ├── modules ├── fs │ ├── create_directory.go │ ├── create_directory_test.go │ ├── error.go │ ├── module.go │ ├── read_directory.go │ ├── read_directory_test.go │ ├── remove.go │ └── remove_test.go ├── http │ ├── error.go │ ├── get.go │ ├── get_requests.go │ ├── get_requests_test.go │ ├── get_test.go │ ├── module.go │ ├── post.go │ ├── post_test.go │ └── setup_test.go ├── json │ ├── decode.go │ ├── decode_test.go │ ├── encode.go │ ├── encode_test.go │ ├── error.go │ └── module.go ├── modules.go ├── os │ ├── exit.go │ ├── exit_test.go │ └── module.go ├── random │ ├── module.go │ ├── number.go │ └── number_test.go └── re │ ├── error.go │ ├── find.go │ ├── find_test.go │ ├── match.go │ ├── match_test.go │ ├── module.go │ ├── replace.go │ ├── replace_test.go │ └── util.go ├── parse ├── comb │ ├── combinator.go │ ├── combinator_test.go │ ├── parser.go │ ├── state.go │ └── state_test.go ├── parse.go ├── parse_test.go └── state.go ├── run ├── run.go └── run_test.go ├── scalar └── scalar.go ├── systemt ├── daemon.go └── daemon_test.go └── utils └── utils.go /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | fixme: 3 | enabled: true 4 | gofmt: 5 | enabled: true 6 | golint: 7 | enabled: true 8 | govet: 9 | enabled: true 10 | markdownlint: 11 | enabled: true 12 | rubocop: 13 | enabled: true 14 | 15 | ratings: 16 | paths: 17 | - "**.go" 18 | - "**.md" 19 | - "**.rb" 20 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "words": [ 3 | "benchmem", 4 | "builtins", 5 | "cloe", 6 | "clutil", 7 | "codecov", 8 | "consts", 9 | "covermode", 10 | "coverprofile", 11 | "docopt", 12 | "gofmt", 13 | "goimports", 14 | "stretchr" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: gomod 12 | directory: / 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yaml: -------------------------------------------------------------------------------- 1 | name: dependabot 2 | on: pull_request 3 | permissions: 4 | contents: write 5 | pull-requests: write 6 | jobs: 7 | merge: 8 | runs-on: ubuntu-latest 9 | if: github.actor == 'dependabot[bot]' 10 | steps: 11 | - run: gh pr merge --auto --squash ${{ github.event.pull_request.html_url }} 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: ruby/setup-ruby@v1 13 | - uses: actions/setup-go@v5 14 | - run: rake build 15 | - run: rake lint 16 | - run: rake format && git diff --exit-code 17 | - run: rake unit_test 18 | - run: rake command_test 19 | - run: rake data_race_test 20 | - uses: codecov/codecov-action@v5 21 | with: 22 | fail_ci_if_error: true 23 | token: ${{ secrets.CODECOV_TOKEN }} 24 | - run: rake bench 25 | - run: rake install 26 | spell_check: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: streetsidesoftware/cspell-action@main 31 | with: 32 | files: "**/*.{go,md,rb}" 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | coverage.txt 3 | site 4 | tmp 5 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | rules "~MD026" 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | gem 'aruba', '~> 2.2.0' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | aruba (2.2.0) 5 | bundler (>= 1.17, < 3.0) 6 | contracts (>= 0.16.0, < 0.18.0) 7 | cucumber (>= 8.0, < 10.0) 8 | rspec-expectations (~> 3.4) 9 | thor (~> 1.0) 10 | builder (3.2.4) 11 | contracts (0.17) 12 | cucumber (9.1.0) 13 | builder (~> 3.2, >= 3.2.4) 14 | cucumber-ci-environment (~> 9.2, >= 9.2.0) 15 | cucumber-core (~> 12.0) 16 | cucumber-cucumber-expressions (~> 17.0) 17 | cucumber-gherkin (>= 24, < 27) 18 | cucumber-html-formatter (~> 20.4, >= 20.4.0) 19 | cucumber-messages (>= 19, < 23) 20 | diff-lcs (~> 1.5, >= 1.5.0) 21 | mini_mime (~> 1.1, >= 1.1.5) 22 | multi_test (~> 1.1, >= 1.1.0) 23 | sys-uname (~> 1.2, >= 1.2.3) 24 | cucumber-ci-environment (9.2.0) 25 | cucumber-core (12.0.0) 26 | cucumber-gherkin (>= 25, < 27) 27 | cucumber-messages (>= 20, < 23) 28 | cucumber-tag-expressions (~> 5.0, >= 5.0.4) 29 | cucumber-cucumber-expressions (17.0.1) 30 | cucumber-gherkin (26.2.0) 31 | cucumber-messages (>= 19.1.4, < 22.1) 32 | cucumber-html-formatter (20.4.0) 33 | cucumber-messages (>= 18.0, < 22.1) 34 | cucumber-messages (22.0.0) 35 | cucumber-tag-expressions (5.0.6) 36 | diff-lcs (1.5.0) 37 | ffi (1.16.3) 38 | mini_mime (1.1.5) 39 | multi_test (1.1.0) 40 | rspec-expectations (3.12.3) 41 | diff-lcs (>= 1.2.0, < 2.0) 42 | rspec-support (~> 3.12.0) 43 | rspec-support (3.12.1) 44 | sys-uname (1.2.3) 45 | ffi (~> 1.1) 46 | thor (1.3.0) 47 | 48 | PLATFORMS 49 | ruby 50 | 51 | DEPENDENCIES 52 | aruba (~> 2.2.0) 53 | 54 | BUNDLED WITH 55 | 1.15.0 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Cloe developers 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloe 2 | 3 | [![GitHub Action](https://img.shields.io/github/actions/workflow/status/cloe-lang/cloe/test.yaml?branch=main&style=flat-square)](https://github.com/cloe-lang/cloe/actions) 4 | [![Coveralls](https://img.shields.io/coveralls/cloe-lang/cloe/master.svg?style=flat-square)](https://coveralls.io/github/cloe-lang/cloe) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/cloe-lang/cloe?style=flat-square)](https://goreportcard.com/report/github.com/cloe-lang/cloe) 6 | [![License](https://img.shields.io/github/license/cloe-lang/cloe.svg?style=flat-square)](https://opensource.org/licenses/MIT) 7 | 8 |
9 | logo 10 |
11 | 12 | Cloe is the _timeless_ functional programming language. 13 | It aims to be simple and practical. 14 | 15 | ## Features 16 | 17 | - Functional programming 18 | - Immutable data 19 | - Lazy evaluation 20 | - Implicit parallelism, concurrency, and reactivity 21 | 22 | ## Installation 23 | 24 | ``` 25 | go get -u github.com/cloe-lang/cloe/... 26 | ``` 27 | 28 | Go 1.8+ is required. 29 | 30 | ## Documentation 31 | 32 | [Here](https://cloe-lang.org). 33 | 34 | ## Examples 35 | 36 | ### Hello, world! 37 | 38 | ``` 39 | (print "Hello, world!") 40 | ``` 41 | 42 | ### HTTP server 43 | 44 | ``` 45 | (import "http") 46 | 47 | (def (handler request) 48 | ((@ request "respond") "Hello, world!")) 49 | 50 | (let requests (http.getRequests ":8080")) 51 | 52 | ..(map handler requests) 53 | ``` 54 | 55 | See [examples](examples) directory for more. 56 | 57 | ## License 58 | 59 | [MIT](LICENSE) 60 | -------------------------------------------------------------------------------- /examples/aruba.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'aruba/cucumber' 4 | 5 | Aruba.configure do |config| 6 | config.exit_timeout = 120 7 | end 8 | -------------------------------------------------------------------------------- /examples/clutil.feature: -------------------------------------------------------------------------------- 1 | Feature: clutil command 2 | Scenario: Install modules in a repository 3 | When I run the following commands: 4 | """ 5 | CLOE_PATH=$PWD/.cloe clutil install https://github.com/cloe-lang/examples 6 | """ 7 | Then I run the following commands: 8 | """ 9 | PATH=$PWD/.cloe/bin:$PATH hello 10 | """ 11 | And the exit status should be 0 12 | And the output should contain "Hello, world!" 13 | 14 | Scenario: Update repositories in a language directory 15 | Given I run the following commands: 16 | """ 17 | CLOE_PATH=$PWD/.cloe clutil install https://github.com/cloe-lang/examples 18 | """ 19 | When I run the following commands: 20 | """ 21 | CLOE_PATH=$PWD/.cloe clutil update 22 | """ 23 | And the exit status should be 0 24 | 25 | Scenario: Clean up a language directory 26 | Given I run the following commands: 27 | """ 28 | CLOE_PATH=$PWD/.cloe clutil install https://github.com/cloe-lang/examples 29 | """ 30 | And I run the following commands: 31 | """ 32 | ls $PWD/.cloe 33 | """ 34 | And the exit status should be 0 35 | When I run the following commands: 36 | """ 37 | CLOE_PATH=$PWD/.cloe clutil clean 38 | """ 39 | Then I run the following commands: 40 | """ 41 | ls $PWD/.cloe/src 42 | """ 43 | And the exit status should not be 0 44 | And I run the following commands: 45 | """ 46 | ls $PWD/.cloe/bin 47 | """ 48 | And the exit status should not be 0 49 | -------------------------------------------------------------------------------- /examples/collections.feature: -------------------------------------------------------------------------------- 1 | Feature: Collections 2 | Scenario: Index elements in collections 3 | Given a file named "main.cloe" with: 4 | """ 5 | (seq! 6 | (print (@ [123 [456 789] "foo" true nil false] 2)) 7 | (print (@ {123 [456 789] "foo" "It's me." nil false} "foo")) 8 | (print (@ "Hello, world!" 6))) 9 | """ 10 | When I successfully run `cloe main.cloe` 11 | Then the stdout should contain exactly: 12 | """ 13 | [456 789] 14 | It's me. 15 | , 16 | """ 17 | 18 | Scenario: Chain indexing 19 | Given a file named "main.cloe" with: 20 | """ 21 | (print (@ {"foo" {"bar" 42}} "foo" "bar")) 22 | """ 23 | When I successfully run `cloe main.cloe` 24 | Then the stdout should contain exactly "42" 25 | 26 | Scenario: Assign values to collections 27 | Given a file named "main.cloe" with: 28 | """ 29 | (seq! ..(map print [ 30 | (assign {} "foo" 123) 31 | (assign {"bar" 123} "bar" 456) 32 | (assign [123] 1 456) 33 | (assign [123 456 789] 2 42) 34 | (assign "Hallo, world!" 2 "e") 35 | (assign "right" 1 "l")])) 36 | """ 37 | When I successfully run `cloe main.cloe` 38 | Then the stdout should contain exactly: 39 | """ 40 | {"foo" 123} 41 | {"bar" 456} 42 | [456] 43 | [123 42 789] 44 | Hello, world! 45 | light 46 | """ 47 | 48 | Scenario: Convert a dictionary to a list 49 | Given a file named "main.cloe" with: 50 | """ 51 | (print (toList {123 456 "foo" "bar"})) 52 | """ 53 | When I successfully run `cloe main.cloe` 54 | Then the stdout should contain exactly: 55 | """ 56 | [["foo" "bar"] [123 456]] 57 | """ 58 | 59 | Scenario: Convert a list to a list 60 | Given a file named "main.cloe" with: 61 | """ 62 | (print (toList [123 nil 456 "foo" true "bar" false])) 63 | """ 64 | When I successfully run `cloe main.cloe` 65 | Then the stdout should contain exactly: 66 | """ 67 | [123 nil 456 "foo" true "bar" false] 68 | """ 69 | 70 | Scenario: Convert a string to a list 71 | Given a file named "main.cloe" with: 72 | """ 73 | (print (toList "Cloe is good.")) 74 | """ 75 | When I successfully run `cloe main.cloe` 76 | Then the stdout should contain exactly: 77 | """ 78 | ["C" "l" "o" "e" " " "i" "s" " " "g" "o" "o" "d" "."] 79 | """ 80 | -------------------------------------------------------------------------------- /examples/comparison.feature: -------------------------------------------------------------------------------- 1 | Feature: Comparison 2 | Scenario: Use < operator 3 | Given a file named "main.cloe" with: 4 | """ 5 | (print (if (< 1 2 3) "OK" "Not OK")) 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "OK" 9 | 10 | Scenario: Use <= operator 11 | Given a file named "main.cloe" with: 12 | """ 13 | (print (if (<= 1 1 3) "OK" "Not OK")) 14 | """ 15 | When I successfully run `cloe main.cloe` 16 | Then the stdout should contain exactly "OK" 17 | 18 | Scenario: Use > operator 19 | Given a file named "main.cloe" with: 20 | """ 21 | (print (if (> 3 2 1) "OK" "Not OK")) 22 | """ 23 | When I successfully run `cloe main.cloe` 24 | Then the stdout should contain exactly "OK" 25 | 26 | Scenario: Use >= operator 27 | Given a file named "main.cloe" with: 28 | """ 29 | (print (if (>= 3 1 1) "OK" "Not OK")) 30 | """ 31 | When I successfully run `cloe main.cloe` 32 | Then the stdout should contain exactly "OK" 33 | 34 | Scenario: Cannot use < operator for boolean values 35 | Given a file named "main.cloe" with: 36 | """ 37 | (print (< false true)) 38 | """ 39 | When I run `cloe main.cloe` 40 | Then the exit status should not be 0 41 | -------------------------------------------------------------------------------- /examples/data_types.feature: -------------------------------------------------------------------------------- 1 | Feature: Data types 2 | Scenario: Use number literals 3 | Given a file named "main.cloe" with: 4 | """ 5 | (let x 123) 6 | (let x -456) 7 | (let x 123.456) 8 | (let x -456.789) 9 | (let x 0xDEADBEEF) 10 | (let x 01234567) 11 | """ 12 | Then I successfully run `cloe main.cloe` 13 | 14 | Scenario: Use string literals 15 | Given a file named "main.cloe" with: 16 | """ 17 | (let x "foo") 18 | (let x "Hello, world!") 19 | (let x "My name is Bob.\nYour name is not Bob.") 20 | (let x "Job:\tProgrammer?") 21 | """ 22 | Then I successfully run `cloe main.cloe` 23 | 24 | Scenario: Expand dictionaries into a dictionary 25 | Given a file named "main.cloe" with: 26 | """ 27 | (print (@ {"foo" 123 ..{"bar" 456} ..{42 2049} ..{nil true true false}} 42)) 28 | """ 29 | When I successfully run `cloe main.cloe` 30 | Then the stdout should contain exactly "2049" 31 | 32 | Scenario: Use a newline character in a string 33 | Given a file named "main.cloe" with: 34 | """ 35 | (print "Hello,\nworld!") 36 | """ 37 | When I successfully run `cloe main.cloe` 38 | Then the stdout should contain exactly: 39 | """ 40 | Hello, 41 | world! 42 | """ 43 | -------------------------------------------------------------------------------- /examples/effects.feature: -------------------------------------------------------------------------------- 1 | Feature: Effects 2 | Scenario: Evaluate multiple effects 3 | Given a file named "main.cloe" with: 4 | """ 5 | (print 123) 6 | (print 456) 7 | (print 789) 8 | """ 9 | When I successfully run `cloe main.cloe` 10 | Then the stdout should contain "123" 11 | And the stdout should contain "456" 12 | And the stdout should contain "789" 13 | 14 | Scenario: Evaluate an expanded effect 15 | Given a file named "main.cloe" with: 16 | """ 17 | ..[(print 123) (print 456) (print 789)] 18 | """ 19 | When I successfully run `cloe main.cloe` 20 | Then the stdout should contain "123" 21 | And the stdout should contain "456" 22 | And the stdout should contain "789" 23 | 24 | Scenario: Purify an effect value 25 | Given a file named "main.cloe" with: 26 | """ 27 | (print (pure (print "Hello, world!"))) 28 | """ 29 | When I successfully run `cloe main.cloe` 30 | Then the stdout should contain exactly: 31 | """ 32 | Hello, world! 33 | nil 34 | """ 35 | -------------------------------------------------------------------------------- /examples/errors.feature: -------------------------------------------------------------------------------- 1 | Feature: Errors 2 | Scenario: Run an erroneous code 3 | Given a file named "main.cloe" with: 4 | """ 5 | (print (+ 1 true)) 6 | """ 7 | When I run `cloe main.cloe` 8 | Then the exit status should not be 0 9 | And the stdout should contain exactly "" 10 | And the stderr should contain "Error" 11 | And the stderr should contain "main.cloe" 12 | And the stderr should contain "(print (+ 1 true))" 13 | 14 | Scenario: Bind 2 values to an argument 15 | Given a file named "main.cloe" with: 16 | """ 17 | (def (f x) 18 | (def (g y) 19 | (def (h z) (+ x y z)) 20 | h) 21 | g) 22 | 23 | (print (((f 123) 456) . x 0)) 24 | """ 25 | When I run `cloe main.cloe` 26 | Then the exit status should not be 0 27 | And the stdout should contain exactly "" 28 | And the stderr should contain "Error" 29 | 30 | Scenario: Catch an error 31 | Given a file named "main.cloe" with: 32 | """ 33 | (print (catch (+ 1 true))) 34 | """ 35 | When I successfully run `cloe main.cloe` 36 | Then the stdout should contain "name" 37 | And the stdout should contain "message" 38 | 39 | Scenario: Catch an error passed by match expression 40 | Given a file named "main.cloe" with: 41 | """ 42 | (print (@ (catch (match (error "FooError" "") x (error "BarError" ""))) "name")) 43 | """ 44 | When I successfully run `cloe main.cloe` 45 | Then the stdout should contain "FooError" 46 | -------------------------------------------------------------------------------- /examples/math.feature: -------------------------------------------------------------------------------- 1 | Feature: Math 2 | Scenario: Add 2 numbers 3 | Given a file named "main.cloe" with: 4 | """ 5 | (print (+ 2016 33)) 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "2049" 9 | 10 | Scenario: Subtract a number from the other 11 | Given a file named "main.cloe" with: 12 | """ 13 | (print (- 2049 33)) 14 | """ 15 | When I successfully run `cloe main.cloe` 16 | Then the stdout should contain exactly "2016" 17 | 18 | Scenario: Divide a number by the other 19 | Given a file named "main.cloe" with: 20 | """ 21 | (print (/ 84 2)) 22 | """ 23 | When I successfully run `cloe main.cloe` 24 | Then the stdout should contain exactly "42" 25 | 26 | Scenario: Use a negative number literal 27 | Given a file named "main.cloe" with: 28 | """ 29 | (print (- 2007 -42)) 30 | """ 31 | When I successfully run `cloe main.cloe` 32 | Then the stdout should contain exactly "2049" 33 | -------------------------------------------------------------------------------- /examples/memory_leak.feature: -------------------------------------------------------------------------------- 1 | Feature: Memory leak 2 | Background: 3 | Given an executable named "leak_memory.sh" with: 4 | """ 5 | #!/bin/sh 6 | 7 | set -e 8 | 9 | cloe $1 > /dev/null & 10 | pid=$! 11 | 12 | sleep 2 # Wait for memory usage to be stable. 13 | 14 | ok=false 15 | last_mem=0 16 | 17 | for _ in $(seq 10) 18 | do 19 | mem=$(ps ho rss $pid) 20 | 21 | if [ $last_mem -ge $mem ] 22 | then 23 | ok=true 24 | break 25 | fi 26 | 27 | last_mem=$mem 28 | sleep 1 29 | done 30 | 31 | kill $pid 32 | $ok 33 | """ 34 | 35 | Scenario: Run infinite effects 36 | This test succeeds only with Go 1.8 onward because of argument liveness. 37 | Given a file named "main.cloe" with: 38 | """ 39 | (def (f) [(print 42) ..(f)]) 40 | ..(f) 41 | """ 42 | When I run `sh leak_memory.sh main.cloe` 43 | Then the exit status should be 0 44 | 45 | Scenario: Evaluate deep recursion 46 | Given a file named "main.cloe" with: 47 | """ 48 | (def (f n) 49 | (match n 50 | 0 "OK!" 51 | _ (f (- n 1)))) 52 | 53 | (print (f 100000000)) 54 | """ 55 | When I run `sh leak_memory.sh main.cloe` 56 | Then the exit status should be 0 57 | 58 | Scenario: Apply a map function to an infinite list 59 | Given a file named "main.cloe" with: 60 | """ 61 | (def (f) [42 ..(f)]) 62 | 63 | ..(map print (f)) 64 | """ 65 | When I run `sh leak_memory.sh main.cloe` 66 | Then the exit status should be 0 67 | 68 | Scenario: Apply a map function to an infinite list of map functions 69 | Given a file named "main.cloe" with: 70 | """ 71 | (def (f) [map ..(f)]) 72 | 73 | ..(map (\ (x) (print (typeOf x))) (f)) 74 | """ 75 | When I run `sh leak_memory.sh main.cloe` 76 | Then the exit status should be 0 77 | 78 | Scenario: Apply max function to an infinite list 79 | Given a file named "main.cloe" with: 80 | """ 81 | (def (f) [42 ..(f)]) 82 | 83 | (print (max ..(f))) 84 | """ 85 | When I run `sh leak_memory.sh main.cloe` 86 | Then the exit status should be 0 87 | -------------------------------------------------------------------------------- /examples/modules/fs.feature: -------------------------------------------------------------------------------- 1 | Feature: File System 2 | Scenario: Import file system module 3 | Given a file named "main.cloe" with: 4 | """ 5 | (import "fs") 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "" 9 | 10 | Scenario: Create a directory 11 | Given a file named "main.cloe" with: 12 | """ 13 | (import "fs") 14 | 15 | (fs.createDirectory "foo") 16 | """ 17 | When I successfully run `cloe main.cloe` 18 | Then I successfully run `rmdir foo` 19 | 20 | Scenario: Remove an entry 21 | Given a file named "main.cloe" with: 22 | """ 23 | (import "fs") 24 | 25 | (fs.remove "foo.txt") 26 | """ 27 | And a file named "foo.txt" with "" 28 | When I successfully run `cloe main.cloe` 29 | Then I run `ls foo.txt` 30 | And the exit status should not be 0 31 | -------------------------------------------------------------------------------- /examples/modules/http.feature: -------------------------------------------------------------------------------- 1 | Feature: HTTP 2 | Scenario: Import HTTP module 3 | Given a file named "main.cloe" with: 4 | """ 5 | (import "http") 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "" 9 | 10 | Scenario: Send GET request 11 | Given a file named "main.cloe" with: 12 | """ 13 | (import "http") 14 | 15 | (print (@ (http.get "http://httpbin.org") "status")) 16 | """ 17 | When I successfully run `cloe main.cloe` 18 | Then the stdout should contain exactly "200" 19 | 20 | Scenario: Send POST request 21 | Given a file named "main.cloe" with: 22 | """ 23 | (import "http") 24 | 25 | (print (@ (http.post "https://httpbin.org/post" "Hello, world!") "status")) 26 | """ 27 | When I successfully run `cloe main.cloe` 28 | Then the stdout should contain exactly "200" 29 | 30 | Scenario: Run a server 31 | Given a file named "main.cloe" with: 32 | """ 33 | (import "http") 34 | 35 | ..(map (\ (r) ((@ r "respond") "Hello, world!")) (http.getRequests ":8080")) 36 | """ 37 | And a file named "main.sh" with: 38 | """ 39 | cloe main.cloe & 40 | pid=$! 41 | sleep 1 && 42 | curl http://127.0.0.1:8080 && 43 | kill $pid 44 | """ 45 | When I successfully run `sh ./main.sh` 46 | Then the stdout should contain exactly "Hello, world!" 47 | -------------------------------------------------------------------------------- /examples/modules/json.feature: -------------------------------------------------------------------------------- 1 | Feature: JSON 2 | Scenario: Import JSON module 3 | Given a file named "main.cloe" with: 4 | """ 5 | (import "json") 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "" 9 | 10 | Scenario: Decode strings in JSON 11 | Given a file named "main.cloe" with: 12 | """ 13 | (import "json") 14 | 15 | (print (json.decode "{\"foo\": 42}")) 16 | """ 17 | When I successfully run `cloe main.cloe` 18 | Then the stdout should contain exactly: 19 | """ 20 | {"foo" 42} 21 | """ 22 | 23 | Scenario: Encode values into JSON 24 | Given a file named "main.cloe" with: 25 | """ 26 | (import "json") 27 | 28 | (seq! 29 | (print (json.encode {"foo" 42})) 30 | (print (json.encode {123 nil})) 31 | (print (json.encode {nil "bar"}))) 32 | """ 33 | When I successfully run `cloe main.cloe` 34 | Then the stdout should contain exactly: 35 | """ 36 | {"foo":42} 37 | {"123":null} 38 | {"null":"bar"} 39 | """ 40 | -------------------------------------------------------------------------------- /examples/modules/os.feature: -------------------------------------------------------------------------------- 1 | Feature: Operating System 2 | Scenario: Import OS module 3 | Given a file named "main.cloe" with: 4 | """ 5 | (import "os") 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "" 9 | 10 | Scenario: Exit with 0 11 | Given a file named "main.cloe" with: 12 | """ 13 | (import "os") 14 | 15 | (os.exit) 16 | """ 17 | When I run `cloe main.cloe` 18 | Then the exit status should be 0 19 | 20 | Scenario: Exit with 1 21 | Given a file named "main.cloe" with: 22 | """ 23 | (import "os") 24 | 25 | (os.exit 1) 26 | """ 27 | When I run `cloe main.cloe` 28 | Then the exit status should be 1 29 | -------------------------------------------------------------------------------- /examples/modules/random.feature: -------------------------------------------------------------------------------- 1 | Feature: Random number generator 2 | Scenario: Import random module 3 | Given a file named "main.cloe" with: 4 | """ 5 | (import "random") 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "" 9 | 10 | Scenario: Generate random numbers 11 | Given a file named "main.cloe" with: 12 | """ 13 | (import "random") 14 | 15 | (seq! (print (<= 0 (random.number) 1))) 16 | """ 17 | When I run `cloe main.cloe` 18 | Then the stdout should contain exactly "true" 19 | -------------------------------------------------------------------------------- /examples/modules/re.feature: -------------------------------------------------------------------------------- 1 | Feature: Regular Expression 2 | Scenario: Import regular expression module 3 | Given a file named "main.cloe" with: 4 | """ 5 | (import "re") 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "" 9 | 10 | Scenario: Match pattern with strings 11 | Given a file named "main.cloe" with: 12 | """ 13 | (import "re") 14 | 15 | (seq! 16 | (print (re.match "ab*c" "ac")) 17 | (print (re.match "ab+c" "ac")) 18 | (print (re.match "ab*c" "abbbbbc"))) 19 | """ 20 | When I successfully run `cloe main.cloe` 21 | Then the stdout should contain exactly: 22 | """ 23 | true 24 | false 25 | true 26 | """ 27 | 28 | Scenario: Find pattern in strings 29 | Given a file named "main.cloe" with: 30 | """ 31 | (import "re") 32 | 33 | (seq! 34 | (print (re.find "abc" "ac")) 35 | (print (re.find "ab*c" "ac")) 36 | (print (re.find "a(bc+)" "abcccc")) 37 | (print (re.find "a(b*c)" "abbbbc"))) 38 | """ 39 | When I successfully run `cloe main.cloe` 40 | Then the stdout should contain exactly: 41 | """ 42 | nil 43 | ["ac"] 44 | ["abcccc" "bcccc"] 45 | ["abbbbc" "bbbbc"] 46 | """ 47 | 48 | Scenario: Replace pattern in strings 49 | Given a file named "main.cloe" with: 50 | """ 51 | (import "re") 52 | 53 | (seq! 54 | (print (re.replace "abc" "foo" "ac")) 55 | (print (re.replace "ab*c" "x${0}z" "ac")) 56 | (print (re.replace "a(bc+)" "x${1}z" "abcccc")) 57 | (print (re.replace "a(b*c)" "x${1}z" "abbbbc"))) 58 | """ 59 | When I successfully run `cloe main.cloe` 60 | Then the stdout should contain exactly: 61 | """ 62 | ac 63 | xacz 64 | xbccccz 65 | xbbbbcz 66 | """ 67 | -------------------------------------------------------------------------------- /examples/others.feature: -------------------------------------------------------------------------------- 1 | Feature: Others 2 | Scenario: Run Cloe with an empty source 3 | Given a file named "main.cloe" with: 4 | """ 5 | """ 6 | When I successfully run `cloe main.cloe` 7 | Then the stdout should contain exactly "" 8 | 9 | Scenario: Read a source from stdin 10 | Given a file named "main.cloe" with: 11 | """ 12 | (print "Hello, world!") 13 | """ 14 | When I run the following script: 15 | """ 16 | cloe < main.cloe 17 | """ 18 | Then the stdout should contain exactly "Hello, world!" 19 | 20 | Scenario: Run Cloe script with shebang 21 | Given a file named "main.cloe" with mode "0755" and with: 22 | """ 23 | #!/usr/bin/env cloe 24 | 25 | (print "Hello, world!") 26 | """ 27 | When I successfully run `sh -c ./main.cloe` 28 | Then the stdout should contain exactly "Hello, world!" 29 | -------------------------------------------------------------------------------- /examples/parallelism.feature: -------------------------------------------------------------------------------- 1 | Feature: Parallelism 2 | Scenario: Evaluate effects in parallel 3 | Given a file named "main.cloe" with: 4 | """ 5 | (print (par [1 2 3] [4 5 6] [7 8 9])) 6 | """ 7 | When I successfully run `cloe main.cloe` 8 | Then the stdout should contain exactly "[7 8 9]" 9 | 10 | Scenario: Evaluate effects sequentially 11 | Given a file named "main.cloe" with: 12 | """ 13 | (seq! 14 | (print 0) 15 | (print 1) 16 | (print 2) 17 | (print 3) 18 | (print 4) 19 | (print 5) 20 | (print 6) 21 | (print 7) 22 | (print 8) 23 | (print 9)) 24 | """ 25 | When I successfully run `cloe main.cloe` 26 | Then the stdout should contain exactly: 27 | """ 28 | 0 29 | 1 30 | 2 31 | 3 32 | 4 33 | 5 34 | 6 35 | 7 36 | 8 37 | 9 38 | """ 39 | 40 | Scenario: Apply rally function to an infinite list 41 | Given a file named "main.cloe" with: 42 | """ 43 | (def (f) [42 ..(f)]) 44 | (let a (f)) 45 | (print (first a)) 46 | (let b (rest a)) 47 | (print (first b)) 48 | (let c (rest b)) 49 | (print (first c)) 50 | """ 51 | When I successfully run `cloe main.cloe` 52 | Then the stdout should contain exactly: 53 | """ 54 | 42 55 | 42 56 | 42 57 | """ 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloe-lang/cloe 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Jeffail/gabs v1.4.0 7 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 8 | github.com/kr/text v0.2.0 9 | github.com/raviqqe/hamt v0.0.0-20221128043838-ed6c8eb5e3bd 10 | github.com/stretchr/testify v1.10.0 11 | gopkg.in/src-d/go-git.v4 v4.13.1 12 | ) 13 | 14 | require ( 15 | github.com/Microsoft/go-winio v0.6.0 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/emirpasic/gods v1.18.1 // indirect 18 | github.com/google/go-cmp v0.5.7 // indirect 19 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 20 | github.com/kevinburke/ssh_config v1.2.0 // indirect 21 | github.com/mitchellh/go-homedir v1.1.0 // indirect 22 | github.com/pkg/errors v0.9.1 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/sergi/go-diff v1.3.1 // indirect 25 | github.com/src-d/gcfg v1.4.0 // indirect 26 | github.com/xanzy/ssh-agent v0.3.3 // indirect 27 | golang.org/x/crypto v0.6.0 // indirect 28 | golang.org/x/mod v0.8.0 // indirect 29 | golang.org/x/net v0.7.0 // indirect 30 | golang.org/x/sys v0.5.0 // indirect 31 | golang.org/x/tools v0.6.0 // indirect 32 | gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect 33 | gopkg.in/warnings.v0 v0.1.2 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /rakefile.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | TOTAL_COVERAGE_FILE = 'coverage.txt' # This path is specified by codecov. 4 | BIN_PATH = File.absolute_path 'bin' 5 | 6 | task :build do 7 | %w[cloe clutil].each do |command| 8 | sh "go build -o bin/#{command} ./src/cmd/#{command}" 9 | end 10 | end 11 | 12 | task :fast_unit_test do 13 | sh 'go test ./...' 14 | end 15 | 16 | task :unit_test do 17 | coverage_file = "/tmp/cloe-unit-test-#{Process.pid}.coverage" 18 | 19 | sh "echo mode: atomic > #{TOTAL_COVERAGE_FILE}" 20 | 21 | `go list ./src/lib/...`.split.each do |package| 22 | sh %W[go test 23 | -covermode atomic 24 | -coverprofile #{coverage_file} 25 | #{package}].join ' ' 26 | 27 | verbose false do 28 | if File.exist? coverage_file 29 | sh "tail -n +2 #{coverage_file} >> #{TOTAL_COVERAGE_FILE}" 30 | rm coverage_file 31 | end 32 | end 33 | end 34 | end 35 | 36 | task command_test: :build do 37 | sh 'bundler install' 38 | sh %W[bundler exec cucumber 39 | -r examples/aruba.rb 40 | PATH=#{BIN_PATH}:$PATH 41 | examples].join ' ' 42 | end 43 | 44 | task :performance_test do 45 | sh 'go test -v -tags performance -run "^TestPerformance" ./...' 46 | end 47 | 48 | task :data_race_test do 49 | raise 'Architecture is not amd64' unless `uname -m` =~ /x86_64/ 50 | 51 | sh 'go test -race ./...' 52 | end 53 | 54 | task test: %i[unit_test command_test] 55 | 56 | task :bench do 57 | sh "go test -bench . -run '^$' -benchmem ./..." 58 | end 59 | 60 | task :format do 61 | sh 'go fix ./...' 62 | sh 'go fmt ./...' 63 | sh 'rubocop -a' 64 | end 65 | 66 | task :lint do 67 | sh 'gem install rubocop' 68 | sh 'rubocop' 69 | end 70 | 71 | task install: %i[test build] do 72 | sh 'go get ./...' 73 | end 74 | 75 | task default: %i[test build] 76 | 77 | task :clean do 78 | sh 'git clean -dfx' 79 | end 80 | -------------------------------------------------------------------------------- /src/cmd/cloe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime/pprof" 7 | "strings" 8 | 9 | "github.com/cloe-lang/cloe/src/lib/compile" 10 | "github.com/cloe-lang/cloe/src/lib/debug" 11 | "github.com/cloe-lang/cloe/src/lib/run" 12 | "github.com/docopt/docopt-go" 13 | ) 14 | 15 | func main() { 16 | args := getArgs() 17 | 18 | if args["--debug"].(bool) { 19 | debug.Debug = true 20 | } else { 21 | defer func() { 22 | if r := recover(); r != nil { 23 | switch x := r.(type) { 24 | case error: 25 | printToStderr(x.Error()) 26 | case string: 27 | printToStderr(x) 28 | default: 29 | panic(x) 30 | } 31 | 32 | os.Exit(1) 33 | } 34 | }() 35 | } 36 | 37 | if args["--profile"] != nil { 38 | f, err := os.Create(args["--profile"].(string)) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | err = pprof.StartCPUProfile(f) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | defer pprof.StopCPUProfile() 49 | } 50 | 51 | es, err := compile.Compile(args[""].(string)) 52 | 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | run.Run(es) 58 | } 59 | 60 | func getArgs() map[string]interface{} { 61 | usage := `Cloe interpreter 62 | 63 | Usage: 64 | cloe [-d] [-p ] [] 65 | 66 | Options: 67 | -d, --debug Turn on debug mode. 68 | -p, --profile Turn on profiling. 69 | -h, --help Show this help.` 70 | 71 | args, err := docopt.ParseArgs(usage, os.Args[1:], "0.1.0") 72 | 73 | if err != nil { 74 | printToStderr(err.Error()) 75 | os.Exit(1) 76 | } else if args[""] == nil { 77 | args[""] = "" 78 | } 79 | 80 | return args 81 | } 82 | 83 | func printToStderr(s string) { 84 | fmt.Fprintf(os.Stderr, strings.TrimSpace(s)+"\n") 85 | } 86 | -------------------------------------------------------------------------------- /src/cmd/clutil/clean.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/consts" 7 | ) 8 | 9 | func clean() error { 10 | for _, f := range []func() (string, error){consts.GetModulesDirectory, consts.GetCommandsDirectory} { 11 | d, err := f() 12 | 13 | if err != nil { 14 | return err 15 | } 16 | 17 | if err := os.RemoveAll(d); err != nil { 18 | return err 19 | } 20 | } 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /src/cmd/clutil/git.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "gopkg.in/src-d/go-git.v4" 7 | ) 8 | 9 | func gitPull(p string) error { 10 | r, err := git.PlainOpen(p) 11 | 12 | if err != nil { 13 | return err 14 | } 15 | 16 | w, err := r.Worktree() 17 | 18 | if err != nil { 19 | return err 20 | } 21 | 22 | err = w.Pull(&git.PullOptions{RemoteName: "origin"}) 23 | 24 | if err != nil && err != git.NoErrAlreadyUpToDate { 25 | return err 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func gitClone(u, p string) error { 32 | _, err := git.PlainClone(p, false, &git.CloneOptions{ 33 | URL: u, 34 | Progress: os.Stdout, 35 | }) 36 | 37 | return err 38 | } 39 | -------------------------------------------------------------------------------- /src/cmd/clutil/install.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func install(u string) error { 4 | i, err := newInstaller(u) 5 | 6 | if err != nil { 7 | return err 8 | } 9 | 10 | err = i.InstallModule() 11 | 12 | if err != nil { 13 | return err 14 | } 15 | 16 | return i.InstallCommands() 17 | } 18 | -------------------------------------------------------------------------------- /src/cmd/clutil/installer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/url" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/cloe-lang/cloe/src/lib/consts" 11 | ) 12 | 13 | type installer struct { 14 | url *url.URL 15 | modulesDirectory string 16 | moduleDirectory string 17 | } 18 | 19 | func newInstaller(s string) (installer, error) { 20 | d, err := consts.GetModulesDirectory() 21 | 22 | if err != nil { 23 | return installer{}, err 24 | } 25 | 26 | u, err := url.Parse(s) 27 | 28 | if err != nil { 29 | return installer{}, err 30 | } 31 | 32 | return installer{ 33 | u, 34 | d, 35 | filepath.Join(d, u.Hostname(), filepath.FromSlash(u.Path)), 36 | }, nil 37 | } 38 | 39 | func (i installer) InstallModule() error { 40 | j, err := os.Stat(i.moduleDirectory) 41 | 42 | if err == nil { 43 | if !j.IsDir() { 44 | return errors.New("module directory is not a directory") 45 | } 46 | 47 | return gitPull(i.moduleDirectory) 48 | } 49 | 50 | if !os.IsNotExist(err) { 51 | return err 52 | } 53 | 54 | return gitClone(i.url.String(), i.moduleDirectory) 55 | } 56 | 57 | func (i installer) InstallCommands() error { 58 | ps := []string{} 59 | 60 | err := filepath.Walk(i.moduleDirectory, func(p string, i os.FileInfo, err error) error { 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if !i.IsDir() && filepath.Base(p) == "main.cloe" { 66 | ps = append(ps, p) 67 | } 68 | 69 | return nil 70 | }) 71 | 72 | if err != nil { 73 | return err 74 | } 75 | 76 | d, err := consts.GetCommandsDirectory() 77 | 78 | if err != nil { 79 | return err 80 | } 81 | 82 | for _, p := range ps { 83 | bs, err := ioutil.ReadFile(p) 84 | 85 | if err != nil { 86 | return err 87 | } 88 | 89 | err = ioutil.WriteFile(filepath.Join(d, filepath.Base(filepath.Dir(p))), bs, 0755) 90 | 91 | if err != nil { 92 | return err 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /src/cmd/clutil/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/docopt/docopt-go" 8 | ) 9 | 10 | func main() { 11 | if err := command(); err != nil { 12 | fmt.Fprintln(os.Stderr, err) 13 | os.Exit(1) 14 | } 15 | } 16 | 17 | func command() error { 18 | c, args, err := getArgs() 19 | 20 | if err != nil { 21 | return err 22 | } 23 | 24 | switch c { 25 | case "install": 26 | return install(args[""].(string)) 27 | case "update": 28 | return update() 29 | case "clean": 30 | return clean() 31 | default: 32 | panic("invalid subcommand") 33 | } 34 | } 35 | 36 | func getArgs() (string, map[string]interface{}, error) { 37 | usage := `Cloe utility 38 | 39 | Usage: 40 | clutil install 41 | clutil update 42 | clutil clean 43 | 44 | Options: 45 | -h, --help Show this help.` 46 | 47 | args, err := docopt.ParseArgs(usage, os.Args[1:], "0.1.0") 48 | 49 | if err != nil { 50 | return "", nil, err 51 | } else if args[""] == nil { 52 | args[""] = "" 53 | } 54 | 55 | return os.Args[1], args, nil 56 | } 57 | -------------------------------------------------------------------------------- /src/cmd/clutil/update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/cloe-lang/cloe/src/lib/consts" 9 | ) 10 | 11 | func update() error { 12 | d, err := consts.GetModulesDirectory() 13 | 14 | if err != nil { 15 | return err 16 | } 17 | 18 | rs, err := listRepositories(d) 19 | 20 | if err != nil { 21 | return err 22 | } 23 | 24 | for _, r := range rs { 25 | if err := gitPull(r); err != nil { 26 | return err 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func listRepositories(root string) ([]string, error) { 34 | rs := []string{} 35 | 36 | ds, err := ioutil.ReadDir(root) 37 | 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | for _, d := range ds { 43 | d := filepath.Join(root, d.Name()) 44 | 45 | if i, err := os.Stat(filepath.Join(d, ".git")); err == nil && i.IsDir() { 46 | rs = append(rs, d) 47 | continue 48 | } 49 | 50 | ss, err := listRepositories(d) 51 | 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | rs = append(rs, ss...) 57 | } 58 | 59 | return rs, nil 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/ast/anonymous_function.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // AnonymousFunction represents a anonymous function as an expression. 6 | type AnonymousFunction struct { 7 | signature Signature 8 | lets []interface{} 9 | body interface{} 10 | } 11 | 12 | // NewAnonymousFunction creates a anonymous function. 13 | func NewAnonymousFunction(s Signature, ls []interface{}, b interface{}) AnonymousFunction { 14 | return AnonymousFunction{s, ls, b} 15 | } 16 | 17 | // Signature returns a signature of an anonymous function. 18 | func (f AnonymousFunction) Signature() Signature { 19 | return f.signature 20 | } 21 | 22 | // Lets returns let statements contained in an anonymous function. 23 | func (f AnonymousFunction) Lets() []interface{} { 24 | return f.lets 25 | } 26 | 27 | // Body returns a body expression of an anonymous function. 28 | func (f AnonymousFunction) Body() interface{} { 29 | return f.body 30 | } 31 | 32 | func (f AnonymousFunction) String() string { 33 | return fmt.Sprintf("(\\ (%v) %v)", f.signature, f.body) 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/ast/app.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/debug" 7 | ) 8 | 9 | // App represents an application of a function to arguments. 10 | type App struct { 11 | function interface{} 12 | args Arguments 13 | info *debug.Info 14 | } 15 | 16 | // NewApp creates an App from a function and arguments with debug information. 17 | func NewApp(f interface{}, args Arguments, info *debug.Info) App { 18 | return App{f, args, info} 19 | } 20 | 21 | // NewPApp don't creates PPap but PApp. 22 | func NewPApp(f interface{}, args []interface{}, info *debug.Info) App { 23 | ps := make([]PositionalArgument, 0, len(args)) 24 | 25 | for _, arg := range args { 26 | ps = append(ps, NewPositionalArgument(arg, false)) 27 | } 28 | 29 | return App{f, NewArguments(ps, nil), info} 30 | } 31 | 32 | // Function returns a function of an application. 33 | func (a App) Function() interface{} { 34 | return a.function 35 | } 36 | 37 | // Arguments returns arguments of an application. 38 | func (a App) Arguments() Arguments { 39 | return a.args 40 | } 41 | 42 | // DebugInfo returns debug information of an application. 43 | func (a App) DebugInfo() *debug.Info { 44 | return a.info 45 | } 46 | 47 | func (a App) String() string { 48 | return fmt.Sprintf("(%v %v)", a.function, a.args) 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/ast/arguments.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Arguments represents arguments passed to a function. 8 | type Arguments struct { 9 | positionals []PositionalArgument 10 | keywords []KeywordArgument 11 | } 12 | 13 | // NewArguments creates arguments. 14 | func NewArguments(ps []PositionalArgument, ks []KeywordArgument) Arguments { 15 | return Arguments{ps, ks} 16 | } 17 | 18 | // Positionals returns positional arguments contained in arguments. 19 | func (a Arguments) Positionals() []PositionalArgument { 20 | return a.positionals 21 | } 22 | 23 | // Keywords returns keyword arguments contained in arguments. 24 | func (a Arguments) Keywords() []KeywordArgument { 25 | return a.keywords 26 | } 27 | 28 | func (a Arguments) String() string { 29 | ss := make([]string, 0, len(a.positionals)+len(a.keywords)+1) 30 | 31 | for _, p := range a.positionals { 32 | ss = append(ss, p.String()) 33 | } 34 | 35 | if len(a.keywords) > 0 { 36 | ss = append(ss, ".") 37 | } 38 | 39 | for _, k := range a.keywords { 40 | ss = append(ss, k.String()) 41 | } 42 | 43 | return strings.Join(ss, " ") 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/ast/def_function.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/debug" 8 | "github.com/kr/text" 9 | ) 10 | 11 | // DefFunction represents a def-function statement node in ASTs. 12 | type DefFunction struct { 13 | name string 14 | signature Signature 15 | lets []interface{} 16 | body interface{} 17 | info *debug.Info 18 | } 19 | 20 | // NewDefFunction creates a DefFunction from its function name, signature, 21 | // internal let statements, and a body expression. 22 | func NewDefFunction(name string, sig Signature, lets []interface{}, expr interface{}, i *debug.Info) DefFunction { 23 | return DefFunction{name, sig, lets, expr, i} 24 | } 25 | 26 | // Name returns a name of a function defined by the def-function statement. 27 | func (f DefFunction) Name() string { 28 | return f.name 29 | } 30 | 31 | // Signature returns a signature of a function defined by the def-function statement. 32 | func (f DefFunction) Signature() Signature { 33 | return f.signature 34 | } 35 | 36 | // Lets returns let statements contained in the def-function statement. 37 | // Returned values should be LetVar or DefFunction. 38 | func (f DefFunction) Lets() []interface{} { 39 | return f.lets 40 | } 41 | 42 | // Body returns a body expression of a function defined by the def-function statement. 43 | func (f DefFunction) Body() interface{} { 44 | return f.body 45 | } 46 | 47 | // DebugInfo returns debug information of a function defined by the def-function statement. 48 | func (f DefFunction) DebugInfo() *debug.Info { 49 | return f.info 50 | } 51 | 52 | func (f DefFunction) String() string { 53 | ss := make([]string, 0, len(f.lets)) 54 | 55 | for _, l := range f.lets { 56 | ss = append(ss, text.Indent(l.(fmt.Stringer).String(), "\t")) 57 | } 58 | 59 | return fmt.Sprintf("(def (%v %v)\n%v\t%v)\n", f.name, f.signature, strings.Join(ss, ""), f.body) 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/ast/effect.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // Effect represents effects of programs. 6 | type Effect struct { 7 | expr interface{} 8 | expanded bool 9 | } 10 | 11 | // NewEffect creates an Effect. 12 | func NewEffect(expr interface{}, expanded bool) Effect { 13 | return Effect{expr, expanded} 14 | } 15 | 16 | // Expr returns an expression of the effect. 17 | func (o Effect) Expr() interface{} { 18 | return o.expr 19 | } 20 | 21 | // Expanded returns true when the effect is expanded. 22 | func (o Effect) Expanded() bool { 23 | return o.expanded 24 | } 25 | 26 | func (o Effect) String() string { 27 | if o.expanded { 28 | return fmt.Sprintf("..%v", o.expr) 29 | } 30 | 31 | return fmt.Sprintf("%v", o.expr) 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/ast/import.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/debug" 7 | ) 8 | 9 | // Import represents an import of a sub module. 10 | type Import struct { 11 | path string 12 | prefix string 13 | info *debug.Info 14 | } 15 | 16 | // NewImport creates an Import. 17 | func NewImport(path, prefix string, info *debug.Info) Import { 18 | return Import{path, prefix, info} 19 | } 20 | 21 | // Path returns a path to an imported sub module. 22 | func (i Import) Path() string { 23 | return i.path 24 | } 25 | 26 | // Prefix returns a prefix apppended to members' names in the imported module. 27 | func (i Import) Prefix() string { 28 | return i.prefix 29 | } 30 | 31 | func (i Import) String() string { 32 | return fmt.Sprintf("(import %v)", i.path) 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/ast/keyword_argument.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // KeywordArgument represents a keyword argument passed to a function. 6 | type KeywordArgument struct { 7 | name string 8 | value interface{} 9 | } 10 | 11 | // NewKeywordArgument creates a keyword argument from a bound name and its value. 12 | func NewKeywordArgument(name string, value interface{}) KeywordArgument { 13 | return KeywordArgument{name, value} 14 | } 15 | 16 | // Name returns a bound name of a keyword argument. 17 | func (k KeywordArgument) Name() string { 18 | return k.name 19 | } 20 | 21 | // Value returns a value of a keyword argument. 22 | func (k KeywordArgument) Value() interface{} { 23 | return k.value 24 | } 25 | 26 | func (k KeywordArgument) String() string { 27 | if k.name == "" { 28 | return fmt.Sprintf("..%v", k.value) 29 | } 30 | 31 | return fmt.Sprintf("%v %v", k.name, k.value) 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/ast/keyword_parameters.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "strings" 4 | 5 | type keywordParameters struct { 6 | parameters []OptionalParameter 7 | rest string 8 | } 9 | 10 | func (ks keywordParameters) names() []string { 11 | ns := make([]string, 0, len(ks.parameters)+1) 12 | 13 | for _, k := range ks.parameters { 14 | ns = append(ns, k.name) 15 | } 16 | 17 | if ks.rest != "" { 18 | ns = append(ns, ks.rest) 19 | } 20 | 21 | return ns 22 | } 23 | 24 | func (ks keywordParameters) String() string { 25 | ss := make([]string, 0, len(ks.parameters)+1) 26 | 27 | for _, o := range ks.parameters { 28 | ss = append(ss, o.String()) 29 | } 30 | 31 | if ks.rest != "" { 32 | ss = append(ss, ".."+ks.rest) 33 | } 34 | 35 | return strings.Join(ss, " ") 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/ast/let_expression.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // LetExpression represents a let expression node in ASTs. 9 | type LetExpression struct { 10 | lets []interface{} 11 | expr interface{} 12 | } 13 | 14 | // NewLetExpression creates a let expression. 15 | func NewLetExpression(lets []interface{}, expr interface{}) LetExpression { 16 | return LetExpression{lets, expr} 17 | } 18 | 19 | // Lets returns let-match statements used in the let expression. 20 | func (l LetExpression) Lets() []interface{} { 21 | return l.lets 22 | } 23 | 24 | // Expr returns an expression of the let expression. 25 | func (l LetExpression) Expr() interface{} { 26 | return l.expr 27 | } 28 | 29 | func (l LetExpression) String() string { 30 | ss := []string{} 31 | 32 | for _, l := range l.lets { 33 | switch l := l.(type) { 34 | case LetVar: 35 | ss = append(ss, l.Name(), fmt.Sprint(l.Expr())) 36 | case LetMatch: 37 | ss = append(ss, fmt.Sprint(l.Pattern(), l.Expr())) 38 | default: 39 | panic("unreachable") 40 | } 41 | } 42 | 43 | return fmt.Sprintf("(let %v %v)", strings.Join(ss, " "), l.expr) 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/ast/let_match.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // LetMatch represents a let-match statement in ASTs. 6 | type LetMatch struct { 7 | pattern interface{} 8 | expr interface{} 9 | } 10 | 11 | // NewLetMatch creates a let-match statement from a pattern and an expression. 12 | func NewLetMatch(pattern interface{}, expr interface{}) LetMatch { 13 | return LetMatch{pattern, expr} 14 | } 15 | 16 | // Pattern returns a pattern matched with a value of the let-match statement. 17 | func (m LetMatch) Pattern() interface{} { 18 | return m.pattern 19 | } 20 | 21 | // Expr returns an expression of a variable value"" 22 | func (m LetMatch) Expr() interface{} { 23 | return m.expr 24 | } 25 | 26 | func (m LetMatch) String() string { 27 | return fmt.Sprintf("(let %v %v)\n", m.pattern, m.expr) 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/ast/let_var.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // LetVar represents a let-variable statement node in ASTs. 6 | type LetVar struct { 7 | name string 8 | expr interface{} 9 | } 10 | 11 | // NewLetVar creates a LetVar from a variable name and its value of an expression. 12 | func NewLetVar(name string, expr interface{}) LetVar { 13 | return LetVar{name, expr} 14 | } 15 | 16 | // Name returns a variable name defined by the let-variable statement. 17 | func (v LetVar) Name() string { 18 | return v.name 19 | } 20 | 21 | // Expr returns an expression of a variable value"" 22 | func (v LetVar) Expr() interface{} { 23 | return v.expr 24 | } 25 | 26 | func (v LetVar) String() string { 27 | return fmt.Sprintf("(let %v %v)\n", v.name, v.expr) 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/ast/match.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Match represents a match expression. 9 | type Match struct { 10 | value interface{} 11 | cases []MatchCase 12 | } 13 | 14 | // NewMatch creates a match expression. 15 | func NewMatch(v interface{}, cs []MatchCase) Match { 16 | if len(cs) == 0 { 17 | panic("MatchCases in a match expression must be more than 0.") 18 | } 19 | 20 | return Match{v, cs} 21 | } 22 | 23 | // Value returns a value which will be matched with patterns in a match expression. 24 | func (m Match) Value() interface{} { 25 | return m.value 26 | } 27 | 28 | // Cases returns pairs of a pattern and corrensponding value in a match expression. 29 | func (m Match) Cases() []MatchCase { 30 | return m.cases 31 | } 32 | 33 | func (m Match) String() string { 34 | ss := make([]string, 0, len(m.cases)) 35 | 36 | for _, c := range m.cases { 37 | ss = append(ss, c.String()) 38 | } 39 | 40 | return fmt.Sprintf("(match %v %v)", m.value, strings.Join(ss, " ")) 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/ast/match_case.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // MatchCase represents a case of a pattern and corrensponding value. 6 | type MatchCase struct { 7 | pattern interface{} 8 | value interface{} 9 | } 10 | 11 | // NewMatchCase creates a case in a match expression. 12 | func NewMatchCase(p interface{}, v interface{}) MatchCase { 13 | return MatchCase{p, v} 14 | } 15 | 16 | // Pattern returns a pattern of a case in a match expression. 17 | func (c MatchCase) Pattern() interface{} { 18 | return c.pattern 19 | } 20 | 21 | // Value returns a value corrensponding to a pattern in a match expression. 22 | func (c MatchCase) Value() interface{} { 23 | return c.value 24 | } 25 | 26 | func (c MatchCase) String() string { 27 | return fmt.Sprintf("%v %v", c.pattern, c.value) 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/ast/mutual_recursion.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/debug" 8 | ) 9 | 10 | // MutualRecursion represents a definition of mutually-recursive functions. 11 | type MutualRecursion struct { 12 | letFunctions []DefFunction 13 | info *debug.Info 14 | } 15 | 16 | // NewMutualRecursion creates a mutual recursion node from mutually-recursive 17 | // functions. 18 | func NewMutualRecursion(fs []DefFunction, i *debug.Info) MutualRecursion { 19 | if len(fs) < 2 { 20 | panic("A number of mutually recursive functions must be more than 2.") 21 | } 22 | 23 | return MutualRecursion{fs, i} 24 | } 25 | 26 | // DefFunctions returns let-function statements in a mutual recursion 27 | // definition. 28 | func (mr MutualRecursion) DefFunctions() []DefFunction { 29 | return mr.letFunctions 30 | } 31 | 32 | // DebugInfo returns debug information of mutually-recursive function definition. 33 | func (mr MutualRecursion) DebugInfo() *debug.Info { 34 | return mr.info 35 | } 36 | 37 | func (mr MutualRecursion) String() string { 38 | ss := make([]string, 0, len(mr.letFunctions)) 39 | 40 | for _, l := range mr.letFunctions { 41 | ss = append(ss, l.String()) 42 | } 43 | 44 | return fmt.Sprintf("(mr %v)", strings.Join(ss, " ")) 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/ast/optional_parameter.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // OptionalParameter represents an optional argument defined in a function. 6 | type OptionalParameter struct { 7 | name string 8 | defaultValue interface{} 9 | } 10 | 11 | // NewOptionalParameter creates an optional argument. 12 | func NewOptionalParameter(n string, v interface{}) OptionalParameter { 13 | return OptionalParameter{n, v} 14 | } 15 | 16 | // Name returns a name of an optional argument. 17 | func (o OptionalParameter) Name() string { 18 | return o.name 19 | } 20 | 21 | // DefaultValue returns a default value of an optional argument. 22 | func (o OptionalParameter) DefaultValue() interface{} { 23 | return o.defaultValue 24 | } 25 | 26 | func (o OptionalParameter) String() string { 27 | return fmt.Sprintf("%v %v", o.name, o.defaultValue) 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/ast/positional_argument.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // PositionalArgument represents a positional argument passed to a function. 6 | type PositionalArgument struct { 7 | value interface{} 8 | expanded bool 9 | } 10 | 11 | // NewPositionalArgument creates a positional argument. 12 | func NewPositionalArgument(value interface{}, expanded bool) PositionalArgument { 13 | return PositionalArgument{value, expanded} 14 | } 15 | 16 | // Value returns a value of a positional argument. 17 | func (p PositionalArgument) Value() interface{} { 18 | return p.value 19 | } 20 | 21 | // Expanded returns true if a positional argument is an expanded list or false otherwise. 22 | func (p PositionalArgument) Expanded() bool { 23 | return p.expanded 24 | } 25 | 26 | func (p PositionalArgument) String() string { 27 | if p.expanded { 28 | return fmt.Sprintf("..%v", p.value) 29 | } 30 | 31 | return fmt.Sprintf("%v", p.value) 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/ast/positional_parameters.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "strings" 4 | 5 | type positionalParameters struct { 6 | parameters []string 7 | rest string 8 | } 9 | 10 | func (ps positionalParameters) names() []string { 11 | ns := ps.parameters 12 | 13 | if ps.rest != "" { 14 | ns = append(ns, ps.rest) 15 | } 16 | 17 | return ns 18 | } 19 | 20 | func (ps positionalParameters) String() string { 21 | ss := make([]string, 0, len(ps.parameters)+1) 22 | 23 | ss = append(ss, ps.parameters...) 24 | 25 | if ps.rest != "" { 26 | ss = append(ss, ".."+ps.rest) 27 | } 28 | 29 | return strings.Join(ss, " ") 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/ast/signature.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Signature represents a signature of a function. 4 | type Signature struct { 5 | positionals positionalParameters 6 | keywords keywordParameters 7 | } 8 | 9 | // NewSignature creates a Signature from {positional, keyword} x 10 | // {required, optional} arguments and a positional rest argument 11 | // and a keyword rest argument. 12 | func NewSignature(ps []string, pr string, ks []OptionalParameter, kr string) Signature { 13 | return Signature{ 14 | positionals: positionalParameters{ps, pr}, 15 | keywords: keywordParameters{ks, kr}, 16 | } 17 | } 18 | 19 | // Positionals returns positional required arguments of a signature. 20 | func (s Signature) Positionals() []string { 21 | return s.positionals.parameters 22 | } 23 | 24 | // RestPositionals returns a positional rest argument of a signature. 25 | func (s Signature) RestPositionals() string { 26 | return s.positionals.rest 27 | } 28 | 29 | // Keywords returns keyword optional arguments of a signature. 30 | func (s Signature) Keywords() []OptionalParameter { 31 | return s.keywords.parameters 32 | } 33 | 34 | // RestKeywords returns a keyword rest argument of a signature. 35 | func (s Signature) RestKeywords() string { 36 | return s.keywords.rest 37 | } 38 | 39 | // NameToIndex converts an argument name into an index in arguments inside a signature. 40 | func (s Signature) NameToIndex() map[string]int { 41 | m := map[string]int{} 42 | 43 | for i, n := range append(s.positionals.names(), s.keywords.names()...) { 44 | m[n] = i 45 | } 46 | 47 | return m 48 | } 49 | 50 | func (s Signature) String() string { 51 | str := s.positionals.String() 52 | 53 | if ks := s.keywords.String(); ks != "" { 54 | str += " . " + ks 55 | } 56 | 57 | return str 58 | } 59 | -------------------------------------------------------------------------------- /src/lib/ast/switch.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Switch represents a switch expression. 9 | type Switch struct { 10 | value interface{} 11 | cases []SwitchCase 12 | defaultCase interface{} 13 | } 14 | 15 | // NewSwitch creates a match expression. 16 | func NewSwitch(v interface{}, cs []SwitchCase, d interface{}) Switch { 17 | if len(cs) == 0 && d == nil { 18 | panic("Cases in a match expression must be more than 0.") 19 | } 20 | 21 | return Switch{v, cs, d} 22 | } 23 | 24 | // Value returns a value which will be matched with patterns in a match expression. 25 | func (s Switch) Value() interface{} { 26 | return s.value 27 | } 28 | 29 | // Cases returns pairs of a pattern and corrensponding value in a match expression. 30 | func (s Switch) Cases() []SwitchCase { 31 | return s.cases 32 | } 33 | 34 | // DefaultCase returns a default case in a switch expression. 35 | func (s Switch) DefaultCase() interface{} { 36 | return s.defaultCase 37 | } 38 | 39 | func (s Switch) String() string { 40 | ss := make([]string, 0, len(s.cases)) 41 | 42 | for _, c := range s.cases { 43 | ss = append(ss, c.String()) 44 | } 45 | 46 | return fmt.Sprintf("(switch %v %v %v)", s.value, strings.Join(ss, " "), s.defaultCase) 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/ast/switch_case.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | // SwitchCase represents a case of a pattern and corrensponding value. 6 | type SwitchCase struct { 7 | pattern string 8 | value interface{} 9 | } 10 | 11 | // NewSwitchCase creates a case in a switch expression. 12 | func NewSwitchCase(p string, v interface{}) SwitchCase { 13 | return SwitchCase{p, v} 14 | } 15 | 16 | // Pattern returns a pattern of a case in a switch expression. 17 | func (c SwitchCase) Pattern() string { 18 | return c.pattern 19 | } 20 | 21 | // Value returns a value corrensponding to a pattern in a switch expression. 22 | func (c SwitchCase) Value() interface{} { 23 | return c.value 24 | } 25 | 26 | func (c SwitchCase) String() string { 27 | return fmt.Sprintf("%v %v", c.pattern, c.value) 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/builtins/compare.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | func createCompareFunction(checkOrder func(core.NumberType) bool) core.Value { 6 | return core.NewLazyFunction( 7 | core.NewSignature(nil, "args", nil, ""), 8 | func(ts ...core.Value) core.Value { 9 | l := ts[0] 10 | 11 | if v := core.ReturnIfEmptyList(l, core.True); v != nil { 12 | return v 13 | } 14 | 15 | prev := core.PApp(core.First, l) 16 | 17 | if b, err := core.EvalBoolean(core.PApp(core.IsOrdered, prev)); err != nil { 18 | return err 19 | } else if !b { 20 | return core.NotOrderedError(prev) 21 | } 22 | 23 | for { 24 | l = core.PApp(core.Rest, l) 25 | 26 | if v := core.ReturnIfEmptyList(l, core.True); v != nil { 27 | return v 28 | } 29 | 30 | current := core.PApp(core.First, l) 31 | 32 | if n, err := core.EvalNumber(core.PApp(core.Compare, prev, current)); err != nil { 33 | return err 34 | } else if !checkOrder(n) { 35 | return core.False 36 | } 37 | 38 | prev = current 39 | } 40 | }) 41 | } 42 | 43 | // Less checks if arguments are aligned in ascending order or not. 44 | var Less = createCompareFunction(func(n core.NumberType) bool { return n == -1 }) 45 | 46 | // LessEq checks if arguments are aligned in ascending order or not. 47 | var LessEq = createCompareFunction(func(n core.NumberType) bool { return n == -1 || n == 0 }) 48 | 49 | // Greater checks if arguments are aligned in ascending order or not. 50 | var Greater = createCompareFunction(func(n core.NumberType) bool { return n == 1 }) 51 | 52 | // GreaterEq checks if arguments are aligned in ascending order or not. 53 | var GreaterEq = createCompareFunction(func(n core.NumberType) bool { return n == 1 || n == 0 }) 54 | -------------------------------------------------------------------------------- /src/lib/builtins/par.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/core" 5 | "github.com/cloe-lang/cloe/src/lib/systemt" 6 | ) 7 | 8 | // Par evaluates arguments in parallel and returns the last one. 9 | var Par = core.NewLazyFunction( 10 | core.NewSignature(nil, "args", nil, ""), 11 | func(ts ...core.Value) core.Value { 12 | l := ts[0] 13 | 14 | for { 15 | v := core.PApp(core.First, l) 16 | l = core.PApp(core.Rest, l) 17 | 18 | if v := core.ReturnIfEmptyList(l, v); v != nil { 19 | return v 20 | } 21 | 22 | systemt.Daemonize(func() { core.EvalPure(v) }) 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/lib/builtins/par_test.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/cloe-lang/cloe/src/lib/systemt" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPar(t *testing.T) { 12 | go systemt.RunDaemons() 13 | 14 | n := core.PApp(Par, core.True, core.False, core.DummyError, core.Nil) 15 | 16 | assert.True(t, bool(*core.EvalPure(core.PApp(core.Equal, core.Nil, n)).(*core.BooleanType))) 17 | } 18 | 19 | func TestParError(t *testing.T) { 20 | go systemt.RunDaemons() 21 | 22 | _, ok := core.EvalPure(core.PApp(Par, 23 | core.True, 24 | core.False, 25 | core.Nil, 26 | core.DummyError, 27 | )).(*core.ErrorType) 28 | 29 | assert.True(t, ok) 30 | } 31 | 32 | func TestParWithNoArgument(t *testing.T) { 33 | go systemt.RunDaemons() 34 | 35 | _, ok := core.EvalPure(core.PApp(Par)).(*core.ErrorType) 36 | assert.True(t, ok) 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/builtins/performance_test.go: -------------------------------------------------------------------------------- 1 | //go:build performance 2 | 3 | package builtins 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPerformanceY(t *testing.T) { 12 | r := testing.Benchmark(BenchmarkY) 13 | s := testing.Benchmark(BenchmarkGoY) 14 | t.Log(r) 15 | t.Log(s) 16 | assert.True(t, r.NsPerOp() < 10*s.NsPerOp()) 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/builtins/print.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/cloe-lang/cloe/src/lib/core" 9 | ) 10 | 11 | // Print prints string representation of arguments to stdout. 12 | var Print = core.NewEffectFunction( 13 | core.NewSignature( 14 | nil, "args", 15 | []core.OptionalParameter{ 16 | core.NewOptionalParameter("sep", core.NewString(" ")), 17 | core.NewOptionalParameter("end", core.NewString("\n")), 18 | core.NewOptionalParameter("file", core.NewNumber(1)), 19 | core.NewOptionalParameter("mode", core.NewNumber(0664)), 20 | }, "", 21 | ), 22 | func(ts ...core.Value) core.Value { 23 | sep, err := evalGoString(ts[1]) 24 | 25 | if err != nil { 26 | return err 27 | } 28 | 29 | f, err := evalFileArguments(ts[3], ts[4]) 30 | 31 | if err != nil { 32 | return err 33 | } 34 | 35 | l, err := core.EvalList(ts[0]) 36 | 37 | if err != nil { 38 | return err 39 | } 40 | 41 | ss := []string{} 42 | 43 | for !l.Empty() { 44 | s, err := evalGoString(core.PApp(core.ToString, l.First())) 45 | 46 | if err != nil { 47 | return err 48 | } 49 | 50 | ss = append(ss, s) 51 | 52 | l, err = core.EvalList(l.Rest()) 53 | 54 | if err != nil { 55 | return err 56 | } 57 | } 58 | 59 | end, err := evalGoString(ts[2]) 60 | 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if _, err := fmt.Fprint(f, strings.Join(ss, sep)+end); err != nil { 66 | return fileError(err) 67 | } 68 | 69 | return core.Nil 70 | }) 71 | 72 | func evalGoString(t core.Value) (string, core.Value) { 73 | s, err := core.EvalString(t) 74 | 75 | if err != nil { 76 | return "", err 77 | } 78 | 79 | return string(s), nil 80 | } 81 | 82 | func evalFileArguments(f, m core.Value) (*os.File, core.Value) { 83 | switch x := core.EvalPure(f).(type) { 84 | case core.StringType: 85 | m, e := core.EvalNumber(m) 86 | 87 | if e != nil { 88 | return nil, e 89 | } 90 | 91 | f, err := os.OpenFile( 92 | string(x), 93 | os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 94 | os.FileMode(m)) 95 | 96 | if err != nil { 97 | return nil, fileError(err) 98 | } 99 | 100 | return f, nil 101 | case *core.NumberType: 102 | switch *x { 103 | case 1: 104 | return os.Stdout, nil 105 | case 2: 106 | return os.Stderr, nil 107 | } 108 | } 109 | 110 | return nil, core.ValueError( 111 | "file optional argument's value must be 1 or 2, or a string filename.") 112 | } 113 | -------------------------------------------------------------------------------- /src/lib/builtins/rally.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/cloe-lang/cloe/src/lib/systemt" 8 | ) 9 | 10 | const maxConcurrency = 256 11 | const valueChannelCapacity = 1024 12 | const channelCloseDuration = 100 * time.Millisecond 13 | 14 | // Rally sorts arguments by time. 15 | var Rally = core.NewLazyFunction( 16 | core.NewSignature(nil, "args", nil, ""), 17 | func(vs ...core.Value) core.Value { 18 | c := make(chan core.Value, valueChannelCapacity) 19 | 20 | systemt.Daemonize(func() { 21 | l, err := core.EvalList(vs[0]) 22 | 23 | if err != nil { 24 | c <- err 25 | return 26 | } 27 | 28 | sem := make(chan bool, maxConcurrency) 29 | 30 | for !l.Empty() { 31 | sem <- true 32 | go func(v core.Value) { 33 | c <- core.EvalPure(v) 34 | <-sem 35 | }(l.First()) 36 | 37 | l, err = core.EvalList(l.Rest()) 38 | 39 | if err != nil { 40 | c <- err 41 | break 42 | } 43 | } 44 | 45 | // HACK: Wait for other goroutines to put elements in a value channel 46 | // for a while. This is only for unit test. 47 | time.Sleep(channelCloseDuration) 48 | c <- nil 49 | }) 50 | 51 | return core.PApp(core.PApp(Y, core.NewLazyFunction( 52 | core.NewSignature([]string{"me"}, "", nil, ""), 53 | func(vs ...core.Value) core.Value { 54 | v := <-c 55 | 56 | if v == nil { 57 | return core.EmptyList 58 | } else if err, ok := v.(*core.ErrorType); ok { 59 | return err 60 | } 61 | 62 | return core.StrictPrepend([]core.Value{v}, core.PApp(vs[0])) 63 | }))) 64 | }) 65 | -------------------------------------------------------------------------------- /src/lib/builtins/read.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/cloe-lang/cloe/src/lib/core" 9 | ) 10 | 11 | // Read reads a string from stdin or a file. 12 | var Read = createReadFunction(os.Stdin) 13 | 14 | func createReadFunction(stdin io.Reader) core.Value { 15 | return core.NewLazyFunction( 16 | core.NewSignature( 17 | nil, "", 18 | []core.OptionalParameter{core.NewOptionalParameter("file", core.Nil)}, "", 19 | ), 20 | func(vs ...core.Value) core.Value { 21 | file := stdin 22 | 23 | switch x := core.EvalPure(vs[0]).(type) { 24 | case core.StringType: 25 | var err error 26 | file, err = os.Open(string(x)) 27 | 28 | if err != nil { 29 | return fileError(err) 30 | } 31 | case core.NilType: 32 | default: 33 | s, err := core.StrictDump(x) 34 | 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return core.ValueError( 40 | "file optional argument's value must be nil or a filename. Got %s.", 41 | s) 42 | } 43 | 44 | s, err := ioutil.ReadAll(file) 45 | 46 | if err != nil { 47 | return fileError(err) 48 | } 49 | 50 | return core.NewString(string(s)) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/builtins/read_test.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/cloe-lang/cloe/src/lib/core" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestReadWithStdin(t *testing.T) { 13 | assert.Equal(t, core.NewString(""), core.EvalPure(core.PApp(Read))) 14 | } 15 | 16 | func TestReadError(t *testing.T) { 17 | for _, th := range []core.Value{ 18 | core.True, 19 | core.NewString("nonExistentFile"), 20 | core.DummyError, 21 | core.NewList(core.DummyError), 22 | } { 23 | _, ok := core.EvalPure(core.App( 24 | Read, 25 | core.NewArguments(nil, []core.KeywordArgument{core.NewKeywordArgument("file", th)}), 26 | )).(*core.ErrorType) 27 | 28 | assert.True(t, ok) 29 | } 30 | } 31 | 32 | func TestReadWithClosedStdin(t *testing.T) { 33 | f, err := ioutil.TempFile("", "") 34 | assert.Nil(t, err) 35 | err = f.Close() 36 | assert.Nil(t, err) 37 | defer os.Remove(f.Name()) 38 | 39 | _, ok := core.EvalPure(core.PApp(createReadFunction(f))).(*core.ErrorType) 40 | assert.True(t, ok) 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/builtins/seq.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/core" 5 | ) 6 | 7 | func createSeqFunction(f func(t core.Value) core.Value) core.Value { 8 | return core.NewLazyFunction( 9 | core.NewSignature(nil, "args", nil, ""), 10 | func(ts ...core.Value) core.Value { 11 | l := ts[0] 12 | 13 | for { 14 | t := core.PApp(core.First, l) 15 | l = core.PApp(core.Rest, l) 16 | 17 | if v := core.ReturnIfEmptyList(l, t); v != nil { 18 | return v 19 | } 20 | 21 | if err, ok := f(t).(*core.ErrorType); ok { 22 | return err 23 | } 24 | } 25 | }) 26 | } 27 | 28 | // Seq runs arguments of pure values sequentially and returns the last one. 29 | var Seq = createSeqFunction(func(v core.Value) core.Value { return core.EvalPure(v) }) 30 | 31 | // EffectSeq runs arguments of effects sequentially and returns the last one. 32 | var EffectSeq = createSeqFunction(func(v core.Value) core.Value { return core.EvalImpure(v) }) 33 | -------------------------------------------------------------------------------- /src/lib/builtins/seq_test.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSeq(t *testing.T) { 11 | for _, ts := range [][]core.Value{ 12 | {core.Nil}, 13 | {core.Nil, core.Nil}, 14 | } { 15 | assert.Equal( 16 | t, 17 | core.Nil, 18 | core.EvalPure(core.PApp(Seq, ts...))) 19 | } 20 | } 21 | 22 | func TestSeqWithEffects(t *testing.T) { 23 | w := core.PApp(Print, core.Nil) 24 | 25 | for _, ts := range [][]core.Value{ 26 | {w}, 27 | {w, w}, 28 | } { 29 | _, ok := core.EvalPure(core.PApp(Seq, ts...)).(*core.ErrorType) 30 | assert.True(t, ok) 31 | } 32 | } 33 | 34 | func TestEffectSeq(t *testing.T) { 35 | assert.Equal( 36 | t, 37 | core.Nil, 38 | core.EvalImpure(core.PApp( 39 | EffectSeq, 40 | core.PApp(Print, core.NewNumber(42)), 41 | core.PApp(Print, core.NewString("OK!"))))) 42 | } 43 | 44 | func TestEffectSeqWithPureValues(t *testing.T) { 45 | for _, ts := range [][]core.Value{ 46 | {core.Nil}, 47 | {core.Nil, core.Nil}, 48 | } { 49 | _, ok := core.EvalImpure(core.PApp(EffectSeq, ts...)).(*core.ErrorType) 50 | assert.True(t, ok) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/builtins/util.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | func fileError(err error) core.Value { 6 | return core.NewError("FileError", err.Error()) 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/builtins/util_test.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCheckEmptyListError(t *testing.T) { 11 | v := core.ReturnIfEmptyList(core.DummyError, core.Nil) 12 | t.Log(v) 13 | _, ok := v.(*core.ErrorType) 14 | assert.True(t, ok) 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/builtins/y.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Y is Y combinator which takes a function whose first argument is itself 6 | // applied to the combinator. 7 | // 8 | // THE COMMENT BELOW MAY BE OUTDATED because we moved from a lambda calculus 9 | // based combinator to an implementation based on a recursive function in Go. 10 | // 11 | // Using Y combinator to define built-in functions in Go source is dangerous 12 | // because top-level recursive functions generate infinitely nested closures. 13 | // (i.e. closure{f, x} where x will also be evaluated as closure{f, x}.) 14 | var Y = core.NewLazyFunction( 15 | core.NewSignature([]string{"function"}, "", nil, ""), 16 | func(ts ...core.Value) core.Value { 17 | return y(ts[0]) 18 | }) 19 | 20 | func y(f core.Value) core.Value { 21 | return core.NewRawFunction(func(args core.Arguments) core.Value { 22 | return core.App(f, core.NewPositionalArguments(y(f)).Merge(args)) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/builtins/y_star.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Ys is Y* combinator which takes functions whose first arguments are a list 6 | // of themselves applied to the combinator. 7 | var Ys = core.NewLazyFunction( 8 | core.NewSignature(nil, "functions", nil, ""), 9 | func(ts ...core.Value) core.Value { 10 | t := ts[0] 11 | 12 | return core.PApp(xx, core.NewLazyFunction( 13 | core.NewSignature([]string{"x"}, "", nil, ""), 14 | func(ts ...core.Value) core.Value { 15 | s := ts[0] 16 | 17 | applyF := core.NewLazyFunction( 18 | core.NewSignature([]string{"f"}, "args", nil, "kwargs"), 19 | func(ts ...core.Value) core.Value { 20 | return core.App(ts[0], core.NewArguments( 21 | []core.PositionalArgument{ 22 | core.NewPositionalArgument(core.PApp(s, s), false), 23 | core.NewPositionalArgument(ts[1], true), 24 | }, 25 | []core.KeywordArgument{core.NewKeywordArgument("", ts[2])})) 26 | }) 27 | 28 | return createNewFuncs(t, applyF) 29 | })) 30 | }) 31 | 32 | func createNewFuncs(olds, applyF core.Value) core.Value { 33 | if v := core.ReturnIfEmptyList(olds, core.EmptyList); v != nil { 34 | return v 35 | } 36 | 37 | return core.StrictPrepend( 38 | []core.Value{core.PApp(core.Partial, applyF, core.PApp(core.First, olds))}, 39 | createNewFuncs(core.PApp(core.Rest, olds), applyF)) 40 | } 41 | 42 | var xx = core.NewLazyFunction( 43 | core.NewSignature([]string{"x"}, "", nil, ""), 44 | func(ts ...core.Value) core.Value { 45 | return core.PApp(ts[0], ts[0]) 46 | }) 47 | -------------------------------------------------------------------------------- /src/lib/builtins/y_star_test.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestYsMultipleFs(t *testing.T) { 11 | evenWithExtraArg := core.NewLazyFunction( 12 | core.NewSignature( 13 | []string{"fs", "dummyArg", "num"}, "", 14 | nil, "", 15 | ), 16 | func(ts ...core.Value) core.Value { 17 | n := ts[2] 18 | 19 | return core.PApp(core.If, 20 | core.PApp(core.Equal, n, core.NewNumber(0)), 21 | core.True, 22 | core.PApp(core.PApp(core.First, core.PApp(core.Rest, ts[0])), core.PApp(core.Sub, n, core.NewNumber(1)))) 23 | }) 24 | 25 | odd := core.NewLazyFunction( 26 | core.NewSignature( 27 | []string{"fs", "num"}, "", 28 | nil, "", 29 | ), 30 | func(ts ...core.Value) core.Value { 31 | n := ts[1] 32 | 33 | return core.PApp(core.If, 34 | core.PApp(core.Equal, n, core.NewNumber(0)), 35 | core.False, 36 | core.PApp(core.PApp(core.First, ts[0]), core.Nil, core.PApp(core.Sub, n, core.NewNumber(1)))) 37 | }) 38 | 39 | fs := core.PApp(Ys, evenWithExtraArg, odd) 40 | 41 | for _, n := range []float64{0, 1, 2, 3, 4, 5, 6, 42, 100, 121, 256, 1023} { 42 | b1 := bool(*core.EvalPure(core.PApp(core.PApp(core.First, fs), core.NewString("unused"), core.NewNumber(n))).(*core.BooleanType)) 43 | b2 := bool(*core.EvalPure(core.PApp(core.PApp(core.First, core.PApp(core.Rest, fs)), core.NewNumber(n))).(*core.BooleanType)) 44 | 45 | t.Logf("n = %v, even? %v, odd? %v\n", n, b1, b2) 46 | 47 | rem := int(n) % 2 48 | assert.Equal(t, b1, rem == 0) 49 | assert.Equal(t, b2, rem != 0) 50 | } 51 | } 52 | 53 | func TestYsWithErroneousArgument(t *testing.T) { 54 | v := core.EvalPure(core.App( 55 | Ys, 56 | core.NewArguments( 57 | []core.PositionalArgument{core.NewPositionalArgument(core.DummyError, true)}, 58 | nil))) 59 | _, ok := v.(*core.ErrorType) 60 | assert.True(t, ok) 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/builtins/y_test.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/cloe-lang/cloe/src/lib/systemt" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestY(t *testing.T) { 12 | for _, n := range []float64{0, 1, 2, 3, 4, 5, 6, 42, 100} { 13 | n1 := lazyFactorial(core.NewNumber(n)) 14 | n2 := strictFactorial(n) 15 | 16 | t.Logf("%d: %f == %f?\n", int(n), n1, n2) 17 | 18 | assert.Equal(t, n1, n2) 19 | } 20 | } 21 | 22 | func strictFactorial(n float64) float64 { 23 | if n == 0 { 24 | return 1 25 | } 26 | 27 | return n * strictFactorial(n-1) 28 | } 29 | 30 | func lazyFactorial(v core.Value) float64 { 31 | return float64(*core.EvalPure(core.PApp(core.PApp(Y, lazyFactorialImpl), v)).(*core.NumberType)) 32 | } 33 | 34 | var lazyFactorialImpl = core.NewLazyFunction( 35 | core.NewSignature([]string{"me", "num"}, "", nil, ""), 36 | func(ts ...core.Value) core.Value { 37 | return core.PApp(core.If, 38 | core.PApp(core.Equal, ts[1], core.NewNumber(0)), 39 | core.NewNumber(1), 40 | core.PApp(core.Mul, 41 | ts[1], 42 | core.PApp(ts[0], append([]core.Value{core.PApp(core.Sub, ts[1], core.NewNumber(1))}, ts[2:]...)...))) 43 | }) 44 | 45 | func BenchmarkYFactorial(b *testing.B) { 46 | for i := 0; i < b.N; i++ { 47 | lazyFactorial(core.NewNumber(100)) 48 | } 49 | } 50 | 51 | func BenchmarkYInfiniteRecursion(b *testing.B) { 52 | v := core.PApp(Y, core.NewLazyFunction( 53 | core.NewSignature([]string{"me"}, "", nil, ""), 54 | func(ts ...core.Value) core.Value { 55 | return ts[0] 56 | })) 57 | 58 | b.ResetTimer() 59 | 60 | for i := 0; i < b.N; i++ { 61 | v = core.PApp(core.EvalPure(v)) 62 | } 63 | } 64 | 65 | func BenchmarkY(b *testing.B) { 66 | go systemt.RunDaemons() 67 | core.EvalPure(core.PApp(toZero, core.NewNumber(float64(b.N)))) 68 | } 69 | 70 | func BenchmarkGoY(b *testing.B) { 71 | toZeroGo(float64(b.N)) 72 | } 73 | 74 | var toZero = core.PApp(Y, core.NewLazyFunction( 75 | core.NewSignature([]string{"me", "num"}, "", nil, ""), 76 | func(vs ...core.Value) core.Value { 77 | n, err := core.EvalNumber(vs[1]) 78 | 79 | if err != nil { 80 | return err 81 | } 82 | 83 | if n == 0 { 84 | return core.Nil 85 | } 86 | 87 | n-- 88 | 89 | return core.PApp(vs[0], &n) 90 | })) 91 | 92 | func toZeroGo(f float64) string { 93 | v := core.Value(core.NewNumber(f)) 94 | n := *core.EvalPure(v).(*core.NumberType) 95 | 96 | for n > 0 { 97 | v = core.PApp(core.Sub, v, core.NewNumber(1)) 98 | n = *core.EvalPure(v).(*core.NumberType) 99 | } 100 | 101 | return "Benchmark finished!" 102 | } 103 | -------------------------------------------------------------------------------- /src/lib/compile/cache.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "errors" 5 | "path" 6 | ) 7 | 8 | type modulesCache map[string]module 9 | 10 | func newModulesCache() modulesCache { 11 | return modulesCache{} 12 | } 13 | 14 | func (c modulesCache) Set(p string, m module) error { 15 | if !path.IsAbs(p) { 16 | return errors.New("module path is not absolute") 17 | } 18 | 19 | c[p] = m 20 | 21 | return nil 22 | } 23 | 24 | func (c modulesCache) Get(p string) (module, bool) { 25 | m, ok := c[p] 26 | return m, ok 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/compile/cache_test.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewModulesCache(t *testing.T) { 12 | assert.NotEqual(t, module(nil), newModulesCache()) 13 | } 14 | 15 | func TestModulesSet(t *testing.T) { 16 | assert.Nil(t, newModulesCache().Set("/foo", nil)) 17 | } 18 | 19 | func TestModulesSetInNonExsitentDirectory(t *testing.T) { 20 | inNonExistentDirectory(t, func() { 21 | err := newModulesCache().Set("foo", nil) 22 | assert.NotNil(t, err) 23 | }) 24 | } 25 | 26 | func TestModulesGet(t *testing.T) { 27 | c := newModulesCache() 28 | 29 | err := c.Set("/foo", nil) 30 | assert.Nil(t, err) 31 | 32 | m, ok := c.Get("/foo") 33 | assert.Equal(t, module(nil), m) 34 | assert.True(t, ok) 35 | } 36 | 37 | func inNonExistentDirectory(t *testing.T, f func()) { 38 | d, err := ioutil.TempDir("", "") 39 | assert.Nil(t, err) 40 | 41 | wd, err := os.Getwd() 42 | assert.Nil(t, err) 43 | 44 | os.Chdir(d) 45 | os.Remove(d) 46 | 47 | f() 48 | 49 | os.Chdir(wd) 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/compile/compile.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | 9 | "github.com/cloe-lang/cloe/src/lib/desugar" 10 | "github.com/cloe-lang/cloe/src/lib/parse" 11 | ) 12 | 13 | // Compile compiles a main module of a path into effects of thunks. 14 | func Compile(p string) ([]Effect, error) { 15 | q, s, err := readFileOrStdin(p) 16 | 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | m, err := parse.MainModule(q, s) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | c := newCompiler(builtinsEnvironment(), newModulesCache()) 28 | return c.compileModule(desugar.Desugar(m), filepath.ToSlash(path.Dir(p))) // path.Dir("") == "." 29 | } 30 | 31 | func readFileOrStdin(path string) (string, string, error) { 32 | f := os.Stdin 33 | 34 | if path == "" { 35 | path = "" 36 | } else { 37 | var err error 38 | f, err = os.Open(path) 39 | 40 | if err != nil { 41 | return "", "", err 42 | } 43 | } 44 | 45 | bs, err := ioutil.ReadAll(f) 46 | 47 | if err != nil { 48 | return "", "", err 49 | } 50 | 51 | return path, string(bs), nil 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/compile/compile_test.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "testing" 9 | 10 | "github.com/cloe-lang/cloe/src/lib/consts" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestCompile(t *testing.T) { 15 | for _, s := range []string{ 16 | `(print "Hello, world!")`, 17 | `(import "http") (print (http.get "http://httpbin.org"))`, 18 | } { 19 | f, err := ioutil.TempFile("", "") 20 | assert.Nil(t, err) 21 | 22 | f.WriteString(s) 23 | 24 | err = f.Close() 25 | assert.Nil(t, err) 26 | 27 | es, err := Compile(f.Name()) 28 | 29 | assert.Nil(t, err) 30 | assert.Equal(t, 1, len(es)) 31 | } 32 | } 33 | 34 | func TestCompileSourceOfInvalidSyntax(t *testing.T) { 35 | f, err := ioutil.TempFile("", "") 36 | assert.Nil(t, err) 37 | 38 | f.WriteString(`(print "Hello, world!"`) 39 | 40 | err = f.Close() 41 | assert.Nil(t, err) 42 | 43 | _, err = Compile(f.Name()) 44 | 45 | assert.NotNil(t, err) 46 | } 47 | 48 | func TestCompileWithInvalidPath(t *testing.T) { 49 | _, err := Compile("I'm the invalid path.") 50 | assert.NotNil(t, err) 51 | } 52 | 53 | func TestCompileStdin(t *testing.T) { 54 | es, err := Compile("") 55 | assert.Nil(t, err) 56 | assert.Zero(t, len(es)) 57 | } 58 | 59 | func TestCompileWithSubModule(t *testing.T) { 60 | m := createModuleScript(t) 61 | 62 | f, err := ioutil.TempFile("", "") 63 | assert.Nil(t, err) 64 | 65 | f.WriteString(fmt.Sprintf(`(import "%v") (print (%v.hello "John"))`, m, path.Base(m))) 66 | 67 | err = f.Close() 68 | assert.Nil(t, err) 69 | 70 | es, err := Compile(f.Name()) 71 | 72 | assert.Nil(t, err) 73 | assert.Equal(t, 1, len(es)) 74 | } 75 | 76 | func createModuleScript(t *testing.T) string { 77 | f, err := ioutil.TempFile("", "module") 78 | assert.Nil(t, err) 79 | 80 | f.WriteString(`(def (hello name) (merge "Hello, " name "!"))`) 81 | 82 | err = f.Close() 83 | assert.Nil(t, err) 84 | 85 | err = os.Rename(f.Name(), f.Name()+consts.FileExtension) 86 | assert.Nil(t, err) 87 | 88 | return f.Name() 89 | } 90 | -------------------------------------------------------------------------------- /src/lib/compile/effect.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Effect represents an effect of a program. 6 | type Effect struct { 7 | value core.Value 8 | expanded bool 9 | } 10 | 11 | // NewEffect creates an effect. 12 | func NewEffect(value core.Value, expanded bool) Effect { 13 | return Effect{value, expanded} 14 | } 15 | 16 | // Value returns an effect of a thunk. 17 | func (o Effect) Value() core.Value { 18 | return o.value 19 | } 20 | 21 | // Expanded returns true if it is a expanded list of effects or false otherwise. 22 | func (o Effect) Expanded() bool { 23 | return o.expanded 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/compile/effect_test.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewEffect(t *testing.T) { 11 | t.Log(NewEffect(core.Nil, false)) 12 | } 13 | 14 | func TestEffectValue(t *testing.T) { 15 | assert.NotEqual(t, nil, NewEffect(core.Nil, false).Value()) 16 | } 17 | 18 | func TestEffectExpanded(t *testing.T) { 19 | assert.False(t, NewEffect(core.Nil, false).Expanded()) 20 | assert.True(t, NewEffect(core.EmptyList, true).Expanded()) 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/compile/environment.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | type environment struct { 10 | me module 11 | fallback func(string) (core.Value, error) 12 | } 13 | 14 | func newEnvironment(fallback func(string) (core.Value, error)) environment { 15 | return environment{ 16 | me: module{}, 17 | fallback: fallback, 18 | } 19 | } 20 | 21 | func (e *environment) set(s string, t core.Value) { 22 | e.me[s] = t 23 | } 24 | 25 | func (e environment) get(s string) core.Value { 26 | if t, ok := e.me[s]; ok { 27 | return t 28 | } 29 | 30 | t, err := e.fallback(s) 31 | 32 | if err == nil { 33 | return t 34 | } 35 | 36 | panic(fmt.Errorf("The name, %s is not found", s)) 37 | } 38 | 39 | func (e environment) toMap() module { 40 | return e.me 41 | } 42 | 43 | func (e environment) copy() environment { 44 | m := make(module, len(e.me)) 45 | 46 | for k, v := range e.me { 47 | m[k] = v 48 | } 49 | 50 | return environment{m, e.fallback} 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/compile/environment_test.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/core" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func testFallback(s string) (core.Value, error) { 12 | return nil, errors.New("name not found") 13 | } 14 | 15 | func TestNewEnvironment(t *testing.T) { 16 | newEnvironment(testFallback) 17 | } 18 | 19 | func TestEnvironmentGetFail(t *testing.T) { 20 | assert.Panics(t, func() { 21 | newEnvironment(testFallback).get("foo") 22 | }) 23 | } 24 | 25 | func TestEnvironmentGet(t *testing.T) { 26 | e := newEnvironment(testFallback) 27 | e.set("foo", core.Nil) 28 | v := e.get("foo") 29 | assert.Equal(t, core.Nil, core.EvalPure(v)) 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/compile/module.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | type module map[string]core.Value 6 | -------------------------------------------------------------------------------- /src/lib/compile/performance_test.go: -------------------------------------------------------------------------------- 1 | //go:build performance 2 | 3 | package compile 4 | 5 | import ( 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestPerformanceMap(t *testing.T) { 13 | r := testing.Benchmark(BenchmarkMap) 14 | s := testing.Benchmark(BenchmarkGoMap) 15 | t.Log(r) 16 | t.Log(s) 17 | assert.True(t, r.NsPerOp() < 15*s.NsPerOp()) 18 | } 19 | 20 | func TestPerformanceMapOrder(t *testing.T) { 21 | b := func(N int) float64 { 22 | var start time.Time 23 | benchmarkMap(N, func() { start = time.Now() }, t.Fail) 24 | return time.Since(start).Seconds() 25 | } 26 | 27 | r := b(10000) / b(2000) 28 | t.Log(r) 29 | assert.True(t, 4 < r && r < 6) 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/consts/language_path.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | 10 | "github.com/cloe-lang/cloe/src/lib/utils" 11 | ) 12 | 13 | func getLanguageSubDirectory(s string) (string, error) { 14 | d := os.Getenv("CLOE_PATH") 15 | 16 | if d == "" { 17 | h := os.Getenv("HOME") 18 | 19 | if h == "" { 20 | return "", errors.New("HOME environment variable is not set") 21 | } 22 | 23 | d = filepath.Join(h, ".cloe") 24 | } 25 | 26 | if !path.IsAbs(d) { 27 | return "", fmt.Errorf("language path, %s is not absolute", d) 28 | } 29 | 30 | d = filepath.Join(d, s) 31 | 32 | if err := utils.MkdirRecursively(d); err != nil { 33 | return "", err 34 | } 35 | 36 | return d, nil 37 | } 38 | 39 | // GetModulesDirectory gets a modules directory of the current user. 40 | func GetModulesDirectory() (string, error) { 41 | return getLanguageSubDirectory("src") 42 | } 43 | 44 | // GetCommandsDirectory gets a commands directory of the current user. 45 | func GetCommandsDirectory() (string, error) { 46 | return getLanguageSubDirectory("bin") 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/consts/names.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | // Names are predefined names used internally by desugarers and compilers. 4 | var Names = struct { 5 | DictionaryFunction string 6 | EmptyDictionary string 7 | EmptyList string 8 | IndexFunction string 9 | ListFunction string 10 | }{ 11 | DictionaryFunction: "$dictionary", 12 | EmptyDictionary: "$emptyDictionary", 13 | EmptyList: "$emptyList", 14 | IndexFunction: "$@", 15 | ListFunction: "$list", 16 | } 17 | 18 | // FileExtension is a file extension of the language. 19 | const FileExtension = ".cloe" 20 | 21 | // ModuleFilename is the name of the top level scripts in module directories 22 | // with no file extension. 23 | const ModuleFilename = "module" 24 | -------------------------------------------------------------------------------- /src/lib/core/arguments_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewArguments(t *testing.T) { 10 | NewArguments([]PositionalArgument{ 11 | NewPositionalArgument(Nil, false), 12 | NewPositionalArgument(EmptyList, true), 13 | NewPositionalArgument(Nil, false), 14 | NewPositionalArgument(EmptyList, true), 15 | }, nil) 16 | } 17 | 18 | func TestArgumentsEmpty(t *testing.T) { 19 | for _, a := range []Arguments{ 20 | NewPositionalArguments(Nil), 21 | NewPositionalArguments(Nil, Nil), 22 | } { 23 | v := a.checkEmptyness() 24 | assert.NotNil(t, v) 25 | v = EvalPure(v) 26 | t.Logf("%#v\n", v) 27 | _, ok := v.(*ErrorType) 28 | assert.True(t, ok) 29 | } 30 | } 31 | 32 | func TestArgumentsMerge(t *testing.T) { 33 | a := NewArguments([]PositionalArgument{NewPositionalArgument(NewList(Nil), true)}, nil) 34 | a = a.Merge(a) 35 | assert.Equal(t, NewNumber(2), EvalPure(PApp(Size, a.restPositionals()))) 36 | } 37 | 38 | func TestArgumentsRestKeywords(t *testing.T) { 39 | a := NewArguments(nil, []KeywordArgument{NewKeywordArgument("", DummyError)}) 40 | assert.Equal(t, "DummyError", EvalPure(a.restKeywords()).(*ErrorType).Name()) 41 | } 42 | 43 | func TestArgumentsWithNormalAndRestKeywords(t *testing.T) { 44 | vs, e := NewSignature(nil, "", []OptionalParameter{NewOptionalParameter("foo", False)}, "").Bind( 45 | NewArguments( 46 | nil, 47 | []KeywordArgument{NewKeywordArgument("foo", True), NewKeywordArgument("", EmptyDictionary)})) 48 | 49 | assert.Equal(t, True, EvalPure(vs[0])) 50 | assert.Nil(t, e) 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/core/boolean.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "hash/fnv" 5 | 6 | "github.com/raviqqe/hamt" 7 | ) 8 | 9 | // BooleanType represents a boolean values in the language. 10 | type BooleanType bool 11 | 12 | // Eval evaluates a value into a WHNF. 13 | func (b *BooleanType) eval() Value { 14 | return b 15 | } 16 | 17 | var ( 18 | trueStruct, falseStruct = BooleanType(true), BooleanType(false) 19 | 20 | // True is a true value. 21 | True = &trueStruct 22 | 23 | // False is a false value. 24 | False = &falseStruct 25 | ) 26 | 27 | // NewBoolean converts a Go boolean value into BooleanType. 28 | func NewBoolean(b bool) *BooleanType { 29 | if b { 30 | return True 31 | } 32 | 33 | return False 34 | } 35 | 36 | // If returns the second argument when the first one is true or the third one 37 | // otherwise. 38 | var If = NewLazyFunction( 39 | NewSignature(nil, "args", nil, ""), 40 | func(vs ...Value) Value { 41 | v := vs[0] 42 | 43 | for { 44 | l, err := EvalList(v) 45 | 46 | if err != nil { 47 | return err 48 | } 49 | 50 | ll, err := EvalList(l.Rest()) 51 | 52 | if err != nil { 53 | return err 54 | } else if ll.Empty() { 55 | return l.First() 56 | } 57 | 58 | b, err := EvalBoolean(l.First()) 59 | 60 | if err != nil { 61 | return err 62 | } else if b { 63 | return ll.First() 64 | } 65 | 66 | v = ll.Rest() 67 | } 68 | }) 69 | 70 | func (b *BooleanType) compare(c comparable) int { 71 | if *b == *c.(*BooleanType) { 72 | return 0 73 | } else if *b { 74 | return 1 75 | } 76 | 77 | return -1 78 | } 79 | 80 | // Hash hashes a value. 81 | func (b *BooleanType) Hash() uint32 { 82 | h := fnv.New32() 83 | 84 | if *b { 85 | if _, err := h.Write([]byte{1}); err != nil { 86 | panic(err) 87 | } 88 | } else { 89 | if _, err := h.Write([]byte{0}); err != nil { 90 | panic(err) 91 | } 92 | } 93 | 94 | return h.Sum32() 95 | } 96 | 97 | // Equal checks equality. 98 | func (b *BooleanType) Equal(e hamt.Entry) bool { 99 | if c, ok := e.(*BooleanType); ok { 100 | return *b == *c 101 | } 102 | 103 | return false 104 | } 105 | 106 | func (b *BooleanType) string() Value { 107 | return sprint(*b) 108 | } 109 | -------------------------------------------------------------------------------- /src/lib/core/boolean_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestBooleanEqual(t *testing.T) { 10 | for _, vs := range [][2]Value{ 11 | {True, True}, 12 | {False, False}, 13 | } { 14 | assert.True(t, testEqual(vs[0], vs[1])) 15 | } 16 | 17 | for _, vs := range [][2]Value{ 18 | {True, False}, 19 | {False, True}, 20 | } { 21 | assert.True(t, !testEqual(vs[0], vs[1])) 22 | } 23 | } 24 | 25 | func TestBooleanToString(t *testing.T) { 26 | test := func(s string, b bool) { 27 | assert.Equal(t, NewString(s), EvalPure(PApp(ToString, NewBoolean(b)))) 28 | } 29 | 30 | test("true", true) 31 | test("false", false) 32 | } 33 | 34 | func TestIf(t *testing.T) { 35 | for _, vs := range [][]Value{ 36 | {Nil}, 37 | {True, Nil, False}, 38 | {False, False, Nil}, 39 | {False, False, True, Nil, False, True, False}, 40 | } { 41 | assert.Equal(t, Nil, EvalPure(PApp(If, vs...))) 42 | } 43 | } 44 | 45 | func TestIfWithInvalidArguments(t *testing.T) { 46 | for _, a := range []Arguments{ 47 | NewPositionalArguments(), 48 | NewPositionalArguments(Nil, Nil, Nil), 49 | NewPositionalArguments(False, Nil), 50 | NewArguments([]PositionalArgument{NewPositionalArgument(Nil, true)}, nil), 51 | NewArguments( 52 | []PositionalArgument{ 53 | NewPositionalArgument(PApp(Prepend, True, DummyError), true), 54 | }, 55 | nil), 56 | } { 57 | _, ok := EvalPure(App(If, a)).(*ErrorType) 58 | assert.True(t, ok) 59 | } 60 | } 61 | 62 | func BenchmarkIf(b *testing.B) { 63 | for i := 0; i < b.N; i++ { 64 | EvalPure(PApp(If, False, False, False, False, False, False, False, False, True)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/lib/core/effect.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // effectType represents an effect value returned from an impure function. 4 | // effectType is meant to be used to distinguish calls of pure and impure 5 | // functions and also represent a "result" value of an impure function which 6 | // can be extracted by a special function named "out" and passed to a pure 7 | // function. 8 | type effectType struct { 9 | value Value 10 | } 11 | 12 | // Eval evaluates a value into a WHNF. 13 | func (e effectType) eval() Value { 14 | return e 15 | } 16 | 17 | // newEffect creates an effect value. 18 | func newEffect(value Value) Value { 19 | return effectType{value} 20 | } 21 | 22 | // Pure extracts a result value in an effect value. 23 | var Pure = NewLazyFunction( 24 | NewSignature([]string{"arg"}, "", nil, ""), 25 | func(vs ...Value) Value { 26 | return EvalImpure(vs[0]) 27 | }) 28 | -------------------------------------------------------------------------------- /src/lib/core/effect_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEffectEval(t *testing.T) { 10 | effectType{value: Nil}.eval() 11 | } 12 | 13 | func TestPure(t *testing.T) { 14 | assert.Equal(t, True, EvalPure(PApp(Pure, PApp(impureFunction, True)))) 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/core/error_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestError(t *testing.T) { 10 | assert.Equal(t, 11 | "MyError", 12 | EvalPure(PApp(Error, NewString("MyError"), NewString("This is my message."))).(*ErrorType).Name()) 13 | } 14 | 15 | func TestErrorInvalidName(t *testing.T) { 16 | assert.Equal(t, 17 | "TypeError", 18 | EvalPure(PApp(Error, Nil, NewString("This is my message."))).(*ErrorType).Name()) 19 | } 20 | 21 | func TestErrorInvalidMessage(t *testing.T) { 22 | assert.Equal(t, 23 | "TypeError", 24 | EvalPure(PApp(Error, NewString("MyError"), Nil)).(*ErrorType).Name()) 25 | } 26 | 27 | func TestErrorName(t *testing.T) { 28 | assert.Equal(t, "DummyError", DummyError.Name()) 29 | } 30 | 31 | func TestErrorCatch(t *testing.T) { 32 | _, ok := EvalPure(PApp(Catch, DummyError)).(*DictionaryType) 33 | assert.True(t, ok) 34 | 35 | _, ok = EvalPure(PApp(Catch, Nil)).(NilType) 36 | assert.True(t, ok) 37 | } 38 | 39 | func TestKeyNotFoundErrorFail(t *testing.T) { 40 | assert.NotEqual(t, 41 | "KeyNotFoundError", 42 | keyNotFoundError(DummyError).Name()) 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/core/function.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/systemt" 4 | 5 | // FunctionType represents a function. 6 | type FunctionType func(Arguments) Value 7 | 8 | func (f FunctionType) call(args Arguments) Value { 9 | return f(args) 10 | } 11 | 12 | func (f FunctionType) eval() Value { 13 | return f 14 | } 15 | 16 | // NewRawFunction creates a function which takes arguments directly. 17 | func NewRawFunction(f func(Arguments) Value) FunctionType { 18 | return FunctionType(f) 19 | } 20 | 21 | // NewLazyFunction creates a function whose arguments are evaluated lazily. 22 | func NewLazyFunction(s Signature, f func(...Value) Value) FunctionType { 23 | return NewRawFunction(func(args Arguments) Value { 24 | vs, err := s.Bind(args) 25 | 26 | if err != nil { 27 | return err 28 | } 29 | 30 | return f(vs...) 31 | }) 32 | } 33 | 34 | // NewStrictFunction creates a function whose arguments are evaluated strictly. 35 | func NewStrictFunction(s Signature, f func(...Value) Value) FunctionType { 36 | return NewLazyFunction(s, func(vs ...Value) Value { 37 | systemt.Daemonize(func() { 38 | for _, t := range vs { 39 | tt := t 40 | systemt.Daemonize(func() { tt.eval() }) 41 | } 42 | }) 43 | 44 | return f(vs...) 45 | }) 46 | } 47 | 48 | // NewEffectFunction creates a effect function which returns an effect value. 49 | func NewEffectFunction(s Signature, f func(...Value) Value) Value { 50 | ff := NewLazyFunction(s, f) 51 | 52 | return NewRawFunction(func(args Arguments) Value { 53 | return newEffect(App(ff, args)) 54 | }) 55 | } 56 | 57 | // Partial creates a partially-applied function with arguments. 58 | var Partial = FunctionType(func(vars Arguments) Value { 59 | return NewRawFunction(func(args Arguments) Value { 60 | vars := vars 61 | v := EvalPure(vars.nextPositional()) 62 | f, ok := v.(FunctionType) 63 | 64 | if !ok { 65 | return NotFunctionError(v) 66 | } 67 | 68 | return f.call(vars.Merge(args)) 69 | }) 70 | }) 71 | 72 | func (f FunctionType) string() Value { 73 | return NewString("") 74 | } 75 | -------------------------------------------------------------------------------- /src/lib/core/function_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/systemt" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFunctionCallError(t *testing.T) { 12 | f := NewLazyFunction( 13 | NewSignature([]string{"foo"}, "", nil, ""), 14 | func(vs ...Value) Value { return vs[0] }) 15 | 16 | for _, v := range []Value{ 17 | App(f, NewArguments([]PositionalArgument{NewPositionalArgument(EmptyList, true)}, nil)), 18 | App(f, NewArguments(nil, []KeywordArgument{NewKeywordArgument("bar", Nil)})), 19 | } { 20 | _, ok := EvalPure(v).(*ErrorType) 21 | assert.True(t, ok) 22 | } 23 | } 24 | 25 | func TestFunctionToString(t *testing.T) { 26 | assert.Equal(t, NewString(""), EvalPure(PApp(ToString, If))) 27 | } 28 | 29 | func TestStrictFunctionParallelization(t *testing.T) { 30 | go systemt.RunDaemons() 31 | 32 | f := NewStrictFunction( 33 | NewSignature([]string{"foo"}, "", []OptionalParameter{NewOptionalParameter("bar", Nil)}, ""), 34 | func(vs ...Value) Value { return vs[0] }) 35 | 36 | EvalPure(App(f, NewArguments(nil, []KeywordArgument{NewKeywordArgument("bar", Nil)}))) 37 | 38 | time.Sleep(100 * time.Millisecond) 39 | } 40 | 41 | func TestNewEffectFunction(t *testing.T) { 42 | v := PApp(NewEffectFunction( 43 | NewSignature(nil, "", nil, ""), 44 | func(...Value) Value { return Nil })) 45 | 46 | assert.Equal(t, "ImpureFunctionError", EvalPure(v).(*ErrorType).Name()) 47 | assert.Equal(t, Nil, EvalImpure(v).(NilType)) 48 | } 49 | 50 | func TestPartial(t *testing.T) { 51 | ifFunc := func(vs ...Value) bool { 52 | return bool(*EvalPure(PApp(PApp(Partial, If, False, True), vs...)).(*BooleanType)) 53 | } 54 | 55 | assert.True(t, ifFunc(True)) 56 | assert.True(t, !ifFunc(False)) 57 | } 58 | 59 | func TestPartialError(t *testing.T) { 60 | for _, a := range []Arguments{ 61 | NewPositionalArguments(Nil), 62 | NewPositionalArguments(Prepend), 63 | } { 64 | _, ok := EvalPure(PApp(App(Partial, a))).(*ErrorType) 65 | assert.True(t, ok) 66 | } 67 | } 68 | 69 | func TestClosureToString(t *testing.T) { 70 | assert.Equal(t, NewString(""), EvalPure(PApp(ToString, PApp(Partial, If, True)))) 71 | } 72 | -------------------------------------------------------------------------------- /src/lib/core/init.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | func init() { 4 | First = initFirst() 5 | Insert = initInsert() 6 | Merge = initMerge() 7 | Rest = initRest() 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/core/keyword_argument.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // KeywordArgument represents a keyword argument passed to a function. 4 | type KeywordArgument struct { 5 | name string // Empty strings means values are expanded. 6 | value Value 7 | } 8 | 9 | // NewKeywordArgument creates a KeywordArgument from a bound name and its value. 10 | func NewKeywordArgument(s string, v Value) KeywordArgument { 11 | return KeywordArgument{s, v} 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/core/keyword_parameters.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type keywordParameters struct { 4 | parameters []OptionalParameter 5 | rest string 6 | } 7 | 8 | func (ks keywordParameters) arity() int { 9 | n := +len(ks.parameters) 10 | 11 | if ks.rest != "" { 12 | n++ 13 | } 14 | 15 | return n 16 | } 17 | 18 | func (ks keywordParameters) bind(args *Arguments) []Value { 19 | vs := make([]Value, 0, ks.arity()) 20 | 21 | for _, o := range ks.parameters { 22 | v := args.searchKeyword(o.name) 23 | 24 | if v == nil { 25 | v = o.defaultValue 26 | } 27 | 28 | vs = append(vs, v) 29 | } 30 | 31 | if ks.rest != "" { 32 | vs = append(vs, args.restKeywords()) 33 | } 34 | 35 | return vs 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/core/nil.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "hash/fnv" 5 | 6 | "github.com/raviqqe/hamt" 7 | ) 8 | 9 | // NilType represents a nil value. You know. 10 | type NilType struct{} 11 | 12 | // Eval evaluates a value into a WHNF. 13 | func (n NilType) eval() Value { 14 | return n 15 | } 16 | 17 | // Nil is the evil or million-dollar mistake. 18 | var Nil = NilType{} 19 | 20 | func (NilType) compare(comparable) int { 21 | return 0 22 | } 23 | 24 | func (NilType) string() Value { 25 | return NewString("nil") 26 | } 27 | 28 | // Hash hashes a value. 29 | func (NilType) Hash() uint32 { 30 | return fnv.New32().Sum32() 31 | } 32 | 33 | // Equal checks equality. 34 | func (NilType) Equal(e hamt.Entry) bool { 35 | _, ok := e.(NilType) 36 | return ok 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/core/nil_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNilEqual(t *testing.T) { 10 | assert.True(t, testEqual(Nil, Nil)) 11 | assert.True(t, !testEqual(Nil, True)) 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/core/optional_parameter.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // OptionalParameter represents an optional argument defined in a function. 4 | type OptionalParameter struct { 5 | name string 6 | defaultValue Value 7 | } 8 | 9 | // NewOptionalParameter creates an optional argument. 10 | func NewOptionalParameter(n string, v Value) OptionalParameter { 11 | return OptionalParameter{n, v} 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/core/positional_argument.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // PositionalArgument represents a positional argument. 4 | // It can be expanded as a list. 5 | type PositionalArgument struct { 6 | value Value 7 | expanded bool 8 | } 9 | 10 | // NewPositionalArgument creates a PositionalArgument. 11 | func NewPositionalArgument(v Value, expanded bool) PositionalArgument { 12 | return PositionalArgument{v, expanded} 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/core/positional_parameters.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type positionalParameters struct { 4 | parameters []string 5 | rest string 6 | } 7 | 8 | func (ps positionalParameters) arity() int { 9 | n := len(ps.parameters) 10 | 11 | if ps.rest != "" { 12 | n++ 13 | } 14 | 15 | return n 16 | } 17 | 18 | func (ps positionalParameters) bind(args *Arguments) ([]Value, Value) { 19 | vs := make([]Value, 0, ps.arity()) 20 | 21 | for _, s := range ps.parameters { 22 | v := args.nextPositional() 23 | 24 | if v == nil { 25 | return nil, argumentError("positional argument, %s is missing", s) 26 | } 27 | 28 | vs = append(vs, v) 29 | } 30 | 31 | if ps.rest != "" { 32 | vs = append(vs, args.restPositionals()) 33 | } 34 | 35 | return vs, nil 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/core/sequence.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type sequence interface { 4 | collection 5 | 6 | insert(Value, Value) Value 7 | } 8 | 9 | // Insert inserts an element into a sequence. 10 | var Insert FunctionType 11 | 12 | func initInsert() FunctionType { 13 | return NewLazyFunction( 14 | NewSignature([]string{"collection"}, "indexValuePairs", nil, ""), 15 | func(vs ...Value) (result Value) { 16 | s, err := evalSequence(vs[0]) 17 | 18 | if err != nil { 19 | return err 20 | } 21 | 22 | l, err := EvalList(vs[1]) 23 | 24 | if err != nil { 25 | return err 26 | } 27 | 28 | for !l.Empty() { 29 | k := l.First() 30 | 31 | if l, err = EvalList(l.Rest()); err != nil { 32 | return err 33 | } 34 | 35 | s, err = evalSequence(s.insert(EvalPure(k), l.First())) 36 | 37 | if err != nil { 38 | return err 39 | } 40 | 41 | if l, err = EvalList(l.Rest()); err != nil { 42 | return err 43 | } 44 | } 45 | 46 | return s 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/lib/core/signature.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // Signature represents function signature. 4 | type Signature struct { 5 | positionals positionalParameters 6 | keywords keywordParameters 7 | } 8 | 9 | // NewSignature defines a new Signature. 10 | func NewSignature(ps []string, pr string, ks []OptionalParameter, kr string) Signature { 11 | return Signature{positionalParameters{ps, pr}, keywordParameters{ks, kr}} 12 | } 13 | 14 | // Bind binds Arguments to names defined in Signature and returns full 15 | // arguments to be passed to a function. 16 | func (s Signature) Bind(args Arguments) ([]Value, Value) { 17 | ps, err := s.positionals.bind(&args) 18 | 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | ks := s.keywords.bind(&args) 24 | 25 | if err := args.checkEmptyness(); err != nil { 26 | return nil, err 27 | } 28 | 29 | return append(ps, ks...), nil 30 | } 31 | 32 | func (s Signature) arity() int { 33 | return s.positionals.arity() + s.keywords.arity() 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/core/signature_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSignatureBind(t *testing.T) { 10 | for _, c := range []struct { 11 | signature Signature 12 | arguments Arguments 13 | }{ 14 | { 15 | NewSignature(nil, "", nil, ""), 16 | NewArguments(nil, nil), 17 | }, 18 | { 19 | NewSignature([]string{"x"}, "", nil, ""), 20 | NewArguments([]PositionalArgument{NewPositionalArgument(NewList(Nil), true)}, nil), 21 | }, 22 | { 23 | NewSignature(nil, "", []OptionalParameter{NewOptionalParameter("x", Nil)}, ""), 24 | NewArguments(nil, nil), 25 | }, 26 | { 27 | NewSignature(nil, "", []OptionalParameter{NewOptionalParameter("foo", Nil)}, ""), 28 | NewArguments(nil, []KeywordArgument{NewKeywordArgument("", NewDictionary([]KeyValue{{NewString("foo"), Nil}}))}), 29 | }, 30 | { 31 | NewSignature(nil, "", nil, "foo"), 32 | NewArguments( 33 | nil, 34 | []KeywordArgument{NewKeywordArgument("foo", Nil), NewKeywordArgument("", NewDictionary([]KeyValue{{NewString("bar"), Nil}}))}), 35 | }, 36 | } { 37 | vs, err := c.signature.Bind(c.arguments) 38 | assert.Equal(t, c.signature.arity(), len(vs)) 39 | assert.Equal(t, nil, err) 40 | } 41 | } 42 | 43 | func TestSignatureBindError(t *testing.T) { 44 | for _, c := range []struct { 45 | signature Signature 46 | arguments Arguments 47 | }{ 48 | { 49 | NewSignature([]string{"x"}, "", nil, ""), 50 | NewArguments(nil, nil), 51 | }, 52 | { 53 | NewSignature(nil, "", nil, ""), 54 | NewArguments([]PositionalArgument{NewPositionalArgument(Nil, false)}, nil), 55 | }, 56 | { 57 | NewSignature(nil, "", []OptionalParameter{NewOptionalParameter("arg", Nil)}, ""), 58 | NewArguments([]PositionalArgument{NewPositionalArgument(Nil, false)}, nil), 59 | }, 60 | } { 61 | _, err := c.signature.Bind(c.arguments) 62 | assert.NotEqual(t, nil, err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib/core/thunk_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | var impureFunction = NewLazyFunction( 10 | NewSignature([]string{"x"}, "", nil, ""), 11 | func(vs ...Value) Value { 12 | return newEffect(vs[0]) 13 | }) 14 | 15 | func TestThunkEvalWithNonFunction(t *testing.T) { 16 | assert.Equal(t, "TypeError", EvalPure(PApp(Nil)).(*ErrorType).Name()) 17 | } 18 | 19 | func TestThunkEvalWithImpureFunctionCall(t *testing.T) { 20 | assert.Equal(t, "ImpureFunctionError", EvalPure(PApp(impureFunction, Nil)).(*ErrorType).Name()) 21 | } 22 | 23 | func TestThunkEvalByCallingError(t *testing.T) { 24 | e := EvalPure(PApp(DummyError)).(*ErrorType) 25 | t.Log(e) 26 | assert.Equal(t, 1, len(e.callTrace)) 27 | } 28 | 29 | func TestThunkEvalByCallingErrorTwice(t *testing.T) { 30 | e := EvalPure(PApp(PApp(DummyError))).(*ErrorType) 31 | t.Log(e) 32 | assert.Equal(t, 2, len(e.callTrace)) 33 | } 34 | 35 | func TestThunkEvalImpure(t *testing.T) { 36 | s := NewString("foo") 37 | assert.Equal(t, s, EvalImpure(PApp(impureFunction, s))) 38 | } 39 | 40 | func TestThunkEvalImpureWithNonEffect(t *testing.T) { 41 | for _, v := range []Value{Nil, PApp(identity, Nil)} { 42 | v := EvalImpure(v) 43 | err, ok := v.(*ErrorType) 44 | t.Logf("%#v\n", v) 45 | assert.True(t, ok) 46 | assert.Equal(t, "TypeError", err.Name()) 47 | } 48 | } 49 | 50 | func TestThunkEvalImpureWithError(t *testing.T) { 51 | v := EvalImpure(DummyError) 52 | err, ok := v.(*ErrorType) 53 | t.Logf("%#v\n", v) 54 | assert.True(t, ok) 55 | assert.Equal(t, "DummyError", err.Name()) 56 | } 57 | 58 | func BenchmarkThunkEval(b *testing.B) { 59 | for i := 0; i < b.N; i++ { 60 | EvalPure(PApp(identity, NewNumber(42))) 61 | } 62 | } 63 | 64 | func BenchmarkAppWithInfo(b *testing.B) { 65 | for i := 0; i < b.N; i++ { 66 | AppWithInfo(identity, NewPositionalArguments(), nil) 67 | } 68 | } 69 | 70 | func BenchmarkPApp(b *testing.B) { 71 | for i := 0; i < b.N; i++ { 72 | PApp(identity, Nil) 73 | } 74 | } 75 | 76 | func BenchmarkMultiplePApp(b *testing.B) { 77 | for i := 0; i < b.N; i++ { 78 | f := identity 79 | l := EmptyList 80 | 81 | PApp(If, 82 | PApp(Equal, l, EmptyList), 83 | EmptyList, 84 | PApp(Prepend, 85 | PApp(f, PApp(First, l)), 86 | PApp(f, f, PApp(Rest, l)))) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/lib/core/util.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func sprint(s interface{}) StringType { 8 | return NewString(fmt.Sprint(s)) 9 | } 10 | 11 | // Dump dumps a value into a string type value. 12 | var Dump = NewLazyFunction( 13 | NewSignature([]string{"arg"}, "", nil, ""), 14 | func(vs ...Value) Value { 15 | s, err := StrictDump(vs[0]) 16 | 17 | if err != nil { 18 | return err 19 | } 20 | 21 | return s 22 | }) 23 | 24 | // StrictDump is a variant of Dump which evaluates input strictly. 25 | func StrictDump(v Value) (StringType, Value) { 26 | switch x := EvalPure(v).(type) { 27 | case *ErrorType: 28 | return "", x 29 | case StringType: 30 | v = x.quoted() 31 | case stringable: 32 | v = x.string() 33 | default: 34 | panic(fmt.Errorf("Invalid value: %#v", x)) 35 | } 36 | 37 | s, err := EvalString(v) 38 | 39 | if err != nil { 40 | return "", err 41 | } 42 | 43 | return s, nil 44 | } 45 | 46 | var identity = NewLazyFunction( 47 | NewSignature([]string{"arg"}, "", nil, ""), 48 | func(vs ...Value) Value { return vs[0] }) 49 | 50 | // TypeOf returns a type name of an argument as a string. 51 | var TypeOf = NewLazyFunction( 52 | NewSignature([]string{"arg"}, "", nil, ""), 53 | typeOf) 54 | 55 | func typeOf(vs ...Value) Value { 56 | // No case of effectType should be here. 57 | switch v := EvalPure(vs[0]).(type) { 58 | case *BooleanType: 59 | return NewString("boolean") 60 | case *DictionaryType: 61 | return NewString("dictionary") 62 | case *ListType: 63 | return NewString("list") 64 | case NilType: 65 | return NewString("nil") 66 | case *NumberType: 67 | return NewString("number") 68 | case StringType: 69 | return NewString("string") 70 | case FunctionType: 71 | return NewString("function") 72 | case *ErrorType: 73 | return v 74 | } 75 | 76 | panic("unreachable") 77 | } 78 | -------------------------------------------------------------------------------- /src/lib/core/util_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDump(t *testing.T) { 10 | for _, c := range []struct { 11 | argument Value 12 | answer StringType 13 | }{ 14 | {NewString("foo"), `"foo"`}, 15 | {NewList(NewString("foo")), `["foo"]`}, 16 | {NewDictionary([]KeyValue{{NewString("foo"), NewString("bar")}}), `{"foo" "bar"}`}, 17 | } { 18 | assert.Equal(t, c.answer, EvalPure(PApp(Dump, c.argument)).(StringType)) 19 | } 20 | } 21 | 22 | func TestDumpError(t *testing.T) { 23 | for _, v := range []Value{ 24 | DummyError, 25 | NewList(DummyError), 26 | } { 27 | v := EvalPure(PApp(Dump, v)) 28 | t.Log(v) 29 | 30 | _, ok := v.(*ErrorType) 31 | assert.True(t, ok) 32 | } 33 | } 34 | 35 | func TestStrictDumpPanic(t *testing.T) { 36 | assert.Panics(t, func() { 37 | StrictDump(nil) 38 | }) 39 | } 40 | 41 | func TestInternalStrictDumpFail(t *testing.T) { 42 | for _, v := range []Value{ 43 | NewList(DummyError), 44 | NewDictionary([]KeyValue{{Nil, DummyError}}), 45 | } { 46 | _, err := StrictDump(EvalPure(v)) 47 | assert.NotNil(t, err) 48 | } 49 | } 50 | 51 | func TestStrictDump(t *testing.T) { 52 | for _, v := range []Value{ 53 | Nil, 54 | True, 55 | False, 56 | EmptyList, 57 | EmptyDictionary, 58 | NewNumber(42), 59 | NewString("foo"), 60 | } { 61 | s, err := StrictDump(EvalPure(v)) 62 | assert.NotEqual(t, "", s) 63 | assert.Nil(t, err) 64 | } 65 | } 66 | 67 | func TestIdentity(t *testing.T) { 68 | for _, v := range []Value{Nil, NewNumber(42), True, False, NewString("foo")} { 69 | assert.True(t, testEqual(PApp(identity, v), v)) 70 | } 71 | } 72 | 73 | func TestTypeOf(t *testing.T) { 74 | for _, test := range []struct { 75 | typ string 76 | thunk Value 77 | }{ 78 | {"nil", Nil}, 79 | {"list", EmptyList}, 80 | {"list", EmptyList}, 81 | {"boolean", True}, 82 | {"number", NewNumber(123)}, 83 | {"string", NewString("foo")}, 84 | {"dictionary", EmptyDictionary}, 85 | {"function", identity}, 86 | {"function", PApp(Partial, identity)}, 87 | } { 88 | v := EvalPure(PApp(TypeOf, test.thunk)) 89 | t.Log(v) 90 | assert.Equal(t, test.typ, string(v.(StringType))) 91 | } 92 | } 93 | 94 | func TestTypeOfError(t *testing.T) { 95 | for _, v := range []Value{ 96 | DummyError, 97 | PApp(impureFunction, NewNumber(42)), 98 | } { 99 | v = EvalPure(PApp(TypeOf, v)) 100 | _, ok := v.(*ErrorType) 101 | t.Logf("%#v\n", v) 102 | assert.True(t, ok) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/lib/core/value.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // Value represents a value. 4 | type Value interface { 5 | // Eval svaluates a value into WHNF. (i.e. Thunks would be unwrapped.) 6 | eval() Value 7 | } 8 | 9 | // EvalBoolean evaluates a thunk which is expected to be a boolean value. 10 | func EvalBoolean(v Value) (BooleanType, Value) { 11 | b, ok := EvalPure(v).(*BooleanType) 12 | 13 | if !ok { 14 | return *False, NotBooleanError(v) 15 | } 16 | 17 | return *b, nil 18 | } 19 | 20 | // EvalDictionary evaluates a thunk which is expected to be a dictionary value. 21 | func EvalDictionary(v Value) (*DictionaryType, Value) { 22 | d, ok := EvalPure(v).(*DictionaryType) 23 | 24 | if !ok { 25 | return EmptyDictionary, NotDictionaryError(v) 26 | } 27 | 28 | return d, nil 29 | } 30 | 31 | // EvalList evaluates a thunk which is expected to be a list value. 32 | func EvalList(v Value) (*ListType, Value) { 33 | l, ok := EvalPure(v).(*ListType) 34 | 35 | if !ok { 36 | return EmptyList, NotListError(v) 37 | } 38 | 39 | return l, nil 40 | } 41 | 42 | // EvalNumber evaluates a thunk which is expected to be a number value. 43 | func EvalNumber(v Value) (NumberType, Value) { 44 | n, ok := EvalPure(v).(*NumberType) 45 | 46 | if !ok { 47 | return 0, NotNumberError(v) 48 | } 49 | 50 | return *n, nil 51 | } 52 | 53 | // EvalString evaluates a thunk which is expected to be a string value. 54 | func EvalString(v Value) (StringType, Value) { 55 | s, ok := EvalPure(v).(StringType) 56 | 57 | if !ok { 58 | return "", NotStringError(v) 59 | } 60 | 61 | return s, nil 62 | } 63 | 64 | func evalCollection(v Value) (collection, Value) { 65 | c, ok := EvalPure(v).(collection) 66 | 67 | if !ok { 68 | return nil, NotCollectionError(v) 69 | } 70 | 71 | return c, nil 72 | } 73 | 74 | func evalSequence(v Value) (sequence, Value) { 75 | s, ok := EvalPure(v).(sequence) 76 | 77 | if !ok { 78 | return nil, NotSequenceError(v) 79 | } 80 | 81 | return s, nil 82 | } 83 | 84 | // EvalPure evaluates a pure value. 85 | func EvalPure(v Value) Value { 86 | v = v.eval() 87 | _, ok := v.(effectType) 88 | 89 | if ok { 90 | return impureFunctionError() 91 | } 92 | 93 | return v 94 | } 95 | 96 | // EvalImpure evaluates an impure function call. 97 | func EvalImpure(v Value) Value { 98 | e, ok := v.eval().(effectType) 99 | 100 | if !ok { 101 | return NotEffectError(v) 102 | } 103 | 104 | return EvalPure(e.value) 105 | } 106 | -------------------------------------------------------------------------------- /src/lib/core/value_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "testing" 4 | 5 | func BenchmarkValueCopyBoolean(b *testing.B) { 6 | generateCopyBenchmark(True)(b) 7 | } 8 | 9 | func BenchmarkValueCopyDictionary(b *testing.B) { 10 | generateCopyBenchmark(NewDictionary([]KeyValue{{NewString("foo"), NewNumber(42)}}))(b) 11 | } 12 | 13 | func BenchmarkValueCopyFunction(b *testing.B) { 14 | generateCopyBenchmark(If)(b) 15 | } 16 | 17 | func BenchmarkValueCopyList(b *testing.B) { 18 | generateCopyBenchmark(NewList(NewNumber(42)))(b) 19 | } 20 | 21 | func BenchmarkValueCopyNil(b *testing.B) { 22 | generateCopyBenchmark(Nil)(b) 23 | } 24 | 25 | func BenchmarkValueCopyNumber(b *testing.B) { 26 | generateCopyBenchmark(NewNumber(42))(b) 27 | } 28 | 29 | func BenchmarkValueCopyString(b *testing.B) { 30 | generateCopyBenchmark(NewString("foo"))(b) 31 | } 32 | 33 | func BenchmarkValueCopyError(b *testing.B) { 34 | generateCopyBenchmark(DummyError)(b) 35 | } 36 | 37 | type UnboxedNumberType float64 38 | 39 | func (b UnboxedNumberType) eval() Value { 40 | return b 41 | } 42 | 43 | func BenchmarkValueCopyInefficientUnboxedType(b *testing.B) { 44 | // The result must be 1 allocs/op. 45 | generateCopyBenchmark(UnboxedNumberType(42))(b) 46 | } 47 | 48 | func generateCopyBenchmark(v Value) func(b *testing.B) { 49 | return func(b *testing.B) { 50 | b.ResetTimer() 51 | 52 | for i := 0; i < b.N; i++ { 53 | v = v.eval() 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/debug/debug.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | // Debug is a debug mode flag for an entire program. 4 | var Debug = false 5 | -------------------------------------------------------------------------------- /src/lib/debug/info.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | // Info represents metadata of a call. 9 | type Info struct { 10 | file string 11 | lineNumber, linePosition int 12 | source string // source code at lineNumber in file 13 | } 14 | 15 | // NewInfo creates a Info. 16 | func NewInfo(file string, lineNumber, linePosition int, source string) *Info { 17 | return &Info{file, lineNumber, linePosition, source} 18 | } 19 | 20 | // NewGoInfo creates a Info of debug information about Go source. 21 | func NewGoInfo(skip int) *Info { 22 | if !Debug { 23 | return nil 24 | } 25 | 26 | _, file, line, ok := runtime.Caller(skip + 1) 27 | 28 | if !ok { 29 | panic("runtime.Caller failed.") 30 | } 31 | 32 | return NewInfo(file, line, -1, "") 33 | } 34 | 35 | // Lines returns string representation of Info which can be printed on stdout or 36 | // stderr as is. 37 | func (i *Info) Lines() string { 38 | if i == nil { 39 | return "" 40 | } 41 | 42 | p := "NA" 43 | 44 | if i.linePosition > 0 { 45 | p = fmt.Sprint(i.linePosition) 46 | } 47 | 48 | return fmt.Sprintf("%s:%d:%s:\t%s\n", i.file, i.lineNumber, p, i.source) 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/debug/info_test.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewGoInfo(t *testing.T) { 11 | Debug = true 12 | assert.Equal(t, "info_test.go", filepath.Base(NewGoInfo(0).file)) 13 | } 14 | 15 | func TestNewGoInfoWithInvalidSkip(t *testing.T) { 16 | assert.Panics(t, func() { 17 | Debug = true 18 | NewGoInfo(10) 19 | }) 20 | } 21 | 22 | func TestLines(t *testing.T) { 23 | Debug = true 24 | t.Log(NewGoInfo(0).Lines()) 25 | } 26 | 27 | func TestLinesWithLinePosition(t *testing.T) { 28 | Debug = true 29 | t.Log(NewInfo("", 1, 1, "(print (+ 123 456))").Lines()) 30 | } 31 | 32 | func TestInfoLinesEmpty(t *testing.T) { 33 | Debug = false 34 | assert.Equal(t, "", NewGoInfo(0).Lines()) 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/desugar/anonymous_function.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | "github.com/cloe-lang/cloe/src/lib/debug" 6 | "github.com/cloe-lang/cloe/src/lib/gensym" 7 | ) 8 | 9 | func desugarAnonymousFunctions(x interface{}) []interface{} { 10 | c := newAnonymousFunctionConverter() 11 | x = c.convert(x) 12 | return append(c.lets, x) 13 | } 14 | 15 | type anonymousFunctionConverter struct { 16 | lets []interface{} 17 | } 18 | 19 | func newAnonymousFunctionConverter() anonymousFunctionConverter { 20 | return anonymousFunctionConverter{} 21 | } 22 | 23 | func (c *anonymousFunctionConverter) convert(x interface{}) interface{} { 24 | return ast.Convert(func(x interface{}) interface{} { 25 | switch x := x.(type) { 26 | case ast.AnonymousFunction: 27 | n := gensym.GenSym() 28 | c.lets = append( 29 | c.lets, 30 | desugarAnonymousFunctionsInDefFunction( 31 | ast.NewDefFunction(n, x.Signature(), x.Lets(), x.Body(), debug.NewGoInfo(0)))) 32 | return n 33 | case ast.DefFunction: 34 | return desugarAnonymousFunctionsInDefFunction(x) 35 | } 36 | 37 | return nil 38 | }, x) 39 | } 40 | 41 | func desugarAnonymousFunctionsInDefFunction(f ast.DefFunction) ast.DefFunction { 42 | ls := make([]interface{}, 0, len(f.Lets())) 43 | 44 | for _, l := range f.Lets() { 45 | ls = append(ls, desugarAnonymousFunctions(l)...) 46 | } 47 | 48 | c := newAnonymousFunctionConverter() 49 | b := c.convert(f.Body()) 50 | ls = append(ls, c.lets...) 51 | 52 | return ast.NewDefFunction(f.Name(), f.Signature(), ls, b, f.DebugInfo()) 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/desugar/anonymous_function_test.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/debug" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestDesugarAnonymousFunctions(t *testing.T) { 12 | for _, s := range []interface{}{ 13 | ast.NewLetVar( 14 | "foo", 15 | ast.NewAnonymousFunction(ast.NewSignature(nil, "", nil, ""), nil, "123")), 16 | ast.NewDefFunction( 17 | "foo", 18 | ast.NewSignature(nil, "", nil, ""), 19 | nil, 20 | ast.NewAnonymousFunction(ast.NewSignature(nil, "", nil, ""), nil, "123"), 21 | debug.NewGoInfo(0)), 22 | ast.NewDefFunction( 23 | "foo", 24 | ast.NewSignature(nil, "", nil, ""), 25 | []interface{}{ 26 | ast.NewLetVar( 27 | "x", 28 | ast.NewAnonymousFunction(ast.NewSignature(nil, "", nil, ""), nil, "123")), 29 | }, 30 | "x", 31 | debug.NewGoInfo(0)), 32 | } { 33 | t.Logf("%#v", s) 34 | 35 | for _, s := range desugarAnonymousFunctions(s) { 36 | ast.Convert(func(x interface{}) interface{} { 37 | _, ok := x.(ast.AnonymousFunction) 38 | assert.False(t, ok) 39 | 40 | return nil 41 | }, s) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/desugar/desugar.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/desugar/match" 5 | ) 6 | 7 | // Desugar desugars a module of statements in AST. 8 | func Desugar(ss []interface{}) []interface{} { 9 | for _, f := range []func(interface{}) []interface{}{ 10 | desugarLetMatch, 11 | desugarLetExpression, 12 | desugarEmptyCollection, 13 | desugarDictionaryExpansion, 14 | match.Desugar, 15 | desugarAnonymousFunctions, 16 | desugarMutualRecursionStatement, 17 | desugarSelfRecursiveStatement, 18 | flattenStatement, 19 | removeUnusedVariables, 20 | removeAliases, 21 | } { 22 | new := make([]interface{}, 0, 2*len(ss)) 23 | 24 | for _, s := range ss { 25 | new = append(new, f(s)...) 26 | } 27 | 28 | ss = new 29 | } 30 | 31 | return ss 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/desugar/dictionary_expansion.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | "github.com/cloe-lang/cloe/src/lib/consts" 6 | ) 7 | 8 | func desugarDictionaryExpansion(x interface{}) []interface{} { 9 | return []interface{}{ast.Convert(func(x interface{}) interface{} { 10 | a, ok := x.(ast.App) 11 | 12 | if !ok || a.Function() != consts.Names.DictionaryFunction { 13 | return nil 14 | } 15 | 16 | ps := a.Arguments().Positionals() 17 | args := make([]interface{}, 0, len(ps)) 18 | dicts := make([]interface{}, 0, len(ps)) 19 | 20 | for _, p := range ps { 21 | if p.Expanded() { 22 | dicts = append(dicts, p.Value()) 23 | } else { 24 | args = append(args, p.Value()) 25 | } 26 | } 27 | 28 | return ast.NewPApp("$merge", append([]interface{}{ast.NewPApp(a.Function(), args, a.DebugInfo())}, dicts...), a.DebugInfo()) 29 | }, x)} 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/desugar/dictionary_expansion_test.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/consts" 8 | "github.com/cloe-lang/cloe/src/lib/debug" 9 | ) 10 | 11 | func TestDesugarDictionaryExpansion(t *testing.T) { 12 | desugarDictionaryExpansion(ast.NewLetVar( 13 | "foo", 14 | ast.NewApp( 15 | consts.Names.DictionaryFunction, 16 | ast.NewArguments([]ast.PositionalArgument{ 17 | ast.NewPositionalArgument("foo", false), 18 | ast.NewPositionalArgument("bar", true), 19 | }, nil), 20 | debug.NewGoInfo(0)))) 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/desugar/empty_collections.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | "github.com/cloe-lang/cloe/src/lib/consts" 6 | ) 7 | 8 | func desugarEmptyCollection(x interface{}) []interface{} { 9 | return []interface{}{ast.Convert(func(x interface{}) interface{} { 10 | a, ok := x.(ast.App) 11 | 12 | if !ok { 13 | return nil 14 | } 15 | 16 | if len(a.Arguments().Positionals()) != 0 { 17 | return nil 18 | } 19 | 20 | switch a.Function() { 21 | case consts.Names.ListFunction: 22 | return consts.Names.EmptyList 23 | case consts.Names.DictionaryFunction: 24 | return consts.Names.EmptyDictionary 25 | } 26 | 27 | return nil 28 | }, x)} 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/desugar/empty_collections_test.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/consts" 8 | "github.com/cloe-lang/cloe/src/lib/parse" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestDesugarEmptyCollection(t *testing.T) { 13 | for _, s := range []string{ 14 | `(print [])`, 15 | `(print {})`, 16 | `(print nil)`, 17 | `(print [42 []])`, 18 | `(print (read))`, 19 | } { 20 | m, err := parse.MainModule("", s) 21 | assert.Nil(t, err) 22 | 23 | for _, s := range m { 24 | for _, s := range desugarEmptyCollection(s) { 25 | ast.Convert(func(x interface{}) interface{} { 26 | a, ok := x.(ast.App) 27 | 28 | if !ok { 29 | return nil 30 | } 31 | 32 | switch a.Function() { 33 | case consts.Names.EmptyList, consts.Names.EmptyDictionary: 34 | assert.NotZero(t, len(a.Arguments().Positionals())) 35 | } 36 | 37 | return nil 38 | }, s) 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/desugar/flatten.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/gensym" 8 | ) 9 | 10 | func flattenStatement(s interface{}) []interface{} { 11 | switch s := s.(type) { 12 | case ast.DefFunction: 13 | return flattenDefFunction(s) 14 | default: 15 | return []interface{}{s} 16 | } 17 | } 18 | 19 | func flattenStatements(old []interface{}) []interface{} { 20 | new := make([]interface{}, 0) 21 | 22 | for _, s := range old { 23 | new = append(new, flattenStatement(s)...) 24 | } 25 | 26 | return new 27 | } 28 | 29 | func flattenDefFunction(f ast.DefFunction) []interface{} { 30 | f = flattenInnerStatements(f) 31 | 32 | ss := make([]interface{}, 0) 33 | ls := make([]interface{}, 0) 34 | ns := signatureToNames(f.Signature()) 35 | 36 | for _, l := range f.Lets() { 37 | switch l := l.(type) { 38 | case ast.LetVar: 39 | ls = append(ls, l) 40 | ns.add(l.Name()) 41 | case ast.DefFunction: 42 | args := ns.findInDefFunction(l).slice() 43 | n := gensym.GenSym() 44 | 45 | ss = append(ss, letFlattenedFunction(l, n, args)) 46 | ls = append(ls, letClosure(l, n, args)) 47 | 48 | ns.add(l.Name()) 49 | default: 50 | panic(fmt.Errorf("Invalid value: %#v", l)) 51 | } 52 | } 53 | 54 | return append(ss, ast.NewDefFunction(f.Name(), f.Signature(), ls, f.Body(), f.DebugInfo())) 55 | } 56 | 57 | func letFlattenedFunction(f ast.DefFunction, n string, args []string) ast.DefFunction { 58 | return ast.NewDefFunction( 59 | n, 60 | prependPositionalsToSig(args, f.Signature()), 61 | f.Lets(), 62 | f.Body(), 63 | f.DebugInfo()) 64 | } 65 | 66 | func letClosure(f ast.DefFunction, n string, args []string) ast.LetVar { 67 | return ast.NewLetVar( 68 | f.Name(), 69 | ast.NewApp( 70 | "$partial", 71 | ast.NewArguments(namesToPosArgs(append([]string{n}, args...)), nil), 72 | f.DebugInfo())) 73 | } 74 | 75 | func namesToPosArgs(ns []string) []ast.PositionalArgument { 76 | ps := make([]ast.PositionalArgument, 0, len(ns)) 77 | 78 | for _, n := range ns { 79 | ps = append(ps, ast.NewPositionalArgument(n, false)) 80 | } 81 | 82 | return ps 83 | } 84 | 85 | func flattenInnerStatements(f ast.DefFunction) ast.DefFunction { 86 | return ast.NewDefFunction( 87 | f.Name(), 88 | f.Signature(), 89 | flattenStatements(f.Lets()), 90 | f.Body(), 91 | f.DebugInfo()) 92 | } 93 | -------------------------------------------------------------------------------- /src/lib/desugar/let_expression.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | "github.com/cloe-lang/cloe/src/lib/debug" 6 | ) 7 | 8 | func desugarLetExpression(x interface{}) []interface{} { 9 | return []interface{}{convertLetExpression(x)} 10 | } 11 | 12 | func convertLetExpression(x interface{}) interface{} { 13 | return ast.Convert(func(x interface{}) interface{} { 14 | switch x := x.(type) { 15 | case ast.LetExpression: 16 | ls := x.Lets() 17 | y := convertLetExpression(x.Expr()) 18 | 19 | for i := range ls { 20 | l := convertLetExpression(ls[len(ls)-1-i]) 21 | 22 | switch l := l.(type) { 23 | case ast.LetVar: 24 | y = ast.NewPApp( 25 | ast.NewAnonymousFunction(ast.NewSignature([]string{l.Name()}, "", nil, ""), nil, y), 26 | []interface{}{l.Expr()}, 27 | debug.NewGoInfo(0)) 28 | case ast.LetMatch: 29 | y = ast.NewMatch(l.Expr(), []ast.MatchCase{ast.NewMatchCase(l.Pattern(), y)}) 30 | default: 31 | panic("unreachable") 32 | } 33 | } 34 | 35 | return y 36 | } 37 | 38 | return nil 39 | }, x) 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/desugar/let_match.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/consts" 8 | "github.com/cloe-lang/cloe/src/lib/debug" 9 | "github.com/cloe-lang/cloe/src/lib/gensym" 10 | "github.com/cloe-lang/cloe/src/lib/scalar" 11 | ) 12 | 13 | func desugarLetMatch(x interface{}) []interface{} { 14 | x = ast.Convert(convertLetMatch, x) 15 | 16 | if ls, ok := x.([]interface{}); ok { 17 | return ls 18 | } 19 | 20 | return []interface{}{x} 21 | } 22 | 23 | func convertLetMatch(x interface{}) interface{} { 24 | switch x := x.(type) { 25 | case ast.LetMatch: 26 | ns := []string{} 27 | 28 | ast.Convert(func(x interface{}) interface{} { 29 | n, ok := x.(string) 30 | 31 | if !ok { 32 | return nil 33 | } 34 | 35 | if n[:1] != "$" && !scalar.Defined(n) { 36 | ns = append(ns, n) 37 | } 38 | 39 | return nil 40 | }, x.Pattern()) 41 | 42 | d := gensym.GenSym() 43 | 44 | ls := []interface{}{ 45 | ast.NewLetVar( 46 | d, 47 | ast.NewMatch( 48 | x.Expr(), 49 | []ast.MatchCase{ 50 | ast.NewMatchCase( 51 | x.Pattern(), 52 | ast.NewPApp( 53 | consts.Names.DictionaryFunction, 54 | namesToPairsOfIndexAndName(ns), 55 | debug.NewGoInfo(0))), 56 | })), 57 | } 58 | 59 | for i, n := range ns { 60 | ls = append(ls, 61 | ast.NewLetVar(n, ast.NewPApp( 62 | consts.Names.IndexFunction, 63 | []interface{}{d, fmt.Sprint(i)}, 64 | debug.NewGoInfo(0)))) 65 | } 66 | 67 | return ls 68 | case ast.DefFunction: 69 | return ast.NewDefFunction( 70 | x.Name(), x.Signature(), convertLets(x.Lets()), x.Body(), x.DebugInfo()) 71 | case ast.LetExpression: 72 | return ast.NewLetExpression(convertLets(x.Lets()), x.Expr()) 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func namesToPairsOfIndexAndName(ns []string) []interface{} { 79 | args := make([]interface{}, 0, 2*len(ns)) 80 | 81 | for i, n := range ns { 82 | args = append(args, fmt.Sprint(i), n) 83 | } 84 | 85 | return args 86 | } 87 | 88 | func convertLets(ls []interface{}) []interface{} { 89 | ns := make([]interface{}, 0, len(ls)) 90 | 91 | for _, l := range ls { 92 | switch x := ast.Convert(convertLetMatch, l).(type) { 93 | case []interface{}: 94 | ns = append(ns, x...) 95 | default: 96 | ns = append(ns, x) 97 | } 98 | } 99 | 100 | return ns 101 | } 102 | -------------------------------------------------------------------------------- /src/lib/desugar/match/cases_desugarer_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/consts" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEqualPatterns(t *testing.T) { 11 | dictionary := consts.Names.DictionaryFunction 12 | list := consts.Names.ListFunction 13 | 14 | for _, p := range []interface{}{ 15 | "42", 16 | "foo", 17 | app(list), 18 | app(list, "true"), 19 | app(list, app(list)), 20 | app(dictionary), 21 | app(dictionary, "123", "true"), 22 | app(dictionary, app(dictionary)), 23 | } { 24 | assert.True(t, equalPatterns(p, p)) 25 | } 26 | 27 | for _, ps := range [][2]interface{}{ 28 | {"42", "2049"}, 29 | {"foo", "bar"}, 30 | {app(list), app(dictionary)}, 31 | {app(list, "true"), app(list, "false")}, 32 | {app(list, app(list)), app(list, app(list, "42"))}, 33 | {app(dictionary), app(dictionary, "0", "1")}, 34 | {app(dictionary, "123", "true"), app(dictionary, "456", "true")}, 35 | {app(dictionary, app(dictionary)), app(dictionary, app(list))}, 36 | } { 37 | assert.True(t, !equalPatterns(ps[0], ps[1])) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/desugar/match/desugar.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/ast" 4 | 5 | // Desugar desugars match expressions in a statement. 6 | func Desugar(s interface{}) []interface{} { 7 | return []interface{}{desugarMatch(s)} 8 | } 9 | 10 | func desugarMatch(x interface{}) interface{} { 11 | return ast.Convert(func(x interface{}) interface{} { 12 | m, ok := x.(ast.Match) 13 | 14 | if !ok { 15 | return nil 16 | } 17 | 18 | cs := make([]ast.MatchCase, 0, len(m.Cases())) 19 | 20 | for _, c := range m.Cases() { 21 | p, r := newPatternRenamer().Rename(c.Pattern()) 22 | cs = append(cs, ast.NewMatchCase(p, r.Rename(desugarMatch(c.Value())))) 23 | } 24 | 25 | return app(newCasesDesugarer().Desugar(cs), desugarMatch(m.Value())) 26 | }, x) 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/desugar/match/desugar_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/consts" 8 | "github.com/cloe-lang/cloe/src/lib/debug" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestDesugar(t *testing.T) { 13 | for _, s := range []interface{}{ 14 | ast.NewDefFunction( 15 | "factorial", 16 | ast.NewSignature([]string{"n"}, "", nil, ""), 17 | nil, 18 | ast.NewMatch("n", []ast.MatchCase{ 19 | ast.NewMatchCase("0", "1"), 20 | ast.NewMatchCase("_", app("*", "n", app("factorial", app("-", "n", "1")))), 21 | }), debug.NewGoInfo(0)), 22 | ast.NewMutualRecursion([]ast.DefFunction{ 23 | ast.NewDefFunction( 24 | "even?", 25 | ast.NewSignature([]string{"n"}, "", nil, ""), 26 | nil, 27 | ast.NewMatch("n", []ast.MatchCase{ 28 | ast.NewMatchCase("0", "true"), 29 | ast.NewMatchCase("_", app("odd?", app("-", "n", "1"))), 30 | }), debug.NewGoInfo(0)), 31 | ast.NewDefFunction( 32 | "odd?", 33 | ast.NewSignature([]string{"n"}, "", nil, ""), 34 | nil, 35 | ast.NewMatch("n", []ast.MatchCase{ 36 | ast.NewMatchCase("0", "true"), 37 | ast.NewMatchCase("_", app("even?", app("-", "n", "1"))), 38 | }), debug.NewGoInfo(0)), 39 | }, debug.NewGoInfo(0)), 40 | ast.NewLetVar("x", ast.NewMatch("nil", []ast.MatchCase{ 41 | ast.NewMatchCase(app(consts.Names.ListFunction, "1", "x"), "x"), 42 | ast.NewMatchCase(app(consts.Names.DictionaryFunction, "1", "x", `"foo"`, "true"), "x"), 43 | })), 44 | } { 45 | for _, s := range Desugar(s) { 46 | t.Logf("%#v", s) 47 | 48 | ast.Convert(func(x interface{}) interface{} { 49 | _, ok := x.(ast.Match) 50 | assert.False(t, ok) 51 | 52 | return nil 53 | }, s) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/desugar/match/pattern_renamer.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/consts" 8 | "github.com/cloe-lang/cloe/src/lib/gensym" 9 | "github.com/cloe-lang/cloe/src/lib/scalar" 10 | ) 11 | 12 | type patternRenamer struct { 13 | nameMap map[string]string 14 | } 15 | 16 | func newPatternRenamer() patternRenamer { 17 | return patternRenamer{map[string]string{}} 18 | } 19 | 20 | func (r patternRenamer) Rename(p interface{}) (interface{}, valueRenamer) { 21 | p = r.renameNames(p) 22 | return p, newValueRenamer(r.nameMap) 23 | } 24 | 25 | func (r patternRenamer) renameNames(p interface{}) interface{} { 26 | switch x := p.(type) { 27 | case string: 28 | if scalar.Defined(x) { 29 | return x 30 | } 31 | 32 | r.nameMap[x] = gensym.GenSym() 33 | return r.nameMap[x] 34 | case ast.App: 35 | switch x.Function().(string) { 36 | case consts.Names.ListFunction: 37 | fallthrough 38 | case consts.Names.DictionaryFunction: 39 | ps := make([]ast.PositionalArgument, 0, len(x.Arguments().Positionals())) 40 | 41 | for _, p := range x.Arguments().Positionals() { 42 | ps = append(ps, ast.NewPositionalArgument(r.renameNames(p.Value()), p.Expanded())) 43 | } 44 | 45 | return ast.NewApp(x.Function(), ast.NewArguments(ps, nil), x.DebugInfo()) 46 | } 47 | } 48 | 49 | panic(fmt.Errorf("Invalid pattern: %#v", p)) 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/desugar/match/pattern_renamer_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPatternRenamerRenamePanic(t *testing.T) { 11 | assert.Panics(t, func() { 12 | newPatternRenamer().Rename( 13 | ast.NewSwitch("nil", []ast.SwitchCase{ast.NewSwitchCase("nil", "true")}, false)) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/desugar/match/pattern_type.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | type patternType int 4 | 5 | const ( 6 | listPattern patternType = iota 7 | dictionaryPattern 8 | scalarPattern 9 | namePattern 10 | ) 11 | -------------------------------------------------------------------------------- /src/lib/desugar/match/util.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | "github.com/cloe-lang/cloe/src/lib/debug" 6 | ) 7 | 8 | func app(f interface{}, args ...interface{}) interface{} { 9 | return ast.NewPApp(f, args, debug.NewGoInfo(1)) 10 | } 11 | 12 | func newSwitch(v interface{}, cs []ast.SwitchCase, d interface{}) interface{} { 13 | if len(cs) == 0 { 14 | return d 15 | } 16 | 17 | return ast.NewSwitch(v, cs, d) 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/desugar/match/value_renamer.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | ) 8 | 9 | type valueRenamer struct { 10 | nameMap map[string]string 11 | } 12 | 13 | func newValueRenamer(m map[string]string) valueRenamer { 14 | return valueRenamer{m} 15 | } 16 | 17 | func (r valueRenamer) Rename(v interface{}) interface{} { 18 | switch x := v.(type) { 19 | case string: 20 | if n, ok := r.nameMap[x]; ok { 21 | return n 22 | } 23 | 24 | return x 25 | case ast.App: 26 | args := x.Arguments() 27 | ps := make([]ast.PositionalArgument, 0, len(args.Positionals())) 28 | 29 | for _, p := range args.Positionals() { 30 | ps = append(ps, ast.NewPositionalArgument(r.Rename(p.Value()), p.Expanded())) 31 | } 32 | 33 | ks := make([]ast.KeywordArgument, 0, len(args.Keywords())) 34 | 35 | for _, k := range args.Keywords() { 36 | ks = append(ks, ast.NewKeywordArgument(k.Name(), r.Rename(k.Value()))) 37 | } 38 | 39 | return ast.NewApp(r.Rename(x.Function()), ast.NewArguments(ps, ks), x.DebugInfo()) 40 | case ast.AnonymousFunction: 41 | r := r.copy() 42 | 43 | for n := range x.Signature().NameToIndex() { 44 | r.delete(n) 45 | } 46 | 47 | ls := make([]interface{}, 0, len(x.Lets())) 48 | 49 | for _, l := range x.Lets() { 50 | switch x := l.(type) { 51 | case ast.LetVar: 52 | ls = append(ls, ast.NewLetVar(x.Name(), r.Rename(x.Expr()))) 53 | r.delete(x.Name()) 54 | default: 55 | panic("unreachable") 56 | } 57 | } 58 | 59 | return ast.NewAnonymousFunction(x.Signature(), ls, r.Rename(x.Body())) 60 | case ast.Switch: 61 | cs := make([]ast.SwitchCase, 0, len(x.Cases())) 62 | 63 | for _, c := range x.Cases() { 64 | // Switch case patterns are already renamed as match case patterns. 65 | cs = append(cs, ast.NewSwitchCase(c.Pattern(), r.Rename(c.Value()))) 66 | } 67 | 68 | return ast.NewSwitch(r.Rename(x.Value()), cs, r.Rename(x.DefaultCase())) 69 | } 70 | 71 | panic(fmt.Errorf("Invalid value: %#v", v)) 72 | } 73 | 74 | func (r valueRenamer) copy() valueRenamer { 75 | m := make(map[string]string, len(r.nameMap)) 76 | 77 | for k, v := range r.nameMap { 78 | m[k] = v 79 | } 80 | 81 | return newValueRenamer(m) 82 | } 83 | 84 | func (r valueRenamer) delete(s string) { 85 | delete(r.nameMap, s) 86 | } 87 | -------------------------------------------------------------------------------- /src/lib/desugar/match/value_renamer_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/debug" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestValueRenamerRename(t *testing.T) { 12 | r := newValueRenamer(map[string]string{"foo": "bar"}) 13 | 14 | for _, x := range []interface{}{ 15 | "foo", 16 | ast.NewPApp("foo", []interface{}{"bar"}, debug.NewGoInfo(0)), 17 | ast.NewSwitch("123", []ast.SwitchCase{ 18 | ast.NewSwitchCase("456", "false"), 19 | ast.NewSwitchCase("123", "true"), 20 | }, "$matchError"), 21 | ast.NewApp( 22 | "foo", 23 | ast.NewArguments( 24 | []ast.PositionalArgument{ast.NewPositionalArgument("123", false)}, 25 | []ast.KeywordArgument{ 26 | ast.NewKeywordArgument("key", `"value"`), 27 | ast.NewKeywordArgument("", "bar"), 28 | }), 29 | debug.NewGoInfo(0)), 30 | } { 31 | r.Rename(x) 32 | } 33 | } 34 | 35 | func TestValueRenamerRenamePanic(t *testing.T) { 36 | assert.Panics(t, func() { 37 | newValueRenamer(map[string]string{"foo": "bar"}).Rename( 38 | ast.NewMatch("nil", []ast.MatchCase{ast.NewMatchCase("nil", "nil")})) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/desugar/names_test.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/ast" 7 | "github.com/cloe-lang/cloe/src/lib/debug" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNamesFindInLetVar(t *testing.T) { 12 | n := "x" 13 | assert.True(t, newNames(n).findInLetVar(ast.NewLetVar(n, n)).include(n)) 14 | } 15 | 16 | func TestNamesFindInDefFunction(t *testing.T) { 17 | n := "x" 18 | 19 | for _, test := range []struct { 20 | letFunc ast.DefFunction 21 | answer bool 22 | }{ 23 | { 24 | ast.NewDefFunction( 25 | n, 26 | ast.NewSignature(nil, "", nil, ""), 27 | nil, 28 | n, 29 | debug.NewGoInfo(0)), 30 | true, 31 | }, 32 | { 33 | ast.NewDefFunction( 34 | n, 35 | ast.NewSignature(nil, "", nil, ""), 36 | []interface{}{ast.NewLetVar(n, "y")}, 37 | n, 38 | debug.NewGoInfo(0)), 39 | false, 40 | }, 41 | { 42 | ast.NewDefFunction( 43 | n, 44 | ast.NewSignature(nil, "", nil, ""), 45 | []interface{}{ast.NewLetVar(n, "x")}, 46 | "42", 47 | debug.NewGoInfo(0)), 48 | true, 49 | }, 50 | } { 51 | assert.Equal(t, test.answer, newNames(n).findInDefFunction(test.letFunc).include(n)) 52 | } 53 | } 54 | 55 | func TestNamesFindInDefFunctionPanic(t *testing.T) { 56 | assert.Panics(t, func() { 57 | newNames().findInDefFunction(ast.NewDefFunction( 58 | "func", 59 | ast.NewSignature(nil, "", nil, ""), 60 | []interface{}{nil}, 61 | "x", 62 | debug.NewGoInfo(0))) 63 | }) 64 | } 65 | 66 | func TestNamesFindInExpression(t *testing.T) { 67 | n := "x" 68 | 69 | for _, c := range []struct { 70 | expression interface{} 71 | answer bool 72 | }{ 73 | { 74 | "x", 75 | true, 76 | }, 77 | { 78 | ast.NewPApp("f", []interface{}{"x"}, debug.NewGoInfo(0)), 79 | true, 80 | }, 81 | { 82 | ast.NewApp( 83 | "f", 84 | ast.NewArguments(nil, []ast.KeywordArgument{ast.NewKeywordArgument("foo", "x")}), 85 | debug.NewGoInfo(0)), 86 | true, 87 | }, 88 | { 89 | ast.NewApp( 90 | "f", 91 | ast.NewArguments(nil, []ast.KeywordArgument{ast.NewKeywordArgument("", "x")}), 92 | debug.NewGoInfo(0)), 93 | true, 94 | }, 95 | { 96 | ast.NewAnonymousFunction(ast.NewSignature([]string{"x"}, "", nil, ""), nil, "x"), 97 | false, 98 | }, 99 | } { 100 | assert.Equal(t, c.answer, newNames(n).findInExpression(c.expression).include(n)) 101 | } 102 | } 103 | 104 | func TestNamesFindInExpressionPanic(t *testing.T) { 105 | assert.Panics(t, func() { 106 | newNames().findInExpression(nil) 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /src/lib/desugar/remove_aliases.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | ) 6 | 7 | func removeAliases(x interface{}) []interface{} { 8 | return []interface{}{ast.Convert(func(x interface{}) interface{} { 9 | f, ok := x.(ast.DefFunction) 10 | 11 | if !ok { 12 | return nil 13 | } 14 | 15 | ls := make([]interface{}, 0, len(f.Lets())) 16 | b := f.Body() 17 | 18 | for _, l := range reverseSlice(f.Lets()) { 19 | l := l.(ast.LetVar) 20 | s, ok := l.Expr().(string) 21 | 22 | if !ok { 23 | ls = append(ls, l) 24 | continue 25 | } 26 | 27 | r := renamer(l.Name(), s) 28 | 29 | for i, l := range ls { 30 | l := l.(ast.LetVar) 31 | ls[i] = ast.NewLetVar(l.Name(), r(l.Expr())) 32 | } 33 | 34 | b = r(b) 35 | } 36 | 37 | return ast.NewDefFunction(f.Name(), f.Signature(), reverseSlice(ls), b, f.DebugInfo()) 38 | }, x)} 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/desugar/remove_unused_variables.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | ) 6 | 7 | func removeUnusedVariables(x interface{}) []interface{} { 8 | return []interface{}{ast.Convert(func(x interface{}) interface{} { 9 | f, ok := x.(ast.DefFunction) 10 | 11 | if !ok { 12 | return nil 13 | } 14 | 15 | ls := make([]interface{}, 0, len(f.Lets())) 16 | 17 | for _, l := range reverseSlice(f.Lets()) { 18 | for _, e := range append(letVarsToExpressions(ls), f.Body()) { 19 | if len(newNames(l.(ast.LetVar).Name()).findInExpression(e)) > 0 { 20 | ls = append(ls, l) 21 | break 22 | } 23 | } 24 | } 25 | 26 | return ast.NewDefFunction(f.Name(), f.Signature(), reverseSlice(ls), f.Body(), f.DebugInfo()) 27 | }, x)} 28 | } 29 | 30 | func letVarsToExpressions(ls []interface{}) []interface{} { 31 | es := make([]interface{}, 0, len(ls)) 32 | 33 | for _, l := range ls { 34 | es = append(es, l.(ast.LetVar).Expr()) 35 | } 36 | 37 | return es 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/desugar/self_recursion.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/ast" 5 | "github.com/cloe-lang/cloe/src/lib/gensym" 6 | ) 7 | 8 | func desugarSelfRecursiveStatement(x interface{}) []interface{} { 9 | y := ast.Convert(func(x interface{}) interface{} { 10 | switch x := x.(type) { 11 | case ast.DefFunction: 12 | x = desugarInnerSelfRecursions(x) 13 | 14 | if len(newNames(x.Name()).findInDefFunction(x)) == 0 { 15 | return x 16 | } 17 | 18 | unrec := gensym.GenSym() 19 | 20 | return []interface{}{ 21 | ast.NewDefFunction( 22 | unrec, 23 | prependPositionalsToSig([]string{x.Name()}, x.Signature()), 24 | x.Lets(), 25 | x.Body(), 26 | x.DebugInfo()), 27 | ast.NewLetVar(x.Name(), ast.NewPApp("$y", []interface{}{unrec}, x.DebugInfo())), 28 | } 29 | } 30 | 31 | return nil 32 | }, x) 33 | 34 | if ys, ok := y.([]interface{}); ok { 35 | return ys 36 | } 37 | 38 | return []interface{}{y} 39 | } 40 | 41 | func desugarInnerSelfRecursions(f ast.DefFunction) ast.DefFunction { 42 | ls := make([]interface{}, 0, 2*len(f.Lets())) 43 | 44 | for _, l := range f.Lets() { 45 | ls = append(ls, desugarSelfRecursiveStatement(l)...) 46 | } 47 | 48 | return ast.NewDefFunction(f.Name(), f.Signature(), ls, f.Body(), f.DebugInfo()) 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/desugar/util.go: -------------------------------------------------------------------------------- 1 | package desugar 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/ast" 4 | 5 | func signatureToNames(s ast.Signature) names { 6 | ns := newNames() 7 | 8 | for n := range s.NameToIndex() { 9 | ns.add(n) 10 | } 11 | 12 | return ns 13 | } 14 | 15 | func prependPositionalsToSig(ns []string, s ast.Signature) ast.Signature { 16 | return ast.NewSignature( 17 | append(ns, s.Positionals()...), s.RestPositionals(), 18 | s.Keywords(), s.RestKeywords()) 19 | } 20 | 21 | func renamer(a, b string) func(x interface{}) interface{} { 22 | return func(x interface{}) interface{} { 23 | return ast.Convert(func(x interface{}) interface{} { 24 | s, ok := x.(string) 25 | 26 | if !ok || s != a { 27 | return nil 28 | } 29 | 30 | return b 31 | }, x) 32 | } 33 | } 34 | 35 | func reverseSlice(xs []interface{}) []interface{} { 36 | ys := make([]interface{}, 0, len(xs)) 37 | 38 | for i := range xs { 39 | ys = append(ys, xs[len(xs)-i-1]) 40 | } 41 | 42 | return ys 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/gensym/gensym.go: -------------------------------------------------------------------------------- 1 | package gensym 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | ) 7 | 8 | var index uint64 9 | 10 | // GenSym creates a unique symbol as a string from its components. 11 | func GenSym() string { 12 | return "$" + fmt.Sprint(atomic.AddUint64(&index, 1)-1) 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/gensym/gensym_test.go: -------------------------------------------------------------------------------- 1 | package gensym 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGenSym(t *testing.T) { 11 | assert.Equal(t, "$0", GenSym()) 12 | assert.Equal(t, "$1", GenSym()) 13 | assert.Equal(t, "$2", GenSym()) 14 | } 15 | 16 | func TestGenSymAtomicity(t *testing.T) { 17 | const n = 1024 18 | 19 | c := make(chan string, n) 20 | wg := sync.WaitGroup{} 21 | 22 | for i := 0; i < n; i++ { 23 | wg.Add(1) 24 | go func() { 25 | c <- GenSym() 26 | wg.Done() 27 | }() 28 | } 29 | 30 | wg.Wait() 31 | close(c) 32 | 33 | m := make(map[string]bool) 34 | 35 | for s := range c { 36 | m[s] = true 37 | } 38 | 39 | assert.Equal(t, n, len(m)) 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/ir/app.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/core" 5 | "github.com/cloe-lang/cloe/src/lib/debug" 6 | ) 7 | 8 | // App represents an application of a function to arguments. 9 | type App struct { 10 | function interface{} 11 | args Arguments 12 | info *debug.Info 13 | } 14 | 15 | // NewApp creates an App from a function and arguments of expressions in IR. 16 | func NewApp(f interface{}, args Arguments, info *debug.Info) App { 17 | return App{f, args, info} 18 | } 19 | 20 | func (app App) interpret(args []core.Value) core.Value { 21 | ps := make([]core.PositionalArgument, 0, len(app.args.positionals)) 22 | 23 | for _, p := range app.args.positionals { 24 | ps = append(ps, p.interpret(args)) 25 | } 26 | 27 | ks := make([]core.KeywordArgument, 0, len(app.args.keywords)) 28 | 29 | for _, k := range app.args.keywords { 30 | ks = append(ks, k.interpret(args)) 31 | } 32 | 33 | return core.AppWithInfo(interpretExpression(args, app.function), core.NewArguments(ps, ks), app.info) 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/ir/app_test.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/cloe-lang/cloe/src/lib/debug" 8 | ) 9 | 10 | func TestAppInterpret(t *testing.T) { 11 | NewApp( 12 | core.ToString, 13 | NewArguments( 14 | []PositionalArgument{NewPositionalArgument(core.Nil, false)}, 15 | []KeywordArgument{NewKeywordArgument("foo", core.Nil), NewKeywordArgument("", core.Nil)}), 16 | debug.NewGoInfo(0)).interpret(nil) 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/ir/arguments.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | // Arguments represents arguments passed to a function in IR. 4 | type Arguments struct { 5 | positionals []PositionalArgument 6 | keywords []KeywordArgument 7 | } 8 | 9 | // NewArguments creates arguments from positional and keyword arguments and 10 | // expanded dictionaries. 11 | func NewArguments(ps []PositionalArgument, ks []KeywordArgument) Arguments { 12 | return Arguments{ps, ks} 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/ir/case.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Case represents a case of a pattern and a corresponding value in a switch expression. 6 | type Case struct { 7 | pattern core.Value 8 | value interface{} 9 | } 10 | 11 | // NewCase creates a case in a switch expression. 12 | func NewCase(p core.Value, v interface{}) Case { 13 | return Case{p, v} 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/ir/keyword_argument.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // KeywordArgument represents a keyword argument passed to a function. 6 | type KeywordArgument struct { 7 | name string 8 | value interface{} 9 | } 10 | 11 | // NewKeywordArgument creates a keyword argument from a bound name and its value. 12 | func NewKeywordArgument(n string, v interface{}) KeywordArgument { 13 | return KeywordArgument{n, v} 14 | } 15 | 16 | func (k KeywordArgument) interpret(args []core.Value) core.KeywordArgument { 17 | return core.NewKeywordArgument(k.name, interpretExpression(args, k.value)) 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/ir/keyword_argument_test.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | func TestNewKeywordArgument(t *testing.T) { 10 | NewKeywordArgument("foo", 0) 11 | } 12 | 13 | func TestKeywordArgumentInterpret(t *testing.T) { 14 | NewKeywordArgument("foo", 0).interpret([]core.Value{core.NewNumber(123)}) 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/ir/positional_argument.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // PositionalArgument represents a positional argument passed to a function. 6 | // It can be a list value and expanded into multiple arguments. 7 | type PositionalArgument struct { 8 | value interface{} 9 | expanded bool 10 | } 11 | 12 | // NewPositionalArgument creates a positional argument. 13 | func NewPositionalArgument(v interface{}, expanded bool) PositionalArgument { 14 | return PositionalArgument{v, expanded} 15 | } 16 | 17 | func (p PositionalArgument) interpret(args []core.Value) core.PositionalArgument { 18 | return core.NewPositionalArgument(interpretExpression(args, p.value), p.expanded) 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/ir/switch.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/core" 5 | ) 6 | 7 | // Switch represents a switch expression. 8 | type Switch struct { 9 | matchedValue interface{} 10 | caseValues []interface{} 11 | defaultCase interface{} 12 | dict core.Value 13 | } 14 | 15 | // NewSwitch creates a switch expression. 16 | func NewSwitch(m interface{}, cs []Case, d interface{}) Switch { 17 | if d == nil { 18 | panic("Default cases must be provided in switch expressions") 19 | } 20 | 21 | vs := make([]interface{}, 0, len(cs)) 22 | 23 | for _, c := range cs { 24 | vs = append(vs, c.value) 25 | } 26 | 27 | return Switch{m, vs, d, compileCasesToDictionary(cs)} 28 | } 29 | 30 | func compileCasesToDictionary(cs []Case) core.Value { 31 | kvs := make([]core.KeyValue, 0, len(cs)) 32 | 33 | for i, c := range cs { 34 | kvs = append(kvs, core.KeyValue{Key: c.pattern, Value: core.NewNumber(float64(i))}) 35 | } 36 | 37 | return core.EvalPure(core.NewDictionary(kvs)) 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/ir/switch_test.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewSwitch(t *testing.T) { 11 | NewSwitch(0, []Case{NewCase(core.Nil, 1)}, core.Nil) 12 | } 13 | 14 | func TestNewSwitchNoDefaultCase(t *testing.T) { 15 | assert.Panics(t, func() { 16 | NewSwitch(0, []Case{}, nil) 17 | }) 18 | } 19 | 20 | func TestSwitchInFunction(t *testing.T) { 21 | f := CompileFunction( 22 | core.NewSignature([]string{"x"}, "", nil, ""), 23 | nil, 24 | NewSwitch(0, []Case{ 25 | NewCase(core.NewString("foo"), core.NewNumber(42)), 26 | NewCase(core.True, core.NewNumber(1993)), 27 | }, core.NewNumber(2049))) 28 | 29 | for _, c := range []struct{ answer, argument core.Value }{ 30 | {core.NewNumber(42), core.NewString("foo")}, 31 | {core.NewNumber(1993), core.True}, 32 | {core.NewNumber(2049), core.Nil}, 33 | } { 34 | assert.Equal(t, c.answer, core.EvalPure(core.PApp(f, c.argument))) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/ir/util.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // CompileFunction compiles a function in IR into a thunk. 6 | func CompileFunction(s core.Signature, vars []interface{}, expr interface{}) core.Value { 7 | // TODO: Compile everything into bytecode here. 8 | 9 | return core.NewLazyFunction( 10 | s, 11 | func(ts ...core.Value) core.Value { 12 | args := append(make([]core.Value, 0, len(ts)+len(vars)), ts...) 13 | 14 | for _, v := range vars { 15 | args = append(args, interpretExpression(args, v)) 16 | } 17 | 18 | return interpretExpression(args, expr) 19 | }) 20 | } 21 | 22 | func interpretExpression(args []core.Value, expr interface{}) core.Value { 23 | switch x := expr.(type) { 24 | case int: 25 | return args[x] 26 | case core.Value: 27 | return x 28 | case App: 29 | return x.interpret(args) 30 | case Switch: 31 | v := core.EvalPure(core.PApp(core.Index, x.dict, interpretExpression(args, x.matchedValue))) 32 | n, ok := v.(*core.NumberType) 33 | 34 | if !ok { 35 | return interpretExpression(args, x.defaultCase) 36 | } 37 | 38 | return interpretExpression(args, x.caseValues[int(*n)]) 39 | } 40 | 41 | panic("unreachable") 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/ir/util_test.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/core" 8 | "github.com/cloe-lang/cloe/src/lib/debug" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCompileFunction(t *testing.T) { 13 | const n1, n2, n3 = 2, 3, 4 14 | 15 | f := CompileFunction( 16 | core.NewSignature( 17 | []string{"f", "x1", "x2", "x3"}, "", 18 | nil, "", 19 | ), 20 | nil, 21 | newAppWithDummyInfo(0, newPositionalArguments(1, newAppWithDummyInfo(0, newPositionalArguments(2, 3))))) 22 | 23 | x1 := *core.NewNumber(math.Pow(n1, math.Pow(n2, n3))) 24 | x2, err := core.EvalNumber(core.PApp(f, core.Pow, core.NewNumber(n1), core.NewNumber(n2), core.NewNumber(n3))) 25 | 26 | assert.Nil(t, err) 27 | t.Logf("%v == %v?", x2, x1) 28 | assert.Equal(t, x1, x2) 29 | } 30 | 31 | func TestCompileFunctionWithVars(t *testing.T) { 32 | const n1, n2, n3 = 2, 3, 4 33 | 34 | f := CompileFunction( 35 | core.NewSignature( 36 | []string{"f", "x1", "x2", "x3"}, "", 37 | nil, "", 38 | ), 39 | []interface{}{newAppWithDummyInfo(0, newPositionalArguments(2, 3))}, 40 | newAppWithDummyInfo(0, newPositionalArguments(1, 4))) 41 | 42 | x1 := *core.NewNumber(math.Pow(n1, math.Pow(n2, n3))) 43 | x2, err := core.EvalNumber(core.PApp(f, core.Pow, core.NewNumber(n1), core.NewNumber(n2), core.NewNumber(n3))) 44 | 45 | assert.Nil(t, err) 46 | t.Logf("%v == %v?", x2, x1) 47 | assert.Equal(t, x1, x2) 48 | } 49 | 50 | func newPositionalArguments(xs ...interface{}) Arguments { 51 | ps := make([]PositionalArgument, 0, len(xs)) 52 | 53 | for _, x := range xs { 54 | ps = append(ps, NewPositionalArgument(x, false)) 55 | } 56 | 57 | return NewArguments(ps, nil) 58 | } 59 | 60 | func newAppWithDummyInfo(f interface{}, args Arguments) App { 61 | return NewApp(f, args, debug.NewGoInfo(0)) 62 | } 63 | 64 | func TestInterpretExpressionFail(t *testing.T) { 65 | assert.Panics(t, func() { 66 | interpretExpression(nil, "foo") 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/lib/modules/fs/create_directory.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var createDirectory = core.NewEffectFunction( 10 | core.NewSignature( 11 | []string{"name"}, "", 12 | []core.OptionalParameter{core.NewOptionalParameter("existOk", core.False)}, "", 13 | ), 14 | func(vs ...core.Value) core.Value { 15 | s, e := core.EvalString(vs[0]) 16 | 17 | if e != nil { 18 | return e 19 | } 20 | 21 | b, e := core.EvalBoolean(vs[1]) 22 | 23 | if e != nil { 24 | return e 25 | } else if b { 26 | if f, err := os.Stat(string(s)); err == nil && f.IsDir() { 27 | return core.Nil 28 | } 29 | } 30 | 31 | if err := os.Mkdir(string(s), os.ModeDir|0775); err != nil { 32 | return fileSystemError(err) 33 | } 34 | 35 | return core.Nil 36 | }) 37 | -------------------------------------------------------------------------------- /src/lib/modules/fs/create_directory_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/cloe-lang/cloe/src/lib/core" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestCreateDirectory(t *testing.T) { 14 | root, err := ioutil.TempDir("", "") 15 | 16 | assert.Nil(t, err) 17 | 18 | d := filepath.Join(root, "foo") 19 | 20 | _, ok := core.EvalImpure(core.PApp(createDirectory, core.NewString(d))).(core.NilType) 21 | assert.True(t, ok) 22 | 23 | e, ok := core.EvalImpure(core.PApp(createDirectory, core.NewString(d))).(*core.ErrorType) 24 | assert.True(t, ok) 25 | assert.Equal(t, "FileSystemError", e.Name()) 26 | 27 | _, ok = core.EvalImpure(core.App( 28 | createDirectory, 29 | core.NewArguments( 30 | []core.PositionalArgument{core.NewPositionalArgument(core.NewString(d), false)}, 31 | []core.KeywordArgument{core.NewKeywordArgument("existOk", core.True)}), 32 | )).(core.NilType) 33 | assert.True(t, ok) 34 | 35 | os.Remove(root) 36 | } 37 | 38 | func TestCreateDirectoryWithInvalidArguments(t *testing.T) { 39 | _, ok := core.EvalImpure(core.PApp(createDirectory, core.Nil)).(*core.ErrorType) 40 | assert.True(t, ok) 41 | 42 | _, ok = core.EvalImpure(core.App( 43 | createDirectory, 44 | core.NewArguments( 45 | []core.PositionalArgument{core.NewPositionalArgument(core.NewString("foo"), false)}, 46 | []core.KeywordArgument{core.NewKeywordArgument("existOk", core.Nil)}), 47 | )).(*core.ErrorType) 48 | assert.True(t, ok) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/modules/fs/error.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | func fileSystemError(err error) core.Value { 6 | return core.NewError("FileSystemError", err.Error()) 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/modules/fs/module.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Module is a module in the language. 6 | var Module = map[string]core.Value{ 7 | "createDirectory": createDirectory, 8 | "readDirectory": readDirectory, 9 | "remove": remove, 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/modules/fs/read_directory.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var readDirectory = core.NewEffectFunction( 10 | core.NewSignature([]string{"name"}, "", nil, ""), 11 | func(vs ...core.Value) core.Value { 12 | s, e := core.EvalString(vs[0]) 13 | 14 | if e != nil { 15 | return e 16 | } 17 | 18 | fs, err := ioutil.ReadDir(string(s)) 19 | 20 | if err != nil { 21 | return fileSystemError(err) 22 | } 23 | 24 | ss := make([]core.Value, 0, len(fs)) 25 | 26 | for _, f := range fs { 27 | ss = append(ss, core.NewString(f.Name())) 28 | } 29 | 30 | return core.NewList(ss...) 31 | }) 32 | -------------------------------------------------------------------------------- /src/lib/modules/fs/read_directory_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "testing" 8 | 9 | "github.com/cloe-lang/cloe/src/lib/core" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestReadDirectory(t *testing.T) { 14 | d, err := ioutil.TempDir("", "") 15 | assert.Nil(t, err) 16 | 17 | l, e := core.EvalList(core.EvalImpure(core.PApp(readDirectory, core.NewString(d)))) 18 | assert.Nil(t, e) 19 | 20 | n, e := core.EvalNumber(core.PApp(core.Size, l)) 21 | assert.Nil(t, e) 22 | assert.Equal(t, core.NumberType(0), n) 23 | 24 | f, err := os.OpenFile(path.Join(d, "foo.txt"), os.O_CREATE, 0600) 25 | assert.Nil(t, err) 26 | 27 | l, e = core.EvalList(core.EvalImpure(core.PApp(readDirectory, core.NewString(d)))) 28 | assert.Nil(t, e) 29 | 30 | n, e = core.EvalNumber(core.PApp(core.Size, l)) 31 | assert.Nil(t, e) 32 | assert.Equal(t, core.NumberType(1), n) 33 | 34 | assert.Nil(t, os.Remove(f.Name())) 35 | assert.Nil(t, os.Remove(d)) 36 | } 37 | 38 | func TestReadDirectoryError(t *testing.T) { 39 | for _, v := range []core.Value{core.Nil, core.NewString("dir")} { 40 | _, e := core.EvalList(core.EvalImpure(core.PApp(readDirectory, v))) 41 | assert.NotNil(t, e) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/modules/fs/remove.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var remove = core.NewEffectFunction( 10 | core.NewSignature([]string{"name"}, "", nil, ""), 11 | func(vs ...core.Value) core.Value { 12 | s, e := core.EvalString(vs[0]) 13 | 14 | if e != nil { 15 | return e 16 | } 17 | 18 | if err := os.Remove(string(s)); err != nil { 19 | return fileSystemError(err) 20 | } 21 | 22 | return core.Nil 23 | }) 24 | -------------------------------------------------------------------------------- /src/lib/modules/fs/remove_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/core" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestRemove(t *testing.T) { 12 | f, err := ioutil.TempFile("", "") 13 | assert.Nil(t, err) 14 | 15 | _, ok := core.EvalImpure(core.PApp(remove, core.NewString(f.Name()))).(core.NilType) 16 | assert.True(t, ok) 17 | 18 | e, ok := core.EvalImpure(core.PApp(remove, core.NewString(f.Name()))).(*core.ErrorType) 19 | assert.True(t, ok) 20 | assert.Equal(t, "FileSystemError", e.Name()) 21 | } 22 | 23 | func TestRemoveError(t *testing.T) { 24 | _, ok := core.EvalImpure(core.PApp(remove, core.Nil)).(*core.ErrorType) 25 | assert.True(t, ok) 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/modules/http/error.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | func httpError(err error) core.Value { 6 | return core.NewError("HTTPError", err.Error()) 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/modules/http/get.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/cloe-lang/cloe/src/lib/core" 9 | ) 10 | 11 | var get = core.NewLazyFunction( 12 | core.NewSignature( 13 | []string{"url"}, "", 14 | []core.OptionalParameter{core.NewOptionalParameter("error", core.True)}, "", 15 | ), 16 | func(vs ...core.Value) core.Value { 17 | s, e := core.EvalString(vs[0]) 18 | 19 | if e != nil { 20 | return e 21 | } 22 | 23 | r, err := http.Get(string(s)) 24 | 25 | return handleMethodResult(r, err, vs[1]) 26 | }) 27 | 28 | func handleMethodResult(r *http.Response, err error, errorOption core.Value) core.Value { 29 | if err != nil { 30 | return httpError(err) 31 | } 32 | 33 | bs, err := ioutil.ReadAll(r.Body) 34 | 35 | if err != nil { 36 | return httpError(err) 37 | } 38 | 39 | b, e := core.EvalBoolean(errorOption) 40 | 41 | if e != nil { 42 | return e 43 | } 44 | 45 | if b && r.StatusCode/100 != 2 { 46 | return httpError(errors.New("status code is not 2XX")) 47 | } 48 | 49 | if err = r.Body.Close(); err != nil { 50 | return httpError(err) 51 | } 52 | 53 | return core.NewDictionary([]core.KeyValue{ 54 | {Key: core.NewString("status"), Value: core.NewNumber(float64(r.StatusCode))}, 55 | {Key: core.NewString("body"), Value: core.NewString(string(bs))}, 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/modules/http/get_requests_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "testing" 7 | "time" 8 | 9 | "github.com/cloe-lang/cloe/src/lib/core" 10 | "github.com/cloe-lang/cloe/src/lib/systemt" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestGetRequests(t *testing.T) { 15 | go systemt.RunDaemons() 16 | 17 | v := core.PApp(getRequests, core.NewString(":8080")) 18 | 19 | go core.EvalPure(v) 20 | time.Sleep(100 * time.Millisecond) 21 | 22 | rc := make(chan string) 23 | go func() { 24 | r, err := http.Get("http://127.0.0.1:8080/foo/bar?baz=123") 25 | 26 | assert.Nil(t, err) 27 | 28 | bs, err := ioutil.ReadAll(r.Body) 29 | 30 | assert.Nil(t, err) 31 | 32 | rc <- string(bs) 33 | }() 34 | 35 | _, ok := core.EvalPure(v).(*core.ListType) 36 | 37 | assert.True(t, ok) 38 | 39 | r := core.PApp(core.First, v) 40 | 41 | testRequest(t, r) 42 | 43 | v = core.PApp( 44 | core.PApp(core.Index, r, core.NewString("respond")), 45 | core.NewString("Hello, world!")) 46 | 47 | assert.Equal(t, core.Nil, core.EvalImpure(v)) 48 | assert.Equal(t, "Hello, world!", <-rc) 49 | } 50 | 51 | func testRequest(t *testing.T, v core.Value) { 52 | assert.Equal(t, 53 | core.NewString(""), 54 | core.EvalPure(core.PApp(core.Index, v, core.NewString("body")))) 55 | assert.Equal(t, 56 | core.NewString("GET"), 57 | core.EvalPure(core.PApp(core.Index, v, core.NewString("method")))) 58 | assert.Equal(t, 59 | core.NewString("/foo/bar?baz=123"), 60 | core.EvalPure(core.PApp(core.Index, v, core.NewString("url")))) 61 | } 62 | 63 | func TestGetRequestsWithCustomStatus(t *testing.T) { 64 | go systemt.RunDaemons() 65 | 66 | v := core.PApp(getRequests, core.NewString(":8888")) 67 | 68 | go core.EvalPure(v) 69 | time.Sleep(100 * time.Millisecond) 70 | 71 | status := make(chan int) 72 | go func() { 73 | r, err := http.Get("http://127.0.0.1:8888/foo/bar?baz=123") 74 | 75 | assert.Nil(t, err) 76 | 77 | status <- r.StatusCode 78 | }() 79 | 80 | v = core.App( 81 | core.PApp(core.Index, core.PApp(core.First, v), core.NewString("respond")), 82 | core.NewArguments( 83 | []core.PositionalArgument{core.NewPositionalArgument(core.NewString(""), false)}, 84 | []core.KeywordArgument{ 85 | core.NewKeywordArgument("status", core.NewNumber(404)), 86 | })) 87 | 88 | assert.Equal(t, core.Nil, core.EvalImpure(v)) 89 | assert.Equal(t, 404, <-status) 90 | } 91 | 92 | func TestGetRequestsError(t *testing.T) { 93 | go systemt.RunDaemons() 94 | 95 | _, ok := core.EvalPure(core.PApp(getRequests, core.Nil)).(*core.ErrorType) 96 | assert.True(t, ok) 97 | } 98 | -------------------------------------------------------------------------------- /src/lib/modules/http/get_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGet(t *testing.T) { 11 | v := core.PApp(get, core.NewString(rootURL)) 12 | _, ok := core.EvalPure(v).(*core.DictionaryType) 13 | 14 | assert.True(t, ok) 15 | assert.Equal(t, 16 | core.NewNumber(200), 17 | core.EvalPure(core.PApp(core.Index, v, core.NewString("status")))) 18 | 19 | _, ok = core.EvalPure(core.PApp(core.Index, v, core.NewString("body"))).(core.StringType) 20 | 21 | assert.True(t, ok) 22 | } 23 | 24 | func TestGetWithInvalidArgument(t *testing.T) { 25 | e, ok := core.EvalPure(core.PApp(get, core.Nil)).(*core.ErrorType) 26 | 27 | assert.True(t, ok) 28 | assert.Equal(t, "TypeError", e.Name()) 29 | } 30 | 31 | func TestGetWithInvalidHost(t *testing.T) { 32 | e, ok := core.EvalPure(core.PApp(get, core.NewString(invalidHostURL))).(*core.ErrorType) 33 | 34 | assert.True(t, ok) 35 | assert.Equal(t, "HTTPError", e.Name()) 36 | } 37 | 38 | func TestGetWithInvalidPath(t *testing.T) { 39 | e, ok := core.EvalPure(core.PApp(get, core.NewString(invalidPathURL))).(*core.ErrorType) 40 | 41 | assert.True(t, ok) 42 | assert.Equal(t, "HTTPError", e.Name()) 43 | } 44 | 45 | func TestGetWithInvalidPathButNoError(t *testing.T) { 46 | v := core.App( 47 | get, 48 | core.NewArguments( 49 | []core.PositionalArgument{ 50 | core.NewPositionalArgument(core.NewString(invalidPathURL), false), 51 | }, 52 | []core.KeywordArgument{ 53 | core.NewKeywordArgument("error", core.False)})) 54 | 55 | _, ok := core.EvalPure(v).(*core.DictionaryType) 56 | assert.True(t, ok) 57 | 58 | n, err := core.EvalNumber(core.PApp(core.Index, v, core.NewString("status"))) 59 | 60 | assert.Equal(t, nil, err) 61 | assert.Equal(t, 404.0, float64(n)) 62 | } 63 | 64 | func TestGetWithInvalidErrorArgument(t *testing.T) { 65 | e, ok := core.EvalPure(core.App( 66 | get, 67 | core.NewArguments( 68 | []core.PositionalArgument{ 69 | core.NewPositionalArgument(core.NewString(invalidPathURL), false), 70 | }, 71 | []core.KeywordArgument{ 72 | core.NewKeywordArgument("error", core.Nil)}))).(*core.ErrorType) 73 | 74 | assert.True(t, ok) 75 | assert.Equal(t, "TypeError", e.Name()) 76 | } 77 | -------------------------------------------------------------------------------- /src/lib/modules/http/module.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Module is a module in the language. 6 | var Module = map[string]core.Value{ 7 | "get": get, 8 | "getRequests": getRequests, 9 | "post": post, 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/modules/http/post.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/core" 8 | ) 9 | 10 | var post = core.NewLazyFunction( 11 | core.NewSignature( 12 | []string{"url", "body"}, "", 13 | []core.OptionalParameter{ 14 | core.NewOptionalParameter("contentType", core.NewString("text/plain")), 15 | core.NewOptionalParameter("error", core.True), 16 | }, 17 | "", 18 | ), 19 | func(vs ...core.Value) core.Value { 20 | ss := make([]string, 0, 3) 21 | 22 | for i := 0; i < cap(ss); i++ { 23 | s, err := core.EvalString(vs[i]) 24 | 25 | if err != nil { 26 | return err 27 | } 28 | 29 | ss = append(ss, string(s)) 30 | } 31 | 32 | r, err := http.Post(ss[0], ss[2], strings.NewReader(ss[1])) 33 | 34 | return handleMethodResult(r, err, vs[3]) 35 | }) 36 | -------------------------------------------------------------------------------- /src/lib/modules/http/post_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPost(t *testing.T) { 11 | v := core.PApp(post, core.NewString(postURL), core.NewString("")) 12 | _, ok := core.EvalPure(v).(*core.DictionaryType) 13 | 14 | t.Log(core.EvalPure(v)) 15 | assert.True(t, ok) 16 | assert.Equal(t, 17 | core.NewNumber(200), 18 | core.EvalPure(core.PApp(core.Index, v, core.NewString("status")))) 19 | 20 | _, ok = core.EvalPure(core.PApp(core.Index, v, core.NewString("body"))).(core.StringType) 21 | 22 | assert.True(t, ok) 23 | } 24 | 25 | func TestPostWithInvalidURL(t *testing.T) { 26 | e, ok := core.EvalPure(core.PApp(post, core.Nil, core.NewString(""))).(*core.ErrorType) 27 | 28 | assert.True(t, ok) 29 | assert.Equal(t, "TypeError", e.Name()) 30 | } 31 | 32 | func TestPostWithInvalidBody(t *testing.T) { 33 | e, ok := core.EvalPure(core.PApp(post, core.NewString(rootURL), core.Nil)).(*core.ErrorType) 34 | 35 | assert.True(t, ok) 36 | assert.Equal(t, "TypeError", e.Name()) 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/modules/http/setup_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const ( 11 | rootURL = "http://localhost:2049/" 12 | postURL = "http://localhost:2049/post" 13 | invalidPathURL = "http://localhost:2049/invalid-path" 14 | invalidHostURL = "http://hostlocal:2049/" 15 | ) 16 | 17 | type testHandler struct{} 18 | 19 | func (testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 20 | switch r.URL.Path { 21 | case "/": 22 | w.Write(nil) 23 | case "/post": 24 | if r.Method != "POST" { 25 | w.WriteHeader(404) 26 | } 27 | default: 28 | w.WriteHeader(404) 29 | } 30 | } 31 | 32 | func TestMain(m *testing.M) { 33 | go http.ListenAndServe(":2049", testHandler{}) 34 | 35 | time.Sleep(time.Millisecond) 36 | 37 | os.Exit(m.Run()) 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/modules/json/decode.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "github.com/Jeffail/gabs" 5 | "github.com/cloe-lang/cloe/src/lib/core" 6 | ) 7 | 8 | var decode = core.NewLazyFunction( 9 | core.NewSignature([]string{"encoded"}, "", nil, ""), 10 | func(vs ...core.Value) core.Value { 11 | s, err := core.EvalString(vs[0]) 12 | 13 | if err != nil { 14 | return err 15 | } 16 | 17 | return decodeString(string(s)) 18 | }) 19 | 20 | func decodeString(s string) core.Value { 21 | j, err := gabs.ParseJSON([]byte(s)) 22 | 23 | if err != nil { 24 | return jsonError(err) 25 | } 26 | 27 | return convertToValue(j.Data()) 28 | } 29 | 30 | func convertToValue(x interface{}) core.Value { 31 | if x == nil { 32 | return core.Nil 33 | } 34 | 35 | switch x := x.(type) { 36 | case []interface{}: 37 | ts := []core.Value{} 38 | 39 | for _, y := range x { 40 | ts = append(ts, convertToValue(y)) 41 | } 42 | 43 | return core.NewList(ts...) 44 | case map[string]interface{}: 45 | kvs := make([]core.KeyValue, 0, len(x)) 46 | 47 | for k, v := range x { 48 | kvs = append(kvs, core.KeyValue{Key: core.NewString(k), Value: convertToValue(v)}) 49 | } 50 | 51 | return core.NewDictionary(kvs) 52 | case string: 53 | return core.NewString(x) 54 | case float64: 55 | return core.NewNumber(x) 56 | case bool: 57 | return core.NewBoolean(x) 58 | } 59 | 60 | panic("unreachable") 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/modules/json/decode_test.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var jsons = []string{ 11 | `true`, 12 | `false`, 13 | `123`, 14 | `-3.14`, 15 | `"foo"`, 16 | `null`, 17 | `[]`, 18 | `["foo", 42, "bar"]`, 19 | `{}`, 20 | `{"foo": 42, "bar": true, "baz": "blah"}`, 21 | } 22 | 23 | func TestDecode(t *testing.T) { 24 | for _, s := range jsons { 25 | t.Log(core.EvalPure(core.PApp(decode, core.NewString(s)))) 26 | } 27 | } 28 | 29 | func TestDecodeWithNonString(t *testing.T) { 30 | _, ok := core.EvalPure(core.PApp(decode, core.Nil)).(*core.ErrorType) 31 | assert.True(t, ok) 32 | } 33 | 34 | func TestDecodeString(t *testing.T) { 35 | for _, s := range jsons { 36 | t.Log(core.EvalPure(core.PApp(core.ToString, decodeString(s)))) 37 | } 38 | } 39 | 40 | func TestDecodeStringWithInvalidJSON(t *testing.T) { 41 | for _, s := range []string{ 42 | `s`, 43 | `[`, 44 | `{`, 45 | `nul`, 46 | `nil`, 47 | } { 48 | _, ok := core.EvalPure(decodeString(s)).(*core.ErrorType) 49 | assert.True(t, ok) 50 | } 51 | } 52 | 53 | func TestConvertToValueWithInvalidType(t *testing.T) { 54 | assert.Panics(t, func() { 55 | convertToValue(0) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/modules/json/encode.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/cloe-lang/cloe/src/lib/core" 9 | ) 10 | 11 | var jsonEncodeError = jsonError(errors.New("cannot be encoded as JSON")) 12 | 13 | var encode = core.NewLazyFunction( 14 | core.NewSignature([]string{"decoded"}, "", nil, ""), 15 | func(vs ...core.Value) core.Value { 16 | s, err := encodeValue(vs[0]) 17 | 18 | if err != nil { 19 | return err 20 | } else if s == "" { 21 | return jsonEncodeError 22 | } 23 | 24 | return core.NewString(s) 25 | }) 26 | 27 | func encodeValue(v core.Value) (string, core.Value) { 28 | switch v := core.EvalPure(v).(type) { 29 | case core.NilType: 30 | return "null", nil 31 | case *core.NumberType: 32 | return fmt.Sprintf("%v", *v), nil 33 | case core.StringType: 34 | return fmt.Sprintf("%#v", string(v)), nil 35 | case *core.BooleanType: 36 | if *v { 37 | return "true", nil 38 | } 39 | 40 | return "false", nil 41 | case *core.ErrorType: 42 | return "", v 43 | case *core.ListType: 44 | ss := []string{} 45 | 46 | for !v.Empty() { 47 | s, err := encodeValue(v.First()) 48 | 49 | if err != nil { 50 | return "", err 51 | } else if s != "" { 52 | ss = append(ss, s) 53 | } 54 | 55 | v, err = core.EvalList(v.Rest()) 56 | 57 | if err != nil { 58 | return "", err 59 | } 60 | } 61 | 62 | return "[" + strings.Join(ss, ",") + "]", nil 63 | case *core.DictionaryType: 64 | l, err := core.EvalList(core.PApp(core.ToList, v)) 65 | 66 | if err != nil { 67 | return "", err 68 | } 69 | 70 | ss := []string{} 71 | 72 | for !l.Empty() { 73 | ll, err := core.EvalList(l.First()) 74 | 75 | if err != nil { 76 | return "", err 77 | } 78 | 79 | kk := core.EvalPure(ll.First()) 80 | 81 | switch kk.(type) { 82 | case *core.BooleanType, core.NilType, *core.NumberType: 83 | s, err := encodeValue(kk) 84 | 85 | if err != nil { 86 | return "", err 87 | } 88 | 89 | kk = core.StringType(s) 90 | } 91 | 92 | k, err := encodeValue(kk) 93 | 94 | if err != nil { 95 | return "", err 96 | } 97 | 98 | ll, err = core.EvalList(ll.Rest()) 99 | 100 | if err != nil { 101 | return "", err 102 | } 103 | 104 | v, err := encodeValue(ll.First()) 105 | 106 | if err != nil { 107 | return "", err 108 | } 109 | 110 | ss = append(ss, k+":"+v) 111 | 112 | l, err = core.EvalList(l.Rest()) 113 | 114 | if err != nil { 115 | return "", err 116 | } 117 | } 118 | 119 | return "{" + strings.Join(ss, ",") + "}", nil 120 | } 121 | 122 | return "", nil 123 | } 124 | -------------------------------------------------------------------------------- /src/lib/modules/json/encode_test.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncode(t *testing.T) { 11 | for _, c := range []struct { 12 | value core.Value 13 | answer string 14 | }{ 15 | {core.True, `true`}, 16 | {core.False, `false`}, 17 | {core.NewNumber(123), `123`}, 18 | {core.NewNumber(-0.125), `-0.125`}, 19 | {core.NewString("foo"), `"foo"`}, 20 | {core.Nil, `null`}, 21 | {core.EmptyList, `[]`}, 22 | {core.NewList(core.If), `[]`}, 23 | {core.NewList( 24 | core.NewString("foo"), 25 | core.NewNumber(42), 26 | core.NewString("bar"), 27 | ), `["foo",42,"bar"]`}, 28 | {core.EmptyDictionary, "{}"}, 29 | { 30 | core.NewDictionary([]core.KeyValue{{Key: core.Nil, Value: core.Nil}}), 31 | `{"null":null}`, 32 | }, 33 | { 34 | core.NewDictionary([]core.KeyValue{{Key: core.NewNumber(42), Value: core.NewNumber(42)}}), 35 | `{"42":42}`, 36 | }, 37 | { 38 | core.NewDictionary([]core.KeyValue{{Key: core.True, Value: core.True}}), 39 | `{"true":true}`, 40 | }, 41 | } { 42 | s, ok := core.EvalPure(core.PApp(encode, c.value)).(core.StringType) 43 | 44 | assert.True(t, ok) 45 | assert.Equal(t, c.answer, string(s)) 46 | } 47 | } 48 | 49 | func TestEncodeAndDecode(t *testing.T) { 50 | for _, th := range []core.Value{ 51 | core.True, 52 | core.False, 53 | core.NewNumber(123), 54 | core.NewNumber(-0.125), 55 | core.NewString("foo"), 56 | core.Nil, 57 | core.EmptyList, 58 | core.NewList( 59 | core.NewString("foo"), 60 | core.NewNumber(42), 61 | core.NewString("bar"), 62 | ), 63 | core.EmptyDictionary, 64 | core.NewDictionary([]core.KeyValue{ 65 | {Key: core.NewString("foo"), Value: core.NewNumber(42)}, 66 | {Key: core.NewString("bar"), Value: core.True}, 67 | {Key: core.NewString("baz"), Value: core.NewString("blah")}, 68 | }), 69 | } { 70 | s, ok := core.EvalPure(core.PApp(encode, th)).(core.StringType) 71 | 72 | assert.True(t, ok) 73 | 74 | b, ok := core.EvalPure(core.PApp(core.Equal, th, core.PApp(decode, s))).(*core.BooleanType) 75 | 76 | assert.True(t, ok) 77 | assert.True(t, bool(*b)) 78 | } 79 | } 80 | 81 | func TestEncodeWithInvalidArguments(t *testing.T) { 82 | for _, th := range []core.Value{ 83 | core.If, 84 | core.DummyError, 85 | core.PApp(core.Prepend, core.Nil, core.DummyError), 86 | core.NewList(core.DummyError), 87 | core.PApp(core.Insert, core.EmptyDictionary, core.NewString("foo"), core.DummyError), 88 | core.NewDictionary([]core.KeyValue{{Key: core.NewList(core.DummyError), Value: core.Nil}}), 89 | core.NewDictionary([]core.KeyValue{{Key: core.Nil, Value: core.DummyError}}), 90 | } { 91 | _, ok := core.EvalPure(core.PApp(encode, th)).(*core.ErrorType) 92 | assert.True(t, ok) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/lib/modules/json/error.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | func jsonError(err error) core.Value { 6 | return core.NewError("JSONError", err.Error()) 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/modules/json/module.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Module is a module in the language. 6 | var Module = map[string]core.Value{ 7 | "decode": decode, 8 | "encode": encode, 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/modules/modules.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/core" 5 | "github.com/cloe-lang/cloe/src/lib/modules/fs" 6 | "github.com/cloe-lang/cloe/src/lib/modules/http" 7 | "github.com/cloe-lang/cloe/src/lib/modules/json" 8 | "github.com/cloe-lang/cloe/src/lib/modules/os" 9 | "github.com/cloe-lang/cloe/src/lib/modules/random" 10 | "github.com/cloe-lang/cloe/src/lib/modules/re" 11 | ) 12 | 13 | // Modules is a set of built-in modules 14 | var Modules = map[string]map[string]core.Value{ 15 | "fs": fs.Module, 16 | "http": http.Module, 17 | "json": json.Module, 18 | "os": os.Module, 19 | "random": random.Module, 20 | "re": re.Module, 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/modules/os/exit.go: -------------------------------------------------------------------------------- 1 | package os 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var exit = createExitFunction(os.Exit) 10 | 11 | func createExitFunction(exit func(int)) core.Value { 12 | return core.NewEffectFunction( 13 | core.NewSignature( 14 | nil, "", 15 | []core.OptionalParameter{core.NewOptionalParameter("status", core.NewNumber(0))}, ""), 16 | func(vs ...core.Value) core.Value { 17 | n, err := core.EvalNumber(vs[0]) 18 | 19 | if err != nil { 20 | return err 21 | } 22 | 23 | exit(int(n)) 24 | 25 | return core.Nil 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/modules/os/exit_test.go: -------------------------------------------------------------------------------- 1 | package os 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestExit(t *testing.T) { 11 | s := 0 12 | f := createExitFunction(func(i int) { s = i }) 13 | 14 | for i, v := range []core.Value{ 15 | core.PApp(f), 16 | core.App(f, core.NewArguments(nil, []core.KeywordArgument{core.NewKeywordArgument("status", core.NewNumber(1))})), 17 | core.App(f, core.NewArguments(nil, []core.KeywordArgument{core.NewKeywordArgument("status", core.NewNumber(2))})), 18 | } { 19 | assert.Equal(t, core.Nil, core.EvalImpure(v)) 20 | assert.Equal(t, i, s) 21 | } 22 | } 23 | 24 | func TestExitError(t *testing.T) { 25 | f := createExitFunction(func(int) {}) 26 | 27 | for _, v := range []core.Value{ 28 | core.EvalPure(core.PApp(f)), 29 | core.EvalImpure(core.App(f, core.NewArguments(nil, []core.KeywordArgument{core.NewKeywordArgument("status", core.Nil)}))), 30 | } { 31 | _, ok := v.(*core.ErrorType) 32 | assert.True(t, ok) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/modules/os/module.go: -------------------------------------------------------------------------------- 1 | package os 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Module is a module in the language. 6 | var Module = map[string]core.Value{ 7 | "exit": exit, 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/modules/random/module.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Module is a module in the language. 6 | var Module = map[string]core.Value{ 7 | "number": number, 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/modules/random/number.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var number = core.NewLazyFunction( 10 | core.NewSignature(nil, "", nil, ""), 11 | func(vs ...core.Value) core.Value { 12 | return core.NewNumber(rand.Float64()) 13 | }) 14 | -------------------------------------------------------------------------------- /src/lib/modules/random/number_test.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNumber(t *testing.T) { 11 | for i := 0; i < 1000; i++ { 12 | n, err := core.EvalNumber(core.PApp(number)) 13 | assert.True(t, 0 <= n && n < 1) 14 | assert.Nil(t, err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/modules/re/error.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | func regexError(err error) core.Value { 6 | return core.NewError("RegexError", err.Error()) 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/modules/re/find.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var find = core.NewLazyFunction( 10 | core.NewSignature([]string{"pattern", "src"}, "", nil, ""), 11 | func(vs ...core.Value) core.Value { 12 | ss, e := evaluateStringArguments(vs) 13 | 14 | if e != nil { 15 | return e 16 | } 17 | 18 | r, err := regexp.Compile(ss[0]) 19 | 20 | if err != nil { 21 | return regexError(err) 22 | } 23 | 24 | ss = r.FindStringSubmatch(ss[1]) 25 | 26 | if len(ss) == 0 { 27 | return core.Nil 28 | } 29 | 30 | vs = make([]core.Value, 0, len(ss)) 31 | 32 | for _, s := range ss { 33 | v := core.Value(core.Nil) 34 | 35 | if s != "" { 36 | v = core.NewString(s) 37 | } 38 | 39 | vs = append(vs, v) 40 | } 41 | 42 | return core.NewList(vs...) 43 | }) 44 | -------------------------------------------------------------------------------- /src/lib/modules/re/find_test.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFind(t *testing.T) { 11 | for _, c := range []struct { 12 | pattern, src string 13 | answer core.Value 14 | }{ 15 | {"foo", "f", core.Nil}, 16 | {"foo", "foo", core.NewList(core.NewString("foo"))}, 17 | {"fo", "foo", core.NewList(core.NewString("fo"))}, 18 | {"f(o)*", "f", core.NewList(core.NewString("f"), core.Nil)}, 19 | {"f(o)*", "afoo", core.NewList(core.NewString("foo"), core.NewString("o"))}, 20 | } { 21 | th := core.PApp( 22 | find, 23 | core.NewString(c.pattern), 24 | core.NewString(c.src)) 25 | 26 | t.Log(core.EvalPure(core.PApp(core.Dump, th))) 27 | 28 | b, ok := core.EvalPure(core.PApp(core.Equal, th, c.answer)).(*core.BooleanType) 29 | 30 | assert.True(t, ok) 31 | assert.True(t, bool(*b)) 32 | } 33 | } 34 | 35 | func TestFindError(t *testing.T) { 36 | for _, v := range []core.Value{ 37 | core.PApp(find), 38 | core.PApp(find, core.NewString("foo")), 39 | core.PApp(find, core.NewString("foo"), core.Nil), 40 | core.PApp(find, core.NewString("(foo"), core.NewString("foo")), 41 | } { 42 | _, ok := core.EvalPure(v).(*core.ErrorType) 43 | assert.True(t, ok) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/modules/re/match.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var match = core.NewLazyFunction( 10 | core.NewSignature([]string{"pattern", "src"}, "", nil, ""), 11 | func(ts ...core.Value) core.Value { 12 | ss, e := evaluateStringArguments(ts) 13 | 14 | if e != nil { 15 | return e 16 | } 17 | 18 | b, err := regexp.MatchString(ss[0], ss[1]) 19 | 20 | if err != nil { 21 | return regexError(err) 22 | } 23 | 24 | return core.NewBoolean(b) 25 | }) 26 | -------------------------------------------------------------------------------- /src/lib/modules/re/match_test.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMatch(t *testing.T) { 11 | for _, c := range []struct { 12 | pattern, src string 13 | answer bool 14 | }{ 15 | {"foo", "foo", true}, 16 | {"fo", "foo", true}, 17 | {"foo", "fo", false}, 18 | {"f(o)*", "f", true}, 19 | {"f(o)*", "afoo", true}, 20 | } { 21 | b, ok := core.EvalPure(core.PApp( 22 | match, 23 | core.NewString(c.pattern), 24 | core.NewString(c.src))).(*core.BooleanType) 25 | 26 | assert.True(t, ok) 27 | assert.Equal(t, c.answer, bool(*b)) 28 | } 29 | } 30 | 31 | func TestMatchError(t *testing.T) { 32 | for _, v := range []core.Value{ 33 | core.PApp(match), 34 | core.PApp(match, core.NewString("foo")), 35 | core.PApp(match, core.NewString("foo"), core.Nil), 36 | core.PApp(match, core.NewString("(foo"), core.NewString("foo")), 37 | } { 38 | _, ok := core.EvalPure(v).(*core.ErrorType) 39 | assert.True(t, ok) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/modules/re/module.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | // Module is a module in the language. 6 | var Module = map[string]core.Value{ 7 | "find": find, 8 | "match": match, 9 | "replace": replace, 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/modules/re/replace.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | ) 8 | 9 | var replace = core.NewLazyFunction( 10 | core.NewSignature([]string{"pattern", "repl", "src"}, "", nil, ""), 11 | func(ts ...core.Value) core.Value { 12 | ss, e := evaluateStringArguments(ts) 13 | 14 | if e != nil { 15 | return e 16 | } 17 | 18 | r, err := regexp.Compile(ss[0]) 19 | 20 | if err != nil { 21 | return regexError(err) 22 | } 23 | 24 | return core.NewString(r.ReplaceAllString(ss[2], ss[1])) 25 | }) 26 | -------------------------------------------------------------------------------- /src/lib/modules/re/replace_test.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloe-lang/cloe/src/lib/core" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestReplace(t *testing.T) { 11 | for _, c := range []struct { 12 | pattern, repl, src, dest string 13 | }{ 14 | {"foo", "bar", "foo", "bar"}, 15 | {"f(.*)a", "b${1}r", "fooa", "boor"}, 16 | } { 17 | s, ok := core.EvalPure(core.PApp( 18 | replace, 19 | core.NewString(c.pattern), 20 | core.NewString(c.repl), 21 | core.NewString(c.src))).(core.StringType) 22 | 23 | assert.True(t, ok) 24 | assert.Equal(t, c.dest, string(s)) 25 | } 26 | } 27 | 28 | func TestReplaceError(t *testing.T) { 29 | for _, v := range []core.Value{ 30 | core.PApp(replace), 31 | core.PApp(replace, core.NewString("foo")), 32 | core.PApp(replace, core.NewString("foo"), core.Nil, core.NewString("bar")), 33 | core.PApp(replace, core.NewString("(foo"), core.NewString("foo"), core.NewString("foo")), 34 | } { 35 | _, ok := core.EvalPure(v).(*core.ErrorType) 36 | assert.True(t, ok) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/modules/re/util.go: -------------------------------------------------------------------------------- 1 | package re 2 | 3 | import "github.com/cloe-lang/cloe/src/lib/core" 4 | 5 | func evaluateStringArguments(vs []core.Value) ([]string, core.Value) { 6 | ss := make([]string, 0, len(vs)) 7 | 8 | for _, v := range vs { 9 | s, err := core.EvalString(v) 10 | 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | ss = append(ss, string(s)) 16 | } 17 | 18 | return ss, nil 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/parse/comb/parser.go: -------------------------------------------------------------------------------- 1 | package comb 2 | 3 | // Parser is a type of parsers as a function which returns a parsing result or 4 | // an error. 5 | type Parser func() (interface{}, error) 6 | -------------------------------------------------------------------------------- /src/lib/parse/comb/state.go: -------------------------------------------------------------------------------- 1 | package comb 2 | 3 | import "strings" 4 | 5 | // State represents a parser state. 6 | type State struct { 7 | source []rune 8 | lineNumber, linePosition, sourcePosition int 9 | } 10 | 11 | // NewState creates a parser state. 12 | func NewState(source string) *State { 13 | return &State{source: []rune(source)} 14 | } 15 | 16 | func (s State) exhausted() bool { 17 | return s.sourcePosition >= len(s.source) 18 | } 19 | 20 | func (s State) currentRune() rune { 21 | if s.exhausted() { 22 | return '\x00' 23 | } 24 | 25 | return s.source[s.sourcePosition] 26 | } 27 | 28 | func (s *State) increment() { 29 | if s.currentRune() == '\n' { 30 | s.lineNumber++ 31 | } 32 | 33 | s.sourcePosition++ 34 | } 35 | 36 | // LineNumber returns a current line number. 37 | func (s *State) LineNumber() int { 38 | return s.lineNumber + 1 39 | } 40 | 41 | // LinePosition returns a position in a current line. 42 | func (s State) LinePosition() int { 43 | return s.linePosition + 1 44 | } 45 | 46 | // Line returns a current line. 47 | func (s *State) Line() string { 48 | return strings.Split(string(s.source), "\n")[s.lineNumber] 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/parse/comb/state_test.go: -------------------------------------------------------------------------------- 1 | package comb 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestStateLine(t *testing.T) { 10 | assert.Equal(t, "a", NewState("a\nb\n").Line()) 11 | } 12 | 13 | func TestStateLineBeforeNewLine(t *testing.T) { 14 | s := NewState("a\nb\n") 15 | _, err := s.String("a")() 16 | assert.Equal(t, "a", s.Line()) 17 | assert.Nil(t, err) 18 | } 19 | 20 | func TestStateLineAfterNewLine(t *testing.T) { 21 | s := NewState("a\nb\n") 22 | _, err := s.String("a\n")() 23 | assert.Equal(t, "b", s.Line()) 24 | assert.Nil(t, err) 25 | } 26 | 27 | func TestStateLineNumber(t *testing.T) { 28 | assert.Equal(t, 1, NewState("").LineNumber()) 29 | } 30 | 31 | func TestStateLinePosition(t *testing.T) { 32 | assert.Equal(t, 1, NewState("").LinePosition()) 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/parse/state.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "github.com/cloe-lang/cloe/src/lib/debug" 5 | "github.com/cloe-lang/cloe/src/lib/parse/comb" 6 | ) 7 | 8 | type state struct { 9 | *comb.State 10 | file string 11 | } 12 | 13 | func newState(file, source string) *state { 14 | return &state{comb.NewState(source), file} 15 | } 16 | 17 | func (s state) debugInfo() *debug.Info { 18 | return debug.NewInfo(s.file, s.LineNumber(), s.LinePosition(), s.Line()) 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/run/run.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | 8 | "github.com/cloe-lang/cloe/src/lib/compile" 9 | "github.com/cloe-lang/cloe/src/lib/core" 10 | "github.com/cloe-lang/cloe/src/lib/systemt" 11 | ) 12 | 13 | const maxConcurrentEffects = 256 14 | 15 | var sem = make(chan bool, maxConcurrentEffects) 16 | 17 | // Run runs effects. 18 | func Run(os []compile.Effect) { 19 | go systemt.RunDaemons() 20 | 21 | wg := sync.WaitGroup{} 22 | 23 | for _, v := range os { 24 | wg.Add(1) 25 | 26 | if v.Expanded() { 27 | go evalEffectList(v.Value(), &wg, fail) 28 | } else { 29 | sem <- true 30 | go runEffect(v.Value(), &wg, fail) 31 | } 32 | } 33 | 34 | wg.Wait() 35 | } 36 | 37 | func evalEffectList(v core.Value, parent *sync.WaitGroup, fail func(error)) { 38 | wg := sync.WaitGroup{} 39 | defer func() { 40 | wg.Wait() 41 | parent.Done() 42 | }() 43 | 44 | for { 45 | if b, err := core.EvalBoolean(core.EvalPure(core.PApp(core.Equal, v, core.EmptyList))); err != nil { 46 | fail(err.(*core.ErrorType)) 47 | } else if b { 48 | break 49 | } 50 | 51 | wg.Add(1) 52 | sem <- true 53 | go runEffect(core.PApp(core.First, v), &wg, fail) 54 | 55 | v = core.PApp(core.Rest, v) 56 | } 57 | } 58 | 59 | func runEffect(v core.Value, wg *sync.WaitGroup, fail func(error)) { 60 | defer func() { 61 | <-sem 62 | wg.Done() 63 | }() 64 | 65 | if err, ok := core.EvalImpure(v).(*core.ErrorType); ok { 66 | fail(err) 67 | } 68 | } 69 | 70 | var fail = failWithExit(os.Exit) 71 | 72 | func failWithExit(exit func(int)) func(error) { 73 | return func(err error) { 74 | _, err = fmt.Fprint(os.Stderr, err) 75 | 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | exit(1) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/lib/run/run_test.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/cloe-lang/cloe/src/lib/builtins" 10 | "github.com/cloe-lang/cloe/src/lib/compile" 11 | "github.com/cloe-lang/cloe/src/lib/core" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestRunWithNoThunk(t *testing.T) { 16 | Run(nil) 17 | } 18 | 19 | func TestRunWithOneThunk(t *testing.T) { 20 | Run([]compile.Effect{compile.NewEffect(core.PApp(builtins.Print, core.NewNumber(42)), false)}) 21 | } 22 | 23 | func TestRunWithThunks(t *testing.T) { 24 | o := compile.NewEffect(core.PApp(builtins.Print, core.NewNumber(42)), false) 25 | Run([]compile.Effect{o, o, o, o, o, o, o, o}) 26 | } 27 | 28 | func TestRunWithExpandedList(t *testing.T) { 29 | Run([]compile.Effect{compile.NewEffect( 30 | core.NewList(core.PApp(builtins.Print, core.True), core.PApp(builtins.Print, core.False)), 31 | true)}) 32 | } 33 | 34 | func TestEvalEffectListFail(t *testing.T) { 35 | assert.Panics(t, func() { 36 | wg := sync.WaitGroup{} 37 | wg.Add(1) 38 | evalEffectList(core.DummyError, &wg, func(err error) { panic(err) }) 39 | }) 40 | } 41 | 42 | func TestRunEffectFail(t *testing.T) { 43 | assert.Panics(t, func() { 44 | wg := sync.WaitGroup{} 45 | sem <- true 46 | wg.Add(1) 47 | runEffect(core.Nil, &wg, func(err error) { panic(err) }) 48 | }) 49 | } 50 | 51 | func TestFail(t *testing.T) { 52 | s := 0 53 | 54 | fail := failWithExit(func(i int) { s = i }) 55 | 56 | fail(errors.New("foo")) 57 | 58 | assert.Equal(t, 1, s) 59 | } 60 | 61 | func TestFailPanic(t *testing.T) { 62 | assert.Panics(t, func() { 63 | os.Stderr.Close() 64 | fail := failWithExit(func(int) {}) 65 | fail(errors.New("foo")) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /src/lib/scalar/scalar.go: -------------------------------------------------------------------------------- 1 | package scalar 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/cloe-lang/cloe/src/lib/consts" 8 | "github.com/cloe-lang/cloe/src/lib/core" 9 | ) 10 | 11 | var predefined = func() map[string]core.Value { 12 | m := map[string]core.Value{ 13 | "true": core.True, 14 | "false": core.False, 15 | "nil": core.Nil, 16 | consts.Names.EmptyList: core.EmptyList, 17 | consts.Names.EmptyDictionary: core.EmptyDictionary, 18 | } 19 | 20 | for k, v := range m { 21 | if k[:1] != "$" { 22 | m["$"+k] = v 23 | } 24 | } 25 | 26 | return m 27 | }() 28 | 29 | // Convert converts a string into a scalar value of a number, string, bool, or 30 | // nil. 31 | func Convert(name string) (core.Value, error) { 32 | if t, ok := predefined[name]; ok { 33 | return t, nil 34 | } 35 | 36 | if n, err := strconv.ParseInt(name, 0, 64); err == nil && name[0] == '0' { 37 | return core.NewNumber(float64(n)), nil 38 | } 39 | 40 | if n, err := strconv.ParseFloat(name, 64); err == nil { 41 | return core.NewNumber(n), nil 42 | } 43 | 44 | if s, err := strconv.Unquote(name); err == nil { 45 | return core.NewString(s), nil 46 | } 47 | 48 | return nil, fmt.Errorf("the name, %s not found", name) 49 | } 50 | 51 | // Defined checks if a given name is a defined scalar value or not. 52 | func Defined(name string) bool { 53 | if _, err := Convert(name); err == nil { 54 | return true 55 | } 56 | 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /src/lib/systemt/daemon.go: -------------------------------------------------------------------------------- 1 | package systemt 2 | 3 | const daemonChannelCapacity = 1024 4 | 5 | var ds = make(chan func(), daemonChannelCapacity) 6 | 7 | // Daemonize daemonizes a function running it in a goroutine. 8 | func Daemonize(f func()) { 9 | ds <- f 10 | } 11 | 12 | // RunDaemons runs daemons. 13 | // The current implementation doesn't limit a number of spawn goroutines 14 | // because it can lead to dead lock. However, it should be done to keep 15 | // reference locality for cache in one way or another. 16 | func RunDaemons() { 17 | for d := range ds { 18 | go func(d func()) { 19 | d() 20 | }(d) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/systemt/daemon_test.go: -------------------------------------------------------------------------------- 1 | package systemt 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDaemonize(t *testing.T) { 11 | Daemonize(func() {}) 12 | assert.Equal(t, 1, len(ds)) 13 | } 14 | 15 | func TestRunDaemons(t *testing.T) { 16 | c := make(chan bool, 64) 17 | 18 | go RunDaemons() 19 | 20 | for i := 0; i < 10; i++ { 21 | j := i // To avoid data race of a loop variable. 22 | 23 | Daemonize(func() { 24 | if j%2 == 0 { 25 | c <- true 26 | } else { 27 | c <- false 28 | } 29 | }) 30 | } 31 | 32 | time.Sleep(100 * time.Millisecond) 33 | assert.Equal(t, 10, len(c)) 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // MkdirRecursively works just like mkdir -p. 9 | func MkdirRecursively(d string) error { 10 | if err := os.MkdirAll(d, 0700); err != nil && !os.IsExist(err) { 11 | return err 12 | } 13 | 14 | if i, err := os.Stat(d); err != nil { 15 | return err 16 | } else if !i.IsDir() { 17 | return fmt.Errorf("%s is not a directory", d) 18 | } 19 | 20 | return nil 21 | } 22 | --------------------------------------------------------------------------------