├── .devcontainer └── devcontainer.json ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── MISTAKES.md ├── README.md ├── STANDARDS.md ├── core_language_basics.md ├── examples ├── .unseemly_prelude ├── bal_test.unseemly ├── build_a_language.unseemly ├── commentary.unseemly ├── comments.unseemly ├── fact.unseemly ├── function_pipe.unseemly ├── if_macro.unseemly ├── multifile │ ├── comment_library.unseemly │ ├── comment_user.comments │ └── comment_user.unseemly ├── sdl │ ├── example_scene.sdl │ └── sdl.unseemly ├── sum_list.unseemly ├── trad_let.unseemly └── worked_example.unseemly ├── hll_ideas ├── pitch.md ├── rustfmt.toml ├── src ├── alpha.rs ├── ast.rs ├── ast_walk.rs ├── beta.rs ├── cli.rs ├── core_extra_forms.rs ├── core_forms.rs ├── core_macro_forms.rs ├── core_qq_forms.rs ├── core_type_forms.rs ├── earley.rs ├── end_to_end__tests.rs ├── expand.rs ├── form.rs ├── grammar.rs ├── highlighter_generation.rs ├── macros │ ├── flimsy_syntax.rs │ ├── macros.rs │ ├── mod.rs │ └── reification_macros.rs ├── main.rs ├── name.rs ├── read.rs ├── runtime │ ├── core_values.rs │ ├── eval.rs │ ├── mod.rs │ └── reify.rs ├── subtype.rs ├── ty.rs ├── ty_compare.rs ├── unparse.rs ├── util │ ├── assoc.rs │ ├── asterism.rs │ ├── err.rs │ ├── mbe.rs │ ├── mod.rs │ ├── sky.rs │ └── vrep.rs └── walk_mode.rs ├── structure ├── syntax_bikeshed └── web_ide ├── .gitignore ├── GETTING_STARTED.md ├── config.js ├── example.newlang ├── example.unseemly ├── ide.html ├── newlang.unseemly ├── unseemly.png └── unseemly_worker.js /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "features": { 4 | "ghcr.io/devcontainers/features/rust:1": {}, 5 | "ghcr.io/devcontainers-contrib/features/zsh-plugins:0": {} 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | target-install 3 | *~ 4 | .unseemly_history 5 | .vscode -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | # For nightly rustfmt: 4 | RUN bash -cl "rustup toolchain install nightly --allow-downgrade -c rustfmt" 5 | # For the Web IDE: 6 | RUN bash -cl "cargo install wasm-pack" -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml 3 | 4 | tasks: 5 | - init: cargo build 6 | command: cargo watch -x test 7 | 8 | vscode: 9 | extensions: 10 | - rust-lang.rust-analyzer 11 | 12 | image: 13 | file: .gitpod.Dockerfile 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: required 3 | dist: trusty 4 | addons: 5 | apt: 6 | packages: 7 | - libssl-dev 8 | cache: cargo 9 | rust: 10 | - stable 11 | - beta 12 | - nightly 13 | matrix: 14 | allow_failures: 15 | - rust: nightly 16 | 17 | before_cache: | 18 | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then 19 | cargo install cargo-tarpaulin -f 20 | fi 21 | 22 | script: 23 | - cargo clean 24 | - cargo build 25 | - cargo test 26 | 27 | after_success: | 28 | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then 29 | cargo tarpaulin --ignore-panics --ignore-tests --ciserver travis-ci --coveralls $TRAVIS_JOB_ID 30 | fi 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ## Current version 4 | ### Fixed 5 | - Macro type parameters can now be used in defining the macro. 6 | 7 | ## 0.0.3 - 2021-12-02 8 | ### Added 9 | - Added a Web IDE, with automatically-generated syntax-highlighting. 10 | ### Fixed 11 | - Improved the parser somewhat. 12 | 13 | ## 0.0.2 - 2021-08-22 14 | ### Added 15 | - Added more builtin functions, and the `Option` type. 16 | - Added `common` and `reserving` to the syntax for grammars. 17 | ### Fixed 18 | - Fixed an erratic bug where `Form` identity was lost. 19 | - Improved display of parse ambiguities. 20 | 21 | ## 0.0.1 - 2021-08-20 22 | ### Added 23 | - Added rudimentary multi-file program support. 24 | * To define a library, introduce the syntax and values that should be exposed, 25 | and write `capture_language` as the program body 26 | * To use a library write `import \[library_definition.unseemly]\`. 27 | This overwrites the current syntactic environment and extends the runtime environment. 28 | (So, only the most recent `import` provides macros.) 29 | * You interpret a file in a specific language with 30 | `unseemly language_definition.unseemly program.lang` 31 | - Introduced string literals and the `String` type. 32 | - Introduced sequence literals. 33 | - Introduced the `Cell` type for side-effects. 34 | - Introduced some basic operations on `Sequence`, `String`, and `Cell` types. 35 | - Introduced value and type "prefabs". 36 | * `(prefab v)` produces (imaginary) syntax for an expression that returns `v`. 37 | * `prefab_type T` produces (imaginary) syntax for the type `T`. 38 | - Added support for `UNSEEMLY_FRESHEN_WATCH`. 39 | ### Fixed 40 | - Display of multiline error messages now uses newlines instead of "\n". 41 | - Macros can now be *used* in the implementation of other macros, not just expanded-to. 42 | - Values can be captured in macro definitions. 43 | 44 | ## 0.0.0 - 2020-02-02 45 | ### Added 46 | - Implemented the Unseemly programming language. Usage is described in `core_language_basics.md`. 47 | It's a bit buggy at the moment, and it's missing some features. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at paul.stansifer@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Ways you can contribute: 2 | * You can write code in Unseemly! It needs testing! You can add examples to `examples`, and 3 | file bugs in the [issue tracker] when things don't work. 4 | * You can fix a bug. There are some in the [issue tracker]. Issues that don't require much 5 | context about the language and are on the smaller side are marked ["good first issue"]. 6 | * You can fix a TODO. There are [a lot of TODOs] in the codebase. Some of them are simple 7 | refactorings, some are self-contained design issues, others are more complex. 8 | * You can file a bug. If you find a problem or want a feature, the [issue tracker] is the place to 9 | go. Many of the TODOs should be given associated issues (e.g. `// TODO #7832`). 10 | * You can write tests. [Test coverage] is decent, but could be better. (You can get the list of 11 | all files sorted by number of missed lines to look for low-hanging fruit.) Even writing tests 12 | for low-priority things (like `impl Debug`s) to get the numbers up helps focus attention on 13 | existing gaps. Also of interest: what's test coverage without end-to-end tests? 14 | * If you're ambitious, you can pick something in MISTAKES.md and fix it! 15 | 16 | [issue tracker]: https://github.com/paulstansifer/unseemly/issues 17 | ["good first issue"]: https://github.com/paulstansifer/unseemly/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 18 | [a lot of TODOs]: https://github.com/paulstansifer/unseemly/search?q=TODO&unscoped_q=TODO 19 | [Test coverage]: https://coveralls.io/github/paulstansifer/unseemly 20 | 21 | Things to know about developing Unseemly: 22 | * Try setting the `UNSEEMLY_TRACE` environment variable to `full` for a complete view of all 23 | `Ast`-walking. 24 | So if you get test failures, try `UNSEEMLY_TRACE=full cargo test` 25 | * Appended carrots (🥕) distinguish variable names that "look" the same, but differ due to 26 | freshening. (If you see tomatoes; they serve a similar purpose, but for debug-printing.) 27 | * To observe freshening, try `UNSEEMLY_FRESHEN_WATCH=variable_name`; it will show what's happening 28 | each time that name is freshened. 29 | * In the REPL (`cargo run --release`), use ctrl-R to search your REPL history. 30 | Add commonly-used definitions to your `~/.unseemly_prelude` file. 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unseemly" 3 | description = "A typed macro language" 4 | version = "0.0.3" 5 | authors = ["Paul Stansifer "] 6 | edition = "2018" 7 | 8 | homepage = "https://unseemly.github.io/" 9 | repository = "https://github.com/paulstansifer/unseemly/" 10 | readme = "README.md" 11 | keywords = ["programming-language"] 12 | license = "MIT" 13 | 14 | [badges] 15 | travis-ci = { repository = "paulstansifer/unseemly" } 16 | appveyor = { repository = "paulstansifer/unseemly", service = "github" } 17 | maintenance = { status = "actively-developed" } 18 | 19 | [dependencies] 20 | regex = "1.5.5" 21 | num = "0.4.0" 22 | custom_derive = "0.1.7" 23 | dirs = "3.0.2" 24 | tap = "1.0.1" 25 | color-backtrace = "0.5" 26 | im-rc = "15.0" 27 | codespan-reporting = "0.11.1" 28 | ansi-to-html = "0.1.0" 29 | wasm-bindgen = "0.2.76" 30 | indoc = "1.0.3" 31 | 32 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 33 | rustyline = "8.0.0" 34 | 35 | [target.'cfg(target_arch = "wasm32")'.dependencies] 36 | console_error_panic_hook = "0.1.6" 37 | 38 | [[bin]] 39 | name = "unseemly" 40 | path = "src/cli.rs" 41 | # Seems a lot faster to run all the tests in the library. 42 | test = false 43 | 44 | [lib] 45 | name = "libunseemly" 46 | path = "src/main.rs" 47 | crate-type = ["cdylib", "rlib"] 48 | 49 | [profile.dev] 50 | # After minor changes, using `cargo test` to build and test is slightly *faster* at this opt level: 51 | opt-level = 2 52 | 53 | [profile.release] 54 | opt-level = 3 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016–2019 the Unseemly contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /MISTAKES.md: -------------------------------------------------------------------------------- 1 | If I had to do it all over again... 2 | 3 | * There should be one primary macro for defining language forms, 4 | which would also define a destructuring macro for that form. 5 | (And maybe it could make flimsy_syntax.rs simpler or nonexistent.) 6 | * This may overlap with the purpose of the previous, 7 | but there should be something like `Ast`, except with binding and quotation omitted. 8 | It would represent syntax "as written" 9 | and be nice for making, e.g., syntax-aware `diff`-like tools. 10 | * Names for "parts" would appear as barewords, not strings, in macros. Maybe. Not sure about this. 11 | * There are so many variations on maps and reduces in mbe.rs, 12 | and pretty much all of them are used once. 13 | I think something visitor-pattern-like might be able to unify them? 14 | * I think `FormPat` is more like a language than I realized. 15 | It seems to have positive and negative forms, mediated by `Scope` and `Named`. 16 | There might need to be more structure, to enforce what can go where, 17 | as well as, uh, possibly some scoped way to define names *internal* to the grammar. 18 | Also, `FormPat` is a bad name. 19 | * The walk_mode.rs/ast_walk.rs distinction isn't great; I never know what's where. 20 | * It would probably have been simpler to add a few more cases (or one `Any` case) to `Value` 21 | and build some primitive operations on them 22 | than it was to build all those reification macros. 23 | * In examples and tests, `Int` and `Nat` are frequently used, 24 | and the user is supposed to assume that neither is a subtype of the other. 25 | That's unintuitive! 26 | Also, they are a little similar-looking. 27 | * Forms ought to be all given names (at the Rust level, I mean); 28 | `find_core_form()` is unpleasant and should be removed. 29 | 30 | Former mistakes: 31 | * There used to be a `Ty` type that was just a wrapper around `Ast` (in the 32 | distant past, it had had additional functionality). (Issue #30) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ≉ [![Build Status](https://travis-ci.com/paulstansifer/unseemly.svg?branch=master)](https://travis-ci.com/paulstansifer/unseemly) [![Coverage Status](https://coveralls.io/repos/github/paulstansifer/unseemly/badge.svg)](https://coveralls.io/github/paulstansifer/unseemly) 2 | 3 | Unseemly typechecks the code that you wrote, not the code your macros wrote. 4 | This makes macros feel like part of the language, not something tacked-on. 5 | 6 | For a more complete pitch, see http://unseemly.github.io 7 | 8 | Unseemly has a bare minimum of forms 9 | necessary to bootstrap the implementation of practical languages. 10 | 11 | Unseemly is still pretty early-stage, so, while all of the features below exist, 12 | there are still a number of things that are janky or incomplete. 13 | 14 | ## Features 15 | 16 | ### From the ML family 17 | * Algebraic types (i.e., supports structs and (rich) enums) 18 | * Typesafe destructuring with `match`. 19 | * Generic types (or parametric types) (e.g. `List`) 20 | * Recursive types 21 | ### From the Scheme family 22 | * Syntax quasiquotation 23 | (`'[Expr | … ]'` quotes an expression, 24 | but inside that, `,[Expr | … ],` evaluates its contents and interpolates them) 25 | * Pretty-printing respects macro invocations and quoted syntax 26 | (the pretty-printer is rather limited at the moment, though) 27 | * Hygenic macros (all operations respect α-equivalence) 28 | * Macro By Example (easily implement n-ary forms without writing boilerplate loops). 29 | ### Unusual features 30 | * No type errors in generated code 31 | (if a macro invocation typechecks, the code it expands to doesn't need typechecking)†. 32 | * Typechecking under syntax quotation 33 | (so `'[Expr | (plus one ,[e1],)]'` is a type error if `e1` has the type `Expr`) 34 | * Extensible parsing and lexing (for embedded languages like SQL and regex). 35 | 36 | † Except that there are [known soundness bugs](https://github.com/paulstansifer/unseemly/issues?q=is%3Aissue+is%3Aopen+label%3Asoundness). 37 | ### Other features 38 | * Full-featured REPL, with persistent command history and line editing (courtesy of `rustyline`). 39 | 40 | 41 | ## How to use it 42 | 43 | Install Rust, if you haven't already: 44 | 45 | curl https://sh.rustup.rs -sSf | sh 46 | 47 | From your Unseemly repository directory, run an example program: 48 | 49 | cargo run --release examples/sum_list.unseemly 50 | 51 | (Recommended) Get the default prelude for the Unseemly REPL: 52 | 53 | cp examples/.unseemly_prelude ~/ 54 | 55 | Start the REPL: 56 | 57 | cargo run --release 58 | 59 | ## Documentation 60 | 61 | Look at core_language_basics.md for documentation of the language. 62 | 63 | ## Related work 64 | 65 | ### Research projects 66 | #### [FreshML](https://www.cl.cam.ac.uk/~amp12/freshml/) / [Romeo](https://repository.library.northeastern.edu/files/neu:cj82mb52h) 67 | 68 | Unseemly is most closely based on Romeo, which descends from FreshML. 69 | (Romeo is closer to Pure FreshML, but the "Pure" part is not present in Unseemly.) 70 | Romeo allowed for manipulation of syntax types with complex binding information, but 71 | * syntax was otherwise untyped 72 | * there was no macro system (so the syntax manipulation was pointless!) 73 | * it is just a core calculus 74 | 75 | #### [SugarJ](https://github.com/sugar-lang) / [SoundX](https://github.com/florenzen/soundx) 76 | 77 | SoundX is a language with syntax extensions in which typechecking occurs before expansion. 78 | It provides sound language extensions, but 79 | * it doesn't support binding annotations 80 | (in practice, this means that syntax extension authors wind up writing specifications 81 | that contain logic-y things like `x ∉ dom(E)`.) 82 | * the language extensions aren't macros (they're not themselves part of the language) 83 | * it is just a core calculus 84 | 85 | (TODO: are the extensions themselves statically verified to be type-preserving? 86 | I think so, but I don't remember for sure.) 87 | 88 | ### Practical languages 89 | #### [Scala](https://www.scala-lang.org/) 90 | 91 | If I understand correctly, Scala's blackbox macros are typechecked before expansion, 92 | but they can't do everything that whitebox macros can. 93 | Unseemly macros are typechecked before expansion, but are the only macro system needed, 94 | because they can (in particular) define new binding forms safely. 95 | (TODO: learn more about Scala's macro system) 96 | 97 | #### [Wyvern](http://wyvernlang.github.io/) 98 | 99 | Wyvern's primary motivating example 100 | (write SQL, not strings containing SQL, in your general-purpose code) 101 | is a lot like Unseemly's vision of inline syntax extension. 102 | Wyvern is a full-fledged language, not a core language. 103 | I believe that writing new embedded languages is not as easy as macro definition. 104 | 105 | Wyvern also includes a number of features that are outside the scope of Unseemly. 106 | 107 | (TODO: learn more about Wyvern) 108 | 109 | #### [Terra](http://terralang.org/) 110 | 111 | Terra, from a quick glance (TODO: learn more), 112 | appears to be a language with a close relationship to Lua, 113 | similar to the relationship that Unseemly-based languages would have. 114 | 115 | In this case, it looks like the goal is to marry a high-level and low-level language together, 116 | without an FFI and with inline embedding. 117 | 118 | #### [MetaOCaml](http://okmij.org/ftp/ML/MetaOCaml.html) 119 | 120 | MetaOCaml is an extension to OCaml that supports type-safe syntax quotation. 121 | Though Unseemly uses "quasiquotation" to describe syntax quotation, 122 | it is more similar to [MetaOCaml's bracket](http://okmij.org/ftp/meta-programming/implementations.html#meta-scheme) 123 | than to Scheme's quasiquotation, 124 | because it respects alpha-equivalence. 125 | 126 | 127 | Unlike Unseemly, MetaOCaml doesn't use its safe syntax quotation to implement a macro system. 128 | 129 | #### [Rust](http://rust-lang.org) and [SweetJS](https://www.sweetjs.org/) 130 | 131 | Rust and SweetJS are non-S-expression-based languages with macro systems that allow rich syntax. 132 | 133 | Unseemly is implemented in Rust, and it uses *lots* of macros. 134 | -------------------------------------------------------------------------------- /STANDARDS.md: -------------------------------------------------------------------------------- 1 | 1. Use `cargo +nightly fmt` before comitting for consistency. 2 | It enforces 100-character lines, and some other stuf that's 3 | .. perhaps a bit on the agressively terse side. 4 | 5 | 2. It's okay for comments to ask questions 6 | that the author of the code ought to have known the answer to. 7 | 8 | 3. Everything else on this list is a little weird, and shouldn't be taken too seriously. 9 | 10 | 4. Text in comments (and, where possible, in other natural-language prose) 11 | is formatted in a hierarchical fashion. 12 | Ending a sentence ends the line that it's on. 13 | Manually-word wrap by inserting line-breaks at major breaks in thought 14 | (often, it's after a comma or a where one would pause when reading aloud), 15 | and indent successive lines of that sentence 16 | in a way that communicates the grammatical hierarchy. 17 | 18 | 5. If a variable name has more than two underscores in it, 19 | consider doubling (or, *shudder* tripling) some of them 20 | to indicate how they relate to each other. 21 | For example, `maybe_literally__walk` is a function that walks, and might do so literally. 22 | If it were named `maybe__literally_walk`, then it might literally walk, but might do nothing. 23 | 24 | TODO: Change `EnvMBE` to `EnvMbe` to follow [Rust naming style]. 25 | 26 | [Rust naming style]: https://rust-lang.github.io/api-guidelines/naming.html 27 | -------------------------------------------------------------------------------- /examples/.unseemly_prelude: -------------------------------------------------------------------------------- 1 | # I tend to use `again` as the name for recursive calls when using `fix`: 2 | factorial := (fix .[ again : [ -> [ Int -> Int ]] . .[ n : Int . match (zero? n) { +[True]+ => one +[False]+ => (times n ((again) (minus n one))) } ]. ].) 3 | 4 | # Recursive types need `mu_type`: 5 | IntList t= mu_type IntList . { +[Nil]+ +[Cons Int IntList]+ } 6 | 7 | # The unfolded form is useful because that's what the constructors create: 8 | IntListUF t= { +[Nil]+ +[Cons Int IntList]+ } 9 | 10 | # Constructing an enum requires a type annotation, as does `fold`. 11 | empty_ilist := fold +[Nil]+ : IntListUF : IntList 12 | ilist_3 := fold +[Cons three empty_ilist]+ : IntListUF : IntList 13 | ilist_23 := fold +[Cons two ilist_3]+ : IntListUF : IntList 14 | ilist_123 := fold +[Cons one ilist_23]+ : IntListUF : IntList 15 | 16 | 17 | sum_int_list := (fix .[again : [-> [IntList -> Int]] . .[ lst : IntList . match unfold lst { +[Nil]+ => zero +[Cons hd tl]+ => (plus hd ((again) tl))} ]. ]. ) 18 | 19 | # Parametric recursive types: 20 | # Note: There's no type relationship between `IntList` and `List`. Use the latter! 21 | List t= forall T . mu_type List . { +[Nil]+ +[Cons T List ]+ } 22 | ListUF t= forall T . { +[Nil]+ +[Cons T List ]+ } 23 | 24 | # Accepting parametrically-typed arguments: 25 | list_len := forall T . (fix .[again : [-> [List -> Int]] . .[ lst : List . match unfold lst { +[Nil]+ => zero +[Cons hd tl]+ => (plus one ((again) tl))} ]. ].) 26 | 27 | empty_int_list := fold +[Nil]+ : ListUF : List 28 | 29 | # In order to prevent having to write *two* type annotations every cons, let's define a helper function: 30 | cons := forall T . .[ car : T cdr : List . fold +[Cons car cdr]+ : ListUF : List ]. 31 | 32 | list_3 := (cons three empty_int_list) 33 | list_23 := (cons two list_3) 34 | list_123 := (cons one list_23) 35 | 36 | # Now try `(list_len list_123)`! 37 | # Remember to save useful things to the prelude! 38 | -------------------------------------------------------------------------------- /examples/bal_test.unseemly: -------------------------------------------------------------------------------- 1 | import /[build_a_language.unseemly]/ 2 | let eleven = (plus ten one) ; 3 | in 4 | letfn (plusplusplus a: Int b: Int) -> Int = (plus a b) ; 5 | in 6 | (plusplusplus eleven (foldl s[ 5 10 15 ]s 0 plus)) 7 | -------------------------------------------------------------------------------- /examples/build_a_language.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | DefaultSeparator ::= /((?s:\s|#[^\n|][^\n]*|#\|.*?\|#)*)/ ; 3 | in 4 | # Now we've got comments! 5 | # This builds up a sort-of useable language in Unseemly. 6 | # See `worked_example.unseemly` for a similar process, 7 | # but with detailed explanations. 8 | extend_syntax 9 | ### Introduce `let` expressions. 10 | Expr ::=also 11 | forall T S . '{ [ 12 | lit ,{ DefaultToken }, = 'let' 13 | [ 14 | pat := ( ,{ Pat }, ) 15 | lit ,{ DefaultToken }, = '=' 16 | val := ( ,{ Expr }, ) 17 | lit ,{ DefaultToken }, = ';' 18 | ] * 19 | lit ,{ DefaultToken }, = 'in' 20 | body := ( ,{ Expr }, <-- ...[pat = val]... ) 21 | ] }' let_macro -> .{ 22 | '[Expr | 23 | match **[...[,val, >> ,[val], ]... ]** 24 | { **[...[,pat, >> ,[pat],]... ]** => ,[body], } ]' 25 | }. ; 26 | in 27 | extend_syntax 28 | ### Introduce `for` loops 29 | Expr ::=also forall T . '{ [ 30 | lit ,{ DefaultToken }, = 'for' 31 | pat := ( ,{ Pat }, ) 32 | lit ,{ DefaultToken }, = 'in' 33 | seq := ( ,{ Expr> }, ) 34 | # This uses `:` instead of `=`, because `T` is a type. 35 | # `body` returns `Unit`; it's just being invoked for side-effects. 36 | body := ( ,{ Expr }, <-- pat : T ) 37 | ] }' for_loop -> .{ 38 | '[Expr | 39 | (foldl ,[seq], 40 | **[]** 41 | # Interpolation into atom positions doesn't work yet, 42 | # so we use `let` to interpolate a pattern. 43 | .[unit : Unit arg : ,[prefab_type T], . 44 | let ,[pat], = arg ; in ,[body], 45 | ]. ) 46 | ]' 47 | }. ; 48 | in 49 | extend_syntax 50 | ### Introduce `reclet` expressions. 51 | Expr ::=also 52 | forall T S . '{ [ 53 | lit ,{ DefaultToken }, = 'reclet' 54 | pat := ( ,{ Pat }, ) 55 | lit ,{ DefaultToken }, = '=' 56 | val := ( ,{ Expr }, <-- pat = val ) 57 | lit ,{ DefaultToken }, = ';' 58 | lit ,{ DefaultToken }, = 'in' 59 | body := ( ,{ Expr }, <-- pat = val ) 60 | ] }' reclet_macro -> .{ 61 | '[Expr | 62 | let opt = (new_cell fold +[None]+ : {+[Some ,[prefab_type S],]+ +[None]+} : Option< ,[prefab_type S], >); 63 | in 64 | -{ 65 | (assign opt fold +[Some ,[val],]+ : {+[Some ,[prefab_type S],]+ +[None]+} : Option< ,[prefab_type S], >); 66 | match unfold (value opt) { 67 | +[Some ,[pat],]+ => ,[body], 68 | } 69 | }- ]' 70 | }. ; 71 | in 72 | extend_syntax 73 | ### Introduce `letfn` for defining 1-argument functions. 74 | # Currently, `...[]...` can only be put around a whole expression/pattern/type (issue #38), 75 | # so, one has to make a separate macro for each number of arguments. 76 | # Also, we can't interpolate into atom positions (issue #37). 77 | Expr ::=also 78 | forall I0 O T . '{ [ 79 | lit ,{ DefaultToken }, = 'letfn' 80 | lit ,{ OpenDelim }, = '(' 81 | fn_name := ( ,{ Pat< [I0 -> O] > }, ) 82 | arg_name0 := ( ,{ Pat }, ) 83 | lit ,{ DefaultToken }, = ':' 84 | # The fact that the syntax `[Int -> Int]` has type `Type<[Int -> Int]>` is weird, 85 | # but so far it works fine! 86 | arg_type0 := ( ,{ Type }, ) 87 | lit ,{ CloseDelim }, = ')' 88 | lit ,{ DefaultToken }, = '->' 89 | ret_type := ( ,{ Type }, ) 90 | lit ,{ DefaultToken }, = '=' 91 | fn_body := ( ,{ Expr }, <-- arg_name0 = arg_type0 ) 92 | lit ,{ DefaultToken }, = ';' 93 | lit ,{ DefaultToken }, = 'in' 94 | body := ( ,{ Expr }, <-- fn_name = [ arg_type0 -> ret_type ] ) 95 | ] }' let_fn1 -> .{ 96 | # In case the function is recursive, wrap it in `fix`: 97 | '[Expr | let ,[fn_name], = (fix 98 | .[ again: [ -> [ ,[arg_type0], -> ,[ret_type], ] ] . 99 | .[ a0: ,[arg_type0], . 100 | # Workaround for not being able to interpolate atoms: 101 | let ,[arg_name0], = a0; in ,[fn_body], ]. 102 | ].) ; in 103 | ,[body], 104 | ]' 105 | }. ; 106 | in 107 | extend_syntax 108 | ### Introduce `letfn` for defining 2-argument functions. 109 | Expr ::=also 110 | forall I0 I1 O T . '{ [ 111 | lit ,{ DefaultToken }, = 'letfn' 112 | lit ,{ OpenDelim }, = '(' 113 | fn_name := ( ,{ Pat< [I0 I1 -> O] > }, ) 114 | arg_name0 := ( ,{ Pat }, ) 115 | lit ,{ DefaultToken }, = ':' 116 | # The fact that the syntax `[Int -> Int]` has type `Type<[Int -> Int]>` is weird, 117 | # but so far it works fine! 118 | arg_type0 := ( ,{ Type }, ) 119 | arg_name1 := ( ,{ Pat }, ) 120 | lit ,{ DefaultToken }, = ':' 121 | arg_type1 := ( ,{ Type }, ) 122 | lit ,{ CloseDelim }, = ')' 123 | lit ,{ DefaultToken }, = '->' 124 | ret_type := ( ,{ Type }, ) 125 | lit ,{ DefaultToken }, = '=' 126 | fn_body := ( ,{ Expr }, <-- [ arg_name0 = arg_type0 o> arg_name1 = arg_type1 ] ) 127 | lit ,{ DefaultToken }, = ';' 128 | lit ,{ DefaultToken }, = 'in' 129 | body := ( ,{ Expr }, <-- fn_name = [ arg_type0 arg_type1 -> ret_type ] ) 130 | ] }' let_fn2 -> .{ 131 | # In case the function is recursive, wrap it in `fix`: 132 | '[Expr | let ,[fn_name], = (fix 133 | .[ again: [ -> [ ,[arg_type0], ,[arg_type1], -> ,[ret_type], ] ] . 134 | .[ a0: ,[arg_type0], a1: ,[arg_type1], . 135 | # Workaround for not being able to interpolate atoms: 136 | let ,[arg_name0], = a0; ,[arg_name1], = a1; in ,[fn_body], ]. 137 | ].) ; in 138 | ,[body], 139 | ]' 140 | }. ; 141 | in 142 | extend_syntax 143 | ### Add numeric literals. 144 | Expr ::=also 145 | common (forall . '{ 146 | # Awkward `pick` stuff just to have and then throw away `DefaultSeparator`: 147 | digit_string := (pick tok in [,{DefaultSeparator}, tok := (/([0-9]+)/) ]) 148 | }' base_ten_literal -> .{ 149 | let number = (new_cell zero) ; in 150 | -{ 151 | for digit in (string_to_sequence (ident_to_string digit_string)) 152 | (assign number (plus (times (value number) ten) 153 | # The character for digit 0 is codepoint 48: 154 | (minus digit (plus (times ten four) eight)))) ; 155 | (prefab (value number)) 156 | }- 157 | }.) ; 158 | in 159 | extend_syntax 160 | Expr ::=also forall T . '{ 161 | [ 162 | lit ,{ DefaultToken }, = 'if' 163 | cond := ( ,{ Expr< Bool > }, ) 164 | lit ,{ DefaultToken }, = 'then' 165 | then_e := ( ,{ Expr< T > }, ) 166 | lit ,{ DefaultToken }, = 'else' 167 | else_e := ( ,{ Expr< T > }, ) 168 | ] 169 | }' conditional -> .{ 170 | '[Expr | match ,[cond], { 171 | +[True]+ => ,[then_e], 172 | +[False]+ => ,[else_e], } ]' }. ; 173 | in 174 | capture_language -------------------------------------------------------------------------------- /examples/comments.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | DefaultSeparator ::= /((?s:\s|#[^\n|][^\n]*|#\|.*?\|#)*)/ ; 3 | in 4 | # Now we have full-line comments! 5 | (plus one #| And terminated comments! |# one) 6 | -------------------------------------------------------------------------------- /examples/fact.unseemly: -------------------------------------------------------------------------------- 1 | ((fix .[ again : [ -> [ Int -> Int ]] . 2 | .[ n : Int . 3 | match (zero? n) { 4 | +[True]+ => one 5 | +[False]+ => (times n ((again) (minus n one))) 6 | } 7 | ]. 8 | ].) five) 9 | -------------------------------------------------------------------------------- /examples/function_pipe.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | DefaultSeparator ::= /((?s:\s|%|#[^\n|][^\n]*|#\|.*?\|#)*)/ ; 3 | in 4 | extend_syntax 5 | Expr ::=also forall T S . '{[ 6 | lhs := (,{Expr},) 7 | lit ,{DefaultToken}, = '>=>' 8 | rhs := (,{Expr< [T -> S] >},) 9 | ]}' function_pipe -> .{ 10 | '[Expr | (,[rhs], ,[lhs],) ]' 11 | }. ; 12 | # This has a couple of limitations at the moment. 13 | # Unseemly still needs support for n-ary function types before you can do 14 | # one >=> (plus one) 15 | # And it also lacks support for operator precedence, 16 | # so you need to create a bunch of new nonterminals if you want precedence. 17 | in 18 | (plus one one) >=> zero? 19 | -------------------------------------------------------------------------------- /examples/if_macro.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | Expr ::=also forall T . '{ 3 | [ 4 | lit ,{ DefaultToken }, = 'if' 5 | cond := ( ,{ Expr< Bool > }, ) 6 | lit ,{ DefaultToken }, = 'then' 7 | then_e := ( ,{ Expr< T > }, ) 8 | lit ,{ DefaultToken }, = 'else' 9 | else_e := ( ,{ Expr< T > }, ) 10 | ] 11 | }' conditional -> .{ 12 | '[Expr | match ,[cond], { 13 | +[True]+ => ,[then_e], 14 | +[False]+ => ,[else_e], } ]' }. ; 15 | in 16 | if (zero? five) then eight else two 17 | -------------------------------------------------------------------------------- /examples/multifile/comment_library.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | DefaultSeparator ::= /((?s:\s|%|#[^\n|][^\n]*|#\|.*?\|#)*)/ ; 3 | in 4 | # Don't bother defining `let`, just bind `comment_usefulness`: 5 | (.[ comment_usefulness : Int . capture_language ]. one) -------------------------------------------------------------------------------- /examples/multifile/comment_user.comments: -------------------------------------------------------------------------------- 1 | # Invoke with `unseemly comment_library.unseemly comment_user.comments` 2 | 3 | (plus 4 | comment_usefulness 5 | # We can use comments, even though the definition is in another file! 6 | one) -------------------------------------------------------------------------------- /examples/multifile/comment_user.unseemly: -------------------------------------------------------------------------------- 1 | import /[comment_library.unseemly]/ 2 | (plus 3 | comment_usefulness 4 | # We can use comments, even though the definition is in another file! 5 | one) -------------------------------------------------------------------------------- /examples/sdl/example_scene.sdl: -------------------------------------------------------------------------------- 1 | scene (box < 0 0 0 > < 1 1 2 > ) 2 | -------------------------------------------------------------------------------- /examples/sdl/sdl.unseemly: -------------------------------------------------------------------------------- 1 | import /[../build_a_language.unseemly]/ 2 | # This is a demo of a super-basic Scene Description Language for 3D scenes 3 | # that compiles to the POV-Ray SDL. 4 | # If you install POV-Ray, it should pop up an image if you run 5 | # `unseemly sdl.unseemly example_scene.sdl` 6 | 7 | # We will use strings to represent numbers, since we're just going to pass them through... 8 | let_type 9 | # We should probably remove semicolons from `let` for consistency. 10 | Coord = *[x: String y: String z: String]* 11 | in 12 | let_type 13 | Shape = { 14 | +[Box Coord Coord]+ 15 | +[Sphere Coord String]+ 16 | } 17 | in 18 | let header = " 19 | // based on 'Cube Building' in http://www.ms.uky.edu/~lee/visual05/povray/ 20 | #include \"colors.inc\" 21 | #include \"stones.inc\" 22 | #include \"metals.inc\" 23 | 24 | camera { 25 | sky <0,0,1> direction < -1,0,0> 26 | location <10,10,5> look_at <0,0,0> 27 | right < -4/3,0,0> angle 30 28 | } 29 | 30 | light_source {<10,-10,10> color rgb <3,3,3>} 31 | 32 | background { color rgb <2,2,2> } 33 | 34 | plane { // floor 35 | <0,0,-1>,0 36 | texture { T_Silver_1B } 37 | } 38 | " ; 39 | in 40 | letfn (write_coord c: Coord) -> String = 41 | let *[x: x y: y z: z]* = c; 42 | in 43 | (join s["<" x "," y "," z ">"]s "") 44 | ; 45 | in 46 | letfn (write_shape s: Shape) -> String = 47 | match s { 48 | +[Box c_a c_b]+ => (join 49 | s[ "object { box { " (write_coord c_a) " " (write_coord c_b) "} texture {T_Stone32}}" ]s 50 | "") 51 | +[Sphere center radius]+ => "???" 52 | } 53 | ; 54 | in 55 | extend_syntax 56 | Expr ::= 57 | alt[ 58 | forall . '{ 59 | # Awkward `pick` stuff just to have and then throw away `DefaultSeparator`: 60 | digit_string := (pick tok in [,{DefaultSeparator}, tok := (/-?([0-9.]+)/) ]) 61 | }' base_ten_literal_new -> .{ 62 | (prefab (ident_to_string digit_string)) 63 | }. 64 | forall . '{[ 65 | lit ,{ DefaultToken }, = '<' 66 | x := ( ,{Expr}, ) 67 | y := ( ,{Expr}, ) 68 | z := ( ,{Expr}, ) 69 | lit ,{ DefaultToken }, = '>' 70 | ]}' coords -> .{ 71 | '[Expr| *[x: ,[x], y: ,[y], z: ,[z],]*]' 72 | }. 73 | forall . '{[ 74 | lit ,{ OpenDelim }, = '(' 75 | lit ,{ DefaultToken }, = 'box' 76 | corner_a := ( ,{Expr}, ) 77 | corner_b := ( ,{Expr}, ) 78 | lit ,{ CloseDelim }, = ')' 79 | ]}' box -> .{ 80 | '[Expr| +[Box ,[corner_a], ,[corner_b], ]+ : Shape]' 81 | }. 82 | forall . '{[ 83 | lit ,{ DefaultToken }, = 'scene' 84 | body := ( ,{Expr}, ) 85 | ]}' whole_scene -> .{ 86 | '[Expr| -{ 87 | (write_file "/tmp/tmp.pov" (concat header (write_shape ,[body], ))) ; 88 | (os_command "povray" s["/tmp/tmp.pov" "+P" "+A0.1" "+H1200" "+W1600"]s) 89 | }- ]' 90 | }. 91 | # Add `capture_language` to the SDL, so we can be a library. 92 | # Making a macro expand to `'[Expr| capture_language]'` doesn't do the right thing! 93 | capture_language_form 94 | ]alt ; 95 | in 96 | capture_language -------------------------------------------------------------------------------- /examples/sum_list.unseemly: -------------------------------------------------------------------------------- 1 | let_type List = forall T . mu_type List . { +[Nil]+ +[Cons T List]+ } 2 | in 3 | let_type ListUF = forall T . { +[Nil]+ +[Cons T List]+ } 4 | in 5 | (.[ list_123 : List . ((fix 6 | .[again : [-> [List -> Int]] . 7 | .[ lst : List . 8 | match unfold lst { +[Nil]+ => zero 9 | +[Cons hd tl]+ => (plus hd ((again) tl))} ]. ]. ) 10 | list_123)]. 11 | fold +[Cons one 12 | fold +[Cons two 13 | fold +[Cons three fold +[Nil]+ 14 | : ListUF : List ]+ 15 | : ListUF : List]+ : ListUF : List]+ 16 | : ListUF : List ) 17 | -------------------------------------------------------------------------------- /examples/trad_let.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | Expr ::=also 3 | forall T S . '{ [ 4 | lit ,{ DefaultToken }, = 'let' 5 | pat := ( ,{ Pat }, ) 6 | lit ,{ DefaultToken }, = '=' 7 | value := ( ,{ Expr }, ) 8 | lit ,{ DefaultToken }, = ';' 9 | lit ,{ DefaultToken }, = 'in' 10 | body := ( ,{ Expr }, <-- pat = value ) 11 | ] }' let_macro -> .{ 12 | '[Expr | ( .[ arg : ,[ Type | prefab_type S ], . 13 | match arg { ,[pat], => ,[body], } ]. 14 | ,[value], ) ]' 15 | }. ; 16 | in 17 | let fifteen = (plus five ten) ; 18 | in 19 | (plus fifteen one) 20 | -------------------------------------------------------------------------------- /examples/worked_example.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | DefaultSeparator ::= alt[ /(#[^\n|][^\n]*)/ as comment 3 | /\s+/ ]alt * ; 4 | in 5 | # This file demonstrates how to bootstrap a (somewhat more) useable language in Unseemly. 6 | # You should copy it and play around (it's under the MIT license). 7 | 8 | # Unseemly doesn't have comments, 9 | # so the above `extend_syntax` added Python-style #-to-end-of-line comments. 10 | # In the base Unseemly grammar, `DefaultSeparator is matched before each token, 11 | # but only matches whitespace `/\s*/`, 12 | # so when we changed it out, we made it possible to put comments anywhere. 13 | 14 | # After the `in`, the changes to the language take effect, so we can use comments! 15 | # But there's a lot more work to do... 16 | extend_syntax 17 | ### Introduce a simple let-binding expression. 18 | # Using `::=also` in a grammar definition extends, rather than overwrites, 19 | # the definition of `Expr`: 20 | Expr ::=also 21 | # The new form is a macro, and there are two unknown types, which we'll call `T` and `S`: 22 | forall T S . '{ [ 23 | # When specifying a literal, one also provides a nonterminal responsible for lexing. 24 | # `DefaultToken` is just `DefaultSeparator` followed be a lexer 25 | # that matches some nonwhitespace characters. 26 | lit ,{ DefaultToken }, = 'single_let' 27 | # We allow arbitrary patterns, not just variable names, in part to be nice 28 | # ...and in part because `Atom` is a weird special case 29 | # that Unseemly can't interpolate properly in a lot of cases ) : 30 | pat := ( ,{ Pat }, ) 31 | lit ,{ DefaultToken }, = '=' 32 | value := ( ,{ Expr }, ) 33 | lit ,{ DefaultToken }, = ';' 34 | lit ,{ DefaultToken }, = 'in' 35 | # The body of the `single_let` is an expression in which `pat` binds its names. 36 | # A crucial feature for type safety in Unseemly 37 | # is that bindings are specified in macro grammars (with `<--`). 38 | # Both `pat` and `value` are syntax; the `=` says that 39 | # we should use the names in `pat`, assuming `pat` has the same type as `value`. 40 | body := ( ,{ Expr }, <-- ...[pat = value]... ) 41 | ] }' single_let_macro -> .{ 42 | # Now for the actual implementation of the single_let macro. 43 | # We use syntax quotation (`'[NonterminalName| ]'`) to produce a `match` statement. 44 | # and `,[value],` is unquotation; it interpolates the expression `value`. 45 | # A very similiar process builds a pattern that matches all of those values, 46 | # for binding inside `body`. 47 | '[Expr | match ,[value], { ,[pat], => ,[body], } ]' 48 | }. ; 49 | in 50 | single_let eleven = (plus ten one) ; # use it! 51 | in 52 | extend_syntax 53 | ### Introduce `let` expressions. 54 | # Like `single_let`, but this can bind multiple patterns at once: 55 | Expr ::=also 56 | forall T S . '{ [ 57 | lit ,{ DefaultToken }, = 'let' 58 | # After `let` comes an arbitrary number of bindings. 59 | # `S` will be a tuple type, each of whose elements is the type of one arm. 60 | [ 61 | pat := ( ,{ Pat }, ) 62 | lit ,{ DefaultToken }, = '=' 63 | value := ( ,{ Expr }, ) 64 | lit ,{ DefaultToken }, = ';' 65 | ] * 66 | lit ,{ DefaultToken }, = 'in' 67 | # The `...[ ]...` is necessary because `pat` and `value` match multiple times. 68 | body := ( ,{ Expr }, <-- ...[pat = value]... ) 69 | ] }' let_macro -> .{ 70 | # We use `**[ ]**` to build a tuple literal: 71 | # `...[,value, >> ]...` walks over the elements of `value`, 72 | # (underneath it, `value` is a single expression). 73 | '[Expr | 74 | match **[...[,value, >> ,[value], ]... ]** 75 | { **[...[,pat, >> ,[pat],]... ]** => ,[body], } ]' 76 | }. ; 77 | in 78 | extend_syntax 79 | ### Introduce `for` loops 80 | # Invokes `body` once per element in `seq`, which must be of type `Sequence`. 81 | # This macro will use our existing `let` macro. 82 | Expr ::=also forall T . '{ [ 83 | lit ,{ DefaultToken }, = 'for' 84 | pat := ( ,{ Pat }, ) 85 | lit ,{ DefaultToken }, = 'in' 86 | seq := ( ,{ Expr> }, ) 87 | # This uses `:` instead of `=`, because `T` is a type. 88 | # `body` returns `Unit`; it's just being invoked for side-effects. 89 | body := ( ,{ Expr }, <-- pat : T ) 90 | ] }' for_loop -> .{ 91 | '[Expr | 92 | # Unseemly uses Lisp-style function application (function name inside the parens). 93 | # `**[]**` is an empty tuple (the only value with type `Unit`). 94 | # `.[ ].` is a lambda (this one has two arguments, `unit`, and `arg`). 95 | # `prefab_type T` is an expression that produces *syntax* for the type `T`. 96 | # (Oddly, syntax for the type `T` has the type `Type`. It works, though!) 97 | (foldl ,[seq], 98 | **[]** 99 | .[unit : Unit arg : ,[prefab_type T], . 100 | let ,[pat], = arg ; in ,[body], 101 | ]. ) 102 | ]' 103 | }. ; 104 | in 105 | extend_syntax 106 | ### Add numeric literals. 107 | Expr ::=also 108 | # This macro doesn't have any unknown types, 109 | # so the `forall` that's part of the syntax looks a little weird. 110 | forall . '{ 111 | # The awkward `pick` construction here is so that these literals 112 | # benefit from the standard whitespace/comment consumption. 113 | # We have to call `DefaultSeparator`, but then we have to throw it out 114 | # and just look at `tok`, the actual digits. 115 | digit_string := (pick tok in 116 | [,{DefaultSeparator}, tok := (/([0-9]+)/ as constant.number) ]) 117 | }' base_ten_literal -> .{ 118 | # All our previous macros immediately expanded to 119 | # a syntax quotation with a few trivial interpolations. 120 | # This one will actually execute some nontrivial code to decide what to expand to. 121 | # Here we use the `let` (as opposed to just expanding to it). 122 | # Also, we demonstrate Unseemly's highly-non-ergonomic side-effect features: 123 | # `new_cell` creates a mutable value. `assign` and `value` write and read it. 124 | # `-{ }-` is a block, for sequencing operations. 125 | let number = (new_cell zero) ; in 126 | -{ 127 | for digit in (string_to_sequence (ident_to_string digit_string)) 128 | (assign number (plus (times (value number) ten) 129 | # The character for digit 0 is codepoint 48: 130 | (minus digit (plus (times ten four) eight)))) ; 131 | (prefab (value number)) 132 | }- 133 | }. ; 134 | in 135 | extend_syntax 136 | ### Introduce `letfn` for defining 2-argument functions. 137 | # Currently, one has to make a separate macro for each number of arguments, 138 | # because `...[]...` can only be put around a whole expression/pattern/type (issue #38). 139 | Expr ::=also 140 | forall I0 I1 O T . '{ [ 141 | lit ,{ DefaultToken }, = 'letfn' 142 | lit ,{ OpenDelim }, = '(' 143 | fn_name := ( ,{ Pat< [I0 I1 -> O] > }, ) 144 | arg_name0 := ( ,{ Pat }, ) 145 | lit ,{ DefaultToken }, = ':' 146 | arg_type0 := ( ,{ Type }, ) 147 | arg_name1 := ( ,{ Pat }, ) 148 | lit ,{ DefaultToken }, = ':' 149 | arg_type1 := ( ,{ Type }, ) 150 | lit ,{ CloseDelim }, = ')' 151 | lit ,{ DefaultToken }, = '->' 152 | ret_type := ( ,{ Type }, ) 153 | lit ,{ DefaultToken }, = '=' 154 | # `[ o> ]` means "bind the LHS, then bind the RHS". 155 | fn_body := ( ,{ Expr }, <-- [ arg_name0 = arg_type0 o> arg_name1 = arg_type1 ] ) 156 | lit ,{ DefaultToken }, = ';' 157 | lit ,{ DefaultToken }, = 'in' 158 | body := ( ,{ Expr }, <-- fn_name = [ arg_type0 arg_type1 -> ret_type ] ) 159 | ] }' let_fn2 -> .{ 160 | # In case the function is recursive, wrap it in `fix`: 161 | '[Expr | let ,[fn_name], = (fix 162 | .[ again: [ -> [ ,[arg_type0], ,[arg_type1], -> ,[ret_type], ] ] . 163 | .[ a0: ,[arg_type0], a1: ,[arg_type1], . 164 | # Workaround for not being able to interpolate atoms: 165 | let ,[arg_name0], = a0; ,[arg_name1], = a1; in ,[fn_body], ]. 166 | ].) ; in 167 | ,[body], 168 | ]' 169 | }. ; 170 | in 171 | let eleven = (plus 10 one) ; 172 | in 173 | letfn (plusplusplus a: Int b: Int) -> Int = (plus a b) ; 174 | in 175 | (plusplusplus eleven one) 176 | -------------------------------------------------------------------------------- /hll_ideas: -------------------------------------------------------------------------------- 1 | * segmented unit tests: for when your unit tests want to depend on other code 2 | you've written to avoid writing grotty constructors and constructor-like 3 | code. You insert boundaries, and the unit test system tells you which side 4 | changed (thus, who to blame). Try a few variations on this. 5 | * hey, if we're letting our tooling look into the version history, 6 | something with the backwards-compatibility properties of protocol buffers 7 | should be automatically derivable from type definitions. Okay, this probably 8 | takes way too much guessing for something whose semantics can be permanently 9 | affected by past commits. 10 | * macro-powered protobuf-style serialization. (On the theory that any field 11 | could always be absent, and unknown fields can always be present.) When you 12 | deserialize, the type is peppered with `Option` everywhere. At type definition 13 | time, you can specify default values that prevent `Option` introduction. 14 | * a tool for quickly expanding templates over cross-products of lists of syntax, 15 | for avoiding small-scale repetition. 16 | * a tool to automatically turn on logging and `diff` log messages from the 17 | failing tests in current version of the repo against the last good run. 18 | * macro-powered mocks. Need to mock something with a concrete type? No problem, 19 | recompile everything with a different type. 20 | (https://klausi.github.io/rustnish/2019/03/31/mocking-in-rust-with-conditional-compilation.html) 21 | * a simple syntax for trait-directed `.` resolution: `something.Trait::member()` 22 | (https://twitter.com/myrrlyn/status/1133799922082492416) 23 | * package-manager level CI: can we enforce an "at least it compiles" subset of semvar 24 | by looking at the types of interfaces and verifying compilation before publishing a version? 25 | 26 | - example systems I've heard good things about: ggplot, R 27 | -------------------------------------------------------------------------------- /pitch.md: -------------------------------------------------------------------------------- 1 | ## Philosophical pitch 2 | 3 | Unseemly is the first language that can safely typecheck all macros before expansion. 4 | 5 | I like to divide the design of programming languages into two main threads. 6 | There are other, equally-valid, ways of looking at them, 7 | but this one appeals to me. 8 | 9 | One thread, the family of typed languages 10 | includes the MLs and Haskell, as well as C++, Java, Rust, and so on. 11 | Programmers in those languages use type systems 12 | to both describe data they are interested in and to express invariants. 13 | 14 | The other, smaller, thread is macro languages. 15 | These are mostly direct descendants of Lisp: Scheme, and Racket, etc. 16 | If you squint, the dynamic metaprogramming systems of Ruby and JavaScript 17 | make them cousins of this family, too. 18 | Programmers in those languages use metaprogramming to 19 | abstract over surface syntax, control flow, and binding, 20 | and to construct lightweight DSLs that integrate with the main language well. 21 | 22 | But if you write in a typed language, 23 | you almost certainly hear the advice to use the macro system sparingly, 24 | if at all. 25 | And the macro languages all lack type systems. 26 | It's because type errors in macro-generated code are incredibly difficult to understand. 27 | 28 | This is no small issue. 29 | Type errors are the user interface of a typed language; 30 | the primary purpose of types is to produce useful error messages. 31 | 32 | Macros in Unseemly have types. 33 | This means that typechecking happens on code with macros in it, 34 | as opposed to code with all the macros expanded away. 35 | 36 | So, just like a true Scheme, in Unseemly you don't know 37 | whether something is part of the language or whether it's a macro. 38 | And, just like a true ML, Unseemly's type errors are concise and useful. 39 | 40 | 41 | ## Simple, comparative-PL pitch 42 | 43 | Unseemly is a language with ML-like types and Scheme-like macros. 44 | In a sense, it's the first one. 45 | 46 | It's very small, and the syntax is weird, 47 | but you can build a bigger, better language with the macros. 48 | The macros are typechecked before expansion, 49 | so the programmer won't care what is a macro, and what's part of the core language. 50 | 51 | To my knowledge, Unseemly is the first language 52 | where macros that bind names can be safely typechecked without expanding them. 53 | (Modulo one thing that still needs to be designed and implemented) -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | indent_style = "Block" 2 | use_small_heuristics = "Max" 3 | where_single_line = true 4 | format_strings = true 5 | imports_granularity = "Crate" 6 | max_width = 100 7 | normalize_comments = true 8 | use_try_shorthand = true 9 | overflow_delimited_expr = true 10 | fn_single_line = true 11 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | //! This abstract syntax tree is *really* abstract. 2 | //! It makes binding explicit, but everything else about the language is hidden inside `Node`; 3 | //! Their meaning comes from `Form`. 4 | 5 | #![macro_use] 6 | 7 | use crate::{ 8 | beta::{Beta, ExportBeta}, 9 | form::Form, 10 | name::*, 11 | util::mbe::EnvMBE, 12 | }; 13 | use std::{fmt, rc::Rc}; 14 | 15 | // TODO: This really ought to be an `Rc` around an `enum` 16 | #[derive(Clone, PartialEq)] 17 | pub enum AstContents { 18 | Trivial, 19 | /// Typically, a binder 20 | Atom(Name), 21 | VariableReference(Name), 22 | 23 | /// Shift environment to quote more (the `bool` indicates whether it's positive or negative) 24 | QuoteMore(Ast, bool), 25 | /// Shift environment to quote less (the `u8` indicates the number of steps out) 26 | QuoteLess(Ast, u8), 27 | 28 | /// A meaningful chunk of syntax, governed by a form, containing an environment, 29 | /// potentially exporting some names. 30 | Node(std::rc::Rc
, EnvMBE, ExportBeta), 31 | 32 | /// For parsing purposes. 33 | IncompleteNode(EnvMBE), 34 | /// For parsing purposes. 35 | Shape(Vec), 36 | 37 | /// Variable binding 38 | ExtendEnv(Ast, Beta), 39 | /// Variable binding, affects all phases. 40 | /// This is weird, but needed for marcos, it seems. 41 | ExtendEnvPhaseless(Ast, Beta), 42 | } 43 | 44 | #[derive(Clone, PartialEq)] 45 | pub struct LocatedAst { 46 | /// "contents" 47 | pub c: AstContents, 48 | pub file_id: usize, 49 | pub begin: usize, 50 | pub end: usize, 51 | } 52 | 53 | #[derive(Clone)] 54 | pub struct Ast(pub Rc); 55 | 56 | // For testing purposes, we want to ignore span information 57 | impl PartialEq for Ast { 58 | fn eq(&self, other: &Ast) -> bool { self.c() == other.c() } 59 | } 60 | 61 | // Reification macros would totally work for this, 62 | // but it's worth having a special case in `Value` in order to make this faster. 63 | impl crate::runtime::reify::Reifiable for Ast { 64 | fn ty_name() -> Name { n("Ast") } 65 | 66 | fn reify(&self) -> crate::runtime::eval::Value { 67 | crate::runtime::eval::Value::AbstractSyntax(self.clone()) 68 | } 69 | 70 | fn reflect(v: &crate::runtime::eval::Value) -> Ast { 71 | extract!((v) crate::runtime::eval::Value::AbstractSyntax = (ref ast) => ast.clone()) 72 | } 73 | } 74 | 75 | pub use self::AstContents::*; 76 | 77 | impl fmt::Debug for Ast { 78 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:#?}", self.c()) } 79 | } 80 | 81 | impl fmt::Debug for AstContents { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | match self { 84 | Trivial => write!(f, "⨉"), 85 | Atom(n) => write!(f, "∘{}∘", n.print()), 86 | VariableReference(v) => write!(f, "{}", v.print()), 87 | Shape(v) => { 88 | write!(f, "(")?; 89 | let mut first = true; 90 | for elt in v { 91 | if !first { 92 | write!(f, " ")? 93 | } 94 | elt.fmt(f)?; 95 | first = false; 96 | } 97 | write!(f, ")") 98 | } 99 | Node(form, body, export) => { 100 | write!(f, "{{ ({}); {:#?}", form.name.sp(), body)?; 101 | match *export { 102 | crate::beta::ExportBeta::Nothing => {} 103 | _ => write!(f, " ⇑{:#?}", export)?, 104 | } 105 | write!(f, "}}") 106 | } 107 | QuoteMore(body, pos) => { 108 | if *pos { 109 | write!(f, "pos``{:#?}``", body) 110 | } else { 111 | write!(f, "neg``{:#?}``", body) 112 | } 113 | } 114 | QuoteLess(body, depth) => write!(f, ",,({}){:#?},,", depth, body), 115 | IncompleteNode(body) => write!(f, "{{ INCOMPLETE; {:#?} }}", body), 116 | ExtendEnv(body, beta) => write!(f, "{:#?}↓{:#?}", body, beta), 117 | ExtendEnvPhaseless(body, beta) => write!(f, "{:#?}±↓{:#?}", body, beta), 118 | } 119 | } 120 | } 121 | 122 | // Warning: this assumes the core language! To properly display an `Ast`, you need the `SynEnv`. 123 | impl fmt::Display for Ast { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.c()) } 125 | } 126 | 127 | impl fmt::Display for AstContents { 128 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 129 | match self { 130 | Atom(ref n) => write!(f, "{}", n.print()), 131 | VariableReference(ref v) => write!(f, "{}", v.print()), 132 | Node(ref form, ref body, _) => { 133 | let s = crate::unparse::unparse_mbe( 134 | &form.grammar, 135 | self, 136 | body, 137 | &crate::core_forms::get_core_forms(), 138 | ); 139 | write!(f, "{}", s) 140 | } 141 | Shape(ref v) => { 142 | write!(f, "(")?; 143 | let mut first = true; 144 | for elt in v { 145 | if !first { 146 | write!(f, " ")? 147 | } 148 | elt.fmt(f)?; 149 | first = false; 150 | } 151 | write!(f, ")") 152 | } 153 | ExtendEnv(ref body, _) => write!(f, "{}↓", body), 154 | ExtendEnvPhaseless(ref body, _) => write!(f, "{}±↓", body), 155 | QuoteMore(body, _) => { 156 | write!(f, "``{}``", body) 157 | } 158 | QuoteLess(body, _) => write!(f, ",,{},,", body), 159 | _ => write!(f, "{:#?}", self), 160 | } 161 | } 162 | } 163 | 164 | impl Ast { 165 | /// "Contents". Ignore location information and get the interesting bits 166 | pub fn c(&self) -> &AstContents { &self.0.c } 167 | 168 | /// Replace the contents. 169 | pub fn c_map(&self, f: &dyn Fn(&AstContents) -> AstContents) -> Ast { self.with_c(f(self.c())) } 170 | 171 | /// Keep the location information, but replace the contents. 172 | pub fn with_c(&self, c: AstContents) -> Ast { 173 | Ast(Rc::new(LocatedAst { 174 | c: c, 175 | file_id: self.0.file_id, 176 | begin: self.0.begin, 177 | end: self.0.end, 178 | })) 179 | } 180 | 181 | // TODO: this ought to at least warn if we're losing anything other than `Shape` 182 | pub fn flatten(&self) -> EnvMBE { 183 | match self.c() { 184 | Trivial | Atom(_) => EnvMBE::new(), 185 | VariableReference(_) => EnvMBE::new(), 186 | Shape(ref v) => { 187 | let mut accum = EnvMBE::new(); 188 | for sub_a in v { 189 | accum = accum.combine_overriding(&sub_a.flatten()) 190 | } 191 | accum 192 | } 193 | IncompleteNode(ref env) => env.clone(), 194 | Node(ref _f, ref _body, ref _export) => { 195 | // TODO: think about what should happen when 196 | // `Scope` contains a `Scope` without an intervening `Named` 197 | panic!("I don't know what to do with {:#?}!", self) 198 | } 199 | QuoteMore(ref body, _) => body.flatten(), 200 | QuoteLess(ref body, _) => body.flatten(), 201 | ExtendEnv(ref body, _) | ExtendEnvPhaseless(ref body, _) => body.flatten(), 202 | } 203 | } 204 | 205 | // TODO: use `Mode::context_match` whenever possible 206 | pub fn destructure( 207 | &self, 208 | expd_form: std::rc::Rc, 209 | ) -> Option> { 210 | if let Node(ref f, ref parts, _) = self.c() { 211 | if f == &expd_form { 212 | return Some(parts.clone()); 213 | } 214 | } 215 | None 216 | } 217 | 218 | pub fn is_node(&self) -> bool { 219 | match self.c() { 220 | Node(_, _, _) => true, 221 | _ => false, 222 | } 223 | } 224 | 225 | // TODO: I think we have a lot of places where we ought to use this function: 226 | pub fn node_parts(&self) -> &EnvMBE { 227 | match self.c() { 228 | Node(_, ref body, _) => body, 229 | _ => icp!(), 230 | } 231 | } 232 | 233 | pub fn maybe_node_parts(&self) -> Option<&EnvMBE> { 234 | match self.c() { 235 | Node(_, ref body, _) => Some(body), 236 | _ => None, 237 | } 238 | } 239 | 240 | pub fn node_form(&self) -> &Form { 241 | match self.c() { 242 | Node(ref form, _, _) => form, 243 | _ => icp!(), 244 | } 245 | } 246 | 247 | pub fn free_vrs(&self) -> Vec { 248 | match self.c() { 249 | Trivial | Atom(_) => vec![], 250 | VariableReference(v) => vec![*v], 251 | Shape(_) | IncompleteNode(_) => unimplemented!("TODO"), 252 | QuoteLess(_, _) | QuoteMore(_, _) => unimplemented!("TODO"), 253 | // This one is actually encounterable by real-world code 254 | // (if a ∀ somehow ends up underneath a `*` in syntax.) 255 | // And we need to take a LazyWalkReses to do this right. 256 | ExtendEnv(_, _) | ExtendEnvPhaseless(_, _) => unimplemented!("TODO"), 257 | Node(_, ref body, _) => body.map_reduce( 258 | &|a| a.free_vrs(), 259 | &|v0, v1| { 260 | let mut res = v0.clone(); 261 | res.append(&mut v1.clone()); 262 | res 263 | }, 264 | vec![], 265 | ), 266 | } 267 | } 268 | 269 | pub fn to_name(&self) -> Name { 270 | match self.c() { 271 | Atom(n) => *n, 272 | _ => icp!("{:#?} is not an atom", self), 273 | } 274 | } 275 | 276 | pub fn vr_to_name(&self) -> Name { 277 | match self.c() { 278 | VariableReference(n) => *n, 279 | _ => icp!("{:#?} is not an atom", self), 280 | } 281 | } 282 | 283 | pub fn orig_str<'a, 'b>(&'a self, prog: &'b str) -> &'b str { &prog[self.0.begin..self.0.end] } 284 | } 285 | 286 | #[test] 287 | fn star_construction() { 288 | let env = mbe!( "a" => ["1", "2"]); 289 | 290 | assert_eq!( 291 | ast!( { - "x" => [* env =>("a") env : (, env.get_leaf_or_panic(&n("a")).clone())]} ), 292 | ast!( { - "x" => ["1", "2"] }) 293 | ); 294 | 295 | let env = mbe!( "a" => [@"duo" "1", "2"], "b" => [@"duo" "11", "22"]); 296 | 297 | assert_eq!( 298 | ast!( { - "x" => [* env =>("a", "b") env : 299 | ((, env.get_leaf_or_panic(&n("b")).clone()) 300 | (, env.get_leaf_or_panic(&n("a")).clone()))]} ), 301 | ast!( { - "x" => [("11" "1"), ("22" "2")] }) 302 | ); 303 | } 304 | 305 | #[test] 306 | fn mbe_r_and_r_roundtrip() { 307 | use crate::runtime::reify::Reifiable; 308 | let mbe1 = mbe!( "a" => [@"duo" "1", "2"], "b" => [@"duo" "11", "22"]); 309 | assert_eq!(mbe1, EnvMBE::::reflect(&mbe1.reify())); 310 | } 311 | -------------------------------------------------------------------------------- /src/core_extra_forms.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::{Ast, AstContents::*}, 3 | ast_walk::{LazyWalkReses, WalkRule::*}, 4 | core_type_forms::get__primitive_type, 5 | grammar::FormPat, 6 | name::*, 7 | runtime::eval::Value, 8 | util::mbe::EnvMBE, 9 | }; 10 | use std::rc::Rc; 11 | 12 | pub fn extend__capture_language( 13 | pc: crate::earley::ParseContext, 14 | _starter_info: Ast, 15 | ) -> crate::earley::ParseContext { 16 | crate::earley::ParseContext { 17 | grammar: assoc_n!("OnlyNt" => 18 | Rc::new(FormPat::Named(n("body"), Rc::new(FormPat::Anyways(raw_ast!(Node( 19 | basic_typed_form!( 20 | [], // No syntax 21 | cust_rc_box!(|parts| { 22 | // Reify the current type environment: 23 | let mut struct_body = vec![]; 24 | 25 | for (k, v) in parts.env.iter_pairs() { 26 | struct_body.push(EnvMBE::new_from_leaves(assoc_n!( 27 | "component_name" => ast!((at *k)), 28 | "component" => v.clone() 29 | ))) 30 | } 31 | 32 | // HACK: Anything that was added to the prelude is phaseless. 33 | let phaseless_env = parts.prelude_env.cut_common( 34 | &crate::runtime::core_values::core_types()); 35 | 36 | let mut struct_body__phaseless = vec![]; 37 | 38 | for (k, v) in phaseless_env.iter_pairs() { 39 | struct_body__phaseless.push(EnvMBE::new_from_leaves(assoc_n!( 40 | "component_name" => ast!((at *k)), 41 | "component" => v.clone() 42 | ))) 43 | } 44 | 45 | Ok(ast!({"Type" "tuple" : 46 | "component" => [ 47 | (, get__primitive_type(n("LanguageSyntax"))), 48 | (, raw_ast!(Node(crate::core_forms::find("Type", "struct"), 49 | EnvMBE::new_from_anon_repeat(struct_body), 50 | crate::beta::ExportBeta::Nothing))), 51 | (, raw_ast!(Node(crate::core_forms::find("Type", "struct"), 52 | EnvMBE::new_from_anon_repeat(struct_body__phaseless), 53 | crate::beta::ExportBeta::Nothing)))] 54 | }))}), 55 | cust_rc_box!(move |parts| { 56 | Ok(Value::Sequence(vec![ 57 | // The captured language syntax: 58 | Rc::new(Value::ParseContext(Box::new(pc.clone()))), 59 | // Reifying the value environment is easy: 60 | Rc::new(Value::Struct(parts.env)) 61 | ]))}) 62 | ), 63 | EnvMBE::::new(), 64 | crate::beta::ExportBeta::Nothing 65 | ))))))), 66 | // We can't just squirrel `reified_language` here: 67 | // these only affect earlier phases, and we need the language in phase 0 68 | eval_ctxt: LazyWalkReses::::new_empty(), 69 | type_ctxt: LazyWalkReses::::new_empty(), 70 | } 71 | } 72 | 73 | // Shift the parser into the language specified in "filename". 74 | // TODO: This is probably unhygenic in some sense. Perhaps this needs to be a new kind of `Beta`? 75 | fn extend_import( 76 | _pc: crate::earley::ParseContext, 77 | starter_info: Ast, 78 | ) -> crate::earley::ParseContext { 79 | let filename = match starter_info.c() { 80 | // Skip "import" and the separator: 81 | Shape(ref parts) => match parts[2].c() { 82 | IncompleteNode(ref parts) => { 83 | parts.get_leaf_or_panic(&n("filename")).to_name().orig_sp() 84 | } 85 | _ => icp!("Unexpected structure {:#?}", parts), 86 | }, 87 | _ => icp!("Unexpected structure {:#?}", starter_info), 88 | }; 89 | 90 | let crate::Language { pc, type_env, type_env__phaseless, value_env } = 91 | crate::language_from_file(&std::path::Path::new(&filename)); 92 | 93 | crate::earley::ParseContext { 94 | grammar: pc.grammar.set( 95 | n("ImportStarter"), 96 | Rc::new(FormPat::Scope( 97 | basic_typed_form!( 98 | (named "body", (call "Expr")), 99 | cust_rc_box!(move |parts| { 100 | // HACK: Copied from `ExtendEnvPhaseless` 101 | LazyWalkReses { 102 | env: parts.env.set_assoc(&type_env) 103 | .set_assoc(&type_env__phaseless), 104 | prelude_env: parts.prelude_env.set_assoc(&type_env__phaseless), 105 | more_quoted_env: parts.more_quoted_env.iter().map( 106 | |e| e.set_assoc(&type_env__phaseless)).collect(), 107 | less_quoted_env: parts.less_quoted_env.iter().map( 108 | |e| e.set_assoc(&type_env__phaseless)).collect(), 109 | .. parts.clone() 110 | }.get_res(n("body")) 111 | }), 112 | cust_rc_box!(move |parts| { 113 | parts.with_environment( 114 | parts.env.set_assoc(&value_env)).get_res(n("body")) 115 | }) 116 | ), 117 | crate::beta::ExportBeta::Nothing, 118 | )), 119 | ), 120 | ..pc 121 | } 122 | } 123 | 124 | /// Some of these forms are theoretically implementable as macros from other forms, 125 | /// but for performance and debugability reasons, they are a part of Unseemly. 126 | /// Other of these forms are just not central to the design of Unseemly and have ad-hoc designs. 127 | /// 128 | /// Stored as a `FormPat` instead of a `SynEnv` 129 | /// because we need to merge this with the rest of the "Expr"s. 130 | pub fn make_core_extra_forms() -> FormPat { 131 | // I think we want to have "Stmt" separate from "Expr", once #4 is complete. 132 | // Should "Item"s be valid "Stmt"s? Let's do whatever Rust does. 133 | 134 | forms_to_form_pat![ 135 | typed_form!("prefab_type", 136 | [(lit "prefab_type"), (named "ty", (call "Type"))], 137 | /* type */ 138 | cust_rc_box!(move |part_types| { 139 | Ok(ast!({"Type" "type_apply" : 140 | "type_rator" => (, (get__primitive_type(n("Type")))), 141 | "arg" => [(, part_types.get_res(n("ty"))?)] 142 | })) 143 | }), 144 | /* evaluation */ 145 | // HACK: at evaluation time, nobody cares 146 | cust_rc_box!(move |_| { 147 | Ok(Value::AbstractSyntax(ast!((trivial)))) 148 | }) 149 | ), 150 | typed_form!("block", 151 | (delim "-{", "{", [(star [(named "effect", (call "Expr")), (lit ";")]), 152 | (named "result", (call "Expr"))]), 153 | /* type */ 154 | cust_rc_box!(move |part_types| { 155 | let _ = part_types.get_rep_res(n("effect"))?; 156 | part_types.get_res(n("result")) 157 | }), 158 | /* evaluation */ 159 | cust_rc_box!( move | part_values | { 160 | for effect_values in part_values.march_all(&[n("effect")]) { 161 | let _ = effect_values.get_res(n("effect"))?; 162 | } 163 | part_values.get_res(n("result")) 164 | })), 165 | typed_form!("capture_language", 166 | // Immediately descend into a grammar with one NT pointing to one form, 167 | // which has captured the whole parse context. 168 | (extend_nt [(lit "capture_language")], "OnlyNt", extend__capture_language), 169 | Body(n("body")), 170 | Body(n("body"))), 171 | typed_form!("import_language_from_file", 172 | (extend 173 | [(lit "import"), (call "DefaultSeparator"), 174 | (named "filename", (scan r"/\[(.*)]/"))], 175 | (named "body", (call "ImportStarter")), 176 | extend_import), 177 | Body(n("body")), 178 | Body(n("body"))), 179 | typed_form!("string_literal", 180 | (named "body", (scan_cat r#"\s*"((?:[^"\\]|\\"|\\\\)*)""#, "string.quoted.double")), 181 | cust_rc_box!(|_| { 182 | Ok(ast!({"Type" "String" :})) 183 | }), 184 | cust_rc_box!(|parts| { 185 | // Undo the escaping: 186 | Ok(Value::Text(parts.get_term(n("body")).to_name().orig_sp() 187 | .replace(r#"\""#, r#"""#) 188 | .replace(r#"\\"#, r#"\"#))) 189 | }) 190 | ), 191 | // Sequence literals. These actually can't be implemented as a macro 192 | // until we get recursive macro invocations: 193 | // there's no other way to go from a tuple to a sequence. 194 | typed_form!("seq_literal", 195 | (delim "s[", "[", (star (named "elt", (call "Expr")))), 196 | cust_rc_box!(|parts| { 197 | let mut elts = parts.get_rep_res(n("elt"))?; 198 | match elts.pop() { 199 | None => Ok(ast!({"Type" "forall_type" : 200 | "param" => ["T"], 201 | "body" => (import [* [forall "param"]] { "Type" "type_apply" : 202 | "type_rator" => (vr "Sequence"), "arg" => [(vr "T")]})})), 203 | Some(ref t) => { 204 | for ref other_elt in elts { 205 | crate::ty_compare::must_equal(t, other_elt, &parts).map_err( 206 | |e| crate::util::err::sp(e, parts.this_ast.clone()) 207 | )?; 208 | } 209 | Ok(ast!({ "Type" "type_apply" : 210 | "type_rator" => (vr "Sequence"), 211 | "arg" => [(, t.clone())]})) 212 | } 213 | } 214 | }), 215 | cust_rc_box!(|parts| { 216 | Ok(Value::Sequence( 217 | parts.get_rep_res(n("elt"))?.into_iter().map(|elt| Rc::new(elt)).collect())) 218 | }) 219 | ) 220 | ] 221 | } 222 | -------------------------------------------------------------------------------- /src/expand.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::Ast, 3 | ast_walk::{LazyWalkReses, WalkRule, WalkRule::LiteralLike}, 4 | form::Form, 5 | name::{n, Name}, 6 | runtime::{eval, eval::Value}, 7 | walk_mode::{NegativeWalkMode, WalkMode}, 8 | }; 9 | 10 | custom_derive! { 11 | #[derive(Copy, Clone, Debug, Reifiable)] 12 | pub struct ExpandMacros {} 13 | } 14 | custom_derive! { 15 | #[derive(Copy, Clone, Debug, Reifiable)] 16 | pub struct UnusedNegativeExpandMacros {} 17 | } 18 | 19 | impl WalkMode for ExpandMacros { 20 | fn name() -> &'static str { "MExpand" } 21 | type Elt = Value; 22 | type Negated = UnusedNegativeExpandMacros; 23 | type AsPositive = ExpandMacros; 24 | type AsNegative = UnusedNegativeExpandMacros; 25 | type Err = ::Err; 26 | type D = crate::walk_mode::Positive; 27 | type ExtraInfo = (); 28 | 29 | fn get_walk_rule(f: &Form) -> WalkRule { 30 | if f.name == n("macro_invocation") { 31 | let rule = f.eval.pos().clone(); 32 | cust_rc_box!(move |parts| { 33 | match rule { 34 | WalkRule::Custom(ref ts_fn) => ts_fn(parts.switch_mode::()), 35 | _ => icp!(), 36 | } 37 | }) 38 | } else { 39 | LiteralLike 40 | } 41 | } 42 | fn automatically_extend_env() -> bool { false } 43 | 44 | // TODO: maybe keep this from being called? 45 | fn underspecified(_: Name) -> Value { val!(enum "why is this here?", ) } 46 | 47 | fn walk_var(name: Name, _parts: &LazyWalkReses) -> Result { 48 | use crate::runtime::reify::Reifiable; 49 | Ok(ast!((vr name)).reify()) // Even variables are literal in macro expansion! 50 | } 51 | } 52 | impl WalkMode for UnusedNegativeExpandMacros { 53 | fn name() -> &'static str { "XXXXX" } 54 | type Elt = Value; 55 | type Negated = ExpandMacros; 56 | type AsPositive = ExpandMacros; 57 | type AsNegative = UnusedNegativeExpandMacros; 58 | type Err = ::Err; 59 | type D = crate::walk_mode::Negative; 60 | type ExtraInfo = (); 61 | fn get_walk_rule(_: &Form) -> WalkRule { icp!() } 62 | fn automatically_extend_env() -> bool { icp!() } 63 | } 64 | 65 | impl NegativeWalkMode for UnusedNegativeExpandMacros { 66 | fn needs_pre_match() -> bool { panic!() } 67 | } 68 | 69 | // I *think* the environment doesn't matter 70 | pub fn expand(ast: &Ast) -> Result { 71 | use crate::runtime::reify::Reifiable; 72 | Ok(Ast::reflect(&crate::ast_walk::walk::(ast, &LazyWalkReses::new_empty())?)) 73 | } 74 | 75 | #[test] 76 | fn expand_basic_macros() { 77 | use crate::{core_macro_forms::macro_invocation, util::assoc::Assoc}; 78 | 79 | // Quasiquotation doesn't work with `u!`, so we have to use `ast!`: 80 | let macro_body_0_args = ast!({"Expr" "quote_expr" : "nt" => (vr "Expr"), 81 | "body" => (++ true (,u!({apply : plus [one ; two]})))}); 82 | 83 | let uqef = crate::core_qq_forms::unquote_form(n("Expr"), true, 1); 84 | let uqpf = crate::core_qq_forms::unquote_form(n("Pat"), true, 1); 85 | 86 | let macro_def_0_args = u!({Syntax scope : 87 | [] {literal => [] : {call : DefaultToken} (at just_add_1_and_2)} 88 | just_add_1_and_2_macro 89 | (,macro_body_0_args.clone()) 90 | }); 91 | 92 | // Full of closures, so hard to compare: 93 | assert_m!(eval::eval_top(¯o_def_0_args), Ok(_)); 94 | 95 | assert_eq!( 96 | expand(&u!({ 97 | macro_invocation( 98 | form_pat!((lit "just_add_1_and_2")), 99 | n("just_add_1_and_2_macro"), 100 | vec![], 101 | eval::Closure { body: macro_body_0_args, params: vec![], env: Assoc::new() }, 102 | vec![], 103 | ); 104 | })), 105 | Ok(u!({apply : plus [one ; two]})) 106 | ); 107 | 108 | // A macro that generates a one-adding expression: 109 | 110 | let macro_body_1_arg = ast!({"Expr" "quote_expr" : "nt" => (vr "Expr"), 111 | "body" => (++ true (,u!({apply : plus [one ; { uqef.clone(); (~) e}]})))}); 112 | 113 | let macro_def_1_arg = u!({Syntax scope : 114 | [] {seq => [* ["elt"]] : [{literal => [] : {call : DefaultToken} (at add_1)} ; 115 | {named => ["part_name"] : e {call : Expr}}] } 116 | add_1_macro 117 | (,macro_body_1_arg.clone()) 118 | }); 119 | 120 | // Full of closures, so hard to compare: 121 | assert_m!(eval::eval_top(¯o_def_1_arg), Ok(_)); 122 | 123 | assert_eq!( 124 | expand(&u!({ 125 | macro_invocation( 126 | // duplicates the syntax syntax above 127 | form_pat!([(lit "add_1"), (named "e", (call "Expr"))]), 128 | n("add_1_macro"), 129 | vec![], 130 | eval::Closure { body: macro_body_1_arg, params: vec![n("e")], env: Assoc::new() }, 131 | vec![], 132 | ); 133 | five // syntax argument for e 134 | })), 135 | Ok(u!({apply : plus [one ; five]})) 136 | ); 137 | 138 | // A let macro: 139 | 140 | let macro_body_let = ast!({"Expr" "quote_expr" : "nt" => (vr "Expr"), 141 | "body" => (++ true (,u!( 142 | {match : { uqef.clone(); (~) let_val} 143 | [{ uqpf.clone(); (~) let_pat } {uqef.clone(); (~) let_body}]})))}); 144 | 145 | let macro_def_let = u!({Syntax scope : 146 | [T; S] {seq => [* ["elt"]] : [{literal => [] : {call : DefaultToken} (at let)} ; 147 | {named => ["part_name"] : let_pat {call : Pat}} ; 148 | {named => ["part_name"] : let_val {call : Expr}} ; 149 | {named => ["part_name"] : let_body {call : Expr}}] } 150 | let_macro 151 | (,macro_body_let.clone()) 152 | }); 153 | 154 | // Full of closures, so hard to compare: 155 | assert_m!(eval::eval_top(¯o_def_let), Ok(_)); 156 | 157 | assert_eq!( 158 | expand(&u!({ 159 | macro_invocation( 160 | // duplicates the syntax syntax above 161 | form_pat!([(lit "let"), (named "let_pat", (call "Pat")), 162 | (named "let_val", (call "Expr")), (named "let_body", (call "Expr"))]), 163 | n("let_macro"), 164 | vec![u!(T), u!(S)], 165 | eval::Closure { 166 | body: macro_body_let.clone(), 167 | params: vec![n("let_val"), n("let_pat"), n("let_body")], 168 | env: Assoc::new(), 169 | }, 170 | vec![], 171 | ); 172 | x // let_pat 173 | five // let_val 174 | {apply : times [x ; eight]} // let_body 175 | })), 176 | Ok(u!({match : five [x {apply : times [x ; eight]}]})) 177 | ); 178 | 179 | // The previous example was unrealistic, since no actual binding took place in the input. 180 | // (It works because the binding is actually irrelevant at this stage.) 181 | // Once more, with binding: 182 | assert_eq!( 183 | expand(&u!({ 184 | macro_invocation( 185 | // duplicates the syntax syntax above 186 | form_pat!([(lit "let"), (named "let_pat", (call "Pat")), 187 | (named "let_val", (call "Expr")), 188 | (named "let_body", (import ["let_pat" = "let_val"], (call "Expr")))]), 189 | n("let_macro"), 190 | vec![u!(T), u!(S)], 191 | eval::Closure { 192 | body: macro_body_let, 193 | params: vec![n("let_val"), n("let_pat"), n("let_body")], 194 | env: Assoc::new(), 195 | }, 196 | vec![], 197 | ); 198 | x // let_pat 199 | five // let_val 200 | (, raw_ast!(ExtendEnv(u!({apply : times [x ; eight]}), 201 | beta!(["let_pat" = "let_val"])))) // let_body 202 | })), 203 | Ok(u!({match : five [x {apply : times [x ; eight]}]})) 204 | ); 205 | 206 | // An n-ary let macro: 207 | 208 | let dddef = crate::core_qq_forms::dotdotdot_form(n("Expr")); 209 | let dddpf = crate::core_qq_forms::dotdotdot_form(n("Pat")); 210 | 211 | let macro_body_nary_let = ast!({"Expr" "quote_expr" : "nt" => (vr "Expr"), 212 | "body" => (++ true (, u!( 213 | {match : {tuple_expr : [{dddef.clone() ; [(~ let_val)] { uqef.clone(); (~) let_val}}]} 214 | [{Pat tuple_pat : [{dddpf.clone() ; [(~ let_pat)] { uqpf.clone(); (~) let_pat }}]} 215 | { uqef.clone(); (~) let_body}]})))}); 216 | 217 | let macro_def_nary_let = u!({Syntax scope : 218 | [T; S] {seq => [* ["elt"]] : 219 | [{literal => [] : {call : DefaultToken} (at let)} ; 220 | {star => ["body"] : {named => ["part_name"] : let_pat {call : Pat}}} ; 221 | {star => ["body"] : {named => ["part_name"] : let_val {call : Expr}}} ; 222 | {named => ["part_name"] : let_body {call : Expr}}] } 223 | nary_let_macro 224 | (,macro_body_nary_let.clone()) 225 | }); 226 | 227 | // Full of closures, so hard to compare: 228 | assert_m!(eval::eval_top(¯o_def_nary_let), Ok(_)); 229 | 230 | assert_eq!( 231 | expand(&u!({ 232 | macro_invocation( 233 | // duplicates the syntax syntax above 234 | form_pat!([(lit "let"), (star (named "let_pat", (call "Pat"))), 235 | (star (named "let_val", (call "Expr"))), 236 | (named "let_body", (call "Expr"))]), 237 | n("nary_let_macro"), 238 | vec![u!(T), u!(S)], 239 | eval::Closure { 240 | body: macro_body_nary_let, 241 | params: vec![n("let_val"), n("let_pat"), n("let_body")], 242 | env: Assoc::new(), 243 | }, 244 | vec![], 245 | ); 246 | [x; y] // let_pat 247 | [five; seven] // let_val 248 | {apply : times [x ; eight]} // let_body 249 | })), 250 | Ok(u!({match : {tuple_expr : [five; seven]} 251 | [{Pat tuple_pat : [x; y]} {apply : times [x; eight]}]})) 252 | ); 253 | } 254 | -------------------------------------------------------------------------------- /src/form.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | use crate::{ 4 | ast_walk::WalkRule, grammar::FormPat, name::*, util::assoc::Assoc, walk_mode::WalkMode, 5 | }; 6 | use std::{ 7 | fmt::{Debug, Error, Formatter}, 8 | rc::Rc, 9 | }; 10 | 11 | pub type NMap = Assoc; 12 | 13 | /// "BiDirectionalWalkRule": a walk rule, abstracted over whether the walk is positive or negative 14 | pub type BiDiWR = EitherPN, WalkRule>; 15 | 16 | custom_derive! { 17 | /// Unseemly language form. This is what tells us what a particular `Node` actually does. 18 | #[derive(Reifiable)] 19 | pub struct Form { 20 | /// The name of the form. Mainly for internal use. 21 | pub name: Name, 22 | /// The grammar the programmer should use to invoke this form. 23 | /// This contains information about bindings and syntax extension: 24 | pub grammar: Rc, 25 | /// (Meaningful for types only) Subtype. 26 | pub type_compare: BiDiWR, 27 | /// From a type environment, construct the type of this term. 28 | pub synth_type: BiDiWR, 29 | /// (Meaningful for exprs and pats only) From a value environment, evaluate this term. 30 | /// Or, (HACK) macro expansion, for macro invocations (just so we don't need another field) 31 | pub eval: BiDiWR, 32 | /// At runtime, pick up code to use it as a value 33 | pub quasiquote: BiDiWR, 34 | } 35 | } 36 | 37 | custom_derive! { 38 | /// The distinction between `Form`s with positive and negative walks is documented at `Mode`. 39 | #[derive(Reifiable)] 40 | pub enum EitherPN { 41 | Positive(L), 42 | Negative(R), 43 | Both(L, R) 44 | // Maybe instead of WalkRule::NotWalked, we need EitherPN::Neither 45 | } 46 | } 47 | pub use self::EitherPN::*; 48 | 49 | impl EitherPN, WalkRule> { 50 | pub fn pos(&self) -> &WalkRule { 51 | match *self { 52 | Positive(ref l) | Both(ref l, _) => l, 53 | Negative(_) => &WalkRule::NotWalked, 54 | } 55 | } 56 | pub fn neg(&self) -> &WalkRule { 57 | match *self { 58 | Negative(ref r) | Both(_, ref r) => r, 59 | Positive(_) => &WalkRule::NotWalked, 60 | } 61 | } 62 | pub fn is_pos(&self) -> bool { 63 | match *self { 64 | Negative(_) => false, 65 | _ => true, 66 | } 67 | } 68 | pub fn is_neg(&self) -> bool { 69 | match *self { 70 | Positive(_) => false, 71 | _ => true, 72 | } 73 | } 74 | } 75 | 76 | impl PartialEq for Form { 77 | /// pointer equality on the underlying structure! 78 | fn eq(&self, other: &Form) -> bool { self as *const Form == other as *const Form } 79 | } 80 | 81 | impl Debug for Form { 82 | fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { 83 | formatter.write_str(format!("[FORM {:#?}]", self.name).as_str()) 84 | } 85 | } 86 | 87 | pub fn simple_form(form_name: &str, p: FormPat) -> Rc { 88 | use WalkRule::*; 89 | Rc::new(Form { 90 | name: n(form_name), 91 | grammar: Rc::new(p), 92 | type_compare: Both(NotWalked, NotWalked), 93 | synth_type: Positive(NotWalked), 94 | eval: Positive(NotWalked), 95 | quasiquote: Both(LiteralLike, LiteralLike), 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /src/highlighter_generation.rs: -------------------------------------------------------------------------------- 1 | use crate::{earley::parse, grammar::SynEnv}; 2 | 3 | pub fn ace_rules(se: &SynEnv) -> String { 4 | let mut categories = vec![]; 5 | let mut keyword_operators = vec![]; 6 | for (_, nt_grammar) in se.iter_pairs() { 7 | // Separate "keyword.operator" out; there are so many of them. 8 | // TODO: The principled thing to do would be to do this for each name... 9 | let (keyword_operator, mut normal) = nt_grammar 10 | .textmate_categories() 11 | .into_iter() 12 | .partition(|(_, cat)| cat == "keyword.operator"); 13 | categories.append(&mut normal); 14 | keyword_operators.append(&mut keyword_operator.into_iter().map(|(pat, _)| pat).collect()); 15 | } 16 | 17 | keyword_operators.sort(); 18 | keyword_operators.dedup(); 19 | 20 | // Make one big rule for all of them (will perform better, probably): 21 | categories.push((keyword_operators.join("|"), "keyword.operator".to_string())); 22 | 23 | // Order, roughly, by specificity of syntax: 24 | categories.sort_by(|a, b| { 25 | if a.1 == "keyword" { 26 | return std::cmp::Ordering::Less; 27 | } 28 | if b.1 == "keyword" { 29 | return std::cmp::Ordering::Greater; 30 | } 31 | if a.1.starts_with("string") { 32 | return std::cmp::Ordering::Less; 33 | } 34 | if b.1.starts_with("string") { 35 | return std::cmp::Ordering::Greater; 36 | } 37 | if a.1.starts_with("paren") { 38 | return std::cmp::Ordering::Less; 39 | } 40 | if b.1.starts_with("paren") { 41 | return std::cmp::Ordering::Greater; 42 | } 43 | if a.1.starts_with("keyword.operator") { 44 | return std::cmp::Ordering::Less; 45 | } 46 | if b.1.starts_with("keyword.operator") { 47 | return std::cmp::Ordering::Greater; 48 | } 49 | if a.1.starts_with("variable") { 50 | return std::cmp::Ordering::Less; 51 | } 52 | if b.1.starts_with("variable") { 53 | return std::cmp::Ordering::Greater; 54 | } 55 | 56 | std::cmp::Ordering::Equal 57 | }); 58 | categories.dedup(); 59 | 60 | let mut res = String::new(); 61 | for (pat, name) in categories { 62 | if let Ok(re) = regex::Regex::new(&pat) { 63 | if re.is_match("") { 64 | continue; // TODO: warn about regexes matching empty strings! 65 | } 66 | } else { 67 | continue; // TODO: warn about bad regexes! 68 | } 69 | res.push_str(&format!( 70 | "{{ token: '{}', regex: /{}/ }},\n", 71 | name, 72 | // Remove some regexp concepts not supported by JS: 73 | pat.replace(r"\p{Letter}", r"[a-zA-Z\xa1-\uFFFF]") 74 | .replace(r"\p{Number}", r"[0-9]") 75 | .replace("/", "\\/") // Escape slashes 76 | )) 77 | } 78 | res 79 | } 80 | 81 | pub fn dynamic__ace_rules(prog: &str, lang: &crate::Language) -> String { 82 | // This only works with the Unseemly syntax extension form, which sets this side-channel: 83 | crate::core_macro_forms::syn_envs__for__highlighting.with(|envs| envs.borrow_mut().clear()); 84 | 85 | // Errors are okay, especially late! 86 | let _ = parse(&crate::core_forms::outermost_form(), lang.pc.clone(), prog); 87 | 88 | let mut result = String::new(); 89 | 90 | crate::core_macro_forms::syn_envs__for__highlighting.with(|envs| { 91 | use indoc::writedoc; 92 | use std::fmt::Write; 93 | 94 | let mut prev_grammar = lang.pc.grammar.clone(); 95 | let mut cur_rule_name = "start".to_string(); 96 | let mut idx = 0; 97 | 98 | for (extender_ast, grammar) in &*envs.borrow() { 99 | let longest_line = extender_ast 100 | .orig_str(prog) 101 | .split('\n') 102 | .map(str::trim) 103 | .max_by(|a, b| a.len().cmp(&b.len())) 104 | .unwrap(); 105 | 106 | writedoc!( 107 | result, 108 | " 109 | {}: [ 110 | {{ token: 'text', regex: /(?={})/, next: 'still_{}' }}, {} 111 | ], 112 | ", 113 | cur_rule_name, 114 | regex::escape(longest_line).replace('/', "\\/"), 115 | cur_rule_name, 116 | ace_rules(&prev_grammar), 117 | ) 118 | .unwrap(); 119 | 120 | idx += 1; 121 | let next_rule_name = format!("lang_{}", idx); 122 | 123 | // Stay in the current language until we hit `in`. 124 | writedoc!( 125 | result, 126 | " 127 | still_{}: [ 128 | {{ token: 'keyword.operator', regex: 'in', next: '{}' }}, {} 129 | ], 130 | ", 131 | cur_rule_name, 132 | next_rule_name, 133 | ace_rules(&prev_grammar), 134 | ) 135 | .unwrap(); 136 | 137 | cur_rule_name = next_rule_name; 138 | 139 | prev_grammar = grammar.clone(); 140 | } 141 | 142 | writedoc!( 143 | result, 144 | " 145 | {}: [ 146 | {} ],", 147 | cur_rule_name, 148 | ace_rules(&prev_grammar), 149 | ) 150 | .unwrap(); 151 | }); 152 | 153 | result 154 | } 155 | -------------------------------------------------------------------------------- /src/macros/mod.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | pub mod flimsy_syntax; 4 | pub mod macros; 5 | pub mod reification_macros; 6 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Unseemly is a "core" typed language with (typed!) macros. 2 | // You shouldn't write code in Unseemly. 3 | // Instead, you should implement your programming language as Unseemly macros. 4 | 5 | #![allow(dead_code, unused_macros, non_snake_case, non_upper_case_globals, deprecated)] 6 | // dead_code and unused_macros are hopefully temporary allowances 7 | // non_snake_case is stylistic, so we can write `non__snake_case`. 8 | // non_upper_case_globals is stylistic ... but maybe thread_locals really ought to be upper case. 9 | // deprecated is temporary, until `Sky` replaces `EnvMBE` (and the deprecated calls are cleaned up) 10 | #![recursion_limit = "128"] // Yikes. 11 | 12 | // for testing; requires `cargo +nightly` 13 | // #![feature(log_syntax, trace_macros)] 14 | // trace_macros!(true); 15 | 16 | // TODO: turn these into `use` statements in the appropriate places 17 | #[macro_use] 18 | extern crate custom_derive; 19 | 20 | pub mod macros; 21 | 22 | pub mod name; // should maybe be moved to `util`; `mbe` needs it 23 | 24 | pub mod util; 25 | 26 | pub mod alpha; 27 | pub mod ast; 28 | pub mod beta; 29 | pub mod read; 30 | 31 | pub mod earley; 32 | pub mod grammar; 33 | pub mod unparse; 34 | 35 | pub mod form; 36 | 37 | pub mod ast_walk; 38 | pub mod expand; 39 | pub mod subtype; 40 | pub mod ty; 41 | pub mod ty_compare; 42 | pub mod walk_mode; 43 | 44 | pub mod runtime; 45 | 46 | pub mod core_extra_forms; 47 | pub mod core_forms; 48 | pub mod core_macro_forms; 49 | pub mod core_qq_forms; 50 | pub mod core_type_forms; 51 | 52 | pub mod highlighter_generation; 53 | 54 | mod end_to_end__tests; 55 | 56 | use crate::{ 57 | ast::Ast, 58 | name::Name, 59 | runtime::eval::{eval, Value}, 60 | util::assoc::Assoc, 61 | }; 62 | 63 | use wasm_bindgen::prelude::*; 64 | 65 | /// Everything you need to turn text into behavior. 66 | #[derive(Clone)] 67 | pub struct Language { 68 | pub pc: crate::earley::ParseContext, 69 | // TODO: how do these differ from the corresponding elements of `ParseContext`? 70 | // Should we get rid of `Language` in favor of it??? 71 | pub type_env: Assoc, 72 | pub type_env__phaseless: Assoc, 73 | pub value_env: Assoc, 74 | } 75 | 76 | /// Generate Unseemly. 77 | /// (This is the core language.) 78 | pub fn unseemly() -> Language { 79 | Language { 80 | pc: crate::core_forms::outermost__parse_context(), 81 | type_env: crate::runtime::core_values::core_types(), 82 | type_env__phaseless: crate::runtime::core_values::core_types(), 83 | value_env: crate::runtime::core_values::core_values(), 84 | } 85 | } 86 | 87 | /// Run the file (which hopefully evaluates to `capture_language`), and get the language it defines. 88 | /// Returns the parse context, the type environment, the phaseless version of the type environment, 89 | /// and the value environment. 90 | /// This doesn't take a language 4-tuple -- it assumes that the language is in Unseemly 91 | /// (but of course it may do `include /[some_language.unseemly]/` itself). 92 | /// TODO: we only need the phaselessness for macros, and maybe we can get rid of it there? 93 | pub fn language_from_file(path: &std::path::Path) -> Language { 94 | let mut raw_lib = String::new(); 95 | 96 | use std::io::Read; 97 | let orig_dir = std::env::current_dir().unwrap(); 98 | std::fs::File::open(path) 99 | .expect("Error opening file") 100 | .read_to_string(&mut raw_lib) 101 | .expect("Error reading file"); 102 | // Evaluate the file in its own directory: 103 | if let Some(dir) = path.parent() { 104 | // Might be empty: 105 | if dir.is_dir() { 106 | std::env::set_current_dir(dir).unwrap(); 107 | } 108 | } 109 | 110 | let lang = get_language(&raw_lib, unseemly()); 111 | 112 | // Go back to the original directory: 113 | std::env::set_current_dir(orig_dir).unwrap(); 114 | 115 | return lang; 116 | } 117 | 118 | pub fn get_language(program: &str, lang: Language) -> Language { 119 | // TODO: I guess syntax extensions ought to return `Result`, too... 120 | let lib_ast = crate::grammar::parse(&core_forms::outermost_form(), lang.pc, &program).unwrap(); 121 | let lib_typed = ast_walk::walk::( 122 | &lib_ast, 123 | &ast_walk::LazyWalkReses::new(lang.type_env, lang.type_env__phaseless, lib_ast.clone()), 124 | ) 125 | .unwrap(); 126 | let lib_expanded = crate::expand::expand(&lib_ast).unwrap(); 127 | let lib_evaled = crate::runtime::eval::eval(&lib_expanded, lang.value_env).unwrap(); 128 | let (new_pc, new__value_env) = if let Value::Sequence(mut lang_and_env) = lib_evaled { 129 | let env_value = lang_and_env.pop().unwrap(); 130 | let lang_value = lang_and_env.pop().unwrap(); 131 | let new_pc = match &*lang_value { 132 | Value::ParseContext(boxed_pc) => (**boxed_pc).clone(), 133 | _ => icp!("[type error] not a language"), 134 | }; 135 | let new__value_env = if let Value::Struct(ref env) = *env_value { 136 | let mut new__value_env = Assoc::new(); 137 | // We need to un-freshen the names that we're importing 138 | // so they can actually be referred to. 139 | for (k, v) in env.iter_pairs() { 140 | new__value_env = new__value_env.set(k.unhygienic_orig(), v.clone()) 141 | } 142 | new__value_env 143 | } else { 144 | icp!("[type error] Unexpected lib syntax structure: {:#?}", env_value) 145 | }; 146 | (new_pc, new__value_env) 147 | } else { 148 | icp!("[type error] Unexpected lib syntax strucutre: {:#?}", lib_evaled); 149 | }; 150 | 151 | node_let!(lib_typed => {Type tuple} 152 | lang_and_types *= component); 153 | node_let!(lang_and_types[1] => {Type struct} 154 | keys *= component_name, values *= component); 155 | 156 | let mut new__type_env = Assoc::::new(); 157 | for (k, v) in keys.into_iter().zip(values.into_iter()) { 158 | // As above, unfreshen: 159 | new__type_env = new__type_env.set(k.to_name().unhygienic_orig(), v.clone()); 160 | } 161 | 162 | // Do it again, to unpack the phaseless type environment: 163 | node_let!(lang_and_types[2] => {Type struct} 164 | pl_keys *= component_name, pl_values *= component); 165 | 166 | let mut new___type_env__phaseless = Assoc::::new(); 167 | for (k, v) in pl_keys.into_iter().zip(pl_values.into_iter()) { 168 | // As above, unfreshen: 169 | new___type_env__phaseless = 170 | new___type_env__phaseless.set(k.to_name().unhygienic_orig(), v.clone()); 171 | } 172 | 173 | Language { 174 | pc: new_pc, 175 | type_env: new__type_env, 176 | type_env__phaseless: new___type_env__phaseless, 177 | value_env: new__value_env, 178 | } 179 | } 180 | 181 | /// Evaluate a program written in some language. 182 | pub fn eval_program(program: &str, lang: Language) -> Result { 183 | // TODO: looks like `outermost_form` ought to be a property of `ParseContext` 184 | let ast: Ast = crate::grammar::parse(&core_forms::outermost_form(), lang.pc, program) 185 | .map_err(|e| e.msg)?; 186 | 187 | let _type = ast_walk::walk::( 188 | &ast, 189 | &ast_walk::LazyWalkReses::new(lang.type_env, lang.type_env__phaseless, ast.clone()), 190 | ) 191 | .map_err(|e| format!("{}", e))?; 192 | 193 | let core_ast = crate::expand::expand(&ast).map_err(|_| "???".to_string())?; 194 | 195 | eval(&core_ast, lang.value_env).map_err(|_| "???".to_string()) 196 | } 197 | 198 | /// Evaluate a program written in Unseemly. 199 | /// Of course, it may immediately do `include /[something]/` to switch languages. 200 | pub fn eval_unseemly_program_top(program: &str) -> Result { 201 | eval_program(program, unseemly()) 202 | } 203 | 204 | /// Type program written in Unseemly. 205 | /// Of course, it may immediately do `include /[something]/` to switch languages. 206 | pub fn type_unseemly_program_top(program: &str) -> Result { 207 | let unseemly = unseemly(); 208 | let ast: Ast = crate::grammar::parse(&core_forms::outermost_form(), unseemly.pc, program) 209 | .map_err(|e| e.msg)?; 210 | 211 | ast_walk::walk::( 212 | &ast, 213 | &ast_walk::LazyWalkReses::new(unseemly.type_env, unseemly.type_env__phaseless, ast.clone()), 214 | ) 215 | .map_err(|e| format!("{}", e)) 216 | } 217 | 218 | /// Displays `res` on a color terminal. 219 | pub fn terminal_display(res: Result) { 220 | match res { 221 | Ok(v) => println!("\x1b[1;32m≉\x1b[0m {}", v), 222 | Err(s) => println!("\x1b[1;31m✘\x1b[0m {}", s), 223 | } 224 | } 225 | 226 | fn html_render(res: Result) -> String { 227 | match res { 228 | Ok(v) => format!("{}", v), 229 | // HACK: codespan_reporting uses terminal escapes 230 | Err(s) => format!("
{}
", ansi_to_html::convert_escaped(&s).unwrap()), 231 | } 232 | } 233 | 234 | use std::iter::FromIterator; 235 | 236 | thread_local! { 237 | static language_stash: std::cell::RefCell> 238 | = std::cell::RefCell::new(std::collections::HashMap::from_iter( 239 | vec![("unseemly".to_string(), unseemly())].into_iter())); 240 | } 241 | 242 | #[wasm_bindgen] 243 | pub fn html__eval_program(program: &str, stashed_lang: &str) -> String { 244 | let lang: Language = 245 | language_stash.with(|ls| (*ls.borrow()).get(stashed_lang).unwrap().clone()); 246 | html_render(eval_program(program, lang)) 247 | } 248 | 249 | /// Evaluate `program` in `lang_of_program`, and stash the resulting language in `result_name`. 250 | /// "unseemly" starts out in the stash, so it's possible to start from somewhere. 251 | #[wasm_bindgen] 252 | pub fn stash_lang(result_name: &str, program: &str, lang_of_progam: &str) { 253 | let orig_lang = language_stash.with(|ls| (*ls.borrow()).get(lang_of_progam).unwrap().clone()); 254 | let new_lang = get_language(program, orig_lang); 255 | language_stash.with(|ls| ls.borrow_mut().insert(result_name.to_string(), new_lang)); 256 | } 257 | 258 | #[wasm_bindgen] 259 | pub fn generate__ace_rules(stashed_lang: &str) -> String { 260 | let rules = language_stash.with(|ls| { 261 | highlighter_generation::ace_rules(&(*ls.borrow()).get(stashed_lang).unwrap().pc.grammar) 262 | }); 263 | format!( 264 | "start: [ {} // HACK: comments aren't part of the base language: 265 | {{ token: 'comment', regex: '#[^\\\\n|][^\\\\n]*|#\\\\|.*?\\\\|#' }}]", 266 | rules 267 | ) 268 | } 269 | 270 | #[wasm_bindgen] 271 | pub fn generate__ace_rules__for(program: &str, stashed_lang: &str) -> String { 272 | let lang = language_stash 273 | .with(|ls| (*ls.borrow()).get(stashed_lang).expect("Language not defined").clone()); 274 | 275 | highlighter_generation::dynamic__ace_rules(program, &lang) 276 | } 277 | -------------------------------------------------------------------------------- /src/name.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | use std::{ 4 | cell::RefCell, 5 | collections::{HashMap, HashSet}, 6 | fmt, 7 | string::String, 8 | }; 9 | 10 | /// An interned, freshenable identifier. 11 | /// Generally, one creates names with `n()` (short for `Name::global()`); 12 | /// two names created this way with the same spelling will be treated as the same name. 13 | /// Hygiene comes from freshening (implemented in `alpha.rs`, invoked in `walk_mode.rs`). 14 | /// If a name is created in an unusual way that might cause it to collide, 15 | /// `Name::gensym()` ensures uniqueness. 16 | /// Only names that were copied or clone from the original will compare equal. 17 | #[derive(PartialEq, Eq, Clone, Copy, Hash)] 18 | pub struct Name { 19 | id: usize, 20 | } 21 | 22 | pub struct Spelling { 23 | // No two different variables have this the same. Tomatoes may have been added: 24 | unique: String, 25 | // The original spelling that the programmer chose. 26 | orig: String, 27 | } 28 | 29 | thread_local! { 30 | // From `Spelling.unique` to `id`s: 31 | static id_map: RefCell> = RefCell::new(HashMap::new()); 32 | // From `id`s to `Spelling`s 33 | static spellings: RefCell> = RefCell::new(vec![]); 34 | 35 | static printables: RefCell> = RefCell::new(HashMap::new()); 36 | // The values of `printables`, for lookup purposes. 37 | static printables_used: RefCell> = RefCell::new(HashSet::new()); 38 | 39 | // Should we do "naive" freshening for testing purposes? 40 | static fake_freshness: RefCell = RefCell::new(false); 41 | } 42 | 43 | impl crate::runtime::reify::Reifiable for Name { 44 | fn ty_name() -> Name { n("Name") } 45 | 46 | fn reify(&self) -> crate::runtime::eval::Value { val!(ast(at * self)) } 47 | 48 | fn reflect(v: &crate::runtime::eval::Value) -> Name { 49 | extract!((v) crate::runtime::eval::Value::AbstractSyntax = (ref ast) 50 | => ast.to_name()) 51 | } 52 | } 53 | 54 | impl std::cmp::PartialOrd for Name { 55 | fn partial_cmp(&self, other: &Name) -> Option { 56 | Some(self.orig_sp().cmp(&other.orig_sp())) 57 | } 58 | } 59 | 60 | impl std::cmp::Ord for Name { 61 | fn cmp(&self, other: &Name) -> std::cmp::Ordering { self.orig_sp().cmp(&other.orig_sp()) } 62 | } 63 | 64 | // These are for isolating tests of alpha-equivalence from each other. 65 | 66 | pub fn enable_fake_freshness(ff: bool) { 67 | fake_freshness.with(|fake_freshness_| { 68 | *fake_freshness_.borrow_mut() = ff; 69 | }) 70 | } 71 | 72 | // only available on nightly: 73 | // impl !Send for Name {} 74 | 75 | impl Name { 76 | /// Two names that are unequal to each other will have different "spelling"s. 77 | /// Tomatoes (🍅) may have been added to the end to ensure uniqueness. 78 | pub fn sp(self) -> String { spellings.with(|us| us.borrow()[self.id].unique.clone()) } 79 | /// The "original spelling" of a name; the string that was used to define it. These may collide. 80 | pub fn orig_sp(self) -> String { spellings.with(|us| us.borrow()[self.id].orig.clone()) } 81 | 82 | /// This extracts the "original" `Name`, prior to any freshening. 83 | /// This is probably not ever the *right* thing to do, but may be needed as a workaround. 84 | pub fn unhygienic_orig(self) -> Name { 85 | spellings.with(|us| Name::new(&us.borrow()[self.id].orig, false)) 86 | } 87 | 88 | /// Printable names are unique, like names from `sp()`, but generated lazily. 89 | /// So, if the compiler freshens some name a bunch of times, producing a tomato-filled mess, 90 | /// but only prints one version of the name, it gets to print an unadorned name. 91 | /// If absolutely necessary to avoid collision, carrots (🥕) are added to the end. 92 | pub fn print(self) -> String { 93 | printables.with(|printables_| { 94 | printables_used.with(|printables_used_| { 95 | printables_ 96 | .borrow_mut() 97 | .entry(self.id) 98 | .or_insert_with(|| { 99 | let mut print_version = self.orig_sp(); 100 | while printables_used_.borrow().contains(&print_version) { 101 | // Graffiti seen at Berkley: "EⒶT YOUR VEGETABLES 🥕" 102 | print_version = format!("{}🥕", print_version); 103 | } 104 | printables_used_.borrow_mut().insert(print_version.clone()); 105 | print_version.clone() 106 | }) 107 | .clone() 108 | }) 109 | }) 110 | } 111 | 112 | pub fn global(s: &str) -> Name { Name::new(s, false) } 113 | pub fn gensym(s: &str) -> Name { Name::new(s, true) } 114 | pub fn freshen(self) -> Name { Name::new(&self.orig_sp(), true) } 115 | 116 | fn new(orig_spelling: &str, freshen: bool) -> Name { 117 | let fake_freshness_ = fake_freshness.with(|ff| *ff.borrow()); 118 | 119 | id_map.with(|id_map_| { 120 | let mut unique_spelling = orig_spelling.to_owned(); 121 | // Find a fresh version by adding tomatoes, if requested: 122 | while freshen && id_map_.borrow().contains_key(&unique_spelling) { 123 | unique_spelling = format!("{}🍅", unique_spelling); 124 | } 125 | 126 | if freshen && fake_freshness_ { 127 | // Forget doing it right; only add exactly one tomato: 128 | unique_spelling = format!("{}🍅", orig_spelling); 129 | } 130 | 131 | let claim_id = || { 132 | spellings.with(|spellings_| { 133 | let new_id = spellings_.borrow().len(); 134 | spellings_.borrow_mut().push(Spelling { 135 | unique: unique_spelling.clone(), 136 | orig: orig_spelling.to_owned(), 137 | }); 138 | new_id 139 | }) 140 | }; 141 | 142 | // Claim our `unique_spelling` and a fresh ID: 143 | let id = *id_map_.borrow_mut().entry(unique_spelling.clone()).or_insert_with(claim_id); 144 | 145 | Name { id: id } 146 | }) 147 | } 148 | pub fn is(self, s: &str) -> bool { self.sp() == s } 149 | 150 | pub fn is_name(self, n: Name) -> bool { self.sp() == n.sp() } 151 | } 152 | 153 | impl From<&str> for Name { 154 | fn from(s: &str) -> Name { Name::global(s) } 155 | } 156 | 157 | impl From<&String> for Name { 158 | fn from(s: &String) -> Name { Name::global(&*s) } 159 | } 160 | 161 | // TODO: move to `ast_walk` 162 | // TODO: using `lazy_static!` (with or without gensym) makes some tests fail. Why? 163 | /// Special name for negative `ast_walk`ing 164 | pub fn negative_ret_val() -> Name { Name::global("⋄") } 165 | 166 | impl fmt::Debug for Name { 167 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "«{}»", self.sp()) } 168 | } 169 | 170 | impl fmt::Display for Name { 171 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.print()) } 172 | } 173 | 174 | pub fn n(s: &str) -> Name { Name::global(s) } 175 | 176 | #[test] 177 | fn name_interning() { 178 | // This test fails under tarpaulin; why? It must be related to `thread_local!` somehow... 179 | let a = n("a"); 180 | assert_eq!(a, a); 181 | assert_eq!(a, n("a")); 182 | assert_ne!(a, a.freshen()); 183 | assert_eq!(a, a.freshen().unhygienic_orig()); 184 | 185 | assert_ne!(a, n("x🍅")); 186 | assert_ne!(a.freshen(), a.freshen()); 187 | assert_ne!(a.freshen().sp(), a.freshen().sp()); 188 | 189 | assert_ne!(n("a"), n("y")); 190 | 191 | enable_fake_freshness(true); 192 | 193 | let x = n("x"); 194 | assert_eq!(x, x); 195 | assert_eq!(x, n("x")); 196 | assert_ne!(x, x.freshen()); 197 | 198 | // ... but now we the freshened version of `x` is accessible (and doesn't avoid existing names) 199 | assert_eq!(x.freshen(), n("x🍅")); 200 | assert_eq!(x.freshen(), x.freshen()); 201 | 202 | // Printable versions are first-come, first-served 203 | assert_eq!(a.freshen().print(), "a"); 204 | assert_eq!(a.print(), "a🥕"); 205 | } 206 | -------------------------------------------------------------------------------- /src/read.rs: -------------------------------------------------------------------------------- 1 | // TODO: This file should be absorbed into `grammar.rs`. 2 | 3 | custom_derive! { 4 | #[derive(Debug,PartialEq,Eq,Clone,Copy,Reifiable)] 5 | pub enum DelimChar { Paren, SquareBracket, CurlyBracket } 6 | } 7 | 8 | impl DelimChar { 9 | pub fn open(self) -> char { 10 | match self { 11 | Paren => '(', 12 | SquareBracket => '[', 13 | CurlyBracket => '{', 14 | } 15 | } 16 | pub fn close(self) -> char { 17 | match self { 18 | Paren => ')', 19 | SquareBracket => ']', 20 | CurlyBracket => '}', 21 | } 22 | } 23 | } 24 | 25 | use self::DelimChar::*; 26 | 27 | pub fn delim(s: &str) -> DelimChar { 28 | match s { 29 | "(" | ")" => Paren, 30 | "[" | "]" => SquareBracket, 31 | "{" | "}" => CurlyBracket, 32 | _ => icp!("not a delimiter!"), 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/runtime/eval.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | use crate::{ 4 | ast::Ast, 5 | ast_walk::{walk, LazyWalkReses, WalkRule}, 6 | form::Form, 7 | name::*, 8 | util::assoc::Assoc, 9 | walk_mode::{NegativeWalkMode, WalkMode}, 10 | }; 11 | use num::bigint::BigInt; 12 | use std::{self, rc::Rc}; 13 | 14 | /// Values in Unseemly. 15 | 16 | #[derive(Debug, Clone, PartialEq)] 17 | pub enum Value { 18 | Int(BigInt), 19 | Sequence(Vec>), // TODO: switch to a different core sequence type 20 | Function(Rc), // TODO: unsure if this Rc is needed 21 | BuiltInFunction(BIF), 22 | AbstractSyntax(Ast), 23 | Struct(Assoc), 24 | Enum(Name, Vec), 25 | // Hypothesis: all strings are either 26 | // in a formal language (and should be stored as ASTs instead) 27 | // or in a human language (and should be tokens for localization). 28 | // But for now, here's a plain string. 29 | Text(String), 30 | Cell(Rc>), 31 | // Reifying `Form`s causes loss of identity, so have an explicit (opaque) representation. 32 | // Perhaps drop reification entirely, and just use an opaque type based on `std::any::Any`? 33 | ParseContext(Box), 34 | } 35 | 36 | pub use self::Value::*; 37 | 38 | #[derive(Debug, Clone, PartialEq)] 39 | pub struct Closure { 40 | pub body: Ast, 41 | pub params: Vec, 42 | pub env: Assoc, 43 | } 44 | 45 | impl Value { 46 | /// Turns this `Value` into a "magic" `Ast` that evaluates to it. 47 | /// The `Ast` will have the universal type 48 | pub fn prefab(self) -> Ast { 49 | raw_ast!(Node( 50 | typed_form!( 51 | "prefab_internal", 52 | (impossible), // no syntax 53 | // TODO: Do we even need to be well-typed? 54 | cust_rc_box!(move |_| Ok(ast!( 55 | // Cheat: has the universal type, but we know it's safe because . 56 | {"Type" "forall_type" : 57 | "param" => ["T"], 58 | "body" => (import [* [forall "param"]] (vr "T"))}))), 59 | cust_rc_box!(move |_| Ok(self.clone())) 60 | ), 61 | crate::util::mbe::EnvMBE::new(), 62 | crate::beta::ExportBeta::Nothing 63 | )) 64 | } 65 | } 66 | 67 | // Built-in function 68 | pub struct BIF(pub Rc<(dyn Fn(Vec) -> Value)>); 69 | 70 | pub fn apply__function_value(f: &Value, args: Vec) -> Value { 71 | match *f { 72 | BuiltInFunction(BIF(ref f)) => f(args.into_iter().collect()), 73 | Function(ref cl) => { 74 | let mut clo_env = cl.env.clone(); 75 | if cl.params.len() != args.len() { 76 | panic!( 77 | "[type error] Attempted to apply {} arguments to function requiring {} \ 78 | parameters", 79 | args.len(), 80 | cl.params.len() 81 | ); 82 | } 83 | for (p, a) in cl.params.iter().zip(args.into_iter()) { 84 | clo_env = clo_env.set(*p, a) 85 | } 86 | eval(&cl.body, clo_env).unwrap() 87 | } 88 | _ => panic!("[type error] {:#?} is not a function", f), 89 | } 90 | } 91 | 92 | impl PartialEq for BIF { 93 | fn eq(&self, other: &BIF) -> bool { self as *const BIF == other as *const BIF } 94 | } 95 | 96 | impl Clone for BIF { 97 | fn clone(&self) -> BIF { BIF(self.0.clone()) } 98 | } 99 | 100 | impl std::fmt::Display for Value { 101 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 102 | match *self { 103 | Int(ref bi) => write!(f, "{}", bi), 104 | Sequence(ref seq) => { 105 | for elt in seq { 106 | write!(f, "{}", &*elt)?; 107 | } 108 | Ok(()) 109 | } 110 | Function(_) => write!(f, "[closure]"), 111 | BuiltInFunction(_) => write!(f, "[built-in function]"), 112 | AbstractSyntax(ref ast) => write!(f, "'[{}]'", ast), 113 | Struct(ref parts) => { 114 | write!(f, "*[")?; 115 | for (k, v) in parts.iter_pairs() { 116 | write!(f, "{}: {} ", k, v)?; 117 | } 118 | write!(f, "]*") 119 | } 120 | Enum(n, ref parts) => { 121 | write!(f, "+[{}", n)?; 122 | for p in parts.iter() { 123 | write!(f, " {}", p)?; 124 | } 125 | write!(f, "]+") 126 | } 127 | Text(ref st) => write!(f, "{}", st), 128 | Cell(ref cell) => write!(f, "{}", cell.borrow()), 129 | ParseContext(_) => write!(f, "[a language]"), 130 | } 131 | } 132 | } 133 | 134 | impl std::fmt::Debug for BIF { 135 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 136 | formatter.write_str("[built-in function]") 137 | } 138 | } 139 | 140 | impl crate::walk_mode::WalkElt for Value { 141 | fn from_ast(a: &Ast) -> Value { AbstractSyntax(a.clone()) } 142 | fn to_ast(&self) -> Ast { 143 | match *self { 144 | AbstractSyntax(ref a) => a.clone(), 145 | _ => icp!("[type error] {} is not syntax", self), 146 | } 147 | } 148 | } 149 | 150 | custom_derive! { 151 | #[derive(Copy, Clone, Debug, Reifiable)] 152 | pub struct Eval {} 153 | } 154 | custom_derive! { 155 | #[derive(Copy, Clone, Debug, Reifiable)] 156 | pub struct Destructure {} 157 | } 158 | 159 | impl WalkMode for Eval { 160 | fn name() -> &'static str { "Evalu" } 161 | 162 | type Elt = Value; 163 | type Negated = Destructure; 164 | type AsPositive = Eval; 165 | type AsNegative = Destructure; 166 | type Err = (); 167 | type D = crate::walk_mode::Positive; 168 | type ExtraInfo = (); 169 | 170 | fn get_walk_rule(f: &Form) -> WalkRule { 171 | // Macro invocations use `eval`, to avoid having a whole extra field in `Form`: 172 | if f.name == n("macro_invocation") { 173 | icp!("unexpanded macro!") 174 | } 175 | f.eval.pos().clone() 176 | } 177 | fn automatically_extend_env() -> bool { true } 178 | 179 | fn walk_var(n: Name, cnc: &LazyWalkReses) -> Result { 180 | match cnc.env.find(&n) { 181 | Some(v) => Ok(v.clone()), 182 | None => panic!("Undefined var `{}` in {}", n, cnc.env), 183 | } 184 | } 185 | 186 | // TODO: maybe keep this from being called? 187 | fn underspecified(_: Name) -> Value { val!(enum "why is this here?", ) } 188 | } 189 | 190 | impl WalkMode for Destructure { 191 | fn name() -> &'static str { "Destr" } 192 | 193 | type Elt = Value; 194 | type Negated = Eval; 195 | type AsPositive = Eval; 196 | type AsNegative = Destructure; 197 | type Err = (); 198 | type D = crate::walk_mode::Negative; 199 | type ExtraInfo = (); 200 | 201 | /// The whole point of program evaluation is that the enviornment 202 | /// isn't generateable from the source tree. 203 | /// Does that make sense? I suspect it does not. 204 | fn get_walk_rule(f: &Form) -> WalkRule { f.eval.neg().clone() } 205 | fn automatically_extend_env() -> bool { true } // TODO: think about this 206 | } 207 | 208 | impl NegativeWalkMode for Destructure { 209 | fn needs_pre_match() -> bool { false } // Values don't have binding (in this mode!) 210 | } 211 | 212 | impl crate::walk_mode::WalkElt for Ast { 213 | fn from_ast(a: &Ast) -> Ast { a.clone() } 214 | fn to_ast(&self) -> Ast { self.clone() } 215 | } 216 | 217 | pub fn eval_top(expr: &Ast) -> Result { eval(expr, Assoc::new()) } 218 | 219 | pub fn eval(expr: &Ast, env: Assoc) -> Result { 220 | walk::(expr, &LazyWalkReses::new_wrapper(env)) 221 | } 222 | 223 | pub fn neg_eval(pat: &Ast, env: Assoc) -> Result, ()> { 224 | walk::(pat, &LazyWalkReses::new_wrapper(env)) 225 | } 226 | 227 | custom_derive! { 228 | #[derive(Copy, Clone, Debug, Reifiable)] 229 | pub struct QQuote {} 230 | } 231 | custom_derive! { 232 | #[derive(Copy, Clone, Debug, Reifiable)] 233 | pub struct QQuoteDestr {} 234 | } 235 | 236 | impl WalkMode for QQuote { 237 | fn name() -> &'static str { "QQuote" } 238 | 239 | // Why not `Ast`? Because QQuote and Eval need to share environments. 240 | type Elt = Value; 241 | type Negated = QQuoteDestr; 242 | type AsPositive = QQuote; 243 | type AsNegative = QQuoteDestr; 244 | type Err = (); 245 | type D = crate::walk_mode::Positive; 246 | type ExtraInfo = (); 247 | 248 | fn walk_var(n: Name, _: &LazyWalkReses) -> Result { Ok(val!(ast (vr n))) } 249 | fn walk_atom(n: Name, _: &LazyWalkReses) -> Result { Ok(val!(ast (at n))) } 250 | // TODO #26: Just special-case "unquote" and "dotdotdot" 251 | fn get_walk_rule(f: &Form) -> WalkRule { f.quasiquote.pos().clone() } 252 | fn automatically_extend_env() -> bool { false } 253 | } 254 | 255 | impl WalkMode for QQuoteDestr { 256 | fn name() -> &'static str { "QQDes" } 257 | 258 | type Elt = Value; 259 | type Negated = QQuote; 260 | type AsPositive = QQuote; 261 | type AsNegative = QQuoteDestr; 262 | type Err = (); 263 | type D = crate::walk_mode::Negative; 264 | type ExtraInfo = (); 265 | 266 | fn walk_var(n: Name, cnc: &LazyWalkReses) -> Result, ()> { 267 | let val = val!(ast (vr n)); 268 | if cnc.context_elt() == &val { 269 | Ok(Assoc::::new()) 270 | } else { 271 | Err(Self::qlit_mismatch_error(val, cnc.context_elt().clone())) 272 | } 273 | } 274 | fn walk_atom(n: Name, cnc: &LazyWalkReses) -> Result, ()> { 275 | let val = val!(ast (at n)); 276 | if cnc.context_elt() == &val { 277 | Ok(Assoc::::new()) 278 | } else { 279 | Err(Self::qlit_mismatch_error(val, cnc.context_elt().clone())) 280 | } 281 | } 282 | // TODO #26: Just special-case "unquote" 283 | fn get_walk_rule(f: &Form) -> WalkRule { f.quasiquote.neg().clone() } 284 | fn automatically_extend_env() -> bool { false } 285 | } 286 | 287 | impl NegativeWalkMode for QQuoteDestr { 288 | fn needs_pre_match() -> bool { true } // Quoted syntax does have binding! 289 | } 290 | 291 | // `env` is a trap! We want a shifted `LazyWalkReses`! 292 | // pub fn qquote(expr: &Ast, env: Assoc) -> Result { 293 | // walk::(expr, &LazyWalkReses::new_wrapper(env)) 294 | // } 295 | // 296 | // pub fn qquote_destr(pat: &Ast, env: Assoc) 297 | // -> Result,()> { 298 | // walk::(pat, &LazyWalkReses::new_wrapper(env)) 299 | // } 300 | -------------------------------------------------------------------------------- /src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod core_values; 2 | pub mod eval; 3 | pub mod reify; 4 | -------------------------------------------------------------------------------- /src/subtype.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::{ 3 | Ast, 4 | AstContents::{Atom, Node, Shape, VariableReference}, 5 | }, 6 | ast_walk::{walk, LazyWalkReses, WalkRule}, 7 | form::Form, 8 | name::{n, Name}, 9 | ty::TypeError, 10 | util::assoc::Assoc, 11 | walk_mode::{NegativeWalkMode, WalkMode}, 12 | }; 13 | 14 | custom_derive! { 15 | #[derive(Copy, Clone, Debug, Reifiable)] 16 | pub struct Subtype {} 17 | } 18 | 19 | // TODO: gensym and store 20 | fn mystery_id() -> Name { n("mystery_for_typechecking") } 21 | 22 | fn new_mystery(supers: Vec, subs: Vec) -> Ast { 23 | raw_ast!(Shape(vec![ 24 | raw_ast!(Atom(mystery_id())), 25 | raw_ast!(Shape(supers)), 26 | raw_ast!(Shape(subs)) 27 | ])) 28 | } 29 | 30 | fn is_mystery(a: &Ast) -> bool { 31 | let Shape(shape_parts) = a.c() else { return false; }; 32 | return !shape_parts.is_empty() && shape_parts[0].c() == &Atom(mystery_id()); 33 | } 34 | 35 | fn unpack_mystery(mystery: &Ast) -> (Vec, Vec) { 36 | let Shape(mystery_parts) = mystery.c() else { icp!() }; 37 | let Shape(subs) = mystery_parts[2].c().clone() else {icp!() }; 38 | let Shape(supers) = mystery_parts[1].c().clone() else {icp!() }; 39 | 40 | (supers, subs) 41 | } 42 | 43 | fn complete_mystery() -> Ast { new_mystery(vec![], vec![]) } 44 | 45 | fn constrain_mystery(mystery: &Ast, constraint: Ast, super_type: bool) -> Ast { 46 | let (mut supers, mut subs) = unpack_mystery(mystery); 47 | 48 | let constraints = if super_type { &mut supers } else { &mut subs }; 49 | 50 | if !constraints.contains(&constraint) { 51 | constraints.push(constraint); 52 | } 53 | 54 | new_mystery(supers, subs) 55 | } 56 | 57 | // If `ty` is a mystery, return it; otherwise, make it a fully-constrained mystery 58 | fn ensure_mystery(ty: Ast) -> Ast { 59 | if is_mystery(&ty) { 60 | return ty; 61 | } 62 | new_mystery(vec![ty.clone()], vec![ty]) 63 | } 64 | 65 | fn merge_mysteries(mystery_lhs: &Ast, mystery_rhs: &Ast) -> Ast { 66 | let mut lhs = unpack_mystery(mystery_lhs); 67 | let rhs = unpack_mystery(mystery_rhs); 68 | 69 | for constraint in rhs.0 { 70 | if !lhs.0.contains(&constraint) { 71 | lhs.0.push(constraint); 72 | } 73 | } 74 | 75 | for constraint in rhs.1 { 76 | if !lhs.1.contains(&constraint) { 77 | lhs.1.push(constraint); 78 | } 79 | } 80 | 81 | new_mystery(lhs.0, lhs.1) 82 | } 83 | 84 | fn mystery_satisfiable(mystery: &Ast, parts: &LazyWalkReses) -> Result<(), TypeError> { 85 | let (supers, subs) = unpack_mystery(mystery); 86 | 87 | // Pick a maximally-constrained constraint on one side; 88 | // does it satisfy all the constraints on the other? 89 | // TODO: Does this always find a satisfaction if there is one? 90 | for super_constraint in &supers { 91 | if !supers 92 | .iter() 93 | .all(|other_super| must_subtype(super_constraint, other_super, parts).is_ok()) 94 | { 95 | continue; 96 | } 97 | // all other super constraints are a supertype to this 98 | 99 | for sub_constriant in &subs { 100 | must_subtype(sub_constriant, super_constraint, parts)? 101 | } 102 | } 103 | Ok(()) 104 | } 105 | 106 | // TODO: we should really have some sort of general mechanism... 107 | // `expect_ty_node!` isn't quite right; we just want to panic if it fails 108 | fn destr_forall(a: &Ast) -> Option<(Vec, &Ast)> { 109 | if let Node(f, parts, _) = a.c() { 110 | if f.name != n("forall_type") { 111 | return None; 112 | } 113 | return Some(( 114 | parts.get_rep_leaf_or_panic(n("param")).into_iter().map(Ast::to_name).collect(), 115 | parts.get_leaf_or_panic(&n("body")), 116 | )); 117 | } else { 118 | return None; 119 | } 120 | } 121 | 122 | fn merge_bindings(lhs: Ast, rhs: Ast) -> Ast { 123 | // As an optimization, if the types are spelled the same, we know they're equivalent: 124 | if lhs == rhs { 125 | return lhs; 126 | } 127 | 128 | merge_mysteries(&ensure_mystery(lhs), &ensure_mystery(rhs)) 129 | } 130 | 131 | impl WalkMode for Subtype { 132 | fn name() -> &'static str { "Subtype" } 133 | 134 | type Elt = Ast; 135 | type Negated = UnusedPositiveSubtype; 136 | type AsPositive = UnusedPositiveSubtype; 137 | type AsNegative = Subtype; 138 | type Err = TypeError; 139 | type D = crate::walk_mode::Negative; 140 | type ExtraInfo = (); 141 | 142 | fn get_walk_rule(_f: &Form) -> WalkRule { 143 | cust_rc_box!(|part_types: LazyWalkReses| { 144 | match (destr_forall(&part_types.this_ast), destr_forall(part_types.context_elt())) { 145 | (None, None) => { 146 | panic!("TODO") 147 | // TODO: rename to .subtype 148 | // Ok(f.type_compare.neg().clone()) 149 | } 150 | _ => { 151 | panic!("TODO") 152 | } 153 | } 154 | }) 155 | } 156 | fn automatically_extend_env() -> bool { true } 157 | 158 | fn walk_var(n: Name, cnc: &LazyWalkReses) -> Result, TypeError> { 159 | // TODO: actually constrain unknowns, and ignore non-unknowns 160 | match cnc.env.find(&n) { 161 | // If it's protected, stop: 162 | Some(t) if &VariableReference(n) == t.c() => Ok(Assoc::new()), 163 | Some(t) => Ok(Assoc::single(n, crate::ty::synth_type(t, cnc.env.clone())?)), 164 | // Or canonicalize(t, cnc.env.clone()), ? 165 | None => ty_err!(UnboundName(n) at cnc.this_ast), 166 | } 167 | } 168 | 169 | // Simply protect the name; don't try to unify it. 170 | fn underspecified(nm: Name) -> Ast { ast!((vr nm)) } 171 | 172 | fn neg__env_merge( 173 | lhs: &Assoc, 174 | rhs: &Assoc, 175 | ) -> Result, TypeError> { 176 | // combine constraints 177 | Ok(lhs.union_with(rhs, merge_bindings)) 178 | // TODO: handle types with mysteries embedded in them 179 | // Perhaps we can just recur into them at the end? 180 | } 181 | } 182 | 183 | impl NegativeWalkMode for Subtype { 184 | fn needs_pre_match() -> bool { false } // we hack `get_walk_rule` for a similar purpose 185 | } 186 | 187 | custom_derive! { 188 | #[derive(Copy, Clone, Debug, Reifiable)] 189 | pub struct UnusedPositiveSubtype {} 190 | } 191 | 192 | impl WalkMode for UnusedPositiveSubtype { 193 | fn name() -> &'static str { "XXXXX" } 194 | 195 | type Elt = Ast; 196 | type Negated = Subtype; 197 | type AsPositive = UnusedPositiveSubtype; 198 | type AsNegative = Subtype; 199 | type Err = TypeError; 200 | type D = crate::walk_mode::Positive; 201 | type ExtraInfo = (); 202 | 203 | fn get_walk_rule(_: &Form) -> WalkRule { icp!() } 204 | fn automatically_extend_env() -> bool { icp!() } 205 | } 206 | 207 | pub fn must_subtype(sub: &Ast, sup: &Ast, parts: &LazyWalkReses) -> Result<(), TypeError> { 208 | if sub as *const Ast == sup as *const Ast { 209 | return Ok(()); 210 | } 211 | if sub == sup { 212 | return Ok(()); 213 | } 214 | 215 | let result_env = walk::(sup, &parts.with_context(sub.clone()))?; 216 | let result_parts = parts.with_environment(result_env.clone()); 217 | 218 | for mystery in result_env.iter_values() { 219 | mystery_satisfiable(mystery, &result_parts)? 220 | } 221 | return Ok(()); 222 | } 223 | 224 | pub fn must_equal(lhs: &Ast, rhs: &Ast, parts: &LazyWalkReses) -> Result<(), TypeError> { 225 | must_subtype(lhs, rhs, parts)?; 226 | must_subtype(rhs, lhs, parts) 227 | } 228 | -------------------------------------------------------------------------------- /src/ty.rs: -------------------------------------------------------------------------------- 1 | // Type synthesis is a recursive traversal of an abstract syntax tree. 2 | // It is compositional, 3 | // except for binding, which is indicated by ExtendTypeEnv nodes. 4 | // These nodes may depend on 5 | // the result of type-synthesizing sibling AST nodes 6 | // or the actual value of AST nodes corresponding to types 7 | // (i.e., type annotations). 8 | 9 | use crate::{ 10 | ast::*, 11 | ast_walk::{ 12 | walk, LazyWalkReses, 13 | WalkRule::{self}, 14 | }, 15 | form::Form, 16 | name::*, 17 | util::assoc::Assoc, 18 | walk_mode::WalkMode, 19 | }; 20 | use std::{fmt, rc::Rc}; 21 | 22 | impl Ast { 23 | // TODO: use this more 24 | // TODO: make `expd_form` a reference 25 | pub fn ty_destructure( 26 | &self, 27 | expd_form: Rc, 28 | loc: &Ast, 29 | ) -> Result, TypeError> { 30 | self.destructure(expd_form.clone()) 31 | .ok_or(ty_err_val!(UnableToDestructure(self.clone(), expd_form.name) at loc /*TODO*/)) 32 | } 33 | } 34 | 35 | custom_derive! { 36 | #[derive(Copy, Clone, Debug, Reifiable)] 37 | pub struct SynthTy {} 38 | } 39 | custom_derive! { 40 | #[derive(Copy, Clone, Debug, Reifiable)] 41 | pub struct UnpackTy {} 42 | } 43 | 44 | impl WalkMode for SynthTy { 45 | fn name() -> &'static str { "SynTy" } 46 | type Elt = Ast; 47 | type Negated = UnpackTy; 48 | type AsPositive = SynthTy; 49 | type AsNegative = UnpackTy; 50 | type Err = TypeError; 51 | type D = crate::walk_mode::Positive; 52 | type ExtraInfo = (); 53 | 54 | fn get_walk_rule(f: &Form) -> WalkRule { f.synth_type.pos().clone() } 55 | fn automatically_extend_env() -> bool { true } 56 | 57 | fn walk_var( 58 | name: Name, 59 | parts: &crate::ast_walk::LazyWalkReses, 60 | ) -> Result { 61 | match parts.env.find(&name) { 62 | None => Err(crate::util::err::sp(TyErr::UnboundName(name), parts.this_ast.clone())), 63 | // If name is protected, stop: 64 | Some(ty) if &VariableReference(name) == ty.c() => Ok(ty.clone()), 65 | Some(ref ty) => synth_type(ty, parts.env.clone()), 66 | } 67 | } 68 | 69 | // Simply protect the name; don't try to unify it. 70 | fn underspecified(name: Name) -> Ast { ast!((vr name)) } 71 | } 72 | 73 | impl WalkMode for UnpackTy { 74 | fn name() -> &'static str { "UnpTy" } 75 | type Elt = Ast; 76 | type Negated = SynthTy; 77 | type AsPositive = SynthTy; 78 | type AsNegative = UnpackTy; 79 | type Err = TypeError; 80 | type D = crate::walk_mode::Negative; 81 | type ExtraInfo = (); 82 | 83 | fn get_walk_rule(f: &Form) -> WalkRule { f.synth_type.neg().clone() } 84 | fn automatically_extend_env() -> bool { true } 85 | 86 | fn underspecified(name: Name) -> Ast { ast!((vr name)) } 87 | } 88 | 89 | impl crate::walk_mode::NegativeWalkMode for UnpackTy { 90 | fn needs_pre_match() -> bool { true } 91 | } 92 | 93 | pub fn synth_type_top(expr: &Ast) -> TypeResult { 94 | walk::(expr, &LazyWalkReses::new_wrapper(Assoc::new())) 95 | } 96 | 97 | pub fn synth_type(expr: &Ast, env: Assoc) -> TypeResult { 98 | walk::(expr, &LazyWalkReses::new_wrapper(env)) 99 | } 100 | 101 | pub fn neg_synth_type(pat: &Ast, env: Assoc) -> Result, TypeError> { 102 | walk::(pat, &LazyWalkReses::new_wrapper(env)) 103 | } 104 | 105 | // TODO: Rename this. (Maybe `TypeComplaint`?) 106 | custom_derive! { 107 | #[derive(Reifiable, Clone, PartialEq)] 108 | pub enum TyErr { 109 | Mismatch(Ast, Ast), // got, expected 110 | LengthMismatch(Vec, usize), 111 | NtInterpMismatch(Name, Name), 112 | NonexistentEnumArm(Name, Ast), 113 | NonexistentStructField(Name, Ast), 114 | NonExhaustiveMatch(Ast), 115 | UnableToDestructure(Ast, Name), 116 | UnboundName(Name), 117 | // TODO: the reification macros can't handle empty `enum` cases. Fix that! 118 | AnnotationRequired(()), 119 | NeedsDriver(()), 120 | // TODO: replace all uses of `Other` with more specific errors: 121 | Other(String) 122 | } 123 | } 124 | 125 | impl fmt::Display for TyErr { 126 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 127 | use self::TyErr::*; 128 | match *self { 129 | Mismatch(ref got, ref exp) => { 130 | write!(f, "[Mismatch] got:\n `{}`\n expected:\n `{}`\n", got, exp) 131 | } 132 | LengthMismatch(ref got, exp_len) => { 133 | write!(f, "[LengthMismatch] got:\n ")?; 134 | for g in got { 135 | write!(f, "{}, ", g)?; 136 | } 137 | write!(f, "\n expected {} arguments.\n", exp_len) 138 | } 139 | NtInterpMismatch(got, exp) => write!( 140 | f, 141 | "[NtInterpMismatch] expected the nonterminal `{}`, but `{}` was interpolated", 142 | exp, got 143 | ), 144 | NonexistentEnumArm(got_name, ref ty) => write!( 145 | f, 146 | "[NonexistentEnumArm] the enum `{}` doesn't have an arm named `{}`", 147 | ty, got_name 148 | ), 149 | NonexistentStructField(got_name, ref ty) => write!( 150 | f, 151 | "[NonexistentStructField] the struct `{}` doesn't have a field named `{}`", 152 | ty, got_name 153 | ), 154 | NonExhaustiveMatch(ref ty) => { 155 | write!(f, "[NonExhaustiveMatch] non-exhaustive match of `{}`", ty) 156 | } 157 | UnableToDestructure(ref ty, expected_name) => { 158 | write!(f, "[UnableToDestructure] expected a `{}` type, got `{}`", expected_name, ty) 159 | } 160 | UnboundName(name) => write!(f, "[UnboundName] `{}` is not defined", name), 161 | AnnotationRequired(()) => write!( 162 | f, 163 | "[AnnotationRequired] Negative syntax (e.g. a pattern) inside positive syntax \ 164 | (e.g. an expression) requires a type annotation." 165 | ), 166 | NeedsDriver(()) => write!(f, "[NeedsDriver] Repetition needs a driver"), 167 | Other(ref s) => write!(f, "[Other] {}", s), 168 | } 169 | } 170 | } 171 | 172 | // temporary, until we get rid of `Debug` as the way of outputting errors 173 | impl fmt::Debug for TyErr { 174 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } 175 | } 176 | 177 | // TODO: I hope I don't need this 178 | // impl From<()> for TyErr { 179 | // fn from(_: ()) -> TyErr { 180 | // panic!("Tried to discard a type error"); 181 | // } 182 | // } 183 | 184 | pub type TypeError = crate::util::err::Spanned; 185 | 186 | pub type TypeResult = Result; 187 | 188 | pub fn expect_type(expected: &Ast, got: &Ast, loc: &Ast) -> Result<(), TypeError> { 189 | if got != expected { 190 | Err(crate::util::err::Spanned { 191 | loc: loc.clone(), 192 | body: TyErr::Mismatch(expected.clone(), got.clone()), 193 | }) 194 | } else { 195 | Ok(()) 196 | } 197 | } 198 | 199 | #[test] 200 | fn basic_type_synth() { 201 | use crate::ast_walk::WalkRule::*; 202 | 203 | let mt_ty_env = Assoc::new(); 204 | let int_ty = ast!({ 205 | crate::core_forms::find_core_form("Type", "Int"); 206 | }); 207 | let nat_ty = ast!({ 208 | crate::core_forms::find_core_form("Type", "Nat"); 209 | }); 210 | 211 | let simple_ty_env = mt_ty_env.set(n("x"), int_ty.clone()); 212 | 213 | let body = basic_typed_form!(atom, Body(n("body")), NotWalked); 214 | let untypeable = basic_typed_form!(atom, NotWalked, NotWalked); 215 | 216 | assert_eq!(synth_type(&ast!((vr "x")), simple_ty_env.clone()), Ok(int_ty.clone())); 217 | 218 | assert_eq!( 219 | synth_type( 220 | &ast!({body.clone() ; 221 | ["irrelevant" => {untypeable.clone() ; }, 222 | "body" => (vr "x")]}), 223 | simple_ty_env.clone() 224 | ), 225 | Ok(int_ty.clone()) 226 | ); 227 | 228 | assert_eq!( 229 | synth_type( 230 | &ast!({body.clone() ; 231 | "type_of_new_var" => (, int_ty.clone()), 232 | "new_var" => "y", 233 | "body" => (import ["new_var" : "type_of_new_var"] (vr "y"))}), 234 | simple_ty_env.clone() 235 | ), 236 | Ok(int_ty.clone()) 237 | ); 238 | 239 | assert_eq!( 240 | synth_type( 241 | &ast!({ 242 | basic_typed_form!( 243 | atom, 244 | Custom(Rc::new(Box::new(|_| Ok(ast!({ 245 | crate::core_forms::find_core_form("Type", "Nat"); 246 | }))))), 247 | NotWalked 248 | ); 249 | [] 250 | }), 251 | simple_ty_env.clone() 252 | ), 253 | Ok(nat_ty.clone()) 254 | ); 255 | 256 | let chained_ty_env = 257 | assoc_n!("a" => ast!((vr "B")), "B" => ast!((vr "C")), "C" => ast!({"Type" "Int":})); 258 | 259 | assert_eq!(synth_type(&ast!((vr "a")), chained_ty_env), Ok(ast!({"Type" "Int":}))); 260 | } 261 | 262 | #[test] 263 | fn type_specialization() { 264 | let nat_ty = ast!( { "Type" "Nat" : }); 265 | 266 | fn tbn(nm: &'static str) -> Ast { ast!((vr nm)) } 267 | 268 | let _para_ty_env = assoc_n!( 269 | "some_int" => ast!( { "Type" "Int" : }), 270 | "convert_to_nat" => ast!({ "Type" "forall_type" : 271 | "param" => ["t"], 272 | "body" => (import [* [forall "param"]] { "Type" "fn" : 273 | "param" => [ (, tbn("t") ) ], 274 | "ret" => (, nat_ty.clone() ) })}), 275 | "identity" => ast!({ "Type" "forall_type" : 276 | "param" => ["t"], 277 | "body" => (import [* [forall "param"]] { "Type" "fn" : 278 | "param" => [ (, tbn("t") ) ], 279 | "ret" => (, tbn("t") ) })})); 280 | 281 | // assert_eq!(synth_type(&ast!({ "Expr" "apply" : 282 | // "rator" => (vr "convert_to_nat"), 283 | // "rand" => [ (vr "some_int") ] 284 | // }), para_ty_env.clone()), 285 | // Ok(ast!( { "Type" "Nat" : }))); 286 | 287 | // assert_eq!(synth_type(&ast!({ "Expr" "apply" : 288 | // "rator" => (vr "identity"), 289 | // "rand" => [ (vr "some_int") ] 290 | // }), para_ty_env.clone()), 291 | // Ok(ast!( { "Type" "Int" : }))); 292 | // TODO: test that ∀ X. ∀ Y. [ X → Y ] is a (sortof) sensible type (for transmogrify) 293 | // and that ∀ X. [ X → ∀ Y . Y ] is ridiculously permissive 294 | } 295 | -------------------------------------------------------------------------------- /src/unparse.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::{Ast, AstContents, AstContents::*}, 3 | grammar::{ 4 | FormPat::{self, *}, 5 | SynEnv, 6 | }, 7 | name::*, 8 | util::mbe::EnvMBE, 9 | }; 10 | 11 | fn node_names_mentioned(pat: &FormPat) -> Vec { 12 | match *pat { 13 | Named(n, ref body) => { 14 | let mut res = node_names_mentioned(&*body); 15 | res.push(n); 16 | res 17 | } 18 | Scope(_, _) => vec![], 19 | Pick(_, _) => vec![], 20 | Star(ref body) 21 | | Plus(ref body) 22 | | NameImport(ref body, _) 23 | | NameImportPhaseless(ref body, _) 24 | | VarRef(ref body) 25 | | Literal(ref body, _) 26 | | QuoteDeepen(ref body, _) 27 | | QuoteEscape(ref body, _) 28 | | Common(ref body) 29 | | Reserved(ref body, _) => node_names_mentioned(&*body), 30 | Seq(ref sub_pats) | Alt(ref sub_pats) => { 31 | let mut res = vec![]; 32 | for pat in sub_pats { 33 | res.append(&mut node_names_mentioned(pat)); 34 | } 35 | res 36 | } 37 | Biased(ref lhs, ref rhs) => { 38 | let mut res = node_names_mentioned(&*lhs); 39 | res.append(&mut node_names_mentioned(&*rhs)); 40 | res 41 | } 42 | Anyways(_) | Impossible | Scan(_, _) | Call(_) | SynImport(_, _, _) => vec![], 43 | } 44 | } 45 | 46 | pub fn unparse_mbe(pat: &FormPat, actl: &AstContents, context: &EnvMBE, s: &SynEnv) -> String { 47 | // HACK: handle underdetermined forms 48 | let undet = crate::ty_compare::underdetermined_form.with(|u| u.clone()); 49 | match actl { 50 | Node(form, body, _) if form == &undet => { 51 | return crate::ty_compare::unification.with(|unif| { 52 | let var = body.get_leaf_or_panic(&n("id")).to_name(); 53 | let looked_up = unif.borrow().get(&var).cloned(); 54 | match looked_up { 55 | // Apparently the environment is recursive; `{}`ing it stack-overflows 56 | Some(ref clo) => { 57 | format!("{} in some environment", clo.it /* , {:#?} clo.env */) 58 | } 59 | None => format!("¿{}?", var), 60 | } 61 | }); 62 | } 63 | _ => {} 64 | } 65 | 66 | // TODO: this really ought to notice when `actl` is ill-formed for `pat`. 67 | match (pat, actl) { 68 | (&Named(name, ref body), _) => { 69 | // TODO: why does the `unwrap_or` case happen once after each variable is printed? 70 | unparse_mbe(&*body, context.get_leaf(name).unwrap_or(&ast!((at ""))).c(), context, s) 71 | } 72 | (&Call(sub_form), _) => unparse_mbe(s.find_or_panic(&sub_form), actl, context, s), 73 | (&Anyways(_), _) | (&Impossible, _) => "".to_string(), 74 | (&Literal(_, n), _) => n.print(), 75 | (&Scan(_, _), &Atom(n)) => n.print(), 76 | (&Scan(_, _), _) => "".to_string(), // HACK for `Alt` 77 | (&VarRef(ref sub_form), &VariableReference(n)) => { 78 | unparse_mbe(&*sub_form, &Atom(n), context, s) 79 | } 80 | (&VarRef(_), _) => "".to_string(), // HACK for `Alt` 81 | (&Seq(ref sub_pats), _) => { 82 | let mut prev_empty = true; 83 | let mut res = String::new(); 84 | for sub_pat in sub_pats { 85 | let sub_res = unparse_mbe(&*sub_pat, actl, context, s); 86 | if !prev_empty && sub_res != "" { 87 | res.push(' '); 88 | } 89 | prev_empty = sub_res == ""; 90 | res.push_str(&sub_res); 91 | } 92 | res 93 | } 94 | (&Alt(ref sub_pats), _) => { 95 | let mut any_scopes = false; 96 | for sub_pat in sub_pats { 97 | if let Scope(_, _) = &**sub_pat { 98 | any_scopes = true; 99 | continue; 100 | } 101 | 102 | let sub_res = unparse_mbe(&*sub_pat, actl, context, s); 103 | if sub_res != "" { 104 | return sub_res; 105 | } // HACK: should use `Option` 106 | } 107 | // HACK: certain forms don't live in the syntax environment, 108 | // but "belong" under an `Alt`, so just assume forms know their grammar: 109 | if any_scopes { 110 | if let &Node(ref form_actual, ref body, _) = actl { 111 | return unparse_mbe(&*form_actual.grammar, actl, body, s); 112 | } 113 | } 114 | 115 | return "".to_string(); // Not sure if it's an error, or really just empty 116 | } 117 | (&Biased(ref lhs, ref rhs), _) => { 118 | format!("{}{}", unparse_mbe(lhs, actl, context, s), unparse_mbe(rhs, actl, context, s)) 119 | } 120 | (&Star(ref sub_pat), _) | (&Plus(ref sub_pat), _) => { 121 | let mut first = true; 122 | let mut res = String::new(); 123 | for marched_ctxt in context.march_all(&node_names_mentioned(&*sub_pat)) { 124 | if !first { 125 | res.push(' '); 126 | } 127 | first = false; 128 | res.push_str(&unparse_mbe(&*sub_pat, actl, &marched_ctxt, s)); 129 | } 130 | res 131 | } 132 | (&Scope(ref form, _), &Node(ref form_actual, ref body, _)) => { 133 | if form == form_actual { 134 | unparse_mbe(&*form.grammar, actl, body, s) 135 | } else { 136 | "".to_string() // HACK for `Alt` 137 | } 138 | } 139 | (&Scope(_, _), _) => "".to_string(), // Non-match 140 | (&Pick(ref body, _), _) | (&Common(ref body), _) => unparse_mbe(&*body, actl, context, s), 141 | (&NameImport(ref body, _), &ExtendEnv(ref actl_body, _)) => { 142 | unparse_mbe(&*body, actl_body.c(), context, s) 143 | } 144 | (&NameImport(_, _), _) => format!("[Missing import]→{:#?}←", actl), 145 | (&NameImportPhaseless(ref body, _), &ExtendEnvPhaseless(ref actl_body, _)) => { 146 | unparse_mbe(&*body, actl_body.c(), context, s) 147 | } 148 | (&NameImportPhaseless(_, _), _) => format!("[Missing import]±→{:#?}←±", actl), 149 | (&QuoteDeepen(ref body, _), &QuoteMore(ref actl_body, _)) => { 150 | unparse_mbe(&*body, actl_body.c(), context, s) 151 | } 152 | (&QuoteDeepen(_, _), _) => format!("[Missing qm]{:#?}", actl), 153 | (&QuoteEscape(ref body, _), &QuoteLess(ref actl_body, _)) => { 154 | unparse_mbe(&*body, actl_body.c(), context, s) 155 | } 156 | (&QuoteEscape(_, _), _) => format!("[Missing ql]{:#?}", actl), 157 | (&SynImport(ref _lhs_grammar, ref _rhs, _), &Node(_, ref actl_body, _)) => { 158 | // TODO: I think we need to store the LHS or the new SynEnv to make this pretty. 159 | format!("?syntax import? {}", actl_body) 160 | } 161 | (&SynImport(_, _, _), _) => "".to_string(), 162 | (&Reserved(ref body, _), _) => unparse_mbe(body, actl, context, s), 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/util/assoc.rs: -------------------------------------------------------------------------------- 1 | use crate::runtime::reify::Reifiable; 2 | use std::{clone::Clone, fmt, hash::Hash, rc::Rc}; 3 | 4 | extern crate im_rc; 5 | 6 | use self::im_rc::HashMap; 7 | 8 | thread_local! { 9 | static next_id: std::cell::RefCell = std::cell::RefCell::new(0); 10 | } 11 | 12 | fn get_next_id() -> u32 { 13 | next_id.with(|id| { 14 | let res = *id.borrow(); 15 | *id.borrow_mut() += 1; 16 | res 17 | }) 18 | } 19 | 20 | /// A persistent key-value store. `clone`, `set`, and `find` are sub-linear. 21 | #[derive(Clone)] 22 | pub struct Assoc 23 | where K: Eq + Hash + Clone 24 | { 25 | hamt: HashMap, 26 | // TODO: this is a hack, needed for `almost_ptr_eq`, 27 | // which in turn is only needed in `earley.rs`. 28 | // `earley.rs` should use interning as a replacement optimization, and `id` should be removed. 29 | id: u32, 30 | } 31 | 32 | impl PartialEq for Assoc { 33 | // `id` is not relevant for equality 34 | fn eq(&self, other: &Self) -> bool { self.hamt == other.hamt } 35 | } 36 | 37 | impl Eq for Assoc {} 38 | 39 | impl Default for Assoc { 40 | fn default() -> Self { Self::new() } 41 | } 42 | 43 | impl Reifiable for Assoc { 44 | fn ty_name() -> crate::name::Name { crate::name::n("Assoc") } 45 | 46 | fn concrete_arguments() -> Option> { 47 | Some(vec![K::ty_invocation(), V::ty_invocation()]) 48 | } 49 | 50 | fn reify(&self) -> crate::runtime::eval::Value { 51 | let res: Vec<_> = 52 | self.hamt.iter().map(|(k, v)| Rc::new((k.clone(), v.clone()).reify())).collect(); 53 | 54 | crate::runtime::eval::Value::Sequence(res) 55 | } 56 | 57 | fn reflect(v: &crate::runtime::eval::Value) -> Self { 58 | let mut res = Assoc::::new(); 59 | 60 | extract!((v) crate::runtime::eval::Value::Sequence = (ref parts) => { 61 | for part in parts { 62 | let (k_part, v_part) = <(K,V)>::reflect(&**part); 63 | res = res.set(k_part, v_part); 64 | } 65 | }); 66 | res 67 | } 68 | } 69 | 70 | impl fmt::Debug for Assoc { 71 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 72 | write!(f, "⟦")?; 73 | let mut first = true; 74 | for (k, v) in self.iter_pairs() { 75 | if !first { 76 | write!(f, ", ")?; 77 | } 78 | write!(f, "{:#?} ⇒ {:#?}", k, v)?; 79 | first = false; 80 | } 81 | write!(f, "⟧") 82 | } 83 | } 84 | 85 | impl fmt::Display for Assoc { 86 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 87 | write!(f, "⟦")?; 88 | let mut first = true; 89 | for (k, v) in self.iter_pairs() { 90 | if !first { 91 | write!(f, ", ")?; 92 | } 93 | write!(f, "{} ⇒ {}", k, v)?; 94 | first = false; 95 | } 96 | write!(f, "⟧") 97 | } 98 | } 99 | 100 | // Maybe we should admit that `K` is always `Name` (=`usize`) and stop taking references to it? 101 | // Maybe we shouldn't even parameterize over it? 102 | impl Assoc { 103 | fn from_hamt(hamt: HashMap) -> Self { Assoc { hamt: hamt, id: get_next_id() } } 104 | 105 | pub fn new() -> Self { Self::from_hamt(HashMap::new()) } 106 | 107 | pub fn find(&self, key: &K) -> Option<&V> { self.hamt.get(key) } 108 | 109 | pub fn set(&self, key: K, value: V) -> Self { Self::from_hamt(self.hamt.update(key, value)) } 110 | 111 | pub fn set_assoc(&self, other: &Self) -> Self { 112 | Self::from_hamt(other.hamt.clone().union(self.hamt.clone())) 113 | } 114 | 115 | pub fn union_with(&self, other: &Self, f: F) -> Self 116 | where F: FnMut(V, V) -> V { 117 | Self::from_hamt(self.hamt.clone().union_with(other.hamt.clone(), f)) 118 | } 119 | 120 | pub fn mut_set(&mut self, key: K, value: V) { self.hamt.insert(key, value); } 121 | 122 | pub fn single(key: K, value: V) -> Self { Self::new().set(key, value) } 123 | 124 | pub fn empty(&self) -> bool { self.hamt.is_empty() } 125 | 126 | pub fn iter_pairs(&self) -> im_rc::hashmap::Iter { self.hamt.iter() } 127 | 128 | pub fn iter_keys(&self) -> im_rc::hashmap::Keys { self.hamt.keys() } 129 | 130 | pub fn iter_values(&self) -> im_rc::hashmap::Values { self.hamt.values() } 131 | 132 | pub fn map(&self, mut f: F) -> Assoc 133 | where F: FnMut(&V) -> NewV { 134 | self.map_borrow_f(&mut f) 135 | } 136 | 137 | pub fn map_borrow_f<'assoc, NewV: Clone, F>(&'assoc self, f: &mut F) -> Assoc 138 | where F: FnMut(&'assoc V) -> NewV { 139 | Assoc::::from_hamt(self.hamt.iter().map(|(k, ref v)| (k.clone(), f(v))).collect()) 140 | } 141 | pub fn keyed_map_borrow_f(&self, f: &mut F) -> Assoc 142 | where F: FnMut(&K, &V) -> NewV { 143 | Assoc::::from_hamt( 144 | self.hamt.iter().map(|(k, ref v)| (k.clone(), f(k, v))).collect(), 145 | ) 146 | } 147 | 148 | pub fn map_with( 149 | &self, 150 | other: &Assoc, 151 | f: &dyn Fn(&V, &OtherV) -> NewV, 152 | ) -> Assoc { 153 | Assoc::::from_hamt( 154 | self.hamt 155 | .clone() 156 | .intersection_with_key(other.hamt.clone(), |_, ref v_l, ref v_r| f(v_l, v_r)), 157 | ) 158 | } 159 | 160 | pub fn keyed_map_with( 161 | &self, 162 | other: &Assoc, 163 | f: &dyn Fn(&K, &V, &OtherV) -> NewV, 164 | ) -> Assoc { 165 | Assoc::::from_hamt( 166 | self.hamt 167 | .clone() 168 | .intersection_with_key(other.hamt.clone(), |ref k, ref v_l, ref v_r| { 169 | f(k, v_l, v_r) 170 | }), 171 | ) 172 | } 173 | 174 | pub fn find_value<'assoc, 'f>(&'assoc self, target: &'f V) -> Option<&'assoc K> 175 | where V: PartialEq { 176 | self.hamt.iter().find(|(_, v)| v == &target).map(|(k, _)| k) 177 | } 178 | 179 | pub fn find_or_panic<'assoc, 'f>(&'assoc self, target: &'f K) -> &'assoc V 180 | where K: fmt::Display { 181 | self.find(target).unwrap_or_else(|| icp!("'{}' not found in {}", target, self.map(|_| "…"))) 182 | } 183 | 184 | pub fn remove<'assoc, 'f>(&'assoc mut self, target: &'f K) -> Option { 185 | self.hamt.remove(target) 186 | } 187 | 188 | pub fn remove_or_panic<'assoc, 'f>(&'assoc mut self, target: &'f K) -> V 189 | where K: fmt::Display { 190 | self.hamt 191 | .remove(target) 192 | .unwrap_or_else(|| icp!("{} not found in {}", target, self.map(|_| "…"))) 193 | } 194 | 195 | // Generates a version of `self` that lacks the entries that have identical values in `other` 196 | pub fn cut_common(&self, other: &Assoc) -> Assoc 197 | where V: PartialEq { 198 | let mut hamt = self.hamt.clone(); 199 | hamt.retain(|k, v| other.find(k) != Some(v)); 200 | Self::from_hamt(hamt) 201 | } 202 | 203 | pub fn unset(&self, k: &K) -> Assoc { Self::from_hamt(self.hamt.without(k)) } 204 | 205 | pub fn reduce(&self, red: &dyn Fn(&K, &V, Out) -> Out, base: Out) -> Out { 206 | self.hamt.iter().fold(base, |base, (k, v)| red(k, v, base)) 207 | } 208 | } 209 | 210 | impl Assoc { 211 | pub fn almost_ptr_eq(&self, other: &Assoc) -> bool { 212 | self.id == other.id // Only true if they are clones of each other 213 | } 214 | } 215 | 216 | impl Assoc> { 217 | pub fn lift_result(self) -> Result, E> { 218 | let mut oks = vec![]; 219 | for (k, res_v) in self.hamt.into_iter() { 220 | oks.push((k, res_v?)) 221 | } 222 | Ok(Assoc::::from_hamt(HashMap::from(oks))) 223 | } 224 | } 225 | 226 | #[test] 227 | fn basic_assoc() { 228 | let mt: Assoc = Assoc::new(); 229 | let a1 = mt.set(5, 6); 230 | let a2 = a1.set(6, 7); 231 | let a_override = a2.set(5, 500); 232 | 233 | assert_eq!(mt.find(&5), None); 234 | assert_eq!(a1.find(&6), None); 235 | assert_eq!(a2.find(&999), None); 236 | assert_eq!(a_override.find(&999), None); 237 | assert_eq!(a1.find(&5), Some(&6)); 238 | assert_eq!(a2.find(&5), Some(&6)); 239 | assert_eq!(a2.find(&6), Some(&7)); 240 | assert_eq!(a2.find(&5), Some(&6)); 241 | assert_eq!(a_override.find(&5), Some(&500)); 242 | assert_eq!(a_override.find(&6), Some(&7)); 243 | 244 | assert_eq!(a_override.unset(&5).find(&5), None); 245 | assert_eq!(a_override.unset(&6).find(&6), None); 246 | 247 | assert_eq!(a_override.unset(&6).find(&5), Some(&500)); 248 | assert_eq!(a_override.unset(&5).find(&6), Some(&7)); 249 | 250 | assert_eq!(a_override.unset(&-111).find(&5), Some(&500)); 251 | } 252 | 253 | #[test] 254 | fn assoc_equality() { 255 | let mt: Assoc = Assoc::new(); 256 | let a1 = mt.set(5, 6); 257 | let a2 = a1.set(6, 7); 258 | let a_override = a2.set(5, 500); 259 | 260 | let a2_opposite = mt.set(6, 7).set(5, 6); 261 | let a_override_direct = mt.set(5, 500).set(6, 7); 262 | 263 | assert_eq!(mt, Assoc::new()); 264 | assert_eq!(a1, a1); 265 | assert!(a1 != mt); 266 | assert!(mt != a1); 267 | assert_eq!(a2, a2); 268 | assert_eq!(a2, a2_opposite); 269 | assert_eq!(a_override, a_override_direct); 270 | assert!(a2 != a_override); 271 | 272 | let a1_again = mt.set(5, 6); 273 | 274 | // Nothing shared: no-op 275 | assert_eq!(mt.cut_common(&mt), mt); 276 | assert_eq!(a1.cut_common(&mt), a1); 277 | assert_eq!(mt.cut_common(&a1), mt); 278 | 279 | // Everything shared: empty result 280 | assert_eq!(a1_again.cut_common(&a1), mt); 281 | assert_eq!(a_override_direct.cut_common(&a_override), mt); 282 | assert_eq!(a_override.cut_common(&a_override_direct), mt); 283 | assert_eq!(a1.cut_common(&a1), mt); 284 | assert_eq!(a2.cut_common(&a2), mt); 285 | 286 | // Partial share: 287 | assert_eq!(a2.cut_common(&a1), mt.set(6, 7)); 288 | assert_eq!(a_override.cut_common(&a2), mt.set(5, 500)); 289 | 290 | assert!(mt.almost_ptr_eq(&mt)); 291 | assert!(a2.almost_ptr_eq(&a2)); 292 | assert!(a_override_direct.almost_ptr_eq(&a_override_direct)); 293 | assert!(!a2.almost_ptr_eq(&a2_opposite)); 294 | // assert!(mt.almost_ptr_eq(&Assoc::new())); 295 | } 296 | 297 | #[test] 298 | fn assoc_r_and_r_roundtrip() { 299 | use num::BigInt; 300 | let mt: Assoc = Assoc::new(); 301 | let a1 = mt.set(BigInt::from(5), BigInt::from(6)); 302 | let a2 = a1.set(BigInt::from(6), BigInt::from(7)); 303 | 304 | assert_eq!(mt, Assoc::::reflect(&mt.reify())); 305 | assert_eq!(a2, Assoc::::reflect(&a2.reify())); 306 | } 307 | 308 | #[test] 309 | fn assoc_map() { 310 | let a1 = assoc_n!("x" => 1, "y" => 2, "z" => 3); 311 | assert_eq!(a1.map(|a| a + 1), assoc_n!("x" => 2, "y" => 3, "z" => 4)); 312 | 313 | let a2 = assoc_n!("y" => -2, "z" => -3, "x" => -1); 314 | assert_eq!(a1.map_with(&a2, &|a, b| a + b), assoc_n!("x" => 0, "y" => 0, "z" => 0)); 315 | } 316 | 317 | #[test] 318 | fn assoc_reduce() { 319 | let a1 = assoc_n!("x" => 1, "y" => 2, "z" => 3); 320 | assert_eq!(a1.reduce(&|_key, a, b| a + b, 0), 6); 321 | 322 | let a1 = assoc_n!("x" => 1, "y" => 2, "z" => 3); 323 | assert_eq!(a1.reduce(&|key, a, b| if key.is("y") { b } else { a + b }, 0), 4); 324 | } 325 | -------------------------------------------------------------------------------- /src/util/asterism.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, iter::Iterator}; 2 | 3 | // An `Asterism` is a tree with an arbirary arity at each node, 4 | // but all leaves are at the same depth. 5 | // This structure arises from parsing under Kleene stars; each star correspons to a level of nodes. 6 | 7 | // [ ] <- 2 children 8 | // [ ][ ] <- 3, 1 children 9 | // [ ][][ ][ ] <- 5, 0, 1, 2 children 10 | // [ ][ ][ ][ ][ ] [ ][ ][ ] <- 2, 1, 2, 2, 2, 4, 1, 1 children 11 | // a b c d e f g h i j k l m n o 12 | 13 | // * * # a b # c # d e # f g # h i * * # j k l m * * # n # o 14 | // ----| --| ----| ----| ----| | --------| --| --| <- these are `PackedNodes`es 15 | // ----------------------------| | ----------| --------| <- everything below is a `Node` 16 | // --------------------------------------------| ----------| <- ⋮ 17 | 18 | #[derive(Debug, PartialEq, Eq, Clone)] 19 | /// This is only `pub` for technical reasons; it doesn't need to be exposed to other modules. 20 | /// 21 | /// `Node`s and `PackedNodes`es appear at the beginning of a slice to guide marching. 22 | /// If you see `[PackedNodes, …]`, you know you're at depth 1 and [1..] is your leaves. 23 | /// Otherwise, `[Node(n), , Node(m), , …]` is the shape of the current level. 24 | pub enum LeafOrNode { 25 | Leaf(T), 26 | /// A depth-1 node: *each* subtree is a single `Leaf` 27 | PackedNodes, 28 | /// A depth >1 node: *this* subtree is `usize` entires long 29 | Node(usize), 30 | } 31 | 32 | #[derive(PartialEq, Eq, Clone)] 33 | pub struct Asterism(Vec>); 34 | #[derive(PartialEq, Eq)] 35 | pub struct AsterismSlice<'a, T>(&'a [LeafOrNode]); 36 | 37 | // `AsterismSlice` is a reference-like type, so it's always `Clone` and `Copy`, even if `T` isn't: 38 | impl<'a, T> Copy for AsterismSlice<'a, T> {} 39 | impl<'a, T> Clone for AsterismSlice<'a, T> { 40 | fn clone(&self) -> AsterismSlice<'a, T> { *self } 41 | } 42 | 43 | /// This trait is for reference-like views of `Asterism`. 44 | pub trait AsterMarchable<'a, T: 'a>: Sized + Clone + Copy { 45 | fn as_slice(self) -> AsterismSlice<'a, T>; 46 | 47 | /// Only used for implementation of other methods; 48 | /// adding another layer of trait seems like too much trouble to hide this. 49 | fn inner(self) -> &'a [LeafOrNode]; 50 | 51 | /// Returns an iterator of `AsterMarchable`s 52 | fn march(self) -> std::vec::IntoIter> { 53 | let mut subs = vec![]; 54 | if self.inner().is_empty() { 55 | return subs.into_iter(); 56 | } 57 | 58 | // A `PackedNodes` means the rest of the slice is our children (all leaves): 59 | let depth_1: bool = matches!(self.inner()[0], LeafOrNode::PackedNodes); 60 | let mut i = if depth_1 { 1 } else { 0 }; // Skip the `PackedNodes` 61 | 62 | while i < self.inner().len() { 63 | let span = match self.inner()[i] { 64 | LeafOrNode::Leaf(_) => { 65 | if !depth_1 { 66 | icp!("Unexpected Leaf") 67 | } 68 | 1 69 | } 70 | LeafOrNode::PackedNodes => icp!("Unexpected PackedNodes"), 71 | LeafOrNode::Node(span) => { 72 | if depth_1 { 73 | icp!("Unexpected Node") 74 | } 75 | i += 1; 76 | span 77 | } 78 | }; 79 | subs.push(AsterismSlice(&self.inner()[i..i + span])); 80 | 81 | i += span; 82 | } 83 | 84 | subs.into_iter() 85 | } 86 | 87 | fn collect(self) -> Vec> { 88 | let mut res = vec![]; 89 | for sub in self.march() { 90 | res.push(sub); 91 | } 92 | res 93 | } 94 | 95 | fn is_leaf(self) -> bool { 96 | let inner = self.as_slice().0; 97 | if inner.is_empty() { 98 | return false; 99 | } 100 | matches!(inner[0], LeafOrNode::Leaf(_)) 101 | } 102 | 103 | fn as_leaf(self) -> &'a T { 104 | let inner = self.as_slice().0; 105 | if inner.len() != 1 { 106 | icp!("not a leaf, length is {}", inner.len()) 107 | } 108 | match inner[0] { 109 | LeafOrNode::Leaf(ref l) => l, 110 | _ => icp!("malformed Asterism"), 111 | } 112 | } 113 | 114 | fn as_depth_1(self) -> Box + 'a> { 115 | if self.as_slice().0.is_empty() { 116 | // The "official" representation of an empty depth-1 node is a sequence with 1 `PN`. 117 | // ...but `Asterism::join(vec![])` doesn't know whether it's depth-1 or not! 118 | // So we also support an empty vector. 119 | // TODO: is there a better way? 120 | return Box::new(std::iter::empty()); 121 | } 122 | match self.as_slice().0[0] { 123 | LeafOrNode::PackedNodes => {} 124 | _ => icp!("Not depth-1"), 125 | } 126 | Box::new(self.as_slice().0[1..].iter().map(|lon| match lon { 127 | LeafOrNode::Leaf(ref l) => l, 128 | _ => icp!("Not depth-1"), 129 | })) 130 | } 131 | } 132 | 133 | impl<'a, T> AsterMarchable<'a, T> for AsterismSlice<'a, T> { 134 | fn as_slice(self) -> AsterismSlice<'a, T> { self } 135 | fn inner(self) -> &'a [LeafOrNode] { self.0 } 136 | } 137 | 138 | impl<'a, T> AsterMarchable<'a, T> for &'a Asterism { 139 | fn as_slice(self) -> AsterismSlice<'a, T> { AsterismSlice(&self.0[..]) } 140 | fn inner(self) -> &'a [LeafOrNode] { &self.0[..] } 141 | } 142 | 143 | impl Asterism { 144 | pub fn join(subs: Vec>) -> Asterism { 145 | let mut res = vec![]; 146 | if subs.is_empty() { 147 | return Asterism(vec![LeafOrNode::Node(0)]); 148 | } 149 | if !subs[0].0.is_empty() && matches!(subs[0].0[0], LeafOrNode::Leaf(_)) { 150 | let mut res = vec![LeafOrNode::PackedNodes]; 151 | for mut leaf_asterism in subs { 152 | if !leaf_asterism.is_leaf() { 153 | icp!("Not a valid leaf"); 154 | } 155 | res.push(leaf_asterism.0.remove(0)); 156 | } 157 | return Asterism(res); 158 | } 159 | 160 | for mut aster in subs { 161 | res.push(LeafOrNode::Node(aster.0.len())); 162 | res.append(&mut aster.0); 163 | } 164 | Asterism(res) 165 | } 166 | 167 | pub fn from_leaf(leaf: T) -> Asterism { Asterism(vec![LeafOrNode::Leaf(leaf)]) } 168 | 169 | pub fn from_depth_1(leaves: Vec) -> Asterism { 170 | let mut res = vec![LeafOrNode::PackedNodes]; 171 | for leaf in leaves { 172 | res.push(LeafOrNode::Leaf(leaf)) 173 | } 174 | 175 | Asterism(res) 176 | } 177 | } 178 | 179 | impl<'a, T: Clone> AsterismSlice<'a, T> { 180 | pub fn to_asterism(self) -> Asterism { Asterism(self.0.to_vec()) } 181 | } 182 | 183 | impl fmt::Debug for AsterismSlice<'_, T> { 184 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 185 | if self.is_leaf() { 186 | write!(f, "{:?}", self.as_leaf()) 187 | } else { 188 | write!(f, "[")?; 189 | let mut first = true; 190 | for ref sub in self.march() { 191 | if !first { 192 | write!(f, " ")?; 193 | } 194 | write!(f, "{:?}", sub)?; 195 | first = false; 196 | } 197 | write!(f, "]") 198 | } 199 | } 200 | } 201 | 202 | impl fmt::Debug for Asterism { 203 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.as_slice().fmt(f) } 204 | } 205 | 206 | impl fmt::Display for AsterismSlice<'_, T> { 207 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 208 | if self.is_leaf() { 209 | write!(f, "{}", self.as_leaf()) 210 | } else { 211 | write!(f, "[")?; 212 | let mut first = true; 213 | for ref sub in self.march() { 214 | if !first { 215 | write!(f, " ")?; 216 | } 217 | write!(f, "{}", sub)?; 218 | first = false; 219 | } 220 | write!(f, "]") 221 | } 222 | } 223 | } 224 | 225 | impl fmt::Display for Asterism { 226 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.as_slice().fmt(f) } 227 | } 228 | 229 | impl Asterism { 230 | /// For internal debugging only 231 | fn show(&self) { 232 | for elt in &self.0 { 233 | match elt { 234 | LeafOrNode::Node(n) => print!("N{} ", n), 235 | LeafOrNode::PackedNodes => print!("PN "), 236 | LeafOrNode::Leaf(l) => print!("L{:?} ", l), 237 | } 238 | } 239 | } 240 | } 241 | 242 | #[test] 243 | fn asterism_basics() { 244 | let abc = Asterism::from_depth_1(vec!["a", "b", "c"]); 245 | 246 | assert_eq!(abc.as_slice().as_depth_1().collect::>(), vec![&"a", &"b", &"c"]); 247 | 248 | let (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = 249 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); 250 | 251 | let a_through_i = Asterism::join(vec![ 252 | Asterism::from_depth_1(vec![a, b]), 253 | Asterism::from_depth_1(vec![c]), 254 | Asterism::from_depth_1(vec![d, e]), 255 | Asterism::from_depth_1(vec![f, g]), 256 | Asterism::from_depth_1(vec![h, i]), 257 | ]); 258 | 259 | let a_through_m = Asterism::join(vec![ 260 | a_through_i, 261 | Asterism::join(vec![]), // Empty `Asterism`s can be deeper than they look 262 | Asterism::join(vec![Asterism::from_depth_1(vec![j, k, l, m])]), 263 | ]); 264 | 265 | let a_through_o = Asterism::join(vec![ 266 | a_through_m, 267 | Asterism::join(vec![Asterism::join(vec![ 268 | Asterism::from_depth_1(vec![n]), 269 | Asterism::from_depth_1(vec![o]), 270 | ])]), 271 | ]); 272 | 273 | assert_eq!( 274 | format!("{}", a_through_o), 275 | "[[[[0 1] [2] [3 4] [5 6] [7 8]] [[]] [[9 10 11 12]]] [[[13] [14]]]]" 276 | ); 277 | 278 | let mut expected_d1 = 0; 279 | let mut expected_m = 0; 280 | for m in a_through_o.march() { 281 | for mm in m.march() { 282 | for mmm in mm.march() { 283 | for mmmm in mmm.as_depth_1() { 284 | assert_eq!(*mmmm, expected_d1); 285 | expected_d1 += 1; 286 | } 287 | for mmmm in mmm.march() { 288 | assert_eq!(*mmmm.as_leaf(), expected_m); 289 | expected_m += 1; 290 | } 291 | } 292 | } 293 | } 294 | assert_eq!(15, expected_d1); 295 | assert_eq!(15, expected_m); 296 | 297 | let d1 = Asterism::from_depth_1(vec![vec![1, 3], vec![2, 3]]); 298 | 299 | for v in d1.as_depth_1() { 300 | assert_eq!(v.len(), 2) 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/util/err.rs: -------------------------------------------------------------------------------- 1 | use codespan_reporting::{ 2 | diagnostic::{Diagnostic, Label}, 3 | term::termcolor::{Ansi, ColorChoice, StandardStream, WriteColor}, 4 | }; 5 | use std::fmt::{Debug, Display, Formatter, Result}; 6 | 7 | custom_derive! { 8 | #[derive(Reifiable, Clone, PartialEq)] 9 | pub struct Spanned { 10 | // The actual contents are ignored; only the span information is used. 11 | // (Unless the span is a ficitious `Ast`, which Should Not Happen, but does.) 12 | pub loc: crate::ast::Ast, 13 | pub body: T 14 | } 15 | } 16 | 17 | pub fn sp(t: T, a: crate::ast::Ast) -> Spanned { Spanned { loc: a, body: t } } 18 | 19 | impl Spanned { 20 | pub fn emit_to_writer(&self, mut writer: &mut dyn WriteColor) { 21 | let diagnostic = 22 | Diagnostic::error().with_message(format!("{}", self.body)).with_labels(vec![ 23 | Label::primary(self.loc.0.file_id, self.loc.0.begin..self.loc.0.end), 24 | ]); 25 | 26 | let config = codespan_reporting::term::Config::default(); 27 | 28 | if let Err(_) = crate::earley::files.with(|f| { 29 | codespan_reporting::term::emit(&mut writer, &config, &*f.borrow(), &diagnostic) 30 | }) { 31 | writer.write(format!("[NO FILE] {} at {}", self.body, self.loc).as_bytes()).unwrap(); 32 | } 33 | } 34 | 35 | pub fn emit(&self) { 36 | let mut writer = StandardStream::stderr(ColorChoice::Always); 37 | self.emit_to_writer(&mut writer); 38 | } 39 | } 40 | 41 | // Temporary HACK: capture the ANSI terminal output in a string, 42 | // assuming we'll get printed to a terminal. 43 | impl Display for Spanned { 44 | fn fmt(&self, f: &mut Formatter) -> Result { 45 | let mut writer = Ansi::new(Vec::::new()); 46 | 47 | self.emit_to_writer(&mut writer); 48 | 49 | write!(f, "{}", std::str::from_utf8(&writer.into_inner()).unwrap()) 50 | } 51 | } 52 | 53 | // Force pretty version 54 | impl Debug for Spanned { 55 | fn fmt(&self, f: &mut Formatter) -> Result { 56 | let mut writer = Ansi::new(Vec::::new()); 57 | 58 | self.emit_to_writer(&mut writer); 59 | 60 | write!(f, "{}", std::str::from_utf8(&writer.into_inner()).unwrap()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod assoc; 2 | pub mod asterism; 3 | pub mod err; 4 | pub mod mbe; 5 | pub mod sky; 6 | pub mod vrep; 7 | -------------------------------------------------------------------------------- /src/util/sky.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | name::Name, 3 | util::{ 4 | assoc::Assoc, 5 | asterism::{AsterMarchable, Asterism, AsterismSlice}, 6 | }, 7 | }; 8 | use std::iter::Iterator; 9 | 10 | type Sky = Assoc>; 11 | /// `SkySlice` isn't a slice itself, 12 | /// so we can't pull the same trick we pulled with `AsterismSlice`. 13 | /// (Maybe rename it, then?) 14 | type SkySlice<'a, T> = Assoc>; 15 | 16 | impl<'a, T> SkySlice<'a, T> 17 | where T: Clone 18 | { 19 | fn get(&self, n: Name) -> AsterismSlice<'a, T> { *self.find_or_panic(&n) } 20 | 21 | fn combine_overriding(&self, rhs: &Self) -> Assoc> { 22 | self.set_assoc(rhs) 23 | } 24 | 25 | #[deprecated] 26 | // In `EnvMBE`, this respected named repeats. 27 | fn merge(&self, rhs: &Self) -> Assoc> { self.set_assoc(rhs) } 28 | 29 | /// Each of `driving_names` must be a repetition of the same length. 30 | /// Produces an iterator with the same length, 31 | /// yielding `SkySlice`s in which the driven names have that repetition removed. 32 | fn march(&self, driving_names: &[Name]) -> Box> + 'a> { 33 | let marchers: Vec<(Name, std::vec::IntoIter>)> = 34 | driving_names.iter().map(|n| (*n, self.get(*n).march())).collect(); 35 | 36 | // Require the lengths to be the same 37 | for (n, marcher) in &marchers[1..] { 38 | if marcher.len() != marchers[0].1.len() { 39 | icp!( 40 | "Lengths don't match in march names: {} ({}) != {} ({})", 41 | marcher.len(), 42 | n, 43 | marchers[0].1.len(), 44 | marchers[0].0 45 | ); 46 | } 47 | } 48 | 49 | // By default, names refer to the same Asterims (just sliced) 50 | let mut res: Box> + 'a> = 51 | Box::new(std::iter::repeat(self.clone())); 52 | 53 | // For the driving names, override with the current step of the march 54 | for (name, marched) in marchers { 55 | res = Box::new(res.zip(marched.into_iter()).map( 56 | move |(base, marched): (SkySlice, AsterismSlice)| base.set(name, marched), 57 | )); 58 | } 59 | 60 | res 61 | } 62 | 63 | #[deprecated(note = "use `march` instead")] 64 | // The `_all` is no longer meaningful. This does a bunch of unnecessary cloning. 65 | fn march_all(&self, driving_names: &[Name]) -> Vec> { 66 | self.march(driving_names).map(|s| s.to_sky()).collect() 67 | } 68 | 69 | #[deprecated(note = "do we need this?")] 70 | fn get_leaf<'b>(&'b self, n: Name) -> Option<&'a T> { 71 | match self.find(&n) { 72 | None => None, 73 | Some(aster) => Some(aster.as_leaf()), 74 | } 75 | } 76 | 77 | fn leaf<'b>(&'b self, n: Name) -> &'a T 78 | where 'a: 'b { 79 | self.get(n).as_leaf() 80 | } 81 | 82 | #[deprecated(note = "use `leaf` instead")] 83 | fn get_leaf_or_panic(&self, n: Name) -> &T { self.get(n).as_leaf() } 84 | 85 | #[deprecated(note = "use `depth_1` instead")] 86 | fn get_rep_leaf(&self, n: Name) -> Option> { 87 | self.find(&n).map(|asterism| asterism.as_depth_1().collect::>()) 88 | } 89 | 90 | #[deprecated(note = "use `depth_1` instead")] 91 | fn get_rep_leaf_or_panic(&'a self, n: Name) -> Vec<&'a T> { 92 | self.get(n).as_depth_1().collect::>() 93 | } 94 | 95 | fn depth_1<'b>(&'b self, n: Name) -> Box + 'a> { 96 | self.get(n).as_depth_1() 97 | } 98 | 99 | // fn map_flatten_rep_leaf_or_panic(&self, n: Name, depth: u8, m: &dyn Fn(&T) -> S, f: &dyn Fn(Vec) -> S) -> S { 100 | // unimplemented!() 101 | // } 102 | } 103 | 104 | impl SkySlice<'_, T> { 105 | pub fn to_sky(&self) -> Sky { 106 | self.map_borrow_f(&mut |a: &AsterismSlice| a.to_asterism()) 107 | } 108 | } 109 | 110 | impl Sky { 111 | // `l` could be a reference, but do we ever want that? 112 | pub fn new_from_leaves(l: Assoc) -> Self { l.map(|l| Asterism::from_leaf(l.clone())) } 113 | 114 | #[deprecated] 115 | pub fn new_from_named_repeat(_n: Name, r: Vec) -> Self { Self::new_from_anon_repeat(r) } 116 | 117 | pub fn new_from_anon_repeat(mut r: Vec) -> Self { 118 | if r.is_empty() { 119 | return Sky::new(); 120 | } 121 | let mut res = Assoc::>::new(); 122 | if !r.is_empty() { 123 | let keys: Vec = r[0].iter_keys().copied().collect(); 124 | for k in keys { 125 | let per_name_asterisms: Vec> = 126 | r.iter_mut().map(|sky| sky.remove_or_panic(&k)).collect(); 127 | res = res.set(k, Asterism::join(per_name_asterisms)); 128 | } 129 | } 130 | res 131 | } 132 | 133 | pub fn leaf(&self, n: Name) -> &T { self.find_or_panic(&n).as_leaf() } 134 | 135 | pub fn depth_1<'b>(&'b self, n: Name) -> Box + 'b> { 136 | self.find_or_panic(&n).as_depth_1() 137 | } 138 | 139 | pub fn to_sky_slices<'b>(&'b self) -> SkySlice<'b, T> { 140 | self.map_borrow_f(&mut |a: &Asterism| a.as_slice()) 141 | } 142 | 143 | pub fn march<'b>( 144 | &'b self, 145 | driving_names: &'b [Name], 146 | ) -> Box> + 'b> { 147 | self.to_sky_slices().march(driving_names) 148 | } 149 | 150 | pub fn add_leaf(&mut self, n: Name, v: T) { self.mut_set(n, Asterism::from_leaf(v)); } 151 | 152 | #[deprecated(note = "inefficent, and named repeats are gone")] 153 | pub fn add_named_repeat(&mut self, _: Name, sub: Vec>) { self.add_anon_repeat(sub) } 154 | 155 | // TODO: how DO we construct in a general case? 156 | #[deprecated(note = "inefficent")] 157 | pub fn add_anon_repeat(&mut self, sub: Vec>) { 158 | if sub.is_empty() || sub[0].empty() { 159 | return; 160 | } 161 | for n in sub[0].iter_keys() { 162 | let asters: Vec> = 163 | sub.iter().map(|sky| sky.find_or_panic(n).clone()).collect(); 164 | self.mut_set(*n, Asterism::join(asters)); 165 | } 166 | } 167 | 168 | #[deprecated(note = "named repeats are gone")] 169 | pub fn anonimize_repeat(&mut self, _: Name) {} 170 | } 171 | 172 | // TODO: move these to macros.rs (and fully-qualify their names) 173 | macro_rules! asterism { 174 | ([$( $sub:tt ),*]) => { 175 | Asterism::join(vec![ 176 | $( asterism!($sub) ),* 177 | ]) 178 | }; 179 | ($leaf:expr) => { Asterism::from_leaf($leaf) }; 180 | } 181 | 182 | macro_rules! sky { 183 | ( $($n:tt => $rhs:tt),* ) => { 184 | assoc_n!( $( (stringify!($n)) => asterism!($rhs) ),* ) 185 | }; 186 | } 187 | 188 | #[test] 189 | fn sky_basics() { 190 | use crate::name::n; 191 | 192 | let abc: Sky = sky!( 193 | a => [[1, 2], [3], []], 194 | b => 9, 195 | c => [4, 5, 6], 196 | d => [7, 8] 197 | ); 198 | 199 | assert_eq!(abc.leaf(n("b")), &9); 200 | assert_eq!(abc.depth_1(n("c")).collect::>(), vec![&4, &5, &6]); 201 | 202 | let mut cur_c = 4; 203 | for abccc in abc.march(&[n("c")]) { 204 | assert_eq!(abccc.leaf(n("b")), &9); 205 | assert_eq!(abccc.leaf(n("c")), &cur_c); 206 | cur_c += 1; 207 | assert_eq!(abccc.depth_1(n("d")).collect::>(), vec![&7, &8]); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/util/vrep.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | name::{n, Name}, 3 | runtime::reify::Reifiable, 4 | }; 5 | use std::iter::FromIterator; 6 | 7 | custom_derive! { 8 | #[derive(Clone, PartialEq, Eq, Debug, Reifiable)] 9 | pub enum VRepElt { 10 | Single(T), 11 | Rep(T, Vec), 12 | } 13 | } 14 | 15 | impl VRepElt { 16 | pub fn map<'a, U, F>(&'a self, f: &mut F) -> VRepElt 17 | where F: FnMut(&'a T) -> U { 18 | match self { 19 | Single(e) => Single(f(e)), 20 | Rep(e, names) => Rep(f(e), names.clone()), 21 | } 22 | } 23 | 24 | pub fn into_map(self, f: &mut F) -> VRepElt 25 | where F: FnMut(T) -> U { 26 | match self { 27 | Single(e) => Single(f(e)), 28 | Rep(e, names) => Rep(f(e), names), 29 | } 30 | } 31 | 32 | pub fn zip_map(&self, other: &VRepElt, mut f: F) -> Option> 33 | where F: FnMut(&T, &T) -> U { 34 | match (self, other) { 35 | (Single(s), Single(r)) => Some(Single(f(s, r))), 36 | (Rep(s, s_names), Rep(r, r_names)) if s_names == r_names => { 37 | Some(Rep(f(s, r), s_names.clone())) 38 | } 39 | _ => None, 40 | } 41 | } 42 | } 43 | 44 | use VRepElt::*; 45 | 46 | #[derive(Debug, PartialEq, Eq)] 47 | pub enum VRepLen { 48 | Exactly(usize), 49 | AtLeast(usize), 50 | } 51 | 52 | #[derive(Clone, PartialEq, Eq)] 53 | pub struct VRep(Vec>); 54 | pub struct SRep<'a, T>(&'a [VRepElt]); 55 | 56 | impl VRep { 57 | pub fn expand_reps(&self, mut f: F) -> Vec 58 | where 59 | F: FnMut(&T, &Vec) -> Vec, 60 | T: Clone, 61 | { 62 | let mut res = vec![]; 63 | for elt in &self.0 { 64 | match elt { 65 | Single(e) => res.push(e.clone()), 66 | Rep(es, names) => { 67 | let mut expanded = f(es, names); 68 | res.append(&mut expanded) 69 | } 70 | } 71 | } 72 | res 73 | } 74 | 75 | pub fn concrete(&self) -> bool { 76 | for elt in &self.0 { 77 | match elt { 78 | Rep(_, _) => return false, 79 | Single(_) => {} 80 | } 81 | } 82 | return true; 83 | } 84 | 85 | pub fn is_empty(&self) -> bool { self.0.is_empty() } 86 | 87 | pub fn can_be_empty(&self) -> bool { 88 | for elt in &self.0 { 89 | match elt { 90 | Rep(_, _) => {} 91 | Single(_) => return false, 92 | } 93 | } 94 | return true; 95 | } 96 | 97 | pub fn len(&self) -> VRepLen { 98 | let mut min_len: usize = 0; 99 | let mut exact: bool = true; 100 | for elt in &self.0 { 101 | match elt { 102 | Single(_) => min_len += 1, 103 | Rep(_, _) => exact = false, 104 | } 105 | } 106 | if exact { 107 | VRepLen::Exactly(min_len) 108 | } else { 109 | VRepLen::AtLeast(min_len) 110 | } 111 | } 112 | 113 | pub fn iter(&self) -> std::slice::Iter> { self.0.iter() } 114 | 115 | pub fn map<'a, U, F>(&'a self, mut f: F) -> VRep 116 | where F: FnMut(&'a T) -> U { 117 | let mut res = vec![]; 118 | for elt in &self.0 { 119 | res.push(elt.map(&mut f)); 120 | } 121 | VRep(res) 122 | } 123 | 124 | pub fn into_map(self, mut f: F) -> VRep 125 | where F: FnMut(T) -> U { 126 | let mut res = vec![]; 127 | for elt in self.0 { 128 | res.push(elt.into_map(&mut f)); 129 | } 130 | VRep(res) 131 | } 132 | } 133 | 134 | // Only needed because our custom_derive! doesn't support newtype-style structs: 135 | impl Reifiable for VRep { 136 | fn ty_name() -> Name { n("VRep") } 137 | 138 | fn concrete_arguments() -> Option> { Some(vec![T::ty_invocation()]) } 139 | 140 | fn reify(&self) -> crate::runtime::eval::Value { 141 | let res: Vec<_> = self.0.iter().map(|e| std::rc::Rc::new(e.reify())).collect(); 142 | 143 | crate::runtime::eval::Value::Sequence(res) 144 | } 145 | 146 | fn reflect(v: &crate::runtime::eval::Value) -> Self { 147 | let mut res = vec![]; 148 | 149 | extract!((v) crate::runtime::eval::Value::Sequence = (ref parts) => { 150 | for part in parts { 151 | res.push(>::reflect(&**part)); 152 | } 153 | }); 154 | VRep(res) 155 | } 156 | } 157 | 158 | // Turns a plain Vec into a VRep without repetitions 159 | impl From> for VRep { 160 | fn from(flat: Vec) -> Self { VRep(flat.into_iter().map(Single).collect()) } 161 | } 162 | 163 | impl std::fmt::Debug for VRep { 164 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 165 | write!(f, "[")?; 166 | let mut first = false; 167 | for elt in &self.0 { 168 | if !first { 169 | write!(f, ", ")?; 170 | } 171 | first = false; 172 | match elt { 173 | Single(e) => write!(f, "{:?}", e)?, 174 | Rep(e, names) => write!(f, "{:?} ...({:?})", e, names)?, 175 | } 176 | } 177 | write!(f, "]") 178 | } 179 | } 180 | 181 | impl IntoIterator for VRep { 182 | type Item = VRepElt; 183 | type IntoIter = std::vec::IntoIter; 184 | 185 | fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } 186 | } 187 | 188 | impl<'a, T: 'a> IntoIterator for &'a VRep { 189 | type Item = &'a VRepElt; 190 | type IntoIter = std::slice::Iter<'a, VRepElt>; 191 | 192 | fn into_iter(self) -> Self::IntoIter { self.0.iter() } 193 | } 194 | 195 | impl FromIterator> for VRep { 196 | fn from_iter>>(iter: I) -> Self { 197 | let mut res = vec![]; 198 | for i in iter { 199 | res.push(i); 200 | } 201 | VRep(res) 202 | } 203 | } 204 | 205 | #[test] 206 | fn vrep_basics() { 207 | assert_eq!(vrep![1, 2, 3, 4, 5], VRep::from(vec![1, 2, 3, 4, 5])); 208 | 209 | assert_eq!(vrep![1, 2, 3, 4, 5].len(), VRepLen::Exactly(5)); 210 | 211 | let with_rep = vrep![1, 2 => (a, b, c), 3]; 212 | assert_eq!(with_rep.len(), VRepLen::AtLeast(2)); 213 | 214 | assert_eq!(with_rep.expand_reps(|_, _| vec![7, 7, 7]), vec![1, 7, 7, 7, 3]); 215 | 216 | // Reification roundtrip: 217 | assert_eq!(with_rep, >::reflect(&with_rep.reify())) 218 | } 219 | -------------------------------------------------------------------------------- /structure: -------------------------------------------------------------------------------- 1 | Like a normal language implementation, we have 2 | a reader that turns text into tokens, 3 | a parser that turns tokens into AST, 4 | a typechecker that validates an AST, 5 | a macro expander that transforms 6 | an AST with user-defined forms 7 | into an AST in a core language, 8 | and an evaluator that turns ASTs into behavior. 9 | Unlike a normal language implementation, 10 | the typechecker runs before the macro expander. 11 | This means that the typechecker has to operate on 12 | user-defined AST nodes, 13 | which is possible, 14 | because (I think) one can derive a typechecker for a form 15 | from its definition. 16 | The weirdness of this language implementation starts with the AST. 17 | 18 | Well, the parser and tokenizer are weird, but they don't have to be. 19 | 20 | 21 | TODO: a demo of the steps involved in typing and expanding an actual program 22 | would probably be more informative than the "Pipeline" diagram. 23 | 24 | Language definition: 25 | 26 | [Ast] ast.rs and ast_walk.rs 27 | 28 | The core language is small, but the AST definition is much smaller. 29 | It contains only: 30 | * variable introductions and references, 31 | * binding information, 32 | * (temporary parsing information), 33 | * quotation level indicators, and 34 | * `Node`, which represents complex AST nodes. 35 | 36 | `walk` traverses these nodes and keeps the environment 37 | (actually a stack of environments, for nested quotation) 38 | set up appropriately. 39 | 40 | A "positive" walk takes an environment of `Elt`, and results in a single `Elt`. 41 | A "negative" walk takes an `Elt`, and produces an environment of `Elt`. 42 | Evaluation or typechecking expressions is a positive walk; 43 | evaluating or typechecking patterns is negative. 44 | 45 | 46 | [Core] core_forms.rs, core_type_forms.rs, core_qq_forms.rs, core_macro_forms.rs 47 | 48 | The Turing-complete "core" language is Unseemly's user interface. 49 | 50 | There are separate files 51 | for defining the types themselves ("core_type_forms.rs") 52 | and for defining syntax (un)quotation ("core_qq_forms.rs"). 53 | 54 | Pipeline: 55 | ⋮ ⋮ ⋮ 56 | ↱ `Ast`→ [TypeSynth] →`Ok(_)` 57 | → [Expand] →`Ast`→ [Evaluate] →`Value`→ [Reflect] ↩ (phase 2) 58 | ↱ `Ast`→ [TypeSynth] →`Ok(_)` 59 | → [Expand] →`Ast`→ [Evaluate] →`Value`→ [Reflect] ↩ (phase 1) 60 | `&str`→ [Parse] →`Ast`→ [TypeSynth] →`Ok(_)` 61 | → [Expand] →`Ast`→ [Evaluate] →`Value` (runtime) 62 | 63 | [Parse] grammar.rs and earley.rs 64 | The parser has to parse arbitrary grammars (`FormPat`s). 65 | Furthermore, it must be able to 66 | extend the grammar it is currently parsing 67 | (it does this by extending `SynEnv`), 68 | and generate new `Form`s. 69 | Extending the grammar may entail invoking the rest of the pipeline 70 | (Typesynth and Evaluate), 71 | the timing of which should be controlled by phasing. 72 | The parser is scannerless, because we need to change tokenization 73 | when we change grammars sometimes. 74 | 75 | [TypeSynth] ty.rs 76 | Type synthesis. 77 | 78 | It should be possible to derive type synthesis for macros! 79 | Synthesize a type for the macro body, and it'll tell you what you get. 80 | I think. 81 | If not, there's not much point to making this language. 82 | 83 | [Expand] expand.rs 84 | Recursively expand macros. 85 | Because macros are identified by their syntax (by the parser), there's no environment. 86 | 87 | [Evaluate] eval.rs 88 | Evaluation is a tree traversal, surprisingly similar to type synthesis. 89 | 90 | Maybe at some point, this will be translation into some other language 91 | (like Ocaml? Rust? LLVM?), 92 | but it may be faster to interpret 93 | the many small programs that macros are. 94 | 95 | [↱ Reflect ↩] reify.rs 96 | Macros are programs that manipulate `Ast`s 97 | (they can also access environments). 98 | We need to reflect internal `Ast`s into Unseemly values 99 | and reify them back out. 100 | 101 | Fun fact: `Ast` transitively includes 102 | almost every type in the compiler. 103 | So we use a macro (see reification_macros.rs) 104 | to generate the reification/reflection, 105 | rather than do it by hand. 106 | 107 | 108 | 109 | Important data structures: 110 | 111 | `Name` name.rs 112 | Will contain hygiene metadata, eventually 113 | 114 | `TokenTree` read.rs 115 | The result of reading; a tree with nodes at every ()[]{} 116 | 117 | `FormPat` parse.rs 118 | Grammar for forms (core and user-defined) 119 | 120 | `Form` form.rs 121 | Everything one needs to know about a language form. Contains: 122 | * `grammar : FormPat` [Parse] -- grammar (how we know the user used the form) 123 | Also contains: 124 | * how things should bind 125 | * grammar "binding" information (syntax extension) 126 | * `synth_type : WalkRule` [TypeSynth] -- typechecking rule 127 | * `eval : WalkRule` [Evaluate] -- evaluation rule 128 | * `quasiquote : WalkRule` [Evaluate] -- trivial, except for unquotation 129 | * TODO [misc] -- pretty-printing guidance 130 | 131 | `Ast` ast.rs 132 | Syntax, in its logical structure. 133 | 134 | `Beta` beta.rs 135 | How terms bind. Surprisingly important! 136 | 137 | `SynEnv` parse.rs 138 | Indicates what the grammar is at a particular point. 139 | [Map from [named grammar nodes] to `FormPat`] 140 | 141 | `EnvMBE` util/mbe.rs 142 | Ergonomic (...in a sense) representation for the contents of `Ast` nodes 143 | which have parts that can repeat `n` times. 144 | 145 | Other imporant things: 146 | 147 | alpha.rs has α-equivalence utilities, used by `ast_walk.rs` 148 | 149 | 150 | 151 | Questions: 152 | 153 | What if a macro like Rust's `try!` 154 | has a type error in its return value? 155 | What happens? What should happen? 156 | -------------------------------------------------------------------------------- /syntax_bikeshed: -------------------------------------------------------------------------------- 1 | We should have a general idea of a theme for each of [] () and {}. 2 | 3 | Perhaps the on-both-sides thing is too cute. :(sad): 4 | 5 | lambda: 6 | [x : Num -> x + 1] 7 | `[x : Num -> x + 1]` 8 | \ x : Num . x + 1 9 | ^[x : Num -> x + 1]^ 10 | .[x : Num -> x + 1]. 11 | ^ x : Num -> x + 1 12 | .[x : Num . x + 1]. 13 | -[x : Num]- x + 1 14 | ->[x : Num]-> x + 1 15 | x : Num ->[x + 1]-> 16 | 17 | quick lambda: 18 | (Assuming we can get inference to work. Otherwise, maybe `the Num` or `_ : Num` instead) 19 | [_ + 1] 20 | _[_ + 1]_ 21 | -> _ + 1 22 | 23 | type parameterization: 24 | List @ Num 25 | List <[Num]< ...fish X-ray! 26 | @[List Num]@ 27 | t[List Num]t 28 | List Num and Map (String Num) 29 | Num List and (String Num) Map ...Englishy! 30 | 31 | syntax quotation: 32 | '[,[e], + 1]' and ''[,[ e ], + ,,[e],,]'' 33 | +[-[e]- + 1]+ 34 | '[ ...[ ,[e], + 1 ]... ]' ...but how do we escape it? 35 | '[ *[ ,[e], + 1 ]* ]' 36 | '[ ,,...[e],,... ]' '[ ,[ (dotdotdotsyntax '[e]')], ]' 37 | (turns out we need to name our nonterminals) 38 | '[Expr | ,[e], + 1]' 39 | E[,[e], + 1]E 40 | 41 | form definition: 42 | (Gosh, there's a lot of stuff in here...) 43 | '[(lambda ( ,{x : Name}, : ,{t : Type}, ) ,{e : Expr @ S <-- (x : t)}, )]' 44 | '[(extend-syntax ,{f : Forms}, ,{body : T <-syn- f})]' 45 | '[(begin-for-syntax ,{+phase e : Expr},)]' 46 | 47 | function invocation: 48 | (map [the Num + 1] lst) ...Schemey! 49 | map ([the Num + 1] lst) ...Cy! (but with an extra space) 50 | map [the Num + 1] lst ...MLy! 51 | map [the Num + 1] over lst ...Smalltalky! 52 | 53 | macro types: 54 | 55 | (Is the concrete syntax part of the type? We may need it to be, for `...` to work) 56 | ∀ ...{T}... . ∀ S . 57 | '[let ...[ ,[ var ⇑ v ], = ,[ expr<[T]< ], ]... 58 | in ,[ expr<[S]< ↓ ...{v = T}...], ]' 59 | -> expr<[S]< 60 | 61 | 62 | '[letrec 63 | ...[ ( ,[ var ⇑ v], ...[ ,[ var ⇑ arg], ]... ) 64 | = ,[ expr <[T]< ↓ ...{arg}...], ]... 65 | in ,[ expr <[S]< ], ]' 66 | 67 | 68 | We should try to have types and expressions look different. 69 | One tradition is for types to be capitalized and expressions to be lowercase. 70 | Perhaps that means that types generally get "bigger" operators? 71 | -[x : Int -> x]- : =[Int => Int]= 72 | ~[the Int]~ : =[Int => Int]= 73 | 74 | We also need [](){} groupers for a lot of different purposes: 75 | syntax quotation, syntax unquotation, lambda, possibly function application 76 | forced precedence in expressions, 77 | forced precedence for syntax operations (maybe just `...`), 78 | forced precedence for betas (maybe just `...`), 79 | function types, type precedence (maybe just type abstraction invocation), 80 | enum/struct types, 81 | literal lists/maps/sets, 82 | ...probably more kinds of quotation, 83 | 84 | One natural allocation is [] for sequences in which order matters, {} for sets, 85 | and () for forcing precedence. 86 | An immediate problem with that is that {} will hardly ever get used. 87 | 88 | We could use `[]` for "level shifts" (i.e. literals, quotation, lambdas), 89 | `()` for precedence, and `{}` for... everything else? 90 | -------------------------------------------------------------------------------- /web_ide/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.d.ts 3 | libunseemly.js 4 | README.md 5 | package.json 6 | LICENSE -------------------------------------------------------------------------------- /web_ide/GETTING_STARTED.md: -------------------------------------------------------------------------------- 1 | ## The Web IDE for Unseemly 2 | 3 | The Unseemly Web IDE is totally client-side; 4 | you can host it as a set of static files. 5 | It's not hard to set up! 6 | 7 | To build the Wasm version of the compiler, 8 | first switch to the root of where you checked out the Unseemly repo 9 | (or change the `--out-dir` argument below). 10 | Then: 11 | 12 | ``` 13 | cargo install wasm-pack 14 | wasm-pack build --target no-modules --out-dir web_ide/wasm/ --no-typescript --release 15 | ``` 16 | 17 | This will create files in `web_ide/wasm/`, but they will be `.gitignore`d. 18 | 19 | To test that it worked, load `ide.html`. 20 | You can't use a `file://` URL; 21 | you'll need to use a local webserver like 22 | the Live Server extension in VS Code 23 | or `python3 -m http.server`. 24 | or `npm i local-web-server` and then `ws`. 25 | 26 | Then, edit `config.js` to configure the IDE. 27 | For example, 28 | `base_language = "newlang.unseemly"` and `starter_file = "example.newlang"` 29 | to make an IDE for the language defined in `newlang.unseemly`. 30 | (Files that you reference need to be in this directory.) 31 | 32 | To publish, host the files together (you can delete the `.gitignore` and the human-readable files). 33 | One option is to use [GitHub Pages](https://pages.github.com/). -------------------------------------------------------------------------------- /web_ide/config.js: -------------------------------------------------------------------------------- 1 | 2 | // Dynamic Highlighting attempts to respect in-file syntax extensions. 3 | // It's janky and slow 4 | // (it's implemented by, every few seconds, re-parsing the file 5 | // (which involves running phase-1+ code), 6 | // and finding long lines in the syntax extension 7 | // to mark where the language changes), 8 | // but it makes for a cool demo! 9 | dynamic_highlighting = true; 10 | 11 | // Start by editing this file: 12 | starter_file = "example.newlang"; 13 | 14 | // If empty, Unseemly. Otherwise, the URL of the language to use 15 | // for execution and syntax-highlighting purposes. 16 | base_language = "newlang.unseemly"; 17 | -------------------------------------------------------------------------------- /web_ide/example.newlang: -------------------------------------------------------------------------------- 1 | # Hi! 2 | 3 | let many = (plus one two) ; in 4 | (times many many) -------------------------------------------------------------------------------- /web_ide/example.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | DefaultSeparator ::= alt[ /(#[^\n|][^\n]*)/ as comment 3 | /\s+/ ]alt * ; 4 | in 5 | # This file demonstrates how to bootstrap a (somewhat more) useable language in Unseemly. 6 | # You should copy it and play around (it's under the MIT license). 7 | 8 | # Unseemly doesn't have comments, 9 | # so the above `extend_syntax` added Python-style #-to-end-of-line comments. 10 | # In the base Unseemly grammar, `DefaultSeparator is matched before each token, 11 | # but only matches whitespace `/\s*/`, 12 | # so when we changed it out, we made it possible to put comments anywhere. 13 | 14 | # After the `in`, the changes to the language take effect, so we can use comments! 15 | # But there's a lot more work to do... 16 | extend_syntax 17 | ### Introduce a simple let-binding expression. 18 | # Using `::=also` in a grammar definition extends, rather than overwrites, 19 | # the definition of `Expr`: 20 | Expr ::=also 21 | # The new form is a macro, and there are two unknown types, which we'll call `T` and `S`: 22 | forall T S . '{ [ 23 | # When specifying a literal, one also provides a nonterminal responsible for lexing. 24 | # `DefaultToken` is just `DefaultSeparator` followed be a lexer 25 | # that matches some nonwhitespace characters. 26 | lit ,{ DefaultToken }, = 'single_let' 27 | # We allow arbitrary patterns, not just variable names, in part to be nice 28 | # ...and in part because `Atom` is a weird special case 29 | # that Unseemly can't interpolate properly in a lot of cases ) : 30 | pat := ( ,{ Pat }, ) 31 | lit ,{ DefaultToken }, = '=' 32 | value := ( ,{ Expr }, ) 33 | lit ,{ DefaultToken }, = ';' 34 | lit ,{ DefaultToken }, = 'in' 35 | # The body of the `single_let` is an expression in which `pat` binds its names. 36 | # A crucial feature for type safety in Unseemly 37 | # is that bindings are specified in macro grammars (with `<--`). 38 | # Both `pat` and `value` are syntax; the `=` says that 39 | # we should use the names in `pat`, assuming `pat` has the same type as `value`. 40 | body := ( ,{ Expr }, <-- ...[pat = value]... ) 41 | ] }' single_let_macro -> .{ 42 | # Now for the actual implementation of the single_let macro. 43 | # We use syntax quotation (`'[NonterminalName| ]'`) to produce a `match` statement. 44 | # and `,[value],` is unquotation; it interpolates the expression `value`. 45 | # A very similiar process builds a pattern that matches all of those values, 46 | # for binding inside `body`. 47 | '[Expr | match ,[value], { ,[pat], => ,[body], } ]' 48 | }. ; 49 | in 50 | single_let eleven = (plus ten one) ; # use it! 51 | in 52 | extend_syntax 53 | ### Introduce `let` expressions. 54 | # Like `single_let`, but this can bind multiple patterns at once: 55 | Expr ::=also 56 | forall T S . '{ [ 57 | lit ,{ DefaultToken }, = 'let' 58 | # After `let` comes an arbitrary number of bindings. 59 | # `S` will be a tuple type, each of whose elements is the type of one arm. 60 | [ 61 | pat := ( ,{ Pat }, ) 62 | lit ,{ DefaultToken }, = '=' 63 | value := ( ,{ Expr }, ) 64 | lit ,{ DefaultToken }, = ';' 65 | ] * 66 | lit ,{ DefaultToken }, = 'in' 67 | # The `...[ ]...` is necessary because `pat` and `value` match multiple times. 68 | body := ( ,{ Expr }, <-- ...[pat = value]... ) 69 | ] }' let_macro -> .{ 70 | # We use `**[ ]**` to build a tuple literal: 71 | # `...[,value, >> ]...` walks over the elements of `value`, 72 | # (underneath it, `value` is a single expression). 73 | '[Expr | 74 | match **[...[,value, >> ,[value], ]... ]** 75 | { **[...[,pat, >> ,[pat],]... ]** => ,[body], } ]' 76 | }. ; 77 | in 78 | extend_syntax 79 | ### Introduce `for` loops 80 | # Invokes `body` once per element in `seq`, which must be of type `Sequence`. 81 | # This macro will use our existing `let` macro. 82 | Expr ::=also forall T . '{ [ 83 | lit ,{ DefaultToken }, = 'for' 84 | pat := ( ,{ Pat }, ) 85 | lit ,{ DefaultToken }, = 'in' 86 | seq := ( ,{ Expr> }, ) 87 | # This uses `:` instead of `=`, because `T` is a type. 88 | # `body` returns `Unit`; it's just being invoked for side-effects. 89 | body := ( ,{ Expr }, <-- pat : T ) 90 | ] }' for_loop -> .{ 91 | '[Expr | 92 | # Unseemly uses Lisp-style function application (function name inside the parens). 93 | # `**[]**` is an empty tuple (the only value with type `Unit`). 94 | # `.[ ].` is a lambda (this one has two arguments, `unit`, and `arg`). 95 | # `prefab_type T` is an expression that produces *syntax* for the type `T`. 96 | # (Oddly, syntax for the type `T` has the type `Type`. It works, though!) 97 | (foldl ,[seq], 98 | **[]** 99 | .[unit : Unit arg : ,[prefab_type T], . 100 | let ,[pat], = arg ; in ,[body], 101 | ]. ) 102 | ]' 103 | }. ; 104 | in 105 | extend_syntax 106 | ### Add numeric literals. 107 | Expr ::=also 108 | # This macro doesn't have any unknown types, 109 | # so the `forall` that's part of the syntax looks a little weird. 110 | forall . '{ 111 | # The awkward `pick` construction here is so that these literals 112 | # benefit from the standard whitespace/comment consumption. 113 | # We have to call `DefaultSeparator`, but then we have to throw it out 114 | # and just look at `tok`, the actual digits. 115 | digit_string := (pick tok in 116 | [,{DefaultSeparator}, tok := (/([0-9]+)/ as constant.number) ]) 117 | }' base_ten_literal -> .{ 118 | # All our previous macros immediately expanded to 119 | # a syntax quotation with a few trivial interpolations. 120 | # This one will actually execute some nontrivial code to decide what to expand to. 121 | # Here we use the `let` (as opposed to just expanding to it). 122 | # Also, we demonstrate Unseemly's highly-non-ergonomic side-effect features: 123 | # `new_cell` creates a mutable value. `assign` and `value` write and read it. 124 | # `-{ }-` is a block, for sequencing operations. 125 | let number = (new_cell zero) ; in 126 | -{ 127 | for digit in (string_to_sequence (ident_to_string digit_string)) 128 | (assign number (plus (times (value number) ten) 129 | # The character for digit 0 is codepoint 48: 130 | (minus digit (plus (times ten four) eight)))) ; 131 | (prefab (value number)) 132 | }- 133 | }. ; 134 | in 135 | extend_syntax 136 | ### Introduce `letfn` for defining 2-argument functions. 137 | # Currently, one has to make a separate macro for each number of arguments, 138 | # because `...[]...` can only be put around a whole expression/pattern/type (issue #38). 139 | Expr ::=also 140 | forall I0 I1 O T . '{ [ 141 | lit ,{ DefaultToken }, = 'letfn' 142 | lit ,{ OpenDelim }, = '(' 143 | fn_name := ( ,{ Pat< [I0 I1 -> O] > }, ) 144 | arg_name0 := ( ,{ Pat }, ) 145 | lit ,{ DefaultToken }, = ':' 146 | arg_type0 := ( ,{ Type }, ) 147 | arg_name1 := ( ,{ Pat }, ) 148 | lit ,{ DefaultToken }, = ':' 149 | arg_type1 := ( ,{ Type }, ) 150 | lit ,{ CloseDelim }, = ')' 151 | lit ,{ DefaultToken }, = '->' 152 | ret_type := ( ,{ Type }, ) 153 | lit ,{ DefaultToken }, = '=' 154 | # `[ o> ]` means "bind the LHS, then bind the RHS". 155 | fn_body := ( ,{ Expr }, <-- [ arg_name0 = arg_type0 o> arg_name1 = arg_type1 ] ) 156 | lit ,{ DefaultToken }, = ';' 157 | lit ,{ DefaultToken }, = 'in' 158 | body := ( ,{ Expr }, <-- fn_name = [ arg_type0 arg_type1 -> ret_type ] ) 159 | ] }' let_fn2 -> .{ 160 | # In case the function is recursive, wrap it in `fix`: 161 | '[Expr | let ,[fn_name], = (fix 162 | .[ again: [ -> [ ,[arg_type0], ,[arg_type1], -> ,[ret_type], ] ] . 163 | .[ a0: ,[arg_type0], a1: ,[arg_type1], . 164 | # Workaround for not being able to interpolate atoms: 165 | let ,[arg_name0], = a0; ,[arg_name1], = a1; in ,[fn_body], ]. 166 | ].) ; in 167 | ,[body], 168 | ]' 169 | }. ; 170 | in 171 | let eleven = (plus 10 one) ; 172 | in 173 | letfn (plusplusplus a: Int b: Int) -> Int = (plus a b) ; 174 | in 175 | (plusplusplus eleven one) 176 | -------------------------------------------------------------------------------- /web_ide/ide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Unseemly Web IDE 6 | 7 | 48 | 49 | 50 | 51 |
52 |
(plus one one)
53 |
54 |
55 | 56 | Use Ctrl-E to execute. Use Ctrl-, to open settings. Use Ctrl-D to 57 | multi-select. Use F1 to activate the command palette. 58 |
59 |
60 | 61 | 63 | 65 | 66 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /web_ide/newlang.unseemly: -------------------------------------------------------------------------------- 1 | extend_syntax 2 | DefaultSeparator ::= alt[ /(#[^\n|][^\n]*)/ as comment 3 | /\s+/ ]alt * ; 4 | in 5 | # Now we've got comments! 6 | extend_syntax 7 | # Introduce `let` expressions. 8 | Expr ::=also 9 | forall T S . '{ [ 10 | lit ,{ DefaultToken }, = 'let' 11 | [ 12 | pat := ( ,{ Pat }, ) 13 | lit ,{ DefaultToken }, = '=' 14 | val := ( ,{ Expr }, ) 15 | lit ,{ DefaultToken }, = ';' 16 | ] * 17 | lit ,{ DefaultToken }, = 'in' 18 | body := ( ,{ Expr }, <-- ...[pat = val]... ) 19 | ] }' let_macro -> .{ 20 | '[Expr | 21 | match **[...[,val, >> ,[val], ]... ]** 22 | { **[...[,pat, >> ,[pat],]... ]** => ,[body], } ]' 23 | }. ; 24 | in 25 | capture_language -------------------------------------------------------------------------------- /web_ide/unseemly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulstansifer/unseemly/dd1f55be7b09b741a25af954c7a902eeac00a0c2/web_ide/unseemly.png -------------------------------------------------------------------------------- /web_ide/unseemly_worker.js: -------------------------------------------------------------------------------- 1 | importScripts('wasm/libunseemly.js') 2 | 3 | const { html__eval_program, generate__ace_rules, generate__ace_rules__for, stash_lang } 4 | = wasm_bindgen; 5 | 6 | async function run() { 7 | await wasm_bindgen('./wasm/libunseemly_bg.wasm'); 8 | 9 | self.addEventListener('message', function (msg) { 10 | var result = "[error]"; 11 | try { 12 | if (msg.data.task == 'execute') { 13 | result = html__eval_program(msg.data.program, msg.data.lang); 14 | } else if (msg.data.task == 'stash') { 15 | stash_lang(msg.data.store_as, msg.data.program, msg.data.lang); 16 | result = '[done]'; 17 | } else if (msg.data.task == 'ace_rules') { 18 | if (msg.data.program) { 19 | result = generate__ace_rules__for(msg.data.program, msg.data.lang) 20 | } else { 21 | result = generate__ace_rules(msg.data.lang) 22 | } 23 | } 24 | } finally { 25 | self.postMessage({ task: msg.data.task, output: result }); 26 | } 27 | }) 28 | 29 | self.postMessage({ task: "startup" }) 30 | } 31 | 32 | run(); 33 | --------------------------------------------------------------------------------