├── .github
└── workflows
│ └── rust.yml
├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE.md
├── README.md
├── REFERENCE.md
├── alkyne.sublime-syntax
├── rust-toolchain.toml
├── rustfmt.toml
└── src
├── eval
├── conformance
│ ├── blocks.ak
│ ├── bools.ak
│ ├── conditionals.ak
│ ├── fns.ak
│ ├── lists.ak
│ ├── loops.ak
│ ├── mod.rs
│ ├── must_pass.ak
│ ├── nulls.ak
│ ├── numbers.ak
│ ├── objects.ak
│ ├── strings.ak
│ └── variables.ak
├── encode
│ ├── alkyne.rs
│ ├── json.rs
│ └── mod.rs
├── error.rs
├── escaping.rs
├── mod.rs
├── stdlib.rs
└── value
│ ├── convert.rs
│ ├── fns.rs
│ ├── mod.rs
│ ├── native_macros.rs
│ ├── object.rs
│ └── seq.rs
├── exec
├── fs.rs
├── mod.rs
├── parallel.rs
└── repl.rs
├── main.rs
└── syn
├── alkyne.pest
├── mod.rs
└── parser.rs
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Rust
16 |
17 | on:
18 | push:
19 | branches: [ main ]
20 | pull_request:
21 | branches: [ main ]
22 |
23 | env:
24 | CARGO_TERM_COLOR: always
25 |
26 | jobs:
27 | check_lints:
28 | runs-on: ubuntu-latest
29 | steps:
30 | - uses: actions/checkout@v2
31 |
32 | - name: Check format
33 | run: cargo fmt -- --check --files-with-diff
34 | - name: Check clippy lints
35 | run: cargo clippy --all-targets --verbose
36 |
37 | build_and_test:
38 | runs-on: ubuntu-latest
39 | steps:
40 | - uses: actions/checkout@v2
41 |
42 | - name: Build with default settings
43 | run: cargo build -v
44 |
45 | - name: Build docs
46 | run: cargo doc --verbose
47 |
48 | - name: Run tests
49 | run: cargo test --verbose
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | *.rs.bk
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code Reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google/conduct/).
29 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | [package]
16 | name = "alkyne"
17 | version = "0.1.0"
18 | edition = "2018"
19 |
20 | authors = ["Miguel Young "]
21 | description = "A simple scripting language for generating JSON blobs"
22 | homepage = "https://github.com/mcy/alkyne"
23 | repository = "https://github.com/mcy/alkyne"
24 |
25 | license = "Apache-2.0"
26 |
27 | [dependencies]
28 | crossbeam = "0.7.2"
29 | chashmap = "2.2.2"
30 | pest = "2.1.2"
31 | pest_derive = "2.1.0"
32 | rustc_version = "0.2.3"
33 | rustyline = "5.0.5"
34 | termion = "1.1.1"
35 | toolshed = "0.8.1"
36 | thread_local = "1.0.1"
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Alkyne Language
2 |
3 | Alkyne is a non-Turing-complete scripting language that can be used for generating
4 | JSON-like blobs.
5 |
6 | See the [REFERENCE.md](REFERENCE.md) for more information on language semantics.
7 |
8 | ---
9 |
10 | This is not an officially supported Google product.
11 |
--------------------------------------------------------------------------------
/alkyne.sublime-syntax:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | %YAML 1.2
15 | ---
16 | name: Alkyne
17 | file_extensions: [ak]
18 | scope: source.alkyne
19 | contexts:
20 | main:
21 | # Double- and single-quoted strings.
22 | - match: '"'
23 | scope: punctuation.definition.string.double.begin.alkyne
24 | push: dstring
25 | - match: "'"
26 | scope: punctuation.definition.string.single.begin.alkyne
27 | push: sstring
28 |
29 | # Comments.
30 | - match: '//'
31 | scope: punctuation.definition.comment.alkyne
32 | push: line_comment
33 | - match: '/\*'
34 | scope: punctuation.definition.comment.alkyne
35 | push: block_comment
36 |
37 | # Syntactic control keywords.
38 | - match:
39 | '\b(break|continue|else|fn|for|if|in|let|return|switch|use|yield)\b'
40 | scope: keyword.control.alkyne
41 |
42 | # Simple operators.
43 | - match: '(!|%|&|\*|-|=|\+|<|>|\?|/)'
44 | scope: keyword.operator.alkyne
45 | # `oper` indentifiers.
46 | - match: '\boper(\+|-|\*|/|%|==|!)'
47 | scope: keyword.operator.alkyne
48 |
49 | # Constant keywords.
50 | - match: '\bnull\b'
51 | scope: constant.language.null.alkyne
52 |
53 | - match: '\bfalse\b'
54 | scope: constant.language.boolean.false.alkyne
55 | - match: '\btrue\b'
56 | scope: constant.language.boolean.true.alkyne
57 |
58 | - match: '\bself\b'
59 | scope: constant.language.self.alkyne
60 | - match: '\bsuper\b'
61 | scope: constant.language.super.alkyne
62 |
63 | - match: '\btop\b'
64 | scope: constant.language.top.alkyne
65 | - match: '\bhere\b'
66 | scope: constant.language.here.alkyne
67 | - match: '\bstd\b'
68 | scope: constant.language.std.alkyne
69 |
70 | # Numbers
71 | - match: '\b(-)?[0-9][0-9.]*\b'
72 | scope: constant.numeric.alkyne
73 |
74 | # Bracket matching.
75 | - match: '[(\[{]'
76 | scope: punctuation.definition.group.begin.alkyne
77 | push: bracket_match
78 | - match: '[)\]}]'
79 | scope: invalid.illegal.stray-bracket-end
80 |
81 | # Separators.
82 | - match: '(\.|,|:|;)'
83 | scope: punctuation.separator.alkyne
84 |
85 | # Function calls.
86 | - match: '(\w+)(?=\()'
87 | captures:
88 | 1: variable.function.alkyne
89 |
90 | bracket_match:
91 | - match: '[)\]}]'
92 | scope: punctuation.definition.group.end.alkyne
93 | pop: true
94 | - include: main
95 |
96 | string_escapes:
97 | - match: "\\[0ntrbf\\\"\']"
98 | scope: constant.character.escape.alkyne
99 | - match: '\\x[0-9a-fA-F]{2}'
100 | scope: constant.character.escape.alkyne
101 | - match: '\\u[0-9a-fA-F]{4}'
102 | scope: constant.character.escape.alkyne
103 | - match: '\\U[0-9a-fA-F]{8}'
104 | scope: constant.character.escape.alkyne
105 | - match: "\\[^0ntrbfxuU\\\"\']"
106 | scope: invalid.illegal.unknown-escape.alkyne
107 | string_format_specs:
108 | - match: '%[%dsqf]'
109 | scope: constant.character.other.alkyne
110 | dstring:
111 | - meta_scope: string.quoted.double.alkyne
112 | - include: string_escapes
113 | - match: '"'
114 | scope: punctuation.definition.string.double.end.alkyne
115 | pop: true
116 | sstring:
117 | - meta_scope: string.quoted.single.alkyne
118 | - include: string_escapes
119 | - match: "'"
120 | scope: punctuation.definition.string.single.end.alkyne
121 | pop: true
122 |
123 | line_comment:
124 | - meta_scope: comment.line.alkyne
125 | - match: $
126 | pop: true
127 | block_comment:
128 | - meta_scope: comment.block.alkyne
129 | - match: '/\*'
130 | scope: punctuation.definition.comment.alkyne
131 | push: block_comment
132 | - match: '\*/'
133 | scope: punctuation.definition.comment.alkyne
134 | pop: true
135 |
136 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | [toolchain]
16 | channel = "1.52.1"
17 | profile = "default"
18 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | max_width = 80
16 | tab_spaces = 2
17 |
--------------------------------------------------------------------------------
/src/eval/conformance/blocks.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that block expressions and scopes work correctly.
16 |
17 | use t = "test_lib.ak";
18 |
19 | let x = {5};
20 | t.expect(x == 5);
21 |
22 | let x = {
23 | let y = x;
24 | { let y = 11; }
25 | y * 2
26 | };
27 | t.expect(x == 10);
28 | t.expect_death(fn() y);
29 |
30 | let z = {
31 | let foo = 5;
32 | here
33 | };
34 | t.expect(z.foo == 5);
35 |
36 | let baz = {
37 | let z = 7;
38 | top.z.foo
39 | };
40 | t.expect(baz == 5);
41 |
42 | let none = {
43 | let x = 0;
44 | };
45 | t.expect(none == null);
46 |
47 | let obj = {{}};
48 | t.expect(obj != null);
--------------------------------------------------------------------------------
/src/eval/conformance/bools.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that all valid bool operations work as expected.
16 |
17 | use t = "test_lib.ak";
18 |
19 | t.expect(true == true);
20 | t.expect(false == false);
21 | t.expect(true != false);
22 | t.expect(false != true);
23 |
24 | t.expect(true == !false);
25 | t.expect(false == !true);
26 |
27 | t.expect((true || false) == true);
28 | t.expect((true && true) == true);
29 | t.expect((true && false) == false);
30 | t.expect((false || false) == false);
31 |
32 | t.expect((false && null) == false);
33 | t.expect((true || t.check(false)) == true);
34 | t.expect_death(fn() null && false);
35 |
--------------------------------------------------------------------------------
/src/eval/conformance/conditionals.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that conditional expressions and scopes work correctly.
16 |
17 | use t = "test_lib.ak";
18 |
19 | let x = if true { 42 };
20 | t.expect(x == 42);
21 |
22 | let y = if false { 42 };
23 | t.expect(y == null);
24 |
25 | let z = if false { 42 } else { 55 };
26 | t.expect(z == 55);
27 |
28 | if true { null } else {
29 | t.fail()
30 | }
31 |
32 | if true {
33 | null
34 | } else if t.fail() {
35 | null
36 | }
37 |
38 | let foo = switch "foo" {
39 | "bar" -> 0,
40 | "foo", "baz" -> 1,
41 | };
42 | t.expect(foo == 1);
43 |
44 | let bar = switch "bar" {
45 | "baz" -> 5,
46 | else -> 7,
47 | };
48 | t.expect(bar == 7);
49 |
50 | let baz = switch "baz" {
51 | "baz" -> 5,
52 | "baz" -> 6,
53 | "baz" -> t.fail(),
54 | };
55 |
56 | t.expect_death(fn() switch "foo" { 0 -> 0, });
57 |
--------------------------------------------------------------------------------
/src/eval/conformance/fns.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that all valid list operations work as expected.
16 |
17 | use t = "test_lib.ak";
18 |
19 | fn one() = 1;
20 | t.expect(one() == 1);
21 |
22 | fn plus_5() = one() + 5;
23 | t.expect(plus_5() == 6);
24 |
25 | fn make_scope(n) {
26 | let x = n;
27 | here
28 | }
29 | let scope1 = make_scope(4);
30 | let scope2 = make_scope(2);
31 | t.expect(scope1.x == 4);
32 | t.expect(scope2.x == 2);
33 |
34 | let x = 0;
35 | fn get_x() = x;
36 | t.expect(get_x() == 0);
37 | let x = 1;
38 | t.expect(get_x() == 1);
39 |
40 |
41 | fn forever() = forever;
42 | forever()()()()()()()()()()();
43 |
44 | fn recurse() = recurse();
45 | t.expect_death(fn() recurse());
46 |
47 | fn clever_recurse(f) = f(f);
48 | t.expect_death(fn() clever_recurse(clever_recurse));
49 |
50 | fn sum(a, b, c) = a + b + c;
51 |
52 | t.expect(sum(1, 2, 3) == 6);
53 | t.expect_death(fn() sum(1));
54 |
55 | fn maybe(cond) if cond {
56 | 5
57 | } else {
58 | t.fail()
59 | }
60 | t.expect(maybe(true) == 5);
61 |
62 | fn early_ret(cond) {
63 | if !cond { return 20 }
64 | t.fail()
65 | }
66 | early_ret(false);
67 |
68 | fn break_out() = break;
69 | fn cont_out() = continue;
70 |
71 | t.expect_death(fn() break_out());
72 | t.expect_death(fn() cont_out());
73 |
74 | let len = "foo" do { std.len(it) };
75 | t.expect(len == 3);
76 |
77 | fn do_ret() {
78 | 42 do { return it + 5 }
79 | }
80 | t.expect(do_ret() == 47);
81 |
82 | let x = 5 do? if it == 5 { "foo" } else { "bar" };
83 | t.expect(x == "foo");
84 |
85 | null do? { t.fail() };
86 |
87 | let obj = {
88 | bound: fn(x) self.member + x,
89 | member: 24,
90 | };
91 |
92 | t.expect(obj.bound(5) == 29);
93 | let bound = obj.bound;
94 | t.expect(obj.bound(7) == 31);
95 |
96 | fn unbound(x) = self.member - x;
97 | t.expect(std.bind(unbound, obj)(4) == 20);
98 |
--------------------------------------------------------------------------------
/src/eval/conformance/lists.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that all valid list operations work as expected.
16 |
17 | use t = "test_lib.ak";
18 |
19 | t.expect([1, 2, 3][0] == 1);
20 | t.expect([1, 2, 3][1, 3][0] == 2);
21 |
22 | t.expect_death(fn() [1, 2, 3][-1]);
23 | t.expect_death(fn() [1, 2, 3][5]);
24 | t.expect_death(fn() [1, 2, 3][0.9]);
25 | t.expect_death(fn() [1, 2, 3][0, 1.3]);
26 | t.expect_death(fn() [1, 2, 3][0, 0, 0]);
27 | t.expect_death(fn() [1, 2, 3][]);
28 | t.expect_death(fn() [][]);
29 |
30 | t.expect(std.len([1, 2, 3]) == 3);
31 | t.expect(std.len([]) == 0);
32 | t.expect(std.len([[[[[]]]]][0]) == 1);
33 |
34 | t.expect_death(fn() [] == []);
35 |
--------------------------------------------------------------------------------
/src/eval/conformance/loops.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that loops and comprehensions work as expected.
16 |
17 | use t = "test_lib.ak";
18 |
19 | let xs = for i in std.range(0, 5) yield i * 2;
20 | t.expect(xs[2] == 4);
21 | t.expect(xs[4] == 8);
22 |
23 | let xs_map = for i, v in xs yield [std.fmt('%d', [i])]: v;
24 | t.expect(xs_map."3" == 6);
25 |
26 | let obj = { abc: 0, xyz: 5 };
27 | let obj2 = for k, v, _ in obj {
28 | let key = k + k[0];
29 | yield [key]: k;
30 | };
31 | t.expect(obj2.xyzx == "xyz");
32 |
33 | let keys = for k, _, _ in obj { z: 4 } yield k;
34 | t.expect(std.len(keys) == 3);
35 |
36 | let chopped = for i, c in "hello!" {
37 | if c == "l" { continue }
38 | yield std.fmt('%d%s', [i, c]);
39 | };
40 | t.expect(std.concat(chopped) == "0h1e4o5!");
41 |
42 | let early = for _, v in ["a", "b"] {
43 | if v == "b" {
44 | break
45 | }
46 | yield [v]: v;
47 | };
48 | t.expect(early?.a == "a");
49 | t.expect(early?.b == null);
50 |
51 | let no_yield = for _ in std.range(0, 5) { 42 };
52 | t.expect(no_yield == null);
53 |
54 | for _ in std.range(0, 0) {
55 | t.fail()
56 | }
57 |
58 | fn return_in_loop(xs) for _, x in xs {
59 | if x == 10 {
60 | return 42
61 | }
62 | yield 0;
63 | }
64 |
65 | t.expect(return_in_loop([1, 2, 4, 10, 6]) == 42);
66 | t.expect(return_in_loop([1, 2, 3])[1] == 0);
--------------------------------------------------------------------------------
/src/eval/conformance/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Conformance tests for the interpreter.
16 | //!
17 | //! A conformance test consists of a Alkyne file; for each conformance conformance, the
18 | //! file is parsed, loaded into the interpreter, and executed.
19 | //!
20 | //! Tests should import the magic "test lib" file to get access to assertions.
21 | //! This can be done with
22 | //! ```
23 | //! use t = "test_lib.ak";
24 | //! ```
25 |
26 | use std::cell::Cell;
27 |
28 | use crate::eval;
29 | use crate::eval::value::Object;
30 | use crate::eval::value::Value;
31 | use crate::syn;
32 |
33 | const TEST_LIB: &str = "test_lib.ak";
34 |
35 | /// Macro for generating conformance tests.
36 | macro_rules! conf_test {
37 | ($test_name:ident) => {
38 | #[test]
39 | fn $test_name() {
40 | conformance_test(
41 | concat!(stringify!($test_name), ".ak"),
42 | include_str!(concat!(stringify!($test_name), ".ak")),
43 | )
44 | }
45 | };
46 | }
47 |
48 | thread_local! {
49 | static FAILURE_FLAG: Cell = Cell::new(false);
50 | }
51 |
52 | fn test_lib<'a>() -> Value<'a> {
53 | let test_lib = Object::new();
54 |
55 | test_lib.define(
56 | "fail",
57 | native_fn!((ctx) {
58 | FAILURE_FLAG.with(|f| f.set(true));
59 | native_err_no_return!(ctx, "unconditional failure")
60 | }),
61 | false,
62 | );
63 |
64 | test_lib.define(
65 | "expect",
66 | native_fn!((ctx, cond: bool) {
67 | if !cond {
68 | FAILURE_FLAG.with(|f| f.set(true));
69 | native_err_no_return!(ctx, "assertion failed")
70 | }
71 | }),
72 | false,
73 | );
74 |
75 | test_lib.define(
76 | "assert",
77 | native_fn!((ctx, cond: bool) {
78 | if !cond {
79 | native_err!(ctx, "assertion failed")
80 | }
81 | }),
82 | false,
83 | );
84 |
85 | test_lib.define(
86 | "expect_death",
87 | native_fn!((ctx, body: fn) {
88 | if ctx.call(native_span!(), body, vec![]).is_ok() {
89 | FAILURE_FLAG.with(|f| f.set(true));
90 | native_err_no_return!(ctx, "body failed to die")
91 | }
92 | }),
93 | false,
94 | );
95 |
96 | Value::Object(test_lib)
97 | }
98 |
99 | /// Basic fixture for all conformance conformance.
100 | fn conformance_test(name: &'static str, text: &'static str) {
101 | FAILURE_FLAG.with(|f| f.set(false));
102 |
103 | let arena = syn::Arena::new();
104 | let ast = match syn::parse(name.as_ref(), text, &arena) {
105 | Ok(ast) => ast,
106 | Err(e) => {
107 | eprintln!("{}", e);
108 | panic!("failed to parse testcase")
109 | }
110 | };
111 |
112 | let mut ctx = eval::Context::new();
113 | let result = ctx.eval(&ast, |file| match file {
114 | TEST_LIB => Some(test_lib()),
115 | _ => None,
116 | });
117 |
118 | if let Err(e) = result {
119 | eprintln!("{}", e);
120 | FAILURE_FLAG.with(|f| f.set(true));
121 | }
122 |
123 | if FAILURE_FLAG.with(|f| f.get()) {
124 | panic!("unexpected failure")
125 | }
126 | }
127 |
128 | conf_test!(must_pass);
129 |
130 | conf_test!(variables);
131 | conf_test!(fns);
132 | conf_test!(blocks);
133 | conf_test!(conditionals);
134 | conf_test!(loops);
135 |
136 | conf_test!(nulls);
137 | conf_test!(bools);
138 | conf_test!(numbers);
139 | conf_test!(strings);
140 | conf_test!(lists);
141 | conf_test!(objects);
142 |
--------------------------------------------------------------------------------
/src/eval/conformance/must_pass.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // This test is a sanity test for the testing system;
16 | // it does nothing interesting and should always pass.
17 |
18 | use t = "test_lib.ak";
19 |
20 | t.expect(true);
21 | t.assert(true);
22 | t.expect_death(fn() t.assert(false));
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/eval/conformance/nulls.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that all valid null-value operations work as expected.
16 |
17 | use t = "test_lib.ak";
18 |
19 | t.expect(null == null);
20 | t.expect(null != 0);
21 | t.expect(false != null);
22 | t.expect("null" != null);
23 | t.expect([null] != null);
24 | t.expect(null != {});
25 |
26 | t.expect(null ?? 0 == 0);
27 | t.expect(null ?? 1 ?? 0 == 1);
28 | t.expect("a" ?? "b" == "a");
29 |
--------------------------------------------------------------------------------
/src/eval/conformance/numbers.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that number operations work correctly.
16 | // We don't go too deeply into float testing, though.
17 |
18 | use t = "test_lib.ak";
19 |
20 | t.expect(0 == 0.0);
21 | t.expect(1 != 0);
22 |
23 | t.expect(1 + 1 == 2);
24 | t.expect(1 - 3 == -2);
25 | t.expect(5 * 3 == 15);
26 | t.expect(4 / 2 == 2);
27 | t.expect(5 / 2 == 2.5);
28 |
29 | t.expect(6 % 4 == 2);
30 | t.expect(-1 % 4 == 3);
31 |
32 | t.expect(1/0 == 2/0);
33 | t.expect(1/0 != (-2)/0);
34 |
35 | let nan = 0/0;
36 | t.expect(nan != nan);
37 |
38 | t.expect(0 < 1);
39 | t.expect(-1 > -3);
40 | t.expect(-4 <= -4);
41 | t.expect(!(-4 < -4));
42 |
--------------------------------------------------------------------------------
/src/eval/conformance/objects.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that all valid objct operations work as expected.
16 |
17 | use t = "test_lib.ak";
18 |
19 | let obj = {
20 | foo: 0,
21 | "bar": "abc",
22 | 'baz': [1, 2],
23 | ['fu' + 'nc']: fn() self['baz'],
24 | "self": fn() self,
25 | };
26 |
27 | t.expect(obj.foo == 0);
28 | t.expect(obj["bar"] == "abc");
29 | t.expect(obj.'baz'[1] == 2);
30 | t.expect(obj.func()[0] == 1);
31 | t.expect(obj.'self'()."self"()['foo'] == 0);
32 |
33 | t.expect_death(fn() obj.missing);
34 | t.expect_death(fn() obj['nope']);
35 |
36 |
37 | let proto = { a: 1, b: 2, c: 3 };
38 | let sub = proto { a: 2, d: 4 };
39 |
40 | t.expect(proto.a == 1);
41 | t.expect(sub.a == 2);
42 | t.expect(proto.c == 3);
43 | t.expect(sub.c == 3);
44 | t.expect_death(fn() proto.d);
45 | t.expect(sub.d == 4);
46 |
47 |
48 | let proto = { foo: { a: 1, b: 'c' } };
49 | let sub = proto { super.foo: { a: 'z' } };
50 |
51 | t.expect(proto.foo.a == 1);
52 | t.expect(sub.foo.a == 'z');
53 | t.expect(proto.foo.b == 'c');
54 | t.expect(sub.foo.b == 'c');
55 |
56 |
57 | let obj = { a: 'a', e: 5 };
58 | t.expect(obj?.a == 'a');
59 | t.expect(obj?.b == null);
60 | t.expect(obj?['a'] == 'a');
61 | t.expect(obj?['b'] == null);
62 | t.expect(obj?.a?.b == null);
63 |
64 | let sub = obj {
65 | a?: super?.b,
66 | b: super?.c,
67 | c?: 0,
68 | d?: null,
69 | e: null,
70 | };
71 |
72 | t.expect(sub?.a == 'a');
73 | t.expect(sub.a == 'a');
74 |
75 | t.expect(sub?.b == null);
76 | t.expect(sub.b == null);
77 |
78 | t.expect(sub?.c == 0);
79 | t.expect(sub.c == 0);
80 |
81 | t.expect(sub?.d == null);
82 | t.expect_death(fn() sub.d);
83 |
84 | t.expect(obj?.e == 5);
85 | t.expect(sub?.e == null);
86 | t.expect(sub.e == null);
87 |
88 | let obj = { a: {} };
89 |
90 | let sub = obj {
91 | super?.a: { x: 5 },
92 | super?.b: { x: 5 },
93 | };
94 | t.expect(sub?.a?.x == 5);
95 | t.expect(sub?.b?.x == null);
96 |
97 | let add = {
98 | val: 1,
99 | oper+: fn(that) self { val: self.val + that.val },
100 | };
101 |
102 | t.expect((add + add + add).val == 3);
103 |
104 | t.expect_death(fn() {} == {});
105 |
--------------------------------------------------------------------------------
/src/eval/conformance/strings.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that string operations work correctly.
16 |
17 | use t = "test_lib.ak";
18 |
19 | t.expect("abc" == "abc");
20 | t.expect("abc" != "abd");
21 | t.expect("\n" == "
22 | ");
23 | t.expect('"' == "\"");
24 | t.expect("'" == '\'');
25 | t.expect("\x20" == " ");
26 | t.expect("\u6587" == "文");
27 | t.expect("\U0001F914" == "🤔");
28 |
29 | t.expect("" == "");
30 | t.expect("abc" + "" == "abc");
31 | t.expect("ab" + "abc" == "ababc");
32 |
33 | t.expect("abcdef"[3] == "d");
34 | t.expect("abcdef"[2, 4] == "cd");
35 | t.expect("abcdef"[5, 5] == "");
36 | t.expect("abcdef"[0] == "a");
37 |
38 | t.expect_death(fn() "abc"[-1]);
39 | t.expect_death(fn() "abc"[5]);
40 | t.expect_death(fn() "abc"[0.9]);
41 | t.expect_death(fn() "abc"[0, 1.3]);
42 | t.expect_death(fn() "abc"[0, 0, 0]);
43 | t.expect_death(fn() "abc"[]);
44 |
45 | t.expect(std.len("") == 0);
46 | t.expect(std.len("abc") == 3);
--------------------------------------------------------------------------------
/src/eval/conformance/variables.ak:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Check that variable references and pattern matching work correctly.
16 |
17 | use t = "test_lib.ak";
18 |
19 | let x = 0;
20 | t.expect(x == 0);
21 |
22 | let y = 1;
23 | t.expect(x == 0);
24 | t.expect(y == 1);
25 |
26 | let x = 2;
27 | t.expect(x == 2);
28 | t.expect(y == 1);
29 |
30 | t.expect_death(fn() z);
31 | t.expect_death(fn() { let x = z; });
32 |
33 | let _ = 0;
34 | t.expect_death(fn() _);
35 |
36 | let null = null;
37 | let "let" = "let";
38 | t.expect_death(fn() { let "let" = "lett"; });
39 |
40 | let [] = [];
41 |
42 | let [a, b, _] = [1, 2, 3];
43 | t.expect(a == 1);
44 |
45 | let [x, y, ..] = [1, 2];
46 | t.expect(x == 1);
47 |
48 | let [x, y, ..] = [3, 4, 5];
49 | t.expect(x == 3);
50 |
51 | let [x, y, ..zs] = [6, 7, 8, 9];
52 | t.expect(x == 6);
53 | t.expect(zs[1] == 9);
54 |
55 | let [x, .., y] = ["10", "11", "12", "13"];
56 | t.expect(y == "13");
57 |
58 | let [c] = [0];
59 | t.expect(c == 0);
60 |
61 | t.expect_death(fn() { let [x, y] = 0; });
62 | t.expect_death(fn() { let [x, y] = [0]; });
63 | t.expect_death(fn() { let [x, x] = [0, 0]; });
64 |
65 | let {} = {};
66 | let {..} = {};
67 |
68 | let { x } = { x: 5 };
69 | t.expect(x == 5);
70 |
71 | let { no_match: _ } = { no_match: 20 };
72 | t.expect_death(fn() no_match);
73 |
74 | t.expect_death(fn() { let { x } = { x: 4, y: 5 }; });
75 |
76 | let { list: [first, ..] } = { list: [5, 4, 3] };
77 | t.expect(first == 5);
78 | t.expect_death(fn() list);
79 |
80 | let { maybe? } = { maybe: 5 };
81 | t.expect(maybe == 5);
82 |
83 | let { maybe? } = {};
84 | t.expect(maybe == null);
85 |
86 | let { x0, .. } = { x0: 7, x1: 77, x2: 777 };
87 | t.expect(x0 == 7);
88 | t.expect_death(fn() x2);
89 |
90 | let { "quoted key" } = { "quoted key": "q" };
91 | t.expect_death(fn() here."quoted key" );
92 |
93 | let { "quoted key": q } = { "quoted key": "q" };
94 | t.expect(q == "q");
95 |
96 | let { "quoted_id" } = { quoted_id: "qq" };
97 | t.expect(quoted_id == "qq");
--------------------------------------------------------------------------------
/src/eval/encode/alkyne.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Alkyne-like encoding for Alkyne values.
16 |
17 | use std::collections::HashSet;
18 |
19 | use crate::eval::encode::Context;
20 | use crate::eval::encode::Encode;
21 | use crate::eval::encode::EncodeError;
22 | use crate::eval::encode::PathComponent;
23 | use crate::eval::escaping;
24 | use crate::eval::value::DebugInfo;
25 | use crate::eval::value::Value;
26 |
27 | #[derive(Default)]
28 | pub struct UclEncoding(());
29 |
30 | impl<'i> Encode<'i> for UclEncoding {
31 | fn encode(
32 | mut ctx: Context,
33 | val: &Value<'i>,
34 | ) -> Result<(), EncodeError<'i>> {
35 | use std::fmt::Write;
36 | match val {
37 | Value::Null => {
38 | let _ = write!(ctx.buf(), "null");
39 | }
40 | Value::Bool(b) => {
41 | let _ = write!(ctx.buf(), "{}", b);
42 | }
43 | Value::Number(n) => {
44 | let _ = write!(ctx.buf(), "{}", n);
45 | }
46 | Value::String(s) => {
47 | escaping::escape_alkyne_string(s.as_ref(), false, ctx.buf());
48 | }
49 | Value::List(val) => {
50 | let _ = write!(ctx.buf(), "[");
51 | for (i, v) in val.iter().enumerate() {
52 | if i != 0 {
53 | let _ = write!(ctx.buf(), ", ");
54 | }
55 | match ctx.recurse(PathComponent::Index(i), v) {
56 | Ok(()) => {}
57 | Err(EncodeError::Cycle { .. }) => {
58 | let _ = write!(ctx.buf(), "");
59 | }
60 | e @ Err(_) => return e,
61 | }
62 | }
63 | let _ = write!(ctx.buf(), "]");
64 | }
65 | Value::Object(val) => {
66 | let mut first = true;
67 | let _ = write!(ctx.buf(), "{{");
68 | // Ensure that we only encode the *latest* version of a particular
69 | // key.
70 | let mut encoded_keys = HashSet::new();
71 | for (k, v, _) in val.iter() {
72 | if encoded_keys.contains(&*k) {
73 | continue;
74 | }
75 | encoded_keys.insert(&*k);
76 |
77 | if !first {
78 | let _ = write!(ctx.buf(), ", ");
79 | } else {
80 | let _ = write!(ctx.buf(), " ");
81 | }
82 | first = false;
83 |
84 | escaping::escape_alkyne_string(k, true, ctx.buf());
85 | let _ = write!(ctx.buf(), ": ");
86 | match ctx.recurse(PathComponent::Ident(k.to_string()), v) {
87 | Ok(()) => {}
88 | Err(EncodeError::Cycle { .. }) => {
89 | let _ = write!(ctx.buf(), "");
90 | }
91 | e @ Err(_) => return e,
92 | }
93 | }
94 | if !first {
95 | let _ = write!(ctx.buf(), " ");
96 | }
97 | let _ = write!(ctx.buf(), "}}");
98 | }
99 | Value::Fn(fnc) => {
100 | let _ = write!(ctx.buf(), "{}", fnc);
101 | }
102 | Value::NativeFn(fnc) => {
103 | let _ = write!(ctx.buf(), "{}", fnc);
104 | }
105 | Value::Std => {
106 | let _ = write!(ctx.buf(), "");
107 | }
108 | _ => {
109 | let _ = write!(ctx.buf(), "");
110 | }
111 | }
112 | Ok(())
113 | }
114 | }
115 |
116 | #[derive(Default)]
117 | pub struct VerboseEncoding {
118 | indent: usize,
119 | }
120 |
121 | impl<'i> Encode<'i> for VerboseEncoding {
122 | fn encode(
123 | mut ctx: Context,
124 | val: &Value<'i>,
125 | ) -> Result<(), EncodeError<'i>> {
126 | use std::fmt::Write;
127 | match val {
128 | Value::Null => {
129 | let _ = write!(ctx.buf(), "{}null", ().debug_info());
130 | }
131 | Value::Bool(b) => {
132 | let _ = write!(ctx.buf(), "{}{}", b.debug_info(), b);
133 | }
134 | Value::Number(n) => {
135 | let _ = write!(ctx.buf(), "{}{}", n.debug_info(), n);
136 | }
137 | Value::String(s) => {
138 | let _ = write!(ctx.buf(), "{}", s.debug_info());
139 | escaping::escape_alkyne_string(s.as_ref(), false, ctx.buf());
140 | }
141 | Value::List(val) => {
142 | let _ = write!(ctx.buf(), "{}", val.debug_info());
143 | let _ = write!(ctx.buf(), "[");
144 |
145 | let indent = " ".repeat(ctx.indent);
146 | ctx.indent += 1;
147 |
148 | for (i, v) in val.iter().enumerate() {
149 | if i == 0 {
150 | let _ = writeln!(ctx.buf());
151 | }
152 | let _ = write!(ctx.buf(), "{} [{}]: ", indent, i);
153 | match ctx.recurse(PathComponent::Index(i), v) {
154 | Ok(()) => {}
155 | Err(EncodeError::Cycle { .. }) => {
156 | let _ = write!(ctx.buf(), "");
157 | }
158 | e @ Err(_) => return e,
159 | }
160 |
161 | let _ = writeln!(ctx.buf(), ",");
162 | }
163 |
164 | if !val.is_empty() {
165 | let _ = write!(ctx.buf(), "{}", indent);
166 | }
167 |
168 | ctx.indent -= 1;
169 | let _ = write!(ctx.buf(), "]");
170 | }
171 | Value::Object(val) => {
172 | let _ = write!(ctx.buf(), "{}", val.debug_info());
173 | let _ = write!(ctx.buf(), "{{");
174 | let indent = " ".repeat(ctx.indent);
175 | ctx.indent += 1;
176 |
177 | let mut looped = false;
178 | for (k, v, o) in val.iter() {
179 | looped = true;
180 | let _ = writeln!(ctx.buf());
181 |
182 | let _ = write!(ctx.buf(), "{} key: {}", indent, k.debug_info());
183 | escaping::escape_alkyne_string(k, false, ctx.buf());
184 | let _ = writeln!(ctx.buf(), ",");
185 |
186 | let _ = write!(ctx.buf(), "{} val: ", indent);
187 | match ctx.recurse(PathComponent::Ident(k.to_string()), v) {
188 | Ok(()) => {}
189 | Err(EncodeError::Cycle { .. }) => {
190 | let _ = write!(ctx.buf(), "");
191 | }
192 | e @ Err(_) => return e,
193 | }
194 | let _ = writeln!(ctx.buf(), ",");
195 |
196 | let _ =
197 | writeln!(ctx.buf(), "{} own: {}{{..}},", indent, o.debug_info());
198 | }
199 |
200 | ctx.indent -= 1;
201 | if looped {
202 | let _ = write!(ctx.buf(), "{}", indent);
203 | }
204 | let _ = write!(ctx.buf(), "}}");
205 | }
206 | Value::Fn(fnc) => {
207 | let indent = " ".repeat(ctx.indent);
208 | let _ = writeln!(ctx.buf(), "<{}>{{", fnc);
209 | ctx.indent += 1;
210 |
211 | let _ = writeln!(ctx.buf(), "{} src: {:p},", indent, fnc.src());
212 |
213 | let _ = write!(ctx.buf(), "{} name: ", indent);
214 | match ctx.recurse(
215 | PathComponent::Ident("name".to_string()),
216 | &fnc.name().map(Value::String).unwrap_or(Value::Null),
217 | ) {
218 | Ok(()) => {}
219 | Err(EncodeError::Cycle { .. }) => {
220 | let _ = write!(ctx.buf(), "");
221 | }
222 | e @ Err(_) => return e,
223 | }
224 | let _ = writeln!(ctx.buf(), ",");
225 |
226 | let _ = write!(ctx.buf(), "{} self: ", indent);
227 | match ctx.recurse(
228 | PathComponent::Ident("self".to_string()),
229 | &fnc.referent().map(Value::Object).unwrap_or(Value::Null),
230 | ) {
231 | Ok(()) => {}
232 | Err(EncodeError::Cycle { .. }) => {
233 | let _ = write!(ctx.buf(), "");
234 | }
235 | e @ Err(_) => return e,
236 | }
237 | let _ = writeln!(ctx.buf(), ",");
238 |
239 | let _ = write!(ctx.buf(), "{} here: ", indent);
240 | match ctx.recurse(
241 | PathComponent::Ident("here".to_string()),
242 | &Value::Object(fnc.captures()),
243 | ) {
244 | Ok(()) => {}
245 | Err(EncodeError::Cycle { .. }) => {
246 | let _ = write!(ctx.buf(), "");
247 | }
248 | e @ Err(_) => return e,
249 | }
250 | let _ = writeln!(ctx.buf(), ",");
251 |
252 | ctx.indent -= 1;
253 | let _ = write!(ctx.buf(), "{}}}", indent);
254 | }
255 | Value::NativeFn(fnc) => {
256 | let indent = " ".repeat(ctx.indent);
257 | let _ = writeln!(ctx.buf(), "<{}>{{", fnc);
258 | ctx.indent += 1;
259 |
260 | let _ = writeln!(ctx.buf(), "{} src: {:#x},", indent, fnc.ptr_value());
261 |
262 | let _ = write!(ctx.buf(), "{} name: ", indent);
263 | match ctx.recurse(
264 | PathComponent::Ident("name".to_string()),
265 | &fnc.name().map(Value::String).unwrap_or(Value::Null),
266 | ) {
267 | Ok(()) => {}
268 | Err(EncodeError::Cycle { .. }) => {
269 | let _ = write!(ctx.buf(), "");
270 | }
271 | e @ Err(_) => return e,
272 | }
273 | let _ = writeln!(ctx.buf(), ",");
274 |
275 | ctx.indent -= 1;
276 | let _ = write!(ctx.buf(), "{}}}", indent);
277 | }
278 | Value::Std => {
279 | let _ = write!(ctx.buf(), "");
280 | }
281 | _ => {
282 | let _ = write!(ctx.buf(), "");
283 | }
284 | }
285 | Ok(())
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/src/eval/encode/json.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Json encoding for Alkyne values.
16 |
17 | use std::collections::HashSet;
18 |
19 | use crate::eval::encode::Context;
20 | use crate::eval::encode::Encode;
21 | use crate::eval::encode::EncodeError;
22 | use crate::eval::encode::PathComponent;
23 | use crate::eval::escaping;
24 | use crate::eval::value::Value;
25 |
26 | #[derive(Default)]
27 | pub struct JsonEncoding(());
28 |
29 | impl<'i> Encode<'i> for JsonEncoding {
30 | fn encode(
31 | mut ctx: Context,
32 | val: &Value<'i>,
33 | ) -> Result<(), EncodeError<'i>> {
34 | use std::fmt::Write;
35 | match val {
36 | Value::Null => {
37 | let _ = write!(ctx.buf(), "null");
38 | }
39 | Value::Bool(b) => {
40 | let _ = write!(ctx.buf(), "{}", b);
41 | }
42 | Value::Number(n) => {
43 | let _ = write!(ctx.buf(), "{}", n);
44 | }
45 | Value::String(s) => {
46 | escaping::escape_json_string(s.as_ref(), ctx.buf());
47 | }
48 | Value::List(val) => {
49 | let _ = write!(ctx.buf(), "[");
50 | for (i, v) in val.iter().enumerate() {
51 | if i != 0 {
52 | let _ = write!(ctx.buf(), ",");
53 | }
54 | ctx.recurse(PathComponent::Index(i), v)?
55 | }
56 | let _ = write!(ctx.buf(), "]");
57 | }
58 | Value::Object(val) => {
59 | let mut first = true;
60 | let _ = write!(ctx.buf(), "{{");
61 | // Ensure that we only encode the *latest* version of a particular
62 | // key.
63 | let mut encoded_keys = HashSet::new();
64 | for (k, v, _) in val.iter() {
65 | if encoded_keys.contains(&*k) {
66 | continue;
67 | }
68 | encoded_keys.insert(&*k);
69 |
70 | if !first {
71 | let _ = write!(ctx.buf(), ",");
72 | }
73 | first = false;
74 |
75 | escaping::escape_json_string(k, &mut ctx.buf());
76 | let _ = write!(ctx.buf(), ":");
77 | ctx.recurse(PathComponent::Ident(k.to_string()), v)?
78 | }
79 | let _ = write!(ctx.buf(), "}}");
80 | }
81 | v => {
82 | return Err(EncodeError::BadType {
83 | bad_type: v.ty(),
84 | path: ctx.path().to_vec(),
85 | })
86 | }
87 | }
88 | Ok(())
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/eval/encode/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Encoding functions for JSON.
16 |
17 | use std::collections::HashSet;
18 | use std::fmt;
19 | use std::ops::Deref;
20 | use std::ops::DerefMut;
21 |
22 | use crate::eval::error::EvalError;
23 | use crate::eval::escaping;
24 | use crate::eval::stdlib::ENCODE_AS;
25 | use crate::eval::value::Type;
26 | use crate::eval::value::Value;
27 | use crate::eval::Context as UclContext;
28 | use crate::eval::ControlFlow;
29 | use crate::syn::Span;
30 |
31 | pub mod alkyne;
32 | pub mod json;
33 |
34 | /// `Encode` represents an encoding of Alkyne values. Types which implement
35 | /// `Encode` can be used to create a textual representation of a Alkyne value.
36 | pub trait Encode<'i>: Sized {
37 | /// Convert `val` into text.
38 | ///
39 | /// The `Context` value can be used to access `self`, as well as to recurse
40 | /// further into the Alkyne value tree.
41 | fn encode(ctx: Context, val: &Value<'i>)
42 | -> Result<(), EncodeError<'i>>;
43 | }
44 |
45 | /// An `Encoder` gradually constructs a textual representation of a Alkyne value.
46 | ///
47 | /// The type parameter `E` represents the choice of encoding.
48 | pub struct Encoder {
49 | buf: String,
50 | cycle_detector: HashSet,
51 | path: Vec,
52 | encode: E,
53 | }
54 |
55 | /// A `Context` is an encoding context, granting limited access to the inner
56 | /// state of an `Encoder`.
57 | ///
58 | /// It is also a smart pointer over `E`, so fields and methods of `E` can be
59 | /// accessed directly in the `Encode::encode()` implementation.
60 | pub struct Context<'a, E>(&'a mut Encoder);
61 |
62 | impl<'i, 'a, E: Encode<'i>> Context<'a, E> {
63 | /// Returns the buffer to which text should be written to.
64 | pub fn buf(&mut self) -> &mut String {
65 | &mut self.0.buf
66 | }
67 |
68 | /// Returns the current path down the Alkyne tree.
69 | pub fn path(&self) -> &[PathComponent] {
70 | &self.0.path[..]
71 | }
72 |
73 | /// Recurse the encoding process, encoding `val` at the given `key`.
74 | ///
75 | /// This function should be called instead of doing manual recursion in
76 | /// `Encode::encode`, since it will automatically handle cycles and
77 | /// `std.EncodeAs` functions.
78 | pub fn recurse(
79 | &mut self,
80 | key: PathComponent,
81 | val: &Value<'i>,
82 | ) -> Result<(), EncodeError<'i>> {
83 | self.0.with_path(key, |this| this.subencode(val))
84 | }
85 | }
86 |
87 | impl Deref for Context<'_, E> {
88 | type Target = E;
89 |
90 | fn deref(&self) -> &E {
91 | &self.0.encode
92 | }
93 | }
94 |
95 | impl DerefMut for Context<'_, E> {
96 | fn deref_mut(&mut self) -> &mut E {
97 | &mut self.0.encode
98 | }
99 | }
100 |
101 | /// A `PathComponent` is used to track the path down a Alkyne value, consisting
102 | /// of the keys and indices of objects and lists.
103 | #[derive(Clone, Debug)]
104 | pub enum PathComponent {
105 | Index(usize),
106 | Ident(String),
107 | }
108 |
109 | impl<'i, E: Encode<'i> + Default> Encoder {
110 | /// Creates a new `Encoder`, with the given encoding.
111 | pub fn new() -> Self {
112 | Self::with(E::default())
113 | }
114 | }
115 |
116 | impl<'i, E: Encode<'i>> Encoder {
117 | /// Creates a new `Encoder` with the given encoding value.
118 | pub fn with(encode: E) -> Self {
119 | Encoder {
120 | buf: String::new(),
121 | cycle_detector: HashSet::new(),
122 | path: Vec::new(),
123 | encode,
124 | }
125 | }
126 |
127 | /// Encode the given value, consuming this encoder.
128 | pub fn encode(mut self, val: &Value<'i>) -> Result> {
129 | self.subencode(val)?;
130 | Ok(self.buf)
131 | }
132 |
133 | /// Call `f`, but with `ptr` marked as "visited". This function is used
134 | /// for cycle detection.
135 | #[inline]
136 | fn with_indirection(
137 | &mut self,
138 | ptr: usize,
139 | f: impl FnOnce(&mut Self) -> Result<(), EncodeError<'i>>,
140 | ) -> Result<(), EncodeError<'i>> {
141 | if self.cycle_detector.contains(&ptr) {
142 | return Err(EncodeError::Cycle {
143 | path: self.path.clone(),
144 | });
145 | }
146 | self.cycle_detector.insert(ptr);
147 | let res = f(self);
148 | self.cycle_detector.remove(&ptr);
149 | res
150 | }
151 |
152 | /// Call `f`, but with `path` pushed onto the path stack.
153 | #[inline]
154 | fn with_path(
155 | &mut self,
156 | path: PathComponent,
157 | f: impl FnOnce(&mut Self) -> Result<(), EncodeError<'i>>,
158 | ) -> Result<(), EncodeError<'i>> {
159 | self.path.push(path);
160 | let res = f(self);
161 | self.path.pop();
162 | res
163 | }
164 |
165 | /// Encode `v` by performing cycle detection and then calling into
166 | /// `Encode::encode()`.
167 | fn subencode(&mut self, v: &Value<'i>) -> Result<(), EncodeError<'i>> {
168 | match v {
169 | Value::Object(val) => {
170 | let ptr = val.ptr_value();
171 | self.with_indirection(ptr, |this| {
172 | if let Some(mut encode_as) = val.lookup(ENCODE_AS) {
173 | if let Value::Fn(fnc) = &mut encode_as {
174 | fnc.bind(val.clone());
175 | fnc.rename(ENCODE_AS.into());
176 | }
177 | let mut ctx = UclContext::new();
178 | return match ctx.call(
179 | Span::synthetic("encode.alkyne".as_ref()),
180 | encode_as,
181 | Vec::new(),
182 | ) {
183 | Ok(obj) => this.subencode(&obj),
184 | Err(ControlFlow::Error(e)) => Err(EncodeError::EvalError {
185 | eval_error: e,
186 | path: this.path.clone(),
187 | }),
188 | _ => unreachable!(),
189 | };
190 | }
191 | Encode::encode(Context(this), v)
192 | })
193 | }
194 | v => Encode::encode(Context(self), v),
195 | }
196 | }
197 | }
198 |
199 | /// An `EncodeError` is an error produced during encoding.
200 | #[derive(Clone, Debug)]
201 | pub enum EncodeError<'i> {
202 | /// Represents a cycle; obviously, cycles cannot be cleanly encoded without
203 | /// using infinite space.
204 | Cycle { path: Vec },
205 | /// Represents encountering a type which the given encoding does not support.
206 | BadType {
207 | bad_type: Type,
208 | path: Vec,
209 | },
210 | /// Represents an evaluation error that happened during encoding.
211 | EvalError {
212 | eval_error: EvalError<'i>,
213 | path: Vec,
214 | },
215 | }
216 |
217 | impl fmt::Display for EncodeError<'_> {
218 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219 | fn write_path(
220 | path: &[PathComponent],
221 | f: &mut fmt::Formatter,
222 | ) -> fmt::Result {
223 | if path.is_empty() {
224 | return write!(f, ".");
225 | }
226 |
227 | for p in path {
228 | match p {
229 | PathComponent::Index(n) => write!(f, "[{}]", n)?,
230 | PathComponent::Ident(i) => {
231 | let mut buf = String::new();
232 | escaping::escape_alkyne_string(i, true, &mut buf);
233 | write!(f, ".{}", buf)?
234 | }
235 | }
236 | }
237 |
238 | Ok(())
239 | }
240 |
241 | match self {
242 | EncodeError::Cycle { path } => {
243 | write!(f, "error: cycle detected at ")?;
244 | write_path(path, f)
245 | }
246 | EncodeError::BadType { bad_type, path } => {
247 | write!(f, "error: unsupported type {} at ", bad_type)?;
248 | write_path(path, f)
249 | }
250 | EncodeError::EvalError { eval_error, path } => {
251 | write!(f, "error: std.EncodeAs evaluation error at ")?;
252 | write_path(path, f)?;
253 | write!(f, "\n{}", eval_error)
254 | }
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/src/eval/error.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Error generation for Alkyne evalution.
16 |
17 | #![allow(dead_code)]
18 |
19 | use std::fmt;
20 |
21 | use crate::eval::value::Value;
22 | use crate::eval::Context;
23 | use crate::syn::Span;
24 |
25 | /// An `EvalError` is any error raised during evaluation; it records an error
26 | /// message and a stack trace.
27 | #[derive(Clone, Debug)]
28 | pub struct EvalError<'i> {
29 | pub message: String,
30 | pub trace: Vec>,
31 | }
32 |
33 | impl<'i> EvalError<'i> {
34 | /// Creates a new `EvalError`, recording an error at the given span with the
35 | /// given message.
36 | pub fn new(ctx: &Context<'i>, span: Span<'i>, message: String) -> Self {
37 | let height = ctx.call_stack.len();
38 | let mut trace = Vec::with_capacity(height);
39 |
40 | // Each stack frame (except the first) contains (fnc, caller_span).
41 | // We want to get (fnc, callee_span).
42 | for i in (0..ctx.call_stack.len()).rev() {
43 | let fn_name = if i == 0 {
44 | " ()".to_string()
45 | } else {
46 | match &ctx.call_stack[i].fnc {
47 | Value::Fn(f) => format!("{}", f),
48 | Value::NativeFn(f) => format!("{}", f),
49 | _ => " (??)".to_string(),
50 | }
51 | };
52 |
53 | let error_span = if i == height - 1 {
54 | span
55 | } else {
56 | ctx.call_stack[i + 1].call_site
57 | };
58 |
59 | trace.push(Frame {
60 | fn_name,
61 | error_span,
62 | })
63 | }
64 |
65 | EvalError { message, trace }
66 | }
67 |
68 | pub fn symbolize(&self, w: &mut impl fmt::Write) -> fmt::Result {
69 | writeln!(w, "error: {}", self.message)?;
70 | // Find the first "ok" frame.
71 | if self
72 | .trace
73 | .iter()
74 | .map(|f| f.error_span)
75 | .any(|s| !s.is_synthetic())
76 | {
77 | //span.pretty(w)?;
78 | }
79 | writeln!(w, "stack dump:")?;
80 |
81 | for frame in &self.trace {
82 | write!(w, "* {} at ", frame.fn_name)?;
83 | if frame.error_span.is_synthetic() {
84 | writeln!(w, "({})", frame.error_span.file_name().display())?;
85 | } else {
86 | let (line, col) = frame.error_span.start_position().unwrap();
87 | writeln!(
88 | w,
89 | "({}:{}:{})",
90 | frame.error_span.file_name().display(),
91 | line,
92 | col
93 | )?;
94 | }
95 | }
96 |
97 | Ok(())
98 | }
99 | }
100 |
101 | impl fmt::Display for EvalError<'_> {
102 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 | self.symbolize(f)
104 | }
105 | }
106 |
107 | /// A `Frame` is stack frame information relating to an evaluation error.
108 | #[derive(Clone, Debug)]
109 | pub struct Frame<'i> {
110 | fn_name: String,
111 | error_span: Span<'i>,
112 | }
113 |
114 | /// Generates, and returns, an `EvalError` given a `Context`, a `Spanned`, and
115 | /// a formatted message.
116 | macro_rules! error {
117 | ($ctx:expr, $expr:expr, $($tt:tt)*) => {{
118 | #[allow(unused_imports)]
119 | use $crate::syn::Spanned;
120 | #[allow(unused_imports)]
121 | use $crate::eval::error::*;
122 | return Err(EvalError::new($ctx, $expr.span(), format!($($tt)*)).into())
123 | }}
124 | }
125 |
126 | macro_rules! bug {
127 | ($($tt:tt)*) => {{
128 | eprintln!("error: internal interpreter error; this is a bug\nerror: ");
129 | eprintln!($($tt)*);
130 | panic!()
131 | }}
132 | }
133 |
--------------------------------------------------------------------------------
/src/eval/escaping.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Miscellaneous escaping functions.
16 |
17 | use std::fmt;
18 |
19 | /// The error returned by `unescape_utf8_literal()`.
20 | #[derive(Clone, Copy, Debug)]
21 | pub enum UnescapeError {
22 | UnexpectedEof,
23 | UnknownEscape(usize, char),
24 | BadHexDigit(usize, char),
25 | BadUnicode(usize, u32),
26 | }
27 |
28 | impl fmt::Display for UnescapeError {
29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30 | match self {
31 | UnescapeError::UnexpectedEof => write!(f, "input ended during escape"),
32 | UnescapeError::UnknownEscape(_, c) => {
33 | write!(f, "unknown escape sequence: '\\{}'", c)
34 | }
35 | UnescapeError::BadHexDigit(_, c) => {
36 | write!(f, "bad hex digit while parsing escape: '{}'", c)
37 | }
38 | UnescapeError::BadUnicode(_, x) => {
39 | write!(f, "bad Unicode codepoint generated by escape: {:#x}", x)
40 | }
41 | }
42 | }
43 | }
44 |
45 | /// Unescapes the string `s`.
46 | ///
47 | /// This function recognizes the following escape sequences:
48 | /// - `\0`, a NUL character.
49 | /// - `\n`, a newline.
50 | /// - `\t`, a tab character.
51 | /// - `\r`, a carriage return.
52 | /// - `\xNN`, a hex-encoded ASCII character.
53 | /// - `\uNNNN`, a hex-encoded Unicode codepoint in the Basic Multilingual
54 | /// Plane.
55 | /// - `\UNNNNNNNN`, any hex-encoded Unicode codepoint.
56 | pub fn unescape_utf8_literal(s: &str) -> Result {
57 | let mut buf = String::with_capacity(s.len());
58 | let mut chars = s.char_indices();
59 | while let Some((_, c)) = chars.next() {
60 | if c != '\\' {
61 | buf.push(c);
62 | continue;
63 | }
64 |
65 | let (escape_idx, escape) = match chars.next() {
66 | Some(c) => c,
67 | None => return Err(UnescapeError::UnexpectedEof),
68 | };
69 |
70 | fn parse_hex(
71 | chars: &mut impl Iterator- ,
72 | digits: usize,
73 | ) -> Result {
74 | fn hex_digit(c: char) -> Option {
75 | let nybble = match c {
76 | '0'..='9' => (c as u8) - b'0',
77 | 'a'..='f' => (c as u8) - b'a' + 10,
78 | 'A'..='F' => (c as u8) - b'A' + 10,
79 | _ => return None,
80 | };
81 | Some(nybble as u32)
82 | }
83 |
84 | let mut total: u32 = 0;
85 |
86 | for _ in 0..digits {
87 | let nybble = match chars.next().map(|(i, c)| (i, c, hex_digit(c))) {
88 | Some((_, _, Some(n))) => n,
89 | Some((idx, c, _)) => return Err(UnescapeError::BadHexDigit(idx, c)),
90 | None => return Err(UnescapeError::UnexpectedEof),
91 | };
92 |
93 | total <<= 4;
94 | total |= nybble;
95 | }
96 |
97 | Ok(total)
98 | }
99 |
100 | let unescaped: char = match escape {
101 | '"' | '\'' | '\\' => escape,
102 | '0' => '\0',
103 | 'n' => '\n',
104 | 't' => '\t',
105 | 'r' => '\r',
106 | 'b' => '\x08',
107 | 'f' => '\x0c',
108 | 'x' => {
109 | let codepoint = parse_hex(&mut chars, 2)?;
110 | match std::char::from_u32(codepoint) {
111 | Some(c) => c,
112 | None => return Err(UnescapeError::BadUnicode(escape_idx, codepoint)),
113 | }
114 | }
115 | 'u' => {
116 | let codepoint = parse_hex(&mut chars, 4)?;
117 | match std::char::from_u32(codepoint) {
118 | Some(c) => c,
119 | None => return Err(UnescapeError::BadUnicode(escape_idx, codepoint)),
120 | }
121 | }
122 | 'U' => {
123 | let codepoint = parse_hex(&mut chars, 8)?;
124 | match std::char::from_u32(codepoint) {
125 | Some(c) => c,
126 | None => return Err(UnescapeError::BadUnicode(escape_idx, codepoint)),
127 | }
128 | }
129 | _ => return Err(UnescapeError::UnknownEscape(escape_idx, escape)),
130 | };
131 |
132 | buf.push(unescaped)
133 | }
134 |
135 | Ok(buf)
136 | }
137 |
138 | /// Checks whether `s` is a valid Alkyne identifier, including `_`.
139 | pub fn is_identifier(s: &str) -> bool {
140 | for (i, c) in s.chars().enumerate() {
141 | match (i, c) {
142 | (0, '0'..='9') => return false,
143 | (_, '0'..='9') | (_, 'a'..='z') | (_, 'A'..='Z') | (_, '_') => {}
144 | _ => return false,
145 | }
146 | }
147 | true
148 | }
149 |
150 | /// Escapes the string `s` according to Alkyne syntax.
151 | ///
152 | /// The quotation marks are included, unless `maybe_id` is true and the string
153 | /// is a valid Alkyne identifier other than `_`.
154 | pub fn escape_alkyne_string(s: &str, maybe_id: bool, buf: &mut String) {
155 | if maybe_id && s != "_" && is_identifier(s) {
156 | buf.push_str(s);
157 | return;
158 | }
159 |
160 | let use_single_quotes = s.contains('"') && !s.contains('\'');
161 | if use_single_quotes {
162 | buf.push('\'');
163 | } else {
164 | buf.push('"');
165 | }
166 |
167 | for c in s.chars() {
168 | use std::fmt::Write;
169 | match c {
170 | '\x00' => buf.push_str("\\0"),
171 | '\x08' => buf.push_str("\\b"),
172 | // Tab.
173 | '\t' => buf.push_str("\\t"),
174 | '\n' => buf.push_str("\\n"),
175 | // Form feed.
176 | '\x0c' => buf.push_str("\\f"),
177 | '\r' => buf.push_str("\\r"),
178 | '"' if !use_single_quotes => buf.push_str("\\\""),
179 | '\'' if use_single_quotes => buf.push_str("\\'"),
180 | '\\' => buf.push_str("\\\\"),
181 | c if !c.is_control() => buf.push(c),
182 | c if (c as u32) < 256 => {
183 | let _ = write!(buf, "\\x{:02x}", c as u32);
184 | }
185 | c if (c as u32) < 65536 => {
186 | let _ = write!(buf, "\\u{:04x}", c as u32);
187 | }
188 | c => {
189 | let _ = write!(buf, "\\U{:08x}", c as u32);
190 | }
191 | }
192 | }
193 |
194 | if use_single_quotes {
195 | buf.push('\'');
196 | } else {
197 | buf.push('"');
198 | }
199 | }
200 |
201 | /// Escapes the string `s` according to JSON syntax.
202 | ///
203 | /// The quotation marks are included.
204 | pub fn escape_json_string(s: &str, buf: &mut String) {
205 | buf.push('"');
206 | for c in s.encode_utf16() {
207 | match c {
208 | // Backspace.
209 | 0x08 => buf.push_str("\\b"),
210 | // Tab.
211 | 0x09 => buf.push_str("\\t"),
212 | // Newline.
213 | 0x0a => buf.push_str("\\n"),
214 | // Form feed.
215 | 0x0c => buf.push_str("\\f"),
216 | // Carriage return.
217 | 0x0d => buf.push_str("\\r"),
218 | // Double quote.
219 | 0x22 => buf.push_str("\\\""),
220 | // Backslash.
221 | 0x5c => buf.push_str("\\\\"),
222 | // Printable ASCII.
223 | 0x20..=0x7e => buf.push(c as u8 as char),
224 | // Everything else, as \uXXXX.
225 | _ => {
226 | use std::fmt::Write;
227 | let _ = write!(buf, "\\u{:04x}", c);
228 | }
229 | }
230 | }
231 | buf.push('"');
232 | }
233 |
--------------------------------------------------------------------------------
/src/eval/stdlib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Native code implementations of the Alkyne standard library.
16 |
17 | use std::cmp::Ordering;
18 |
19 | use crate::eval::value::Object;
20 | use crate::eval::value::Type;
21 | use crate::eval::value::Value;
22 | use crate::syn::Spanned;
23 |
24 | pub const ENCODE_AS: &str = "__encode_as";
25 |
26 | /// Macro for generating the giant stdlib switch-case below.
27 | macro_rules! stdlib {
28 | ($key:expr => $(
29 | fn $fn_name:ident ($($args:tt)*) $body:block
30 | $(let $let_name:ident = $expr:expr;)*
31 | )*) => {
32 | match $key {$(
33 | stringify!($fn_name) => stdlib!(@fn $fn_name ($($args)*) $body),
34 | $(stringify!($let_name) => stdlib!(@let $let_name = $expr;),)*
35 | )* _ => None, }
36 | };
37 |
38 | (@fn $ident:ident($($args:tt)*) $body:block) => {{
39 | let mut fnc = native_fn!(($($args)*) $body);
40 | fnc.rename($crate::eval::value::Str::new_static(stringify!($ident)));
41 | Some(fnc.into())
42 | }};
43 |
44 | (@let $ident:ident = $expr:expr;) => {Some($expr.into())};
45 | }
46 |
47 | /// Looks up `key` in the Alkyne standard library.
48 | ///
49 | /// Returns `None` if `key` is undefined.
50 | pub fn lookup_std<'i>(key: &str) -> Option> {
51 | stdlib! { key =>
52 | // -- General reflection utilities. --
53 |
54 | // Returns the name of the file currently being executed. Note
55 | // that this information is derived from the stack, so functions will only
56 | // ever see the name of the file they were defined in.
57 | fn file_name(ctx) {
58 | let prev_frame = &ctx.call_stack[ctx.call_stack.len() - 2];
59 | match &prev_frame.fnc {
60 | Value::Fn(fnc) => fnc.src().span().file_name().to_str().unwrap_or(""),
61 | _ => "",
62 | }
63 | }
64 |
65 | // EncodeAs represents an implementation-defined object key. If this key is
66 | // set on an object, at encoding time, instead of encoding the object
67 | // directly, this key will be looked up, called with no arguments, and the
68 | // return value will be encoded, instead.
69 | let EncodeAs = ENCODE_AS;
70 |
71 | // Returns an implementation-defined string representing the type of `x`.
72 | fn type_of(x: any) { x.ty().name() }
73 |
74 | // Returns whether `x` has a JSON encoding value, i.e., any
75 | // of null, bool, number, string, list, or object.
76 | fn is_json(x: any) {
77 | matches!(x.ty(),
78 | Type::Null | Type::Bool | Type::Number |
79 | Type::String | Type::List | Type::Object)
80 | }
81 |
82 | // Returns whether `x` is of type null.
83 | fn is_null(x: any) { x.ty() == Type::Null }
84 |
85 | // Returns whether `x` is of type bool.
86 | fn is_bool(x: any) { x.ty() == Type::Bool }
87 |
88 | // Returns whether `x` is of type number.
89 | fn is_number(x: any) { x.ty() == Type::Number }
90 |
91 | // Returns whether `x` is of type string.
92 | fn is_string(x: any) { x.ty() == Type::String }
93 |
94 | // Returns whether `x` is of type list.
95 | fn is_list(x: any) { x.ty() == Type::List }
96 |
97 | // Returns whether `x` is of type object.
98 | fn is_object(x: any) { x.ty() == Type::Object }
99 |
100 | // Returns whether `x` is of type function.
101 | fn is_fn(x: any) { x.ty() == Type::Fn }
102 |
103 | // Returns whether `x` has a non-opaque type
104 | fn has_type(x: any) { x.ty() != Type::Opaque }
105 |
106 | // Compares `a` and `b` for "pointer equality". What this means is
107 | // implementation-defined, but if `same(a, b)` holds, then so must
108 | // `a == b` if they are primitives. Furthermore, for JSON-like types,
109 | // `same(x, x)` always holds.
110 | fn same(a: any, b: any) {
111 | match (a, b) {
112 | (Value::Null, Value::Null) => true,
113 | (Value::Bool(a), Value::Bool(b)) => a == b,
114 | #[allow(clippy::float_cmp)]
115 | (Value::Number(a), Value::Number(b)) => a == b,
116 | (Value::String(a), Value::String(b)) => a.ptr_eq(&b),
117 | (Value::List(a), Value::List(b)) => a.ptr_eq(&b),
118 | (Value::Object(a), Value::Object(b)) => a.ptr_value() == b.ptr_value(),
119 | _ => false,
120 | }
121 | }
122 |
123 | // Binds `zelf` as the referent of `fnc`, so that it is the object
124 | // produced by `self` when `fnc` is called. Returns the re-bound `fnc`.
125 | fn bind(ctx, fnc: any, zelf: object) {
126 | match fnc {
127 | Value::Fn(mut fnc) => {
128 | fnc.bind(zelf);
129 | Value::Fn(fnc)
130 | }
131 | f @ Value::NativeFn(..) => f,
132 | t => native_err!(ctx, "expected function, got {}", t),
133 | }
134 | }
135 |
136 | // Constants for the various type names.
137 | let Null = Type::Null.name();
138 | let Bool = Type::Bool.name();
139 | let Number = Type::Number.name();
140 | let String = Type::String.name();
141 | let List = Type::List.name();
142 | let Object = Type::Object.name();
143 | let Function = Type::Fn.name();
144 |
145 | // -- General functions. --
146 | // Triggers an error when `cond` is not satisfied, printing
147 | // `msg`. Otherwise, it returns `null`.
148 | fn assert(ctx, cond: bool, msg: string) {
149 | if !cond {
150 | native_err!(ctx, "assertion failed: {}", msg)
151 | }
152 | }
153 |
154 | // Just like `assert`, but does not display an error message.
155 | fn check(ctx, cond: bool) {
156 | if !cond {
157 | native_err!(ctx, "assertion failed")
158 | }
159 | }
160 |
161 | // Unconditionally returns an error with message `msg`.
162 | fn error(ctx, msg: string) { native_err!(ctx, "{}", msg) }
163 |
164 | // Returns true if `c[idx]` would succeed, i.e., if `c?[idx]` is nonnull.
165 | fn has(ctx, container: any, index: any) {
166 | ctx.index(native_span!(), container, vec![index]).is_ok()
167 | }
168 |
169 | // Returns a range with step one, form `start` to `end`, exclusive.
170 | // The arguments to this function *must* be integers.
171 | fn range(start: integer, end: integer) {
172 | Value::Range { start, end, step: 1 }
173 | }
174 |
175 | // Returns a range with step one, form `start` to `end`,
176 | // exclusive, with step `step`. The arguments to this function *must* be
177 | // integers; `step` must also be positive.
178 | fn range_step(ctx, start: integer, end: integer, step: integer) {
179 | if step <= 0 {
180 | native_err!(ctx, "expected positive step; got {}", step)
181 | }
182 |
183 | Value::Range { start, end, step }
184 | }
185 |
186 | // Returns the first index at which `needle` appears in `haystack`.
187 | // This function will execute a comparision against each element of
188 | // `haystack`; comparing incomparable values will error. If the element is
189 | // not found, returns null.
190 | fn index_of(ctx, haystack: list, needle: any) {
191 | for (i, val) in haystack.iter().enumerate() {
192 | if ctx.compare_eq(native_span!(), val.clone(), needle.clone())? {
193 | return Ok(Value::Number(i as f64))
194 | }
195 | }
196 | Value::Null
197 | }
198 |
199 | // Returns the number of elements in the list `xs` or the number of
200 | // bytes in the string `xs`, and returns an error in all other cases.
201 | fn len(ctx, x: any) {
202 | let len = match x {
203 | Value::String(s) => s.len(),
204 | Value::List(xs) => xs.len(),
205 | v => native_err!(ctx, "cannot take length of value of type {}", v.ty()),
206 | };
207 | len as f64
208 | }
209 |
210 | // Takes a list of lists, and flattens it into a single list.
211 | fn flatten(ctx, xss: list) {
212 | let mut buf = Vec::new();
213 | for v in xss.iter() {
214 | match v {
215 | Value::List(xs) => buf.extend_from_slice(&*xs),
216 | v => native_err!(ctx, "expected list of lists, got {}", v.ty()),
217 | }
218 | }
219 | buf
220 | }
221 |
222 | // Concatenates a list of strings into one string.
223 | fn concat(ctx, strs: list) {
224 | let mut buf = String::new();
225 | for v in strs.iter() {
226 | match v {
227 | Value::String(s) => buf.push_str(&*s),
228 | v => native_err!(ctx, "expected list of strings, got {}", v.ty()),
229 | }
230 | }
231 | buf
232 | }
233 |
234 | // Takes a format string, and a list of arguments, and printf-formats them.
235 | // Supported verbs are:
236 | // - %%: a literal % character.
237 | // - %s: a string, as-is.
238 | // - %q: a string, but quoted and escaped.
239 | // - %d: an integer.
240 | // - %f: a number.
241 | //
242 | // All other cases will scribble some error message on the resulting
243 | // string, but this function itself will always succeed.
244 | fn fmt(fmt_str: string, args: list) {
245 | use std::fmt::Write;
246 | let mut buf = String::with_capacity(fmt_str.len());
247 |
248 | let mut arg_idx = 0;
249 | let mut chars = fmt_str.chars().peekable();
250 | while let Some(c) = chars.next() {
251 | match (c, chars.peek(), args.get(arg_idx)) {
252 | ('%', Some('%'), _) => buf.push('%'),
253 |
254 | ('%', Some('s'), Some(Value::String(val))) => {
255 | arg_idx += 1;
256 | let _ = write!(buf, "{}", val.as_sliced());
257 | }
258 |
259 | ('%', Some('q'), Some(Value::String(val))) => {
260 | arg_idx += 1;
261 | let _ = write!(buf, "{}", val);
262 | }
263 |
264 | ('%', Some('d'), Some(val)) if val.as_int().is_ok() => {
265 | arg_idx += 1;
266 | let _ = write!(buf, "{}", val.as_int().unwrap());
267 | }
268 |
269 | ('%', Some('f'), Some(Value::Number(val))) => {
270 | arg_idx += 1;
271 | let _ = write!(buf, "{}", val);
272 | }
273 |
274 | ('%', Some(c), Some(v)) => {
275 | arg_idx += 1;
276 | let _ = write!(buf, "%{}", c, v.ty());
277 | }
278 |
279 | ('%', Some(c), None) => {
280 | arg_idx += 1;
281 | let _ = write!(buf, "%{}", c,);
282 | }
283 |
284 | ('%', None, _) => {
285 | let _ = write!(buf, "%");
286 | }
287 |
288 | (c, _, _) => buf.push(c),
289 | }
290 |
291 | if c == '%' {
292 | let _ = chars.next();
293 | }
294 | }
295 | if arg_idx < args.len() {
296 | let _ = write!(buf, "%", args.len() - arg_idx);
297 | }
298 | buf
299 | }
300 |
301 | // Merges the objects `a` and `b`, resulting in a super-less object with all
302 | // of the transitive fields of `a` and `b`.
303 | fn merge(a: object, b: object) {
304 | let new_obj = Object::new();
305 |
306 | for (k, v, _) in a.iter() {
307 | new_obj.define(k.clone(), v.clone(), true);
308 | }
309 |
310 | for (k, v, _) in b.iter() {
311 | new_obj.define(k.clone(), v.clone(), true);
312 | }
313 |
314 | new_obj.freeze();
315 | new_obj
316 | }
317 |
318 | // -- Math stuff. --
319 | // All mathematical functions return an indeterminate value when called
320 | // with values outside of their domain.
321 |
322 | // Computes the minimum of `a` and `b`, if they are comparable.
323 | fn min(ctx, a: any, b: any) {
324 | match ctx.compare_ord(native_span!(), a.clone(), b.clone())? {
325 | Some(Ordering::Greater) => b,
326 | _ => a,
327 | }
328 | }
329 |
330 | // Computes the maximum of `a` and `b`, if they are comparable.
331 | fn max(ctx, a: any, b: any) {
332 | match ctx.compare_ord(native_span!(), a.clone(), b.clone())? {
333 | Some(Ordering::Less) => b,
334 | _ => a,
335 | }
336 | }
337 |
338 | // Archimedes' constant to some precision.
339 | let Pi = std::f64::consts::PI;
340 | // Euler's constant to some precision.
341 | let Euler = std::f64::consts::E;
342 |
343 | // Computes the absolute value of `x`.
344 | fn abs(x: number) { x.abs() }
345 |
346 | // Computes the square root of `x`.
347 | fn sqrt(x: number) { x.sqrt() }
348 |
349 | // Computes the exponential of `x`.
350 | fn exp(x: number) { x.exp() }
351 | // Computes the natural logarithm of `x`.
352 | fn ln(x: number) { x.ln() }
353 |
354 | // Computes `x` raised to the `e`th power.
355 | fn pow(x: number, e: number) { x.powf(e) }
356 | // Computes the logarithm of `x` at base `b`.
357 | fn log(x: number, b: number) { x.log(b) }
358 |
359 | // Converts the angle `t` to degrees (as if it were in radians).
360 | fn deg(x: number) { x.to_degrees() }
361 | // Converts the angle `t` to radians (as if it were in degrees).
362 | fn rad(x: number) { x.to_radians() }
363 |
364 | // Computes the sine of `x`.
365 | fn sin(x: number) { x.sin() }
366 | // Computes the cosine of `x`.
367 | fn cos(x: number) { x.cos() }
368 | // Computes the tangent of `x`.
369 | fn tan(x: number) { x.tan() }
370 |
371 | // Computes the cosecant of `x`.
372 | fn csc(x: number) { x.sin().recip() }
373 | // Computes the secant of `x`.
374 | fn sec(x: number) { x.cos().recip() }
375 | // Computes the cotangent of `x`.
376 | fn cot(x: number) { x.tan().recip() }
377 |
378 | // Computes the arcsine of `x`.
379 | fn asin(x: number) { x.asin() }
380 | // Computes the arccosine of `x`.
381 | fn acos(x: number) { x.acos() }
382 | // Computes the arctangent of `x`.
383 | fn atan(x: number) { x.atan() }
384 |
385 | // Computes the arccosecant of `x`.
386 | fn acsc(x: number) { x.recip().asin() }
387 | // Computes the arcsecant of `x`.
388 | fn asec(x: number) { x.recip().acos() }
389 | // Computes the arccotangent of `x`.
390 | fn acot(x: number) { x.recip().atan() }
391 |
392 | // Computes the hyperbolic sine of `x`.
393 | fn sinh(x: number) { x.sinh() }
394 | // Computes the hyperbolic cosine of `x`.
395 | fn cosh(x: number) { x.cosh() }
396 | // Computes the hyperbolic tangent of `x`.
397 | fn tanh(x: number) { x.tanh() }
398 |
399 | // Computes the hyperbolic cosecant of `x`.
400 | fn csch(x: number) { x.sinh().recip() }
401 | // Computes the hyperbolic secant of `x`.
402 | fn sech(x: number) { x.cosh().recip() }
403 | // Computes the hyperbolic cotangent of `x`.
404 | fn coth(x: number) { x.tanh().recip() }
405 |
406 | // Computes the hyperbolic arcsine of `x`.
407 | fn asinh(x: number) { x.asinh() }
408 | // Computes the hyperbolic arccosine of `x`.
409 | fn acosh(x: number) { x.acosh() }
410 | // Computes the hyperbolic arctangent of `x`.
411 | fn atanh(x: number) { x.atanh() }
412 |
413 | // Computes the hyperbolic arccosecant of `x`.
414 | fn acsch(x: number) { x.recip().asinh() }
415 | // Computes the hyperbolic arcsecant of `x`.
416 | fn asech(x: number) { x.recip().acosh() }
417 | // Computes the hyperbolic arccotangent of `x`.
418 | fn acoth(x: number) { x.recip().atanh() }
419 | }
420 | }
421 |
--------------------------------------------------------------------------------
/src/eval/value/convert.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Traits for converting Rust types into Alkyne values.
16 |
17 | use crate::eval::value::Fn;
18 | use crate::eval::value::List;
19 | use crate::eval::value::NativeFn;
20 | use crate::eval::value::Object;
21 | use crate::eval::value::Str;
22 | use crate::eval::value::Value;
23 | use crate::eval::Result;
24 |
25 | /// Represents a type that can be converted into a `Value`.
26 | ///
27 | /// This trait mostly exists to be different from `Into` to avoid coherence
28 | /// issues, and `Into::into()` should generally be preferred instead.
29 | pub trait IntoValue<'i> {
30 | fn into_value(self) -> Value<'i>;
31 | }
32 |
33 | impl<'i, T: IntoValue<'i>> From for Value<'i> {
34 | #[inline]
35 | fn from(x: T) -> Self {
36 | x.into_value()
37 | }
38 | }
39 |
40 | impl<'i> IntoValue<'i> for () {
41 | fn into_value(self) -> Value<'i> {
42 | Value::Null
43 | }
44 | }
45 |
46 | impl<'i> IntoValue<'i> for bool {
47 | fn into_value(self) -> Value<'i> {
48 | Value::Bool(self)
49 | }
50 | }
51 |
52 | impl<'i> IntoValue<'i> for f64 {
53 | fn into_value(self) -> Value<'i> {
54 | Value::Number(self)
55 | }
56 | }
57 |
58 | impl<'i> IntoValue<'i> for String {
59 | fn into_value(self) -> Value<'i> {
60 | Value::String(self.into())
61 | }
62 | }
63 |
64 | impl<'i> IntoValue<'i> for &'i str {
65 | fn into_value(self) -> Value<'i> {
66 | Value::String(Str::new_static(self))
67 | }
68 | }
69 |
70 | impl<'i> IntoValue<'i> for Str<'i> {
71 | fn into_value(self) -> Value<'i> {
72 | Value::String(self)
73 | }
74 | }
75 |
76 | impl<'i> IntoValue<'i> for Vec> {
77 | fn into_value(self) -> Value<'i> {
78 | Value::List(self.into())
79 | }
80 | }
81 |
82 | impl<'i> IntoValue<'i> for &'i [Value<'i>] {
83 | fn into_value(self) -> Value<'i> {
84 | Value::List(List::new_static(self))
85 | }
86 | }
87 |
88 | impl<'i> IntoValue<'i> for List<'i> {
89 | fn into_value(self) -> Value<'i> {
90 | Value::List(self)
91 | }
92 | }
93 |
94 | impl<'i> IntoValue<'i> for Object<'i> {
95 | fn into_value(self) -> Value<'i> {
96 | Value::Object(self)
97 | }
98 | }
99 |
100 | impl<'i> IntoValue<'i> for Fn<'i> {
101 | fn into_value(self) -> Value<'i> {
102 | Value::Fn(self)
103 | }
104 | }
105 |
106 | impl<'i> IntoValue<'i> for NativeFn<'i> {
107 | fn into_value(self) -> Value<'i> {
108 | Value::NativeFn(self)
109 | }
110 | }
111 |
112 | /// Represents a type that can be converted into a `Result`.
113 | ///
114 | /// This trait mostly exists to be different from `Into` to avoid coherence
115 | /// issues. It is exclusively used by `native_fn!()`.
116 | #[doc(hidden)]
117 | pub trait IntoValueResult<'i> {
118 | fn into_value_result(self) -> Result<'i, Value<'i>>;
119 | }
120 |
121 | impl<'i, I> IntoValueResult<'i> for I
122 | where
123 | I: IntoValue<'i>,
124 | {
125 | fn into_value_result(self) -> Result<'i, Value<'i>> {
126 | Ok(self.into_value())
127 | }
128 | }
129 |
130 | impl<'i> IntoValueResult<'i> for Value<'i> {
131 | fn into_value_result(self) -> Result<'i, Value<'i>> {
132 | Ok(self)
133 | }
134 | }
135 |
136 | impl<'i> IntoValueResult<'i> for Result<'i, Value<'i>> {
137 | fn into_value_result(self) -> Result<'i, Value<'i>> {
138 | self
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/eval/value/fns.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Alkyne Functions
16 |
17 | use std::fmt::Debug;
18 | use std::fmt::Display;
19 |
20 | use crate::eval::escaping;
21 | use crate::eval::value::Object;
22 | use crate::eval::value::Str;
23 | use crate::eval::value::Value;
24 | use crate::eval::Context;
25 | use crate::eval::Result;
26 | use crate::syn;
27 | use crate::syn::Spanned;
28 |
29 | /// A `Fn` is a Alkyne function. It is implemented as a capture of the scope it
30 | /// was defined in, and a copy of its AST for later evaluation.
31 | #[derive(Clone, Debug)]
32 | pub struct Fn<'i> {
33 | src: &'i syn::Fn<'i>,
34 | name: Option>,
35 | captures: Object<'i>,
36 | referent: Option