From d86d3b2224a64edf4cfdb4d2fa73adae852b7efe Mon Sep 17 00:00:00 2001 From: Luca Sepe Date: Fri, 4 Dec 2020 14:20:44 +0100 Subject: [PATCH] first commit --- .gitignore | 37 + .goreleaser.yml | 50 ++ CHANGELOG.md | 2 + LICENSE | 354 ++++++++++ README.md | 312 +++++++++ REPL.png | Bin 0 -> 13315 bytes ast/ast.go | 624 +++++++++++++++++ ast/ast_test.go | 34 + builtins/builtins.go | 84 +++ builtins/stdlib_canvas.go | 656 ++++++++++++++++++ builtins/stdlib_core.go | 306 +++++++++ builtins/stdlib_math.go | 229 +++++++ canvas/canvas.go | 197 ++++++ eval/eval.go | 870 ++++++++++++++++++++++++ eval/eval_test.go | 933 +++++++++++++++++++++++++ examples/arrays.g2d | 12 + examples/conditionals.g2d | 27 + examples/crisp.g2d | 46 ++ examples/crisp.png | Bin 0 -> 1869 bytes examples/ellipse.g2d | 17 + examples/ellipse.png | Bin 0 -> 20308 bytes examples/functions.g2d | 45 ++ examples/hashes.g2d | 31 + examples/lines.g2d | 33 + examples/lines.png | Bin 0 -> 192206 bytes examples/sierpinsky.g2d | 65 ++ examples/sierpinsky.png | Bin 0 -> 4447 bytes examples/snowflake.g2d | 69 ++ examples/snowflake.png | Bin 0 -> 12807 bytes examples/spiral.g2d | 26 + examples/spiral.png | Bin 0 -> 34428 bytes examples/star.g2d | 38 ++ examples/star.png | Bin 0 -> 12422 bytes go.mod | 10 + go.sum | 18 + lexer/lexer.go | 331 +++++++++ lexer/lexer_test.go | 198 ++++++ main.go | 88 +++ object/array.go | 117 ++++ object/bool.go | 50 ++ object/builtin.go | 38 ++ object/environment.go | 71 ++ object/error.go | 33 + object/float.go | 44 ++ object/function.go | 96 +++ object/hash.go | 125 ++++ object/int.go | 50 ++ object/module.go | 37 + object/null.go | 31 + object/object.go | 92 +++ object/object_test.go | 24 + object/screen.go | 25 + object/state.go | 15 + object/str.go | 54 ++ parser/parser.go | 778 +++++++++++++++++++++ parser/parser_test.go | 1354 +++++++++++++++++++++++++++++++++++++ parser/parser_tracing.go | 32 + repl/repl.go | 167 +++++ token/token.go | 178 +++++ token/token_test.go | 24 + typing/typing.go | 115 ++++ utils/utils.go | 53 ++ 62 files changed, 9345 insertions(+) create mode 100644 .gitignore create mode 100644 .goreleaser.yml create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 REPL.png create mode 100644 ast/ast.go create mode 100644 ast/ast_test.go create mode 100644 builtins/builtins.go create mode 100644 builtins/stdlib_canvas.go create mode 100644 builtins/stdlib_core.go create mode 100644 builtins/stdlib_math.go create mode 100644 canvas/canvas.go create mode 100644 eval/eval.go create mode 100644 eval/eval_test.go create mode 100644 examples/arrays.g2d create mode 100644 examples/conditionals.g2d create mode 100644 examples/crisp.g2d create mode 100644 examples/crisp.png create mode 100644 examples/ellipse.g2d create mode 100644 examples/ellipse.png create mode 100644 examples/functions.g2d create mode 100644 examples/hashes.g2d create mode 100644 examples/lines.g2d create mode 100644 examples/lines.png create mode 100644 examples/sierpinsky.g2d create mode 100644 examples/sierpinsky.png create mode 100644 examples/snowflake.g2d create mode 100644 examples/snowflake.png create mode 100644 examples/spiral.g2d create mode 100644 examples/spiral.png create mode 100644 examples/star.g2d create mode 100644 examples/star.png create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lexer/lexer.go create mode 100644 lexer/lexer_test.go create mode 100644 main.go create mode 100644 object/array.go create mode 100644 object/bool.go create mode 100644 object/builtin.go create mode 100644 object/environment.go create mode 100644 object/error.go create mode 100644 object/float.go create mode 100644 object/function.go create mode 100644 object/hash.go create mode 100644 object/int.go create mode 100644 object/module.go create mode 100644 object/null.go create mode 100644 object/object.go create mode 100644 object/object_test.go create mode 100644 object/screen.go create mode 100644 object/state.go create mode 100644 object/str.go create mode 100644 parser/parser.go create mode 100644 parser/parser_test.go create mode 100644 parser/parser_tracing.go create mode 100644 repl/repl.go create mode 100644 token/token.go create mode 100644 token/token_test.go create mode 100644 typing/typing.go create mode 100644 utils/utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..145d0be --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +## Intellij +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/encodings.xml +.idea/**/compiler.xml +.idea/**/misc.xml +.idea/**/modules.xml +.idea/**/vcs.xml + +## VSCode +.vscode/ + +## File-based project format: +*.iws +*.iml +.idea/ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.dat +*.DS_Store + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Goreleaser builds +**/dist/** + +# This is my wip ideas folder +experiments/** diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..5a3b58d --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,50 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +# Run locally with: goreleaser --rm-dist --snapshot --skip-publish +project_name: g2d +before: + hooks: + - go mod tidy + - go mod download +builds: +- binary: '{{ .ProjectName }}' + main: ./main.go + env: + - CGO_ENABLED=0 + ldflags: + - -s -w -X main.Version={{.Version}} -X main.GitCommit={{.Commit}} + - -a -extldflags "-static" + goos: + - windows + - linux + - darwin + goarch: + - amd64 +archives: +- replacements: + darwin: macOS + windows: win + amd64: 64-bit +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .ProjectName }}_{{ .Tag }}" +nfpms: + - + package_name: g2d + vendor: Luca Sepe + homepage: https://lucasepe.it/ + maintainer: Luca Sepe + description: g2D Programming Language - Create beatiful drawings using an adhoc interpreted language. + license: MIT + replacements: + amd64: 64-bit + formats: + - deb + - rpm +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f744227 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.5.0 (Unreleased) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c33dcc7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,354 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..27d36b0 --- /dev/null +++ b/README.md @@ -0,0 +1,312 @@ +# `g2D` Programming Language + +**`g2D`** is an educational programming language conceived to: + +- teach concepts of programming to those who approach the art of coding for the first time +- quick prototyping [canvas rendering 2D contexts](https://developer.mozilla.org/it/docs/Web/API/CanvasRenderingContext2D) - `g2D` builtins api are really similiar to HTML5 Canvas API +- having fun with geometry! It's really easy to play with the scripts and viewing the resulting geometries + +You can create also animated geometries (thanks to the `snapshot` builtin function). + +**`g2D`** is an [interpreted language](https://en.wikipedia.org/wiki/Interpreted_language) and the interpreter is crafted in Go: + +- single binary file +- runs on Linux, MacOS and Windows + +To execute a g2d-script simply pass the name to the interpreter: + +```bash +$ g2d ./example/lines.g2d +``` + +If no script is passed to the interpreter it will open a [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) shell. + +![](./REPL.png) + + +For those who want to see immediately how the language of **`g2D`** feels, here are [some examples](./examples). + +![](./examples/spiral.png)   ![](./examples/star.png)   ![](./examples/snowflake.png) + +![](./examples/sierpinsky.png)   ![](./examples/lines.png)   ![](./examples/crisp.png) + +![](./examples/ellipse.png)   + + +## The `g2D` Programming Language Syntax + +Example-programs can be found beneath [examples/](examples/) which demonstrate these things, as well as parts of the standard-library. + +### Types + +g2D has the following data types: `null`, `bool`, `int`, `float`, `str`, `array`, `hash`, and `fn`. + +Type | Syntax | Notes | +--------- | ----------------------------------------- | --------------------------------------------- | +null | `null` | | +bool | `true false` | | +int | `0 42 1234 -5` | is a signed 64-bit integer | +float | `0.5 4.2 1.234 -5.5` | is a 64-bit double-precision floating point | +str | `"" "foo" "\"quotes\" and a\nline break"` | are immutable arrays of bytes | +array | `[] [1, 2] [1, 2, 3]` | grow-able arrays (*use the `push()` builtin*) | +hash | `{} {"a": 1} {"a": 1, "b": 2}` | are unordered hash maps | + + +### Bindings + +Variables are bounded using the `:=` operator. + +```go +a := 3 +b := 1.2 +``` + +Variables may be integers, floats, strings, or arrays/hashes. + +To update a variable you can simply specify the equals `=` operator: + +```go +a := 3 // Binding +a = a + 5 // Updating +``` + +### Arithmetic operations + +`g2D` supports all the basic arithmetic operation of `int` and `float` types. + +```go +a := 5 +b := 3 + +c := a + b +d := c / 2 +e := d * d +``` + +### Builtin containers + +`cursor` contains two builtin containers: `array` and `hash`. + +#### Arrays + +An array is a list which organizes items by linear sequence. Arrays can hold multiple types. + +```go +a := [1, 2.3, "hello!"] +b := [false, true, "Hello World", 3, 3.13] +``` + +Adding to an array is done via the `push` builtin function: + +```go +a = push(a, "another") +``` + +You can iterate over the contents of an array like so: + +```go +i := 0 +while( i < len(a) ) { + print( "Array index ", i, " contains ", a[i], "\n") + i = i + 1 +} +``` + +With the definition we included that produces this output: + +```text +Array index 0 contains 1 +Array index 1 contains 2.3 +Array index 2 contains hello! +Array index 3 contains another +``` + +#### Hashes + +A hash is a key/value container, but note that keys may only be of type `boolean`, `int` and `string`. + +```go +a := { + "name":"monkey", + true:1, + 7:"seven", + "amount": 7.5 +} + +print(a, "\n") // Outputs: {name: monkey, true: 1, 7: seven, amount: 7.5} +print(a["amount"], "\n") // Outputs: 7.4 + +// Updating... +a[7] = "sette" +print(a[7], "\n") // Outputs: sette +``` + +You can iterate over the keys in a hash via the `keys` builtin function: + +```go +// Fetch all the keys +ids := keys(a) +// ..and iterate +i := 0 +while (i < len(ids)) { + k := ids[i] + print(i, " Key: ", k, " has Val: ", a[k], "\n") + i = i + 1 +} +``` + +You can delete keys via `delete` (returns an updated value rather than changing it in-place): + +```go +// Delete keys via `delete` +a = delete(a, 8) +a = delete(a, "name") +print(a, "\n") // Outputs: {7: sette, amount: 7.5, true: 1} +``` + +### Functions + +`g2D` uses `fn` to define a function which will be assigned to a variable for naming/invocation purposes: + +```go +sum := fn(a, b) { return a + b } + +print(sum(5,3), "\n") // Outputs: 8 +print(sum(2.5,7.5), "\n") // Outputs: 10 +``` + +Functions can be passed as values to others functions: + +```go +addTwo := fn(a, b, f) { + return 2 + f(a, b) +} + +tot := addTwo(68, 1, sum) +print(tot, "\n") // Outputs: 71 +``` + +Functions inside functions + +```go +multiplier := fn(q) { + return fn(x) { + return x*q + } +} + +multThree := multiplier(3) + +print(multThree(2), "\n") // Outputs: 6 +print(multThree(3), "\n") // Outputs: 9 +print(multThree(4), "\n") // Outputs: 12 +``` + +### If-else statements + +`g2D` supports `if-else` statements. + +```go +max := fn(a, b) { + if (a > b) { + return a; + } else { + return b; + } +} + +print( max(1, 2) ) // Outputs: 2 +``` + +### Switch Statements + +`g2D` supports the `switch` and `case` expressions: + +```go +switch n := randi(10) { + case n % 2 == 0 { + print(n, " is even", "\n") + } + default { + print(n, " is odd", "\n") + } +} +``` + +### While Loops + +`g2D` supports only one looping construct, the `while` loop: + +```go +i := 30 +while (i > 0) { + print(i, " ") + i = i - 10 +} +// 30 20 10 +``` + +## Builtin functions + +### Core + +Function | Description +---------------------- | -------------------------------------------------------------------------- | +`args()` | returns an array of command-line options passed to the program | +`exit([status])` | exits the program immediately with the optional status or 0 | +`input([prompt]` | reads a line from standard input optionally printing the specified prompt | +`print(...)` | output a string to stdout | +`printf(pattern, ...)` | output a string to stdout (formatted according the specified pattern) | +`sprintf(pattern, ...)`| like `printf(...)` but returns a _string_ | +`bool(val)` | converts value to a bool | +`float(val)` | converts decimal value str to _float_ - if _val_ is invalid returns _null_ | +`int(val)` | converts decimal value str to _int_ - if _val_ is invalid returns _null_ | +`str(val)` | returns the string representation of _val_ | +`len(iterable)` | returns the length of the iterable (_string_, _array_ or _hash_) | +`push(array, val)` | returns a new array with value pushed onto the end of array | +`keys(hash)` | returns the keys of the specified hash | +`delete(hash, key)` | returns a new has with the given key deleted | + +### Math + +Function | Description +---------------------- | -------------------------------------------------------------------------- | +`abs(x)` | returns the absolute value of _x_ | +`sqrt(x)` | returns the square root of _x_ | +`hypot(p, q)` | returns `sqrt(p*p + q*q)` | +`pow(x, y)` | returns `x**y`, the base _x_ exponential of _y_ | +`atan(x)` | returns the arc tangent, in radians, of _x_ | +`atan2(x, y)` | returns the arc tangent of _y/x_ | +`sin(x)` | returns the sine of the radian argument _x_ | +`cos(x)` | returns the cosine of the radian argument _x_ | +`randf([min], [max])` | returns a random float between min and max - by default min=0.0 and max=1.0| +`randi([min], [max])` | returns a random int between min and max | + +### Canvas + +The canvas is always square. + +Function | Description +------------------------------------- | ------------------------------------------------------------------------------------- | +`screensize([size])` | when _size_ is specified sets the screen size, otherwise returns the screen size | +`worldcoords(xMin, xMax, yMin, yMax)` | sets up user-defined coordinate system; performs a screen reset (drawings are cleared)| +`pencolor(hexcolor)` | sets the pen color to the specified _hexcolor_; example `pencolor("#ff0000")` | +`pencolor(r, g, b, [a])` | sets the pen color to _r,g,b,a_ values - should be between 0 and 1, inclusive | +`pensize(width)` | sets the pen line thickness to the specified _width_ | +`stroke([preserve])` | strokes the current path with the current color and line width the path is cleared after this operation.
If preserve is _true_ the path will be preserved | +`fill([preserve])` | fills the current path with the current color. Open subpaths are implicity closed.
The path is cleared after this operation. If preserve is true the path is preserved after this operation | +`circle(x, y, r)` | draws a circle centered at _[x, y]_ coordinates and with the radius _r_ | +`ellipse(x, y, rx ,ry)` | draws an ellipse centered at [x, y] coordinates and with the radii _rx_ and _ry_ | +`rectangle(x, y, w, h, [r])` | draws a (w x h) rectangle with upper left corner located at _(x, y)_.
If radius _r_ is specified, the rectangle will have rounded corners | +`polygon(n, x, y, r, a)` | draws a regular polygon of _n_ sides, centered at _(x,y)_ with the radius _r_ and _a_ rotation | +`moveTo(x, y)` | starts a new subpath within the current path starting at the specified _(x, y)_ point | +`lineTo(x, y)` | adds a line segment to the current path starting at the current point.
If there is no current point, it is equivalent to MoveTo(x, y) | +`line(x1, y1, x2, y2)` | draws a line from point _(x1, y1)_ to point _(x2, y2)_ | +`arc(x, y, r, sa, ea)` | draws a circular arc centered at _(x, y)_ with a radius of _r_.
The path starts at _sa_ angle_, ends at _ea_ angle, and travels in the direction given by anticlockwise | +`ellArc(x, y, rx, ry, sa, ea)` | draws an elliptical arc centered at _(x, y)_ with a radius of _rx_ in x direction and _ry_ for y direction.
The path starts at _sa_ angle_, ends at _ea_ angle, and travels in the direction given by anticlockwise | +`closePath()` | adds a line segment from the current point to the beginning of the current subpath.
If there is no current point, this is a no-op. | +`clearPath()` | clears the current path. There is no current point after this operation | +`saveState()` | saves the current state of the canvas by pushin it onto a stack | +`restoreState()` | restores the last saved canvas state from the stack | +`rotate(degrees, [x, y] )` | updates the current matrix with a anticlockwise rotation.
If _x, y_ is specified, rotation occurs about this point, otherwise rotation occurs about the origin | +`snapshot([filename])` | creates a PNG image with the current drawings.
If _filename_ is omitted, it will be autogenerated with a progressive counter, that will be incremented on each
`snapshot()` invocation; this is useful if you wants to generate an animation later (using all the generated PNG images). | diff --git a/REPL.png b/REPL.png new file mode 100644 index 0000000000000000000000000000000000000000..59c81fc03efe628be57b00efdea36570a6762450 GIT binary patch literal 13315 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 m{(l2(?ah&JkN 0 { + return p.Statements[0].TokenLiteral() + } + return "" +} + +// String returns a stringified version of the AST for debugging +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Comment a comment +type Comment struct { + Token token.Token // the token.COMMENT token + Value string +} + +func (c *Comment) statementNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (c *Comment) TokenLiteral() string { return c.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (c *Comment) String() string { + var out bytes.Buffer + + out.WriteString(c.TokenLiteral() + " ") + out.WriteString(c.Value) + + return out.String() +} + +// ReturnStatement represenets the `return` statement node +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +// ExpressionStatement represents an expression statement and holds an +// expression +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +// BlockStatement represents a block statement and holds one or more other +// statements +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Identifier represents an identiifer and holds the name of the identifier +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (i *Identifier) String() string { return i.Value } + +// Null represents a null value +type Null struct { + Token token.Token +} + +func (n *Null) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (n *Null) TokenLiteral() string { return n.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (n *Null) String() string { return n.Token.Literal } + +// Boolean represents a boolean value and holds the underlying boolean value +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (b *Boolean) String() string { return b.Token.Literal } + +// IntegerLiteral represents a literal integare and holds an integer value +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +// FloatLiteral holds a floating-point number +type FloatLiteral struct { + Token token.Token + Value float64 +} + +func (fl *FloatLiteral) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (fl *FloatLiteral) TokenLiteral() string { return fl.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (fl *FloatLiteral) String() string { return fl.Token.Literal } + +// StringLiteral represents a literal string and holds a string value +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +// PrefixExpression represents a prefix expression and holds the operator +// as well as the right-hand side expression +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +// InfixExpression represents an infix expression and holds the left-hand +// expression, operator and right-hand expression +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (ie *InfixExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (ie *InfixExpression) TokenLiteral() string { return ie.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (ie *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString(" " + ie.Operator + " ") + out.WriteString(ie.Right.String()) + out.WriteString(")") + + return out.String() +} + +// IfExpression represents an `if` expression and holds the condition, +// consequence and alternative expressions +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +// WhileExpression represents an `while` expression and holds the condition, +// and consequence expression +type WhileExpression struct { + Token token.Token // The 'while' token + Condition Expression + Consequence *BlockStatement +} + +func (we *WhileExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (we *WhileExpression) TokenLiteral() string { return we.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (we *WhileExpression) String() string { + var out bytes.Buffer + + out.WriteString("while") + out.WriteString(we.Condition.String()) + out.WriteString(" ") + out.WriteString(we.Consequence.String()) + + return out.String() +} + +// ImportExpression represents an `import` expression and holds the name +// of the module being imported. +type ImportExpression struct { + Token token.Token // The 'import' token + Name Expression +} + +func (ie *ImportExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (ie *ImportExpression) TokenLiteral() string { return ie.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (ie *ImportExpression) String() string { + var out bytes.Buffer + + out.WriteString(ie.TokenLiteral()) + out.WriteString("(") + out.WriteString(fmt.Sprintf("\"%s\"", ie.Name)) + out.WriteString(")") + + return out.String() +} + +// FunctionLiteral represents a literal functions and holds the function's +// formal parameters and boy of the function as a block statement +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Name string + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fmt.Sprintf("%s %s", fl.TokenLiteral(), fl.Name)) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +// CallExpression represents a call expression and holds the function to be +// called as well as the arguments to be passed to that function +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +// ArrayLiteral represents the array literal and holds a list of expressions +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +// BindExpression represents a binding expression of the form: +// x := 1 +type BindExpression struct { + Token token.Token // The := token + Left Expression + Value Expression +} + +func (be *BindExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (be *BindExpression) TokenLiteral() string { return be.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (be *BindExpression) String() string { + var out bytes.Buffer + + out.WriteString(be.Left.String()) + out.WriteString(be.TokenLiteral()) + out.WriteString(be.Value.String()) + + return out.String() +} + +// AssignmentExpression represents an assignment expression of the form: +// x = 1 or xs[1] = 2 +type AssignmentExpression struct { + Token token.Token // The = token + Left Expression + Value Expression +} + +func (ae *AssignmentExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (ae *AssignmentExpression) TokenLiteral() string { return ae.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (ae *AssignmentExpression) String() string { + var out bytes.Buffer + + out.WriteString(ae.Left.String()) + out.WriteString(ae.TokenLiteral()) + out.WriteString(ae.Value.String()) + + return out.String() +} + +// IndexExpression represents an index operator expression, e.g: xs[2] +// and holds the left expression and index expression +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +// HashLiteral represents a hash map or dictionary literal, a set of +// key/value pairs. +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +// CaseExpression handles the case within a switch statement +type CaseExpression struct { + // Token is the actual token + Token token.Token + + // Default branch? + Default bool + + // The thing we match + Expr []Expression + + // The code to execute if there is a match + Block *BlockStatement +} + +func (ce *CaseExpression) expressionNode() {} + +// TokenLiteral returns the literal token. +func (ce *CaseExpression) TokenLiteral() string { return ce.Token.Literal } + +// String returns this object as a string. +func (ce *CaseExpression) String() string { + var out bytes.Buffer + + if ce.Default { + out.WriteString("default ") + } else { + out.WriteString("case ") + + tmp := []string{} + for _, exp := range ce.Expr { + tmp = append(tmp, exp.String()) + } + out.WriteString(strings.Join(tmp, ",")) + } + out.WriteString(ce.Block.String()) + return out.String() +} + +// SwitchExpression handles a switch statement +type SwitchExpression struct { + // Token is the actual token + Token token.Token + + // Value is the thing that is evaluated to determine + // which block should be executed. + Value Expression + + // The branches we handle + Choices []*CaseExpression +} + +func (se *SwitchExpression) expressionNode() {} + +// TokenLiteral returns the literal token. +func (se *SwitchExpression) TokenLiteral() string { return se.Token.Literal } + +// String returns this object as a string. +func (se *SwitchExpression) String() string { + var out bytes.Buffer + out.WriteString("\nswitch (") + out.WriteString(se.Value.String()) + out.WriteString(")\n{\n") + + for _, tmp := range se.Choices { + if tmp != nil { + out.WriteString(tmp.String()) + } + } + out.WriteString("}\n") + + return out.String() +} diff --git a/ast/ast_test.go b/ast/ast_test.go new file mode 100644 index 0000000..3540daf --- /dev/null +++ b/ast/ast_test.go @@ -0,0 +1,34 @@ +package ast + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/lucasepe/g2d/token" +) + +func TestString(t *testing.T) { + assert := assert.New(t) + + program := &Program{ + Statements: []Statement{ + &ExpressionStatement{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Expression: &BindExpression{ + Token: token.Token{Type: token.BIND, Literal: ":="}, + Left: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + }, + } + + assert.Equal("myVar:=anotherVar", program.String()) +} diff --git a/builtins/builtins.go b/builtins/builtins.go new file mode 100644 index 0000000..59a4105 --- /dev/null +++ b/builtins/builtins.go @@ -0,0 +1,84 @@ +package builtins + +import ( + "fmt" + "sort" + + "github.com/lucasepe/g2d/object" +) + +// Builtins ... +var Builtins = map[string]*object.Builtin{ + // Core + "args": &object.Builtin{Name: "args", Fn: Args}, + "exit": &object.Builtin{Name: "exit", Fn: Exit}, + "input": &object.Builtin{Name: "input", Fn: Input}, + "print": &object.Builtin{Name: "print", Fn: Print}, + "printf": &object.Builtin{Name: "printf", Fn: Printf}, + "sprintf": &object.Builtin{Name: "sprintf", Fn: Sprintf}, + "bool": &object.Builtin{Name: "bool", Fn: Bool}, + "float": &object.Builtin{Name: "float", Fn: Float}, + "int": &object.Builtin{Name: "int", Fn: Int}, + "str": &object.Builtin{Name: "str", Fn: Str}, + "len": &object.Builtin{Name: "len", Fn: Len}, + "push": &object.Builtin{Name: "push", Fn: Push}, + "keys": &object.Builtin{Name: "keys", Fn: hashKeys}, + "delete": &object.Builtin{Name: "delete", Fn: hashDelete}, + "type": &object.Builtin{Name: "type", Fn: TypeOf}, + + // Math + "abs": &object.Builtin{Name: "abs", Fn: Abs}, + "atan": &object.Builtin{Name: "atan", Fn: Atan}, + "atan2": &object.Builtin{Name: "atan2", Fn: Atan2}, + "cos": &object.Builtin{Name: "cos", Fn: Cos}, + "hypot": &object.Builtin{Name: "hypot", Fn: Hypot}, + "pow": &object.Builtin{Name: "pow", Fn: Pow}, + "sin": &object.Builtin{Name: "sin", Fn: Sin}, + "sqrt": &object.Builtin{Name: "sqrt", Fn: Sqrt}, + "randf": &object.Builtin{Name: "randf", Fn: RandomFloat}, + "randi": &object.Builtin{Name: "randi", Fn: RandomInt}, + + // Canvas + "screensize": &object.Builtin{Name: "screensize", Fn: ScreenSize}, + "worldcoords": &object.Builtin{Name: "worldcoords", Fn: WorldCoords}, + "pencolor": &object.Builtin{Name: "pencolor", Fn: PenColor}, + "pensize": &object.Builtin{Name: "pensize", Fn: PenSize}, + "stroke": &object.Builtin{Name: "stroke", Fn: Stroke}, + "fill": &object.Builtin{Name: "fill", Fn: Fill}, + "point": &object.Builtin{Name: "point", Fn: DrawPoint}, + "circle": &object.Builtin{Name: "circle", Fn: DrawCircle}, + "ellipse": &object.Builtin{Name: "ellipse", Fn: DrawEllipse}, + "rectangle": &object.Builtin{Name: "rectangle", Fn: DrawRoundedRectangle}, + "polygon": &object.Builtin{Name: "polygon", Fn: DrawRegularPolygon}, + "moveTo": &object.Builtin{Name: "moveTo", Fn: MoveTo}, + "lineTo": &object.Builtin{Name: "lineTo", Fn: LineTo}, + "line": &object.Builtin{Name: "line", Fn: DrawLine}, + "arc": &object.Builtin{Name: "arc", Fn: DrawArc}, + "ellArc": &object.Builtin{Name: "ellArc", Fn: DrawEllipticalArc}, + "clearPath": &object.Builtin{Name: "clearPath", Fn: ClearPath}, + "closePath": &object.Builtin{Name: "closePath", Fn: ClosePath}, + "saveState": &object.Builtin{Name: "saveState", Fn: SaveState}, + "restoreState": &object.Builtin{Name: "restoreState", Fn: RestoreState}, + "rotate": &object.Builtin{Name: "rotate", Fn: Rotate}, + + "snapshot": &object.Builtin{Name: "snapshot", Fn: Snapshot}, +} + +// BuiltinsIndex ... +var BuiltinsIndex []*object.Builtin + +func init() { + var keys []string + for k := range Builtins { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + BuiltinsIndex = append(BuiltinsIndex, Builtins[k]) + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} diff --git a/builtins/stdlib_canvas.go b/builtins/stdlib_canvas.go new file mode 100644 index 0000000..e70e261 --- /dev/null +++ b/builtins/stdlib_canvas.go @@ -0,0 +1,656 @@ +package builtins + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/fogleman/gg" + "github.com/lucasepe/g2d/canvas" + "github.com/lucasepe/g2d/object" + "github.com/lucasepe/g2d/typing" +) + +// ScreenSize returns or sets the screen size. +// screensize() - returns the screen current size. +// screensize(size) - sets the screen size clearing the screen with the current bgcolor. +func ScreenSize(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("screensize", args, typing.RangeOfArgs(0, 1)); err != nil { + return newError(err.Error()) + } + + if len(args) == 0 { + size := env.Canvas().Value.Size() + return &object.Integer{Value: int64(size)} + } + + size, err := typing.ToInt(args[0]) + if err != nil { + return newError("TypeError: screensize() argument #1 %s", err.Error()) + } + + env.Canvas().Value.Reset(canvas.Size(int64(size))) + return &object.Null{} +} + +// WorldCoords sets up user-defined coordinate system. +// This performs a screen reset, all drawings are cleared. +func WorldCoords(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("worldcoords", args, typing.ExactArgs(4)); err != nil { + return newError(err.Error()) + } + + xMin, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: worldcoords() argument #1 `xMin` %s", err.Error()) + } + + xMax, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: worldcoords() argument #2 `xMax` %s", err.Error()) + } + + yMin, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: worldcoords() argument #3 `yMin` %s", err.Error()) + } + + yMax, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: worldcoords() argument #4 `yMax` %s", err.Error()) + } + + canvas := env.Canvas().Value + if err := canvas.SetWorldCoordinates(xMin, xMax, yMin, yMax); err != nil { + return newError(err.Error()) + } + + return &object.Null{} +} + +// PenColor returns or sets the cursor pen color. +// pencolor(hexcolor) - sets the pen color to `hexcolor`. +// pencolor(r, g, b) - sets the pen color to `r,g,b` values - should be between 0 and 1, inclusive. Alpha will be set to 1 (fully opaque). +// pencolor(r, g, b, a) - sets the pen color to `r,g,b,a` values - should be between 0 and 1, inclusive. +func PenColor(env *object.Environment, args ...object.Object) object.Object { + if len(args) < 1 { + return newError("pencolor() expects one or four arguments") + } + + if len(args) == 1 { + if args[0].Type() == object.STRING { + color := args[0].(*object.String).Value + env.Canvas().Value.Graphics().SetHexColor(color) + return &object.Null{} + } + + return newError("TypeError: pencolor() argument #1 expected to be `string` got `%s`", args[0].Type()) + } + + if err := typing.Check("pencolor", args, typing.RangeOfArgs(3, 4)); err != nil { + return newError(err.Error()) + } + + r, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: pencolor() argument #1 `r` %s", err.Error()) + } + + g, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: pencolor() argument #2 `g` %s", err.Error()) + } + + b, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: pencolor() argument #3 `b` %s", err.Error()) + } + + switch len(args) { + case 3: + env.Canvas().Value.Graphics().SetRGB(r, g, b) + case 4: + a, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: pencolor() argument #4 `a` %s", err.Error()) + } + env.Canvas().Value.Graphics().SetRGBA(r, g, b, a) + } + + return &object.Null{} +} + +// PenSize returns or sets the pen line thickness. +// pensize(width) - sets the pen line thickness to `width`. +func PenSize(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("pensize", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + lw, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: pensize() argument #1 %s", err.Error()) + } + + env.Canvas().Value.Graphics().SetLineWidth(lw) + return &object.Null{} +} + +// Stroke strokes the current path with the current color and line width +// the path is cleared after this operation. +// If preserve is true the path will be preserved. +func Stroke(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("stroke", args, typing.RangeOfArgs(0, 1)); err != nil { + return newError(err.Error()) + } + + preserve := false + if (len(args) > 0) && (args[0].Type() == object.BOOLEAN) { + preserve = args[0].(*object.Boolean).Value + } + + if preserve { + env.Canvas().Value.Graphics().StrokePreserve() + } else { + env.Canvas().Value.Graphics().Stroke() + } + + return &object.Null{} +} + +// Fill fills the current path with the current color. +// Open subpaths are implicity closed. The path is cleared after this operation. +// If preserve is true the path is preserved after this operation. +func Fill(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("fill", args, typing.RangeOfArgs(0, 1)); err != nil { + return newError(err.Error()) + } + + preserve := false + if (len(args) > 0) && (args[0].Type() == object.BOOLEAN) { + preserve = args[0].(*object.Boolean).Value + } + + if preserve { + env.Canvas().Value.Graphics().FillPreserve() + } else { + env.Canvas().Value.Graphics().Fill() + } + + return &object.Null{} +} + +// DrawPoint ... +func DrawPoint(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("point", args, typing.ExactArgs(3)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: point() argument #1 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: point() argument #2 `y` %s", err.Error()) + } + + r, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: point() argument #3 `r` %s", err.Error()) + } + + dc := env.Canvas().Value.Graphics() + dc.DrawPoint(x, y, r) + + return &object.Null{} +} + +// DrawCircle draws a circle centered at [x, y] coordinates and with the radius `r`. +func DrawCircle(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("circle", args, typing.ExactArgs(3)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: circle() argument #1 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: circle() argument #2 `y` %s", err.Error()) + } + + r, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: circle() argument #3 `r` %s", err.Error()) + } + + dc := env.Canvas().Value.Graphics() + dc.DrawCircle(x, y, r) + + return &object.Null{} +} + +// DrawEllipse draws an ellipse centered at [x, y] coordinates and with the radii `rx` and `ry`. +func DrawEllipse(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("ellipse", args, typing.RangeOfArgs(3, 4)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: ellipse() argument #1 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: ellipse() argument #2 `y` %s", err.Error()) + } + + rx, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: ellipse() argument #3 `rx` %s", err.Error()) + } + + if len(args) == 3 { + dc := env.Canvas().Value.Graphics() + dc.DrawCircle(x, y, rx) + return &object.Null{} + } + + ry, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: ellipse() argument #4 `ry` %s", err.Error()) + } + + dc := env.Canvas().Value.Graphics() + dc.DrawEllipse(x, y, rx, ry) + return &object.Null{} +} + +// DrawRoundedRectangle draws a (w x h) rectangle with upper left corner located at (x, y). +// rectangle(x, y, w, h, [r]) if radius `r` is specified, the rectangle will have rounded corners. +func DrawRoundedRectangle(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("rectangle", args, typing.RangeOfArgs(4, 5)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: rectangle() argument #1 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: rectangle() argument #2 `y` %s", err.Error()) + } + + w, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: rectangle() argument #3 `w` %s", err.Error()) + } + + h, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: rectangle() argument #4 `h` %s", err.Error()) + } + + if len(args) == 4 { + dc := env.Canvas().Value.Graphics() + dc.DrawRectangle(x, y, w, h) + return &object.Null{} + } + + r, err := typing.ToFloat(args[4]) + if err != nil { + return newError("TypeError: rectangle() argument #5 `r` %s", err.Error()) + } + + dc := env.Canvas().Value.Graphics() + dc.DrawRoundedRectangle(x, y, w, h, r) + return &object.Null{} +} + +// DrawRegularPolygon draws a regular polygon of `n` sides, centered at (x,y) with radius `r` and `angle` rotation +func DrawRegularPolygon(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("polygon", args, typing.ExactArgs(5)); err != nil { + return newError(err.Error()) + } + + n, err := typing.ToInt(args[0]) + if err != nil { + return newError("TypeError: polygon() argument #1 `n` %s", err.Error()) + } + + x, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: polygon() argument #2 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: polygon() argument #3 `y` %s", err.Error()) + } + + r, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: polygon() argument #4 `r` %s", err.Error()) + } + + deg, err := typing.ToFloat(args[4]) + if err != nil { + return newError("TypeError: polygon() argument #5 `deg` %s", err.Error()) + } + + rad := gg.Radians(deg) + dc := env.Canvas().Value.Graphics() + dc.DrawRegularPolygon(n, x, y, r, rad) + + return &object.Null{} +} + +// MoveTo starts a new subpath within the current path starting at the specified point. +func MoveTo(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("moveTo", args, typing.ExactArgs(2)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: moveTo() argument #1 %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: moveTo() argument #2 %s", err.Error()) + } + + env.Canvas().Value.Graphics().MoveTo(x, y) + return &object.Null{} +} + +// LineTo adds a line segment to the current path starting at the current point. +// If there is no current point, it is equivalent to MoveTo(x, y) +func LineTo(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("lineTo", args, typing.ExactArgs(2)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: lineTo() argument #1 %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: lineTo() argument #2 %s", err.Error()) + } + + env.Canvas().Value.Graphics().LineTo(x, y) + return &object.Null{} +} + +// DrawLine draws a line from point (x1, y1) to point (x2, y2) +func DrawLine(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("line", args, typing.ExactArgs(4)); err != nil { + return newError(err.Error()) + } + + x1, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: line() argument #1 `x1` %s", err.Error()) + } + + y1, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: line() argument #2 `y1` %s", err.Error()) + } + + x2, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: line() argument #3 `x2` %s", err.Error()) + } + + y2, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: line() argument #4 `y2` %s", err.Error()) + } + + env.Canvas().Value.Graphics().DrawLine(x1, y1, x2, y2) + return &object.Null{} +} + +// DrawArc draws a circular arc centered at `x, y` with a radius of `r`. +// The path starts at `angle1`, ends at `angle2`, and travels in the direction given by anticlockwise. +func DrawArc(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("arc", args, typing.ExactArgs(5)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: arc() argument #1 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: arc() argument #2 `y` %s", err.Error()) + } + + r, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: arc() argument #3 `r` %s", err.Error()) + } + + deg1, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: arc() argument #4 `degrees1` %s", err.Error()) + } + + deg2, err := typing.ToFloat(args[4]) + if err != nil { + return newError("TypeError: arc() argument #4 `degrees2` %s", err.Error()) + } + + rad1, rad2 := gg.Degrees(deg1), gg.Degrees(deg2) + env.Canvas().Value.Graphics().DrawArc(x, y, r, rad1, rad2) + return &object.Null{} +} + +// DrawEllipticalArc draws an elliptical arc centered at `x, y` with a radius of `rx` in x direction and `ry` for y direction. +// The path starts at `angle1`, ends at `angle2`, and travels in the direction given by anticlockwise. +func DrawEllipticalArc(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("ellipticalArc", args, typing.ExactArgs(6)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: ellipticalArc() argument #1 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: ellipticalArc() argument #2 `y` %s", err.Error()) + } + + rx, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: ellipticalArc() argument #3 `rx` %s", err.Error()) + } + + ry, err := typing.ToFloat(args[3]) + if err != nil { + return newError("TypeError: ellipticalArc() argument #4 `ry` %s", err.Error()) + } + + deg1, err := typing.ToFloat(args[4]) + if err != nil { + return newError("TypeError: ellipticalArc() argument #5 `degrees1` %s", err.Error()) + } + + deg2, err := typing.ToFloat(args[5]) + if err != nil { + return newError("TypeError: ellipticalArc() argument #6 `degrees2` %s", err.Error()) + } + + rad1, rad2 := gg.Degrees(deg1), gg.Degrees(deg2) + env.Canvas().Value.Graphics().DrawEllipticalArc(x, y, rx, ry, rad1, rad2) + return &object.Null{} +} + +// ClosePath adds a line segment from the current point to the beginning +// of the current subpath. If there is no current point, this is a no-op. +func ClosePath(env *object.Environment, args ...object.Object) object.Object { + env.Canvas().Value.Graphics().ClosePath() + return &object.Null{} +} + +// ClearPath clears the current path. There is no current point after this +// operation. +func ClearPath(env *object.Environment, args ...object.Object) object.Object { + env.Canvas().Value.Graphics().ClearPath() + return &object.Null{} +} + +/* +// Text draws the text centered at the current position. +func Text(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check( + "Text", args, + typing.ExactArgs(1), + typing.WithTypes(object.STRING), + ); err != nil { + return newError(err.Error()) + } + + msg := args[0].(*object.String).Value + env.Cursor().Value.Text(msg) + return &object.Null{} +} + +// PenUp pulls the pen up – no drawing when moving. +func PenUp(env *object.Environment, args ...object.Object) object.Object { + env.Cursor().Value.Up() + return &object.Null{} +} + +// FontSize returns or sets the font size. +// fontsize() - returns the font current size. +// fontsize(size) - sets the font current size. +func FontSize(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("fontsize", args, typing.RangeOfArgs(0, 1)); err != nil { + return newError(err.Error()) + } + + if len(args) == 0 { + size := env.Cursor().Value.Canvas().FontSize() + return &object.Float{Value: size} + } + + fs, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: fontsize() argument #1 %s", err.Error()) + } + + env.Cursor().Value.Canvas().SetFontSize(fs) + return &object.Null{} +} +*/ + +// Snapshot creates a png image with the current drawings. +// snapshot() - saves the png image in the source code folder. +func Snapshot(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("input", args, typing.RangeOfArgs(0, 1), typing.WithTypes(object.STRING)); err != nil { + return newError(err.Error()) + } + + if len(args) == 1 { + filename := args[0].(*object.String).Value + dc := env.Canvas().Value.Graphics() + if err := dc.SavePNG(filename); err != nil { + return newError(err.Error()) + } + + return &object.Null{} + } + + object.SaveCounter = object.SaveCounter + 1 + + folder := filepath.Join(object.WorkDir, object.SourceFile) + if err := mkdir(folder); err != nil { + return newError(err.Error()) + } + + filename := filepath.Join(folder, + fmt.Sprintf("%s_%03d.png", object.SourceFile, object.SaveCounter)) + + dc := env.Canvas().Value.Graphics() + if err := dc.SavePNG(filename); err != nil { + return newError(err.Error()) + } + + return &object.Null{} +} + +// SaveState saves the current state of the canvas by pushin it onto a stack. These can be nested. +func SaveState(env *object.Environment, args ...object.Object) object.Object { + env.Canvas().Value.Graphics().Push() + return &object.Null{} +} + +// RestoreState restores the last saved canvas state from the stack. +func RestoreState(env *object.Environment, args ...object.Object) object.Object { + env.Canvas().Value.Graphics().Pop() + return &object.Null{} +} + +// Rotate updates the current matrix with a anticlockwise rotation. +// rotate(degrees) - rotation occurs about the origin. +// rotate(degrees, x, y) - rotation occurs about the specified point. +// Angle is specified in degrees. +func Rotate(env *object.Environment, args ...object.Object) object.Object { + if len(args) < 1 { + return newError("rotate() expects one or three arguments") + } + + deg, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: rotate() argument #1 `degs` %s", err.Error()) + } + + rad := gg.Radians(deg) + + if len(args) == 1 { + env.Canvas().Value.Graphics().Rotate(rad) + return &object.Null{} + } + + if err := typing.Check("rotate", args, typing.ExactArgs(3)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: rotate() argument #2 `x` %s", err.Error()) + } + + y, err := typing.ToFloat(args[2]) + if err != nil { + return newError("TypeError: rotate() argument #3 `y` %s", err.Error()) + } + + env.Canvas().Value.Graphics().RotateAbout(rad, x, y) + return &object.Null{} +} + +func mkdir(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return os.Mkdir(path, 0755) + } + + return nil +} diff --git a/builtins/stdlib_core.go b/builtins/stdlib_core.go new file mode 100644 index 0000000..6327cbe --- /dev/null +++ b/builtins/stdlib_core.go @@ -0,0 +1,306 @@ +package builtins + +import ( + "bufio" + "fmt" + "io" + "math" + "os" + "strconv" + + "github.com/lucasepe/g2d/object" + "github.com/lucasepe/g2d/typing" +) + +// Args args() Returns an array of command-line options passed to the program +func Args(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check( + "args", args, + typing.ExactArgs(0), + ); err != nil { + return newError(err.Error()) + } + + elements := make([]object.Object, len(object.Arguments)) + for i, arg := range object.Arguments { + elements[i] = &object.String{Value: arg} + } + return &object.Array{Elements: elements} +} + +// Bool converts value to a bool +func Bool(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check( + "bool", args, + typing.ExactArgs(1), + ); err != nil { + return newError(err.Error()) + } + + return &object.Boolean{Value: args[0].Bool()} +} + +// Exit exit([status]) Exits the program immediately with the optional status or 0 +func Exit(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check( + "exit", args, + typing.RangeOfArgs(0, 1), + typing.WithTypes(object.INTEGER), + ); err != nil { + return newError(err.Error()) + } + + var status int + if len(args) == 1 { + status = int(args[0].(*object.Integer).Value) + } + + object.ExitFunction(status) + + return nil +} + +// Float converts decimal value str to float. If value is invalid returns null +func Float(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check( + "float", args, + typing.ExactArgs(1), + ); err != nil { + return newError(err.Error()) + } + + switch arg := args[0].(type) { + case *object.Boolean: + if arg.Value { + return &object.Float{Value: 1} + } + return &object.Float{Value: 0} + case *object.Float: + return arg + case *object.Integer: + return &object.Float{Value: float64(arg.Value)} + case *object.String: + n, err := strconv.ParseFloat(arg.Value, 64) + if err != nil { + return newError("could not parse string to int: %s", err) + } + return &object.Float{Value: n} + default: + return &object.Float{} + } +} + +// Int converts decimal value str to int. If value is invalid returns null +func Int(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check( + "int", args, + typing.ExactArgs(1), + ); err != nil { + return newError(err.Error()) + } + + switch arg := args[0].(type) { + case *object.Boolean: + if arg.Value { + return &object.Integer{Value: 1} + } + return &object.Integer{Value: 0} + case *object.Integer: + return arg + case *object.Float: + return &object.Integer{Value: int64(math.Round(arg.Value))} + case *object.String: + n, err := strconv.ParseInt(arg.Value, 10, 64) + if err != nil { + return newError("could not parse string to int: %s", err) + } + return &object.Integer{Value: n} + default: + return &object.Integer{} + } +} + +// Len len(iterable) Returns the length of the iterable (str, array or hash). +func Len(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("len", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + if size, ok := args[0].(object.Sizeable); ok { + return &object.Integer{Value: int64(size.Len())} + } + + return newError("TypeError: object of type '%s' has no len()", args[0].Type()) +} + +// Input reads a line from standard input optionally printing prompt. +// input([prompt]) prints the prompt. +func Input(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("input", args, typing.RangeOfArgs(0, 1), typing.WithTypes(object.STRING)); err != nil { + return newError(err.Error()) + } + + if len(args) == 1 { + prompt := args[0].(*object.String).Value + fmt.Fprintf(os.Stdout, prompt) + } + + buffer := bufio.NewReader(os.Stdin) + + line, _, err := buffer.ReadLine() + if err != nil && err != io.EOF { + return newError(fmt.Sprintf("error reading input from stdin: %s", err)) + } + return &object.String{Value: string(line)} +} + +// Print output a string to stdout +func Print(env *object.Environment, args ...object.Object) object.Object { + for _, arg := range args { + fmt.Print(arg.Inspect()) + } + return &object.Null{} +} + +// Printf ... +func Printf(env *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("printf", args, typing.MinimumArgs(1)); err != nil { + return newError(err.Error()) + } + + // Convert to the formatted version, via our `sprintf` + // function. + out := Sprintf(env, args...) + + // If that returned a string then we can print it + if out.Type() == object.STRING { + fmt.Print(out.(*object.String).Value) + + } + + return &object.Null{} +} + +// Sprintf is the implementation of our `sprintf` function. +func Sprintf(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("sprintf", args, typing.MinimumArgs(1)); err != nil { + return newError(err.Error()) + } + + // We expect 1+ arguments + if len(args) < 1 { + return &object.String{Value: args[0].String()} + } + + // Type-check + if args[0].Type() != object.STRING { + return &object.Null{} + } + + // Get the format-string. + fs := args[0].(*object.String).Value + + // Convert the arguments to something go's sprintf + // code will understand. + + argLen := len(args) + fmtArgs := make([]interface{}, argLen-1) + + // Here we convert and assign. + for i, v := range args[1:] { + fmtArgs[i] = v.ToInterface() + } + + // Call the helper + out := fmt.Sprintf(fs, fmtArgs...) + + // And now return the value. + return &object.String{Value: out} +} + +// Str returns the string representation of value. +func Str(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("str", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + return &object.String{Value: args[0].String()} +} + +// TypeOf returns a str denoting the type of value: nil, bool, int, float, str, array, hash, or fn. +func TypeOf(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("type", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + return &object.String{Value: string(args[0].Type())} +} + +// Push returns a new array with value pushed onto the end of array. +func Push(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("push", args, typing.ExactArgs(2), typing.WithTypes(object.ARRAY)); err != nil { + return newError(err.Error()) + } + + arr := args[0].(*object.Array) + newArray := arr.Copy() + newArray.Append(args[1]) + + return newArray +} + +// Returns the keys of the specified hash +func hashKeys(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("hashKeys", args, typing.ExactArgs(1), typing.WithTypes(object.HASH)); err != nil { + return newError(err.Error()) + } + + // The object we're working with + hash := args[0].(*object.Hash) + ents := len(hash.Pairs) + + // Create a new array for the results. + array := make([]object.Object, ents) + + // Now copy the keys into it. + i := 0 + for _, ent := range hash.Pairs { + array[i] = ent.Key + i++ + } + + // Return the array. + return &object.Array{Elements: array} +} + +// Delete a given hash-key +func hashDelete(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("hashDelete", args, typing.ExactArgs(2)); err != nil { + return newError(err.Error()) + } + + // The object we're working with + hash, ok := args[0].(*object.Hash) + if !ok { + return newError("TypeError: hashDelete() expected argument #1 to be `hash` got `%s`", args[0].Type()) + } + + // The key we're going to delete + key := args[1].(object.Hashable) + if !ok { + return newError( + "TypeError: hashDelete() expected argument #2 to be `string`, `int`, `float` or `bool` got `%s`", + args[1].Type()) + } + + // Make a new hash + newHash := make(map[object.HashKey]object.HashPair) + + // Copy the values EXCEPT the one we have. + for k, v := range hash.Pairs { + if k != key.HashKey() { + newHash[k] = v + } + } + return &object.Hash{Pairs: newHash} +} diff --git a/builtins/stdlib_math.go b/builtins/stdlib_math.go new file mode 100644 index 0000000..2c3e7be --- /dev/null +++ b/builtins/stdlib_math.go @@ -0,0 +1,229 @@ +package builtins + +import ( + "math" + "math/rand" + "time" + + "github.com/lucasepe/g2d/object" + "github.com/lucasepe/g2d/typing" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// Abs returns the absolute value of x. +func Abs(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("abs", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + if args[0].Type() == object.INTEGER { + value := args[0].(*object.Integer).Value + if value < 0 { + value = value * -1 + } + return &object.Integer{Value: value} + } + + if args[0].Type() == object.FLOAT { + value := args[0].(*object.Float).Value + if value < 0 { + value = value * -1 + } + return &object.Float{Value: value} + } + + return newError("TypeError: abs() argument #1 expected to be `int` or `float` got `%s`", args[0].Type()) +} + +// Sqrt returns the square root of x. +func Sqrt(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("sqrt", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + val, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: sqrt() argument #1 %s", err.Error()) + } + + return &object.Float{Value: math.Sqrt(val)} +} + +// Hypot returns Sqrt(p*p + q*q), taking care to avoid +// unnecessary overflow and underflow. +func Hypot(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("hypot", args, typing.ExactArgs(2)); err != nil { + return newError(err.Error()) + } + + p, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: sqrt() argument #1 %s", err.Error()) + } + + q, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: sqrt() argument #1 %s", err.Error()) + } + + return &object.Float{Value: math.Hypot(p, q)} +} + +// Pow returns x**y, the base-x exponential of y. +func Pow(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("pow", args, typing.ExactArgs(2)); err != nil { + return newError(err.Error()) + } + + x, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: pow() argument #1 %s", err.Error()) + } + + y, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: pow() argument #1 %s", err.Error()) + } + + return &object.Float{Value: math.Pow(x, y)} +} + +// Atan returns the arctangent, in radians, of x. +func Atan(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("atan", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + val, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: atan() argument #1 %s", err.Error()) + } + + return &object.Float{Value: math.Atan(val)} +} + +// Atan2 returns the arc tangent of y/x, using +// the signs of the two to determine the quadrant +// of the return value. +func Atan2(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("atan2", args, typing.ExactArgs(2)); err != nil { + return newError(err.Error()) + } + + y, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: atan2() argument #1 %s", err.Error()) + } + + x, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: atan2() argument #2 %s", err.Error()) + } + + return &object.Float{Value: math.Atan2(y, x)} +} + +// Sin returns the sine of the radian argument x. +func Sin(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("sin", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + val, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: sin() argument #1 %s", err.Error()) + } + + return &object.Float{Value: math.Sin(val)} +} + +// Cos returns the cosine of the radian argument x. +func Cos(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("cos", args, typing.ExactArgs(1)); err != nil { + return newError(err.Error()) + } + + val, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: cos() argument #1 %s", err.Error()) + } + + return &object.Float{Value: math.Cos(val)} +} + +// RandomFloat returns a random float. +// randf() returns a random float between 0.0 and 1.0 +// randf(max) returns a random float between 0.0 and max +// randf(min, max) returns a random float between min and max +func RandomFloat(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("randf", args, typing.RangeOfArgs(0, 2)); err != nil { + return newError(err.Error()) + } + + if len(args) == 1 { + max, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: randf() argument #1 %s", err.Error()) + } + if max <= 0 { + return newError("ValueError: randf() argument #1 must be > 0") + } + return &object.Float{Value: rand.Float64() * max} + } + + if len(args) == 2 { + min, err := typing.ToFloat(args[0]) + if err != nil { + return newError("TypeError: randf() argument #1 `min` %s", err.Error()) + } + + max, err := typing.ToFloat(args[1]) + if err != nil { + return newError("TypeError: randf() argument #2 `max` %s", err.Error()) + } + + if max < min { + return newError("ValueError: randf() argument #1 `min` must be > argument #2 `max`") + } + return &object.Float{ + Value: min + rand.Float64()*(max-min), + } + } + + return &object.Float{Value: rand.Float64()} +} + +// RandomInt returns a ramdom int +// randi() returns a random integer +// randi(max) returns a random integer between 0 and max +// randi(min, max) returns a random integer between min and max +func RandomInt(_ *object.Environment, args ...object.Object) object.Object { + if err := typing.Check("randi", args, + typing.RangeOfArgs(0, 2), + typing.WithTypes(object.INTEGER, object.INTEGER), + ); err != nil { + return newError(err.Error()) + } + + if len(args) == 1 { + max := args[0].(*object.Integer).Value + if max <= 0 { + return newError("ValueError: randi() argument #1 must be > 0") + } + return &object.Integer{Value: rand.Int63n(max + 1)} + } + + if len(args) == 2 { + min := args[0].(*object.Integer).Value + max := args[1].(*object.Integer).Value + if min > max { + return newError("ValueError: randi() argument #1 `min` must be > argument #2 `max`") + } + return &object.Integer{Value: rand.Int63n(max-min+1) + min} + } + + return &object.Integer{Value: rand.Int63()} +} diff --git a/canvas/canvas.go b/canvas/canvas.go new file mode 100644 index 0000000..ca93bb6 --- /dev/null +++ b/canvas/canvas.go @@ -0,0 +1,197 @@ +package canvas + +import ( + "fmt" + "math" + "os" + + "github.com/fogleman/gg" + "github.com/golang/freetype/truetype" + "golang.org/x/image/font/gofont/goregular" +) + +const ( + minimumSize = 256 + maximumSize = 1536 + sizeIncrement = 64 + defaultSize = 1024 + defaultFontSize = 12.0 +) + +// Canvas defines a drawing context. +type Canvas struct { + size int + font *truetype.Font + fontSize float64 + xMin, xMax float64 + yMin, yMax float64 + dc *gg.Context +} + +// Option defines a screen parameter. +type Option func(*Canvas) + +// Size defines the canvas size option. +func Size(size int64) Option { + return func(scr *Canvas) { + if size < minimumSize { + size = minimumSize + } + + if size > maximumSize { + size = maximumSize + } + + div := math.Round(float64(size) / float64(sizeIncrement)) + mul, _ := math.Modf(div) + scr.size = int(mul) * sizeIncrement + } +} + +// NewCanvas creates a drawing context. +func NewCanvas(opts ...Option) *Canvas { + res := &Canvas{ + size: defaultSize, + fontSize: defaultFontSize, + } + + for _, op := range opts { + op(res) + } + + res.dc = gg.NewContext(res.size, res.size) + + res.dc.SetHexColor("#ffffff") + res.dc.Clear() + + font, err := truetype.Parse(goregular.TTF) + if err != nil { + fmt.Fprintf(os.Stderr, "warning: %s\n", err.Error()) + } else { + res.font = font + face := truetype.NewFace(font, &truetype.Options{Size: res.fontSize}) + res.dc.SetFontFace(face) + } + + delta := float64(res.size) + res.xMin = -0.5 * delta + res.xMax = 0.5 * delta + res.yMin = -0.5 * delta + res.yMax = 0.5 * delta + + mapWorldCoordinates(res.dc, res.xMin, res.xMax, res.yMin, res.yMax) + + return res +} + +// Graphics returns the canvas graphic context. +func (scr *Canvas) Graphics() *gg.Context { return scr.dc } + +// Reset resets screen using the specified options. +func (scr *Canvas) Reset(opts ...Option) { + for _, op := range opts { + op(scr) + } + + scr.dc = gg.NewContext(scr.size, scr.size) + + scr.dc.SetHexColor("#ffffff") + scr.dc.Clear() + + scr.SetFontSize(12) + + scr.xMin = 0 + scr.xMax = float64(scr.size) + scr.yMin = 0 + scr.yMax = float64(scr.size) + + delta := float64(scr.size) + scr.xMin = -0.5 * delta + scr.xMax = 0.5 * delta + scr.yMin = -0.5 * delta + scr.yMax = 0.5 * delta + + mapWorldCoordinates(scr.dc, scr.xMin, scr.xMax, scr.yMin, scr.yMax) +} + +// SetFontSize sets the font's size. +func (scr *Canvas) SetFontSize(size float64) { + if scr.font != nil { + scr.fontSize = size + face := truetype.NewFace(scr.font, &truetype.Options{Size: size}) + scr.dc.SetFontFace(face) + } +} + +// FontSize returns font's size. +func (scr *Canvas) FontSize() float64 { return scr.fontSize } + +// Size returns the screen size. +func (scr *Canvas) Size() int { return scr.size } + +// Xmin returns the screen xMin coords. +func (scr *Canvas) Xmin() float64 { return scr.xMin } + +// Xmax returns the screen xMax coords. +func (scr *Canvas) Xmax() float64 { return scr.xMax } + +// Ymin returns the screen yMin coords. +func (scr *Canvas) Ymin() float64 { return scr.yMin } + +// Ymax returns the screen yMax coords. +func (scr *Canvas) Ymax() float64 { return scr.yMax } + +// SetWorldCoordinates sets up user-defined coordinate system. +// +// xMin: x-coordinate of lower left corner of canvas. +// xMax: x-coordinate of upper right corner of canvas. +// yMin: y-coordinate of lower left corner of canvas. +// yMax: y-coordinate of upper right corner of canvas. +func (scr *Canvas) SetWorldCoordinates(xMin, xMax float64, yMin, yMax float64) error { + if xMax <= xMin { + return fmt.Errorf("xMax must be greater then xMin") + } + + if yMax <= yMin { + return fmt.Errorf("yMax must be greater then yMin") + } + + scr.xMin = xMin + scr.xMax = xMax + scr.yMin = yMin + scr.yMax = yMax + mapWorldCoordinates(scr.dc, xMin, xMax, yMin, yMax) + + return nil +} + +// SavePNG saves the picture as PNG to the specified filename. +func (scr *Canvas) SavePNG(path string) error { + return scr.dc.SavePNG(path) +} + +func mapWorldCoordinates(dc *gg.Context, xMin, xMax, yMin, yMax float64) { + w, h := float64(dc.Width()), float64(dc.Height()) + + displayAspect := math.Abs(h / w) + windowAspect := math.Abs((yMax - yMin) / (xMax - xMin)) + + if displayAspect > windowAspect { + // Expand the viewport vertically. + excess := (yMax - yMin) * (displayAspect/windowAspect - 1) + yMax = yMax + excess/2 + yMin = yMin - excess/2 + } else if displayAspect < windowAspect { + // Expand the viewport vertically. + excess := (xMax - xMin) * (windowAspect/displayAspect - 1) + xMax = xMax + excess/2 + xMin = xMin - excess/2 + } + + sx, sy := w/(xMax-xMin), h/(yMin-yMax) + tx, ty := -xMin, -yMax + + dc.Identity() + dc.Scale(sx, sy) + dc.Translate(tx, ty) +} diff --git a/eval/eval.go b/eval/eval.go new file mode 100644 index 0000000..6af2bb6 --- /dev/null +++ b/eval/eval.go @@ -0,0 +1,870 @@ +package eval + +// Package eval implements the evaluator -- a tree-walker implemtnation that +// recursively walks the parsed AST (abstract syntax tree) and evaluates +// the nodes according to their semantic meaning + +import ( + "fmt" + "io/ioutil" + "math" + "strings" + + "github.com/lucasepe/g2d/ast" + "github.com/lucasepe/g2d/builtins" + "github.com/lucasepe/g2d/lexer" + "github.com/lucasepe/g2d/object" + "github.com/lucasepe/g2d/parser" + "github.com/lucasepe/g2d/token" + "github.com/lucasepe/g2d/utils" +) + +var ( + // TRUE is a cached Boolean object holding the `true` value + TRUE = &object.Boolean{Value: true} + + // FALSE is a cached Boolean object holding the `false` value + FALSE = &object.Boolean{Value: false} + + // NULL is a cached Null object + NULL = &object.Null{} +) + +// This program's lexer used for error location in Eval(program) +var lex *lexer.Lexer + +func fromNativeBoolean(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func newError(tok token.Token, format string, a ...interface{}) *object.Error { + if lex == nil { + return &object.Error{Message: fmt.Sprintf(format, a...)} + } + + // get the token position from the error node and append the offending line to the error message + lineNum, _, errorLine := lex.ErrorLine(tok.Position) + errorPosition := fmt.Sprintf("\n * line: %d\t%s", lineNum, errorLine) + return &object.Error{Message: fmt.Sprintf(format, a...) + errorPosition} +} + +/* +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} +*/ + +// EvalModule evaluates the named module and returns a *object.Module object +func EvalModule(tok token.Token, name string) object.Object { + filename := utils.FindModule(name) + if filename == "" { + return newError(tok, "ImportError: no module named '%s'", name) + } + + b, err := ioutil.ReadFile(filename) + if err != nil { + return newError(tok, "IOError: error reading module '%s': %s", name, err) + } + + l := lexer.New(string(b)) + p := parser.New(l) + + module := p.ParseProgram() + if len(p.Errors()) != 0 { + return newError(tok, "ParseError: %s", p.Errors()) + } + + env := object.NewEnvironment() + Eval(module, env) + + return env.ExportedHash() +} + +// BeginEval (program, env, lexer) object.Object +// REPL and testing modules call this function to init the global lexer pointer for error location +// NB. Eval(node, env) is recursive +func BeginEval(program ast.Node, env *object.Environment, lexer *lexer.Lexer) object.Object { + // global lexer + lex = lexer + // run the evaluator + return Eval(program, env) +} + +// Eval evaluates the node and returns an object +func Eval(node ast.Node, env *object.Environment) object.Object { + + switch node := node.(type) { + + case *ast.Program: + return evalProgram(node, env) + + // Statements + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.Return{Value: val} + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + case *ast.FloatLiteral: + return &object.Float{Value: node.Value} + case *ast.StringLiteral: + return &object.String{Value: node.Value} + case *ast.Boolean: + return fromNativeBoolean(node.Value) + case *ast.Null: + return NULL + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Token, node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Token, node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + case *ast.WhileExpression: + return evalWhileExpression(node, env) + case *ast.ImportExpression: + return evalImportExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + return applyFunction(node.Token, env, function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.BindExpression: + value := Eval(node.Value, env) + if isError(value) { + return value + } + + if ident, ok := node.Left.(*ast.Identifier); ok { + if immutable, ok := value.(object.Immutable); ok { + env.Set(ident.Value, immutable.Clone()) + } else { + env.Set(ident.Value, value) + } + + return NULL + } + return newError(node.Token, "expected identifier on left got=%T", node.Left) + + case *ast.AssignmentExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + value := Eval(node.Value, env) + if isError(value) { + return value + } + + if ident, ok := node.Left.(*ast.Identifier); ok { + env.Set(ident.Value, value) + } else if ie, ok := node.Left.(*ast.IndexExpression); ok { + obj := Eval(ie.Left, env) + if isError(obj) { + return obj + } + + if array, ok := obj.(*object.Array); ok { + index := Eval(ie.Index, env) + if isError(index) { + return index + } + if idx, ok := index.(*object.Integer); ok { + array.Elements[idx.Value] = value + } else { + return newError(node.Token, "cannot index array with %#v", index) + } + } else if hash, ok := obj.(*object.Hash); ok { + key := Eval(ie.Index, env) + if isError(key) { + return key + } + if hashKey, ok := key.(object.Hashable); ok { + hashed := hashKey.HashKey() + hash.Pairs[hashed] = object.HashPair{Key: key, Value: value} + } else { + return newError(node.Token, "cannot index hash with %T", key) + } + } else { + return newError(node.Token, "object type %T does not support item assignment", obj) + } + } else { + return newError(node.Token, "expected identifier or index expression got=%T", left) + } + + return NULL + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(node.Token, left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + case *ast.SwitchExpression: + return evalSwitchStatement(node, env) + } + + return NULL +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.Return: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalStatements(stmts []ast.Statement, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range stmts { + result = Eval(statement, env) + + if returnValue, ok := result.(*object.Return); ok { + return returnValue.Value + } + } + + return result +} + +func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN || rt == object.ERROR { + return result + } + } + } + + return result +} + +func evalPrefixExpression(tok token.Token, operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(tok, right) + + default: + return newError(tok, "unknown operator: %s%s", operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(tok token.Token, right object.Object) object.Object { + switch obj := right.(type) { + case *object.Integer: + return &object.Integer{Value: -obj.Value} + case *object.Float: + return &object.Float{Value: -obj.Value} + default: + return newError(tok, "unknown operator: -%s", right.Type()) + } +} + +func evalInfixExpression(tok token.Token, operator string, left, right object.Object) object.Object { + switch { + + // " " * 4 + case operator == "*" && left.Type() == object.STRING && right.Type() == object.INTEGER: + leftVal := left.(*object.String).Value + rightVal := right.(*object.Integer).Value + return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} + + // 4 * " " + case operator == "*" && left.Type() == object.INTEGER && right.Type() == object.STRING: + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.String).Value + return &object.String{Value: strings.Repeat(rightVal, int(leftVal))} + + case operator == "==": + return fromNativeBoolean(left.(object.Comparable).Compare(right) == 0) + case operator == "!=": + return fromNativeBoolean(left.(object.Comparable).Compare(right) != 0) + case operator == "<=": + return fromNativeBoolean(left.(object.Comparable).Compare(right) < 1) + case operator == ">=": + return fromNativeBoolean(left.(object.Comparable).Compare(right) > -1) + case operator == "<": + return fromNativeBoolean(left.(object.Comparable).Compare(right) == -1) + case operator == ">": + return fromNativeBoolean(left.(object.Comparable).Compare(right) == 1) + + case left.Type() == object.BOOLEAN && right.Type() == object.BOOLEAN: + return evalBooleanInfixExpression(tok, operator, left, right) + case left.Type() == object.INTEGER && right.Type() == object.INTEGER: + return evalIntegerInfixExpression(tok, operator, left, right) + case left.Type() == object.FLOAT && right.Type() == object.FLOAT: + return evalFloatInfixExpression(tok, operator, left, right) + case left.Type() == object.INTEGER && right.Type() == object.FLOAT: + return evalIntegerFloatInfixExpression(tok, operator, left, right) + case left.Type() == object.FLOAT && right.Type() == object.INTEGER: + return evalFloatIntegerInfixExpression(tok, operator, left, right) + case left.Type() == object.STRING && right.Type() == object.STRING: + return evalStringInfixExpression(tok, operator, left, right) + + default: + return newError(tok, "unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } +} + +func evalBooleanInfixExpression(tok token.Token, operator string, left, right object.Object) object.Object { + leftVal := left.(*object.Boolean).Value + rightVal := right.(*object.Boolean).Value + + switch operator { + case "&&": + return fromNativeBoolean(leftVal && rightVal) + case "||": + return fromNativeBoolean(leftVal || rightVal) + default: + return newError(tok, "unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } +} + +func evalIntegerInfixExpression(_ token.Token, operator string, left, right object.Object) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "%": + return &object.Integer{Value: leftVal % rightVal} + case "<": + return fromNativeBoolean(leftVal < rightVal) + case "<=": + return fromNativeBoolean(leftVal <= rightVal) + case ">": + return fromNativeBoolean(leftVal > rightVal) + case ">=": + return fromNativeBoolean(leftVal >= rightVal) + case "==": + return fromNativeBoolean(leftVal == rightVal) + case "!=": + return fromNativeBoolean(leftVal != rightVal) + default: + return NULL + } +} + +func evalFloatInfixExpression(tok token.Token, operator string, left, right object.Object) object.Object { + leftVal := left.(*object.Float).Value + rightVal := right.(*object.Float).Value + switch operator { + case "+": + return &object.Float{Value: leftVal + rightVal} + case "+=": + return &object.Float{Value: leftVal + rightVal} + case "-": + return &object.Float{Value: leftVal - rightVal} + case "-=": + return &object.Float{Value: leftVal - rightVal} + case "*": + return &object.Float{Value: leftVal * rightVal} + case "*=": + return &object.Float{Value: leftVal * rightVal} + case "/": + return &object.Float{Value: leftVal / rightVal} + case "/=": + return &object.Float{Value: leftVal / rightVal} + case "%": + return &object.Float{Value: math.Mod(leftVal, rightVal)} + case "<": + return fromNativeBoolean(leftVal < rightVal) + case "<=": + return fromNativeBoolean(leftVal <= rightVal) + case ">": + return fromNativeBoolean(leftVal > rightVal) + case ">=": + return fromNativeBoolean(leftVal >= rightVal) + case "==": + return fromNativeBoolean(leftVal == rightVal) + case "!=": + return fromNativeBoolean(leftVal != rightVal) + default: + return newError(tok, "unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } +} + +func evalIntegerFloatInfixExpression(tok token.Token, operator string, left, right object.Object) object.Object { + leftVal := float64(left.(*object.Integer).Value) + rightVal := right.(*object.Float).Value + + switch operator { + case "+": + return &object.Float{Value: leftVal + rightVal} + case "+=": + return &object.Float{Value: leftVal + rightVal} + case "-": + return &object.Float{Value: leftVal - rightVal} + case "-=": + return &object.Float{Value: leftVal - rightVal} + case "*": + return &object.Float{Value: leftVal * rightVal} + case "*=": + return &object.Float{Value: leftVal * rightVal} + case "/": + return &object.Float{Value: leftVal / rightVal} + case "/=": + return &object.Float{Value: leftVal / rightVal} + case "%": + return &object.Float{Value: math.Mod(leftVal, rightVal)} + case "<": + return fromNativeBoolean(leftVal < rightVal) + case "<=": + return fromNativeBoolean(leftVal <= rightVal) + case ">": + return fromNativeBoolean(leftVal > rightVal) + case ">=": + return fromNativeBoolean(leftVal >= rightVal) + case "==": + return fromNativeBoolean(leftVal == rightVal) + case "!=": + return fromNativeBoolean(leftVal != rightVal) + default: + return newError(tok, "unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } +} + +func evalFloatIntegerInfixExpression(tok token.Token, operator string, left, right object.Object) object.Object { + leftVal := left.(*object.Float).Value + rightVal := float64(right.(*object.Integer).Value) + + switch operator { + case "+": + return &object.Float{Value: leftVal + rightVal} + case "+=": + return &object.Float{Value: leftVal + rightVal} + case "-": + return &object.Float{Value: leftVal - rightVal} + case "-=": + return &object.Float{Value: leftVal - rightVal} + case "*": + return &object.Float{Value: leftVal * rightVal} + case "*=": + return &object.Float{Value: leftVal * rightVal} + case "/": + return &object.Float{Value: leftVal / rightVal} + case "/=": + return &object.Float{Value: leftVal / rightVal} + case "%": + return &object.Float{Value: math.Mod(leftVal, rightVal)} + case "<": + return fromNativeBoolean(leftVal < rightVal) + case "<=": + return fromNativeBoolean(leftVal <= rightVal) + case ">": + return fromNativeBoolean(leftVal > rightVal) + case ">=": + return fromNativeBoolean(leftVal >= rightVal) + case "==": + return fromNativeBoolean(leftVal == rightVal) + case "!=": + return fromNativeBoolean(leftVal != rightVal) + default: + return newError(tok, "unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression(tok token.Token, operator string, left, right object.Object) object.Object { + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + + switch operator { + case "+": + return &object.String{Value: leftVal + rightVal} + default: + return newError(tok, "unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } +} + +func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { + var result object.Object + + for { + condition := Eval(we.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + result = Eval(we.Consequence, env) + if isError(result) { + return result + } + } else { + break + } + } + + if result != nil { + return result + } + return NULL +} + +func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) object.Object { + name := Eval(ie.Name, env) + if isError(name) { + return name + } + + if s, ok := name.(*object.String); ok { + attrs := EvalModule(ie.Token, s.Value) + if isError(attrs) { + return attrs + } + return &object.Module{Name: s.Value, Attrs: attrs} + } + return newError(ie.Token, "ImportError: invalid import path '%s'", name) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR + } + return false +} + +func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins.Builtins[node.Value]; ok { + return builtin + } + + return newError(node.Token, "identifier `%s` not found", node.Value) +} + +func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(tok token.Token, env *object.Environment, fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + fnEnv, err := extendFunctionEnv(tok, fn, args) + if err != nil { + return err + } + + evaluated := Eval(fn.Body, fnEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + if result := fn.Fn(env, args...); result != nil { + return result + } + return NULL + + default: + return newError(tok, "not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv(tok token.Token, fn *object.Function, args []object.Object) (*object.Environment, *object.Error) { + env := fn.Env.Clone() + + for paramIdx, param := range fn.Parameters { + argumentPassed := len(args) > paramIdx + + if !argumentPassed { + return nil, newError(tok, "argument `%s` to function `%s` is missing", param.Value, fn) + } + + env.Set(param.Value, args[paramIdx]) + } + + return env, nil +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.Return); ok { + return returnValue.Value + } + + return obj +} + +/* +func evalIndexAssignmentExpression(left, index, value object.Object) object.Object { + switch { + case left.Type() == object.STRING && index.Type() == object.INTEGER: + return evalStringIndexExpression(left, index) + case left.Type() == object.ARRAY && index.Type() == object.INTEGER: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} +*/ +func evalIndexExpression(tok token.Token, left, index object.Object) object.Object { + switch { + case left.Type() == object.STRING && index.Type() == object.INTEGER: + return evalStringIndexExpression(left, index) + case left.Type() == object.ARRAY && index.Type() == object.INTEGER: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH: + return evalHashIndexExpression(tok, left, index) + case left.Type() == object.MODULE: + return evalModuleIndexExpression(tok, left, index) + default: + return newError(tok, "index operator not supported: %s", left.Type()) + } +} + +func evalHashIndexExpression(tok token.Token, hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError(tok, "unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} + +func evalModuleIndexExpression(tok token.Token, module, index object.Object) object.Object { + moduleObject := module.(*object.Module) + return evalHashIndexExpression(tok, moduleObject.Attrs, index) +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalStringIndexExpression(str, index object.Object) object.Object { + stringObject := str.(*object.String) + idx := index.(*object.Integer).Value + max := int64(len(stringObject.Value) - 1) + + if idx < 0 || idx > max { + return &object.String{Value: ""} + } + + return &object.String{Value: string(stringObject.Value[idx])} +} + +func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError(node.Token, "unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object { + + // Get the value. + obj := Eval(se.Value, env) + + // Try all the choices + for _, opt := range se.Choices { + // skipping the default-case, which we'll + // handle later. + if opt.Default { + continue + } + + // Look at any expression we've got in this case. + for _, val := range opt.Expr { + // Get the value of the case + out := Eval(val, env) + + // Is is a boolean and true? + if (out.Type() == object.BOOLEAN) && (out.Inspect() == "true") { + return evalBlockStatement(opt.Block, env) + } + + // Is it a literal match? + if (obj.Type() == out.Type()) && (obj.Inspect() == out.Inspect()) { + // Evaluate the block and return the value + return evalBlockStatement(opt.Block, env) + } + } + } + + // No match? Handle default if present + for _, opt := range se.Choices { + + // skip default + if opt.Default { + + out := evalBlockStatement(opt.Block, env) + return out + } + } + + return nil +} diff --git a/eval/eval_test.go b/eval/eval_test.go new file mode 100644 index 0000000..6071e66 --- /dev/null +++ b/eval/eval_test.go @@ -0,0 +1,933 @@ +package eval + +import ( + "errors" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/lucasepe/g2d/lexer" + "github.com/lucasepe/g2d/object" + "github.com/lucasepe/g2d/parser" + "github.com/lucasepe/g2d/utils" +) + +func assertEvaluated(t *testing.T, expected interface{}, actual object.Object) { + t.Helper() + + assert := assert.New(t) + + switch expected.(type) { + case nil: + if _, ok := actual.(*object.Null); ok { + assert.True(ok) + } else { + assert.Equal(expected, actual) + } + case int: + if i, ok := actual.(*object.Integer); ok { + assert.Equal(int64(expected.(int)), i.Value) + } else { + assert.Equal(expected, actual) + } + case error: + if e, ok := actual.(*object.Integer); ok { + assert.Equal(expected.(error).Error(), e.Value) + } else { + assert.Equal(expected, actual) + } + case string: + if s, ok := actual.(*object.String); ok { + assert.Equal(expected.(string), s.Value) + } else { + assert.Equal(expected, actual) + } + default: + t.Fatalf("unsupported type for expected got=%T", expected) + } +} + +func TestEvalExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + {"!1", false}, + {"~1", -2}, + {"5 % 2", 1}, + {"1 | 2", 3}, + {"2 ^ 4", 6}, + {"3 & 6", 2}, + {`" " * 4`, " "}, + {`4 * " "`, " "}, + {"1 << 2", 4}, + {"4 >> 2", 1}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + if expected, ok := tt.expected.(int64); ok { + testIntegerObject(t, evaluated, expected) + } else if expected, ok := tt.expected.(bool); ok { + testBooleanObject(t, evaluated, expected) + } + } +} + +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + return true +} + +func testStringObject(t *testing.T, obj object.Object, expected string) bool { + result, ok := obj.(*object.String) + if !ok { + t.Errorf("object is not String. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%s, want=%s", + result.Value, expected) + return false + } + return true +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"!true", false}, + {"!false", true}, + {"true && true", true}, + {"false && true", false}, + {"true && false", false}, + {"false && false", false}, + {"true || true", true}, + {"false || true", true}, + {"true || false", true}, + {"false || false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"(1 <= 2) == true", true}, + {"(1 <= 2) == false", false}, + {"(1 >= 2) == true", false}, + {"(1 >= 2) == false", true}, + {`"a" == "a"`, true}, + {`"a" < "b"`, true}, + {`"abc" == "abc"`, true}, + } + + for _, tt := range tests { + t.Log(tt.input) + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestNullExpression(t *testing.T) { + evaluated := testEval("null") + testNullObject(t, evaluated) +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 < 2) { 10 } else if (1 == 2) { 20 }", 10}, + {"if (1 > 2) { 10 } else if (1 == 2) { 20 } else { 30 }", 30}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestWhileExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"while (false) { }", nil}, + {"n := 0; while (n < 10) { n := n + 1 }; n", 10}, + {"n := 10; while (n > 0) { n := n - 1 }; n", 0}, + {"n := 0; while (n < 10) { n := n + 1 }", nil}, + {"n := 10; while (n > 0) { n := n - 1 }", nil}, + {"n := 0; while (n < 10) { n = n + 1 }; n", 10}, + {"n := 10; while (n > 0) { n = n - 1 }; n", 0}, + {"n := 0; while (n < 10) { n = n + 1 }", nil}, + {"n := 10; while (n > 0) { n = n - 1 }", nil}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "unknown operator: int + bool", + }, + { + "5 + true; 5;", + "unknown operator: int + bool", + }, + { + "-true", + "unknown operator: -bool", + }, + { + "true + false;", + "unknown operator: bool + bool", + }, + { + "5; true + false; 5", + "unknown operator: bool + bool", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: bool + bool", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: bool + bool", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `"Hello" - "World"`, + "unknown operator: str - str", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: fn", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestIndexAssignmentStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"xs := [1, 2, 3]; xs[1] = 4; xs[1];", 4}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestAssignmentExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"a := 0; a = 5;", nil}, + {"a := 0; a = 5; a;", 5}, + {"a := 0; a = 5 * 5;", nil}, + {"a := 0; a = 5 * 5; a;", 25}, + {"a := 0; a = 5; b := 0; b = a;", nil}, + {"a := 0; a = 5; b := 0; b = a; b;", 5}, + {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5;", nil}, + {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5; c;", 15}, + {"a := 5; b := a; a = 0;", nil}, + {"a := 5; b := a; a = 0; b;", 5}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestBindExpressions(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"a := 5; a;", 5}, + {"a := 5 * 5; a;", 25}, + {"a := 5; b := a; b;", 5}, + {"a := 5; b := a; c := a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"identity := fn(x) { x; }; identity(5);", 5}, + {"identity := fn(x) { return x; }; identity(5);", 5}, + {"double := fn(x) { x * 2; }; double(5);", 10}, + {"add := fn(x, y) { x + y; }; add(5, 5);", 10}, + {"add := fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestClosures(t *testing.T) { + input := ` + newAdder := fn(x) { + fn(y) { x + y }; + }; + + addTwo := newAdder(2); + addTwo(2); + ` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `"abc"[0]`, + "a", + }, + { + `"abc"[1]`, + "b", + }, + { + `"abc"[2]`, + "c", + }, + { + `i := 0; "abc"[i];`, + "a", + }, + { + `"abc"[1 + 1];`, + "c", + }, + { + `myString := "abc"; myString[0] + myString[1] + myString[2];`, + "abc", + }, + { + `"abc"[3]`, + "", + }, + { + `"foo"[-1]`, + "", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + str, ok := tt.expected.(string) + if ok { + testStringObject(t, evaluated, string(str)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, errors.New("TypeError: object of type 'int' has no len()")}, + {`len("one", "two")`, errors.New("TypeError: len() takes exactly 1 argument (2 given)")}, + {`len("∑")`, 1}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, errors.New("TypeError: first() expected argument #1 to be `array` got `int`")}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, errors.New("TypeError: last() expected argument #1 to be `array` got `int`")}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, errors.New("TypeError: push() expected argument #1 to be `array` got `int`")}, + {`print("Hello World")`, nil}, + {`input()`, ""}, + {`pop([])`, errors.New("IndexError: pop from an empty array")}, + {`pop([1])`, 1}, + {`bool(1)`, true}, + {`bool(0)`, false}, + {`bool(true)`, true}, + {`bool(false)`, false}, + {`bool(null)`, false}, + {`bool("")`, false}, + {`bool("foo")`, true}, + {`bool([])`, false}, + {`bool([1, 2, 3])`, true}, + {`bool({})`, false}, + {`bool({"a": 1})`, true}, + {`int(true)`, 1}, + {`int(false)`, 0}, + {`int(1)`, 1}, + {`int("10")`, 10}, + {`str(null)`, "null"}, + {`str(true)`, "true"}, + {`str(false)`, "false"}, + {`str(10)`, "10"}, + {`str("foo")`, "foo"}, + {`str([1, 2, 3])`, "[1, 2, 3]"}, + {`str({"a": 1})`, "{\"a\": 1}"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case bool: + testBooleanObject(t, evaluated, expected) + case int: + testIntegerObject(t, evaluated, int64(expected)) + case string: + testStringObject(t, evaluated, expected) + case error: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected.Error() { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayDuplication(t *testing.T) { + input := "[1] * 3" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 1) + testIntegerObject(t, result.Elements[2], 1) +} + +func TestArrayMerging(t *testing.T) { + input := "[1] + [2]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 2 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 2) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "i := 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "myArray := [1, 2, 3]; myArray[2];", + 3, + }, + { + "myArray := [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "myArray := [1, 2, 3]; i := myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `two := "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashMerging(t *testing.T) { + input := `{"a": 1} + {"b": 2}` + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "a"}).HashKey(): 1, + (&object.String{Value: "b"}).HashKey(): 2, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashSelectorExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}.foo`, + 5, + }, + { + `{"foo": 5}.bar`, + nil, + }, + { + `{}.foo`, + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `key := "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestImportExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`mod := import("../testdata/mod"); mod.A`, 5}, + {`mod := import("../testdata/mod"); mod.Sum(2, 3)`, 5}, + {`mod := import("../testdata/mod"); mod.a`, nil}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + assertEvaluated(t, tt.expected, evaluated) + } +} + +func TestImportSearchPaths(t *testing.T) { + utils.AddPath("../testdata") + + tests := []struct { + input string + expected interface{} + }{ + {`mod := import("mod"); mod.A`, 5}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + assertEvaluated(t, tt.expected, evaluated) + } +} + +func TestExamples(t *testing.T) { + matches, err := filepath.Glob("./examples/*.monkey") + if err != nil { + t.Error(err) + } + + for _, match := range matches { + b, err := ioutil.ReadFile(match) + if err != nil { + t.Error(err) + } + testEval(string(b)) + } +} diff --git a/examples/arrays.g2d b/examples/arrays.g2d new file mode 100644 index 0000000..9192eda --- /dev/null +++ b/examples/arrays.g2d @@ -0,0 +1,12 @@ +// Arrays can hold multiple types +a := [1, 2.3, "hello!"] + +// Adding to an array is done via the `push` builtin function +a = push(a, "another") + +// You can iterate over the contents of an array like so: +i := 0 +while( i < len(a) ) { + print("Array index ", i, " contains ", a[i], "\n") + i = i + 1 +} diff --git a/examples/conditionals.g2d b/examples/conditionals.g2d new file mode 100644 index 0000000..0d7d7cd --- /dev/null +++ b/examples/conditionals.g2d @@ -0,0 +1,27 @@ + +// Switch and case expressions +switch n := randi(10) { + case n % 2 == 0 { + print(n, " is even", "\n") + } + default { + print(n, " is odd", "\n") + } +} + +// If-Else statements. +max := fn(a, b) { + if (a > b) { + return a; + } else { + return b; + } +} + +print( max(1, 8), "\n" ) // Outputs: 8 + +i := 30 +while (i > 0) { + print(i, " ") + i = i - 10 +} \ No newline at end of file diff --git a/examples/crisp.g2d b/examples/crisp.g2d new file mode 100644 index 0000000..f4a004a --- /dev/null +++ b/examples/crisp.g2d @@ -0,0 +1,46 @@ + +S := 320 +screensize(S) + +Minor := 10 +Major := 80 + +// minor grid +x := Minor +while (x < S) { + fx := x - S/2 + line(fx, -S/2, fx, S/2) + x = x + Minor +} + +y := Minor +while (y < S) { + fy := y - S/2 + line(-S/2, fy, S/2, fy) + y = y + Minor +} + +pensize(1) +pencolor(0, 0, 0, 0.25) +stroke() + +// major grid +x := Major +while (x < S) { + fx := x - S/2 + line(fx, -S/2, fx, S/2) + x = x + Major +} + +y := Major +while (y < S) { + fy := y - S/2 + line(-S/2, fy, S/2, fy) + y = y + Major +} + +pensize(1) +pencolor(0, 0, 0, 0.5) +stroke() + +snapshot("./examples/crisp.png") \ No newline at end of file diff --git a/examples/crisp.png b/examples/crisp.png new file mode 100644 index 0000000000000000000000000000000000000000..2dfd4a217cec190c7c583f9a629e2d35bb96919d GIT binary patch literal 1869 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Xs4kn<8(*o3`pKo8U zXF9dE->dxgb#aMX=la*l{(paad;Wa2zrVh|ZVK-8e!Kfnh;wi}hIv5ai#tv0A?{hP zZ?I+Oky-XvcU;-0?PF^4m9@-N4nVeVUC?y;rcViQ*_Txo95qKSb( z@dS-=RWa098v?;N1C(rTeqT|ty*4(<5hW4j6G)@pIvJo8yQ*+yZTO6=j90uoiCh@y z`R%u5J7et5yerrJzJG0P-uNoM{_4^K-KW}#^pmbl{`uTP|`70m0qw^7jt1D#QYCt|&g3(kUgc+>D)V1f9w=%>N%`&;k)-}cS&()R7! z`Ty5Go%#0ndtmYM?##W^ zACafcf$jit@n-5F9*i_D3V>zoxBbAH@&Eq?UH=4F-YmHVW@B`-c5u*oBSruaG#vXIsU(YT>j_*j-MB9qH%qFJz`*MY)qlupcZ;q zJj!=S7uJjHM@5A((ALvq3?clT?k4E(={X|c=ePN9mTquorzEeNiHWIc`DkytptF-U zK{*)jZfWM&qhh}D(Op~Hw|kwfHp)D7llRQgL~rr&v9sJJTDsVPZO^q`H{&fv$(rM1 z_o`#7s;cUaMya+o1qM=tsYTOrY;3IV;%^i*NBC2uz<kH7ecQ-3jvadjg&x z?(Pc7on2hi>!5V`LmY9LnVIKaN=9S5uC1-DIGMXBu(gt>Ff}?8Q|qazsawu>P~*UZ z4oXYC0gi`f(dVqhM68L%;?FYB>&Hi5Azzw!p*)o|OM(7iJg?Py$izIMA%svY;E4l+ z?rNNhipuW#^*ql3Z4PEVbc+UlePe^p@fA5uFKU;=)uVq8GQ|~&GQ6x+1Y^CW9u^kX z#O~as;XIFAiF#YQfMh7RWTDt@?9leX!Rx`ss;UR|34b(gZK==O;FaY!W(MJTJ#Heb z^O7&tk?owftsWy8dTHzIm0;-T;6})tzWRczPcK8#7G=5pXXU-AK+2;U|nhay0`an z*qLtUmFpm)%4SW-^ZlZG_kt}aC3*jy+h8!Wm%qQ|w^~yZEJN`=+v(G$-E11WzE~3L{c9g5 zC#SOw`4la-%<1Xr)G>>F#86yf2utQfbl7QN%vz4S30L;#{QQS*J7U==Yn?0MNyk

4Os4fj~o9Y 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 n) { + return 0 + } + + if (k == 0 || k == n) { + return 1 + } + + // Recur + a := comb(n - 1, k - 1) + b := comb(n - 1, k) + + return a + b +} + +print(comb(10, 4), "\n") \ No newline at end of file diff --git a/examples/hashes.g2d b/examples/hashes.g2d new file mode 100644 index 0000000..0e7522d --- /dev/null +++ b/examples/hashes.g2d @@ -0,0 +1,31 @@ +// A hash is a key/value container +a := { + "name":"monkey", + true:1, + 7:"seven", + "amount": 7.5 +} + +print(a, "\n") // Outputs: {name: monkey, true: 1, 7: seven, amount: 7.5} +print(a["amount"], "\n") // Outputs: 7.4 + +// Updating... +a[7] = "sette" +print(a[7], "\n") // Outputs: sette + +a[8] = "otto" + +// Fetch all the keys +ids := keys(a) +// ..and iterate +i := 0 +while (i < len(ids)) { + k := ids[i] + print(i, " Key: ", k, " has Val: ", a[k], "\n") + i = i + 1 +} + +// Delete keys via `delete` +a = delete(a, 8) +a = delete(a, "name") +print(a, "\n") // Outputs: {7: sette, amount: 7.5, true: 1} \ No newline at end of file diff --git a/examples/lines.g2d b/examples/lines.g2d new file mode 100644 index 0000000..0b970e2 --- /dev/null +++ b/examples/lines.g2d @@ -0,0 +1,33 @@ + +randomize := fn(min, max) { + res := min + randf()*(max-min) + return res +} + +S := 320 +screensize(S) + +i := 0 +while (i < 300) { + x1 := randf(-S/2, S/2) + y1 := randf(-S/2, S/2) + + x2 := randf(-S/2, S/2) + y2 := randf(-S/2, S/2) + + w := randf(1, 5) + + r := randf() + g := randf() + b := randf() + a := randf() + + line(x1, y1, x2, y2) + pensize(w) + pencolor(r, g, b, a) + stroke() + + i = i + 1 +} + +snapshot("./examples/lines.png") \ No newline at end of file diff --git a/examples/lines.png b/examples/lines.png new file mode 100644 index 0000000000000000000000000000000000000000..f7cc4f8a2a02a7d53aecf0ed9ba4888babd4c3c4 GIT binary patch literal 192206 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 b00960 0 { + // Search the middle point of every line of the triangle + xA := (p1[0] + p2[0]) / 2 + yA := (p1[1] + p2[1]) / 2 + pA := [xA, yA] + + xB := (p0[0] + p2[0]) / 2 + yB := (p0[1] + p2[1]) / 2 + pB := [xB, yB] + + xC := (p0[0] + p1[0]) / 2 + yC := (p0[1] + p1[1]) / 2 + pC := [xC, yC] + + // Calls the sierpinski function recursively + // Until limit is reached + sierpinski(p0, pB, pC, limit - 1) + sierpinski(pC, pA, p1, limit - 1) + sierpinski(pB, pA, p2, limit - 1) + } else { + triangle(p0, p1, p2) + } +} + +// Canvas size +S := 320 +screensize(S) + +// Triangle side length +side := 0.9*S + +// Triangle Vertices +pA := [-side/2, -side/2] +pB := [side/2, -side/2] +pC := [0, side/2] + +// How many recursions? +deep := 4 + +// Start iterations +sierpinski(pA, pB, pC, deep) + +// Fill the shape +pencolor("#0fd5a5") +fill(true) + +// Stroke the lines +pencolor("#000000") +pensize(2) +stroke() + +// Save the output +snapshot("./examples/sierpinsky.png") \ No newline at end of file diff --git a/examples/sierpinsky.png b/examples/sierpinsky.png new file mode 100644 index 0000000000000000000000000000000000000000..cc31cd37730ad4cc198d9d6708557d48af3197d5 GIT binary patch literal 4447 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 literal 0 HcmV?d00001 diff --git a/examples/snowflake.g2d b/examples/snowflake.g2d new file mode 100644 index 0000000..26caf99 --- /dev/null +++ b/examples/snowflake.g2d @@ -0,0 +1,69 @@ +PI := 3.141592 + +koch := fn(a, b, limit) { + dx := b[0] - a[0] + dy := b[1] - a[1] + dist := hypot(dx, dy) + unit := dist/3 + angle := atan2(dy, dx) + + x1 := a[0] + dx / 3 + y1 := a[1] + dy / 3 + p1 := [x1, y1] + + x2 := x1 + cos(angle - PI / 3) * unit + y2 := y1 + sin(angle - PI / 3) * unit + p2 := [x2, y2] + + x3 := b[0] - dx / 3 + y3 := b[1] - dy / 3 + p3 := [x3, y3] + + if limit > 0 { + koch(a, p1, limit - 1) + koch(p1, p2, limit - 1) + koch(p2, p3, limit - 1) + koch(p3, b, limit - 1) + } else { + saveState() + //moveTo(a[0], a[1]) + lineTo(x1, y1) + lineTo(x2, y2) + lineTo(x3, y3) + lineTo(b[0], b[1]) + restoreState() + } +} + + +// Canvas size +S := 320 +screensize(S) + +// Shape size +side := 0.5*S + +// Shape Vertices +pA := [-side/2, -side/2] +pB := [side/2, -side/2] +pC := [0, side/2] + +// How many recursions? +deep := 3 + +koch(pA, pB, deep) +koch(pB, pC, deep) +koch(pC, pA, deep) + + +// Fill the shape +pencolor("#00ff00") +fill(true) + +// Stroke the lines +pencolor("#000000") +pensize(2) +stroke() + +// Save the output +snapshot("./examples/snowflake.png") \ No newline at end of file diff --git a/examples/snowflake.png b/examples/snowflake.png new file mode 100644 index 0000000000000000000000000000000000000000..e9982c7aaf6f3ca16e5e6a73c444ec9794a68a61 GIT binary patch literal 12807 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 Q0ShI2w6`niCIrz@&+Iq-?tPoF>q}~VzGb$wB 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

Q|8&*FloxO8iS5QJ+ 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

  • QWm79n7|OxN<|dA9I;^1|^vUUquZTi3-- z527%ZbPb=Jm?g_%BKi?Z7a&4hc{g{LIoz>5y4ttiIt?f<2VH9qY!dW2Sca6!KD@ zjUaxo?+k0_E9~=QhuEQ=PXCt=1q4y{i`zbwnC8{1XdL6mrE-VeR}x*Dl{hfx>L~d^hY^~HXytz z&?z`RPwC?>b9?(62djt~DAN?%cXyA~d9qA>L7+p5ryo|OZp~+%@ZAm+F0QT^9OnWZ zI21<2ypOJ?d+0H^GJcYiRySR=}3ZyD}1M9PM zdxS5;=j71X6Zy%g8kkH7&rcOb3JX6?m`BLKk;9BsoWVu|=dahlP7 zg5>^^{f}1b=t2mk`lnC7$4JqSzZZZ;AaKc2Lc1(BHWskQ4R(1aoHXd(>;eecc091< zG_o=`Ba#~Rg1S1-G;zjlwR>TVoSq5PS1^+!yUN7<>h+_?Hwcy#Df-vc$Vib((%9kX z>?~xrMTM{@Gi$#mps)(%oY#gI4?|CC_L?YN7*xa0d+WFT@xhHGKFx|(Q~#M49{*3M zo*jG}d;=RhP)_l2ah3XgCuV{FI;17`AAt51t~nNGrH(zaUwZ zi{zF0M|b3IZnJY0KOqI8!0)DSbPUa++XnSvnM_FY(%8rlH)PY`!?V*{u1)-^h0>%a zvwEL>fx0UDk;TK#?+EEfixMJb7hGRh_yP{79Nq^a}m&SjJ+Rh+4zGDvZAFl1lt1!O;&+1 z{itaxF?0Hyt{}z)<4~$c53KApjCTn#I)xGdf;82QEHRj}Q;S8?p0eMlStTi>X*4Fi z-sZyJWQTZB?a7M>+~VF%?DsV^wqdBjU+O+e%n>kJ!6OMUr*(`ecB7Wv?|1&Zi`Rbi zLO*X@!^hi$1t4H6Vtwk=JX2I4_I}8z`zO17E_I{;$d-?~-o{BMBiDazz9LVW#TnGd zGT*MiLaG5d$N@wE7HfCIyt%)A=mBYC)mphLhxiN^mJ60R&&n(FF)5EFSHokOuKcQ=~k zgYT#N=a6}UtH0BAFO(8Sj`J9=GpQ@hhMeU<*$3(R!q_$qb#=o71Gq+>6csI@6<>3c zzUt!LH~(F|jjBpTF!N=c|3{1&ku$m0sjUj(znOJ>crA0xEP7*Q%*z4Gqy;{#5_iHciH59I8c7=o5qj#FC9qfG2h zv;}~zS(p0WEo!%e_q8lDk}L(+__lsHc~hb)h&tKbPw}_s4smDNu@*cmEv2QSyNy{K zrKO+Yo4I+p?Yp?{#!h6`(U)?HiosidmDhy8nIHx;O1=5YKNTeIDk~Gy=m3i0xY12D zhCdD^W|j(qpWJLde;DBXG4-2dl~GPb#f`>6^q?}ij!t{QPTO$!>BwcwjSnF-&}=wS zdXxV?Ft?s!TICWcxq0(w@QnTa`V^E(#u|U}D=HZNgh5G*-{tT$DE*j$K@RkBYeM)z zfG;v`yYqgD2qQf!L<}KJNYD|@*7*DJ0d&0Ab!CH6g`UMLp?O<+&`{r^8Bw1J#ui5p zB&!ix=nu0{*k6LtCvpq#3|WtYBPL+*(3D@Jj}NNj&6<}8w(=o_@2hoA8-x$Do}K^ue_#Yx(H`7)yN#uycRTRjwZwUP p=ukPkM)-gK_rI`hPaqI>cG}ZL6s&X&Jn=7tV|piaD-Jtc{eSyC^34DM literal 0 HcmV?d00001 diff --git a/examples/star.g2d b/examples/star.g2d new file mode 100644 index 0000000..46795f2 --- /dev/null +++ b/examples/star.g2d @@ -0,0 +1,38 @@ +PI := 3.141592 + +poly := fn(n, x, y, r) { + res := [] + i := 0 + while (i < n) { + a := float(i)*2*PI/float(n) + 0.5 * PI + pt := [x + r * cos(a), y + r * sin(a)] + + res = push(res, pt) + i = i + 1 + } + + return res +} + +S := 320 +screensize(S) + +coords := poly(5, 0, 0, 0.4*S) + +n := len(coords) +i := 0; +while (i < n + 1) { + idx := (i * 2) % n + p := coords[idx] + lineTo(p[0], p[1]) + i = i + 1 +} + +pencolor(0, 0.5, 0, 1) +fill(true) + +pencolor(0, 1, 0, 0.5) +pensize(5) +stroke() + +snapshot("./examples/star.png") \ No newline at end of file diff --git a/examples/star.png b/examples/star.png new file mode 100644 index 0000000000000000000000000000000000000000..2a8e92ac17d79ff1b10f0a3a34b0e6102fc6cff9 GIT binary patch literal 12422 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 begin && pos <= end { + break + } + } + lineNum := idx + 1 + return lineNum, begin, end +} + +// ErrorLine (pos) returns lineNum, column, errorLine +func (l *Lexer) ErrorLine(pos int) (int, int, string) { + lineNum, begin, end := l.linePosition(pos) + errorLine := l.input[begin:end] + column := pos - begin + 1 + return lineNum, column, string(errorLine) +} + +func (l *Lexer) readChar() { + l.prevCh = l.ch + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + + l.position = l.readPosition + l.readPosition++ +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } + + return l.input[l.readPosition] +} + +// NextToken returns the next token read from the input stream +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '#': + tok = token.New(token.COMMENT, l.position, l.readLine()) + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.New(token.EQ, l.position, literal) + } else { + tok = token.New(token.ASSIGN, l.position, string(l.ch)) + } + case '+': + tok = token.New(token.PLUS, l.position, string(l.ch)) + case '-': + tok = token.New(token.MINUS, l.position, string(l.ch)) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.New(token.NEQ, l.position, literal) + } else { + tok = token.New(token.NOT, l.position, string(l.ch)) + } + case '/': + if l.peekChar() == '/' { + l.readChar() // skip over the '/' + tok = token.New(token.COMMENT, l.position, l.readLine()) + } else { + tok = token.New(token.DIVIDE, l.position, string(l.ch)) + } + case '*': + tok = token.New(token.MULTIPLY, l.position, string(l.ch)) + case '%': + tok = token.New(token.MODULO, l.position, string(l.ch)) + case '&': + if l.peekChar() == '&' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.New(token.AND, l.position, literal) + } else { + tok = token.New(token.ILLEGAL, l.position, string(l.ch)) + //tok = token.New(token.BitwiseAND, l.position, string(l.ch)) + } + case '|': + if l.peekChar() == '|' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.New(token.OR, l.position, literal) + } else { + tok = token.New(token.ILLEGAL, l.position, string(l.ch)) + //tok = token.New(token.BitwiseOR, l.position, string(l.ch)) + } + case '<': + if l.peekChar() == '=' { + l.readChar() + tok = token.New(token.LTE, l.position, "<=") + } else { + tok = token.New(token.LT, l.position, string(l.ch)) + } + case '>': + if l.peekChar() == '=' { + l.readChar() + tok = token.New(token.GTE, l.position, ">=") + } else { + tok = token.New(token.GT, l.position, string(l.ch)) + } + case ';': + tok = token.New(token.SEMICOLON, l.position, string(l.ch)) + case ':': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.New(token.BIND, l.position, literal) + } else { + tok = token.New(token.COLON, l.position, string(l.ch)) + } + case ',': + tok = token.New(token.COMMA, l.position, string(l.ch)) + case '.': + tok = token.New(token.DOT, l.position, string(l.ch)) + case '(': + tok = token.New(token.LPAREN, l.position, string(l.ch)) + case ')': + tok = token.New(token.RPAREN, l.position, string(l.ch)) + case '{': + tok = token.New(token.LBRACE, l.position, string(l.ch)) + case '}': + tok = token.New(token.RBRACE, l.position, string(l.ch)) + case '[': + tok = token.New(token.LBRACKET, l.position, string(l.ch)) + case ']': + tok = token.New(token.RBRACKET, l.position, string(l.ch)) + case 0: + tok = token.New(token.EOF, l.position, "") + case '"': + str, err := l.readString() + if err != nil { + tok = token.New(token.ILLEGAL, l.position, string(l.prevCh)) + } else { + tok = token.New(token.STRING, l.position, str) + } + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Position = l.position + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + integer := l.readNumber() + if l.ch == '.' && isDigit(l.peekChar()) { + // OK here we think we've got a float. + l.readChar() + fraction := l.readNumber() + + tok.Type = token.FLOAT + tok.Position = l.position + tok.Literal = fmt.Sprintf("%s.%s", integer, fraction) + } else { + tok.Type = token.INT + tok.Position = l.position + tok.Literal = integer + } + return tok + } else { + tok = token.New(token.ILLEGAL, l.position, string(l.ch)) + } + } + + l.readChar() + + return tok +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) || isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readLine() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '\r' || l.ch == '\n' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() (string, error) { + b := &strings.Builder{} + for { + l.readChar() + + // Support some basic escapes like \" + if l.ch == '\\' { + switch l.peekChar() { + case '"': + b.WriteByte('"') + case 'n': + b.WriteByte('\n') + case 'r': + b.WriteByte('\r') + case 't': + b.WriteByte('\t') + case '\\': + b.WriteByte('\\') + case 'x': + // Skip over the the '\\', 'x' and the next two bytes (hex) + l.readChar() + l.readChar() + l.readChar() + src := string([]byte{l.prevCh, l.ch}) + dst, err := hex.DecodeString(src) + if err != nil { + return "", err + } + b.Write(dst) + continue + } + + // Skip over the '\\' and the matched single escape char + l.readChar() + continue + } else { + if l.ch == '"' || l.ch == 0 { + break + } + } + + b.WriteByte(l.ch) + } + + return b.String(), nil +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\r' || l.ch == '\n' { + l.readChar() + } +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go new file mode 100644 index 0000000..2b0f6a7 --- /dev/null +++ b/lexer/lexer_test.go @@ -0,0 +1,198 @@ +package lexer + +import ( + "testing" + + "github.com/lucasepe/g2d/token" +) + +func TestNextToken(t *testing.T) { + input := `#!./g2d +five := 5; +ten := 10; +fl := 8.88 + +add := fn(x, y) { + x + y; +}; + +# this is a comment +result := add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +// this is another comment +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2] +{"foo": "bar"} +d.foo +!&&|| +` + + tests := []struct { + expectedType token.Type + expectedLiteral string + }{ + {token.COMMENT, "!./g2d"}, + {token.IDENT, "five"}, + {token.BIND, ":="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IDENT, "ten"}, + {token.BIND, ":="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.IDENT, "fl"}, + {token.BIND, ":="}, + {token.FLOAT, "8.88"}, + {token.IDENT, "add"}, + {token.BIND, ":="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.COMMENT, " this is a comment"}, + {token.IDENT, "result"}, + {token.BIND, ":="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.NOT, "!"}, + {token.MINUS, "-"}, + {token.DIVIDE, "/"}, + {token.MULTIPLY, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.COMMENT, " this is another comment"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NEQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.IDENT, "d"}, + {token.DOT, "."}, + {token.IDENT, "foo"}, + {token.NOT, "!"}, + {token.AND, "&&"}, + {token.OR, "||"}, + {token.EOF, ""}, + } + + lexer := New(input) + + for i, test := range tests { + token := lexer.NextToken() + + if token.Type != test.expectedType { + t.Fatalf("tests[%d] - token type wrong. expected=%q, got=%q", + i, test.expectedType, token.Type) + } + + if token.Literal != test.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, test.expectedLiteral, token.Literal) + } + } +} + +func TestStringEscapes(t *testing.T) { + input := `#!./g2d +a := "\"foo\"" +b := "\x00\x0a\x7f" +c := "\r\n\t" +` + + tests := []struct { + expectedType token.Type + expectedLiteral string + }{ + {token.COMMENT, "!./g2d"}, + {token.IDENT, "a"}, + {token.BIND, ":="}, + {token.STRING, "\"foo\""}, + {token.IDENT, "b"}, + {token.BIND, ":="}, + {token.STRING, "\x00\n\u007f"}, + {token.IDENT, "c"}, + {token.BIND, ":="}, + {token.STRING, "\r\n\t"}, + {token.EOF, ""}, + } + + lexer := New(input) + + for i, test := range tests { + token := lexer.NextToken() + + if token.Type != test.expectedType { + t.Fatalf("tests[%d] - token type wrong. expected=%q, got=%q", + i, test.expectedType, token.Type) + } + + if token.Literal != test.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, test.expectedLiteral, token.Literal) + } + } + +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..0948336 --- /dev/null +++ b/main.go @@ -0,0 +1,88 @@ +package main + +// Package main implements the main process which executes a program if +// a filename is supplied as an argument or invokes the interpreter's +// REPL and waits for user input before lexing, parsing nad evaulating. + +import ( + "flag" + "fmt" + "log" + "os" + "os/user" + "path" + "strings" + + "github.com/lucasepe/g2d/repl" +) + +var ( + // Version release version + Version = "0.0.1" + + // GitCommit will be overwritten automatically by the build system + GitCommit = "HEAD" +) + +var ( + interactive bool + version bool + debug bool +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] []\n", path.Base(os.Args[0])) + flag.PrintDefaults() + os.Exit(0) + } + + flag.BoolVar(&version, "v", false, "display version information") + flag.BoolVar(&debug, "d", false, "enable debug mode") + + flag.BoolVar(&interactive, "i", false, "enable interactive mode") +} + +// Indent indents a block of text with an indent string +func Indent(text, indent string) string { + if text[len(text)-1:] == "\n" { + result := "" + for _, j := range strings.Split(text[:len(text)-1], "\n") { + result += indent + j + "\n" + } + return result + } + result := "" + for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") { + result += indent + j + "\n" + } + return result[:len(result)-1] +} + +// FullVersion returns the full version and commit hash +func FullVersion() string { + return fmt.Sprintf("%s@%s", Version, GitCommit) +} + +func main() { + flag.Parse() + + if version { + fmt.Printf("%s %s", path.Base(os.Args[0]), FullVersion()) + os.Exit(0) + } + + user, err := user.Current() + if err != nil { + log.Fatalf("could not determine current user: %s", err) + } + + args := flag.Args() + + opts := &repl.Options{ + Debug: debug, + Interactive: interactive, + } + repl := repl.New(user.Username, args, opts) + repl.Run() +} diff --git a/object/array.go b/object/array.go new file mode 100644 index 0000000..5011928 --- /dev/null +++ b/object/array.go @@ -0,0 +1,117 @@ +package object + +import ( + "bytes" + "strings" +) + +// Array is the array literal type that holds a slice of Object(s) +type Array struct { + Elements []Object +} + +func (ar *Array) Bool() bool { + return len(ar.Elements) > 0 +} + +func (ar *Array) PopLeft() Object { + if len(ar.Elements) > 0 { + e := ar.Elements[0] + ar.Elements = ar.Elements[1:] + return e + } + return &Null{} +} + +func (ar *Array) PopRight() Object { + if len(ar.Elements) > 0 { + e := ar.Elements[(len(ar.Elements) - 1)] + ar.Elements = ar.Elements[:(len(ar.Elements) - 1)] + return e + } + return &Null{} +} + +func (ar *Array) Prepend(obj Object) { + ar.Elements = append([]Object{obj}, ar.Elements...) +} + +func (ar *Array) Append(obj Object) { + ar.Elements = append(ar.Elements, obj) +} + +func (ar *Array) Copy() *Array { + elements := make([]Object, len(ar.Elements)) + for i, e := range ar.Elements { + elements[i] = e + } + return &Array{Elements: elements} +} + +func (ar *Array) Reverse() { + for i, j := 0, len(ar.Elements)-1; i < j; i, j = i+1, j-1 { + ar.Elements[i], ar.Elements[j] = ar.Elements[j], ar.Elements[i] + } +} + +func (ar *Array) Len() int { + return len(ar.Elements) +} + +func (ar *Array) Swap(i, j int) { + ar.Elements[i], ar.Elements[j] = ar.Elements[j], ar.Elements[i] +} + +func (ar *Array) Less(i, j int) bool { + if cmp, ok := ar.Elements[i].(Comparable); ok { + return cmp.Compare(ar.Elements[j]) == -1 + } + return false +} + +func (ar *Array) Compare(other Object) int { + if obj, ok := other.(*Array); ok { + if len(ar.Elements) != len(obj.Elements) { + return -1 + } + for i, el := range ar.Elements { + cmp, ok := el.(Comparable) + if !ok { + return -1 + } + if cmp.Compare(obj.Elements[i]) != 0 { + return cmp.Compare(obj.Elements[i]) + } + } + + return 0 + } + return -1 +} + +func (ar *Array) String() string { return ar.Inspect() } + +// Type returns the type of the object +func (ar *Array) Type() Type { return ARRAY } + +// Inspect returns a stringified version of the object for debugging +func (ar *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ar.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (ar *Array) ToInterface() interface{} { return ar.Elements } diff --git a/object/bool.go b/object/bool.go new file mode 100644 index 0000000..97179ed --- /dev/null +++ b/object/bool.go @@ -0,0 +1,50 @@ +package object + +import ( + "fmt" +) + +// Boolean is the boolean type and used to represent boolean literals and +// holds an interval bool value +type Boolean struct { + Value bool +} + +func (b *Boolean) Bool() bool { + return b.Value +} + +func (b *Boolean) Int() int { + if b.Value { + return 1 + } + return 0 +} + +func (b *Boolean) Compare(other Object) int { + if obj, ok := other.(*Boolean); ok { + return b.Int() - obj.Int() + } + return 1 +} + +func (b *Boolean) String() string { + return b.Inspect() +} + +// Clone creates a new copy +func (b *Boolean) Clone() Object { + return &Boolean{Value: b.Value} +} + +// Type returns the type of the object +func (b *Boolean) Type() Type { return BOOLEAN } + +// Inspect returns a stringified version of the object for debugging +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (b *Boolean) ToInterface() interface{} { return b.Value } diff --git a/object/builtin.go b/object/builtin.go new file mode 100644 index 0000000..515eecc --- /dev/null +++ b/object/builtin.go @@ -0,0 +1,38 @@ +package object + +import ( + "fmt" +) + +// Builtin is the builtin object type that simply holds a reference to +// a BuiltinFunction type that takes zero or more objects as arguments +// and returns an object. +type Builtin struct { + Name string + Fn BuiltinFunction + Env *Environment +} + +func (b *Builtin) Bool() bool { + return true +} + +func (b *Builtin) String() string { + return b.Inspect() +} + +// Type returns the type of the object +func (b *Builtin) Type() Type { return BUILTIN } + +// Inspect returns a stringified version of the object for debugging +func (b *Builtin) Inspect() string { + return fmt.Sprintf("", b.Name) +} + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (b *Builtin) ToInterface() interface{} { + return "" +} diff --git a/object/environment.go b/object/environment.go new file mode 100644 index 0000000..706102d --- /dev/null +++ b/object/environment.go @@ -0,0 +1,71 @@ +package object + +import ( + "unicode" + + "github.com/lucasepe/g2d/canvas" +) + +// NewEnvironment constructs a new Environment object to hold bindings +// of identifiers to their names +func NewEnvironment() *Environment { + return &Environment{ + store: make(map[string]Object), + canvas: &Screen{ + Value: canvas.NewCanvas(), + }, + } +} + +// Environment is an object that holds a mapping of names to bound objets +type Environment struct { + canvas *Screen + store map[string]Object + parent *Environment +} + +// ExportedHash returns a new Hash with the names and values of every publically +// exported binding in the environment. That is every binding that starts with a +// capital letter. This is used by the module import system to wrap up the +// evaulated module into an object. +func (e *Environment) ExportedHash() *Hash { + pairs := make(map[HashKey]HashPair) + for k, v := range e.store { + if unicode.IsUpper(rune(k[0])) { + s := &String{Value: k} + pairs[s.HashKey()] = HashPair{Key: s, Value: v} + } + } + return &Hash{Pairs: pairs} +} + +// Clone returns a new Environment with the parent set to the current +// environment (enclosing environment) +func (e *Environment) Clone() *Environment { + // Create a new Environment referring to the same `cursor` + // for this reason im not calling `NewEnvironment` + env := &Environment{ + store: make(map[string]Object), + canvas: e.canvas, + } + env.parent = e + return env +} + +// Get returns the object bound by name +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.parent != nil { + obj, ok = e.parent.Get(name) + } + return obj, ok +} + +// Set stores the object with the given name +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} + +// Canvas returns the canvas +func (e *Environment) Canvas() *Screen { return e.canvas } diff --git a/object/error.go b/object/error.go new file mode 100644 index 0000000..a676f49 --- /dev/null +++ b/object/error.go @@ -0,0 +1,33 @@ +package object + +// Error is the error type and used to hold a message denoting the details of +// error encountered. This object is trakced through the evaluator and when +// encountered stops evaulation of the program or body of a function. +type Error struct { + Message string +} + +func (e *Error) Bool() bool { + return false +} + +func (e *Error) String() string { + return e.Message +} + +// Clone creates a new copy +func (e *Error) Clone() Object { + return &Error{Message: e.Message} +} + +// Type returns the type of the object +func (e *Error) Type() Type { return ERROR } + +// Inspect returns a stringified version of the object for debugging +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (e *Error) ToInterface() interface{} { return "" } diff --git a/object/float.go b/object/float.go new file mode 100644 index 0000000..d9017f2 --- /dev/null +++ b/object/float.go @@ -0,0 +1,44 @@ +package object + +import ( + "math/big" + "strconv" +) + +// Float is the float type used to represent float literals and holds +// an internal float64 value +type Float struct { + Value float64 +} + +func (f *Float) Bool() bool { + return f.Value != 0 +} + +func (f *Float) Compare(other Object) int { + if obj, ok := other.(*Float); ok { + return big.NewFloat(f.Value).Cmp(big.NewFloat(obj.Value)) + } + return -1 +} + +func (f *Float) String() string { + return f.Inspect() +} + +// Clone creates a new copy +func (f *Float) Clone() Object { + return &Float{Value: f.Value} +} + +// Type returns the type of the object +func (f *Float) Type() Type { return FLOAT } + +// Inspect returns a stringified version of the object for debugging +func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (f *Float) ToInterface() interface{} { return f.Value } diff --git a/object/function.go b/object/function.go new file mode 100644 index 0000000..ee3f092 --- /dev/null +++ b/object/function.go @@ -0,0 +1,96 @@ +package object + +import ( + "bytes" + "strings" + + "github.com/lucasepe/g2d/ast" +) + +// Function is the function type that holds the function's formal parameters, +// body and an environment to support closures. +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Bool() bool { + return false +} + +func (f *Function) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(")") + + return out.String() +} + +// Type returns the type of the object +func (f *Function) Type() Type { return FUNCTION } + +// Inspect returns a stringified version of the object for debugging +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (f *Function) ToInterface() interface{} { + return "" +} + +// Return is the return type and used to hold the value of another object. +// This is used for `return` statements and this object is tracked through +// the evalulator and when encountered stops evaluation of the program, +// or body of a function. +type Return struct { + Value Object +} + +func (rv *Return) Bool() bool { + return true +} + +func (rv *Return) String() string { + return rv.Inspect() +} + +// Type returns the type of the object +func (rv *Return) Type() Type { return RETURN } + +// Inspect returns a stringified version of the object for debugging +func (rv *Return) Inspect() string { return rv.Value.Inspect() } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (rv *Return) ToInterface() interface{} { + return "" +} diff --git a/object/hash.go b/object/hash.go new file mode 100644 index 0000000..a4fbe29 --- /dev/null +++ b/object/hash.go @@ -0,0 +1,125 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "strings" +) + +// HashKey represents a hash key object and holds the Type of Object +// hashed and its hash value in Value +type HashKey struct { + Type Type + Value uint64 +} + +// HashKey returns a HashKey object +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +// HashKey returns a HashKey object +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +// HashKey returns a HashKey object +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +// HashKey returns a hash key for the given object. +func (f *Float) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(f.Inspect())) + return HashKey{Type: f.Type(), Value: h.Sum64()} +} + +// HashPair is an object that holds a key and value of type Object +type HashPair struct { + Key Object + Value Object +} + +// Hash is a hash map and holds a map of HashKey to HashPair(s) +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Len() int { + return len(h.Pairs) +} + +func (h *Hash) Bool() bool { + return len(h.Pairs) > 0 +} + +func (h *Hash) Compare(other Object) int { + if obj, ok := other.(*Hash); ok { + if len(h.Pairs) != len(obj.Pairs) { + return -1 + } + for _, pair := range h.Pairs { + left := pair.Value + hashed := left.(Hashable) + right, ok := obj.Pairs[hashed.HashKey()] + if !ok { + return -1 + } + cmp, ok := left.(Comparable) + if !ok { + return -1 + } + if cmp.Compare(right.Value) != 0 { + return cmp.Compare(right.Value) + } + } + + return 0 + } + return -1 +} + +func (h *Hash) String() string { + return h.Inspect() +} + +// Type returns the type of the object +func (h *Hash) Type() Type { return HASH } + +// Inspect returns a stringified version of the object for debugging +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (h *Hash) ToInterface() interface{} { + return "" +} diff --git a/object/int.go b/object/int.go new file mode 100644 index 0000000..52f5cbc --- /dev/null +++ b/object/int.go @@ -0,0 +1,50 @@ +package object + +import ( + "fmt" +) + +// Integer is the integer type used to represent integer literals and holds +// an internal int64 value +type Integer struct { + Value int64 +} + +func (i *Integer) Bool() bool { + return i.Value != 0 +} + +func (i *Integer) Compare(other Object) int { + if obj, ok := other.(*Integer); ok { + switch { + case i.Value < obj.Value: + return -1 + case i.Value > obj.Value: + return 1 + default: + return 0 + } + } + return -1 +} + +func (i *Integer) String() string { + return i.Inspect() +} + +// Clone creates a new copy +func (i *Integer) Clone() Object { + return &Integer{Value: i.Value} +} + +// Type returns the type of the object +func (i *Integer) Type() Type { return INTEGER } + +// Inspect returns a stringified version of the object for debugging +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (i *Integer) ToInterface() interface{} { return i.Value } diff --git a/object/module.go b/object/module.go new file mode 100644 index 0000000..a75ece0 --- /dev/null +++ b/object/module.go @@ -0,0 +1,37 @@ +package object + +import ( + "fmt" +) + +// Module is the module type used to represent a collection of variabels. +type Module struct { + Name string + Attrs Object +} + +func (m *Module) Bool() bool { + return true +} + +func (m *Module) Compare(other Object) int { + return 1 +} + +func (m *Module) String() string { + return m.Inspect() +} + +// Type returns the type of the object +func (m *Module) Type() Type { return MODULE } + +// Inspect returns a stringified version of the object for debugging +func (m *Module) Inspect() string { return fmt.Sprintf("", m.Name) } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (m *Module) ToInterface() interface{} { + return "" +} diff --git a/object/null.go b/object/null.go new file mode 100644 index 0000000..476b55f --- /dev/null +++ b/object/null.go @@ -0,0 +1,31 @@ +package object + +// Null is the null type and used to represent the absence of a value +type Null struct{} + +func (n *Null) Bool() bool { + return false +} + +func (n *Null) Compare(other Object) int { + if _, ok := other.(*Null); ok { + return 0 + } + return 1 +} + +func (n *Null) String() string { + return n.Inspect() +} + +// Type returns the type of the object +func (n *Null) Type() Type { return NULL } + +// Inspect returns a stringified version of the object for debugging +func (n *Null) Inspect() string { return "null" } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (n *Null) ToInterface() interface{} { return "" } diff --git a/object/object.go b/object/object.go new file mode 100644 index 0000000..270559a --- /dev/null +++ b/object/object.go @@ -0,0 +1,92 @@ +package object + +// Package object implements the object system (or value system) of Monkey +// used to both represent values as the evaluator encounters and constructs +// them as well as how the user interacts with values. + +import ( + "fmt" +) + +const ( + // INTEGER is the Integer object type + INTEGER = "int" + + // FLOAT is the Float object type + FLOAT = "float" + + // STRING is the String object type + STRING = "str" + + // BOOLEAN is the Boolean object type + BOOLEAN = "bool" + + // NULL is the Null object type + NULL = "null" + + // RETURN is the Return object type + RETURN = "return" + + // ERROR is the Error object type + ERROR = "error" + + // FUNCTION is the Function object type + FUNCTION = "fn" + + // BUILTIN is the Builtin object type + BUILTIN = "builtin" + + // ARRAY is the Array object type + ARRAY = "array" + + // HASH is the Hash object type + HASH = "hash" + + // MODULE is the Module object type + MODULE = "module" + + // CANVAS is the Canvas object type + CANVAS = "canvas" +) + +// Comparable is the interface for comparing two Object and their underlying +// values. It is the responsibility of the caller (left) to check for types. +// Returns `true` iif the types and values are identical, `false` otherwise. +type Comparable interface { + Compare(other Object) int +} + +// Sizeable is the interface for returning the size of an Object. +// Object(s) that have a valid size must implement this interface and the +// Len() method. +type Sizeable interface { + Len() int +} + +// Immutable is the interface for all immutable objects which must implement +// the Clone() method used by binding names to values. +type Immutable interface { + Clone() Object +} + +// Hashable is the interface for all hashable objects which must implement +// the HashKey() method which reutrns a HashKey result. +type Hashable interface { + HashKey() HashKey +} + +// BuiltinFunction represents the builtin function type +type BuiltinFunction func(env *Environment, args ...Object) Object + +// Type represents the type of an object +type Type string + +// Object represents a value and implementations are expected to implement +// `Type()` and `Inspect()` functions +type Object interface { + fmt.Stringer + Type() Type + Bool() bool + Inspect() string + ToInterface() interface{} +} diff --git a/object/object_test.go b/object/object_test.go new file mode 100644 index 0000000..4be962f --- /dev/null +++ b/object/object_test.go @@ -0,0 +1,24 @@ +package object + +import ( + "testing" +) + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} diff --git a/object/screen.go b/object/screen.go new file mode 100644 index 0000000..c02d388 --- /dev/null +++ b/object/screen.go @@ -0,0 +1,25 @@ +package object + +import ( + "github.com/lucasepe/g2d/canvas" +) + +type Screen struct { + Value *canvas.Canvas +} + +func (sc *Screen) String() string { return sc.Inspect() } + +func (sc *Screen) Bool() bool { return sc.Value != nil } + +// Type returns the type of the object +func (sc *Screen) Type() Type { return CANVAS } + +// Inspect returns a stringified version of the object for debugging +func (sc *Screen) Inspect() string { return "" } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (sc *Screen) ToInterface() interface{} { return "" } diff --git a/object/state.go b/object/state.go new file mode 100644 index 0000000..7e8e85a --- /dev/null +++ b/object/state.go @@ -0,0 +1,15 @@ +package object + +import ( + "io" +) + +var ( + WorkDir string + SourceFile string + SaveCounter int + Arguments []string + StandardInput io.Reader + StandardOutput io.Writer + ExitFunction func(int) +) diff --git a/object/str.go b/object/str.go new file mode 100644 index 0000000..850ed15 --- /dev/null +++ b/object/str.go @@ -0,0 +1,54 @@ +package object + +import ( + "unicode/utf8" +) + +// String is the string type used to represent string literals and holds +// an internal string value +type String struct { + Value string +} + +func (s *String) Len() int { + return utf8.RuneCountInString(s.Value) +} + +func (s *String) Bool() bool { + return s.Value != "" +} + +func (s *String) Compare(other Object) int { + if obj, ok := other.(*String); ok { + switch { + case s.Value < obj.Value: + return -1 + case s.Value > obj.Value: + return 1 + default: + return 0 + } + } + return 1 +} + +func (s *String) String() string { + return s.Value +} + +// Clone creates a new copy +func (s *String) Clone() Object { + return &String{Value: s.Value} +} + +// Type returns the type of the object +func (s *String) Type() Type { return STRING } + +// Inspect returns a stringified version of the object for debugging +func (s *String) Inspect() string { return s.Value } + +// ToInterface converts this object to a go-interface, which will allow +// it to be used naturally in our sprintf/printf primitives. +// +// It might also be helpful for embedded users. +func (s *String) ToInterface() interface{} { return s.Value } diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..98ccdae --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,778 @@ +package parser + +// Package parser is used to parse input-programs written in g2d +// and convert them to an abstract-syntax tree. + +import ( + "fmt" + "strconv" + + "github.com/lucasepe/g2d/ast" + "github.com/lucasepe/g2d/lexer" + "github.com/lucasepe/g2d/token" +) + +const ( + _ int = iota + // LOWEST the lowest + LOWEST + OR + AND + NOT + IN + ASSIGN // := or = + EQUALS // == + LESSGREATER // > or < + + SUM // + or - + PRODUCT // * / or % + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] + +) + +// precedence table: it associates token types with their precedence +var precedences = map[token.Type]int{ + token.OR: OR, + token.AND: AND, + token.NOT: NOT, + token.BIND: ASSIGN, + token.ASSIGN: ASSIGN, + token.EQ: EQUALS, + token.NEQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.LTE: LESSGREATER, + token.GTE: LESSGREATER, + + token.PLUS: SUM, + token.MINUS: SUM, + token.DIVIDE: PRODUCT, + token.MULTIPLY: PRODUCT, + token.MODULO: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, + token.DOT: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +// Parser object +type Parser struct { + // l is our lexer + l *lexer.Lexer + + // errors holds parsing-errors. + errors []string + + // curToken holds the current token from our lexer. + curToken token.Token + + // peekToken holds the next token which will come from the lexer. + peekToken token.Token + + // prefixParseFns holds a map of parsing methods for + // prefix-based syntax. + prefixParseFns map[token.Type]prefixParseFn + + // infixParseFns holds a map of parsing methods for + // infix-based syntax. + infixParseFns map[token.Type]infixParseFn +} + +// New returns our new parser-object. +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + // Register prefix-functions + p.prefixParseFns = make(map[token.Type]prefixParseFn) + p.registerPrefix(token.NOT, p.parsePrefixExpression) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.FLOAT, p.parseFloatLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.NULL, p.parseNull) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.WHILE, p.parseWhileExpression) + p.registerPrefix(token.IMPORT, p.parseImportExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.SWITCH, p.parseSwitchStatement) + + // Register infix functions + p.infixParseFns = make(map[token.Type]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.DIVIDE, p.parseInfixExpression) + p.registerInfix(token.MULTIPLY, p.parseInfixExpression) + p.registerInfix(token.MODULO, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NEQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.LTE, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + p.registerInfix(token.GTE, p.parseInfixExpression) + p.registerInfix(token.OR, p.parseInfixExpression) + p.registerInfix(token.AND, p.parseInfixExpression) + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + p.registerInfix(token.BIND, p.parseBindExpression) + p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.DOT, p.parseSelectorExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +// ParseProgram used to parse the whole program +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +// Errors return stored errors +func (p *Parser) Errors() []string { + return p.errors +} + +// registerPrefix registers a function for handling a prefix-based statement +func (p *Parser) registerPrefix(tokenType token.Type, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +// registerInfix registers a function for handling a infix-based statement +func (p *Parser) registerInfix(tokenType token.Type, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} + +// nextToken moves to our next token from the lexer. +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() + + if p.curTokenIs(token.ILLEGAL) { + msg := fmt.Sprintf("Illegal token `%s`", p.curToken.Literal) + p.reportError(msg, p.curToken) + } +} + +func (p *Parser) curTokenIs(t token.Type) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.Type) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.Type) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } + + p.peekError(p.curToken) + return false +} + +// peekError raises an error if the next token is not the expected type. +func (p *Parser) peekError(tok token.Token) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", tok.Type, p.peekToken.Type) + p.reportError(msg, tok) +} + +// report error at token location +func (p *Parser) reportError(err string, tok token.Token) { + lineNum, column, errorLine := p.l.ErrorLine(tok.Position) + msg := fmt.Sprintf("%s\n\t[%d:%d]\t%s", err, lineNum, column, errorLine) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(tok token.Token) { + msg := fmt.Sprintf("no prefix parse function for '%s' found", tok.Literal) + p.reportError(msg, tok) +} + +func (p *Parser) noInfixParseFnError(tok token.Token) { + msg := fmt.Sprintf("no infix parse function for %s found", tok.Literal) + p.reportError(msg, tok) +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.COMMENT: + return p.parseComment() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseComment() ast.Statement { + return &ast.Comment{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + p.noInfixParseFnError(p.peekToken) + return nil + //return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +// parseFloatLiteral parses a float-literal +func (p *Parser) parseFloatLiteral() ast.Expression { + lit := &ast.FloatLiteral{Token: p.curToken} + value, err := strconv.ParseFloat(p.curToken.Literal, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as float", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + lit.Value = value + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseNull() ast.Expression { + return &ast.Null{Token: p.curToken} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + //if !p.expectPeek(token.LPAREN) { + // return nil + //} + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + //if !p.expectPeek(token.RPAREN) { + // return nil + //} + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if p.peekTokenIs(token.IF) { + p.nextToken() + expression.Alternative = &ast.BlockStatement{ + Statements: []ast.Statement{ + &ast.ExpressionStatement{ + Expression: p.parseIfExpression(), + }, + }, + } + return expression + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseWhileExpression() ast.Expression { + expression := &ast.WhileExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + return expression +} + +func (p *Parser) parseImportExpression() ast.Expression { + expression := &ast.ImportExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Name = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseExpressionList(end token.Type) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseSelectorExpression(exp ast.Expression) ast.Expression { + p.expectPeek(token.IDENT) + index := &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} + return &ast.IndexExpression{Left: exp, Index: index} +} + +func (p *Parser) parseBindExpression(exp ast.Expression) ast.Expression { + switch node := exp.(type) { + case *ast.Identifier: + default: + msg := fmt.Sprintf("expected identifier expression on left but got %T %#v", node, exp) + p.errors = append(p.errors, msg) + return nil + } + + be := &ast.BindExpression{Token: p.curToken, Left: exp} + + p.nextToken() + + be.Value = p.parseExpression(LOWEST) + + // Correctly bind the function literal to its name so that self-recursive + // functions work. This is used by the compiler to emit LoadSelf so a ref + // to the current function is available. + if fl, ok := be.Value.(*ast.FunctionLiteral); ok { + ident := be.Left.(*ast.Identifier) + fl.Name = ident.Value + } + + return be +} + +func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { + switch node := exp.(type) { + case *ast.Identifier, *ast.IndexExpression: + default: + msg := fmt.Sprintf("expected identifier or index expression on left but got %T %#v", node, exp) + p.errors = append(p.errors, msg) + return nil + } + + ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} + + p.nextToken() + + ae.Value = p.parseExpression(LOWEST) + + return ae +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +// parseSwitchStatement handles a switch statement +func (p *Parser) parseSwitchStatement() ast.Expression { + + // switch + expression := &ast.SwitchExpression{Token: p.curToken} + + // look for (xx) + //if !p.expectPeek(token.LPAREN) { + // return nil + //} + p.nextToken() + expression.Value = p.parseExpression(LOWEST) + if expression.Value == nil { + return nil + } + //if !p.expectPeek(token.RPAREN) { + // return nil + //} + + // Now we have a block containing blocks. + if !p.expectPeek(token.LBRACE) { + return nil + } + p.nextToken() + + // Process the block which we think will contain + // various case-statements + for !p.curTokenIs(token.RBRACE) { + + if p.curTokenIs(token.EOF) { + p.errors = append(p.errors, "unterminated switch statement") + return nil + } + tmp := &ast.CaseExpression{Token: p.curToken} + + // Default will be handled specially + if p.curTokenIs(token.DEFAULT) { + + // We have a default-case here. + tmp.Default = true + + } else if p.curTokenIs(token.CASE) { + + // skip "case" + p.nextToken() + + // Here we allow "case default" even though + // most people would prefer to write "default". + if p.curTokenIs(token.DEFAULT) { + tmp.Default = true + } else { + + // parse the match-expression. + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + for p.peekTokenIs(token.COMMA) { + + // skip the comma + p.nextToken() + + // setup the expression. + p.nextToken() + + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + + } + } + } + + if !p.expectPeek(token.LBRACE) { + + msg := fmt.Sprintf("expected token to be '{', got %s instead", p.curToken.Type) + p.errors = append(p.errors, msg) + fmt.Printf("error\n") + return nil + } + + // parse the block + tmp.Block = p.parseBlockStatement() + + if !p.curTokenIs(token.RBRACE) { + msg := fmt.Sprintf("Syntax Error: expected token to be '}', got %s instead", p.curToken.Type) + p.errors = append(p.errors, msg) + fmt.Printf("error\n") + return nil + + } + p.nextToken() + + // save the choice away + expression.Choices = append(expression.Choices, tmp) + + } + + // ensure we're at the the closing "}" + if !p.curTokenIs(token.RBRACE) { + return nil + } + + // More than one default is a bug + count := 0 + for _, c := range expression.Choices { + if c.Default { + count++ + } + } + if count > 1 { + msg := fmt.Sprintf("A switch-statement should only have one default block") + p.errors = append(p.errors, msg) + return nil + + } + return expression + +} diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..c0404f4 --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,1354 @@ +package parser + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/lucasepe/g2d/ast" + "github.com/lucasepe/g2d/lexer" +) + +func TestComments(t *testing.T) { + tests := []struct { + input string + expectedComment string + }{ + {"// foo", " foo"}, + {"#!g2d", "!g2d"}, + {"# foo", " foo"}, + {" # foo", " foo"}, + {" // x := 1", " x := 1"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + comment := program.Statements[0] + if !testComment(t, comment, tt.expectedComment) { + return + } + } +} + +func TestAssignmentExpressions(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + input string + expected string + }{ + {"x = 5;", "x=5"}, + {"y = true;", "y=true"}, + {"foobar = y;", "foobar=y"}, + {"[1, 2, 3][1] = 4", "([1, 2, 3][1])=4"}, + {`{"a": 1}["b"] = 2`, `({a:1}[b])=2`}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + assert.Equal(tt.expected, program.String()) + } +} + +func TestBindExpressions(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + input string + expected string + }{ + {"x := 5;", "x:=5"}, + {"y := true;", "y:=true"}, + {"foobar := y;", "foobar:=y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + assert.Equal(tt.expected, program.String()) + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestFloatLiteralExpression(t *testing.T) { + input := "5.7;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.FloatLiteral) + if !ok { + t.Fatalf("exp not *ast.FloatLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5.7 { + t.Errorf("literal.Value not %f. got=%f", 5.7, literal.Value) + } + if literal.TokenLiteral() != "5.7" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5.7", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "5 >= 4 == 3 <= 4", + "((5 >= 4) == (3 <= 4))", + }, + { + "5 <= 4 != 3 >= 4", + "((5 <= 4) != (3 >= 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + { + "d.foo * d.bar", + "((d[foo]) * (d[bar]))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestNullExpression(t *testing.T) { + l := lexer.New("null") + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + _, ok = stmt.Expression.(*ast.Null) + if !ok { + t.Fatalf("exp not *ast.Null. got=%T", stmt.Expression) + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestWhileExpression(t *testing.T) { + input := `while (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.WhileExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.WhileExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestIfElseIfExpression(t *testing.T) { + input := `if (x < y) { x } else if (x == y) { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + altexp, ok := alternative.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("alternative.Expression is not ast.IfExpression. got=%T", alternative.Expression) + } + + if !testInfixExpression(t, altexp.Condition, "x", "==", "y") { + return + } + + if len(altexp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(altexp.Consequence.Statements)) + } + + altconsequence, ok := altexp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, altconsequence.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionDefinitionParsing(t *testing.T) { + assert := assert.New(t) + + input := `add := fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + assert.Equal("add:=fn add(x, y) (x + y)", program.String()) +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func testComment(t *testing.T, s ast.Statement, expected string) bool { + comment, ok := s.(*ast.Comment) + if !ok { + t.Errorf("s not *ast.Comment. got=%T", s) + return false + } + + if comment.Value != expected { + t.Errorf("comment.Value not '%s'. got=%s", expected, comment.Value) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingSelectorExpressions(t *testing.T) { + input := "myHash.foo" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + t.Logf("stmt: %#v", stmt) + + exp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + ident, ok := exp.Left.(*ast.Identifier) + if !ok { + t.Fatalf("exp.Left not *ast.Identifier. got=%T", stmt.Expression) + } + + if !testIdentifier(t, ident, "myHash") { + return + } + + index, ok := exp.Index.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp.Index not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if index.Value != "foo" { + t.Fatalf("index.Value != \"foo\"") + } +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + } + + expectedValue := expected[literal.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func TestParsingImportExpressions(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + input string + expected string + }{ + {`import("mod")`, `import("mod")`}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + assert.Equal(tt.expected, program.String()) + } +} diff --git a/parser/parser_tracing.go b/parser/parser_tracing.go new file mode 100644 index 0000000..83935b4 --- /dev/null +++ b/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/repl/repl.go b/repl/repl.go new file mode 100644 index 0000000..d499008 --- /dev/null +++ b/repl/repl.go @@ -0,0 +1,167 @@ +package repl + +// Package repl implements the Read-Eval-Print-Loop or interactive console +// by lexing, parsing and evaluating the input in the interpreter + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + + "github.com/lucasepe/g2d/eval" + "github.com/lucasepe/g2d/lexer" + "github.com/lucasepe/g2d/object" + "github.com/lucasepe/g2d/parser" +) + +// prompt is the REPL prompt displayed for each input +const prompt = "▶▶ " + +const banner = `Hello {{USER}}! + ___ ___ +Welcome __ _ |_ )| \ + to / _' | / / | |) | + \__, |/___||___/ + |___/ Programming language.` + +// Options are the REPL parameters. +type Options struct { + Debug bool + Interactive bool +} + +// REPL is the read-evaluate-print loop. +type REPL struct { + user string + args []string + opts *Options +} + +// New create a REPL +func New(user string, args []string, opts *Options) *REPL { + object.StandardInput = os.Stdin + object.StandardOutput = os.Stdout + object.ExitFunction = os.Exit + + return &REPL{user, args, opts} +} + +// Eval parses and evalulates the program given by f and returns the resulting +// environment, any errors are printed to stderr +func (r *REPL) Eval(f io.Reader) (env *object.Environment) { + env = object.NewEnvironment() + + b, err := ioutil.ReadAll(f) + if err != nil { + fmt.Fprintf(os.Stderr, "error reading source file: %s", err) + return + } + + l := lexer.New(string(b)) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(os.Stderr, p.Errors()) + return + } + + // if obj := eval.Eval(program, env); obj.Type() == object.ERROR {// + if obj := eval.BeginEval(program, env, l); (obj != nil) && (obj.Type() == object.ERROR) { + printParserErrors(os.Stderr, []string{obj.String()}) + } + return +} + +// StartEvalLoop starts the REPL in a continious eval loop +func (r *REPL) StartEvalLoop(in io.Reader, out io.Writer, env *object.Environment) { + scanner := bufio.NewScanner(in) + + if env == nil { + env = object.NewEnvironment() + } + + for { + fmt.Printf(prompt) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + obj := eval.Eval(program, env) + if obj != nil { + if _, ok := obj.(*object.Null); !ok { + io.WriteString(out, obj.Inspect()) + io.WriteString(out, "\n") + } + } + } +} + +// Run execute the read-eval loop. +func (r *REPL) Run() { + object.Arguments = make([]string, len(r.args)) + copy(object.Arguments, r.args) + + if len(r.args) == 0 { + welcome(r.user) + r.StartEvalLoop(os.Stdin, os.Stdout, nil) + return + } + + if len(r.args) > 0 { + workDir, err := filepath.Abs(filepath.Dir(r.args[0])) + if err != nil { + workDir = filepath.Dir(r.args[0]) + } + + sourceFile := filepath.Base(r.args[0]) + + object.WorkDir = workDir + object.SourceFile = strings.TrimSuffix(sourceFile, filepath.Ext(sourceFile)) + + f, err := os.Open(r.args[0]) + if err != nil { + log.Fatalf("could not open source file %s: %s", r.args[0], err) + } + + // Remove program argument (zero) + r.args = r.args[1:] + object.Arguments = object.Arguments[1:] + + env := r.Eval(f) + if r.opts.Interactive { + r.StartEvalLoop(os.Stdin, os.Stdout, env) + } + + } +} + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, "Woops! parser errors:\n") + for _, msg := range errors { + io.WriteString(out, fmt.Sprintf(" - %s\n", msg)) + } +} + +func welcome(user string) { + str := strings.Replace(banner, "{{USER}}", user, 1) + fmt.Print(str, "\n\n") + fmt.Println("Feel free to type in commands.") +} diff --git a/token/token.go b/token/token.go new file mode 100644 index 0000000..b30aa1f --- /dev/null +++ b/token/token.go @@ -0,0 +1,178 @@ +package token + +// Package token implements types and constants to support tokenizing +// the input source before passing the stream of tokens on to the parser. + +const ( + // ILLEGAL represents an illegal token + ILLEGAL = "ILLEGAL" + + // EOF end of file + EOF = "EOF" + + // COMMENT a line comment, e.g: # this is a comment + COMMENT = "COMMENT" + + // + // Identifiers + literals + // + + // IDENT an identifier, e.g: add, foobar, x, y, ... + IDENT = "IDENT" + // INT an integer, e.g: 1234 + INT = "INT" + // FLOAT a float, e.g: 2.5 + FLOAT = "FLOAT" + // STRING a string, e.g: "1234" + STRING = "STRING" + + // + // Operators + // + + // BIND the bind operator + BIND = ":=" + // ASSIGN the assignment operator + ASSIGN = "=" + // PLUS the addition operator + PLUS = "+" + // MINUS the substraction operator + MINUS = "-" + // MULTIPLY the multiplication operator + MULTIPLY = "*" + // DIVIDE the division operator + DIVIDE = "/" + // MODULO the modulo operator + MODULO = "%" + + // + // Logical operators + // + + // NOT the not operator + NOT = "!" + // AND the and operator + AND = "&&" + // OR the or operator + OR = "||" + + // + // Comparision operators + // + + // LT the less than comparision operator + LT = "<" + // LTE the less than or equal comparision operator + LTE = "<=" + + // GT the greater than comparision operator + GT = ">" + // GTE the grather than or equal comparision operator + GTE = ">=" + + // EQ the equality operator + EQ = "==" + // NEQ the inequality operator + NEQ = "!=" + + // + // Delimiters + // + + // COMMA a comma + COMMA = "," + // SEMICOLON a semi-colon + SEMICOLON = ";" + // COLON a comon + COLON = ":" + // DOT a dot + DOT = "." + + // LPAREN a left paranthesis + LPAREN = "(" + // RPAREN a right parenthesis + RPAREN = ")" + // LBRACE a left brace + LBRACE = "{" + // RBRACE a right brace + RBRACE = "}" + // LBRACKET a left bracket + LBRACKET = "[" + // RBRACKET a right bracket + RBRACKET = "]" + + // + // Keywords + // + + // FUNCTION the `fn` keyword (function) + FUNCTION = "FUNCTION" + // TRUE the `true` keyword (true) + TRUE = "TRUE" + // FALSE the `false` keyword (false) + FALSE = "FALSE" + // NULL the `null` keyword (null) + NULL = "NULL" + // IF the `if` keyword (if) + IF = "IF" + // ELSE the `else` keyword (else) + ELSE = "ELSE" + // RETURN the `return` keyword (return) + RETURN = "RETURN" + // WHILE the `while` keyword (while) + WHILE = "WHILE" + + // SWITCH the `switc` keyword + SWITCH = "switch" + // CASE ... + CASE = "case" + // DEFAULT ... + DEFAULT = "DEFAULT" + + // IMPORT the `import` keyword (import) + IMPORT = "IMPORT" +) + +var keywords = map[string]Type{ + "fn": FUNCTION, + "true": TRUE, + "false": FALSE, + "null": NULL, + "if": IF, + "else": ELSE, + "return": RETURN, + "while": WHILE, + "import": IMPORT, + + "case": CASE, + "switch": SWITCH, + "default": DEFAULT, +} + +// Type represents the type of a token +type Type string + +// Token holds a single token type and its literal value +type Token struct { + Type Type + Position int // lexer position in file before token + Literal string +} + +// New creates a token using the specified values +func New(tt Type, pos int, lit string) Token { + return Token{ + Type: tt, + Position: pos, + Literal: lit, + } +} + +// LookupIdent looks up the identifier in ident and returns the appropriate +// token type depending on whether the identifier is user-defined or a keyword +func LookupIdent(ident string) Type { + if token, ok := keywords[ident]; ok { + return token + } + return IDENT +} diff --git a/token/token_test.go b/token/token_test.go new file mode 100644 index 0000000..7aec42c --- /dev/null +++ b/token/token_test.go @@ -0,0 +1,24 @@ +package token + +import ( + "strings" + "testing" +) + +// Test looking up values succeeds, then fails +func TestLookup(t *testing.T) { + + for key, val := range keywords { + + // Obviously this will pass. + if LookupIdent(string(key)) != val { + t.Errorf("Lookup of %s failed", key) + } + + // Once the keywords are uppercase they'll no longer + // match - so we find them as identifiers. + if LookupIdent(strings.ToUpper(string(key))) != IDENT { + t.Errorf("Lookup of %s failed", key) + } + } +} diff --git a/typing/typing.go b/typing/typing.go new file mode 100644 index 0000000..c0cfe22 --- /dev/null +++ b/typing/typing.go @@ -0,0 +1,115 @@ +package typing + +import ( + "fmt" + "math" + + "github.com/lucasepe/g2d/object" +) + +type CheckFunc func(name string, args []object.Object) error + +func Check(name string, args []object.Object, checks ...CheckFunc) error { + for _, check := range checks { + if err := check(name, args); err != nil { + return err + } + } + return nil +} + +func ExactArgs(n int) CheckFunc { + return func(name string, args []object.Object) error { + if len(args) != n { + return fmt.Errorf( + "TypeError: %s() takes exactly %d argument (%d given)", + name, n, len(args), + ) + } + return nil + } +} + +func MinimumArgs(n int) CheckFunc { + return func(name string, args []object.Object) error { + if len(args) < n { + return fmt.Errorf( + "TypeError: %s() takes a minimum %d arguments (%d given)", + name, n, len(args), + ) + } + return nil + } +} + +func RangeOfArgs(n, m int) CheckFunc { + return func(name string, args []object.Object) error { + if len(args) < n || len(args) > m { + return fmt.Errorf( + "TypeError: %s() takes at least %d arguments at most %d (%d given)", + name, n, m, len(args), + ) + } + return nil + } +} + +func WithTypes(types ...object.Type) CheckFunc { + return func(name string, args []object.Object) error { + for i, t := range types { + if i < len(args) && args[i].Type() != t { + return fmt.Errorf( + "TypeError: %s() expected argument #%d to be `%s` got `%s`", + name, (i + 1), t, args[i].Type(), + ) + } + } + return nil + } +} + +func ToFloat(obj object.Object) (float64, error) { + if obj.Type() == object.INTEGER { + val := obj.(*object.Integer).Value + return float64(val), nil + } + + if obj.Type() == object.FLOAT { + val := obj.(*object.Float).Value + return val, nil + } + + return math.NaN(), fmt.Errorf("expected to be `int` or `float` got `%s`", obj.Type()) +} + +func ToInt(obj object.Object) (int, error) { + if obj.Type() == object.INTEGER { + val := obj.(*object.Integer).Value + return int(val), nil + } + + if obj.Type() == object.FLOAT { + val := obj.(*object.Float).Value + return int(math.Round(val)), nil + } + + return -1, fmt.Errorf("expected to be `int` got `%s`", obj.Type()) +} + +func ToString(obj object.Object) (string, error) { + if obj.Type() == object.STRING { + val := obj.(*object.String).Value + return val, nil + } + + return "", fmt.Errorf("expected to be `string` got `%s`", obj.Type()) +} + +func ToBool(obj object.Object) (bool, error) { + if obj.Type() == object.BOOLEAN { + val := obj.(*object.Boolean).Value + return val, nil + } + + return false, fmt.Errorf("expected to be `bool` got `%s`", obj.Type()) +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..5ffbc16 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,53 @@ +package utils + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" +) + +var SearchPaths []string + +func init() { + cwd, err := os.Getwd() + if err != nil { + log.Fatalf("error getting cwd: %s", err) + } + + if e := os.Getenv("G2D_PATH"); e != "" { + tokens := strings.Split(e, ":") + for _, token := range tokens { + AddPath(token) // ignore errors + } + } else { + SearchPaths = append(SearchPaths, cwd) + } +} + +func AddPath(path string) error { + path = os.ExpandEnv(filepath.Clean(path)) + absPath, err := filepath.Abs(path) + if err != nil { + return err + } + SearchPaths = append(SearchPaths, absPath) + return nil +} + +func Exists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func FindModule(name string) string { + basename := fmt.Sprintf("%s.g2d", name) + for _, p := range SearchPaths { + filename := filepath.Join(p, basename) + if Exists(filename) { + return filename + } + } + return "" +}