├── .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 | [](https://github.com/cloe-lang/cloe/actions)
4 | [](https://coveralls.io/github/cloe-lang/cloe)
5 | [](https://goreportcard.com/report/github.com/cloe-lang/cloe)
6 | [](https://opensource.org/licenses/MIT)
7 |
8 |
9 |

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