390 Commits
v0.0.3 ... main

Author SHA1 Message Date
55e02fd919 conlang: updated rustfmt 2025-10-24 05:23:53 -04:00
4e4c61ee4f repline: Goodbye, old friend. You have been promoted. 2025-10-24 03:44:11 -04:00
8d641d0060 cl-repl: Flatten menu structure so new friendo won't have to ^C^C
Plus it was more annoying than this is.
2025-10-23 06:06:56 -04:00
06ed0eae54 repline: Make begin and again post-customizable, and once again fall out of sync 2025-10-23 06:05:24 -04:00
e0eb0d5a02 cl-interpret: add type metadata to tuple variants 2025-10-23 05:05:34 -04:00
d62656c615 interpret: Properly scope enum variants! 2025-10-22 14:55:49 -04:00
acc3ed12b3 fstring.cl: update for conlang-ng 2025-10-22 13:13:09 -04:00
722036dfbb sample-code: link-the-docs using Modern™️ terminal escape sequences 2025-10-22 13:12:55 -04:00
4a2cd9303e repline: stop clobbering the input buffer every time you can't figure out an escape sequence 2025-10-22 13:11:41 -04:00
35edfdbb17 Add config.toml so other people can build 2025-10-19 19:29:24 -04:00
b54826cdd5 sample-code: update for new WIP syntax (it's more persnickety) 2025-10-19 19:27:09 -04:00
6b24980fc7 engine: borrow the bset and rset instead of RCing them 2025-10-19 18:56:32 -04:00
55324af358 populate: use visitor children, and don't mark generics in impls yet. TODO: this is nonsense. 2025-10-19 18:55:50 -04:00
df9973b119 cl-interpret: Change struct layout, add rudimentary operator overloading 2025-09-29 11:48:30 -04:00
f41e5fc49a conlang: add lang items, remove Empty, and shuffle typeck 2025-09-15 10:45:14 -04:00
ead1f351a7 conlang-run: Add Display-formatted errors here too. 2025-09-15 03:54:24 -04:00
986bac9e6b sample-code/calculator: Bit-shifts and better formatting 2025-09-15 00:24:02 -04:00
02239c5ce4 sample-code/ascii: fix reference depth for postfix-call in_range function 2025-09-15 00:22:38 -04:00
1f9d32f972 cl-lexer: Make strings curly-brace-aware, for future format string work 2025-09-15 00:21:14 -04:00
8dd2920fca cl-repl/cli: Pretty-print errors in non-repl mode 2025-09-15 00:20:05 -04:00
df6089f84a cl-interpret: Print function call backtrace on panic 2025-09-15 00:18:51 -04:00
62940b3d24 conlang: More clippy 2025-09-15 00:17:50 -04:00
1fe796dda7 cl-lexer: Bring over new lexer
- Different flow, similar action
- Consolidated all the single-purpose di- and trigraph functions
- Gave the lexer explicit access to its entire string, which is can slice.
2025-09-14 23:02:04 -04:00
f0c871711c compiler: updated to rust 1.84, now we have let chains! 2025-09-14 19:08:59 -04:00
fcab20579a cl-structures/intern: Debug-print interned objects with custom sigil 2025-09-14 19:02:55 -04:00
239785b322 cl-interpret: Literal[String] a la python 2025-07-20 15:57:11 -04:00
259c9f8bb6 repline: update Crossterm dependency to 0.29.0 2025-07-18 05:40:20 -04:00
c9ffeaddce conlang: bump version number to 0.0.10 2025-07-18 05:37:23 -04:00
12daf35c07 .gitignore: Ignore cl-typeck table dumpfile 2025-07-18 05:37:09 -04:00
6a0607b93a sample-code: Add a super hacky format-string implementation using eval and fmt builtins 2025-07-18 05:36:38 -04:00
4f40bd4f99 sample-code: Remove "Student" function from match_test 2025-07-18 05:35:57 -04:00
2f94ddd23f repline: QoL improvement: inserting newline works! 2025-07-18 05:35:00 -04:00
0f9044bb3e stdlib: Inference engine caught some type errors! Also added some hot garbage. 2025-07-18 05:34:03 -04:00
8732cca3f9 cl-typeck: Get some serious type inference going! 2025-07-18 05:30:23 -04:00
74220d3bff cl-lexer: Add base-36 literals, lmao 2025-07-18 05:29:31 -04:00
8b0a122dfc cl-interpret: Environment/stack overhaul + Ref patterns 2025-07-18 05:29:10 -04:00
e165e029dc cl-embed: Calculator example update! 2025-07-18 05:26:39 -04:00
148ef34a01 ast: add gens for ty and impl, raw ptr types, make fn return value non-optional 2025-07-18 05:25:35 -04:00
6ba62ac1c4 repline: Refactor for fewer redraws and more user control.
Definitely has bugs, but eh!
2025-06-17 00:43:21 -04:00
ae026420f1 conlang: Fix rustdoc warnings 2025-06-17 00:43:21 -04:00
d80f2f6315 cl-embed/examples: rename conculator back to calculator, since MI searches by path 2025-05-19 01:23:23 +00:00
89ed9b2a39 test.cl: Never-like enums tempermanently(?) removed 2025-05-18 11:51:22 -04:00
47608668fa cl-embed: Add an example, and a new sample-code (same file) 2025-05-18 11:50:33 -04:00
6ce27c522f cl-embed: Create an (unstable) API for embedding Conlang in your projects! 2025-05-18 11:49:43 -04:00
233e4dab4e visit: docs cleanup 2025-05-18 11:48:22 -04:00
f95c6ee239 cl-interpret: Add convenience function for binding variables 2025-05-18 11:48:04 -04:00
964917d2f0 cl-interpret: let now returns bool
This makes debugging monumentally harder, but it's SO NEAT and instantly adds `if let`/`while let` and `let chaining`
2025-05-18 11:47:13 -04:00
6bb855cff7 cl-parser: semantics changes
allow let in conditionals (restricts init of let to non-assignment)

allow semicolons after items at file scope
2025-05-18 11:44:44 -04:00
124bb2f680 cl-interpret: Builtins! builtins(), chars(str), and panic(msg) 2025-05-18 11:42:33 -04:00
08b5937fc2 cl-typeck: Twiddle with infer (maybe this will help fix the weird behavior?) 2025-05-18 11:39:54 -04:00
ccfa4c7723 cl-interpret: copy-capture closures 2025-05-18 11:30:17 -04:00
d3e20e53d0 cl-interpret: Unit tests for while-else control flow 2025-05-18 04:01:50 -04:00
e08bf57dc1 cl-typeck: Give inference its own result type 2025-05-18 04:01:13 -04:00
a5590168ee cl-parser: Misc cleanup 2025-05-18 04:00:43 -04:00
3e2063835b cl-parser: Dedicated parsing logic for patterns! 2025-05-18 04:00:00 -04:00
e6156343c3 cl-ast: Add inline closure expressions 2025-05-18 03:57:20 -04:00
6c6d2d04a7 cl-ast: Remove variantkind, as it was redundant 2025-05-17 21:28:12 -04:00
a023551d9f repline: Change newline behavior when not at end of input
TODO: This screws with the naming convention of home()/end()
2025-05-17 20:32:24 -04:00
dc1c9bdd6d stdlib: Add map* for Option and Result 2025-05-05 05:32:23 -04:00
c5e817f1e5 cl-ast: Rearrange 2025-05-05 05:26:43 -04:00
6108d66b0a stdlib: Add Option and Result types 2025-05-05 04:55:17 -04:00
09fdb14d79 cl-typeck: Progress population and categorization 2025-05-05 04:54:33 -04:00
4228324ab3 cl-typeck: With super semantics redone, search within self for items 2025-05-05 04:20:40 -04:00
f5f905cd70 cl-ast: more generic impls for weight_of
TODO: get rid of weight_of
2025-05-05 04:19:23 -04:00
883387aaf1 yaml: formatting 2025-05-05 04:18:49 -04:00
2d706ff582 desugar: Add primitive constant folding 2025-05-05 04:18:16 -04:00
cd2e3c3e32 cl-parser: change match parse slightly 2025-05-05 04:16:37 -04:00
8c23aea4af to_c: oops 2025-05-05 04:14:30 -04:00
d6c0a6cf1b cl-ast: Allow c-like enums to take an expr 2025-05-05 02:22:50 -04:00
fc80be5fcc conlang: Remove "self" keyword 2025-05-05 02:20:47 -04:00
7c2dd1468b cl-ast: Finally figure out how visit and walk are supposed to work 2025-05-05 00:24:52 -04:00
4747b65414 stdlib: Add Result and Option types
Since the type checker sucks less now, we can think about maybe
adding some features to the language.

...At some point I'd like to have the type checker clean up
the index map.
2025-04-22 08:00:59 -04:00
8ff17fd475 cl-ast: Add syntax support for generics 2025-04-22 07:22:44 -04:00
681fbc88d3 cl-typeck: More type inference
- Renamed "intrinsic" -> "primitive"
  - I thought it was funny, but it's just annoying.
- Rename "Uninferred" -> "Inferred"
  - It's a concrete type, but an inferred one.
- Add lifetimes to the Entry API
- Categorize does not care about recursive types. You can't have a recursive AST.
- Added helpful constructors to the inference engine
- Added some overloadable operators to the inference engine
  - These are a MAJOR work in progress
- Reorganized the Inference implementation file by functionality.

stdlib:
- Updated standard library.
2025-04-22 06:33:57 -04:00
7cf485fade cl-typeck/infer: Fix some inference errors
yay yippee type checking and inference woohoo
i am very tired
2025-04-21 05:37:34 -04:00
3b96833fcb cl-typeck: Early type inference for let 2025-04-21 04:52:59 -04:00
65b75f95ce cl-repl: Oops, add dependencies. 2025-04-21 04:27:17 -04:00
4c4b49ce00 cl-typeck: Implement a framework for type inference
...And also implement a bunch of the inference rules, too.
2025-04-21 04:26:07 -04:00
7ba808594c cl-ast: Cleanup
- Function bind is now one Pattern
- TyRef now allows &Ty (i.e. &[i32], &(char, bool)
- Range patterns (they cannot bind, only check whether a value is in range
- ArrayRep repeat has been reverted to usize, for now, until early consteval is implemented.
2025-04-21 04:17:45 -04:00
ef92d8b798 examples: Adapt examples/yaml to a broken C generator! woo! 2025-04-16 04:13:39 -04:00
8c8f1254e0 examples/yaml: Remove unnecessary uses of todo!() 2025-04-16 04:13:04 -04:00
82e62ab4ac cl-parser: Dereference first(?) 2025-04-15 23:44:56 -04:00
b09a610c6c interpreter: Include location in error type 2025-04-15 23:42:21 -04:00
fa5244dcf9 cl-interpret: References, part 3
MODS ARE ASLEEP
POST FAT BEAGLE
2025-03-14 08:33:46 -05:00
9b460afed4 cl-interpret/pattern: Fail on mismatch again 2025-03-14 07:00:10 -05:00
27d1d07ed8 conlang: Update to Rust Edition 2024 2025-03-14 06:09:39 -05:00
68e676eda4 repline: Clippy lints unbuffered readers now 2025-03-14 06:09:00 -05:00
c988193049 cl-structures/stack: constify all (most of) the things! 2025-03-14 06:07:48 -05:00
2ecb2efc09 cl-structures: IndexMap::get_disjoint_mut to match Rust 1.86 🎉 2025-03-14 04:35:47 -05:00
a4176c710e cl-ast: Add filename to File
- Better error reporting
- Better pizza
- Papa Cow's
2025-03-14 04:11:22 -05:00
cdb9ec49fe cl-ast: Estimate the "weight" of an AST, for debugging? 2025-03-14 04:02:14 -05:00
6d33c4baa9 cl-ast: Break ast-impl into submodules 2025-03-14 03:58:40 -05:00
33e13425a9 cl-ast: Clean up "to", "extents", Module."kind" 2025-03-14 00:52:43 -05:00
11c8daaed0 cl-ast: Re-add(?) the Infer type-pattern 2025-03-12 01:20:58 -05:00
584207fc8c cl-structures::Interned: Change to_ref() from assoc. function to member function
(it's so much nicer)
2025-03-12 01:16:51 -05:00
dcdb100a8a cl-interpret: Try having separate globals again? 2025-03-11 23:33:49 -05:00
fdf076c272 cl-ast: Remove Option-like "*Kind"s 2025-03-11 23:32:58 -05:00
2fc847fff2 Listen to the truthmachine!
Scream your falsehoods.
2025-03-11 05:03:52 -05:00
4bc088f277 cl-interpret: Pure value stack v1, references v2
References actually work! :D
They can also be stale :(
2025-03-11 05:01:49 -05:00
06bcb6b7c6 interpret: try out Ref == RcRefCell. Subject to change! 2025-03-11 01:31:02 -05:00
7e311cb0ef conlang: RIP THE EXPRKIND BANDAGE OFF
cl-ast: No more bare ExprKind: every Expr has a Span
cl-interpret: Give errors a span
cl-repl: Print eval errors in load_file, instead of returning them. These changes are relevant.
2025-03-11 00:36:42 -05:00
c0ad544486 cl-interpret/convalue: Destructure with (Rust) pattern matching 2025-02-28 20:36:24 -06:00
fd54b513be cl-interpret/pattern: Doc changes, minor format 2025-02-28 20:35:20 -06:00
adbabc66c5 cl-interpret: impls v1 2025-02-23 04:06:14 -06:00
5c99bf09ab stdlib: Make stdlib not error out w/ undefined symbols, add ranges. 2025-02-23 03:29:15 -06:00
4d9b13f7a1 cl-interpret: Enums, pt 1: C was right the whole time!!1 2025-02-23 03:22:48 -06:00
d9ac9e628d cl-interpret: Stage items within a file in resolution order.
TODO: Does this even help???
2025-02-23 03:21:34 -06:00
632ddf0eab cl-interpret: cleanup 2025-02-23 03:00:00 -06:00
e39b390441 cl-parser, cl-repl: Add ./[mod].cl to module search path 2025-02-23 02:44:26 -06:00
2fd08193fd cl-parser: Promote match scrutinee to position 2025-02-23 02:43:22 -06:00
7d3f189100 conlang: Introduce ..rest Patterns, refactor Ranges 2025-02-23 02:41:41 -06:00
cc6168b55e cl-ast: Remove Param, replace with flat Pattern 2025-02-23 02:01:38 -06:00
e3d94d8949 conlang: Unify binding operations!
This breaks out the pattern matching/unification algorithm from cl-interpret/interpret.rs to cl-interpret/pattern.rs

TODO: pattern destructuring in const, static :^)
2025-02-22 05:16:37 -06:00
7a8da33de9 cl-interpret: Tuple structs + fix tuple member access 2025-02-22 03:31:27 -06:00
697d139cfd conlang: Add Tuple-Struct Patterns
- Patterns are no longer parsed with the highest precedence
- Function calls with just a path and a tuple of args can now be transformed into a Pattern
2025-02-22 01:37:08 -06:00
5d2c714bc1 conlang: Patterns...2!
- Deny arbitrary paths in patterns (only one non-keyword identifier allowed!)
- Allow patterns in for-loop binders (literally useless atm, but it's a step toward making patterns the only way to bind names.)

Next: Functions, Tuple Struct Patterns... And solving the stupid syntactic ambiguity of structors.
2025-02-22 01:00:29 -06:00
b115fea71b cl-interpret: fix import in builtin! doctest 2025-02-21 22:49:14 -06:00
eebabf02fb repline: change error formatting 2025-02-20 22:05:43 -06:00
088cd4d1e4 cl-interpret: Change format of todo for Impl 2025-02-20 22:05:23 -06:00
0fd9c002fc cl-interpret: Interpret items in a file in a particular order
1) mod
2) use
3) enum, struct, type
4) fn
5) impl
6) const | static

This is a stopgap until names are statically resolved
2025-02-20 22:04:27 -06:00
772286eefa conlang: Single-expression functions 2025-02-20 21:59:42 -06:00
3b14186b70 sample-code: Add match_test.cl
Demonstrates pattern matching
2025-02-18 21:53:32 -06:00
a6ad20911d builtins: Add temp builtin for dumping the global string pool 2025-02-18 21:49:59 -06:00
01cf9d93e2 cl-repl: Usability improvements
- Don't print empty
- Don't needlessly append newline to cleared screen
2025-02-18 21:48:36 -06:00
edabbe1655 cl-interpret: process use items and imports in the interpreter 2025-02-18 21:44:52 -06:00
af9c293907 cl-interpret, cl-repl:
Move IO builtins into the CLI, so get_line can use repline keybinds.
2025-02-06 21:35:17 -06:00
0e3ba342c4 cl-interpret: make the error type smaller (at the cost of a heap allocation) 2025-01-31 03:35:35 -06:00
d95d35268e cl-interpret: Builtin refactor
- Everything is different now
  - Builtins are now built on top of Rust functions, so they can be recursive!
  - TODO: allow macro-defined builtins to call each other?
- The builtins! macro is a lot nicer to work with
  - No redundant return value
  - Maps the result over Into::into, allowing for type inference!
  - Uses explicit pattern syntax instead of weird binding, where possible
  - Does not #[allow(unused)], so you'll get unused variable warnings now!
2025-01-31 03:34:45 -06:00
0c2b0002ce conlang: Docs! 2025-01-29 05:07:08 -06:00
3534be5fbc conlang: bump version number for pattern matching and destructured bindings 2025-01-29 04:16:53 -06:00
026681787a cl-interpret: Add a new pretty-debug-printer builtin 2025-01-29 04:16:28 -06:00
80e1219808 cl-interpret: Tests for new pattern matching behavior
TODO: Expand control flow tests
2025-01-29 04:15:57 -06:00
6ee9bbd72e conlang: PATTERN MATCHING AND DESTRUCTURED BINDINGS WOOOOO
- Integrate the Match and Pattern nodes into the AST
  - TODO: `let x: T` is ambiguous with `let x: {}`. Currently the latter takes precedence in the parser.

- Implement pattern matching through unification in the interpreter.
  - It's not fast, but it works!

- Refactor destructuring assignments to use the new pattern functionality
2025-01-29 04:15:33 -06:00
6e94b702c9 cl-ast: Add pattern and match nodes, and associated behaviors 2025-01-29 04:05:11 -06:00
d21683ad61 conlang: Add Quote expression as a hack for testing
Possibly removed later, or replaced with something that turns Conlang AST nodes into Conlang data structures.
2025-01-29 03:56:19 -06:00
518fbe74a1 cl-ast: Fix AddrOf misbehavior 2025-01-29 03:31:24 -06:00
bc955c6409 conlang: Bump version number :D 2025-01-28 06:56:48 -06:00
678c0f952c sample-code: Add demo for get_line() 2025-01-28 06:56:30 -06:00
86c4da0689 cl-repl: Don't print ConValue::Empty return values 2025-01-28 06:55:03 -06:00
5db77db6b8 cl-interpret: Use dyn dispatch for iterators.
- This is a hack because the language has the syntax but no concept of iterators
2025-01-28 06:25:04 -06:00
145a24c5ff cl-interpret: Assignment 2.0, now with more destructuring!
TODO: Destructuring `let`, destructuring patterns in function args..?
2025-01-28 06:23:37 -06:00
485afb7843 cl-interpret: Make ConValues act like value types
(Aside from the fact that they're smothered in heap allocations)
2025-01-28 06:20:10 -06:00
01871bf455 cl-interpret: get_line builtin! 2025-01-28 06:14:05 -06:00
fd361f2bea cl-interpret: Upvars 2.0
- Only captures locals
2025-01-28 06:13:38 -06:00
0eef6b910c cl-interpret/collect_upvars: elide lifetime 2025-01-23 18:53:07 -06:00
c50940a44c cl-interpret: Make an attempt at closures
(It kinda sucks, but it emulates closures half decently)
2025-01-23 16:23:42 -06:00
3cda3d83d9 typeck: Replace unsafe static mut with tree interning,
I used tree interning here, because the interner already contains the necessary locks to make it Sync, and I was lazy. Be my guest if you want to do something else.

The computational overhead of interning the ASTs here is negligible, since this is all CLI code anyway.
2025-01-16 21:16:46 -06:00
e5a51ba6c2 cl-structures: add to_ref associated function on Interned type, for non-borrowing conversion 2025-01-16 21:01:28 -06:00
883fd31d38 conlang: Elide lifetimes (fixes clippy lint) 2025-01-16 20:57:33 -06:00
d71276b477 cl-structures: Update error type in unstable get_many_mut feature for IndexMap 2025-01-16 20:32:09 -06:00
d8e32ee263 cl-arena: promote to its own independent repository 2025-01-16 20:30:23 -06:00
e419c23769 repline: fix operator precedence bug, clippy lints 2025-01-16 20:29:43 -06:00
1bd9c021dd sample-code: Fix typo in sqrt.cl 2024-11-21 21:57:10 -06:00
df68d6f2e6 cl-interpret/tests: Fix broken test 2024-09-19 14:29:21 -05:00
ae11d87d68 Merge pull request 'Basic floating point support (WIP)' (#18) from floats into main
Reviewed-on: #18
2024-09-19 19:27:51 +00:00
96be5aba6c repline: Add a code sample demonstrating the use of prebaked::read_and 2024-09-19 14:08:50 -05:00
b9f4994930 sample-code: update sqrt.cl to use new float syntax 2024-09-19 14:02:58 -05:00
f4fe07a08b cl-lexer: Hack around ambiguity between 1.0 and 1..0
This requires more than one token lookahead, but is already part of a hack itself, so... /shrug
2024-09-19 14:02:02 -05:00
94be5d787f cl-ast: always pretty-print decimal for floats 2024-09-19 14:00:22 -05:00
5deb585054 cl-ast: Add float support
- Smuggle floats as integers to maintain `eq`
- This is bad, but not terrible for spec-compliant floats. Might have issues with NaN.

cl_parser: Smuggle floats

cl_interpret: unpack smuggled floats in float literal node
2024-09-19 13:20:19 -05:00
56e71d6782 cl-lexer: Add a hacky workaround for float support.
It's disgusting, but better than nothing!
2024-09-19 13:16:27 -05:00
c62df3d8b3 sample-code: Add square root demo 2024-09-18 01:53:04 -05:00
fad28beb05 interpreter: Add float machinery
- operators
- type casting
2024-09-18 01:02:09 -05:00
0f8b0824ac cl-parser: Fix precedence of comparison operators 2024-09-18 00:57:44 -05:00
99a00875a8 cl-intern: Derive Default for StringInterner and TypedInterner 2024-08-24 18:18:22 -05:00
8675f91aca cl-ast: Remove tail from let (it caused more problems that it could've solved) 2024-07-31 03:19:20 -05:00
de63a8c123 cl-parser: Outline precedence parser 2024-07-31 02:55:01 -05:00
533436afc1 cl-parser: Move precedence parser into its own module 2024-07-31 02:48:39 -05:00
1eb0516baf cl-parser: Rearrange to match cl-ast
Also reorder `Let` in the AST
2024-07-31 02:35:41 -05:00
97808fd855 cl-parser: Transliterate to a trait-based parsing implementation
Bump version number.
2024-07-31 01:39:00 -05:00
388a69948e Revert "cl-ast: Unify break, return, and unary expressions"
This reverts commit adb0fd229c.
2024-07-30 22:31:39 -05:00
5e7ba6de24 cl-ast: Improve formatting of blocks and groups 2024-07-30 20:40:22 -05:00
adb0fd229c cl-ast: Unify break, return, and unary expressions 2024-07-30 20:16:07 -05:00
0e545077c6 cl-ast: Remove "Continue" struct 2024-07-30 19:42:28 -05:00
b64cc232f9 cl-ast: Move loop expression into unary exprs (with lowest priority) 2024-07-30 18:21:25 -05:00
b0341f06fd cl-ast: Move let into Expr 2024-07-30 18:02:09 -05:00
a3e383b53f cl-token: Flatten TokenKind into a single enum (wow!) 2024-07-30 16:47:09 -05:00
1b217b2e75 typeck: Add a query for all strings 2024-07-29 15:55:53 -05:00
5662bd8524 cl-structures: (ab)use the Display trait to print a numbered, sorted list of interned strings. 2024-07-29 15:55:12 -05:00
28f9048087 cl-typeck: Fix infer.rs doctests 2024-07-29 15:42:35 -05:00
b17164b68b cl-interpret: Write an example for driving the interpreter 2024-07-29 15:42:05 -05:00
ecebefe218 cl-interpret: Knock those modules free! 2024-07-27 22:47:46 -05:00
fc374e0108 ascii.cl: TODO: throw out the interpreter (EVIL) 2024-07-27 20:00:22 -05:00
4295982876 ascii.cl: Cleanup on aisle "bitwise" 2024-07-27 19:59:35 -05:00
729155d3a4 ascii.cl: Fix type annotations (though they're not yet evaluated in the interpreter) 2024-07-27 19:38:41 -05:00
8c0ae02a71 sample-code/ascii.cl: Make it cooler
- Compute char value of digit
- Substitute C0 control codes for Unicode C0 Control Pictures
- Extend through Unicode Latin-1 Supplement
- Blank out C1 control code range
2024-07-27 19:34:37 -05:00
7f7836877e sample-code: Add shebang comments to samples with a main() function 2024-07-27 18:56:36 -05:00
b2733aa171 cl-interpret/builtin: Add len builtin as a quick hack to write more interesting programs.
This is temporary, I just want a way to get the length of a thing atm.
2024-07-27 18:43:03 -05:00
a233bb18bc cl-lexer: Record the contents of comments 2024-07-27 18:41:50 -05:00
e06a27a5b1 cl-lexer: Treat #!/ | #!\ as a comment 2024-07-27 18:41:18 -05:00
3f5c5480ae cl-interpret: [NOT FINAL] Add unicode-aware O(n) string indexing 2024-07-27 18:04:39 -05:00
53cf71608a sample-code/hex.cl: Fix casting TODO, add to_string_radix function 2024-07-27 17:46:27 -05:00
883c2677d9 cl-parser: Index is NOT a low precedence operator!!! 2024-07-27 17:37:29 -05:00
7d98ef87d5 sample-code: proper type annotations on HEX_LUT, add FIXME for min and max 2024-07-26 06:22:29 -05:00
a188c5b65e hex.cl: make the lut square 2024-07-26 06:17:00 -05:00
872818fe7c sample-code/fib.cl: rename fib-iterative -> fibit (easier to type) 2024-07-26 06:13:59 -05:00
3aef055739 sample-code/ascii: Use as casting to print the entire printable ASCII range 2024-07-26 06:10:59 -05:00
38a5d31b08 cl-ast: Escape string and char literals when pretty-printing 2024-07-26 05:51:20 -05:00
e43847bbd4 conlang: Introduce as casting
Arbitrary primitive type conversion

Currently implemented as jankily as possible in the interpreter, but it works alright™️
2024-07-26 05:26:08 -05:00
a8b8a91c79 sample-code: print->println to match interpreter behavior 2024-07-26 05:13:52 -05:00
695c812bf5 cl-repl: increase jank: first positional arg is main file, remainder are imports 2024-07-26 05:13:06 -05:00
524c84be9e cl_typeck: Add new primitive types (including joking-point numbers) 2024-07-26 03:24:34 -05:00
4096442f75 cl-typeck: Turn ref into a linked list.
This should be fine, since the only thing you can do with a ref is dereference it.
2024-07-26 02:14:41 -05:00
03a4e76292 cl-typeck: rustfmt implement.rs 2024-07-26 00:15:00 -05:00
46a1639990 sample-code: Have fun with random number generators 2024-07-25 23:59:41 -05:00
5ea8039a8a typeck.rs: Update for new stdlib layout; don't hardcode the root stdlib module. 2024-07-25 07:09:12 -05:00
479efbad73 typeck.rs: Add file-loading mode 2024-07-25 07:08:07 -05:00
a462dd2be3 stdlib: Use Conlang module layout 2024-07-25 07:05:57 -05:00
4d6b94b570 conlang: Bump version to v0.0.6
- Major milestone: cl-typeck doesn't suck as much!
2024-07-25 05:56:05 -05:00
fe2b816f27 cl-typeck: Crate-spanning refactor part 2
- Removed all unreferenced files
- Reimplemented missing/nonfunctional behavior
- Added module documentation for most things
  - TODO: item-level docs on Entry(Mut)
- Reparented the stages of Table population into the `stage` module.
  - TODO: rewrite type inference to use only the tools provided by Table.
2024-07-25 05:55:11 -05:00
e19127facc cl-typeck: Crate-spanning refactor
TODO: remove all unreferenced files
TODO: Finish resolving statically known types of values
TODO: Type inference
2024-07-24 18:22:42 -05:00
b7ad285a11 cl-typeck: give Handle accessors for useful attributes 2024-07-24 14:29:27 -05:00
61d8cf8550 stdlib: Update num.cl with eased name resolution restrictions 2024-07-21 06:03:16 -05:00
70872d86f9 cl-typeck: Improve path resolution semantics, and DON'T REPARENT IMPLs
- Perform heirarchical resolution through "transparent" nodes
- Reparenting impls broke relative path traversal entirely. To impl something, it must already be in scope anyway.
- TODO: well-formedness checks?
2024-07-21 06:01:54 -05:00
6bf34fdff6 cl-typeck: Add path-resolution relative to an ID
Great for interactive debugging
2024-07-21 05:57:15 -05:00
9d7ab77999 cl-typeck: Add Handle type
- Holds a DefID and a reference to the Project
- Pretty-prints def signatures
- Use handles when printing types (WIP)

Known issues:
- Printing recursive types recurses forever
- There's some unrelated name resolution stuff going on that needs investigating.

TODO:
- Better name
- HandleMut?
- More interfaces!
- Migrate everything to use handles when oop semantics are easiest
- Reject plain recursive types, and don't recurse through reference types when printing
2024-07-21 05:45:40 -05:00
82b71e2517 cl-typeck: Refactor display for Def.
- Use the FmtAdapter from cl-ast
  - Add a new delimiter-constructing delimit_with function.
2024-07-21 01:46:20 -05:00
46bd44bd99 cl-typeck: Re-name mod key to mod handle, in preparation for future handlization 2024-07-20 18:30:05 -05:00
3511575669 conlang: Add array and slice type syntax 2024-07-20 18:22:50 -05:00
b3d62c09aa conlang: Self is not a type, it's a path to a type 2024-07-17 15:05:52 -05:00
ded100bf71 repline: Document the editor 2024-07-12 16:40:32 -05:00
c9ddebb946 cl-repl/menu: Revert extra newline in banner 2024-07-11 04:44:01 -05:00
15c4b89bce cl-interpret: builtin.rs whitespace changes 2024-07-11 04:43:25 -05:00
aa7612926e cl-interpret: Add format builtin
Might as well add some new features to play around with until I rip 'em all out
2024-07-11 04:42:36 -05:00
fffc370380 sample-code: Expand the capabilities of the sample code 2024-07-11 04:02:44 -05:00
a646a9e521 cl-interpret: VERY rudimentary support for Const and Static 2024-07-11 03:07:56 -05:00
5f57924f23 cl-repl: Perform module inlining before submitting code to the interpreter 2024-07-11 02:50:15 -05:00
d692f6bb80 cl-interpret: Complain, rather than panic, on outlined module 2024-07-11 02:48:35 -05:00
58c5a01312 cl-structures: Clean up IndexMap and fix doctests 2024-07-10 14:56:17 -05:00
16baaa32f1 sample-code: Add an example function to print a number in hexadecimal 2024-07-09 06:16:25 -05:00
3c4d31c473 cl-repl: Run by default, break into menu 2024-07-09 06:15:15 -05:00
d723f7cece cl-interpret: String-building addition
Note: This drastically increases the number of symbols. Yeowch.
2024-07-09 06:14:44 -05:00
b446677eda cl-interpret: Enforce wrapping behavior 2024-07-09 06:13:55 -05:00
0beb121f32 cl-interpret: Change print to print without newline, add new println builtin 2024-07-09 06:13:05 -05:00
6b16c55d97 cl-typeck: Fix doc list breakage 2024-06-22 02:00:47 -05:00
c16dbca55c cl-structures: Remove #[feature(inline_const)], stabilized in Rust 1.79 2024-06-22 01:59:01 -05:00
4c883d87a4 cl-ast: Link to [Meta] in [Attrs] 2024-05-19 16:00:40 -05:00
1c3a56f5b5 misc: Fix broken doc links, remove "pool" from index_map.rs 2024-05-19 15:32:57 -05:00
406bfb8882 cl-interpret: Stop kidding myself, I'll be replacing the interpreter before I get rid of this. 2024-05-19 15:16:22 -05:00
e0f54aea97 cl-structures: Mention IndexMap and MapIndex in the top level doc comment 2024-05-19 15:12:54 -05:00
fa8a71addc cl_structures: Rename deprecated_intern_pool to the more correct name "IndexMap"
Also, reverse the order of generic args, to make them consistent with other map collections
2024-05-19 14:51:14 -05:00
0cc0cb5cfb conlang: Remove "Identifier" node
It never carried any extra information, and got in the way everywhere it was used.
2024-05-19 14:41:31 -05:00
f330a7eaa5 conlang: Split assignment into plain Assign and assign-with-Modify 2024-05-19 14:31:30 -05:00
8d8928b8a8 conlang: Struct, tuple member accesses, member function call syntax
Currently uses UFCS in the interpreter. This may change after type checking?
2024-05-19 13:55:28 -05:00
a033e9f33b conlang: Enable opt-level 1 in dev profile 2024-05-16 15:07:54 -05:00
be81221895 cl-typeck: Move type inference utils into own module 2024-05-16 15:06:59 -05:00
33b7cd3971 cl-structures: Do not #[derive(Eq)] for Interned<T>
Derive places Eq bound on T, which is not true of Interned<T>
2024-05-16 15:04:37 -05:00
c9266d971f cl-typeck: Insert impls into target type's namespace
TODO: Process imports like this lazily
2024-05-07 13:59:45 -05:00
f76756e0e4 num.cl: Implement "overloaded" operator functions
- These implementations, like the types themselves, are/will be compiler-intrinsic. This allows the definitions of these functions to be (apparently) infinitely recursive.

- TODO: Implement these.
2024-05-07 13:32:58 -05:00
a89f45aa58 cl-typeck: Add isize/usize primitives 2024-05-04 23:07:57 -05:00
d2eb165759 cl-arena: Add iterator allocation to TypedArena
This is copied almost verbatim from rustc-arena e82c861d7e/compiler/rustc_arena/src/lib.rs (L203)

TODO: Add unit tests, run unit tests in Miri
2024-05-04 22:17:34 -05:00
edf175e53b cl-typeck: Add utilities for HM-style type inference via unification 2024-05-04 22:12:33 -05:00
6aea23c8ba cl-ast/desugar: Turn all paths into absolute paths 2024-04-29 16:25:30 -05:00
db0b791b24 cl-ast: Fix pretty-printing for use items 2024-04-29 16:19:52 -05:00
7c73fd335c cl-typeck: Store def metadata in a Node
I'm not overly proud of this code, but it currently works. Will refactor later.
2024-04-29 15:40:30 -05:00
d7ce33e457 cl-arena: Allow the arena to hold its own lifetime.
cl-structures: Stick some arenas inside the interners, rather than taking a reference.
2024-04-28 02:01:52 -05:00
0d937728ed cl-structures: Mention new structures in the top-module blurb 2024-04-27 21:15:45 -05:00
a8ef989084 cl-structures/stack: only drop if T needs drop 2024-04-27 21:08:09 -05:00
e7c5a02afa cl-structures: Fully remove unused arenas v2 sources 2024-04-27 20:36:03 -05:00
12046fa9f7 cl-structures/intern: Fix doc comment 2024-04-27 20:26:45 -05:00
fb7de717d0 cl-structures: Remove arena.rs and hashbrown dependency 2024-04-27 20:24:42 -05:00
3fe5916a4f cl-ast: Switch from old string interner to new string interner
Update cl-parser, et. al. to match.
2024-04-27 20:24:11 -05:00
2c57f848ea cl-structures: Fix doctest in deprecated_intern_pool 2024-04-27 20:19:46 -05:00
81cf05cc69 cl-structures: Interning v3: ACTUALLY DO THE THING
Here we have *real* interning, producing unique references if and only if the input is unique! Boy am I glad I invested time into this, because it's really fun to work with.

Hopefully my logic regarding Send-ness and Sync-ness aren't completely unsound.
2024-04-27 20:16:36 -05:00
83423f37be cl-arena: Make arena constructors const fn 2024-04-27 16:52:20 -05:00
ecf97801d6 cl-typeck: Use helper functions on module to insert into the various namespaces 2024-04-27 16:10:22 -05:00
71745161c4 sample-code: Add example "module-hell.cl" demonstrating inter-module imports and path resolution 2024-04-27 16:08:26 -05:00
9566f098ac cl-typeck: Remove NameCollectable trait, use NameCollector instead :D 2024-04-27 16:05:40 -05:00
b9085551e1 cl-typeck: Reimplement NameCollectable in terms of an AST visitor 2024-04-27 15:51:37 -05:00
a877c0d726 cl-arena: Add arena allocator implementation based HEAVILY on rustc-arena
It seems to pass the most basic of tests in miri (shame it needs the unstable `strict_provenance` feature to do so, but eh.)
2024-04-26 00:02:50 -05:00
893b716c86 cl-structures: Rename the deprecated "intern pool" (lmao)
Don't deprecate it yet, though, we've got more stuff yet.
2024-04-25 23:53:44 -05:00
e49b171bea Rename DefItem/DefSource/DefSorcerer to better reflect their meaning 2024-04-25 16:07:26 -05:00
901e9d1d5b cl-typeck: Remove nightly-only debug_closure_helpers 2024-04-25 15:55:32 -05:00
aa3f357fca conlang: Misc. doc fixes 2024-04-24 20:05:55 -05:00
d4432cda7a cl-structures: Give the StringArena a new home. 2024-04-24 19:52:56 -05:00
40ec9b30e4 conlang: Use interned strings (Sym) for all symbols 2024-04-24 19:34:29 -05:00
ede00c3c86 cl-structures: Cleanup for GlobalSym 2024-04-24 18:11:21 -05:00
be604b7b45 cl-structures: Use hashbrown's hash table implementation for deduplication. 2024-04-24 17:43:02 -05:00
e70ffd1895 cl-structures: Global (ew!) and local string interning!
- StringArena provides an arena for immutable strings, inspired by other string interners, and keeps track of the ends of every allocated string. Strings inserted into the arena are assigned a Symbol.
- intern::Interner keeps track of the hashes of each inserted string, and provides deduplication for interned strings. This allows referential comparison between interned strings
- global_intern::GlobalSym provides metered access to a Global Interner, and has a Display implementation which queries the Interner.

The global interner is planned for use in cl-ast.

TODO: the unstable raw_entry API is about to be removed from Rust. Maybe switch to hashbrown, or write my own hash table?
2024-04-24 17:11:41 -05:00
f24bd10c53 cl-structures: intern_pool isn't unsafe
Nor is it an intern pool, it's more of an unstable typed arena. Ah well.
2024-04-24 17:00:58 -05:00
8453b092f1 cl-typeck: Fix list in doc comment getting mistaken for a doctest 2024-04-24 16:50:30 -05:00
42307d2ab4 cl-typeck: Sketch out a new way to store definition metadata 2024-04-22 23:17:50 -05:00
45d75bb552 cl-structures: Make Span and Loc functions const (now you can make a const Span!) 2024-04-22 21:44:12 -05:00
b74c4cd5bf cl-parser: Forego RAII scopeguard finalization pattern in impl Fold for ModuleInliner
We have an outer function *right there* to do cleanup for us.
2024-04-22 21:43:30 -05:00
0c518b47e6 cl-ast: Give Path some inherent methods 2024-04-22 21:04:30 -05:00
169f61144b cl-ast: Fix typo in ast_impl::convert impl From<AsRef<str>> for PathPart 2024-04-22 20:52:12 -05:00
a3a87e0b67 test.cl: Fix moved i32 to module ::num 2024-04-22 15:47:37 -05:00
ed9b73a1a3 repline: Remove default-features from crossterm, since we don't use the event API 2024-04-22 02:18:57 -05:00
9b11543396 Update readme.md 2024-04-22 02:06:46 -05:00
2ed8481489 cl-repl: Switch from argh to argwerk 2024-04-22 02:03:04 -05:00
a3bb1ef447 stdlib: fix errant let in test.cl 2024-04-22 02:02:25 -05:00
f483d690e2 Revert "Cargo.toml: Add documentation key"
This reverts commit 087969e117.
2024-04-22 00:07:44 -05:00
087969e117 Cargo.toml: Add documentation key 2024-04-21 23:49:24 -05:00
116d98437c cl-typeck: Promote some top-module notes to doc comments 2024-04-21 23:42:45 -05:00
8121c1c8bb Move typeck.rs from cl-repl to cl-typeck 2024-04-21 23:41:38 -05:00
2a5e965edf sample-code: Add hello.cl 2024-04-21 23:07:16 -05:00
bf16338166 cl-typeck: Outline all modules. 2024-04-21 23:05:06 -05:00
9449e5ba06 cl-typeck: Sketch out a new path resolver algorithm, and reparent some unused cruft 2024-04-21 22:48:52 -05:00
b796411742 cl-typeck: Module imports v0.1
- NameCollects use items
- Preprocesses them
- Uses no fancy algorithms
- Doesn't respect item visibility at all
- *declaration-order-dependent* :(
- works, though! :)
- TODO: Lazy evaluation of literally any of this stuff.
2024-04-21 22:43:25 -05:00
ef190f2d66 cl-structures: Add get_many_mut implementation for Pool (currently delegates to unstable std) 2024-04-21 22:32:47 -05:00
9c3c2e8674 cl-parser: Implement a module inlining pass 2024-04-21 22:31:01 -05:00
02323ae6f2 cl-parser: Sync error::Parsing with cl-ast 2024-04-21 21:20:55 -05:00
e36a684422 grammar: Make UseTree less ultra-janky 2024-04-21 18:57:46 -05:00
5341631781 conlang: Add constructor expression for structs!
grammar:
- Add new rules `PathLike`, `Structor`, `Fielder`
- Replace Path with PathLike in Primary expressions

cl-ast:
- Add nodes for Structor and Fielder

cl-parser:
- Add branch to path-expression parsing
- Parse Structor bodies

interpret:
- Add TODO
2024-04-20 15:02:16 -05:00
efd442bbfa conlang: Import items into scope with use!
grammar:
- Improve specification of `Path`
- Add `Use` and `UseTree` rules
- Add `Use` as a variant of ItemKind

cl-token:
- Add new keywords `use` and `as`

cl-ast:
- Add nodes for Use and UseTree
- Add new ItemKind for Use
- Implement traversal in Visit and Fold

cl-interpret:
- Mark ItemKind::Use with a todo

cl-parser:
- Update to match grammar

cl-typeck:
- Update to match changes in AST
- Mark UseTrees as NameCollectable and TypeResolvable, but leave as todos
2024-04-20 14:51:54 -05:00
9dc0cc7841 cl-interpret: Give the interpreter a little love
And stop copying strings around.
2024-04-19 10:49:25 -05:00
90a3818ca0 conlang: Move all cl-libs into the compiler directory 2024-04-19 07:39:23 -05:00
2a62a1c714 repline: Promote to its own crate!
cl-repl: Major refactor based on the design of typeck.rs
2024-04-19 07:30:17 -05:00
01ffdb67a6 cl-ast: Add Fold and Visit traits, to more easily map or collect nodes in the AST
typeck.rs: Since this is apparently my testbed now, add a new mode.
TODO: replace the main `conlang` binary with this, since it's so much better.
2024-04-19 03:21:07 -05:00
de024b6cb7 cl-repl: Remove references to the old Resolver 2024-04-19 03:01:24 -05:00
2834e4a8ea cl-ast: Fix typo in format.rs 2024-04-19 02:47:55 -05:00
4ff101f0ee yaml.rs: Fix extraneous pair in While 2024-04-18 23:58:44 -05:00
1fa027a0c2 cl-ast: Move AST into its own module 2024-04-18 21:31:46 -05:00
9a687624fc stdlib: Add some funky syntax tests
TODO: make stuff like this into actual lexer->parser->analysis->interpreter tests.
2024-04-18 21:10:11 -05:00
e102ae25b4 typeck.rs: Make the REPL output a little less unreadable 2024-04-18 21:05:59 -05:00
a56ee38b15 cl-parser: General cleanup and maintenance
- Made infallible rules infallible
- Don't double-check keywords where keywords are required.
  - Of course, this change means rules aren't self-contained
- Rename the `Call` precedence level
- Made member-access operators left-associative
- Removed the useless `Nothing` error type. :(
2024-04-18 21:04:16 -05:00
f315fb5af7 cl-ast: Overhaul pretty-printing using std::fmt::Write adapters.
Now you don't have to import cl_ast::format::*!!!
2024-04-18 20:47:28 -05:00
e4f270da17 cl-ast: Re-order items for aesthetic reasons 2024-04-18 20:22:08 -05:00
17a522b633 cl-parser: Make break bodies actually optional
Note: This goes against the original plan to 'not give Semi a special meaning', but it should be syntactically unambiguous.
2024-04-18 16:42:48 -05:00
736fc37a81 repline: Remove Ignore trait, make debug output backspace-able 2024-04-18 01:56:45 -05:00
02b775259e cl-ast: Change loop expression to take any expression as its argument, for later desugaring. 2024-04-18 01:53:32 -05:00
00d72b823a conlang: Add unconditional loop expression, for desugaring 2024-04-17 00:29:09 -05:00
ec1a1255ad cl-structures: Add helper for getting index from pool. May delete later. 2024-04-16 23:48:05 -05:00
0e8b4f68c3 repline: Word-deletion, and proper history reloading! 2024-04-16 23:47:10 -05:00
eee9e99aed cl-ast: add todo about slice and array type-expressions 2024-04-16 23:46:24 -05:00
f6e44f3773 cl-ast: Print space between items :-) 2024-04-16 23:45:54 -05:00
9e90eea7b6 cl-typeck: Computer! Define "types!"
WARNING: The type checker is still a MAJOR work in progress. You'll be unable to ignore the stringly-typed error handling, effort duplication, and general poor code quality all around. However, this makes leaps and bounds toward a functional, if primitive, type checker.

Definitions now borrow their data from the AST, reducing needless copies.
- This unfortunately means the REPL has to keep around old ASTs, but... Eh. Probably have to keep those around anyway.

The "char" primitive type has been added. Semantics TBD.

Modules definition, type_kind, and value_kind have been consolidated into one.

Project now keeps track of the set of unnamed (anonymous) types (i.e. tuples, function pointers, references) and will yield them freely.
- Project can now also evaluate arbitrary type expressions via the EvaluableTypeExpression trait.

The NameCollector has been replaced with trait NameCollectable.
- This pass visits each node in the AST which can have an item declaration inside it, and constructs an unevaluated module tree.

The TypeResolver has been replaced with trait TypeResolvable and the function resolve()
- This pass visits each *Def* in the project, and attempts to derive all subtypes.
- It's important to note that for Items, unlike EvaluableTypeExpression, the ID passed into resolve_type is the ID of `self`, not of the parent!

typeck.rs:
- The Conlang "standard library" is included in the binary
- There's a main menu now! Type "help" for options.
- Queries have been upgraded from paths to full type expressions!
  - Querying doesn't currently trigger resolution, but it could!
2024-04-16 23:45:24 -05:00
83694988c3 cl-ast: Let Ty handle the complexities of VariantKind::Tuple's type list 2024-04-16 20:40:02 -05:00
98868d3960 cl-ast: allow TyRef to be mutable
yaml.rs: Print AddrOf and TyRef the same way
2024-04-16 20:35:27 -05:00
75adbd6473 cl-ast: Separate function *signature* from function bindings, for cl-typeck
Note: this breaks cl-typeck
2024-04-16 20:31:23 -05:00
d0ed8309f4 cl-ast: Don't store type metadata in TyTuple. Allow arbitrary TyKind in TyFn args. 2024-04-14 23:16:35 -05:00
0fab11c11b cl-parser: Fix visability qualifier coming before attributes while parsing Item 2024-04-14 23:13:17 -05:00
f958bbcb79 cl-ast: Hash everything 2024-04-14 23:11:48 -05:00
d07a3e1455 cl-ast: Separate Display impl for Ty and TyKind 2024-04-14 23:10:02 -05:00
489a1f7944 cl-structures: Give Pool an iterator over its keys 2024-04-14 23:09:12 -05:00
bc33b60265 cl-parser: Parse Impl/ImplKind 2024-04-14 18:01:58 -05:00
89cd1393ed cl-ast: Give Impl/ImplKind some love 2024-04-14 18:01:30 -05:00
3bebac6798 cl-parser: cleanup doc comments + add new error type 2024-04-14 17:59:29 -05:00
6ea99fc6f5 cl-ast: Fix formatting for Index expression 2024-04-13 23:26:06 -05:00
6589376870 Update .gitignore 2024-04-13 03:38:32 -05:00
fc3cbbf450 Conlang v0.0.5: Pratternization
cl-token:
- Minimize data redundancy by consolidating TokenKind::Literal; TokenData::{String, Identifier}
- Rename Op to Punct

cl-ast:
- Remove ExprKind::{Member, Call} in favor of making them
'binary' operators
- Consolidate boxes (TODO: consolidate more boxes)
- Remove repetition vecs in favor of boxes (this may come with performance tradeoffs!)

cl-lexer:
- Reflect changes from cl-token

cl-interpret, cl-repl/src/examples:
- Reflect changes from cl-ast

cl-parser:
- Switch to Pratt parsing for expressions
  - TODO: Code cleanup
  - TODO: Use total ordering for Precedence instead of binding powers (that's what the binding powers are there for anyway)
- Switch functional parsers to take Punct instead of TokenKind
  - It's not like we need a `for`-separated list
- Remove `binary` macro. No longer needed with precedence climbing.
- Repurpose `operator` macro to produce both the operator and the respective Precedence
- Remove several of the smaller parser functions, since they've been consolidated into the larger `exprkind`
2024-04-13 03:33:26 -05:00
2c36ccc0cf cl-parser: Misc. cleanup 2024-04-13 03:02:54 -05:00
265db668ed cl-parser: Reword an error message 2024-04-13 02:57:29 -05:00
fa51f14db5 Remove collect-identifiers example 2024-04-13 02:54:30 -05:00
3b0190b389 cl-interpret: remove Loc from error type
This allows removal of intermediate expression metadata from the AST
2024-04-13 02:54:02 -05:00
21c9909f0c cl-token: make Token fields public
No sense in having them private, they're just plain old data.
2024-04-13 02:48:16 -05:00
290ede2fa3 cl-token: Break operators into their own separate enum, to make future pratt parsing easier 2024-04-12 16:20:24 -05:00
2091cce570 cl-token: Rename Type to TokenKind, Data to TokenData to match the project's general style 2024-04-12 14:36:26 -05:00
902494e95a cl-token: Merge token_type::Type and token_type::Keyword into a single enum 2024-04-12 14:25:49 -05:00
a213c7f70a grammar: Minor cleanup, fix compat with Grammatical parser 2024-04-11 19:59:00 -05:00
8dfddb739e cl-ast: Remove unused monovariant MemberKind enum 2024-04-06 01:03:01 -05:00
a31d285d99 grammar.ebnf: Clean up grammar
- TODO: Member access is totally broken lmao
2024-04-06 01:02:31 -05:00
a036ce260d cl-ast: Add doc comments for every node
This improves the rustdoc output somewhat
2024-04-03 13:19:57 -05:00
4a52d2bc6a conlang: Update type checker
- cl-typeck: Add modules, intrinsic types, unify definition ids
- cl-ast: make attribute lists `Default`
- cl-structures: Add functions to iterate through a pool
- cl-repl: Create an example REPL for the type checker
2024-04-01 05:14:06 -05:00
614d20ea2c cl-parser: parse enums + enum variants 2024-04-01 04:28:30 -05:00
7b40ddc845 cl-ast: destination side of type alias should be an identifier 2024-04-01 04:20:26 -05:00
bdf0bb68ca cl-ast: improve formatting of enums 2024-04-01 04:18:31 -05:00
8ee318f26b cl-ast: Move ExprKind::Assign outside the box, to be more consistent with other uses of Expr 2024-03-28 16:34:24 -05:00
ba148ef5de cl-typeck: Continue work on symbol namespaces
- Refactor for cl-structures intern pool
- Create a list of types the compiler is supposed to care about/have implementations for (Intrinsic/primitive types)
- Add modules and projects (the sym equivalent of ast::File)
- Flesh out value definitions
  - TODO: Create an IR for statements and expressions, and lower the AST into it
2024-03-27 01:54:19 -05:00
8cbe570811 cl-structures: Sketch out a type-safe generic interning pool 2024-03-27 01:26:27 -05:00
66c29d601c cl-structures: Tree cleanup 2024-03-27 01:25:19 -05:00
9f9a21b4c3 cl-repl: Add example that prints the AST in a more friendly way than Debug but in a more verbose way than Display 2024-03-27 01:24:28 -05:00
2cdf112aa6 cl-structures: add todo w/r/t tree traversal api 2024-03-15 05:53:26 -05:00
af35dd1bb3 cl-structures: add a sized, monotype stack 2024-03-15 05:11:47 -05:00
ecde44910f cl-structures: Add a simple potted tree structure, for future use in the module system 2024-03-15 05:10:34 -05:00
a74cd0b8ac cl-structures: break span into its own file. 2024-03-12 19:32:11 -05:00
2eade74d3a cl-repl: Terminal pipe support + fun stylistic fixups 2024-03-01 05:33:35 -06:00
9cae7e4eb8 cl-lexer: switch to unicode-ident crate, since a dependency of cl-repl depends on it. 2024-03-01 04:11:38 -06:00
a07312bf92 cl-lexer: fix link in doc comment 2024-03-01 03:17:43 -06:00
a9b834e012 conlang: bump version number 2024-03-01 03:16:02 -06:00
57dada7aba gitea: update feature-proposal.md 2024-03-01 03:13:07 -06:00
ba6285e006 Pretend conlang has a complete compiler, and start mocking up a standard library 2024-03-01 02:50:50 -06:00
c7fdeaf37a cl-typeck: Begin work on new type-checker.
Comments in lib.rs outline my current thought processes
2024-03-01 02:49:22 -06:00
09737aa40b temp_type_impl: pretty-print functions 2024-03-01 02:47:07 -06:00
1eec1b06ce cl-ast: Move matched brace indenter out of ast_impl (it doesn't impl the ast) 2024-03-01 02:44:35 -06:00
276f0b1031 "libconlang": Remove libconlang 2024-03-01 02:38:00 -06:00
d7604ba039 cl-repl: restructure for future improvements. Replaces temporary handrolled argument parser with external dependency argh.
TODO: Rewrite `argh` in Conlang :^P
2024-03-01 02:35:58 -06:00
c71f68eb55 "libconlang": remove empty module tree tests 2024-02-29 21:05:39 -06:00
c665e52782 all: #![warn(clippy::all)] 2024-02-29 21:04:45 -06:00
50b473cd55 cl-lexer: Move lexer into its own crate 2024-02-29 20:58:50 -06:00
abf00f383c cl-parser: Move inline modules out of line 2024-02-29 20:44:49 -06:00
ab17ebbadc cl-parser: break parser into inline module 2024-02-29 20:43:40 -06:00
cc281fc6ab cl-parser: Move parser into its own crate 2024-02-29 20:41:07 -06:00
1afde9ce35 cl-ast: Move AST definition into its own crate 2024-02-29 19:49:50 -06:00
6e1d5af134 cl-token: Move token definition into its own crate 2024-02-29 19:36:06 -06:00
ee27095fb3 parser: expand the possibilities for assignment locations
This may be reverted later.
TODO: Formalize the concept of a place expression
TODO: Add this to grammar.ebnf
2024-02-29 19:33:28 -06:00
69f5035a8b span: Break out into its own crate, to make room for future expansion 2024-02-29 18:31:41 -06:00
362817e512 cl-repl: fix doctest in repline::ignore 2024-02-29 17:52:16 -06:00
421aab3aa2 interpreter: Break out into a separate crate
My editor's performance was tanking because of macro interpreter::builtins::builtin!

Temporary solution: move the interpreter into a separate crate

If I intended to keep the interpreter around, in the long-term, it might be an idea to make a proc-macro for builtin expansion.
However, the only reason I need the macros is because the interpreter's dynamic typing implementation is so half-baked. After I bang out the new type checker/inference engine, I'll have to rewrite the entire interpreter anyway!
2024-02-29 17:51:38 -06:00
5eb6411d53 interpreter: BuiltIn overhaul!
- Allowed builtins to self-describe
- Broke builtins into their own module
  - Created a macro to work with BuiltIns easier
  - Uses macro 2.0 syntax, so it requires passing in ALL externally referenced identifiers
    - Luckily, this is already a requirement of good, readable macro definitions!
- As a temporary hack, turn overloadable operators into function calls
  - This is kind of pointless at the moment, since there can only be one definition of a function per name (no ADL/function overloading/traits/type namespaces yet!)
  - This is also pretty slow, but benchmarking shows it's not as slow as I thought (~400x slower in release mode than a native Rust implementation when running `fib.cl`/`fib.rs`. Totally unacceptable for most work, but this is a tree walk interpreter.)
  - TODO: Remove this when desugaring from operators to function calls is implemented
2024-02-29 16:48:09 -06:00
140 changed files with 21050 additions and 7450 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[registries.soft-fish]
index = "https://git.soft.fish/j/_cargo-index.git"

View File

@@ -7,7 +7,7 @@ labels:
- enhancement
---
# Feature Progress
<!-- Describe the steps for implementing this feature in libconlang -->
<!-- Describe the steps for implementing this feature -->
- [ ] <!-- Step 1 of implementing a feature -->
# Feature description
@@ -21,4 +21,4 @@ since it most closely matches what I'm currently aiming for
-->
```rust
```
```

10
.gitignore vendored
View File

@@ -1,3 +1,13 @@
# Visual Studio Code config
.vscode
# Rust
**/Cargo.lock
target
# Symbol table dump?
typeck-table.ron
# Pest files generated by Grammatical
*.p*st

View File

@@ -1,11 +1,24 @@
[workspace]
members = ["libconlang", "cl-repl"]
members = [
"compiler/cl-repl",
"compiler/cl-typeck",
"compiler/cl-embed",
"compiler/cl-interpret",
"compiler/cl-structures",
"compiler/cl-token",
"compiler/cl-ast",
"compiler/cl-parser",
"compiler/cl-lexer",
]
resolver = "2"
[workspace.package]
repository = "https://git.soft.fish/j/Conlang"
version = "0.0.3"
version = "0.0.10"
authors = ["John Breaux <j@soft.fish>"]
edition = "2021"
edition = "2024"
license = "MIT"
publish = ["soft-fish"]
[profile.dev]
opt-level = 1

View File

@@ -1,523 +0,0 @@
//! Collects identifiers into a list
use cl_repl::repline::Repline;
use conlang::{common::Loc, lexer::Lexer, parser::Parser};
use std::{
collections::HashMap,
error::Error,
fmt::Display,
ops::{Deref, DerefMut},
};
fn main() -> Result<(), Box<dyn Error>> {
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
while let Ok(line) = rl.read() {
let mut parser = Parser::new(Lexer::new(&line));
let code = match parser.stmt() {
Ok(code) => {
rl.accept();
code
}
Err(_) => {
continue;
}
};
let c = Collector::from(&code);
print!("\x1b[G\x1b[J{c}");
}
Ok(())
}
/// Ties the [Collector] location stack to the program stack
pub struct CollectorLoc<'loc, 'code> {
inner: &'loc mut Collector<'code>,
}
impl<'l, 'c> CollectorLoc<'l, 'c> {
pub fn new(c: &'l mut Collector<'c>, loc: Loc) -> Self {
c.location.push(loc);
Self { inner: c }
}
}
impl<'l, 'c> Deref for CollectorLoc<'l, 'c> {
type Target = Collector<'c>;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<'l, 'c> DerefMut for CollectorLoc<'l, 'c> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}
impl<'l, 'c> Drop for CollectorLoc<'l, 'c> {
fn drop(&mut self) {
let Self { inner: c } = self;
c.location.pop();
}
}
#[derive(Clone, Debug, Default)]
pub struct Collector<'code> {
location: Vec<Loc>,
defs: HashMap<&'code str, Vec<Loc>>,
refs: HashMap<&'code str, Vec<Loc>>,
}
impl<'c> Collector<'c> {
pub fn new() -> Self {
Self::default()
}
pub fn location<'loc>(&'loc mut self, loc: Loc) -> CollectorLoc<'loc, 'c> {
CollectorLoc::new(self, loc)
}
pub fn collect(&mut self, collectible: &'c impl Collectible<'c>) -> &mut Self {
collectible.collect(self);
self
}
pub fn define(&mut self, name: &'c str) -> &mut Self {
// println!("Inserted definition of {name}");
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
self.defs
.entry(name)
.and_modify(|c| c.push(loc))
.or_insert_with(|| vec![loc]);
self
}
pub fn reference(&mut self, name: &'c str) -> &mut Self {
// println!("Inserted usage of {name}");
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
self.refs
.entry(name)
.and_modify(|c| c.push(loc))
.or_insert_with(|| vec![loc]);
self
}
}
impl<'c> Display for Collector<'c> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { location: _, defs, refs } = self;
writeln!(f, "Definitions:")?;
for (name, locs) in defs {
for loc in locs {
writeln!(f, "{loc} {name}")?;
}
}
writeln!(f, "Usages:")?;
for (name, locs) in refs {
for loc in locs {
writeln!(f, "{loc} {name}")?;
}
}
Ok(())
}
}
impl<'c, C: Collectible<'c>> From<&'c C> for Collector<'c> {
fn from(value: &'c C) -> Self {
let mut c = Self::new();
c.collect(value);
c
}
}
use collectible::Collectible;
pub mod collectible {
use super::Collector;
use conlang::ast::*;
pub trait Collectible<'code> {
fn collect(&'code self, c: &mut Collector<'code>);
}
impl<'c> Collectible<'c> for File {
fn collect(&'c self, c: &mut Collector<'c>) {
let File { items } = self;
for item in items {
item.collect(c)
}
}
}
impl<'c> Collectible<'c> for Item {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, attrs: _, vis: _, kind } = self;
kind.collect(&mut c.location(extents.head));
}
}
impl<'c> Collectible<'c> for ItemKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
ItemKind::Alias(f) => f.collect(c),
ItemKind::Const(f) => f.collect(c),
ItemKind::Static(f) => f.collect(c),
ItemKind::Module(f) => f.collect(c),
ItemKind::Function(f) => f.collect(c),
ItemKind::Struct(f) => f.collect(c),
ItemKind::Enum(f) => f.collect(c),
ItemKind::Impl(f) => f.collect(c),
}
}
}
impl<'c> Collectible<'c> for Alias {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { to, from } = self;
c.collect(to).collect(from);
}
}
impl<'c> Collectible<'c> for Const {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), ty, init } = self;
c.define(name).collect(init).collect(ty);
}
}
impl<'c> Collectible<'c> for Static {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { mutable: _, name: Identifier(name), ty, init } = self;
c.define(name).collect(init).collect(ty);
}
}
impl<'c> Collectible<'c> for Module {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for ModuleKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
ModuleKind::Inline(f) => f.collect(c),
ModuleKind::Outline => {}
}
}
}
impl<'c> Collectible<'c> for Function {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), args, body, rety } = self;
c.define(name).collect(args).collect(body).collect(rety);
}
}
impl<'c> Collectible<'c> for Struct {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for StructKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
StructKind::Empty => {}
StructKind::Tuple(k) => k.collect(c),
StructKind::Struct(k) => k.collect(c),
}
}
}
impl<'c> Collectible<'c> for StructMember {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { vis: _, name: Identifier(name), ty } = self;
c.define(name).collect(ty);
}
}
impl<'c> Collectible<'c> for Enum {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for EnumKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
EnumKind::NoVariants => {}
EnumKind::Variants(v) => v.collect(c),
}
}
}
impl<'c> Collectible<'c> for Variant {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for VariantKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
VariantKind::Plain => {}
VariantKind::CLike(_) => {}
VariantKind::Tuple(v) => v.collect(c),
VariantKind::Struct(v) => v.collect(c),
}
}
}
impl<'c> Collectible<'c> for Impl {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { target, body } = self;
c.collect(target).collect(body);
}
}
impl<'c> Collectible<'c> for Block {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { stmts } = self;
for stmt in stmts {
stmt.collect(c);
}
}
}
impl<'c> Collectible<'c> for Stmt {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, kind, semi: _ } = self;
c.location(extents.head).collect(kind);
}
}
impl<'c> Collectible<'c> for StmtKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
StmtKind::Empty => {}
StmtKind::Local(s) => s.collect(c),
StmtKind::Item(s) => s.collect(c),
StmtKind::Expr(s) => s.collect(c),
}
}
}
impl<'c> Collectible<'c> for Let {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { mutable: _, name: Identifier(name), ty, init } = self;
c.collect(init).collect(ty).define(name);
}
}
impl<'c> Collectible<'c> for Expr {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, kind } = self;
c.location(extents.head).collect(kind);
}
}
impl<'c> Collectible<'c> for ExprKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
ExprKind::Assign(k) => k.collect(c),
ExprKind::Binary(k) => k.collect(c),
ExprKind::Unary(k) => k.collect(c),
ExprKind::Member(k) => k.collect(c),
ExprKind::Call(k) => k.collect(c),
ExprKind::Index(k) => k.collect(c),
ExprKind::Path(k) => k.collect(c),
ExprKind::Literal(k) => k.collect(c),
ExprKind::Array(k) => k.collect(c),
ExprKind::ArrayRep(k) => k.collect(c),
ExprKind::AddrOf(k) => k.collect(c),
ExprKind::Block(k) => k.collect(c),
ExprKind::Empty => {}
ExprKind::Group(k) => k.collect(c),
ExprKind::Tuple(k) => k.collect(c),
ExprKind::While(k) => k.collect(c),
ExprKind::If(k) => k.collect(c),
ExprKind::For(k) => k.collect(c),
ExprKind::Break(k) => k.collect(c),
ExprKind::Return(k) => k.collect(c),
ExprKind::Continue(k) => k.collect(c),
}
}
}
impl<'c> Collectible<'c> for Assign {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, op: _, tail } = self;
c.collect(head).collect(tail);
}
}
impl<'c> Collectible<'c> for Binary {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, tail } = self;
c.collect(head);
for (_, tail) in tail {
c.collect(tail);
}
}
}
impl<'c> Collectible<'c> for Unary {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { ops: _, tail } = self;
c.collect(tail);
}
}
impl<'c> Collectible<'c> for Member {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, tail } = self;
c.collect(head).collect(tail);
}
}
impl<'c> Collectible<'c> for Call {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { callee, args } = self;
c.collect(callee).collect(args);
}
}
impl<'c> Collectible<'c> for Tuple {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { exprs } = self;
c.collect(exprs);
}
}
impl<'c> Collectible<'c> for Index {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, indices } = self;
c.collect(head).collect(indices);
}
}
impl<'c> Collectible<'c> for Indices {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { exprs } = self;
c.collect(exprs);
}
}
impl<'c> Collectible<'c> for Array {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { values } = self;
c.collect(values);
}
}
impl<'c> Collectible<'c> for ArrayRep {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { value, repeat } = self;
c.collect(value).collect(repeat);
}
}
impl<'c> Collectible<'c> for AddrOf {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { count: _, mutable: _, expr } = self;
c.collect(expr);
}
}
impl<'c> Collectible<'c> for Group {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { expr } = self;
c.collect(expr);
}
}
impl<'c> Collectible<'c> for While {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { cond, pass, fail } = self;
c.collect(cond).collect(pass).collect(fail);
}
}
impl<'c> Collectible<'c> for Else {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { body } = self;
c.collect(body);
}
}
impl<'c> Collectible<'c> for If {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { cond, pass, fail } = self;
c.collect(cond).collect(pass).collect(fail);
}
}
impl<'c> Collectible<'c> for For {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { bind: Identifier(name), cond, pass, fail } = self;
c.collect(cond).define(name).collect(pass).collect(fail);
}
}
impl<'c> Collectible<'c> for Break {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { body } = self;
c.collect(body);
}
}
impl<'c> Collectible<'c> for Return {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { body } = self;
c.collect(body);
}
}
impl<'c> Collectible<'c> for Continue {
fn collect(&'c self, _c: &mut Collector<'c>) {}
}
impl<'c> Collectible<'c> for Literal {
fn collect(&'c self, _c: &mut Collector<'c>) {}
}
impl<'c> Collectible<'c> for Identifier {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self(name) = self;
c.reference(name);
}
}
impl<'c> Collectible<'c> for Param {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { mutability: _, name, ty } = self;
c.collect(name).collect(ty);
}
}
impl<'c> Collectible<'c> for Ty {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, kind } = self;
c.location(extents.head).collect(kind);
}
}
impl<'c> Collectible<'c> for TyKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
TyKind::Never => {}
TyKind::Empty => {}
TyKind::SelfTy => {}
TyKind::Path(t) => t.collect(c),
TyKind::Tuple(t) => t.collect(c),
TyKind::Ref(t) => t.collect(c),
TyKind::Fn(t) => t.collect(c),
}
}
}
impl<'c> Collectible<'c> for Path {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { absolute: _, parts } = self;
for part in parts {
c.collect(part);
}
}
}
impl<'c> Collectible<'c> for PathPart {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
PathPart::SuperKw => {}
PathPart::SelfKw => {}
PathPart::Ident(i) => i.collect(c),
}
}
}
impl<'c> Collectible<'c> for TyTuple {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { types } = self;
for ty in types {
c.collect(ty);
}
}
}
impl<'c> Collectible<'c> for TyRef {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { count: _, to } = self;
c.collect(to);
}
}
impl<'c> Collectible<'c> for TyFn {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { args, rety } = self;
c.collect(args).collect(rety);
}
}
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Vec<C> {
fn collect(&'c self, c: &mut Collector<'c>) {
for i in self {
c.collect(i);
}
}
}
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Option<C> {
fn collect(&'c self, c: &mut Collector<'c>) {
if let Some(i) = self {
c.collect(i);
}
}
}
impl<'c, C: Collectible<'c>> Collectible<'c> for Box<C> {
fn collect(&'c self, c: &mut Collector<'c>) {
c.collect(self.as_ref());
}
}
}

View File

@@ -1,513 +0,0 @@
//! Utilities for cl-frontend
//!
//! # TODO
//! - [ ] Readline-like line editing
//! - [ ] Raw mode?
pub mod args {
use crate::cli::Mode;
use std::{
io::{stdin, IsTerminal},
ops::Deref,
path::{Path, PathBuf},
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Args {
pub path: Option<PathBuf>, // defaults None
pub repl: bool, // defaults true if stdin is terminal
pub mode: Mode, // defaults Interpret
}
const HELP: &str = "[( --repl | --no-repl )] [--mode (tokens | pretty | type | run)] [( -f | --file ) <filename>]";
impl Args {
pub fn new() -> Self {
Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret }
}
pub fn parse(mut self) -> Option<Self> {
let mut args = std::env::args();
let name = args.next().unwrap_or_default();
let mut unknown = false;
while let Some(arg) = args.next() {
match arg.deref() {
"--repl" => self.repl = true,
"--no-repl" => self.repl = false,
"-f" | "--file" => self.path = args.next().map(PathBuf::from),
"-m" | "--mode" => {
self.mode = args.next().unwrap_or_default().parse().unwrap_or_default()
}
arg => {
eprintln!("Unknown argument: {arg}");
unknown = true;
}
}
}
if unknown {
println!("Usage: {name} {HELP}");
None?
}
Some(self)
}
/// Returns the path to a file, if one was specified
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
/// Returns whether to start a REPL session or not
pub fn repl(&self) -> bool {
self.repl
}
/// Returns the repl Mode
pub fn mode(&self) -> Mode {
self.mode
}
}
impl Default for Args {
fn default() -> Self {
Self::new()
}
}
}
pub mod program {
use std::{fmt::Display, io::Write};
use conlang::{
ast::{self, ast_impl::format::Pretty},
interpreter::{
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
},
// pretty_printer::{PrettyPrintable, Printer},
lexer::Lexer,
parser::{error::PResult, Parser},
resolver::{error::TyResult, Resolver},
};
pub struct Parsable;
pub enum Parsed {
File(ast::File),
Stmt(ast::Stmt),
Expr(ast::Expr),
}
pub struct Program<'t, Variant> {
text: &'t str,
data: Variant,
}
impl<'t, V> Program<'t, V> {
pub fn lex(&self) -> Lexer {
Lexer::new(self.text)
}
}
impl<'t> Program<'t, Parsable> {
pub fn new(text: &'t str) -> Self {
Self { text, data: Parsable }
}
pub fn parse(self) -> PResult<Program<'t, Parsed>> {
self.parse_file().or_else(|_| self.parse_stmt())
}
pub fn parse_expr(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::Expr(Parser::new(self.lex()).expr()?), text: self.text })
}
pub fn parse_stmt(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::Stmt(Parser::new(self.lex()).stmt()?), text: self.text })
}
pub fn parse_file(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::File(Parser::new(self.lex()).file()?), text: self.text })
}
}
impl<'t> Program<'t, Parsed> {
pub fn debug(&self) {
match &self.data {
Parsed::File(v) => eprintln!("{v:?}"),
Parsed::Stmt(v) => eprintln!("{v:?}"),
Parsed::Expr(v) => eprintln!("{v:?}"),
}
}
pub fn print(&self) {
let mut f = std::io::stdout().pretty();
let _ = match &self.data {
Parsed::File(v) => writeln!(f, "{v}"),
Parsed::Stmt(v) => writeln!(f, "{v}"),
Parsed::Expr(v) => writeln!(f, "{v}"),
};
// println!("{self}")
}
pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
todo!("Program::resolve(\n{self},\n{resolver:?}\n)")
}
pub fn run(&self, env: &mut Environment) -> IResult<ConValue> {
match &self.data {
Parsed::File(v) => v.interpret(env),
Parsed::Stmt(v) => v.interpret(env),
Parsed::Expr(v) => v.interpret(env),
}
}
// pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
// match &mut self.data {
// Parsed::Program(start) => start.resolve(resolver),
// Parsed::Expr(expr) => expr.resolve(resolver),
// }
// .map(|ty| println!("{ty}"))
// }
// /// Runs the [Program] in the specified [Environment]
// pub fn run(&self, env: &mut Environment) -> IResult<()> {
// println!(
// "{}",
// match &self.data {
// Parsed::Program(start) => env.eval(start)?,
// Parsed::Expr(expr) => env.eval(expr)?,
// }
// );
// Ok(())
// }
}
impl<'t> Display for Program<'t, Parsed> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.data {
Parsed::File(v) => write!(f, "{v}"),
Parsed::Stmt(v) => write!(f, "{v}"),
Parsed::Expr(v) => write!(f, "{v}"),
}
}
}
// impl PrettyPrintable for Program<Parsed> {
// fn visit<W: Write>(&self, p: &mut Printer<W>) -> IOResult<()> {
// match &self.data {
// Parsed::Program(value) => value.visit(p),
// Parsed::Expr(value) => value.visit(p),
// }
// }
// }
}
pub mod cli {
use conlang::{interpreter::env::Environment, resolver::Resolver, token::Token};
use crate::{
args::Args,
program::{Parsable, Parsed, Program},
};
use std::{
convert::Infallible,
error::Error,
path::{Path, PathBuf},
str::FromStr,
};
// ANSI color escape sequences
const ANSI_RED: &str = "\x1b[31m";
const ANSI_GREEN: &str = "\x1b[32m";
const ANSI_CYAN: &str = "\x1b[36m";
const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
const ANSI_RESET: &str = "\x1b[0m";
const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
#[derive(Clone, Debug)]
pub enum CLI {
Repl(Repl),
File { mode: Mode, path: PathBuf },
Stdin { mode: Mode },
}
impl From<Args> for CLI {
fn from(value: Args) -> Self {
let Args { path, repl, mode } = value;
match (repl, path) {
(true, Some(path)) => {
let prog = std::fs::read_to_string(path).unwrap();
let code = conlang::parser::Parser::new(conlang::lexer::Lexer::new(&prog))
.file()
.unwrap();
let mut env = conlang::interpreter::env::Environment::new();
env.eval(&code).unwrap();
env.call("dump", &[])
.expect("calling dump in the environment shouldn't fail");
Self::Repl(Repl { mode, env, ..Default::default() })
}
(_, Some(path)) => Self::File { mode, path },
(true, None) => Self::Repl(Repl { mode, ..Default::default() }),
(false, None) => Self::Stdin { mode },
}
}
}
impl CLI {
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
use std::{fs, io};
match self {
CLI::Repl(repl) => repl.repl(),
CLI::File { mode, ref path } => {
// read file
Self::no_repl(*mode, Some(path), &fs::read_to_string(path)?)
}
CLI::Stdin { mode } => {
Self::no_repl(*mode, None, &io::read_to_string(io::stdin())?)
}
}
Ok(())
}
fn no_repl(mode: Mode, path: Option<&Path>, code: &str) {
let program = Program::new(code);
match mode {
Mode::Tokenize => {
for token in program.lex() {
if let Some(path) = path {
print!("{}:", path.display());
}
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
}
Mode::Beautify => Self::beautify(program),
Mode::Resolve => Self::resolve(program, Default::default()),
Mode::Interpret => Self::interpret(program, Environment::new()),
}
}
fn beautify(program: Program<Parsable>) {
match program.parse() {
Ok(program) => program.print(),
Err(e) => eprintln!("{e}"),
};
}
fn resolve(program: Program<Parsable>, mut resolver: Resolver) {
let mut program = match program.parse() {
Ok(program) => program,
Err(e) => {
eprintln!("{e}");
return;
}
};
if let Err(e) = program.resolve(&mut resolver) {
eprintln!("{e}");
}
}
fn interpret(program: Program<Parsable>, mut interpreter: Environment) {
let program = match program.parse() {
Ok(program) => program,
Err(e) => {
eprintln!("{e}");
return;
}
};
if let Err(e) = program.run(&mut interpreter) {
eprintln!("{e}");
return;
}
if let Err(e) = interpreter.call("main", &[]) {
eprintln!("{e}");
}
}
}
/// The CLI's operating mode
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mode {
Tokenize,
Beautify,
Resolve,
#[default]
Interpret,
}
impl Mode {
pub fn ansi_color(self) -> &'static str {
match self {
Mode::Tokenize => ANSI_BRIGHT_BLUE,
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
Mode::Resolve => ANSI_GREEN,
Mode::Interpret => ANSI_CYAN,
}
}
}
impl FromStr for Mode {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Infallible> {
Ok(match s {
"i" | "interpret" | "run" => Mode::Interpret,
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
"r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
"t" | "tokenize" | "tokens" => Mode::Tokenize,
_ => Mode::Interpret,
})
}
}
/// Implements the interactive interpreter
#[derive(Clone, Debug)]
pub struct Repl {
prompt_again: &'static str, // " ?>"
prompt_begin: &'static str, // "cl>"
prompt_error: &'static str, // "! >"
env: Environment,
resolver: Resolver,
mode: Mode,
}
impl Default for Repl {
fn default() -> Self {
Self {
prompt_begin: "cl>",
prompt_again: " ?>",
prompt_error: "! >",
env: Default::default(),
resolver: Default::default(),
mode: Default::default(),
}
}
}
/// Prompt functions
impl Repl {
pub fn prompt_error(&self, err: &impl Error) {
let Self { prompt_error: prompt, .. } = self;
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}",)
}
/// Resets the cursor to the start of the line, clears the terminal,
/// and sets the output color
pub fn begin_output(&self) {
print!("{ANSI_CLEAR_LINES}{ANSI_OUTPUT}")
}
pub fn clear_line(&self) {}
}
/// The actual REPL
impl Repl {
/// Constructs a new [Repl] with the provided [Mode]
pub fn new(mode: Mode) -> Self {
Self { mode, ..Default::default() }
}
/// Runs the main REPL loop
pub fn repl(&mut self) {
use crate::repline::{error::Error, Repline};
let mut rl = Repline::new(self.mode.ansi_color(), self.prompt_begin, self.prompt_again);
fn clear_line() {
print!("\x1b[G\x1b[J");
}
loop {
let buf = match rl.read() {
Ok(buf) => buf,
// Ctrl-C: break if current line is empty
Err(Error::CtrlC(buf)) => {
if buf.is_empty() || buf.ends_with('\n') {
return;
}
rl.accept();
println!("Cancelled. (Press Ctrl+C again to quit.)");
continue;
}
// Ctrl-D: reset input, and parse it for errors
Err(Error::CtrlD(buf)) => {
rl.deny();
if let Err(e) = Program::new(&buf).parse() {
clear_line();
self.prompt_error(&e);
}
continue;
}
Err(e) => {
self.prompt_error(&e);
return;
}
};
self.begin_output();
if self.command(&buf) {
rl.deny();
rl.set_color(self.mode.ansi_color());
continue;
}
let code = Program::new(&buf);
if self.mode == Mode::Tokenize {
self.tokenize(&code);
rl.deny();
continue;
}
match code.lex().into_iter().find(|l| l.is_err()) {
None => {}
Some(Ok(_)) => unreachable!(),
Some(Err(error)) => {
rl.deny();
self.prompt_error(&error);
continue;
}
}
if let Ok(mut code) = code.parse() {
rl.accept();
self.dispatch(&mut code);
}
}
}
fn help(&self) {
println!(
"Commands:\n- $tokens\n Tokenize Mode:\n Outputs information derived by the Lexer\n- $pretty\n Beautify Mode:\n Pretty-prints the input\n- $type\n Resolve Mode:\n Attempts variable resolution and type-checking on the input\n- $run\n Interpret Mode:\n Interprets the input using Conlang\'s work-in-progress interpreter\n- $mode\n Prints the current mode\n- $help\n Prints this help message"
);
}
fn command(&mut self, line: &str) -> bool {
match line.trim() {
"$pretty" => self.mode = Mode::Beautify,
"$tokens" => self.mode = Mode::Tokenize,
"$type" => self.mode = Mode::Resolve,
"$run" => self.mode = Mode::Interpret,
"$mode" => println!("{:?} Mode", self.mode),
"$help" => self.help(),
_ => return false,
}
true
}
/// Dispatches calls to repl functions based on the program
fn dispatch(&mut self, code: &mut Program<Parsed>) {
match self.mode {
Mode::Tokenize => (),
Mode::Beautify => self.beautify(code),
Mode::Resolve => self.typecheck(code),
Mode::Interpret => self.interpret(code),
}
}
fn tokenize(&mut self, code: &Program<Parsable>) {
for token in code.lex() {
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
}
fn interpret(&mut self, code: &Program<Parsed>) {
if let Err(e) = code.run(&mut self.env) {
self.prompt_error(&e)
}
}
fn typecheck(&mut self, code: &mut Program<Parsed>) {
if let Err(e) = code.resolve(&mut self.resolver) {
self.prompt_error(&e)
}
}
fn beautify(&mut self, code: &Program<Parsed>) {
code.print()
}
}
fn print_token(t: &Token) {
println!(
"{:02}:{:02}: {:#19}{}",
t.line(),
t.col(),
t.ty(),
t.data(),
)
}
}
pub mod repline;

View File

@@ -1,6 +0,0 @@
use cl_repl::{args::Args, cli::CLI};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
CLI::from(Args::new().parse().unwrap_or_default()).run()
}

View File

@@ -1,636 +0,0 @@
//! A small pseudo-multiline editing library
// #![allow(unused)]
pub mod error {
/// Result type for Repline
pub type ReplResult<T> = std::result::Result<T, Error>;
/// Borrowed error (does not implement [Error](std::error::Error)!)
#[derive(Debug)]
pub enum Error {
/// User broke with Ctrl+C
CtrlC(String),
/// User broke with Ctrl+D
CtrlD(String),
/// Invalid unicode codepoint
BadUnicode(u32),
/// Error came from [std::io]
IoFailure(std::io::Error),
/// End of input
EndOfInput,
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::CtrlC(_) => write!(f, "Ctrl+C"),
Error::CtrlD(_) => write!(f, "Ctrl+D"),
Error::BadUnicode(u) => write!(f, "0x{u:x} is not a valid unicode codepoint"),
Error::IoFailure(s) => write!(f, "{s}"),
Error::EndOfInput => write!(f, "End of input"),
}
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Self::IoFailure(value)
}
}
}
pub mod ignore {
//! Does nothing, universally.
//!
//! Introduces the [Ignore] trait, and its singular function, [ignore](Ignore::ignore),
//! which does nothing.
impl<T> Ignore for T {}
/// Does nothing
///
/// # Examples
/// ```rust
/// #![deny(unused_must_use)]
/// # use cl_frontend::repline::ignore::Ignore;
/// ().ignore();
/// Err::<(), &str>("Foo").ignore();
/// Some("Bar").ignore();
/// 42.ignore();
///
/// #[must_use]
/// fn the_meaning() -> usize {
/// 42
/// }
/// the_meaning().ignore();
/// ```
pub trait Ignore {
/// Does nothing
fn ignore(&self) {}
}
}
pub mod chars {
//! Converts an <code>[Iterator]<Item = [u8]></code> into an
//! <code>[Iterator]<Item = [char]></code>
use super::error::*;
/// Converts an <code>[Iterator]<Item = [u8]></code> into an
/// <code>[Iterator]<Item = [char]></code>
#[derive(Clone, Debug)]
pub struct Chars<I: Iterator<Item = u8>>(pub I);
impl<I: Iterator<Item = u8>> Chars<I> {
pub fn new(bytes: I) -> Self {
Self(bytes)
}
}
impl<I: Iterator<Item = u8>> Iterator for Chars<I> {
type Item = ReplResult<char>;
fn next(&mut self) -> Option<Self::Item> {
let Self(bytes) = self;
let start = bytes.next()? as u32;
let (mut out, count) = match start {
start if start & 0x80 == 0x00 => (start, 0), // ASCII valid range
start if start & 0xe0 == 0xc0 => (start & 0x1f, 1), // 1 continuation byte
start if start & 0xf0 == 0xe0 => (start & 0x0f, 2), // 2 continuation bytes
start if start & 0xf8 == 0xf0 => (start & 0x07, 3), // 3 continuation bytes
_ => return None,
};
for _ in 0..count {
let cont = bytes.next()? as u32;
if cont & 0xc0 != 0x80 {
return None;
}
out = out << 6 | (cont & 0x3f);
}
Some(char::from_u32(out).ok_or(Error::BadUnicode(out)))
}
}
}
pub mod flatten {
//! Flattens an [Iterator] returning [`Result<T, E>`](Result) or [`Option<T>`](Option)
//! into a *non-[FusedIterator](std::iter::FusedIterator)* over `T`
/// Flattens an [Iterator] returning [`Result<T, E>`](Result) or [`Option<T>`](Option)
/// into a *non-[FusedIterator](std::iter::FusedIterator)* over `T`
pub struct Flatten<T, I: Iterator<Item = T>>(pub I);
impl<T, E, I: Iterator<Item = Result<T, E>>> Iterator for Flatten<Result<T, E>, I> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()?.ok()
}
}
impl<T, I: Iterator<Item = Option<T>>> Iterator for Flatten<Option<T>, I> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()?
}
}
}
pub mod raw {
//! Sets the terminal to [`raw`] mode for the duration of the returned object's lifetime.
/// Sets the terminal to raw mode for the duration of the returned object's lifetime.
pub fn raw() -> impl Drop {
Raw::default()
}
struct Raw();
impl Default for Raw {
fn default() -> Self {
std::thread::yield_now();
crossterm::terminal::enable_raw_mode()
.expect("should be able to transition into raw mode");
Raw()
}
}
impl Drop for Raw {
fn drop(&mut self) {
crossterm::terminal::disable_raw_mode()
.expect("should be able to transition out of raw mode");
// std::thread::yield_now();
}
}
}
mod out {
#![allow(unused)]
use std::io::{Result, Write};
/// A [Writer](Write) that flushes after every wipe
#[derive(Clone, Debug)]
pub(super) struct EagerWriter<W: Write> {
out: W,
}
impl<W: Write> EagerWriter<W> {
pub fn new(writer: W) -> Self {
Self { out: writer }
}
}
impl<W: Write> Write for EagerWriter<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let out = self.out.write(buf)?;
self.out.flush()?;
Ok(out)
}
fn flush(&mut self) -> Result<()> {
self.out.flush()
}
}
}
use self::{chars::Chars, editor::Editor, error::*, flatten::Flatten, ignore::Ignore, raw::raw};
use std::{
collections::VecDeque,
io::{stdout, Bytes, Read, Result, Write},
};
pub struct Repline<'a, R: Read> {
input: Chars<Flatten<Result<u8>, Bytes<R>>>,
history: VecDeque<String>, // previous lines
hindex: usize, // current index into the history buffer
ed: Editor<'a>, // the current line buffer
}
impl<'a, R: Read> Repline<'a, R> {
/// Constructs a [Repline] with the given [Reader](Read), color, begin, and again prompts.
pub fn with_input(input: R, color: &'a str, begin: &'a str, again: &'a str) -> Self {
Self {
input: Chars(Flatten(input.bytes())),
history: Default::default(),
hindex: 0,
ed: Editor::new(color, begin, again),
}
}
/// Set the terminal prompt color
pub fn set_color(&mut self, color: &'a str) {
self.ed.color = color
}
/// Reads in a line, and returns it for validation
pub fn read(&mut self) -> ReplResult<String> {
const INDENT: &str = " ";
let mut stdout = stdout().lock();
let stdout = &mut stdout;
let _make_raw = raw();
// self.ed.begin_frame(stdout)?;
// self.ed.redraw_frame(stdout)?;
self.ed.print_head(stdout)?;
loop {
stdout.flush()?;
match self.input.next().ok_or(Error::EndOfInput)?? {
// Ctrl+C: End of Text. Immediately exits.
// Ctrl+D: End of Transmission. Ends the current line.
'\x03' => {
drop(_make_raw);
writeln!(stdout)?;
return Err(Error::CtrlC(self.ed.to_string()));
}
'\x04' => {
drop(_make_raw);
writeln!(stdout)?;
return Err(Error::CtrlD(self.ed.to_string()));
}
// Tab: extend line by 4 spaces
'\t' => {
self.ed.extend(INDENT.chars(), stdout)?;
}
// ignore newlines, process line feeds. Not sure how cross-platform this is.
'\n' => {}
'\r' => {
self.ed.push('\n', stdout)?;
return Ok(self.ed.to_string());
}
// Escape sequence
'\x1b' => self.escape(stdout)?,
// backspace
'\x08' | '\x7f' => {
let ed = &mut self.ed;
if ed.ends_with(INDENT.chars()) {
for _ in 0..INDENT.len() {
ed.pop(stdout)?;
}
} else {
ed.pop(stdout)?;
}
}
c if c.is_ascii_control() => {
if cfg!(debug_assertions) {
eprint!("\\x{:02x}", c as u32);
}
}
c => {
self.ed.push(c, stdout)?;
}
}
}
}
/// Handle ANSI Escape
fn escape<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
match self.input.next().ok_or(Error::EndOfInput)?? {
'[' => self.csi(w)?,
'O' => todo!("Process alternate character mode"),
other => self.ed.extend(['\x1b', other], w)?,
}
Ok(())
}
/// Handle ANSI Control Sequence Introducer
fn csi<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
match self.input.next().ok_or(Error::EndOfInput)?? {
'A' => {
self.hindex = self.hindex.saturating_sub(1);
self.restore_history(w)?
}
'B' => {
self.hindex = self
.hindex
.saturating_add(1)
.min(self.history.len().saturating_sub(1));
self.restore_history(w)?
}
'C' => self.ed.cursor_forward(1, w)?,
'D' => self.ed.cursor_back(1, w)?,
'H' => self.ed.home(w)?,
'F' => self.ed.end(w)?,
'3' => {
if let '~' = self.input.next().ok_or(Error::EndOfInput)?? {
self.ed.delete(w).ignore()
}
}
other => {
if cfg!(debug_assertions) {
eprint!("{}", other.escape_unicode());
}
}
}
Ok(())
}
/// Restores the currently selected history
pub fn restore_history<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
let Self { history, hindex, ed, .. } = self;
if !(0..history.len()).contains(hindex) {
return Ok(());
};
ed.undraw(w)?;
ed.clear();
ed.print_head(w)?;
ed.extend(
history
.get(*hindex)
.expect("history should contain index")
.chars(),
w,
)
}
/// Append line to history and clear it
pub fn accept(&mut self) {
self.history_append(self.ed.iter().collect());
self.ed.clear();
self.hindex = self.history.len();
}
/// Append line to history
pub fn history_append(&mut self, mut buf: String) {
while buf.ends_with(char::is_whitespace) {
buf.pop();
}
if !self.history.contains(&buf) {
self.history.push_back(buf)
}
while self.history.len() > 20 {
self.history.pop_front();
}
}
/// Clear the line
pub fn deny(&mut self) {
self.ed.clear()
}
}
impl<'a> Repline<'a, std::io::Stdin> {
pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
Self::with_input(std::io::stdin(), color, begin, again)
}
}
pub mod editor {
use crossterm::{cursor::*, execute, queue, style::*, terminal::*};
use std::{collections::VecDeque, fmt::Display, io::Write};
use super::error::{Error, ReplResult};
fn is_newline(c: &char) -> bool {
*c == '\n'
}
fn write_chars<'a, W: Write>(
c: impl IntoIterator<Item = &'a char>,
w: &mut W,
) -> std::io::Result<()> {
for c in c {
write!(w, "{c}")?;
}
Ok(())
}
#[derive(Debug)]
pub struct Editor<'a> {
head: VecDeque<char>,
tail: VecDeque<char>,
pub color: &'a str,
begin: &'a str,
again: &'a str,
}
impl<'a> Editor<'a> {
pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
Self { head: Default::default(), tail: Default::default(), color, begin, again }
}
pub fn iter(&self) -> impl Iterator<Item = &char> {
self.head.iter()
}
pub fn undraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { head, .. } = self;
match head.iter().copied().filter(is_newline).count() {
0 => write!(w, "\x1b[0G"),
lines => write!(w, "\x1b[{}F", lines),
}?;
queue!(w, Clear(ClearType::FromCursorDown))?;
// write!(w, "\x1b[0J")?;
Ok(())
}
pub fn redraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { head, tail, color, begin, again } = self;
write!(w, "{color}{begin}\x1b[0m ")?;
// draw head
for c in head {
match c {
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
_ => w.write_all({ *c as u32 }.to_le_bytes().as_slice()),
}?
}
// save cursor
execute!(w, SavePosition)?;
// draw tail
for c in tail {
match c {
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
_ => write!(w, "{c}"),
}?
}
// restore cursor
execute!(w, RestorePosition)?;
Ok(())
}
pub fn prompt<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { head, color, begin, again, .. } = self;
queue!(
w,
MoveToColumn(0),
Print(color),
Print(if head.is_empty() { begin } else { again }),
ResetColor,
Print(' '),
)?;
Ok(())
}
pub fn print_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
self.prompt(w)?;
write_chars(
self.head.iter().skip(
self.head
.iter()
.rposition(is_newline)
.unwrap_or(self.head.len())
+ 1,
),
w,
)?;
Ok(())
}
pub fn print_tail<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { tail, .. } = self;
queue!(w, SavePosition, Clear(ClearType::UntilNewLine))?;
write_chars(tail.iter().take_while(|&c| !is_newline(c)), w)?;
queue!(w, RestorePosition)?;
Ok(())
}
pub fn push<W: Write>(&mut self, c: char, w: &mut W) -> ReplResult<()> {
// Tail optimization: if the tail is empty,
//we don't have to undraw and redraw on newline
if self.tail.is_empty() {
self.head.push_back(c);
match c {
'\n' => {
write!(w, "\r\n")?;
self.print_head(w)?;
}
c => {
queue!(w, Print(c))?;
}
};
return Ok(());
}
if '\n' == c {
self.undraw(w)?;
}
self.head.push_back(c);
match c {
'\n' => self.redraw(w)?,
_ => {
write!(w, "{c}")?;
self.print_tail(w)?;
}
}
Ok(())
}
pub fn pop<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
if let Some('\n') = self.head.back() {
self.undraw(w)?;
}
let c = self.head.pop_back();
// if the character was a newline, we need to go back a line
match c {
Some('\n') => self.redraw(w)?,
Some(_) => {
// go back a char
queue!(w, MoveLeft(1), Print(' '), MoveLeft(1))?;
self.print_tail(w)?;
}
None => {}
}
Ok(c)
}
pub fn extend<T: IntoIterator<Item = char>, W: Write>(
&mut self,
iter: T,
w: &mut W,
) -> ReplResult<()> {
for c in iter {
self.push(c, w)?;
}
Ok(())
}
pub fn restore(&mut self, s: &str) {
self.clear();
self.head.extend(s.chars())
}
pub fn clear(&mut self) {
self.head.clear();
self.tail.clear();
}
pub fn delete<W: Write>(&mut self, w: &mut W) -> ReplResult<char> {
match self.tail.front() {
Some('\n') => {
self.undraw(w)?;
let out = self.tail.pop_front();
self.redraw(w)?;
out
}
_ => {
let out = self.tail.pop_front();
self.print_tail(w)?;
out
}
}
.ok_or(Error::EndOfInput)
}
pub fn len(&self) -> usize {
self.head.len() + self.tail.len()
}
pub fn is_empty(&self) -> bool {
self.head.is_empty() && self.tail.is_empty()
}
pub fn ends_with(&self, iter: impl DoubleEndedIterator<Item = char>) -> bool {
let mut iter = iter.rev();
let mut head = self.head.iter().rev();
loop {
match (iter.next(), head.next()) {
(None, _) => break true,
(Some(_), None) => break false,
(Some(a), Some(b)) if a != *b => break false,
(Some(_), Some(_)) => continue,
}
}
}
/// Moves the cursor back `steps` steps
pub fn cursor_back<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
for _ in 0..steps {
if let Some('\n') = self.head.back() {
self.undraw(w)?;
}
let Some(c) = self.head.pop_back() else {
return Ok(());
};
self.tail.push_front(c);
match c {
'\n' => self.redraw(w)?,
_ => queue!(w, MoveLeft(1))?,
}
}
Ok(())
}
/// Moves the cursor forward `steps` steps
pub fn cursor_forward<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
for _ in 0..steps {
if let Some('\n') = self.tail.front() {
self.undraw(w)?
}
let Some(c) = self.tail.pop_front() else {
return Ok(());
};
self.head.push_back(c);
match c {
'\n' => self.redraw(w)?,
_ => queue!(w, MoveRight(1))?,
}
}
Ok(())
}
/// Goes to the beginning of the current line
pub fn home<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
loop {
match self.head.back() {
Some('\n') | None => break Ok(()),
Some(_) => self.cursor_back(1, w)?,
}
}
}
/// Goes to the end of the current line
pub fn end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
loop {
match self.tail.front() {
Some('\n') | None => break Ok(()),
Some(_) => self.cursor_forward(1, w)?,
}
}
}
}
impl<'a, 'e> IntoIterator for &'e Editor<'a> {
type Item = &'e char;
type IntoIter = std::iter::Chain<
std::collections::vec_deque::Iter<'e, char>,
std::collections::vec_deque::Iter<'e, char>,
>;
fn into_iter(self) -> Self::IntoIter {
self.head.iter().chain(self.tail.iter())
}
}
impl<'a> Display for Editor<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
let Self { head, tail, .. } = self;
for c in head {
f.write_char(*c)?;
}
for c in tail {
f.write_char(*c)?;
}
Ok(())
}
}
}

View File

@@ -1,5 +1,5 @@
[package]
name = "cl-repl"
name = "cl-ast"
repository.workspace = true
version.workspace = true
authors.workspace = true
@@ -7,8 +7,5 @@ edition.workspace = true
license.workspace = true
publish.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
conlang = { path = "../libconlang" }
crossterm = "0.27.0"
cl-structures = { path = "../cl-structures" }

650
compiler/cl-ast/src/ast.rs Normal file
View File

@@ -0,0 +1,650 @@
//! # The Abstract Syntax Tree
//! Contains definitions of Conlang AST Nodes.
//!
//! # Notable nodes
//! - [Item] and [ItemKind]: Top-level constructs
//! - [Stmt] and [StmtKind]: Statements
//! - [Expr] and [ExprKind]: Expressions
//! - [Assign], [Modify], [Binary], and [Unary] expressions
//! - [ModifyKind], [BinaryKind], and [UnaryKind] operators
//! - [Ty] and [TyKind]: Type qualifiers
//! - [Pattern]: Pattern matching operators
//! - [Path]: Path expressions
use cl_structures::{intern::interned::Interned, span::*};
/// An [Interned] static [str], used in place of an identifier
pub type Sym = Interned<'static, str>;
/// Whether a binding ([Static] or [Let]) or reference is mutable or not
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Mutability {
#[default]
Not,
Mut,
}
/// Whether an [Item] is visible outside of the current [Module]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Visibility {
#[default]
Private,
Public,
}
/// A list of [Item]s
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct File {
pub name: &'static str,
pub items: Vec<Item>,
}
/// A list of [Meta] decorators
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Attrs {
pub meta: Vec<Meta>,
}
/// A metadata decorator
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Meta {
pub name: Sym,
pub kind: MetaKind,
}
/// Information attached to [Meta]data
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MetaKind {
Plain,
Equals(Literal),
Func(Vec<Literal>),
}
// Items
/// Anything that can appear at the top level of a [File]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Item {
pub span: Span,
pub attrs: Attrs,
pub vis: Visibility,
pub kind: ItemKind,
}
/// What kind of [Item] is this?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ItemKind {
// TODO: Trait declaration ("trait") item?
/// A [module](Module)
Module(Module),
/// A [type alias](Alias)
Alias(Alias),
/// An [enumerated type](Enum), with a discriminant and optional data
Enum(Enum),
/// A [structure](Struct)
Struct(Struct),
/// A [constant](Const)
Const(Const),
/// A [static](Static) variable
Static(Static),
/// A [function definition](Function)
Function(Function),
/// An [implementation](Impl)
Impl(Impl),
/// An [import](Use)
Use(Use),
}
/// A list of type variables to introduce
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Generics {
pub vars: Vec<Sym>,
}
/// An ordered collection of [Items](Item)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Module {
pub name: Sym,
pub file: Option<File>,
}
/// An alias to another [Ty]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Alias {
pub name: Sym,
pub from: Option<Box<Ty>>,
}
/// A compile-time constant
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Const {
pub name: Sym,
pub ty: Box<Ty>,
pub init: Box<Expr>,
}
/// A `static` variable
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Static {
pub mutable: Mutability,
pub name: Sym,
pub ty: Box<Ty>,
pub init: Box<Expr>,
}
/// Code, and the interface to that code
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Function {
pub name: Sym,
pub gens: Generics,
pub sign: TyFn,
pub bind: Pattern,
pub body: Option<Expr>,
}
/// A user-defined product type
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Struct {
pub name: Sym,
pub gens: Generics,
pub kind: StructKind,
}
/// Either a [Struct]'s [StructMember]s or tuple [Ty]pes, if present.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum StructKind {
Empty,
Tuple(Vec<Ty>),
Struct(Vec<StructMember>),
}
/// The [Visibility], [Sym], and [Ty]pe of a single [Struct] member
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct StructMember {
pub vis: Visibility,
pub name: Sym,
pub ty: Ty,
}
/// A user-defined sum type
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Enum {
pub name: Sym,
pub gens: Generics,
pub variants: Vec<Variant>,
}
/// A single [Enum] variant
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Variant {
pub name: Sym,
pub kind: StructKind,
pub body: Option<Box<Expr>>,
}
/// Sub-[items](Item) (associated functions, etc.) for a [Ty]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Impl {
pub gens: Generics,
pub target: ImplKind,
pub body: File,
}
// TODO: `impl` Trait for <Target> { }
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ImplKind {
Type(Ty),
Trait { impl_trait: Path, for_type: Box<Ty> },
}
/// An import of nonlocal [Item]s
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Use {
pub absolute: bool,
pub tree: UseTree,
}
/// A tree of [Item] imports
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum UseTree {
Tree(Vec<UseTree>),
Path(PathPart, Box<UseTree>),
Alias(Sym, Sym),
Name(Sym),
Glob,
}
/// A type expression
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ty {
pub span: Span,
pub kind: TyKind,
pub gens: Generics,
}
/// Information about a [Ty]pe expression
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TyKind {
Never,
Infer,
Path(Path),
Array(TyArray),
Slice(TySlice),
Tuple(TyTuple),
Ref(TyRef),
Ptr(TyPtr),
Fn(TyFn),
}
/// An array of [`T`](Ty)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyArray {
pub ty: Box<Ty>,
pub count: usize,
}
/// A [Ty]pe slice expression: `[T]`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TySlice {
pub ty: Box<Ty>,
}
/// A tuple of [Ty]pes
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyTuple {
pub types: Vec<Ty>,
}
/// A [Ty]pe-reference expression as (number of `&`, [Path])
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyRef {
pub mutable: Mutability,
pub count: u16,
pub to: Box<Ty>,
}
/// A [Ty]pe-reference expression as (number of `&`, [Path])
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyPtr {
pub to: Box<Ty>,
}
/// The args and return value for a function pointer [Ty]pe
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyFn {
pub args: Box<Ty>,
pub rety: Box<Ty>,
}
/// A path to an [Item] in the [Module] tree
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Path {
pub absolute: bool,
pub parts: Vec<PathPart>,
}
/// A single component of a [Path]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PathPart {
SuperKw,
SelfTy,
Ident(Sym),
}
/// An abstract statement, and associated metadata
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Stmt {
pub span: Span,
pub kind: StmtKind,
pub semi: Semi,
}
/// Whether the [Stmt] is a [Let], [Item], or [Expr] statement
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum StmtKind {
Empty,
Item(Box<Item>),
Expr(Box<Expr>),
}
/// Whether or not a [Stmt] is followed by a semicolon
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Semi {
Terminated,
Unterminated,
}
/// An expression, the beating heart of the language
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Expr {
pub span: Span,
pub kind: ExprKind,
}
/// Any of the different [Expr]essions
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
pub enum ExprKind {
/// An empty expression: `(` `)`
#[default]
Empty,
/// A [Closure] expression: `|` [`Expr`] `|` ( -> [`Ty`])? [`Expr`]
Closure(Closure),
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
Tuple(Tuple),
/// A [Struct creation](Structor) expression: [Path] `{` ([Fielder] `,`)* [Fielder]? `}`
Structor(Structor),
/// An [Array] literal: `[` [`Expr`] (`,` [`Expr`])\* `]`
Array(Array),
/// An Array literal constructed with [repeat syntax](ArrayRep)
/// `[` [Expr] `;` [Literal] `]`
ArrayRep(ArrayRep),
/// An address-of expression: `&` `mut`? [`Expr`]
AddrOf(AddrOf),
/// A backtick-quoted expression
Quote(Quote),
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
Literal(Literal),
/// A [Grouping](Group) expression `(` [`Expr`] `)`
Group(Group),
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
Block(Block),
/// An [Assign]ment expression: [`Expr`] (`=` [`Expr`])\+
Assign(Assign),
/// A [Modify]-assignment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
Modify(Modify),
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
Binary(Binary),
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
Unary(Unary),
/// A [Member] access expression: [`Expr`] [`MemberKind`]\*
Member(Member),
/// An Array [Index] expression: a[10, 20, 30]
Index(Index),
/// A [Cast] expression: [`Expr`] `as` [`Ty`]
Cast(Cast),
/// A [path expression](Path): `::`? [PathPart] (`::` [PathPart])*
Path(Path),
/// A local bind instruction, `let` [`Sym`] `=` [`Expr`]
Let(Let),
/// A [Match] expression: `match` [Expr] `{` ([MatchArm] `,`)* [MatchArm]? `}`
Match(Match),
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
While(While),
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
If(If),
/// A [For] expression: `for` [`Pattern`] `in` [`Expr`] [`Block`] [`Else`]?
For(For),
/// A [Break] expression: `break` [`Expr`]?
Break(Break),
/// A [Return] expression `return` [`Expr`]?
Return(Return),
/// A continue expression: `continue`
Continue,
}
/// A Closure [expression](Expr): `|` [`Expr`] `|` ( -> [`Ty`])? [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Closure {
pub arg: Box<Pattern>,
pub body: Box<Expr>,
}
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Tuple {
pub exprs: Vec<Expr>,
}
/// A [Struct creation](Structor) expression: [Path] `{` ([Fielder] `,`)* [Fielder]? `}`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Structor {
pub to: Path,
pub init: Vec<Fielder>,
}
/// A [Struct field initializer] expression: [Sym] (`=` [Expr])?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Fielder {
pub name: Sym,
pub init: Option<Box<Expr>>,
}
/// An [Array] literal: `[` [`Expr`] (`,` [`Expr`])\* `]`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Array {
pub values: Vec<Expr>,
}
/// An Array literal constructed with [repeat syntax](ArrayRep)
/// `[` [Expr] `;` [Literal] `]`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ArrayRep {
pub value: Box<Expr>,
pub repeat: Box<Expr>,
}
/// An address-of expression: `&` `mut`? [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AddrOf {
pub mutable: Mutability,
pub expr: Box<Expr>,
}
/// A cast expression: [`Expr`] `as` [`Ty`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Cast {
pub head: Box<Expr>,
pub ty: Ty,
}
/// A backtick-quoted subexpression-literal
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Quote {
pub quote: Box<Expr>,
}
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Literal {
Bool(bool),
Char(char),
Int(u128),
Float(u64),
String(String),
}
/// A [Grouping](Group) expression `(` [`Expr`] `)`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Group {
pub expr: Box<Expr>,
}
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Block {
pub stmts: Vec<Stmt>,
}
/// An [Assign]ment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Assign {
pub parts: Box<(Expr, Expr)>,
}
/// A [Modify]-assignment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Modify {
pub kind: ModifyKind,
pub parts: Box<(Expr, Expr)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ModifyKind {
And,
Or,
Xor,
Shl,
Shr,
Add,
Sub,
Mul,
Div,
Rem,
}
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Binary {
pub kind: BinaryKind,
pub parts: Box<(Expr, Expr)>,
}
/// A [Binary] operator
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BinaryKind {
Lt,
LtEq,
Equal,
NotEq,
GtEq,
Gt,
RangeExc,
RangeInc,
LogAnd,
LogOr,
LogXor,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
Add,
Sub,
Mul,
Div,
Rem,
Call,
}
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Unary {
pub kind: UnaryKind,
pub tail: Box<Expr>,
}
/// A [Unary] operator
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UnaryKind {
Deref,
Neg,
Not,
RangeInc,
RangeExc,
/// A Loop expression: `loop` [`Block`]
Loop,
/// Unused
At,
/// Unused
Tilde,
}
/// A [Member] access expression: [`Expr`] [`MemberKind`]\*
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Member {
pub head: Box<Expr>,
pub kind: MemberKind,
}
/// The kind of [Member] access
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MemberKind {
Call(Sym, Tuple),
Struct(Sym),
Tuple(Literal),
}
/// A repeated [Index] expression: a[10, 20, 30][40, 50, 60]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Index {
pub head: Box<Expr>,
pub indices: Vec<Expr>,
}
/// A local variable declaration [Stmt]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Let {
pub mutable: Mutability,
pub name: Pattern,
pub ty: Option<Box<Ty>>,
pub init: Option<Box<Expr>>,
}
/// A `match` expression: `match` `{` ([MatchArm] `,`)* [MatchArm]? `}`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Match {
pub scrutinee: Box<Expr>,
pub arms: Vec<MatchArm>,
}
/// A single arm of a [Match] expression: [`Pattern`] `=>` [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MatchArm(pub Pattern, pub Expr);
/// A [Pattern] meta-expression (any [`ExprKind`] that fits pattern rules)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Pattern {
Name(Sym),
Path(Path),
Literal(Literal),
Rest(Option<Box<Pattern>>),
Ref(Mutability, Box<Pattern>),
RangeExc(Box<Pattern>, Box<Pattern>),
RangeInc(Box<Pattern>, Box<Pattern>),
Tuple(Vec<Pattern>),
Array(Vec<Pattern>),
Struct(Path, Vec<(Sym, Option<Pattern>)>),
TupleStruct(Path, Vec<Pattern>),
}
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct While {
pub cond: Box<Expr>,
pub pass: Box<Block>,
pub fail: Else,
}
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct If {
pub cond: Box<Expr>,
pub pass: Box<Block>,
pub fail: Else,
}
/// A [For] expression: `for` Pattern `in` [`Expr`] [`Block`] [`Else`]?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct For {
pub bind: Pattern,
pub cond: Box<Expr>,
pub pass: Box<Block>,
pub fail: Else,
}
/// The (optional) `else` clause of a [While], [If], or [For] expression
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Else {
pub body: Option<Box<Expr>>,
}
/// A [Break] expression: `break` [`Expr`]?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Break {
pub body: Option<Box<Expr>>,
}
/// A [Return] expression `return` [`Expr`]?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Return {
pub body: Option<Box<Expr>>,
}

View File

@@ -0,0 +1,8 @@
//! Implementations of AST nodes and traits
use super::*;
mod convert;
mod display;
mod path;
pub(crate) mod weight_of;

View File

@@ -0,0 +1,162 @@
//! Converts between major enums and enum variants
use super::*;
impl<T: AsRef<str>> From<T> for PathPart {
fn from(value: T) -> Self {
match value.as_ref() {
"super" => PathPart::SuperKw,
ident => PathPart::Ident(ident.into()),
}
}
}
macro impl_from ($(impl From for $T:ty {$($from:ty => $to:expr),*$(,)?})*) {$($(
impl From<$from> for $T {
fn from(value: $from) -> Self {
$to(value.into()) // Uses *tuple constructor*
}
}
impl From<Box<$from>> for $T {
fn from(value: Box<$from>) -> Self {
$to((*value).into())
}
}
)*)*}
impl_from! {
impl From for ItemKind {
Alias => ItemKind::Alias,
Const => ItemKind::Const,
Static => ItemKind::Static,
Module => ItemKind::Module,
Function => ItemKind::Function,
Struct => ItemKind::Struct,
Enum => ItemKind::Enum,
Impl => ItemKind::Impl,
Use => ItemKind::Use,
}
impl From for StructKind {
Vec<Ty> => StructKind::Tuple,
// TODO: Struct members in struct
}
impl From for TyKind {
Path => TyKind::Path,
TyTuple => TyKind::Tuple,
TyRef => TyKind::Ref,
TyPtr => TyKind::Ptr,
TyFn => TyKind::Fn,
}
impl From for StmtKind {
Item => StmtKind::Item,
Expr => StmtKind::Expr,
}
impl From for ExprKind {
Let => ExprKind::Let,
Closure => ExprKind::Closure,
Quote => ExprKind::Quote,
Match => ExprKind::Match,
Assign => ExprKind::Assign,
Modify => ExprKind::Modify,
Binary => ExprKind::Binary,
Unary => ExprKind::Unary,
Cast => ExprKind::Cast,
Member => ExprKind::Member,
Index => ExprKind::Index,
Path => ExprKind::Path,
Literal => ExprKind::Literal,
Array => ExprKind::Array,
ArrayRep => ExprKind::ArrayRep,
AddrOf => ExprKind::AddrOf,
Block => ExprKind::Block,
Group => ExprKind::Group,
Tuple => ExprKind::Tuple,
While => ExprKind::While,
If => ExprKind::If,
For => ExprKind::For,
Break => ExprKind::Break,
Return => ExprKind::Return,
}
impl From for Literal {
bool => Literal::Bool,
char => Literal::Char,
u128 => Literal::Int,
String => Literal::String,
}
}
impl From<Option<Expr>> for Else {
fn from(value: Option<Expr>) -> Self {
Self { body: value.map(Into::into) }
}
}
impl From<Expr> for Else {
fn from(value: Expr) -> Self {
Self { body: Some(value.into()) }
}
}
impl TryFrom<Expr> for Pattern {
type Error = Expr;
/// Performs the conversion. On failure, returns the *first* non-pattern subexpression.
fn try_from(value: Expr) -> Result<Self, Self::Error> {
Ok(match value.kind {
ExprKind::Literal(literal) => Pattern::Literal(literal),
ExprKind::Path(Path { absolute: false, ref parts }) => match parts.as_slice() {
[PathPart::Ident(name)] => Pattern::Name(*name),
_ => Err(value)?,
},
ExprKind::Empty => Pattern::Tuple(vec![]),
ExprKind::Group(Group { expr }) => Pattern::Tuple(vec![Pattern::try_from(*expr)?]),
ExprKind::Tuple(Tuple { exprs }) => Pattern::Tuple(
exprs
.into_iter()
.map(Pattern::try_from)
.collect::<Result<_, _>>()?,
),
ExprKind::AddrOf(AddrOf { mutable, expr }) => {
Pattern::Ref(mutable, Box::new(Pattern::try_from(*expr)?))
}
ExprKind::Array(Array { values }) => Pattern::Array(
values
.into_iter()
.map(Pattern::try_from)
.collect::<Result<_, _>>()?,
),
ExprKind::Binary(Binary { kind: BinaryKind::Call, parts }) => {
let (Expr { kind: ExprKind::Path(path), .. }, args) = *parts else {
return Err(parts.0);
};
match args.kind {
ExprKind::Empty | ExprKind::Tuple(_) => {}
_ => return Err(args),
}
let Pattern::Tuple(args) = Pattern::try_from(args)? else {
unreachable!("Arguments should be convertible to pattern!")
};
Pattern::TupleStruct(path, args)
}
ExprKind::Binary(Binary { kind: BinaryKind::RangeExc, parts }) => {
let (head, tail) = (Pattern::try_from(parts.0)?, Pattern::try_from(parts.1)?);
Pattern::RangeExc(head.into(), tail.into())
}
ExprKind::Binary(Binary { kind: BinaryKind::RangeInc, parts }) => {
let (head, tail) = (Pattern::try_from(parts.0)?, Pattern::try_from(parts.1)?);
Pattern::RangeInc(head.into(), tail.into())
}
ExprKind::Unary(Unary { kind: UnaryKind::RangeExc, tail }) => {
Pattern::Rest(Some(Pattern::try_from(*tail)?.into()))
}
ExprKind::Structor(Structor { to, init }) => {
let fields = init
.into_iter()
.map(|Fielder { name, init }| {
Ok((name, init.map(|i| Pattern::try_from(*i)).transpose()?))
})
.collect::<Result<_, Self::Error>>()?;
Pattern::Struct(to, fields)
}
_ => Err(value)?,
})
}
}

View File

@@ -0,0 +1,773 @@
//! Implements [Display] for [AST](super::super) Types
use super::*;
use format::{delimiters::*, *};
use std::{
borrow::Borrow,
fmt::{Display, Write},
};
fn separate<I: Display, W: Write>(
iterable: impl IntoIterator<Item = I>,
sep: &'static str,
) -> impl FnOnce(W) -> std::fmt::Result {
move |mut f| {
for (idx, item) in iterable.into_iter().enumerate() {
if idx > 0 {
f.write_str(sep)?;
}
write!(f, "{item}")?;
}
Ok(())
}
}
impl Display for Mutability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Mutability::Not => Ok(()),
Mutability::Mut => "mut ".fmt(f),
}
}
}
impl Display for Visibility {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Visibility::Private => Ok(()),
Visibility::Public => "pub ".fmt(f),
}
}
}
impl Display for Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Literal::Bool(v) => v.fmt(f),
Literal::Char(v) => write!(f, "'{}'", v.escape_debug()),
Literal::Int(v) => v.fmt(f),
Literal::Float(v) => write!(f, "{:?}", f64::from_bits(*v)),
Literal::String(v) => write!(f, "\"{}\"", v.escape_debug()),
}
}
}
impl Display for File {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
separate(&self.items, "\n\n")(f)
}
}
impl Display for Attrs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { meta } = self;
if meta.is_empty() {
return Ok(());
}
"#".fmt(f)?;
separate(meta, ", ")(&mut f.delimit(INLINE_SQUARE))?;
"\n".fmt(f)
}
}
impl Display for Meta {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, kind } = self;
write!(f, "{name}{kind}")
}
}
impl Display for MetaKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MetaKind::Plain => Ok(()),
MetaKind::Equals(v) => write!(f, " = {v}"),
MetaKind::Func(args) => separate(args, ", ")(f.delimit(INLINE_PARENS)),
}
}
}
impl Display for Item {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { span: _, attrs, vis, kind } = self;
attrs.fmt(f)?;
vis.fmt(f)?;
kind.fmt(f)
}
}
impl Display for ItemKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ItemKind::Alias(v) => v.fmt(f),
ItemKind::Const(v) => v.fmt(f),
ItemKind::Static(v) => v.fmt(f),
ItemKind::Module(v) => v.fmt(f),
ItemKind::Function(v) => v.fmt(f),
ItemKind::Struct(v) => v.fmt(f),
ItemKind::Enum(v) => v.fmt(f),
ItemKind::Impl(v) => v.fmt(f),
ItemKind::Use(v) => v.fmt(f),
}
}
}
impl Display for Generics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Generics { vars } = self;
if !vars.is_empty() {
separate(vars, ", ")(f.delimit_with("<", ">"))?
}
Ok(())
}
}
impl Display for Alias {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, from } = self;
match from {
Some(from) => write!(f, "type {name} = {from};"),
None => write!(f, "type {name};"),
}
}
}
impl Display for Const {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, ty, init } = self;
write!(f, "const {name}: {ty} = {init}")
}
}
impl Display for Static {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { mutable, name, ty, init } = self;
write!(f, "static {mutable}{name}: {ty} = {init}")
}
}
impl Display for Module {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, file } = self;
write!(f, "mod {name}")?;
match file {
Some(items) => {
' '.fmt(f)?;
write!(f.delimit(BRACES), "{items}")
}
None => Ok(()),
}
}
}
impl Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, gens, sign: sign @ TyFn { args, rety }, bind, body } = self;
let types = match args.kind {
TyKind::Tuple(TyTuple { ref types }) => types.as_slice(),
_ => {
write!(f, "Invalid function signature: {sign}")?;
Default::default()
}
};
let bind = match bind {
Pattern::Tuple(patterns) => patterns.as_slice(),
_ => {
write!(f, "Invalid argument binder: {bind}")?;
Default::default()
}
};
debug_assert_eq!(bind.len(), types.len());
write!(f, "fn {name}{gens} ")?;
{
let mut f = f.delimit(INLINE_PARENS);
for (idx, (arg, ty)) in bind.iter().zip(types.iter()).enumerate() {
if idx != 0 {
f.write_str(", ")?;
}
write!(f, "{arg}: {ty}")?;
}
}
if let TyKind::Tuple(TyTuple { types }) = &rety.kind
&& !types.as_slice().is_empty()
{
write!(f, " -> {rety}")?
}
match body {
Some(body) => write!(f, " {body}"),
None => ';'.fmt(f),
}
}
}
impl Display for Struct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, gens, kind } = self;
write!(f, "struct {name}{gens}{kind}")
}
}
impl Display for StructKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StructKind::Empty => ';'.fmt(f),
StructKind::Tuple(v) => separate(v, ", ")(f.delimit(INLINE_PARENS)),
StructKind::Struct(v) => separate(v, ",\n")(f.delimit(SPACED_BRACES)),
}
}
}
impl Display for StructMember {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { vis, name, ty } = self;
write!(f, "{vis}{name}: {ty}")
}
}
impl Display for Enum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, gens, variants } = self;
write!(f, "enum {name}{gens}")?;
separate(variants, ",\n")(f.delimit(SPACED_BRACES))
}
}
impl Display for Variant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, kind, body } = self;
write!(f, "{name}{kind}")?;
match body {
Some(body) => write!(f, " {body}"),
None => Ok(()),
}
}
}
impl Display for Impl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { gens, target, body } = self;
write!(f, "impl{gens} {target} ")?;
write!(f.delimit(BRACES), "{body}")
}
}
impl Display for ImplKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImplKind::Type(t) => t.fmt(f),
ImplKind::Trait { impl_trait, for_type } => {
write!(f, "{impl_trait} for {for_type}")
}
}
}
}
impl Display for Use {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { absolute, tree } = self;
f.write_str(if *absolute { "use ::" } else { "use " })?;
write!(f, "{tree};")
}
}
impl Display for UseTree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UseTree::Tree(tree) => separate(tree, ", ")(f.delimit(INLINE_BRACES)),
UseTree::Path(path, rest) => write!(f, "{path}::{rest}"),
UseTree::Alias(path, name) => write!(f, "{path} as {name}"),
UseTree::Name(name) => write!(f, "{name}"),
UseTree::Glob => write!(f, "*"),
}
}
}
impl Display for Ty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { span: _, kind, gens } = self;
write!(f, "{kind}{gens}")
}
}
impl Display for TyKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TyKind::Never => "!".fmt(f),
TyKind::Infer => "_".fmt(f),
TyKind::Path(v) => v.fmt(f),
TyKind::Array(v) => v.fmt(f),
TyKind::Slice(v) => v.fmt(f),
TyKind::Tuple(v) => v.fmt(f),
TyKind::Ref(v) => v.fmt(f),
TyKind::Ptr(v) => v.fmt(f),
TyKind::Fn(v) => v.fmt(f),
}
}
}
impl Display for TyArray {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { ty, count } = self;
write!(f, "[{ty}; {count}]")
}
}
impl Display for TySlice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { ty } = self;
write!(f, "[{ty}]")
}
}
impl Display for TyTuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
separate(&self.types, ", ")(f.delimit(INLINE_PARENS))
}
}
impl Display for TyRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let &Self { count, mutable, ref to } = self;
for _ in 0..count {
f.write_char('&')?;
}
write!(f, "{mutable}{to}")
}
}
impl Display for TyPtr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { to } = self;
write!(f, "*{to}")
}
}
impl Display for TyFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { args, rety } = self;
write!(f, "fn {args}")?;
if let TyKind::Tuple(TyTuple { types }) = &rety.kind
&& !types.as_slice().is_empty()
{
write!(f, " -> {rety}")?
}
Ok(())
}
}
impl Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { absolute, parts } = self;
if *absolute {
"::".fmt(f)?;
}
separate(parts, "::")(f)
}
}
impl Display for PathPart {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PathPart::SuperKw => "super".fmt(f),
PathPart::SelfTy => "Self".fmt(f),
PathPart::Ident(id) => id.fmt(f),
}
}
}
impl Display for Stmt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Stmt { span: _, kind, semi } = self;
write!(f, "{kind}{semi}")
}
}
impl Display for StmtKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StmtKind::Empty => Ok(()),
StmtKind::Item(v) => v.fmt(f),
StmtKind::Expr(v) => v.fmt(f),
}
}
}
impl Display for Semi {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Semi::Terminated => ';'.fmt(f),
Semi::Unterminated => Ok(()),
}
}
}
impl Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.kind.fmt(f)
}
}
impl Display for ExprKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExprKind::Empty => "()".fmt(f),
ExprKind::Closure(v) => v.fmt(f),
ExprKind::Quote(v) => v.fmt(f),
ExprKind::Let(v) => v.fmt(f),
ExprKind::Match(v) => v.fmt(f),
ExprKind::Assign(v) => v.fmt(f),
ExprKind::Modify(v) => v.fmt(f),
ExprKind::Binary(v) => v.fmt(f),
ExprKind::Unary(v) => v.fmt(f),
ExprKind::Cast(v) => v.fmt(f),
ExprKind::Member(v) => v.fmt(f),
ExprKind::Index(v) => v.fmt(f),
ExprKind::Structor(v) => v.fmt(f),
ExprKind::Path(v) => v.fmt(f),
ExprKind::Literal(v) => v.fmt(f),
ExprKind::Array(v) => v.fmt(f),
ExprKind::ArrayRep(v) => v.fmt(f),
ExprKind::AddrOf(v) => v.fmt(f),
ExprKind::Block(v) => v.fmt(f),
ExprKind::Group(v) => v.fmt(f),
ExprKind::Tuple(v) => v.fmt(f),
ExprKind::While(v) => v.fmt(f),
ExprKind::If(v) => v.fmt(f),
ExprKind::For(v) => v.fmt(f),
ExprKind::Break(v) => v.fmt(f),
ExprKind::Return(v) => v.fmt(f),
ExprKind::Continue => "continue".fmt(f),
}
}
}
impl Display for Closure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { arg, body } = self;
match arg.as_ref() {
Pattern::Tuple(args) => separate(args, ", ")(f.delimit_with("|", "|")),
_ => arg.fmt(f),
}?;
write!(f, " {body}")
}
}
impl Display for Quote {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { quote } = self;
write!(f, "`{quote}`")
}
}
impl Display for Let {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { mutable, name, ty, init } = self;
write!(f, "let {mutable}{name}")?;
if let Some(value) = ty {
write!(f, ": {value}")?;
}
if let Some(value) = init {
write!(f, " = {value}")?;
}
Ok(())
}
}
impl Display for Pattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Pattern::Name(sym) => sym.fmt(f),
Pattern::Path(path) => path.fmt(f),
Pattern::Literal(literal) => literal.fmt(f),
Pattern::Rest(Some(name)) => write!(f, "..{name}"),
Pattern::Rest(None) => "..".fmt(f),
Pattern::Ref(mutability, pattern) => write!(f, "&{mutability}{pattern}"),
Pattern::RangeExc(head, tail) => write!(f, "{head}..{tail}"),
Pattern::RangeInc(head, tail) => write!(f, "{head}..={tail}"),
Pattern::Tuple(patterns) => separate(patterns, ", ")(f.delimit(INLINE_PARENS)),
Pattern::Array(patterns) => separate(patterns, ", ")(f.delimit(INLINE_SQUARE)),
Pattern::Struct(path, items) => {
write!(f, "{path} ")?;
let f = &mut f.delimit(INLINE_BRACES);
for (idx, (name, item)) in items.iter().enumerate() {
if idx != 0 {
f.write_str(", ")?;
}
write!(f, "{name}")?;
if let Some(pattern) = item {
write!(f, ": {pattern}")?
}
}
Ok(())
}
Pattern::TupleStruct(path, items) => {
write!(f, "{path}")?;
separate(items, ", ")(f.delimit(INLINE_PARENS))
}
}
}
}
impl Display for Match {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { scrutinee, arms } = self;
write!(f, "match {scrutinee} ")?;
separate(arms, ",\n")(f.delimit(BRACES))
}
}
impl Display for MatchArm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(pat, expr) = self;
write!(f, "{pat} => {expr}")
}
}
impl Display for Assign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { parts } = self;
write!(f, "{} = {}", parts.0, parts.1)
}
}
impl Display for Modify {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { kind, parts } = self;
write!(f, "{} {kind} {}", parts.0, parts.1)
}
}
impl Display for ModifyKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ModifyKind::Mul => "*=",
ModifyKind::Div => "/=",
ModifyKind::Rem => "%=",
ModifyKind::Add => "+=",
ModifyKind::Sub => "-=",
ModifyKind::And => "&=",
ModifyKind::Or => "|=",
ModifyKind::Xor => "^=",
ModifyKind::Shl => "<<=",
ModifyKind::Shr => ">>=",
}
.fmt(f)
}
}
impl Display for Binary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { kind, parts } = self;
let (head, tail) = parts.borrow();
match kind {
BinaryKind::Call => write!(f, "{head}{tail}"),
_ => write!(f, "{head} {kind} {tail}"),
}
}
}
impl Display for BinaryKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BinaryKind::Lt => "<",
BinaryKind::LtEq => "<=",
BinaryKind::Equal => "==",
BinaryKind::NotEq => "!=",
BinaryKind::GtEq => ">=",
BinaryKind::Gt => ">",
BinaryKind::RangeExc => "..",
BinaryKind::RangeInc => "..=",
BinaryKind::LogAnd => "&&",
BinaryKind::LogOr => "||",
BinaryKind::LogXor => "^^",
BinaryKind::BitAnd => "&",
BinaryKind::BitOr => "|",
BinaryKind::BitXor => "^",
BinaryKind::Shl => "<<",
BinaryKind::Shr => ">>",
BinaryKind::Add => "+",
BinaryKind::Sub => "-",
BinaryKind::Mul => "*",
BinaryKind::Div => "/",
BinaryKind::Rem => "%",
BinaryKind::Call => "()",
}
.fmt(f)
}
}
impl Display for Unary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { kind, tail } = self;
write!(f, "{kind}{tail}")
}
}
impl Display for UnaryKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnaryKind::Loop => "loop ",
UnaryKind::Deref => "*",
UnaryKind::Neg => "-",
UnaryKind::Not => "!",
UnaryKind::RangeExc => "..",
UnaryKind::RangeInc => "..=",
UnaryKind::At => "@",
UnaryKind::Tilde => "~",
}
.fmt(f)
}
}
impl Display for Cast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head, ty } = self;
write!(f, "{head} as {ty}")
}
}
impl Display for Member {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head, kind } = self;
write!(f, "{head}.{kind}")
}
}
impl Display for MemberKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MemberKind::Call(name, args) => write!(f, "{name}{args}"),
MemberKind::Struct(name) => write!(f, "{name}"),
MemberKind::Tuple(name) => write!(f, "{name}"),
}
}
}
impl Display for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head, indices } = self;
write!(f, "{head}")?;
separate(indices, ", ")(f.delimit(INLINE_SQUARE))
}
}
impl Display for Structor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { to, init } = self;
write!(f, "{to} ")?;
separate(init, ", ")(f.delimit(INLINE_BRACES))
}
}
impl Display for Fielder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, init } = self;
write!(f, "{name}")?;
if let Some(init) = init {
write!(f, ": {init}")?;
}
Ok(())
}
}
impl Display for Array {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
separate(&self.values, ", ")(f.delimit(INLINE_SQUARE))
}
}
impl Display for ArrayRep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { value, repeat } = self;
write!(f, "[{value}; {repeat}]")
}
}
impl Display for AddrOf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { mutable, expr } = self;
write!(f, "&{mutable}{expr}")
}
}
impl Display for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { stmts } = self;
match stmts.as_slice() {
[] => "{}".fmt(f),
stmts => separate(stmts, "\n")(f.delimit(BRACES)),
}
}
}
impl Display for Group {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({})", self.expr)
}
}
impl Display for Tuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { exprs } = self;
match exprs.as_slice() {
[] => write!(f, "()"),
[expr] => write!(f, "({expr},)"),
exprs => separate(exprs, ", ")(f.delimit(INLINE_PARENS)),
}
}
}
impl Display for While {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { cond, pass, fail } = self;
write!(f, "while {cond} {pass}{fail}")
}
}
impl Display for If {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { cond, pass, fail } = self;
write!(f, "if {cond} {pass}{fail}")
}
}
impl Display for For {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { bind, cond, pass, fail } = self;
write!(f, "for {bind} in {cond} {pass}{fail}")
}
}
impl Display for Else {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.body {
Some(body) => write!(f, " else {body}"),
_ => Ok(()),
}
}
}
impl Display for Break {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "break")?;
match &self.body {
Some(body) => write!(f, " {body}"),
_ => Ok(()),
}
}
}
impl Display for Return {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "return")?;
match &self.body {
Some(body) => write!(f, " {body}"),
_ => Ok(()),
}
}
}

View File

@@ -0,0 +1,60 @@
//! Utils for [Path]
use crate::{PathPart, Sym, ast::Path};
impl Path {
/// Appends a [PathPart] to this [Path]
pub fn push(&mut self, part: PathPart) {
self.parts.push(part);
}
/// Removes a [PathPart] from this [Path]
pub fn pop(&mut self) -> Option<PathPart> {
self.parts.pop()
}
/// Concatenates `self::other`. If `other` is an absolute [Path],
/// this replaces `self` with `other`
pub fn concat(mut self, other: &Self) -> Self {
if other.absolute {
other.clone()
} else {
self.parts.extend(other.parts.iter().cloned());
self
}
}
/// Gets the defining [Sym] of this path
pub fn as_sym(&self) -> Option<Sym> {
match self.parts.as_slice() {
[.., PathPart::Ident(name)] => Some(*name),
_ => None,
}
}
/// Checks whether this path ends in the given [Sym]
pub fn ends_with(&self, name: &str) -> bool {
match self.parts.as_slice() {
[.., PathPart::Ident(last)] => name == &**last,
_ => false,
}
}
/// Checks whether this path refers to the sinkhole identifier, `_`
pub fn is_sinkhole(&self) -> bool {
if let [PathPart::Ident(id)] = self.parts.as_slice()
&& let "_" = id.to_ref()
{
return true;
}
false
}
}
impl PathPart {
pub fn from_sym(ident: Sym) -> Self {
Self::Ident(ident)
}
}
impl From<Sym> for Path {
fn from(value: Sym) -> Self {
Self { parts: vec![PathPart::Ident(value)], absolute: false }
}
}

View File

@@ -0,0 +1,617 @@
//! Approximates the size of an AST
use std::mem::size_of_val;
use crate::ast::*;
use cl_structures::{intern::interned::Interned, span::Span};
/// Approximates the size of an AST without including indirection (pointers) or padding
pub trait WeightOf {
/// Approximates the size of a syntax tree without including pointer/indirection or padding.
fn weight_of(&self) -> usize;
}
impl WeightOf for File {
fn weight_of(&self) -> usize {
let Self { name, items } = self;
name.weight_of() + items.weight_of()
}
}
impl WeightOf for Attrs {
fn weight_of(&self) -> usize {
let Self { meta } = self;
meta.weight_of()
}
}
impl WeightOf for Meta {
fn weight_of(&self) -> usize {
let Self { name, kind } = self;
name.weight_of() + kind.weight_of()
}
}
impl WeightOf for MetaKind {
fn weight_of(&self) -> usize {
match self {
MetaKind::Plain => size_of_val(self),
MetaKind::Equals(v) => v.weight_of(),
MetaKind::Func(v) => v.weight_of(),
}
}
}
impl WeightOf for Item {
fn weight_of(&self) -> usize {
let Self { span, attrs, vis, kind } = self;
span.weight_of() + attrs.weight_of() + vis.weight_of() + kind.weight_of()
}
}
impl WeightOf for ItemKind {
fn weight_of(&self) -> usize {
match self {
ItemKind::Module(v) => v.weight_of(),
ItemKind::Alias(v) => v.weight_of(),
ItemKind::Enum(v) => v.weight_of(),
ItemKind::Struct(v) => v.weight_of(),
ItemKind::Const(v) => v.weight_of(),
ItemKind::Static(v) => v.weight_of(),
ItemKind::Function(v) => v.weight_of(),
ItemKind::Impl(v) => v.weight_of(),
ItemKind::Use(v) => v.weight_of(),
}
}
}
impl WeightOf for Generics {
fn weight_of(&self) -> usize {
let Self { vars } = self;
vars.iter().map(|v| v.weight_of()).sum()
}
}
impl WeightOf for Module {
fn weight_of(&self) -> usize {
let Self { name, file } = self;
name.weight_of() + file.weight_of()
}
}
impl WeightOf for Alias {
fn weight_of(&self) -> usize {
let Self { name, from } = self;
name.weight_of() + from.weight_of()
}
}
impl WeightOf for Const {
fn weight_of(&self) -> usize {
let Self { name, ty, init } = self;
name.weight_of() + ty.weight_of() + init.weight_of()
}
}
impl WeightOf for Static {
fn weight_of(&self) -> usize {
let Self { mutable, name, ty, init } = self;
mutable.weight_of() + name.weight_of() + ty.weight_of() + init.weight_of()
}
}
impl WeightOf for Function {
fn weight_of(&self) -> usize {
let Self { name, gens, sign, bind, body } = self;
name.weight_of() + gens.weight_of() + sign.weight_of() + bind.weight_of() + body.weight_of()
}
}
impl WeightOf for Struct {
fn weight_of(&self) -> usize {
let Self { name, gens, kind } = self;
name.weight_of() + gens.weight_of() + kind.weight_of()
}
}
impl WeightOf for StructKind {
fn weight_of(&self) -> usize {
match self {
StructKind::Empty => size_of_val(self),
StructKind::Tuple(items) => items.weight_of(),
StructKind::Struct(sm) => sm.weight_of(),
}
}
}
impl WeightOf for StructMember {
fn weight_of(&self) -> usize {
let Self { vis, name, ty } = self;
vis.weight_of() + name.weight_of() + ty.weight_of()
}
}
impl WeightOf for Enum {
fn weight_of(&self) -> usize {
let Self { name, gens, variants } = self;
name.weight_of() + gens.weight_of() + variants.weight_of()
}
}
impl WeightOf for Variant {
fn weight_of(&self) -> usize {
let Self { name, kind, body } = self;
name.weight_of() + kind.weight_of() + body.weight_of()
}
}
impl WeightOf for Impl {
fn weight_of(&self) -> usize {
let Self { gens, target, body } = self;
gens.weight_of() + target.weight_of() + body.weight_of()
}
}
impl WeightOf for ImplKind {
fn weight_of(&self) -> usize {
match self {
ImplKind::Type(ty) => ty.weight_of(),
ImplKind::Trait { impl_trait, for_type } => {
impl_trait.weight_of() + for_type.weight_of()
}
}
}
}
impl WeightOf for Use {
fn weight_of(&self) -> usize {
let Self { absolute, tree } = self;
absolute.weight_of() + tree.weight_of()
}
}
impl WeightOf for UseTree {
fn weight_of(&self) -> usize {
match self {
UseTree::Tree(tr) => tr.weight_of(),
UseTree::Path(pa, tr) => pa.weight_of() + tr.weight_of(),
UseTree::Alias(src, dst) => src.weight_of() + dst.weight_of(),
UseTree::Name(src) => src.weight_of(),
UseTree::Glob => size_of_val(self),
}
}
}
impl WeightOf for Ty {
fn weight_of(&self) -> usize {
let Self { span, kind, gens } = self;
span.weight_of() + kind.weight_of() + gens.weight_of()
}
}
impl WeightOf for TyKind {
fn weight_of(&self) -> usize {
match self {
TyKind::Never | TyKind::Infer => size_of_val(self),
TyKind::Path(v) => v.weight_of(),
TyKind::Array(v) => v.weight_of(),
TyKind::Slice(v) => v.weight_of(),
TyKind::Tuple(v) => v.weight_of(),
TyKind::Ref(v) => v.weight_of(),
TyKind::Ptr(v) => v.weight_of(),
TyKind::Fn(v) => v.weight_of(),
}
}
}
impl WeightOf for TyArray {
fn weight_of(&self) -> usize {
let Self { ty, count } = self;
ty.weight_of() + count.weight_of()
}
}
impl WeightOf for TySlice {
fn weight_of(&self) -> usize {
let Self { ty } = self;
ty.weight_of()
}
}
impl WeightOf for TyTuple {
fn weight_of(&self) -> usize {
let Self { types } = self;
types.weight_of()
}
}
impl WeightOf for TyRef {
fn weight_of(&self) -> usize {
let Self { mutable, count, to } = self;
mutable.weight_of() + count.weight_of() + to.weight_of()
}
}
impl WeightOf for TyPtr {
fn weight_of(&self) -> usize {
let Self { to } = self;
to.weight_of()
}
}
impl WeightOf for TyFn {
fn weight_of(&self) -> usize {
let Self { args, rety } = self;
args.weight_of() + rety.weight_of()
}
}
impl WeightOf for Path {
fn weight_of(&self) -> usize {
let Self { absolute, parts } = self;
absolute.weight_of() + parts.weight_of()
}
}
impl WeightOf for PathPart {
fn weight_of(&self) -> usize {
match self {
PathPart::SuperKw => size_of_val(self),
PathPart::SelfTy => size_of_val(self),
PathPart::Ident(interned) => interned.weight_of(),
}
}
}
impl WeightOf for Stmt {
fn weight_of(&self) -> usize {
let Self { span, kind, semi } = self;
span.weight_of() + kind.weight_of() + semi.weight_of()
}
}
impl WeightOf for StmtKind {
fn weight_of(&self) -> usize {
match self {
StmtKind::Empty => size_of_val(self),
StmtKind::Item(item) => item.weight_of(),
StmtKind::Expr(expr) => expr.weight_of(),
}
}
}
impl WeightOf for Expr {
fn weight_of(&self) -> usize {
let Self { span, kind } = self;
span.weight_of() + kind.weight_of()
}
}
impl WeightOf for ExprKind {
fn weight_of(&self) -> usize {
match self {
ExprKind::Empty => size_of_val(self),
ExprKind::Closure(v) => v.weight_of(),
ExprKind::Quote(v) => v.weight_of(),
ExprKind::Let(v) => v.weight_of(),
ExprKind::Match(v) => v.weight_of(),
ExprKind::Assign(v) => v.weight_of(),
ExprKind::Modify(v) => v.weight_of(),
ExprKind::Binary(v) => v.weight_of(),
ExprKind::Unary(v) => v.weight_of(),
ExprKind::Cast(v) => v.weight_of(),
ExprKind::Member(v) => v.weight_of(),
ExprKind::Index(v) => v.weight_of(),
ExprKind::Structor(v) => v.weight_of(),
ExprKind::Path(v) => v.weight_of(),
ExprKind::Literal(v) => v.weight_of(),
ExprKind::Array(v) => v.weight_of(),
ExprKind::ArrayRep(v) => v.weight_of(),
ExprKind::AddrOf(v) => v.weight_of(),
ExprKind::Block(v) => v.weight_of(),
ExprKind::Group(v) => v.weight_of(),
ExprKind::Tuple(v) => v.weight_of(),
ExprKind::While(v) => v.weight_of(),
ExprKind::If(v) => v.weight_of(),
ExprKind::For(v) => v.weight_of(),
ExprKind::Break(v) => v.weight_of(),
ExprKind::Return(v) => v.weight_of(),
ExprKind::Continue => size_of_val(self),
}
}
}
impl WeightOf for Closure {
fn weight_of(&self) -> usize {
let Self { arg, body } = self;
arg.weight_of() + body.weight_of()
}
}
impl WeightOf for Quote {
fn weight_of(&self) -> usize {
let Self { quote } = self;
quote.weight_of()
}
}
impl WeightOf for Let {
fn weight_of(&self) -> usize {
let Self { mutable, name, ty, init } = self;
mutable.weight_of() + name.weight_of() + ty.weight_of() + init.weight_of()
}
}
impl WeightOf for Pattern {
fn weight_of(&self) -> usize {
match self {
Pattern::Name(s) => size_of_val(s),
Pattern::Path(p) => p.weight_of(),
Pattern::Literal(literal) => literal.weight_of(),
Pattern::Rest(Some(pattern)) => pattern.weight_of(),
Pattern::Rest(None) => 0,
Pattern::Ref(mutability, pattern) => mutability.weight_of() + pattern.weight_of(),
Pattern::RangeExc(head, tail) => head.weight_of() + tail.weight_of(),
Pattern::RangeInc(head, tail) => head.weight_of() + tail.weight_of(),
Pattern::Tuple(patterns) | Pattern::Array(patterns) => patterns.weight_of(),
Pattern::Struct(path, items) => {
let sitems: usize = items
.iter()
.map(|(name, opt)| name.weight_of() + opt.weight_of())
.sum();
path.weight_of() + sitems
}
Pattern::TupleStruct(path, patterns) => path.weight_of() + patterns.weight_of(),
}
}
}
impl WeightOf for Match {
fn weight_of(&self) -> usize {
let Self { scrutinee, arms } = self;
scrutinee.weight_of() + arms.weight_of()
}
}
impl WeightOf for MatchArm {
fn weight_of(&self) -> usize {
let Self(pattern, expr) = self;
pattern.weight_of() + expr.weight_of()
}
}
impl WeightOf for Assign {
fn weight_of(&self) -> usize {
let Self { parts } = self;
parts.0.weight_of() + parts.1.weight_of()
}
}
impl WeightOf for Modify {
#[rustfmt::skip]
fn weight_of(&self) -> usize {
let Self { kind, parts } = self;
kind.weight_of()
+ parts.0.weight_of()
+ parts.1.weight_of()
}
}
impl WeightOf for Binary {
fn weight_of(&self) -> usize {
let Self { kind, parts } = self;
kind.weight_of() + parts.0.weight_of() + parts.1.weight_of()
}
}
impl WeightOf for Unary {
#[rustfmt::skip]
fn weight_of(&self) -> usize {
let Self { kind, tail } = self;
kind.weight_of() + tail.weight_of()
}
}
impl WeightOf for Cast {
fn weight_of(&self) -> usize {
let Self { head, ty } = self;
head.weight_of() + ty.weight_of()
}
}
impl WeightOf for Member {
fn weight_of(&self) -> usize {
let Self { head, kind } = self;
head.weight_of() + kind.weight_of() // accounting
}
}
impl WeightOf for MemberKind {
fn weight_of(&self) -> usize {
match self {
MemberKind::Call(_, tuple) => tuple.weight_of(),
MemberKind::Struct(_) => 0,
MemberKind::Tuple(literal) => literal.weight_of(),
}
}
}
impl WeightOf for Index {
fn weight_of(&self) -> usize {
let Self { head, indices } = self;
head.weight_of() + indices.weight_of()
}
}
impl WeightOf for Literal {
fn weight_of(&self) -> usize {
match self {
Literal::Bool(v) => v.weight_of(),
Literal::Char(v) => v.weight_of(),
Literal::Int(v) => v.weight_of(),
Literal::Float(v) => v.weight_of(),
Literal::String(v) => v.weight_of(),
}
}
}
impl WeightOf for Structor {
fn weight_of(&self) -> usize {
let Self { to, init } = self;
to.weight_of() + init.weight_of()
}
}
impl WeightOf for Fielder {
fn weight_of(&self) -> usize {
let Self { name, init } = self;
name.weight_of() + init.weight_of()
}
}
impl WeightOf for Array {
fn weight_of(&self) -> usize {
let Self { values } = self;
values.weight_of()
}
}
impl WeightOf for ArrayRep {
fn weight_of(&self) -> usize {
let Self { value, repeat } = self;
value.weight_of() + repeat.weight_of()
}
}
impl WeightOf for AddrOf {
fn weight_of(&self) -> usize {
let Self { mutable, expr } = self;
mutable.weight_of() + expr.weight_of()
}
}
impl WeightOf for Block {
fn weight_of(&self) -> usize {
let Self { stmts } = self;
stmts.weight_of()
}
}
impl WeightOf for Group {
fn weight_of(&self) -> usize {
let Self { expr } = self;
expr.weight_of()
}
}
impl WeightOf for Tuple {
fn weight_of(&self) -> usize {
let Self { exprs } = self;
exprs.weight_of()
}
}
impl WeightOf for While {
fn weight_of(&self) -> usize {
let Self { cond, pass, fail } = self;
cond.weight_of() + pass.weight_of() + fail.weight_of()
}
}
impl WeightOf for If {
fn weight_of(&self) -> usize {
let Self { cond, pass, fail } = self;
cond.weight_of() + pass.weight_of() + fail.weight_of()
}
}
impl WeightOf for For {
fn weight_of(&self) -> usize {
let Self { bind, cond, pass, fail } = self;
bind.weight_of() + cond.weight_of() + pass.weight_of() + fail.weight_of()
}
}
impl WeightOf for Else {
fn weight_of(&self) -> usize {
let Self { body } = self;
body.weight_of()
}
}
impl WeightOf for Break {
fn weight_of(&self) -> usize {
let Self { body } = self;
body.weight_of()
}
}
impl WeightOf for Return {
fn weight_of(&self) -> usize {
let Self { body } = self;
body.weight_of()
}
}
// ------------ SizeOf Blanket Implementations
impl<T: WeightOf> WeightOf for Option<T> {
fn weight_of(&self) -> usize {
match self {
Some(t) => t.weight_of().max(size_of_val(t)),
None => size_of_val(self),
}
}
}
impl<T: WeightOf> WeightOf for [T] {
fn weight_of(&self) -> usize {
self.iter().map(WeightOf::weight_of).sum()
}
}
impl<T: WeightOf> WeightOf for Vec<T> {
fn weight_of(&self) -> usize {
size_of::<Self>() + self.iter().map(WeightOf::weight_of).sum::<usize>()
}
}
impl<T: WeightOf> WeightOf for Box<T> {
fn weight_of(&self) -> usize {
(**self).weight_of() + size_of::<Self>()
}
}
impl WeightOf for str {
fn weight_of(&self) -> usize {
self.len()
}
}
impl_size_of! {
// primitives
u8, u16, u32, u64, u128, usize,
i8, i16, i32, i64, i128, isize,
f32, f64, bool, char,
// cl-structures
Span,
// cl-ast
Visibility, Mutability, Semi, ModifyKind, BinaryKind, UnaryKind
}
impl<T> WeightOf for Interned<'_, T> {
fn weight_of(&self) -> usize {
size_of_val(self) // interned values are opaque to SizeOF
}
}
macro impl_size_of($($T:ty),*$(,)?) {
$(impl WeightOf for $T {
fn weight_of(&self) -> usize {
::std::mem::size_of_val(self)
}
})*
}

View File

@@ -0,0 +1,12 @@
//! Contains an [immutable visitor](Visit) and an [owned folder](Fold) trait,
//! with default implementations across the entire AST
pub mod fold;
pub mod visit;
pub mod walk;
pub use fold::Fold;
pub use visit::Visit;
pub use walk::Walk;

View File

@@ -0,0 +1,595 @@
//! A folder (implementer of the [Fold] trait) maps ASTs to ASTs
use crate::ast::*;
use cl_structures::span::Span;
/// Deconstructs the entire AST, and reconstructs it from scratch.
///
/// Each method acts as a customization point.
///
/// There are a set of default implementations for enums
/// under the name [`or_fold_`*](or_fold_expr_kind),
/// provided for ease of use.
///
/// For all other nodes, traversal is *explicit*.
pub trait Fold {
fn fold_span(&mut self, span: Span) -> Span {
span
}
fn fold_mutability(&mut self, mutability: Mutability) -> Mutability {
mutability
}
fn fold_visibility(&mut self, visibility: Visibility) -> Visibility {
visibility
}
fn fold_sym(&mut self, ident: Sym) -> Sym {
ident
}
fn fold_literal(&mut self, lit: Literal) -> Literal {
or_fold_literal(self, lit)
}
fn fold_bool(&mut self, b: bool) -> bool {
b
}
fn fold_char(&mut self, c: char) -> char {
c
}
fn fold_int(&mut self, i: u128) -> u128 {
i
}
fn fold_smuggled_float(&mut self, f: u64) -> u64 {
f
}
fn fold_string(&mut self, s: String) -> String {
s
}
fn fold_file(&mut self, f: File) -> File {
let File { name, items } = f;
File { name, items: items.into_iter().map(|i| self.fold_item(i)).collect() }
}
fn fold_attrs(&mut self, a: Attrs) -> Attrs {
let Attrs { meta } = a;
Attrs { meta: meta.into_iter().map(|m| self.fold_meta(m)).collect() }
}
fn fold_meta(&mut self, m: Meta) -> Meta {
let Meta { name, kind } = m;
Meta { name: self.fold_sym(name), kind: self.fold_meta_kind(kind) }
}
fn fold_meta_kind(&mut self, kind: MetaKind) -> MetaKind {
or_fold_meta_kind(self, kind)
}
fn fold_item(&mut self, i: Item) -> Item {
let Item { span, attrs, vis, kind } = i;
Item {
span: self.fold_span(span),
attrs: self.fold_attrs(attrs),
vis: self.fold_visibility(vis),
kind: self.fold_item_kind(kind),
}
}
fn fold_item_kind(&mut self, kind: ItemKind) -> ItemKind {
or_fold_item_kind(self, kind)
}
fn fold_generics(&mut self, gens: Generics) -> Generics {
let Generics { vars } = gens;
Generics { vars: vars.into_iter().map(|sym| self.fold_sym(sym)).collect() }
}
fn fold_alias(&mut self, a: Alias) -> Alias {
let Alias { name, from } = a;
Alias { name: self.fold_sym(name), from: from.map(|from| Box::new(self.fold_ty(*from))) }
}
fn fold_const(&mut self, c: Const) -> Const {
let Const { name, ty, init } = c;
Const {
name: self.fold_sym(name),
ty: Box::new(self.fold_ty(*ty)),
init: Box::new(self.fold_expr(*init)),
}
}
fn fold_static(&mut self, s: Static) -> Static {
let Static { mutable, name, ty, init } = s;
Static {
mutable: self.fold_mutability(mutable),
name: self.fold_sym(name),
ty: Box::new(self.fold_ty(*ty)),
init: Box::new(self.fold_expr(*init)),
}
}
fn fold_module(&mut self, m: Module) -> Module {
let Module { name, file } = m;
Module { name: self.fold_sym(name), file: file.map(|v| self.fold_file(v)) }
}
fn fold_function(&mut self, f: Function) -> Function {
let Function { name, gens, sign, bind, body } = f;
Function {
name: self.fold_sym(name),
gens: self.fold_generics(gens),
sign: self.fold_ty_fn(sign),
bind: self.fold_pattern(bind),
body: body.map(|b| self.fold_expr(b)),
}
}
fn fold_struct(&mut self, s: Struct) -> Struct {
let Struct { name, gens, kind } = s;
Struct {
name: self.fold_sym(name),
gens: self.fold_generics(gens),
kind: self.fold_struct_kind(kind),
}
}
fn fold_struct_kind(&mut self, kind: StructKind) -> StructKind {
match kind {
StructKind::Empty => StructKind::Empty,
StructKind::Tuple(tys) => {
StructKind::Tuple(tys.into_iter().map(|t| self.fold_ty(t)).collect())
}
StructKind::Struct(mem) => StructKind::Struct(
mem.into_iter()
.map(|m| self.fold_struct_member(m))
.collect(),
),
}
}
fn fold_struct_member(&mut self, m: StructMember) -> StructMember {
let StructMember { vis, name, ty } = m;
StructMember {
vis: self.fold_visibility(vis),
name: self.fold_sym(name),
ty: self.fold_ty(ty),
}
}
fn fold_enum(&mut self, e: Enum) -> Enum {
let Enum { name, gens, variants: kind } = e;
Enum {
name: self.fold_sym(name),
gens: self.fold_generics(gens),
variants: kind.into_iter().map(|v| self.fold_variant(v)).collect(),
}
}
fn fold_variant(&mut self, v: Variant) -> Variant {
let Variant { name, kind, body } = v;
Variant {
name: self.fold_sym(name),
kind: self.fold_struct_kind(kind),
body: body.map(|e| Box::new(self.fold_expr(*e))),
}
}
fn fold_impl(&mut self, i: Impl) -> Impl {
let Impl { gens, target, body } = i;
Impl {
gens: self.fold_generics(gens),
target: self.fold_impl_kind(target),
body: self.fold_file(body),
}
}
fn fold_impl_kind(&mut self, kind: ImplKind) -> ImplKind {
or_fold_impl_kind(self, kind)
}
fn fold_use(&mut self, u: Use) -> Use {
let Use { absolute, tree } = u;
Use { absolute, tree: self.fold_use_tree(tree) }
}
fn fold_use_tree(&mut self, tree: UseTree) -> UseTree {
or_fold_use_tree(self, tree)
}
fn fold_ty(&mut self, t: Ty) -> Ty {
let Ty { span, kind, gens } = t;
Ty {
span: self.fold_span(span),
kind: self.fold_ty_kind(kind),
gens: self.fold_generics(gens),
}
}
fn fold_ty_kind(&mut self, kind: TyKind) -> TyKind {
or_fold_ty_kind(self, kind)
}
fn fold_ty_array(&mut self, a: TyArray) -> TyArray {
let TyArray { ty, count } = a;
TyArray { ty: Box::new(self.fold_ty(*ty)), count }
}
fn fold_ty_slice(&mut self, s: TySlice) -> TySlice {
let TySlice { ty } = s;
TySlice { ty: Box::new(self.fold_ty(*ty)) }
}
fn fold_ty_tuple(&mut self, t: TyTuple) -> TyTuple {
let TyTuple { types } = t;
TyTuple { types: types.into_iter().map(|kind| self.fold_ty(kind)).collect() }
}
fn fold_ty_ref(&mut self, t: TyRef) -> TyRef {
let TyRef { mutable, count, to } = t;
TyRef { mutable: self.fold_mutability(mutable), count, to: Box::new(self.fold_ty(*to)) }
}
fn fold_ty_ptr(&mut self, t: TyPtr) -> TyPtr {
let TyPtr { to } = t;
TyPtr { to: Box::new(self.fold_ty(*to)) }
}
fn fold_ty_fn(&mut self, t: TyFn) -> TyFn {
let TyFn { args, rety } = t;
TyFn { args: Box::new(self.fold_ty(*args)), rety: Box::new(self.fold_ty(*rety)) }
}
fn fold_path(&mut self, p: Path) -> Path {
let Path { absolute, parts } = p;
Path { absolute, parts: parts.into_iter().map(|p| self.fold_path_part(p)).collect() }
}
fn fold_path_part(&mut self, p: PathPart) -> PathPart {
match p {
PathPart::SuperKw => PathPart::SuperKw,
PathPart::SelfTy => PathPart::SelfTy,
PathPart::Ident(i) => PathPart::Ident(self.fold_sym(i)),
}
}
fn fold_stmt(&mut self, s: Stmt) -> Stmt {
let Stmt { span, kind, semi } = s;
Stmt {
span: self.fold_span(span),
kind: self.fold_stmt_kind(kind),
semi: self.fold_semi(semi),
}
}
fn fold_stmt_kind(&mut self, kind: StmtKind) -> StmtKind {
or_fold_stmt_kind(self, kind)
}
fn fold_semi(&mut self, s: Semi) -> Semi {
s
}
fn fold_expr(&mut self, e: Expr) -> Expr {
let Expr { span, kind } = e;
Expr { span: self.fold_span(span), kind: self.fold_expr_kind(kind) }
}
fn fold_expr_kind(&mut self, kind: ExprKind) -> ExprKind {
or_fold_expr_kind(self, kind)
}
fn fold_closure(&mut self, value: Closure) -> Closure {
let Closure { arg, body } = value;
Closure { arg: Box::new(self.fold_pattern(*arg)), body: Box::new(self.fold_expr(*body)) }
}
fn fold_let(&mut self, l: Let) -> Let {
let Let { mutable, name, ty, init } = l;
Let {
mutable: self.fold_mutability(mutable),
name: self.fold_pattern(name),
ty: ty.map(|t| Box::new(self.fold_ty(*t))),
init: init.map(|e| Box::new(self.fold_expr(*e))),
}
}
fn fold_pattern(&mut self, p: Pattern) -> Pattern {
match p {
Pattern::Name(sym) => Pattern::Name(self.fold_sym(sym)),
Pattern::Path(path) => Pattern::Path(self.fold_path(path)),
Pattern::Literal(literal) => Pattern::Literal(self.fold_literal(literal)),
Pattern::Rest(Some(name)) => Pattern::Rest(Some(self.fold_pattern(*name).into())),
Pattern::Rest(None) => Pattern::Rest(None),
Pattern::Ref(mutability, pattern) => Pattern::Ref(
self.fold_mutability(mutability),
Box::new(self.fold_pattern(*pattern)),
),
Pattern::RangeExc(head, tail) => Pattern::RangeInc(
Box::new(self.fold_pattern(*head)),
Box::new(self.fold_pattern(*tail)),
),
Pattern::RangeInc(head, tail) => Pattern::RangeInc(
Box::new(self.fold_pattern(*head)),
Box::new(self.fold_pattern(*tail)),
),
Pattern::Tuple(patterns) => {
Pattern::Tuple(patterns.into_iter().map(|p| self.fold_pattern(p)).collect())
}
Pattern::Array(patterns) => {
Pattern::Array(patterns.into_iter().map(|p| self.fold_pattern(p)).collect())
}
Pattern::Struct(path, items) => Pattern::Struct(
self.fold_path(path),
items
.into_iter()
.map(|(name, bind)| (name, bind.map(|p| self.fold_pattern(p))))
.collect(),
),
Pattern::TupleStruct(path, items) => Pattern::TupleStruct(
self.fold_path(path),
items
.into_iter()
.map(|bind| self.fold_pattern(bind))
.collect(),
),
}
}
fn fold_match(&mut self, m: Match) -> Match {
let Match { scrutinee, arms } = m;
Match {
scrutinee: self.fold_expr(*scrutinee).into(),
arms: arms
.into_iter()
.map(|arm| self.fold_match_arm(arm))
.collect(),
}
}
fn fold_match_arm(&mut self, a: MatchArm) -> MatchArm {
let MatchArm(pat, expr) = a;
MatchArm(self.fold_pattern(pat), self.fold_expr(expr))
}
fn fold_assign(&mut self, a: Assign) -> Assign {
let Assign { parts } = a;
let (head, tail) = *parts;
Assign { parts: Box::new((self.fold_expr(head), self.fold_expr(tail))) }
}
fn fold_modify(&mut self, m: Modify) -> Modify {
let Modify { kind, parts } = m;
let (head, tail) = *parts;
Modify {
kind: self.fold_modify_kind(kind),
parts: Box::new((self.fold_expr(head), self.fold_expr(tail))),
}
}
fn fold_modify_kind(&mut self, kind: ModifyKind) -> ModifyKind {
kind
}
fn fold_binary(&mut self, b: Binary) -> Binary {
let Binary { kind, parts } = b;
let (head, tail) = *parts;
Binary {
kind: self.fold_binary_kind(kind),
parts: Box::new((self.fold_expr(head), self.fold_expr(tail))),
}
}
fn fold_binary_kind(&mut self, kind: BinaryKind) -> BinaryKind {
kind
}
fn fold_unary(&mut self, u: Unary) -> Unary {
let Unary { kind, tail } = u;
Unary { kind: self.fold_unary_kind(kind), tail: Box::new(self.fold_expr(*tail)) }
}
fn fold_unary_kind(&mut self, kind: UnaryKind) -> UnaryKind {
kind
}
fn fold_cast(&mut self, cast: Cast) -> Cast {
let Cast { head, ty } = cast;
Cast { head: Box::new(self.fold_expr(*head)), ty: self.fold_ty(ty) }
}
fn fold_member(&mut self, m: Member) -> Member {
let Member { head, kind } = m;
Member { head: Box::new(self.fold_expr(*head)), kind: self.fold_member_kind(kind) }
}
fn fold_member_kind(&mut self, kind: MemberKind) -> MemberKind {
or_fold_member_kind(self, kind)
}
fn fold_index(&mut self, i: Index) -> Index {
let Index { head, indices } = i;
Index {
head: Box::new(self.fold_expr(*head)),
indices: indices.into_iter().map(|e| self.fold_expr(e)).collect(),
}
}
fn fold_structor(&mut self, s: Structor) -> Structor {
let Structor { to, init } = s;
Structor {
to: self.fold_path(to),
init: init.into_iter().map(|f| self.fold_fielder(f)).collect(),
}
}
fn fold_fielder(&mut self, f: Fielder) -> Fielder {
let Fielder { name, init } = f;
Fielder { name: self.fold_sym(name), init: init.map(|e| Box::new(self.fold_expr(*e))) }
}
fn fold_array(&mut self, a: Array) -> Array {
let Array { values } = a;
Array { values: values.into_iter().map(|e| self.fold_expr(e)).collect() }
}
fn fold_array_rep(&mut self, a: ArrayRep) -> ArrayRep {
let ArrayRep { value, repeat } = a;
ArrayRep { value: Box::new(self.fold_expr(*value)), repeat }
}
fn fold_addrof(&mut self, a: AddrOf) -> AddrOf {
let AddrOf { mutable, expr } = a;
AddrOf { mutable: self.fold_mutability(mutable), expr: Box::new(self.fold_expr(*expr)) }
}
fn fold_block(&mut self, b: Block) -> Block {
let Block { stmts } = b;
Block { stmts: stmts.into_iter().map(|s| self.fold_stmt(s)).collect() }
}
fn fold_group(&mut self, g: Group) -> Group {
let Group { expr } = g;
Group { expr: Box::new(self.fold_expr(*expr)) }
}
fn fold_tuple(&mut self, t: Tuple) -> Tuple {
let Tuple { exprs } = t;
Tuple { exprs: exprs.into_iter().map(|e| self.fold_expr(e)).collect() }
}
fn fold_while(&mut self, w: While) -> While {
let While { cond, pass, fail } = w;
While {
cond: Box::new(self.fold_expr(*cond)),
pass: Box::new(self.fold_block(*pass)),
fail: self.fold_else(fail),
}
}
fn fold_if(&mut self, i: If) -> If {
let If { cond, pass, fail } = i;
If {
cond: Box::new(self.fold_expr(*cond)),
pass: Box::new(self.fold_block(*pass)),
fail: self.fold_else(fail),
}
}
fn fold_for(&mut self, f: For) -> For {
let For { bind, cond, pass, fail } = f;
For {
bind: self.fold_pattern(bind),
cond: Box::new(self.fold_expr(*cond)),
pass: Box::new(self.fold_block(*pass)),
fail: self.fold_else(fail),
}
}
fn fold_else(&mut self, e: Else) -> Else {
let Else { body } = e;
Else { body: body.map(|e| Box::new(self.fold_expr(*e))) }
}
fn fold_break(&mut self, b: Break) -> Break {
let Break { body } = b;
Break { body: body.map(|e| Box::new(self.fold_expr(*e))) }
}
fn fold_return(&mut self, r: Return) -> Return {
let Return { body } = r;
Return { body: body.map(|e| Box::new(self.fold_expr(*e))) }
}
}
#[inline]
/// Folds a [Literal] in the default way
pub fn or_fold_literal<F: Fold + ?Sized>(folder: &mut F, lit: Literal) -> Literal {
match lit {
Literal::Bool(b) => Literal::Bool(folder.fold_bool(b)),
Literal::Char(c) => Literal::Char(folder.fold_char(c)),
Literal::Int(i) => Literal::Int(folder.fold_int(i)),
Literal::Float(f) => Literal::Float(folder.fold_smuggled_float(f)),
Literal::String(s) => Literal::String(folder.fold_string(s)),
}
}
#[inline]
/// Folds a [MetaKind] in the default way
pub fn or_fold_meta_kind<F: Fold + ?Sized>(folder: &mut F, kind: MetaKind) -> MetaKind {
match kind {
MetaKind::Plain => MetaKind::Plain,
MetaKind::Equals(l) => MetaKind::Equals(folder.fold_literal(l)),
MetaKind::Func(lits) => {
MetaKind::Func(lits.into_iter().map(|l| folder.fold_literal(l)).collect())
}
}
}
#[inline]
/// Folds an [ItemKind] in the default way
pub fn or_fold_item_kind<F: Fold + ?Sized>(folder: &mut F, kind: ItemKind) -> ItemKind {
match kind {
ItemKind::Module(m) => ItemKind::Module(folder.fold_module(m)),
ItemKind::Alias(a) => ItemKind::Alias(folder.fold_alias(a)),
ItemKind::Enum(e) => ItemKind::Enum(folder.fold_enum(e)),
ItemKind::Struct(s) => ItemKind::Struct(folder.fold_struct(s)),
ItemKind::Const(c) => ItemKind::Const(folder.fold_const(c)),
ItemKind::Static(s) => ItemKind::Static(folder.fold_static(s)),
ItemKind::Function(f) => ItemKind::Function(folder.fold_function(f)),
ItemKind::Impl(i) => ItemKind::Impl(folder.fold_impl(i)),
ItemKind::Use(u) => ItemKind::Use(folder.fold_use(u)),
}
}
#[inline]
/// Folds a [StructKind] in the default way
pub fn or_fold_struct_kind<F: Fold + ?Sized>(folder: &mut F, kind: StructKind) -> StructKind {
match kind {
StructKind::Empty => StructKind::Empty,
StructKind::Tuple(tys) => {
StructKind::Tuple(tys.into_iter().map(|t| folder.fold_ty(t)).collect())
}
StructKind::Struct(mem) => StructKind::Struct(
mem.into_iter()
.map(|m| folder.fold_struct_member(m))
.collect(),
),
}
}
#[inline]
/// Folds an [ImplKind] in the default way
pub fn or_fold_impl_kind<F: Fold + ?Sized>(folder: &mut F, kind: ImplKind) -> ImplKind {
match kind {
ImplKind::Type(t) => ImplKind::Type(folder.fold_ty(t)),
ImplKind::Trait { impl_trait, for_type } => ImplKind::Trait {
impl_trait: folder.fold_path(impl_trait),
for_type: Box::new(folder.fold_ty(*for_type)),
},
}
}
#[inline]
pub fn or_fold_use_tree<F: Fold + ?Sized>(folder: &mut F, tree: UseTree) -> UseTree {
match tree {
UseTree::Tree(tree) => UseTree::Tree(
tree.into_iter()
.map(|tree| folder.fold_use_tree(tree))
.collect(),
),
UseTree::Path(path, rest) => UseTree::Path(
folder.fold_path_part(path),
Box::new(folder.fold_use_tree(*rest)),
),
UseTree::Alias(path, name) => UseTree::Alias(folder.fold_sym(path), folder.fold_sym(name)),
UseTree::Name(name) => UseTree::Name(folder.fold_sym(name)),
UseTree::Glob => UseTree::Glob,
}
}
#[inline]
/// Folds a [TyKind] in the default way
pub fn or_fold_ty_kind<F: Fold + ?Sized>(folder: &mut F, kind: TyKind) -> TyKind {
match kind {
TyKind::Never => TyKind::Never,
TyKind::Infer => TyKind::Infer,
TyKind::Path(p) => TyKind::Path(folder.fold_path(p)),
TyKind::Array(a) => TyKind::Array(folder.fold_ty_array(a)),
TyKind::Slice(s) => TyKind::Slice(folder.fold_ty_slice(s)),
TyKind::Tuple(t) => TyKind::Tuple(folder.fold_ty_tuple(t)),
TyKind::Ref(t) => TyKind::Ref(folder.fold_ty_ref(t)),
TyKind::Ptr(t) => TyKind::Ptr(folder.fold_ty_ptr(t)),
TyKind::Fn(t) => TyKind::Fn(folder.fold_ty_fn(t)),
}
}
#[inline]
/// Folds a [StmtKind] in the default way
pub fn or_fold_stmt_kind<F: Fold + ?Sized>(folder: &mut F, kind: StmtKind) -> StmtKind {
match kind {
StmtKind::Empty => StmtKind::Empty,
StmtKind::Item(i) => StmtKind::Item(Box::new(folder.fold_item(*i))),
StmtKind::Expr(e) => StmtKind::Expr(Box::new(folder.fold_expr(*e))),
}
}
#[inline]
/// Folds an [ExprKind] in the default way
pub fn or_fold_expr_kind<F: Fold + ?Sized>(folder: &mut F, kind: ExprKind) -> ExprKind {
match kind {
ExprKind::Empty => ExprKind::Empty,
ExprKind::Closure(c) => ExprKind::Closure(folder.fold_closure(c)),
ExprKind::Quote(q) => ExprKind::Quote(q), // quoted expressions are left unmodified
ExprKind::Let(l) => ExprKind::Let(folder.fold_let(l)),
ExprKind::Match(m) => ExprKind::Match(folder.fold_match(m)),
ExprKind::Assign(a) => ExprKind::Assign(folder.fold_assign(a)),
ExprKind::Modify(m) => ExprKind::Modify(folder.fold_modify(m)),
ExprKind::Binary(b) => ExprKind::Binary(folder.fold_binary(b)),
ExprKind::Unary(u) => ExprKind::Unary(folder.fold_unary(u)),
ExprKind::Cast(c) => ExprKind::Cast(folder.fold_cast(c)),
ExprKind::Member(m) => ExprKind::Member(folder.fold_member(m)),
ExprKind::Index(i) => ExprKind::Index(folder.fold_index(i)),
ExprKind::Structor(s) => ExprKind::Structor(folder.fold_structor(s)),
ExprKind::Path(p) => ExprKind::Path(folder.fold_path(p)),
ExprKind::Literal(l) => ExprKind::Literal(folder.fold_literal(l)),
ExprKind::Array(a) => ExprKind::Array(folder.fold_array(a)),
ExprKind::ArrayRep(a) => ExprKind::ArrayRep(folder.fold_array_rep(a)),
ExprKind::AddrOf(a) => ExprKind::AddrOf(folder.fold_addrof(a)),
ExprKind::Block(b) => ExprKind::Block(folder.fold_block(b)),
ExprKind::Group(g) => ExprKind::Group(folder.fold_group(g)),
ExprKind::Tuple(t) => ExprKind::Tuple(folder.fold_tuple(t)),
ExprKind::While(w) => ExprKind::While(folder.fold_while(w)),
ExprKind::If(i) => ExprKind::If(folder.fold_if(i)),
ExprKind::For(f) => ExprKind::For(folder.fold_for(f)),
ExprKind::Break(b) => ExprKind::Break(folder.fold_break(b)),
ExprKind::Return(r) => ExprKind::Return(folder.fold_return(r)),
ExprKind::Continue => ExprKind::Continue,
}
}
pub fn or_fold_member_kind<F: Fold + ?Sized>(folder: &mut F, kind: MemberKind) -> MemberKind {
match kind {
MemberKind::Call(name, args) => {
MemberKind::Call(folder.fold_sym(name), folder.fold_tuple(args))
}
MemberKind::Struct(name) => MemberKind::Struct(folder.fold_sym(name)),
MemberKind::Tuple(name) => MemberKind::Tuple(folder.fold_literal(name)),
}
}

View File

@@ -0,0 +1,260 @@
//! A [visitor](Visit) (implementer of the [Visit] trait) walks the immutable AST, mutating itself.
use crate::ast::*;
use cl_structures::span::Span;
use super::walk::Walk;
/// Immutably walks the entire AST
///
/// Each method acts as a customization point.
pub trait Visit<'a>: Sized {
/// Visits a [Walker](Walk)
#[inline]
fn visit<W: Walk>(&mut self, walker: &'a W) -> &mut Self {
walker.visit_in(self);
self
}
/// Visits the children of a [Walker](Walk)
fn visit_children<W: Walk>(&mut self, walker: &'a W) {
walker.children(self)
}
fn visit_span(&mut self, value: &'a Span) {
value.children(self)
}
fn visit_mutability(&mut self, value: &'a Mutability) {
value.children(self)
}
fn visit_visibility(&mut self, value: &'a Visibility) {
value.children(self)
}
fn visit_sym(&mut self, value: &'a Sym) {
value.children(self)
}
fn visit_literal(&mut self, value: &'a Literal) {
value.children(self)
}
fn visit_bool(&mut self, value: &'a bool) {
value.children(self)
}
fn visit_char(&mut self, value: &'a char) {
value.children(self)
}
fn visit_int(&mut self, value: &'a u128) {
value.children(self)
}
fn visit_smuggled_float(&mut self, value: &'a u64) {
value.children(self)
}
fn visit_string(&mut self, value: &'a str) {
value.children(self)
}
fn visit_file(&mut self, value: &'a File) {
value.children(self)
}
fn visit_attrs(&mut self, value: &'a Attrs) {
value.children(self)
}
fn visit_meta(&mut self, value: &'a Meta) {
value.children(self)
}
fn visit_meta_kind(&mut self, value: &'a MetaKind) {
value.children(self)
}
fn visit_item(&mut self, value: &'a Item) {
value.children(self)
}
fn visit_item_kind(&mut self, value: &'a ItemKind) {
value.children(self)
}
fn visit_generics(&mut self, value: &'a Generics) {
value.children(self)
}
fn visit_alias(&mut self, value: &'a Alias) {
value.children(self)
}
fn visit_const(&mut self, value: &'a Const) {
value.children(self)
}
fn visit_static(&mut self, value: &'a Static) {
value.children(self)
}
fn visit_module(&mut self, value: &'a Module) {
value.children(self)
}
fn visit_function(&mut self, value: &'a Function) {
value.children(self)
}
fn visit_struct(&mut self, value: &'a Struct) {
value.children(self)
}
fn visit_struct_kind(&mut self, value: &'a StructKind) {
value.children(self)
}
fn visit_struct_member(&mut self, value: &'a StructMember) {
value.children(self)
}
fn visit_enum(&mut self, value: &'a Enum) {
value.children(self)
}
fn visit_variant(&mut self, value: &'a Variant) {
value.children(self)
}
fn visit_impl(&mut self, value: &'a Impl) {
value.children(self)
}
fn visit_impl_kind(&mut self, value: &'a ImplKind) {
value.children(self)
}
fn visit_use(&mut self, value: &'a Use) {
value.children(self)
}
fn visit_use_tree(&mut self, value: &'a UseTree) {
value.children(self)
}
fn visit_ty(&mut self, value: &'a Ty) {
value.children(self)
}
fn visit_ty_kind(&mut self, value: &'a TyKind) {
value.children(self)
}
fn visit_ty_array(&mut self, value: &'a TyArray) {
value.children(self)
}
fn visit_ty_slice(&mut self, value: &'a TySlice) {
value.children(self)
}
fn visit_ty_tuple(&mut self, value: &'a TyTuple) {
value.children(self)
}
fn visit_ty_ref(&mut self, value: &'a TyRef) {
value.children(self)
}
fn visit_ty_ptr(&mut self, value: &'a TyPtr) {
value.children(self)
}
fn visit_ty_fn(&mut self, value: &'a TyFn) {
value.children(self)
}
fn visit_path(&mut self, value: &'a Path) {
value.children(self)
}
fn visit_path_part(&mut self, value: &'a PathPart) {
value.children(self)
}
fn visit_stmt(&mut self, value: &'a Stmt) {
value.children(self)
}
fn visit_stmt_kind(&mut self, value: &'a StmtKind) {
value.children(self)
}
fn visit_semi(&mut self, value: &'a Semi) {
value.children(self)
}
fn visit_expr(&mut self, value: &'a Expr) {
value.children(self)
}
fn visit_expr_kind(&mut self, value: &'a ExprKind) {
value.children(self)
}
fn visit_closure(&mut self, value: &'a Closure) {
value.children(self)
}
fn visit_quote(&mut self, value: &'a Quote) {
value.children(self)
}
fn visit_let(&mut self, value: &'a Let) {
value.children(self)
}
fn visit_pattern(&mut self, value: &'a Pattern) {
value.children(self)
}
fn visit_match(&mut self, value: &'a Match) {
value.children(self)
}
fn visit_match_arm(&mut self, value: &'a MatchArm) {
value.children(self)
}
fn visit_assign(&mut self, value: &'a Assign) {
value.children(self)
}
fn visit_modify(&mut self, value: &'a Modify) {
value.children(self)
}
fn visit_modify_kind(&mut self, value: &'a ModifyKind) {
value.children(self)
}
fn visit_binary(&mut self, value: &'a Binary) {
value.children(self)
}
fn visit_binary_kind(&mut self, value: &'a BinaryKind) {
value.children(self)
}
fn visit_unary(&mut self, value: &'a Unary) {
value.children(self)
}
fn visit_unary_kind(&mut self, value: &'a UnaryKind) {
value.children(self)
}
fn visit_cast(&mut self, value: &'a Cast) {
value.children(self)
}
fn visit_member(&mut self, value: &'a Member) {
value.children(self)
}
fn visit_member_kind(&mut self, value: &'a MemberKind) {
value.children(self)
}
fn visit_index(&mut self, value: &'a Index) {
value.children(self)
}
fn visit_structor(&mut self, value: &'a Structor) {
value.children(self)
}
fn visit_fielder(&mut self, value: &'a Fielder) {
value.children(self)
}
fn visit_array(&mut self, value: &'a Array) {
value.children(self)
}
fn visit_array_rep(&mut self, value: &'a ArrayRep) {
value.children(self)
}
fn visit_addrof(&mut self, value: &'a AddrOf) {
value.children(self)
}
fn visit_block(&mut self, value: &'a Block) {
value.children(self)
}
fn visit_group(&mut self, value: &'a Group) {
value.children(self)
}
fn visit_tuple(&mut self, value: &'a Tuple) {
value.children(self)
}
fn visit_while(&mut self, value: &'a While) {
value.children(self)
}
fn visit_if(&mut self, value: &'a If) {
value.children(self)
}
fn visit_for(&mut self, value: &'a For) {
value.children(self)
}
fn visit_else(&mut self, value: &'a Else) {
value.children(self)
}
fn visit_break(&mut self, value: &'a Break) {
value.children(self)
}
fn visit_return(&mut self, value: &'a Return) {
value.children(self)
}
fn visit_continue(&mut self) {}
}

View File

@@ -0,0 +1,963 @@
//! Accepts an AST Visitor. Walks the AST, calling the visitor on each step.
use super::visit::Visit;
use crate::ast::*;
use cl_structures::span::Span;
/// Helps a [Visitor](Visit) walk through `Self`.
pub trait Walk {
/// Calls the respective `visit_*` function in V
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V);
#[allow(unused)]
/// Walks the children of self, visiting them in V
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {}
}
impl Walk for Span {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_span(self);
}
}
impl Walk for Sym {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_sym(self);
}
}
impl Walk for Mutability {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_mutability(self);
}
}
impl Walk for Visibility {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_visibility(self);
}
}
impl Walk for bool {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_bool(self);
}
}
impl Walk for char {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_char(self);
}
}
impl Walk for u128 {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_int(self);
}
}
impl Walk for u64 {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_smuggled_float(self);
}
}
impl Walk for str {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_string(self);
}
}
impl Walk for Literal {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_literal(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
Literal::Bool(value) => value.children(v),
Literal::Char(value) => value.children(v),
Literal::Int(value) => value.children(v),
Literal::Float(value) => value.children(v),
Literal::String(value) => value.children(v),
};
}
}
impl Walk for File {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_file(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let File { name: _, items } = self;
items.iter().for_each(|i| v.visit_item(i));
}
}
impl Walk for Attrs {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_attrs(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Attrs { meta } = self;
meta.children(v);
}
}
impl Walk for Meta {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_meta(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Meta { name, kind } = self;
name.visit_in(v);
kind.visit_in(v);
}
}
impl Walk for MetaKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_meta_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
MetaKind::Plain => {}
MetaKind::Equals(lit) => lit.visit_in(v),
MetaKind::Func(lits) => lits.visit_in(v),
}
}
}
impl Walk for Item {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_item(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Item { span, attrs, vis, kind } = self;
span.visit_in(v);
attrs.visit_in(v);
vis.visit_in(v);
kind.visit_in(v);
}
}
impl Walk for ItemKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_item_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
ItemKind::Module(value) => value.visit_in(v),
ItemKind::Alias(value) => value.visit_in(v),
ItemKind::Enum(value) => value.visit_in(v),
ItemKind::Struct(value) => value.visit_in(v),
ItemKind::Const(value) => value.visit_in(v),
ItemKind::Static(value) => value.visit_in(v),
ItemKind::Function(value) => value.visit_in(v),
ItemKind::Impl(value) => value.visit_in(v),
ItemKind::Use(value) => value.visit_in(v),
}
}
}
impl Walk for Generics {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_generics(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Self { vars } = self;
vars.visit_in(v);
}
}
impl Walk for Module {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_module(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Module { name, file } = self;
name.visit_in(v);
file.visit_in(v);
}
}
impl Walk for Alias {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_alias(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Alias { name, from } = self;
name.visit_in(v);
from.visit_in(v);
}
}
impl Walk for Const {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_const(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Const { name, ty, init } = self;
name.visit_in(v);
ty.visit_in(v);
init.visit_in(v);
}
}
impl Walk for Static {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_static(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Static { mutable, name, ty, init } = self;
mutable.visit_in(v);
name.visit_in(v);
ty.visit_in(v);
init.visit_in(v);
}
}
impl Walk for Function {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_function(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Function { name, gens, sign, bind, body } = self;
name.visit_in(v);
gens.visit_in(v);
sign.visit_in(v);
bind.visit_in(v);
body.visit_in(v);
}
}
impl Walk for Struct {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_struct(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Struct { name, gens, kind } = self;
name.visit_in(v);
gens.visit_in(v);
kind.visit_in(v);
}
}
impl Walk for StructKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_struct_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
StructKind::Empty => {}
StructKind::Tuple(tys) => tys.visit_in(v),
StructKind::Struct(ms) => ms.visit_in(v),
}
}
}
impl Walk for StructMember {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_struct_member(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let StructMember { vis, name, ty } = self;
vis.visit_in(v);
name.visit_in(v);
ty.visit_in(v);
}
}
impl Walk for Enum {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_enum(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Enum { name, gens, variants } = self;
name.visit_in(v);
gens.visit_in(v);
variants.visit_in(v);
}
}
impl Walk for Variant {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_variant(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Variant { name, kind, body } = self;
name.visit_in(v);
kind.visit_in(v);
body.visit_in(v);
}
}
impl Walk for Impl {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_impl(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Impl { gens, target, body } = self;
gens.visit_in(v);
target.visit_in(v);
body.visit_in(v);
}
}
impl Walk for ImplKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_impl_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
ImplKind::Type(t) => t.visit_in(v),
ImplKind::Trait { impl_trait, for_type } => {
impl_trait.visit_in(v);
for_type.visit_in(v);
}
}
}
}
impl Walk for Use {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_use(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Use { absolute: _, tree } = self;
tree.visit_in(v);
}
}
impl Walk for UseTree {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_use_tree(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
UseTree::Tree(tree) => tree.iter().for_each(|t| t.visit_in(v)),
UseTree::Path(part, tree) => {
part.visit_in(v);
tree.visit_in(v);
}
UseTree::Alias(from, to) => {
from.visit_in(v);
to.visit_in(v);
}
UseTree::Name(name) => name.visit_in(v),
UseTree::Glob => {}
}
}
}
impl Walk for Ty {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Ty { span, kind, gens } = self;
span.visit_in(v);
kind.visit_in(v);
gens.visit_in(v);
}
}
impl Walk for TyKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
TyKind::Never => {}
TyKind::Infer => {}
TyKind::Path(value) => value.visit_in(v),
TyKind::Array(value) => value.visit_in(v),
TyKind::Slice(value) => value.visit_in(v),
TyKind::Tuple(value) => value.visit_in(v),
TyKind::Ref(value) => value.visit_in(v),
TyKind::Ptr(value) => value.visit_in(v),
TyKind::Fn(value) => value.visit_in(v),
}
}
}
impl Walk for TyArray {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty_array(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let TyArray { ty, count: _ } = self;
ty.visit_in(v);
// count.walk(v); // not available
}
}
impl Walk for TySlice {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty_slice(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let TySlice { ty } = self;
ty.visit_in(v);
}
}
impl Walk for TyTuple {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty_tuple(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let TyTuple { types } = self;
types.visit_in(v);
}
}
impl Walk for TyRef {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty_ref(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let TyRef { mutable, count: _, to } = self;
mutable.children(v);
to.children(v);
}
}
impl Walk for TyPtr {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty_ptr(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let TyPtr { to } = self;
to.children(v);
}
}
impl Walk for TyFn {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_ty_fn(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let TyFn { args, rety } = self;
args.visit_in(v);
rety.visit_in(v);
}
}
impl Walk for Path {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_path(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Path { absolute: _, parts } = self;
parts.visit_in(v);
}
}
impl Walk for PathPart {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_path_part(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
PathPart::SuperKw => {}
PathPart::SelfTy => {}
PathPart::Ident(sym) => sym.visit_in(v),
}
}
}
impl Walk for Stmt {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_stmt(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Stmt { span, kind, semi } = self;
span.visit_in(v);
kind.visit_in(v);
semi.visit_in(v);
}
}
impl Walk for StmtKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_stmt_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
StmtKind::Empty => {}
StmtKind::Item(value) => value.visit_in(v),
StmtKind::Expr(value) => value.visit_in(v),
}
}
}
impl Walk for Semi {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_semi(self);
}
}
impl Walk for Expr {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_expr(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Expr { span, kind } = self;
span.visit_in(v);
kind.visit_in(v);
}
}
impl Walk for ExprKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_expr_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
ExprKind::Empty => {}
ExprKind::Closure(value) => value.visit_in(v),
ExprKind::Tuple(value) => value.visit_in(v),
ExprKind::Structor(value) => value.visit_in(v),
ExprKind::Array(value) => value.visit_in(v),
ExprKind::ArrayRep(value) => value.visit_in(v),
ExprKind::AddrOf(value) => value.visit_in(v),
ExprKind::Quote(value) => value.visit_in(v),
ExprKind::Literal(value) => value.visit_in(v),
ExprKind::Group(value) => value.visit_in(v),
ExprKind::Block(value) => value.visit_in(v),
ExprKind::Assign(value) => value.visit_in(v),
ExprKind::Modify(value) => value.visit_in(v),
ExprKind::Binary(value) => value.visit_in(v),
ExprKind::Unary(value) => value.visit_in(v),
ExprKind::Member(value) => value.visit_in(v),
ExprKind::Index(value) => value.visit_in(v),
ExprKind::Cast(value) => value.visit_in(v),
ExprKind::Path(value) => value.visit_in(v),
ExprKind::Let(value) => value.visit_in(v),
ExprKind::Match(value) => value.visit_in(v),
ExprKind::While(value) => value.visit_in(v),
ExprKind::If(value) => value.visit_in(v),
ExprKind::For(value) => value.visit_in(v),
ExprKind::Break(value) => value.visit_in(v),
ExprKind::Return(value) => value.visit_in(v),
ExprKind::Continue => v.visit_continue(),
}
}
}
impl Walk for Closure {
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_closure(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Self { arg, body } = self;
v.visit_pattern(arg);
v.visit_expr(body);
}
}
impl Walk for Tuple {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_tuple(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Tuple { exprs } = self;
exprs.visit_in(v);
}
}
impl Walk for Structor {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_structor(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Structor { to, init } = self;
to.visit_in(v);
init.visit_in(v);
}
}
impl Walk for Fielder {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_fielder(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Fielder { name, init } = self;
name.visit_in(v);
init.visit_in(v);
}
}
impl Walk for Array {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_array(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Array { values } = self;
values.visit_in(v);
}
}
impl Walk for ArrayRep {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_array_rep(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let ArrayRep { value, repeat: _ } = self;
value.visit_in(v);
// repeat.visit_in(v) // TODO
}
}
impl Walk for AddrOf {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_addrof(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let AddrOf { mutable, expr } = self;
mutable.visit_in(v);
expr.visit_in(v);
}
}
impl Walk for Cast {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_cast(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Cast { head, ty } = self;
head.visit_in(v);
ty.visit_in(v);
}
}
impl Walk for Quote {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_quote(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Quote { quote } = self;
quote.visit_in(v);
}
}
impl Walk for Group {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_group(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Group { expr } = self;
expr.visit_in(v);
}
}
impl Walk for Block {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_block(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Block { stmts } = self;
stmts.visit_in(v);
}
}
impl Walk for Assign {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_assign(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Assign { parts } = self;
parts.visit_in(v);
}
}
impl Walk for Modify {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_modify(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Modify { kind, parts } = self;
kind.visit_in(v);
parts.visit_in(v);
}
}
impl Walk for ModifyKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_modify_kind(self);
}
}
impl Walk for Binary {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_binary(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Binary { kind, parts } = self;
kind.visit_in(v);
parts.visit_in(v);
}
}
impl Walk for BinaryKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_binary_kind(self);
}
}
impl Walk for Unary {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_unary(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Unary { kind, tail } = self;
kind.visit_in(v);
tail.visit_in(v);
}
}
impl Walk for UnaryKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_unary_kind(self);
}
}
impl Walk for Member {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_member(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Member { head, kind } = self;
head.visit_in(v);
kind.visit_in(v);
}
}
impl Walk for MemberKind {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_member_kind(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
MemberKind::Call(sym, tuple) => {
sym.visit_in(v);
tuple.visit_in(v);
}
MemberKind::Struct(sym) => sym.visit_in(v),
MemberKind::Tuple(literal) => literal.visit_in(v),
}
}
}
impl Walk for Index {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_index(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Index { head, indices } = self;
head.visit_in(v);
indices.visit_in(v);
}
}
impl Walk for Let {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_let(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Let { mutable, name, ty, init } = self;
mutable.visit_in(v);
name.visit_in(v);
ty.visit_in(v);
init.visit_in(v);
}
}
impl Walk for Match {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_match(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Match { scrutinee, arms } = self;
scrutinee.visit_in(v);
arms.visit_in(v);
}
}
impl Walk for MatchArm {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_match_arm(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let MatchArm(pat, expr) = self;
pat.visit_in(v);
expr.visit_in(v);
}
}
impl Walk for Pattern {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_pattern(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
match self {
Pattern::Name(sym) => sym.visit_in(v),
Pattern::Path(path) => path.visit_in(v),
Pattern::Literal(literal) => literal.visit_in(v),
Pattern::Rest(pattern) => pattern.visit_in(v),
Pattern::Ref(mutability, pattern) => {
mutability.visit_in(v);
pattern.visit_in(v);
}
Pattern::RangeExc(from, to) => {
from.visit_in(v);
to.visit_in(v);
}
Pattern::RangeInc(from, to) => {
from.visit_in(v);
to.visit_in(v);
}
Pattern::Tuple(patterns) => patterns.visit_in(v),
Pattern::Array(patterns) => patterns.visit_in(v),
Pattern::Struct(path, items) => {
path.visit_in(v);
items.visit_in(v);
}
Pattern::TupleStruct(path, patterns) => {
path.visit_in(v);
patterns.visit_in(v);
}
}
}
}
impl Walk for While {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_while(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let While { cond, pass, fail } = self;
cond.visit_in(v);
pass.visit_in(v);
fail.visit_in(v);
}
}
impl Walk for If {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_if(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let If { cond, pass, fail } = self;
cond.visit_in(v);
pass.visit_in(v);
fail.visit_in(v);
}
}
impl Walk for For {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_for(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let For { bind, cond, pass, fail } = self;
cond.visit_in(v);
fail.visit_in(v);
bind.visit_in(v);
pass.visit_in(v);
}
}
impl Walk for Else {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_else(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Else { body } = self;
body.visit_in(v);
}
}
impl Walk for Break {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_break(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Break { body } = self;
body.visit_in(v);
}
}
impl Walk for Return {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
v.visit_return(self);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let Return { body } = self;
body.visit_in(v);
}
}
// --- BLANKET IMPLEMENTATIONS
impl<T: Walk> Walk for [T] {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
self.iter().for_each(|value| value.visit_in(v));
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
self.iter().for_each(|value| value.children(v));
}
}
impl<T: Walk> Walk for Vec<T> {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
self.as_slice().visit_in(v);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
self.as_slice().children(v);
}
}
impl<A: Walk, B: Walk> Walk for (A, B) {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let (a, b) = self;
a.visit_in(v);
b.visit_in(v);
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
let (a, b) = self;
a.children(v);
b.children(v);
}
}
impl<T: Walk> Walk for Option<T> {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
if let Some(value) = self.as_ref() {
value.visit_in(v)
}
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
if let Some(value) = self {
value.children(v)
}
}
}
impl<T: Walk> Walk for Box<T> {
#[inline]
fn visit_in<'a, V: Visit<'a>>(&'a self, v: &mut V) {
self.as_ref().visit_in(v)
}
fn children<'a, V: Visit<'a>>(&'a self, v: &mut V) {
self.as_ref().children(v)
}
}

View File

@@ -0,0 +1,11 @@
//! Desugaring passes for Conlang
pub mod constant_folder;
pub mod path_absoluter;
pub mod squash_groups;
pub mod while_else;
pub use constant_folder::ConstantFolder;
pub use path_absoluter::NormalizePaths;
pub use squash_groups::SquashGroups;
pub use while_else::WhileElseDesugar;

View File

@@ -0,0 +1,90 @@
use crate::{
ast::{ExprKind as Ek, *},
ast_visitor::{Fold, fold::or_fold_expr_kind},
};
pub struct ConstantFolder;
macro bin_rule(
match ($kind: ident, $head: expr, $tail: expr) {
$(($op:ident, $impl:expr, $($ty:ident -> $rety:ident),*)),*$(,)?
}
) {
#[allow(clippy::all)]
match ($kind, $head, $tail) {
$($(( BinaryKind::$op,
Expr { kind: ExprKind::Literal(Literal::$ty(a)), .. },
Expr { kind: ExprKind::Literal(Literal::$ty(b)), .. },
) => {
ExprKind::Literal(Literal::$rety($impl(a, b)))
},)*)*
(kind, head, tail) => ExprKind::Binary(Binary {
kind,
parts: Box::new((head, tail)),
}),
}
}
macro un_rule(
match ($kind: ident, $tail: expr) {
$(($op:ident, $impl:expr, $($ty:ident),*)),*$(,)?
}
) {
match ($kind, $tail) {
$($((UnaryKind::$op, Expr { kind: ExprKind::Literal(Literal::$ty(v)), .. }) => {
ExprKind::Literal(Literal::$ty($impl(v)))
},)*)*
(kind, tail) => ExprKind::Unary(Unary { kind, tail: Box::new(tail) }),
}
}
impl Fold for ConstantFolder {
fn fold_expr_kind(&mut self, kind: Ek) -> Ek {
match kind {
Ek::Group(Group { expr }) => self.fold_expr_kind(expr.kind),
Ek::Binary(Binary { kind, parts }) => {
let (head, tail) = *parts;
bin_rule! (match (kind, self.fold_expr(head), self.fold_expr(tail)) {
(Lt, |a, b| a < b, Bool -> Bool, Int -> Bool),
(LtEq, |a, b| a <= b, Bool -> Bool, Int -> Bool),
(Equal, |a, b| a == b, Bool -> Bool, Int -> Bool),
(NotEq, |a, b| a != b, Bool -> Bool, Int -> Bool),
(GtEq, |a, b| a >= b, Bool -> Bool, Int -> Bool),
(Gt, |a, b| a > b, Bool -> Bool, Int -> Bool),
(BitAnd, |a, b| a & b, Bool -> Bool, Int -> Int),
(BitOr, |a, b| a | b, Bool -> Bool, Int -> Int),
(BitXor, |a, b| a ^ b, Bool -> Bool, Int -> Int),
(Shl, |a, b| a << b, Int -> Int),
(Shr, |a, b| a >> b, Int -> Int),
(Add, |a, b| a + b, Int -> Int),
(Sub, |a, b| a - b, Int -> Int),
(Mul, |a, b| a * b, Int -> Int),
(Div, |a, b| a / b, Int -> Int),
(Rem, |a, b| a % b, Int -> Int),
// Cursed bit-smuggled float shenanigans
(Lt, |a, b| f64::from_bits(a) < f64::from_bits(b), Float -> Bool),
(LtEq, |a, b| f64::from_bits(a) >= f64::from_bits(b), Float -> Bool),
(Equal, |a, b| f64::from_bits(a) == f64::from_bits(b), Float -> Bool),
(NotEq, |a, b| f64::from_bits(a) != f64::from_bits(b), Float -> Bool),
(GtEq, |a, b| f64::from_bits(a) <= f64::from_bits(b), Float -> Bool),
(Gt, |a, b| f64::from_bits(a) > f64::from_bits(b), Float -> Bool),
(Add, |a, b| (f64::from_bits(a) + f64::from_bits(b)).to_bits(), Float -> Float),
(Sub, |a, b| (f64::from_bits(a) - f64::from_bits(b)).to_bits(), Float -> Float),
(Mul, |a, b| (f64::from_bits(a) * f64::from_bits(b)).to_bits(), Float -> Float),
(Div, |a, b| (f64::from_bits(a) / f64::from_bits(b)).to_bits(), Float -> Float),
(Rem, |a, b| (f64::from_bits(a) % f64::from_bits(b)).to_bits(), Float -> Float),
})
}
Ek::Unary(Unary { kind, tail }) => {
un_rule! (match (kind, self.fold_expr(*tail)) {
(Not, std::ops::Not::not, Int, Bool),
(Neg, std::ops::Not::not, Bool),
(Neg, |i| -(i as i128) as u128, Int),
(Neg, |f| (-f64::from_bits(f)).to_bits(), Float),
(At, std::ops::Not::not, Float), /* Lmao */
})
}
_ => or_fold_expr_kind(self, kind),
}
}
}

View File

@@ -0,0 +1,55 @@
use crate::{ast::*, ast_visitor::Fold};
/// Converts relative paths into absolute paths
pub struct NormalizePaths {
path: Path,
}
impl NormalizePaths {
pub fn new() -> Self {
Self { path: Path { absolute: true, parts: vec![] } }
}
/// Normalizes paths as if they came from within the provided paths
pub fn in_path(path: Path) -> Self {
Self { path }
}
}
impl Default for NormalizePaths {
fn default() -> Self {
Self::new()
}
}
impl Fold for NormalizePaths {
fn fold_module(&mut self, m: Module) -> Module {
let Module { name, file } = m;
self.path.push(PathPart::Ident(name));
let name = self.fold_sym(name);
let file = file.map(|f| self.fold_file(f));
self.path.pop();
Module { name, file }
}
fn fold_path(&mut self, p: Path) -> Path {
if p.absolute {
p
} else {
self.path.clone().concat(&p)
}
}
fn fold_use(&mut self, u: Use) -> Use {
let Use { absolute, mut tree } = u;
if !absolute {
for segment in self.path.parts.iter().rev() {
tree = UseTree::Path(*segment, Box::new(tree))
}
}
Use { absolute: true, tree: self.fold_use_tree(tree) }
}
}

View File

@@ -0,0 +1,14 @@
//! Squashes group expressions
use crate::{ast::*, ast_visitor::fold::*};
/// Squashes group expressions
pub struct SquashGroups;
impl Fold for SquashGroups {
fn fold_expr_kind(&mut self, kind: ExprKind) -> ExprKind {
match kind {
ExprKind::Group(Group { expr }) => self.fold_expr(*expr).kind,
_ => or_fold_expr_kind(self, kind),
}
}
}

View File

@@ -0,0 +1,37 @@
//! Desugars `while {...} else` expressions
//! into `loop if {...} else break` expressions
use crate::{ast::*, ast_visitor::fold::Fold};
use cl_structures::span::Span;
/// Desugars while-else expressions
/// into loop-if-else-break expressions
pub struct WhileElseDesugar;
impl Fold for WhileElseDesugar {
fn fold_expr(&mut self, e: Expr) -> Expr {
let Expr { span, kind } = e;
let kind = desugar_while(span, kind);
Expr { span: self.fold_span(span), kind: self.fold_expr_kind(kind) }
}
}
/// Desugars while(-else) expressions into loop-if-else-break expressions
fn desugar_while(span: Span, kind: ExprKind) -> ExprKind {
match kind {
// work backwards: fail -> break -> if -> loop
ExprKind::While(While { cond, pass, fail: Else { body } }) => {
// Preserve the else-expression's span, if present, or use the parent's span
let fail_span = body.as_ref().map(|body| body.span).unwrap_or(span);
let break_expr = Expr { span: fail_span, kind: ExprKind::Break(Break { body }) };
let loop_body = If { cond, pass, fail: Else { body: Some(Box::new(break_expr)) } };
let loop_body = ExprKind::If(loop_body);
ExprKind::Unary(Unary {
kind: UnaryKind::Loop,
tail: Box::new(Expr { span, kind: loop_body }),
})
}
_ => kind,
}
}

View File

@@ -0,0 +1,82 @@
use delimiters::Delimiters;
use std::fmt::Write;
impl<W: Write + ?Sized> FmtAdapter for W {}
pub trait FmtAdapter: Write {
fn indent(&mut self) -> Indent<'_, Self> {
Indent { f: self }
}
fn delimit(&mut self, delim: Delimiters) -> Delimit<'_, Self> {
Delimit::new(self, delim)
}
fn delimit_with(&mut self, open: &'static str, close: &'static str) -> Delimit<'_, Self> {
Delimit::new(self, Delimiters { open, close })
}
}
/// Pads text with leading indentation after every newline
pub struct Indent<'f, F: Write + ?Sized> {
f: &'f mut F,
}
impl<F: Write + ?Sized> Write for Indent<'_, F> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
for s in s.split_inclusive('\n') {
self.f.write_str(s)?;
if s.ends_with('\n') {
self.f.write_str(" ")?;
}
}
Ok(())
}
}
/// Prints [Delimiters] around anything formatted with this. Implies [Indent]
pub struct Delimit<'f, F: Write + ?Sized> {
f: Indent<'f, F>,
delim: Delimiters,
}
impl<'f, F: Write + ?Sized> Delimit<'f, F> {
pub fn new(f: &'f mut F, delim: Delimiters) -> Self {
let mut f = f.indent();
let _ = f.write_str(delim.open);
Self { f, delim }
}
}
impl<F: Write + ?Sized> Drop for Delimit<'_, F> {
fn drop(&mut self) {
let Self { f: Indent { f, .. }, delim } = self;
let _ = f.write_str(delim.close);
}
}
impl<F: Write + ?Sized> Write for Delimit<'_, F> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.f.write_str(s)
}
}
pub mod delimiters {
#![allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub struct Delimiters {
pub open: &'static str,
pub close: &'static str,
}
/// Delimits with braces decorated with spaces `" {\n"`, ..., `"\n}"`
pub const SPACED_BRACES: Delimiters = Delimiters { open: " {\n", close: "\n}" };
/// Delimits with braces on separate lines `{\n`, ..., `\n}`
pub const BRACES: Delimiters = Delimiters { open: "{\n", close: "\n}" };
/// Delimits with parentheses on separate lines `{\n`, ..., `\n}`
pub const PARENS: Delimiters = Delimiters { open: "(\n", close: "\n)" };
/// Delimits with square brackets on separate lines `{\n`, ..., `\n}`
pub const SQUARE: Delimiters = Delimiters { open: "[\n", close: "\n]" };
/// Delimits with braces on the same line `{ `, ..., ` }`
pub const INLINE_BRACES: Delimiters = Delimiters { open: "{ ", close: " }" };
/// Delimits with parentheses on the same line `( `, ..., ` )`
pub const INLINE_PARENS: Delimiters = Delimiters { open: "(", close: ")" };
/// Delimits with square brackets on the same line `[ `, ..., ` ]`
pub const INLINE_SQUARE: Delimiters = Delimiters { open: "[", close: "]" };
}

View File

@@ -0,0 +1,23 @@
//! # The Abstract Syntax Tree
//! Contains definitions of Conlang AST Nodes.
//!
//! # Notable nodes
//! - [Item] and [ItemKind]: Top-level constructs
//! - [Stmt] and [StmtKind]: Statements
//! - [Expr] and [ExprKind]: Expressions
//! - [Assign], [Binary], and [Unary] expressions
//! - [ModifyKind], [BinaryKind], and [UnaryKind] operators
//! - [Ty] and [TyKind]: Type qualifiers
//! - [Pattern]: Pattern matching operators
//! - [Path]: Path expressions
#![warn(clippy::all)]
#![feature(decl_macro)]
pub use ast::*;
pub use ast_impl::weight_of::WeightOf;
pub mod ast;
pub mod ast_impl;
pub mod ast_visitor;
pub mod desugar;
pub mod format;

View File

@@ -0,0 +1,18 @@
[package]
name = "cl-embed"
version = "0.1.0"
repository.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
cl-interpret = { path = "../cl-interpret" }
cl-ast = { path = "../cl-ast" }
cl-structures = { path = "../cl-structures" }
cl-lexer = { path = "../cl-lexer" }
cl-parser = { path = "../cl-parser" }
[dev-dependencies]
repline = { version = "*", registry = "soft-fish" }

View File

@@ -0,0 +1,27 @@
//! Demonstrates the cl_embed library
use cl_embed::*;
use repline::{Response, prebaked};
fn main() -> Result<(), repline::Error> {
let mut env = Environment::new();
if let Err(e) = conlang_include!("calculator/expression.cl")(&mut env) {
panic!("{e}")
}
prebaked::read_and("", "calc >", " ? >", |line| {
env.bind("line", line);
let res = conlang! {
let (expr, rest) = parse(line.chars(), Power::None);
execute(expr)
}(&mut env)?;
println!("{res}");
Ok(Response::Accept)
})
}

View File

@@ -0,0 +1 @@
../../../../sample-code/calculator.cl

View File

@@ -0,0 +1,182 @@
//! Embed Conlang code into your Rust project!
//!
//! # This crate is experimental, and has no guarantees of stability.
#![feature(decl_macro)]
#![cfg_attr(test, feature(assert_matches))]
#![allow(unused_imports)]
pub use cl_interpret::{convalue::ConValue as Value, env::Environment};
use cl_ast::{Block, File, Module, ast_visitor::Fold};
use cl_interpret::{convalue::ConValue, interpret::Interpret};
use cl_lexer::Lexer;
use cl_parser::{Parser, error::Error as ParseError, inliner::ModuleInliner};
use std::{path::Path, sync::OnceLock};
/// Constructs a function which evaluates a Conlang Block
///
/// # Examples
///
/// Bind and use a variable
/// ```rust
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use cl_embed::{conlang, Environment, Value};
///
/// let mut env = Environment::new();
///
/// // Bind a variable named `message` to "Hello, world!"
/// env.bind("message", "Hello, World!");
///
/// let print_hello = conlang!{
/// println(message);
/// };
///
/// // Run the function
/// let ret = print_hello(&mut env)?;
///
/// // `println` returns Empty
/// assert!(matches!(ret, Value::Empty));
///
/// # Ok(())
/// # }
/// ```
pub macro conlang(
$($t:tt)*
) {{
// Parse once
static FN: OnceLock<Result<Block, ParseError>> = OnceLock::new();
|env: &mut Environment| -> Result<ConValue, EvalError> {
FN.get_or_init(|| {
// TODO: embed the full module tree at compile time
let path =
AsRef::<Path>::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"), "/../../", file!()))
.with_extension("");
let mut mi = ModuleInliner::new(path);
let code = mi.fold_block(
Parser::new(
concat!(file!(), ":", line!(), ":", column!()),
Lexer::new(stringify!({ $($t)* })),
)
.parse::<Block>()?,
);
if let Some((ie, pe)) = mi.into_errs() {
for (file, err) in ie {
eprintln!("{}: {err}", file.display());
}
for (file, err) in pe {
eprintln!("{}: {err}", file.display());
}
}
Ok(code)
})
.as_ref()
.map_err(Clone::clone)?
.interpret(env)
.map_err(Into::into)
}
}}
pub macro conlang_include{
($path:literal, $name:ident) => {
|env: &mut Environment| -> Result<ConValue, EvalError> {
// TODO: embed the full module tree at compile time
let path = AsRef::<Path>::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"), "/../../", file!()))
.with_file_name(concat!($path));
let mut mi = ModuleInliner::new(path);
let code = mi.fold_module(Module {
name: stringify!($name).into(),
file: Some(
Parser::new(
concat!(file!(), ":", line!(), ":", column!()),
Lexer::new(include_str!($path)),
)
.parse()?,
),
});
if let Some((ie, pe)) = mi.into_errs() {
for (file, err) in ie {
eprintln!("{}: {err}", file.display());
}
for (file, err) in pe {
eprintln!("{}: {err}", file.display());
}
}
code.interpret(env).map_err(Into::into)
}
},
($path:literal) => {
|env: &mut Environment| -> Result<ConValue, EvalError> {
// TODO: embed the full module tree at compile time
let path = AsRef::<Path>::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"), "/../../", file!()))
.with_file_name(concat!($path));
let mut mi = ModuleInliner::new(path);
let code = mi.fold_file(
Parser::new(
concat!(file!(), ":", line!(), ":", column!()),
Lexer::new(include_str!($path)),
)
.parse()?,
);
if let Some((ie, pe)) = mi.into_errs() {
for (file, err) in ie {
eprintln!("{}: {err}", file.display());
}
for (file, err) in pe {
eprintln!("{}: {err}", file.display());
}
}
code.interpret(env).map_err(Into::into)
}
}
}
#[derive(Clone, Debug)]
pub enum EvalError {
Parse(cl_parser::error::Error),
Interpret(cl_interpret::error::Error),
}
impl From<cl_parser::error::Error> for EvalError {
fn from(value: cl_parser::error::Error) -> Self {
Self::Parse(value)
}
}
impl From<cl_interpret::error::Error> for EvalError {
fn from(value: cl_interpret::error::Error) -> Self {
Self::Interpret(value)
}
}
impl std::error::Error for EvalError {}
impl std::fmt::Display for EvalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EvalError::Parse(error) => error.fmt(f),
EvalError::Interpret(error) => error.fmt(f),
}
}
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;
use super::*;
#[test]
fn it_works() -> Result<(), EvalError> {
let mut env = Environment::new();
let result = conlang! {
fn add(left, right) -> isize {
left + right
}
add(2, 2)
}(&mut env);
assert_matches!(result, Ok(Value::Int(4)));
Ok(())
}
}

View File

@@ -0,0 +1,17 @@
[package]
name = "cl-interpret"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
cl-ast = { path = "../cl-ast" }
cl-structures = { path = "../cl-structures" }
[dev-dependencies]
cl-lexer = { path = "../cl-lexer" }
cl-parser = { path = "../cl-parser" }

View File

@@ -0,0 +1,59 @@
//! A bare-minimum harness to evaluate a Conlang program
use std::{error::Error, path::PathBuf};
use cl_ast::Expr;
use cl_interpret::{convalue::ConValue, env::Environment};
use cl_lexer::Lexer;
use cl_parser::{Parser, inliner::ModuleInliner};
fn main() -> Result<(), Box<dyn Error>> {
let mut args = std::env::args();
let prog = args.next().unwrap();
let Some(path) = args.next().map(PathBuf::from) else {
println!("Usage: {prog} `file.cl` [ args... ]");
return Ok(());
};
let parent = path.parent().unwrap_or("".as_ref());
let code = std::fs::read_to_string(&path)?;
let code = Parser::new(path.display().to_string(), Lexer::new(&code)).parse()?;
let code = match ModuleInliner::new(parent).inline(code) {
Ok(code) => code,
Err((code, ioerrs, perrs)) => {
for (p, err) in ioerrs {
eprintln!("{}:{err}", p.display());
}
for (p, err) in perrs {
eprintln!("{}:{err}", p.display());
}
code
}
};
let mut env = Environment::new();
env.eval(&code)?;
let main = "main".into();
if env.get(main).is_ok() {
let args = args
.flat_map(|arg| {
Parser::new(&arg, Lexer::new(&arg))
.parse::<Expr>()
.map(|arg| env.eval(&arg))
})
.collect::<Result<Vec<_>, _>>()?;
match env.call(main, &args) {
Ok(ConValue::Empty) => {}
Ok(retval) => println!("{retval}"),
Err(e) => {
panic!("{e}");
}
}
}
Ok(())
}

View File

@@ -0,0 +1,12 @@
// Calculate Fibonacci numbers
fn main() {
for num in 0..=30 {
println!("fib({num}) = {}", fib(num))
}
}
/// Implements the classic recursive definition of fib()
fn fib(a: i64) -> i64 {
if a > 1 { fib(a - 1) + fib(a - 2) } else { a }
}

View File

@@ -0,0 +1,410 @@
#![allow(non_upper_case_globals)]
use crate::{
convalue::ConValue,
env::Environment,
error::{Error, IResult},
};
use std::io::{Write, stdout};
/// A function built into the interpreter.
#[derive(Clone, Copy)]
pub struct Builtin {
/// An identifier to be used during registration
pub name: &'static str,
/// The signature, displayed when the builtin is printed
pub desc: &'static str,
/// The function to be run when called
pub func: &'static dyn Fn(&mut Environment, &[ConValue]) -> IResult<ConValue>,
}
impl Builtin {
/// Constructs a new Builtin
pub const fn new(
name: &'static str,
desc: &'static str,
func: &'static impl Fn(&mut Environment, &[ConValue]) -> IResult<ConValue>,
) -> Builtin {
Builtin { name, desc, func }
}
pub const fn description(&self) -> &'static str {
self.desc
}
}
impl std::fmt::Debug for Builtin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Builtin")
.field("description", &self.desc)
.finish_non_exhaustive()
}
}
impl std::fmt::Display for Builtin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.desc)
}
}
impl super::Callable for Builtin {
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
(self.func)(interpreter, args)
}
fn name(&self) -> cl_ast::Sym {
self.name.into()
}
}
/// Turns a function definition into a [Builtin].
///
/// ```rust
/// # use cl_interpret::{builtin::builtin, convalue::ConValue};
/// let my_builtin = builtin! {
/// /// Use the `@env` suffix to bind the environment!
/// /// (needed for recursive calls)
/// fn my_builtin(ConValue::Bool(b), rest @ ..) @env {
/// // This is all Rust code!
/// eprintln!("my_builtin({b}, ..)");
/// match rest {
/// [] => Ok(ConValue::Empty),
/// _ => my_builtin(env, rest), // Can be called as a normal function!
/// }
/// }
/// };
/// ```
pub macro builtin(
$(#[$($meta:tt)*])*
fn $name:ident ($($arg:pat),*$(,)?) $(@$env:tt)? $body:block
) {{
$(#[$($meta)*])*
fn $name(_env: &mut Environment, _args: &[ConValue]) -> IResult<ConValue> {
// Set up the builtin! environment
$(#[allow(unused)]let $env = _env;)?
// Allow for single argument `fn foo(args @ ..)` pattern
#[allow(clippy::redundant_at_rest_pattern, irrefutable_let_patterns)]
let [$($arg),*] = _args else {
Err($crate::error::Error::TypeError())?
};
$body.map(Into::into)
}
Builtin {
name: stringify!($name),
desc: stringify![builtin fn $name($($arg),*)],
func: &$name,
}
}}
/// Constructs an array of [Builtin]s from pseudo-function definitions
pub macro builtins($(
$(#[$($meta:tt)*])*
fn $name:ident ($($args:tt)*) $(@$env:tt)? $body:block
)*) {
[$(builtin!($(#[$($meta)*])* fn $name ($($args)*) $(@$env)? $body)),*]
}
/// Creates an [Error::BuiltinError] using interpolation of runtime expressions.
/// See [std::format].
pub macro error_format ($($t:tt)*) {
$crate::error::Error::BuiltinError(format!($($t)*))
}
pub const Builtins: &[Builtin] = &builtins![
/// Unstable variadic format function
fn fmt(args @ ..) {
use std::fmt::Write;
let mut out = String::new();
if let Err(e) = args.iter().try_for_each(|arg| write!(out, "{arg}")) {
eprintln!("{e}");
}
Ok(out)
}
/// Prints the arguments in-order, with no separators
fn print(args @ ..) {
let mut out = stdout().lock();
args.iter().try_for_each(|arg| write!(out, "{arg}") ).ok();
Ok(())
}
/// Prints the arguments in-order, followed by a newline
fn println(args @ ..) {
let mut out = stdout().lock();
args.iter().try_for_each(|arg| write!(out, "{arg}") ).ok();
writeln!(out).ok();
Ok(())
}
/// Debug-prints the argument, returning a copy
fn dbg(arg) {
println!("{arg:?}");
Ok(arg.clone())
}
/// Debug-prints the argument
fn dbgp(args @ ..) {
let mut out = stdout().lock();
args.iter().try_for_each(|arg| writeln!(out, "{arg:#?}") ).ok();
Ok(())
}
fn panic(args @ ..) @env {
use std::fmt::Write;
let mut out = String::new();
if let Err(e) = args.iter().try_for_each(|arg| write!(out, "{arg}")) {
println!("{e}");
}
let mut stdout = stdout().lock();
write!(stdout, "Explicit panic: `").ok();
args.iter().try_for_each(|arg| write!(stdout, "{arg}") ).ok();
writeln!(stdout, "`").ok();
Err(Error::Panic(out))?;
Ok(())
}
/// Dumps the environment
fn dump() @env {
println!("{env}");
Ok(())
}
/// Gets all global variables in the environment
fn globals() @env {
let globals = env.globals();
Ok(ConValue::Slice(globals.base, globals.binds.len()))
}
fn builtins() @env {
let len = env.globals().binds.len();
for builtin in 0..len {
if let Some(value @ ConValue::Builtin(_)) = env.get_id(builtin) {
println!("{builtin}: {value}")
}
}
Ok(())
}
fn alloca(ConValue::Int(len)) @env {
Ok(env.alloca(ConValue::Empty, *len as usize))
}
/// Returns the length of the input list as a [ConValue::Int]
fn len(list) @env {
Ok(match list {
ConValue::Empty => 0,
ConValue::Str(s) => s.chars().count() as _,
ConValue::String(s) => s.chars().count() as _,
ConValue::Ref(r) => {
return len(env, &[env.get_id(*r).ok_or(Error::StackOverflow(*r))?.clone()])
}
ConValue::Slice(_, len) => *len as _,
ConValue::Array(arr) => arr.len() as _,
ConValue::Tuple(t) => t.len() as _,
_ => Err(Error::TypeError())?,
})
}
fn push(ConValue::Ref(index), item) @env{
let Some(ConValue::Array(v)) = env.get_id_mut(*index) else {
Err(Error::TypeError())?
};
let mut items = std::mem::take(v).into_vec();
items.push(item.clone());
*v = items.into_boxed_slice();
Ok(ConValue::Empty)
}
fn chars(string) @env {
Ok(match string {
ConValue::Str(s) => ConValue::Array(s.chars().map(Into::into).collect()),
ConValue::String(s) => ConValue::Array(s.chars().map(Into::into).collect()),
ConValue::Ref(r) => {
return chars(env, &[env.get_id(*r).ok_or(Error::StackOverflow(*r))?.clone()])
}
_ => Err(Error::TypeError())?,
})
}
fn dump_symbols() {
println!("{}", cl_structures::intern::string_interner::StringInterner::global());
Ok(ConValue::Empty)
}
fn slice_of(ConValue::Ref(arr), ConValue::Int(start)) {
Ok(ConValue::Slice(*arr, *start as usize))
}
/// Returns a shark
fn shark() {
Ok('\u{1f988}')
}
];
pub const Math: &[Builtin] = &builtins![
/// Multiplication `a * b`
fn mul(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b),
_ => Err(Error::TypeError())?
})
}
/// Division `a / b`
fn div(lhs, rhs) {
Ok(match (lhs, rhs){
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b),
_ => Err(Error::TypeError())?
})
}
/// Remainder `a % b`
fn rem(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b),
_ => Err(Error::TypeError())?,
})
}
/// Addition `a + b`
fn add(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
(ConValue::Str(a), ConValue::Str(b)) => (a.to_string() + b).into(),
(ConValue::Str(a), ConValue::String(b)) => (a.to_string() + b).into(),
(ConValue::String(a), ConValue::Str(b)) => (a.to_string() + b).into(),
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + b).into(),
(ConValue::Str(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(*c); s.into() }
(ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(*c); s.into() }
(ConValue::Char(a), ConValue::Char(b)) => {
ConValue::String([a, b].into_iter().collect::<String>())
}
_ => Err(Error::TypeError())?
})
}
/// Subtraction `a - b`
fn sub(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b),
_ => Err(Error::TypeError())?,
})
}
/// Shift Left `a << b`
fn shl(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b),
_ => Err(Error::TypeError())?,
})
}
/// Shift Right `a >> b`
fn shr(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b),
_ => Err(Error::TypeError())?,
})
}
/// Bitwise And `a & b`
fn and(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b),
_ => Err(Error::TypeError())?,
})
}
/// Bitwise Or `a | b`
fn or(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b),
_ => Err(Error::TypeError())?,
})
}
/// Bitwise Exclusive Or `a ^ b`
fn xor(lhs, rhs) {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b),
_ => Err(Error::TypeError())?,
})
}
#[allow(non_snake_case)]
fn RangeExc(start, end) @env {
Ok(ConValue::TupleStruct("RangeExc".into(), Box::new(Box::new([start.clone(), end.clone()]))))
}
#[allow(non_snake_case)]
fn RangeInc(start, end) @env {
Ok(ConValue::TupleStruct("RangeInc".into(), Box::new(Box::new([start.clone(), end.clone()]))))
}
#[allow(non_snake_case)]
fn RangeTo(end) @env {
Ok(ConValue::TupleStruct("RangeTo".into(), Box::new(Box::new([end.clone()]))))
}
#[allow(non_snake_case)]
fn RangeToInc(end) @env {
Ok(ConValue::TupleStruct("RangeToInc".into(), Box::new(Box::new([end.clone()]))))
}
/// Negates the ConValue
fn neg(tail) {
Ok(match tail {
ConValue::Empty => ConValue::Empty,
ConValue::Int(v) => ConValue::Int(v.wrapping_neg()),
ConValue::Float(v) => ConValue::Float(-v),
_ => Err(Error::TypeError())?,
})
}
/// Inverts the ConValue
fn not(tail) {
Ok(match tail {
ConValue::Empty => ConValue::Empty,
ConValue::Int(v) => ConValue::Int(!v),
ConValue::Bool(v) => ConValue::Bool(!v),
_ => Err(Error::TypeError())?,
})
}
/// Compares two values
fn cmp(head, tail) {
Ok(ConValue::Int(match (head, tail) {
(ConValue::Int(a), ConValue::Int(b)) => a.cmp(b) as _,
(ConValue::Bool(a), ConValue::Bool(b)) => a.cmp(b) as _,
(ConValue::Char(a), ConValue::Char(b)) => a.cmp(b) as _,
(ConValue::Str(a), ConValue::Str(b)) => a.cmp(b) as _,
(ConValue::Str(a), ConValue::String(b)) => a.to_ref().cmp(b.as_str()) as _,
(ConValue::String(a), ConValue::Str(b)) => a.as_str().cmp(b.to_ref()) as _,
(ConValue::String(a), ConValue::String(b)) => a.cmp(b) as _,
_ => Err(error_format!("Incomparable values: {head}, {tail}"))?
}))
}
/// Does the opposite of `&`
fn deref(tail) @env {
Ok(match tail {
ConValue::Ref(v) => env.get_id(*v).cloned().ok_or(Error::StackOverflow(*v))?,
_ => tail.clone(),
})
}
];

View File

@@ -0,0 +1,68 @@
use crate::{
Callable,
convalue::ConValue,
env::Environment,
error::{Error, ErrorKind, IResult},
function::collect_upvars::CollectUpvars,
interpret::Interpret,
pattern,
};
use cl_ast::{Sym, ast_visitor::Visit};
use std::{collections::HashMap, fmt::Display};
/// Represents an ad-hoc anonymous function
/// which captures surrounding state by COPY
#[derive(Clone, Debug)]
pub struct Closure {
decl: cl_ast::Closure,
lift: HashMap<Sym, ConValue>,
}
impl Closure {
const NAME: &'static str = "{closure}";
}
impl Closure {
pub fn new(env: &mut Environment, decl: &cl_ast::Closure) -> Self {
let lift = CollectUpvars::new(env).visit(decl).finish_copied();
Self { decl: decl.clone(), lift }
}
}
impl Display for Closure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { decl, lift: _ } = self;
write!(f, "{decl}")
}
}
impl Callable for Closure {
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
let Self { decl, lift } = self;
let mut env = env.frame(Self::NAME);
// place lifts in scope
for (name, value) in lift.clone() {
env.insert(name, value);
}
let mut env = env.frame("args");
for (name, value) in pattern::substitution(&env, &decl.arg, ConValue::Tuple(args.into()))? {
env.insert(name, value);
}
let res = decl.body.interpret(&mut env);
drop(env);
match res {
Err(Error { kind: ErrorKind::Return(value), .. }) => Ok(value),
Err(Error { kind: ErrorKind::Break(value), .. }) => Err(Error::BadBreak(value)),
other => other,
}
}
fn name(&self) -> cl_ast::Sym {
Self::NAME.into()
}
}

View File

@@ -0,0 +1,449 @@
//! Values in the dynamically typed AST interpreter.
//!
//! The most permanent fix is a temporary one.
use cl_ast::{Expr, Sym, format::FmtAdapter};
use crate::{closure::Closure, constructor::Constructor};
use super::{
Callable, Environment,
builtin::Builtin,
error::{Error, IResult},
function::Function,
};
use std::{collections::HashMap, ops::*, rc::Rc};
/*
A Value can be:
- A Primitive (Empty, isize, etc.)
- A Record (Array, Tuple, Struct)
- A Variant (discriminant, Value) pair
array [
10, // 0
20, // 1
]
tuple (
10, // 0
20, // 1
)
struct {
x: 10, // x => 0
y: 20, // y => 1
}
*/
type Integer = isize;
/// A Conlang value stores data in the interpreter
#[derive(Clone, Debug, Default)]
pub enum ConValue {
/// The empty/unit `()` type
#[default]
Empty,
/// An integer
Int(Integer),
/// A floating point number
Float(f64),
/// A boolean
Bool(bool),
/// A unicode character
Char(char),
/// A string literal
Str(Sym),
/// A dynamic string
String(String),
/// A reference
Ref(usize),
/// A reference to an array
Slice(usize, usize),
/// An Array
Array(Box<[ConValue]>),
/// A tuple
Tuple(Box<[ConValue]>),
// TODO: Instead of storing the identifier, store the index of the struct module
/// A value of a product type
Struct(Sym, Box<HashMap<Sym, ConValue>>),
/// A value of a product type with anonymous members
TupleStruct(Sym, Box<Box<[ConValue]>>),
/// An entire namespace
Module(Box<HashMap<Sym, ConValue>>),
/// A quoted expression
Quote(Rc<Expr>),
/// A callable thing
Function(Rc<Function>),
/// A tuple constructor
TupleConstructor(Constructor),
/// A closure, capturing by reference
Closure(Rc<Closure>),
/// A built-in function
Builtin(&'static Builtin),
}
impl ConValue {
/// Gets whether the current value is true or false
pub fn truthy(&self) -> IResult<bool> {
match self {
ConValue::Bool(v) => Ok(*v),
_ => Err(Error::TypeError())?,
}
}
pub fn typename(&self) -> &'static str {
match self {
ConValue::Empty => "Empty",
ConValue::Int(_) => "i64",
ConValue::Float(_) => "f64",
ConValue::Bool(_) => "bool",
ConValue::Char(_) => "char",
ConValue::Str(_) => "str",
ConValue::String(_) => "String",
ConValue::Ref(_) => "Ref",
ConValue::Slice(_, _) => "Slice",
ConValue::Array(_) => "Array",
ConValue::Tuple(_) => "Tuple",
ConValue::Struct(_, _) => "Struct",
ConValue::TupleStruct(_, _) => "TupleStruct",
ConValue::Module(_) => "",
ConValue::Quote(_) => "Quote",
ConValue::Function(_) => "Fn",
ConValue::TupleConstructor(_) => "Fn",
ConValue::Closure(_) => "Fn",
ConValue::Builtin(_) => "Fn",
}
}
#[allow(non_snake_case)]
pub fn TupleStruct(id: Sym, values: Box<[ConValue]>) -> Self {
Self::TupleStruct(id, Box::new(values))
}
#[allow(non_snake_case)]
pub fn Struct(id: Sym, values: HashMap<Sym, ConValue>) -> Self {
Self::Struct(id, Box::new(values))
}
pub fn index(&self, index: &Self, _env: &Environment) -> IResult<ConValue> {
let &Self::Int(index) = index else {
Err(Error::TypeError())?
};
match self {
ConValue::Str(string) => string
.chars()
.nth(index as _)
.map(ConValue::Char)
.ok_or(Error::OobIndex(index as usize, string.chars().count())),
ConValue::String(string) => string
.chars()
.nth(index as _)
.map(ConValue::Char)
.ok_or(Error::OobIndex(index as usize, string.chars().count())),
ConValue::Array(arr) => arr
.get(index as usize)
.cloned()
.ok_or(Error::OobIndex(index as usize, arr.len())),
&ConValue::Slice(id, len) => {
let index = if index < 0 {
len.wrapping_add_signed(index)
} else {
index as usize
};
if index < len {
Ok(ConValue::Ref(id + index))
} else {
Err(Error::OobIndex(index, len))
}
}
_ => Err(Error::TypeError()),
}
}
cmp! {
lt: false, <;
lt_eq: true, <=;
eq: true, ==;
neq: false, !=;
gt_eq: true, >=;
gt: false, >;
}
assign! {
add_assign: +;
bitand_assign: &;
bitor_assign: |;
bitxor_assign: ^;
div_assign: /;
mul_assign: *;
rem_assign: %;
shl_assign: <<;
shr_assign: >>;
sub_assign: -;
}
}
impl Callable for ConValue {
fn name(&self) -> Sym {
match self {
ConValue::Function(func) => func.name(),
ConValue::Closure(func) => func.name(),
ConValue::Builtin(func) => func.name(),
_ => "".into(),
}
}
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
match self {
Self::Function(func) => func.call(env, args),
Self::TupleConstructor(func) => func.call(env, args),
Self::Closure(func) => func.call(env, args),
Self::Builtin(func) => func.call(env, args),
Self::Module(m) => {
if let Some(func) = m.get(&"call".into()) {
func.call(env, args)
} else {
Err(Error::NotCallable(self.clone()))
}
}
&Self::Ref(ptr) => {
// Move onto stack, and call
let func = env.get_id(ptr).ok_or(Error::StackOverflow(ptr))?.clone();
func.call(env, args)
}
_ => Err(Error::NotCallable(self.clone())),
}
}
}
/// Templates comparison functions for [ConValue]
macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$(
/// TODO: Remove when functions are implemented:
/// Desugar into function calls
pub fn $fn(&self, other: &Self) -> IResult<Self> {
match (self, other) {
(Self::Empty, Self::Empty) => Ok(Self::Bool($empty)),
(Self::Int(a), Self::Int(b)) => Ok(Self::Bool(a $op b)),
(Self::Float(a), Self::Float(b)) => Ok(Self::Bool(a $op b)),
(Self::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)),
(Self::Char(a), Self::Char(b)) => Ok(Self::Bool(a $op b)),
(Self::Str(a), Self::Str(b)) => Ok(Self::Bool(&**a $op &**b)),
(Self::Str(a), Self::String(b)) => Ok(Self::Bool(&**a $op &**b)),
(Self::String(a), Self::Str(b)) => Ok(Self::Bool(&**a $op &**b)),
(Self::String(a), Self::String(b)) => Ok(Self::Bool(&**a $op &**b)),
_ => Err(Error::TypeError())
}
}
)*}
macro assign($( $fn: ident: $op: tt );*$(;)?) {$(
pub fn $fn(&mut self, other: Self) -> IResult<()> {
*self = (std::mem::take(self) $op other)?;
Ok(())
}
)*}
/// Implements [From] for an enum with 1-tuple variants
macro from ($($T:ty => $v:expr),*$(,)?) {
$(impl From<$T> for ConValue {
fn from(value: $T) -> Self { $v(value.into()) }
})*
}
impl From<&Sym> for ConValue {
fn from(value: &Sym) -> Self {
ConValue::Str(*value)
}
}
from! {
Integer => ConValue::Int,
f64 => ConValue::Float,
bool => ConValue::Bool,
char => ConValue::Char,
Sym => ConValue::Str,
&str => ConValue::Str,
Expr => ConValue::Quote,
String => ConValue::String,
Rc<str> => ConValue::Str,
Function => ConValue::Function,
Vec<ConValue> => ConValue::Tuple,
&'static Builtin => ConValue::Builtin,
}
impl From<()> for ConValue {
fn from(_: ()) -> Self {
Self::Empty
}
}
impl From<&[ConValue]> for ConValue {
fn from(value: &[ConValue]) -> Self {
match value {
[] => Self::Empty,
[value] => value.clone(),
_ => Self::Tuple(value.into()),
}
}
}
/// Implements binary [std::ops] traits for [ConValue]
///
/// TODO: Desugar operators into function calls
macro ops($($trait:ty: $fn:ident = [$($match:tt)*])*) {
$(impl $trait for ConValue {
type Output = IResult<Self>;
/// TODO: Desugar operators into function calls
fn $fn(self, rhs: Self) -> Self::Output {Ok(match (self, rhs) {$($match)*})}
})*
}
ops! {
Add: add = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_add(b)),
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a + b),
(ConValue::Str(a), ConValue::Str(b)) => (a.to_string() + &*b).into(),
(ConValue::Str(a), ConValue::String(b)) => (a.to_string() + &*b).into(),
(ConValue::String(a), ConValue::Str(b)) => (a.to_string() + &*b).into(),
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &*b).into(),
(ConValue::Str(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() }
(ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() }
(ConValue::Char(a), ConValue::Char(b)) => {
ConValue::String([a, b].into_iter().collect::<String>())
}
_ => Err(Error::TypeError())?
]
BitAnd: bitand = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b),
_ => Err(Error::TypeError())?
]
BitOr: bitor = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b),
_ => Err(Error::TypeError())?
]
BitXor: bitxor = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b),
_ => Err(Error::TypeError())?
]
Div: div = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_div(b).unwrap_or_else(|| {
eprintln!("Warning: Divide by zero in {a} / {b}"); a
})),
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a / b),
_ => Err(Error::TypeError())?
]
Mul: mul = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(b)),
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a * b),
_ => Err(Error::TypeError())?
]
Rem: rem = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_rem(b).unwrap_or_else(|| {
println!("Warning: Divide by zero in {a} % {b}"); a
})),
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a % b),
_ => Err(Error::TypeError())?
]
Shl: shl = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shl(b as _)),
_ => Err(Error::TypeError())?
]
Shr: shr = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shr(b as _)),
_ => Err(Error::TypeError())?
]
Sub: sub = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(b)),
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a - b),
_ => Err(Error::TypeError())?
]
}
impl std::fmt::Display for ConValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConValue::Empty => "Empty".fmt(f),
ConValue::Int(v) => v.fmt(f),
ConValue::Float(v) => v.fmt(f),
ConValue::Bool(v) => v.fmt(f),
ConValue::Char(v) => v.fmt(f),
ConValue::Str(v) => v.fmt(f),
ConValue::String(v) => v.fmt(f),
ConValue::Ref(v) => write!(f, "&<{}>", v),
ConValue::Slice(id, len) => write!(f, "&<{id}>[{len}..]"),
ConValue::Array(array) => {
'['.fmt(f)?;
for (idx, element) in array.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?
}
element.fmt(f)?
}
']'.fmt(f)
}
ConValue::Tuple(tuple) => {
'('.fmt(f)?;
for (idx, element) in tuple.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?
}
element.fmt(f)?
}
')'.fmt(f)
}
ConValue::TupleStruct(id, tuple) => {
write!(f, "{id}")?;
'('.fmt(f)?;
for (idx, element) in tuple.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?
}
element.fmt(f)?
}
')'.fmt(f)
}
ConValue::Struct(id, map) => {
use std::fmt::Write;
write!(f, "{id} ")?;
let mut f = f.delimit_with("{", "\n}");
for (k, v) in map.iter() {
write!(f, "\n{k}: {v},")?;
}
Ok(())
}
ConValue::Module(module) => {
use std::fmt::Write;
let mut f = f.delimit_with("{", "\n}");
for (k, v) in module.iter() {
write!(f, "\n{k}: {v},")?;
}
Ok(())
}
ConValue::Quote(q) => {
write!(f, "`{q}`")
}
ConValue::Function(func) => {
write!(f, "{}", func.decl())
}
ConValue::TupleConstructor(Constructor { name: index, arity }) => {
write!(f, "{index}(..{arity})")
}
ConValue::Closure(func) => {
write!(f, "{}", func.as_ref())
}
ConValue::Builtin(func) => {
write!(f, "{}", func)
}
}
}
}
pub macro cvstruct (
$Name:ident {
$($member:ident : $expr:expr),*
}
) {{
let mut members = HashMap::new();
$(members.insert(stringify!($member).into(), ($expr).into());)*
ConValue::Struct(Box::new((stringify!($Name).into(), members)))
}}

View File

@@ -0,0 +1,310 @@
//! Lexical and non-lexical scoping for variables
use crate::{builtin::Builtin, constructor::Constructor, modules::ModuleTree};
use super::{
Callable, Interpret,
builtin::{Builtins, Math},
convalue::ConValue,
error::{Error, IResult},
function::Function,
};
use cl_ast::{Function as FnDecl, Sym};
use std::{
collections::HashMap,
fmt::Display,
ops::{Deref, DerefMut},
rc::Rc,
};
pub type StackFrame = HashMap<Sym, ConValue>;
pub type StackBinds = HashMap<Sym, usize>;
#[derive(Clone, Debug, Default)]
pub(crate) struct EnvFrame {
pub name: Option<&'static str>,
/// The length of the array when this stack frame was constructed
pub base: usize,
/// The bindings of name to stack position
pub binds: StackBinds,
}
/// Implements a nested lexical scope
#[derive(Clone, Debug)]
pub struct Environment {
values: Vec<ConValue>,
frames: Vec<EnvFrame>,
modules: ModuleTree,
}
impl Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for EnvFrame { name, base: _, binds } in self.frames.iter().rev() {
writeln!(
f,
"--- {}[{}] ---",
if let Some(name) = name { name } else { "" },
binds.len(),
)?;
let mut binds: Vec<_> = binds.iter().collect();
binds.sort_by(|(_, a), (_, b)| a.cmp(b));
for (name, idx) in binds {
write!(f, "{idx:4} {name}: ")?;
match self.values.get(*idx) {
Some(value) => writeln!(f, "\t{value}"),
None => writeln!(f, "ERROR: {name}'s address blows the stack!"),
}?
}
}
Ok(())
}
}
impl Default for Environment {
fn default() -> Self {
let mut this = Self::no_builtins();
this.add_builtins(Builtins).add_builtins(Math);
this
}
}
impl Environment {
pub fn new() -> Self {
Self::default()
}
/// Creates an [Environment] with no [builtins](super::builtin)
pub fn no_builtins() -> Self {
Self {
values: Vec::new(),
frames: vec![EnvFrame::default()],
modules: ModuleTree::default(),
}
}
/// Reflexively evaluates a node
pub fn eval(&mut self, node: &impl Interpret) -> IResult<ConValue> {
node.interpret(self)
}
/// Calls a function inside the Environment's scope,
/// and returns the result
pub fn call(&mut self, name: Sym, args: &[ConValue]) -> IResult<ConValue> {
let function = self.get(name)?;
function.call(self, args)
}
pub fn modules_mut(&mut self) -> &mut ModuleTree {
&mut self.modules
}
pub fn modules(&self) -> &ModuleTree {
&self.modules
}
/// Binds a value to the given name in the current scope.
pub fn bind(&mut self, name: impl Into<Sym>, value: impl Into<ConValue>) {
self.insert(name.into(), value.into());
}
pub fn bind_raw(&mut self, name: Sym, id: usize) -> Option<()> {
let EnvFrame { name: _, base: _, binds } = self.frames.last_mut()?;
binds.insert(name, id);
Some(())
}
/// Gets all registered globals, bound or unbound.
pub(crate) fn globals(&self) -> &EnvFrame {
self.frames.first().unwrap()
}
/// Adds builtins
///
/// # Panics
///
/// Will panic if stack contains more than the globals frame!
pub fn add_builtins(&mut self, builtins: &'static [Builtin]) -> &mut Self {
if self.frames.len() != 1 {
panic!("Cannot add builtins to full stack: {self}")
}
for builtin in builtins {
self.insert(builtin.name(), builtin.into());
}
self
}
pub fn push_frame(&mut self, name: &'static str, frame: StackFrame) {
self.frames.push(EnvFrame {
name: Some(name),
base: self.values.len(),
binds: HashMap::new(),
});
for (k, v) in frame {
self.insert(k, v);
}
}
pub fn pop_frame(&mut self) -> Option<(StackFrame, &'static str)> {
let mut out = HashMap::new();
let EnvFrame { name, base, binds } = self.frames.pop()?;
for (k, v) in binds {
out.insert(k, self.values.get_mut(v).map(std::mem::take)?);
}
self.values.truncate(base);
Some((out, name.unwrap_or("")))
}
/// Enters a nested scope, returning a [`Frame`] stack-guard.
///
/// [`Frame`] implements Deref/DerefMut for [`Environment`].
pub fn frame(&mut self, name: &'static str) -> Frame<'_> {
Frame::new(self, name)
}
/// Enters a nested scope, assigning the contents of `frame`,
/// and returning a [`Frame`] stack-guard.
///
/// [`Frame`] implements Deref/DerefMut for [`Environment`].
pub fn with_frame<'e>(&'e mut self, name: &'static str, frame: StackFrame) -> Frame<'e> {
let mut scope = self.frame(name);
for (k, v) in frame {
scope.insert(k, v);
}
scope
}
/// Resolves a variable mutably.
///
/// Returns a mutable reference to the variable's record, if it exists.
pub fn get_mut(&mut self, name: Sym) -> IResult<&mut ConValue> {
let at = self.id_of(name)?;
self.get_id_mut(at).ok_or(Error::NotDefined(name))
}
/// Resolves a variable immutably.
///
/// Returns a reference to the variable's contents, if it is defined and initialized.
pub fn get(&self, name: Sym) -> IResult<ConValue> {
let id = self.id_of(name)?;
let res = self.values.get(id);
Ok(res.ok_or(Error::NotDefined(name))?.clone())
}
/// Resolves the index associated with a [Sym]
pub fn id_of(&self, name: Sym) -> IResult<usize> {
for EnvFrame { binds, .. } in self.frames.iter().rev() {
if let Some(id) = binds.get(&name).copied() {
return Ok(id);
}
}
Err(Error::NotDefined(name))
}
pub fn get_id(&self, id: usize) -> Option<&ConValue> {
self.values.get(id)
}
pub fn get_id_mut(&mut self, id: usize) -> Option<&mut ConValue> {
self.values.get_mut(id)
}
pub fn get_slice(&self, start: usize, len: usize) -> Option<&[ConValue]> {
self.values.get(start..start + len)
}
pub fn get_slice_mut(&mut self, start: usize, len: usize) -> Option<&mut [ConValue]> {
self.values.get_mut(start..start + len)
}
/// Inserts a new [ConValue] into this [Environment]
pub fn insert(&mut self, k: Sym, v: ConValue) {
if self.bind_raw(k, self.values.len()).is_some() {
self.values.push(v);
}
}
/// A convenience function for registering a [FnDecl] as a [Function]
pub fn insert_fn(&mut self, decl: &FnDecl) {
let FnDecl { name, .. } = decl;
let (name, function) = (*name, Rc::new(Function::new(decl)));
self.insert(name, ConValue::Function(function.clone()));
// Tell the function to lift its upvars now, after it's been declared
function.lift_upvars(self);
}
pub fn insert_tup_constructor(&mut self, name: Sym, arity: usize) {
let cs = Constructor { arity: arity as _, name };
self.insert(name, ConValue::TupleConstructor(cs));
}
/// Gets the current stack top position
pub fn pos(&self) -> usize {
self.values.len()
}
/// Allocates a local variable
pub fn stack_alloc(&mut self, value: ConValue) -> IResult<usize> {
let adr = self.values.len();
self.values.push(value);
Ok(adr)
}
/// Allocates some space on the stack
pub fn alloca(&mut self, value: ConValue, len: usize) -> ConValue {
let idx = self.values.len();
self.values.extend(std::iter::repeat_n(value, len));
ConValue::Slice(idx, len)
}
}
/// Represents a stack frame
#[derive(Debug)]
pub struct Frame<'scope> {
scope: &'scope mut Environment,
}
impl<'scope> Frame<'scope> {
fn new(scope: &'scope mut Environment, name: &'static str) -> Self {
scope.frames.push(EnvFrame {
name: Some(name),
base: scope.values.len(),
binds: HashMap::new(),
});
Self { scope }
}
pub fn pop_values(mut self) -> Option<StackFrame> {
let mut out = HashMap::new();
let binds = std::mem::take(&mut self.frames.last_mut()?.binds);
for (k, v) in binds {
out.insert(k, self.values.get_mut(v).map(std::mem::take)?);
}
Some(out)
}
pub fn into_binds(mut self) -> Option<StackBinds> {
let EnvFrame { name: _, base: _, binds } = self.frames.pop()?;
std::mem::forget(self);
Some(binds)
}
}
impl Deref for Frame<'_> {
type Target = Environment;
fn deref(&self) -> &Self::Target {
self.scope
}
}
impl DerefMut for Frame<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.scope
}
}
impl Drop for Frame<'_> {
fn drop(&mut self) {
if let Some(frame) = self.frames.pop() {
self.values.truncate(frame.base);
}
}
}

View File

@@ -0,0 +1,218 @@
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
use cl_ast::{Pattern, Sym};
use cl_structures::span::Span;
use super::convalue::ConValue;
pub type IResult<T> = Result<T, Error>;
#[derive(Clone, Debug)]
pub struct Error {
pub kind: ErrorKind,
pub(super) span: Option<Span>,
}
impl Error {
#![allow(non_snake_case)]
/// Adds a [struct Span] to this [Error], if there isn't already a more specific one.
pub fn with_span(self, span: Span) -> Self {
Self { span: self.span.or(Some(span)), ..self }
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
/// Propagate a Return value
pub fn Return(value: ConValue) -> Self {
Self { kind: ErrorKind::Return(value), span: None }
}
/// Propagate a Break value
pub fn Break(value: ConValue) -> Self {
Self { kind: ErrorKind::Break(value), span: None }
}
/// Break propagated across function bounds
pub fn BadBreak(value: ConValue) -> Self {
Self { kind: ErrorKind::BadBreak(value), span: None }
}
/// Continue to the next iteration of a loop
pub fn Continue() -> Self {
Self { kind: ErrorKind::Continue, span: None }
}
/// Underflowed the stack
pub fn StackUnderflow() -> Self {
Self { kind: ErrorKind::StackUnderflow, span: None }
}
/// Overflowed the stack
pub fn StackOverflow(place: usize) -> Self {
Self { kind: ErrorKind::StackOverflow(place), span: None }
}
/// Exited the last scope
pub fn ScopeExit() -> Self {
Self { kind: ErrorKind::ScopeExit, span: None }
}
/// Type incompatibility
// TODO: store the type information in this error
pub fn TypeError() -> Self {
Self { kind: ErrorKind::TypeError, span: None }
}
/// In clause of For loop didn't yield a Range
pub fn NotIterable() -> Self {
Self { kind: ErrorKind::NotIterable, span: None }
}
/// A value could not be indexed
pub fn NotIndexable() -> Self {
Self { kind: ErrorKind::NotIndexable, span: None }
}
/// An array index went out of bounds
pub fn OobIndex(index: usize, length: usize) -> Self {
Self { kind: ErrorKind::OobIndex(index, length), span: None }
}
/// An expression is not assignable
pub fn NotAssignable() -> Self {
Self { kind: ErrorKind::NotAssignable, span: None }
}
/// A name was not defined in scope before being used
pub fn NotDefined(name: Sym) -> Self {
Self { kind: ErrorKind::NotDefined(name), span: None }
}
/// A name was defined but not initialized
pub fn NotInitialized(name: Sym) -> Self {
Self { kind: ErrorKind::NotInitialized(name), span: None }
}
/// A value was called, but is not callable
pub fn NotCallable(value: ConValue) -> Self {
Self { kind: ErrorKind::NotCallable(value), span: None }
}
/// A function was called with the wrong number of arguments
pub fn ArgNumber(want: usize, got: usize) -> Self {
Self { kind: ErrorKind::ArgNumber { want, got }, span: None }
}
/// A pattern failed to match
pub fn PatFailed(pat: Box<Pattern>) -> Self {
Self { kind: ErrorKind::PatFailed(pat), span: None }
}
/// Fell through a non-exhaustive match
pub fn MatchNonexhaustive() -> Self {
Self { kind: ErrorKind::MatchNonexhaustive, span: None }
}
/// Explicit panic
pub fn Panic(msg: String) -> Self {
Self { kind: ErrorKind::Panic(msg, 0), span: None }
}
/// Error produced by a Builtin
pub fn BuiltinError(msg: String) -> Self {
Self { kind: ErrorKind::BuiltinError(msg), span: None }
}
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { kind, span } = self;
if let Some(Span { head, tail }) = span {
write!(f, "{head}..{tail}: ")?;
}
write!(f, "{kind}")
}
}
/// Represents any error thrown by the [Environment](super::Environment)
#[derive(Clone, Debug)]
pub enum ErrorKind {
/// Propagate a Return value
Return(ConValue),
/// Propagate a Break value
Break(ConValue),
/// Break propagated across function bounds
BadBreak(ConValue),
/// Continue to the next iteration of a loop
Continue,
/// Underflowed the stack
StackUnderflow,
/// Overflowed the stack
StackOverflow(usize),
/// Exited the last scope
ScopeExit,
/// Type incompatibility
// TODO: store the type information in this error
TypeError,
/// In clause of For loop didn't yield a Range
NotIterable,
/// A value could not be indexed
NotIndexable,
/// An array index went out of bounds
OobIndex(usize, usize),
/// An expression is not assignable
NotAssignable,
/// A name was not defined in scope before being used
NotDefined(Sym),
/// A name was defined but not initialized
NotInitialized(Sym),
/// A value was called, but is not callable
NotCallable(ConValue),
/// A function was called with the wrong number of arguments
ArgNumber { want: usize, got: usize },
/// A pattern failed to match
PatFailed(Box<Pattern>),
/// Fell through a non-exhaustive match
MatchNonexhaustive,
/// Explicit panic
Panic(String, usize),
/// Error produced by a Builtin
BuiltinError(String),
}
impl std::error::Error for ErrorKind {}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::Return(value) => write!(f, "return {value}"),
ErrorKind::Break(value) => write!(f, "break {value}"),
ErrorKind::BadBreak(value) => write!(f, "rogue break: {value}"),
ErrorKind::Continue => "continue".fmt(f),
ErrorKind::StackUnderflow => "Stack underflow".fmt(f),
ErrorKind::StackOverflow(id) => {
write!(f, "Attempt to access <{id}> resulted in stack overflow.")
}
ErrorKind::ScopeExit => "Exited the last scope. This is a logic bug.".fmt(f),
ErrorKind::TypeError => "Incompatible types".fmt(f),
ErrorKind::NotIterable => "`in` clause of `for` loop did not yield an iterable".fmt(f),
ErrorKind::NotIndexable => {
write!(f, "expression cannot be indexed")
}
ErrorKind::OobIndex(idx, len) => {
write!(f, "Index out of bounds: index was {idx}. but len is {len}")
}
ErrorKind::NotAssignable => {
write!(f, "expression is not assignable")
}
ErrorKind::NotDefined(value) => {
write!(f, "{value} not bound. Did you mean `let {value};`?")
}
ErrorKind::NotInitialized(value) => {
write!(f, "{value} bound, but not initialized")
}
ErrorKind::NotCallable(value) => {
write!(f, "{value} is not callable.")
}
ErrorKind::ArgNumber { want, got } => {
write!(
f,
"Expected {want} argument{}, got {got}",
if *want == 1 { "" } else { "s" }
)
}
ErrorKind::PatFailed(pattern) => {
write!(f, "Failed to match pattern {pattern}")
}
ErrorKind::MatchNonexhaustive => {
write!(f, "Fell through a non-exhaustive match expression!")
}
ErrorKind::Panic(s, _depth) => write!(f, "Explicit panic: {s}"),
ErrorKind::BuiltinError(s) => write!(f, "{s}"),
}
}
}

View File

@@ -0,0 +1,83 @@
//! Represents a block of code which lives inside the Interpreter
use collect_upvars::collect_upvars;
use crate::error::ErrorKind;
use super::{Callable, ConValue, Environment, Error, IResult, Interpret, pattern};
use cl_ast::{Function as FnDecl, Sym};
use std::{
cell::{Ref, RefCell},
collections::HashMap,
rc::Rc,
};
pub mod collect_upvars;
type Upvars = HashMap<Sym, ConValue>;
/// Represents a block of code which persists inside the Interpreter
#[derive(Clone, Debug)]
pub struct Function {
/// Stores the contents of the function declaration
decl: Rc<FnDecl>,
/// Stores data from the enclosing scopes
upvars: RefCell<Upvars>,
}
impl Function {
pub fn new(decl: &FnDecl) -> Self {
// let upvars = collect_upvars(decl, env);
Self { decl: decl.clone().into(), upvars: Default::default() }
}
pub fn decl(&self) -> &FnDecl {
&self.decl
}
pub fn upvars(&self) -> Ref<'_, Upvars> {
self.upvars.borrow()
}
pub fn lift_upvars(&self, env: &Environment) {
let upvars = collect_upvars(&self.decl, env);
if let Ok(mut self_upvars) = self.upvars.try_borrow_mut() {
*self_upvars = upvars;
}
}
}
impl Callable for Function {
fn name(&self) -> Sym {
let FnDecl { name, .. } = *self.decl;
name
}
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
let FnDecl { name, gens: _, bind, body, sign: _ } = &*self.decl;
// Check arg mapping
let Some(body) = body else {
return Err(Error::NotDefined(*name));
};
let upvars = self.upvars.take();
let mut env = env.with_frame("upvars", upvars);
// TODO: completely refactor data storage
let mut frame = env.frame("fn args");
for (name, value) in pattern::substitution(&frame, bind, ConValue::Tuple(args.into()))? {
frame.insert(name, value);
}
let res = body.interpret(&mut frame);
drop(frame);
if let Some(upvars) = env.pop_values() {
self.upvars.replace(upvars);
}
match res {
Err(Error { kind: ErrorKind::Return(value), .. }) => Ok(value),
Err(Error { kind: ErrorKind::Break(value), .. }) => Err(Error::BadBreak(value)),
Err(Error { kind: ErrorKind::Panic(msg, depth), span: Some(span) }) => {
println!("{depth:>4}: {name}{bind} at {}", span.head);
Err(Error { kind: ErrorKind::Panic(msg, depth + 1), span: None })
}
other => other,
}
}
}

View File

@@ -0,0 +1,112 @@
//! Collects the "Upvars" of a function at the point of its creation, allowing variable capture
use crate::env::Environment;
use cl_ast::{
Function, Let, Path, PathPart, Pattern, Sym,
ast_visitor::{visit::*, walk::Walk},
};
use std::collections::{HashMap, HashSet};
pub fn collect_upvars(f: &Function, env: &Environment) -> super::Upvars {
CollectUpvars::new(env).visit(f).finish_copied()
}
#[derive(Clone, Debug)]
pub struct CollectUpvars<'env> {
env: &'env Environment,
upvars: HashMap<Sym, usize>,
blacklist: HashSet<Sym>,
}
impl<'env> CollectUpvars<'env> {
pub fn new(env: &'env Environment) -> Self {
Self { upvars: HashMap::new(), blacklist: HashSet::new(), env }
}
pub fn finish(&mut self) -> HashMap<Sym, usize> {
std::mem::take(&mut self.upvars)
}
pub fn finish_copied(&mut self) -> super::Upvars {
let Self { env, upvars, blacklist: _ } = self;
std::mem::take(upvars)
.into_iter()
.filter_map(|(k, v)| env.get_id(v).cloned().map(|v| (k, v)))
.collect()
}
pub fn add_upvar(&mut self, name: &Sym) {
let Self { env, upvars, blacklist } = self;
if blacklist.contains(name) || upvars.contains_key(name) {
return;
}
if let Ok(place) = env.id_of(*name) {
upvars.insert(*name, place);
}
}
pub fn bind_name(&mut self, name: &Sym) {
self.blacklist.insert(*name);
}
pub fn scope(&mut self, f: impl Fn(&mut CollectUpvars<'env>)) {
let blacklist = self.blacklist.clone();
// visit the scope
f(self);
// restore the blacklist
self.blacklist = blacklist;
}
}
impl<'a> Visit<'a> for CollectUpvars<'_> {
fn visit_block(&mut self, b: &'a cl_ast::Block) {
self.scope(|cu| b.children(cu));
}
fn visit_let(&mut self, l: &'a cl_ast::Let) {
let Let { mutable, name, ty, init } = l;
self.visit_mutability(mutable);
ty.visit_in(self);
// visit the initializer, which may use the bound name
init.visit_in(self);
// a bound name can never be an upvar
self.visit_pattern(name);
}
fn visit_path(&mut self, p: &'a cl_ast::Path) {
// TODO: path resolution in environments
let Path { absolute: false, parts } = p else {
return;
};
let [PathPart::Ident(name)] = parts.as_slice() else {
return;
};
self.add_upvar(name);
}
fn visit_fielder(&mut self, f: &'a cl_ast::Fielder) {
let cl_ast::Fielder { name, init } = f;
if let Some(init) = init {
self.visit_expr(init);
} else {
self.add_upvar(name); // fielder without init grabs from env
}
}
fn visit_pattern(&mut self, value: &'a cl_ast::Pattern) {
match value {
Pattern::Name(name) => {
self.bind_name(name);
}
Pattern::RangeExc(_, _) | Pattern::RangeInc(_, _) => {}
_ => value.children(self),
}
}
fn visit_match_arm(&mut self, value: &'a cl_ast::MatchArm) {
// MatchArms bind variables with a very small local scope
self.scope(|cu| value.children(cu));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,671 @@
//! Walks a Conlang AST, interpreting it as a program.
#![warn(clippy::all)]
#![feature(decl_macro)]
use cl_ast::Sym;
use convalue::ConValue;
use env::Environment;
use error::{Error, ErrorKind, IResult};
use interpret::Interpret;
/// Callable types can be called from within a Conlang program
pub trait Callable {
/// Calls this [Callable] in the provided [Environment], with [ConValue] args \
/// The Callable is responsible for checking the argument count and validating types
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue>;
/// Returns the common name of this identifier.
fn name(&self) -> Sym;
}
pub mod convalue;
pub mod interpret;
pub mod function;
pub mod constructor {
use cl_ast::Sym;
use crate::{
Callable,
convalue::ConValue,
env::Environment,
error::{Error, IResult},
};
#[derive(Clone, Copy, Debug)]
pub struct Constructor {
pub name: Sym,
pub arity: u32,
}
impl Callable for Constructor {
fn call(&self, _env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
let &Self { name, arity } = self;
if arity as usize == args.len() {
Ok(ConValue::TupleStruct(name, Box::new(args.into())))
} else {
Err(Error::ArgNumber(arity as usize, args.len()))
}
}
fn name(&self) -> cl_ast::Sym {
"tuple-constructor".into()
}
}
}
pub mod closure;
pub mod builtin;
pub mod pattern;
pub mod env;
pub mod modules {
use crate::env::StackBinds;
use cl_ast::{PathPart, Sym};
use std::collections::HashMap;
/// Immutable object-oriented interface to a [ModuleTree]
#[derive(Clone, Copy, Debug)]
pub struct ModuleNode<'tree> {
tree: &'tree ModuleTree,
index: usize,
}
/// Mutable object-oriented interface to a [ModuleTree]
#[derive(Debug)]
pub struct ModuleNodeMut<'tree> {
tree: &'tree mut ModuleTree,
index: usize,
}
macro_rules! module_node_impl {
() => {
/// Gets the index from this node
pub fn index(self) -> usize {
self.index
}
/// Gets this node's parent
pub fn parent(self) -> Option<Self> {
let parent = self.tree.parent(self.index)?;
Some(Self { index: parent, ..self })
}
/// Gets the node's "encompassing Type"
pub fn selfty(self) -> Option<Self> {
let selfty = self.tree.selfty(self.index)?;
Some(Self { index: selfty, ..self })
}
/// Gets the child of this node with the given name
pub fn child(self, name: &Sym) -> Option<Self> {
let child = self.tree.child(self.index, name)?;
Some(Self { index: child, ..self })
}
/// Gets a stack value in this node with the given name
pub fn item(self, name: &Sym) -> Option<usize> {
self.tree.items(self.index)?.get(name).copied()
}
/// Returns true when this node represents type information
pub fn is_ty(self) -> Option<bool> {
self.tree.is_ty.get(self.index).copied()
}
/// Returns a reference to this node's children, if present
pub fn children(&self) -> Option<&HashMap<Sym, usize>> {
self.tree.children(self.index)
}
/// Returns a reference to this node's items, if present
pub fn items(&self) -> Option<&StackBinds> {
self.tree.items(self.index)
}
/// Traverses a path starting at this node
///
/// Returns a new node, and the unconsumed path portion.
pub fn find(self, path: &[PathPart]) -> (Self, &[PathPart]) {
let (index, path) = self.tree.find(self.index, path);
(Self { index, ..self }, path)
}
/// Traverses a path starting at this node
///
/// Returns an item address if the path terminated in an item.
pub fn find_item(&self, path: &[PathPart]) -> Option<usize> {
self.tree.find_item(self.index, path)
}
};
}
impl ModuleNode<'_> {
module_node_impl! {}
}
impl ModuleNodeMut<'_> {
module_node_impl! {}
/// Creates a new child in this node
pub fn add_child(self, name: Sym, is_ty: bool) -> Self {
let node = self.tree.add_child(self.index, name, is_ty);
self.tree.get_mut(node)
}
/// Creates an arbitrary edge in the module graph
pub fn add_import(&mut self, name: Sym, child: usize) {
self.tree.add_import(self.index, name, child)
}
pub fn add_imports(&mut self, binds: HashMap<Sym, usize>) {
self.tree.add_imports(self.index, binds)
}
/// Binds a new item in this node
pub fn add_item(&mut self, name: Sym, stack_index: usize) {
self.tree.add_item(self.index, name, stack_index)
}
/// Binds an entire stack frame in this node
pub fn add_items(&mut self, binds: StackBinds) {
self.tree.add_items(self.index, binds)
}
/// Constructs a borrowing [ModuleNode]
pub fn as_ref(&self) -> ModuleNode<'_> {
let Self { tree, index } = self;
ModuleNode { tree, index: *index }
}
}
#[derive(Clone, Debug)]
pub struct ModuleTree {
parents: Vec<usize>,
children: Vec<HashMap<Sym, usize>>,
items: Vec<StackBinds>,
is_ty: Vec<bool>,
}
impl ModuleTree {
/// Constructs a new ModuleTree with a single root module
pub fn new() -> Self {
Self {
parents: vec![0],
children: vec![HashMap::new()],
items: vec![HashMap::new()],
is_ty: vec![false],
}
}
/// Gets a borrowed handle to the node at `index`
pub fn get(&self, index: usize) -> ModuleNode<'_> {
ModuleNode { tree: self, index }
}
/// Gets a mutable handle to the node at `index`
pub fn get_mut(&mut self, index: usize) -> ModuleNodeMut<'_> {
ModuleNodeMut { tree: self, index }
}
/// Creates a new child in this node
pub fn add_child(&mut self, parent: usize, name: Sym, is_ty: bool) -> usize {
let index = self.parents.len();
self.children[parent].insert(name, index);
self.parents.push(parent);
self.children.push(HashMap::new());
self.is_ty.push(is_ty);
index
}
/// Binds a new item in this node
pub fn add_item(&mut self, node: usize, name: Sym, stack_index: usize) {
self.items[node].insert(name, stack_index);
}
/// Creates an arbitrary child edge
pub fn add_import(&mut self, parent: usize, name: Sym, child: usize) {
self.children[parent].insert(name, child);
}
/// Binds an entire stack frame in this node
pub fn add_items(&mut self, node: usize, binds: StackBinds) {
self.items[node].extend(binds);
}
/// Binds an arbitrary set of child edges
pub fn add_imports(&mut self, node: usize, binds: HashMap<Sym, usize>) {
self.children[node].extend(binds);
}
/// Gets this node's parent
pub fn parent(&self, node: usize) -> Option<usize> {
if node == 0 {
return None;
}
self.parents.get(node).copied()
}
/// Gets the node's "encompassing Type"
pub fn selfty(&self, node: usize) -> Option<usize> {
if self.is_ty[node] {
return Some(node);
}
self.selfty(self.parent(node)?)
}
/// Gets the child of this node with the given name
pub fn child(&self, node: usize, id: &Sym) -> Option<usize> {
self.children[node].get(id).copied()
}
/// Gets a stack value in this node with the given name
pub fn item(&self, node: usize, name: &Sym) -> Option<usize> {
self.items.get(node).and_then(|map| map.get(name).copied())
}
/// Returns a reference to this node's children, if present
pub fn children(&self, node: usize) -> Option<&HashMap<Sym, usize>> {
self.children.get(node)
}
/// Returns a reference to this node's items, if present
pub fn items(&self, node: usize) -> Option<&StackBinds> {
self.items.get(node)
}
/// Traverses a path starting at this node
///
/// Returns a new node, and the unconsumed path portion.
pub fn find<'p>(&self, node: usize, path: &'p [PathPart]) -> (usize, &'p [PathPart]) {
match path {
[PathPart::SuperKw, tail @ ..] => match self.parent(node) {
Some(node) => self.find(node, tail),
None => (node, path),
},
[PathPart::Ident(name), tail @ ..] => match self.child(node, name) {
Some(node) => self.find(node, tail),
None => (node, path),
},
[PathPart::SelfTy, tail @ ..] => match self.selfty(node) {
Some(node) => self.find(node, tail),
None => (node, path),
},
[] => (node, path),
}
}
/// Traverses a path starting at this node
///
/// Returns an item address if the path terminated in an item.
pub fn find_item(&self, node: usize, path: &[PathPart]) -> Option<usize> {
let (node, [PathPart::Ident(name)]) = self.find(node, path) else {
return None;
};
self.item(node, name)
}
}
impl Default for ModuleTree {
fn default() -> Self {
Self::new()
}
}
}
pub mod collector {
use std::ops::{Deref, DerefMut};
use crate::{
convalue::ConValue,
env::Environment,
modules::{ModuleNode, ModuleNodeMut},
};
use cl_ast::{
ast_visitor::{Visit, Walk},
*,
};
pub struct Collector<'env> {
module: usize,
env: &'env mut Environment,
}
impl Collector<'_> {
pub fn as_node(&self) -> ModuleNode<'_> {
self.env.modules().get(self.module)
}
pub fn as_node_mut(&mut self) -> ModuleNodeMut<'_> {
self.env.modules_mut().get_mut(self.module)
}
pub fn scope(&mut self, name: Sym, is_ty: bool, f: impl Fn(&mut Collector<'_>)) {
let module = match self.as_node_mut().child(&name) {
Some(m) => m,
None => self.as_node_mut().add_child(name, is_ty),
}
.index();
let mut frame = self.env.frame(name.to_ref());
f(&mut Collector { env: &mut frame, module });
let binds = frame.into_binds().unwrap_or_default();
self.modules_mut().add_items(module, binds);
}
pub fn in_foreign_scope<F, T>(&mut self, path: &[PathPart], f: F) -> Option<T>
where F: Fn(&mut Collector<'_>) -> T {
let (module, []) = self.env.modules_mut().find(self.module, path) else {
return None;
};
let mut frame = self.env.frame("impl");
let out = f(&mut Collector { env: &mut frame, module });
let binds = frame.into_binds().unwrap_or_default();
self.env.modules_mut().add_items(module, binds);
Some(out)
}
}
impl<'env> Deref for Collector<'env> {
type Target = Environment;
fn deref(&self) -> &Self::Target {
self.env
}
}
impl DerefMut for Collector<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.env
}
}
impl<'a, 'env> Visit<'a> for Collector<'env> {
fn visit_file(&mut self, value: &'a File) {
let mut sorter = ItemSorter::default();
sorter.visit(value);
sorter.visit_all(self);
}
fn visit_block(&mut self, value: &'a Block) {
let mut sorter = ItemSorter::default();
sorter.visit(value);
sorter.visit_all(self);
}
fn visit_module(&mut self, value: &'a cl_ast::Module) {
self.scope(value.name, false, |scope| value.children(scope));
}
fn visit_alias(&mut self, value: &'a cl_ast::Alias) {
let Alias { name, from } = value;
match from.as_ref().map(Box::as_ref) {
Some(Ty { kind: TyKind::Path(path), .. }) => {
let mut node = if path.absolute {
self.modules_mut().get_mut(0)
} else {
self.as_node_mut()
};
if let Some(item) = node.find_item(&path.parts) {
node.add_item(*name, item);
}
}
Some(other) => todo!("Type expressions in the collector: {other}"),
None => self.scope(*name, true, |_| {}),
}
}
fn visit_enum(&mut self, value: &'a cl_ast::Enum) {
let Enum { name, gens: _, variants } = value;
self.scope(*name, true, |frame| {
for (idx, Variant { name, kind, body }) in variants.iter().enumerate() {
frame.visit(body);
frame.scope(*name, false, |frame| {
frame.bind("__discriminant", idx as isize);
match kind {
StructKind::Empty => {
frame.insert_tup_constructor("call".into(), 0);
frame.bind("__nmemb", ConValue::Int(0));
}
StructKind::Tuple(args) => {
// Constructs the AST from scratch. TODO: This, better.
frame.insert_tup_constructor("call".into(), args.len());
frame.bind("__nmemb", ConValue::Int(args.len() as _));
}
StructKind::Struct(members) => {
// TODO: more precise type checking of structs
for (idx, memb) in members.iter().enumerate() {
let StructMember { vis: _, name, ty: _ } = memb;
frame.bind(*name, idx as isize);
}
frame.bind("__nmemb", ConValue::Int(members.len() as _));
}
}
});
}
});
}
fn visit_struct(&mut self, value: &'a cl_ast::Struct) {
let Struct { name, gens: _, kind } = value;
self.scope(*name, true, |frame| {
match kind {
StructKind::Empty => {
frame.insert_tup_constructor("call".into(), 0);
frame.bind("__nmemb", ConValue::Int(0));
}
StructKind::Tuple(args) => {
// Constructs the AST from scratch. TODO: This, better.
frame.insert_tup_constructor("call".into(), args.len());
frame.bind("__nmemb", ConValue::Int(args.len() as _));
}
StructKind::Struct(members) => {
// TODO: more precise type checking of structs
for (idx, memb) in members.iter().enumerate() {
let StructMember { vis: _, name, ty: _ } = memb;
frame.bind(*name, idx as isize);
}
frame.bind("__nmemb", ConValue::Int(members.len() as _));
}
}
});
}
fn visit_const(&mut self, value: &'a cl_ast::Const) {
let Const { name, ty: _, init } = value;
self.visit(init);
self.bind(*name, ());
}
fn visit_static(&mut self, value: &'a cl_ast::Static) {
let Static { mutable: _, name, ty: _, init } = value;
self.visit(init);
self.bind(*name, ());
}
fn visit_function(&mut self, value: &'a cl_ast::Function) {
let Function { name, gens: _, sign: _, bind: _, body } = value;
self.scope(*name, false, |scope| {
scope.visit(body);
let f = crate::function::Function::new(value);
scope.bind("call", f);
});
}
fn visit_impl(&mut self, value: &'a cl_ast::Impl) {
let Impl { gens: _, target: ImplKind::Type(Ty { kind: TyKind::Path(name), .. }), body } =
value
else {
eprintln!("TODO: impl X for Ty");
return;
};
self.in_foreign_scope(&name.parts, |scope| {
body.visit_in(scope);
});
}
fn visit_use(&mut self, value: &'a cl_ast::Use) {
fn traverse(dest: &mut Collector<'_>, node: usize, tree: &UseTree) {
match tree {
UseTree::Tree(ts) => ts.iter().for_each(|tree| traverse(dest, node, tree)),
UseTree::Path(PathPart::Ident(name), tree) => {
if let (node, []) = dest.modules().find(node, &[PathPart::Ident(*name)]) {
traverse(dest, node, tree)
}
}
UseTree::Path(PathPart::SuperKw, tree) => {
if let Some(node) = dest.modules().parent(node) {
traverse(dest, node, tree)
}
}
UseTree::Path(PathPart::SelfTy, tree) => {
if let Some(node) = dest.modules().selfty(node) {
traverse(dest, node, tree)
}
}
UseTree::Alias(name, as_name) => {
if let Some(child) = dest.modules().child(node, name) {
dest.as_node_mut().add_import(*as_name, child);
}
if let Some(item) = dest.modules().item(node, name) {
dest.as_node_mut().add_item(*as_name, item);
}
}
UseTree::Name(name) => {
if let Some(child) = dest.modules().child(node, name) {
dest.as_node_mut().add_import(*name, child);
}
if let Some(item) = dest.modules().item(node, name) {
dest.as_node_mut().add_item(*name, item);
}
}
UseTree::Glob => {
let &mut Collector { module, ref mut env } = dest;
if let Some(children) = env.modules().children(node) {
for (name, index) in children.clone() {
env.modules_mut().add_import(module, name, index);
}
}
if let Some(items) = env.modules().items(node).cloned() {
env.modules_mut().add_items(node, items);
}
}
}
}
let Use { absolute, tree } = value;
let node = if *absolute { 0 } else { self.module };
traverse(self, node, tree);
}
}
// fn make_tuple_constructor(name: Sym, args: &[Ty]) -> ConValue {
// let span = match (
// args.first().map(|a| a.span.head),
// args.last().map(|a| a.span.tail),
// ) {
// (Some(head), Some(tail)) => Span(head, tail),
// _ => Span::dummy(),
// };
// let constructor = Function {
// name,
// gens: Default::default(),
// sign: TyFn {
// args: Ty { kind: TyKind::Tuple(TyTuple { types: args.to_vec() }), span }.into(),
// rety: Some(Ty { span: Span::dummy(), kind: TyKind::Path(Path::from(name))
// }.into()), },
// bind: Pattern::Tuple(
// args.iter()
// .enumerate()
// .map(|(idx, _)| Pattern::Name(idx.to_string().into()))
// .collect(),
// ),
// body: None,
// };
// // ConValue::TupleConstructor(crate::constructor::Constructor {ind})
// todo!("Tuple constructor {constructor}")
// }
/// Sorts items
#[derive(Debug, Default)]
struct ItemSorter<'ast> {
modules: Vec<&'ast Module>,
structs: Vec<&'ast Struct>,
enums: Vec<&'ast Enum>,
aliases: Vec<&'ast Alias>,
consts: Vec<&'ast Const>,
statics: Vec<&'ast Static>,
functions: Vec<&'ast Function>,
impls: Vec<&'ast Impl>,
imports: Vec<&'ast Use>,
}
impl<'a> ItemSorter<'a> {
fn visit_all<V: Visit<'a>>(&self, v: &mut V) {
let Self {
modules,
aliases,
enums,
structs,
consts,
statics,
functions,
impls,
imports,
} = self;
// 0
for item in modules {
item.visit_in(v);
}
// 1
for item in structs {
item.visit_in(v);
}
for item in enums {
item.visit_in(v);
}
for item in aliases {
item.visit_in(v);
}
// 2
// 5
for item in consts {
item.visit_in(v);
}
for item in statics {
item.visit_in(v);
}
for item in functions {
item.visit_in(v);
}
// 4
for item in impls {
item.visit_in(v);
}
// 3
for item in imports {
item.visit_in(v);
}
}
}
impl<'a> Visit<'a> for ItemSorter<'a> {
fn visit_module(&mut self, value: &'a cl_ast::Module) {
self.modules.push(value);
}
fn visit_alias(&mut self, value: &'a cl_ast::Alias) {
self.aliases.push(value);
}
fn visit_enum(&mut self, value: &'a cl_ast::Enum) {
self.enums.push(value);
}
fn visit_struct(&mut self, value: &'a cl_ast::Struct) {
self.structs.push(value);
}
fn visit_const(&mut self, value: &'a cl_ast::Const) {
self.consts.push(value);
}
fn visit_static(&mut self, value: &'a cl_ast::Static) {
self.statics.push(value);
}
fn visit_function(&mut self, value: &'a cl_ast::Function) {
self.functions.push(value);
}
fn visit_impl(&mut self, value: &'a cl_ast::Impl) {
self.impls.push(value);
}
fn visit_use(&mut self, value: &'a cl_ast::Use) {
self.imports.push(value);
}
}
}
pub mod error;
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,347 @@
//! Unification algorithm for cl-ast [Pattern]s and [ConValue]s
//!
//! [`variables()`] returns a flat list of symbols that are bound by a given pattern
//! [`substitution()`] unifies a ConValue with a pattern, and produces a list of bound names
use crate::{
convalue::ConValue,
env::Environment,
error::{Error, IResult},
};
use cl_ast::{Literal, Pattern, Sym};
use std::collections::{HashMap, VecDeque};
/// Gets the path variables in the given Pattern
pub fn variables(pat: &Pattern) -> Vec<&Sym> {
fn patvars<'p>(set: &mut Vec<&'p Sym>, pat: &'p Pattern) {
match pat {
Pattern::Name(name) if name.to_ref() == "_" => {}
Pattern::Name(name) => set.push(name),
Pattern::Path(_) => {}
Pattern::Literal(_) => {}
Pattern::Rest(Some(pattern)) => patvars(set, pattern),
Pattern::Rest(None) => {}
Pattern::Ref(_, pattern) => patvars(set, pattern),
Pattern::RangeExc(_, _) => {}
Pattern::RangeInc(_, _) => {}
Pattern::Tuple(patterns) | Pattern::Array(patterns) => {
patterns.iter().for_each(|pat| patvars(set, pat))
}
Pattern::Struct(_path, items) => {
items.iter().for_each(|(name, pat)| match pat {
Some(pat) => patvars(set, pat),
None => set.push(name),
});
}
Pattern::TupleStruct(_path, items) => {
items.iter().for_each(|pat| patvars(set, pat));
}
}
}
let mut set = Vec::new();
patvars(&mut set, pat);
set
}
fn rest_binding<'pat>(
env: &Environment,
sub: &mut HashMap<Sym, ConValue>,
mut patterns: &'pat [Pattern],
mut values: VecDeque<ConValue>,
) -> IResult<Option<(&'pat Pattern, VecDeque<ConValue>)>> {
// Bind the head of the list
while let [pattern, tail @ ..] = patterns {
if matches!(pattern, Pattern::Rest(_)) {
break;
}
let value = values
.pop_front()
.ok_or_else(|| Error::PatFailed(Box::new(pattern.clone())))?;
append_sub(env, sub, pattern, value)?;
patterns = tail;
}
// Bind the tail of the list
while let [head @ .., pattern] = patterns {
if matches!(pattern, Pattern::Rest(_)) {
break;
}
let value = values
.pop_back()
.ok_or_else(|| Error::PatFailed(Box::new(pattern.clone())))?;
append_sub(env, sub, pattern, value)?;
patterns = head;
}
// Bind the ..rest of the list
match patterns {
[] | [Pattern::Rest(None)] => Ok(None),
[Pattern::Rest(Some(pattern))] => Ok(Some((pattern.as_ref(), values))),
_ => Err(Error::PatFailed(Box::new(Pattern::Array(patterns.into())))),
}
}
fn rest_binding_ref<'pat>(
env: &Environment,
sub: &mut HashMap<Sym, ConValue>,
mut patterns: &'pat [Pattern],
mut head: usize,
mut tail: usize,
) -> IResult<Option<(&'pat Pattern, usize, usize)>> {
// Bind the head of the list
while let [pattern, pat_tail @ ..] = patterns {
if matches!(pattern, Pattern::Rest(_)) {
break;
}
if head >= tail {
return Err(Error::PatFailed(Box::new(pattern.clone())));
}
append_sub(env, sub, pattern, ConValue::Ref(head))?;
head += 1;
patterns = pat_tail;
}
// Bind the tail of the list
while let [pat_head @ .., pattern] = patterns {
if matches!(pattern, Pattern::Rest(_)) {
break;
}
if head >= tail {
return Err(Error::PatFailed(Box::new(pattern.clone())));
};
append_sub(env, sub, pattern, ConValue::Ref(tail))?;
tail -= 1;
patterns = pat_head;
}
// Bind the ..rest of the list
match (patterns, tail - head) {
([], 0) | ([Pattern::Rest(None)], _) => Ok(None),
([Pattern::Rest(Some(pattern))], _) => Ok(Some((pattern.as_ref(), head, tail))),
_ => Err(Error::PatFailed(Box::new(Pattern::Array(patterns.into())))),
}
}
/// Appends a substitution to the provided table
pub fn append_sub(
env: &Environment,
sub: &mut HashMap<Sym, ConValue>,
pat: &Pattern,
value: ConValue,
) -> IResult<()> {
match (pat, value) {
(Pattern::Literal(Literal::Bool(a)), ConValue::Bool(b)) => {
(*a == b).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::Char(a)), ConValue::Char(b)) => {
(*a == b).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::Float(a)), ConValue::Float(b)) => (f64::from_bits(*a) == b)
.then_some(())
.ok_or(Error::NotAssignable()),
(Pattern::Literal(Literal::Int(a)), ConValue::Int(b)) => {
(b == *a as _).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::String(a)), ConValue::Str(b)) => {
(*a == *b).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::String(a)), ConValue::String(b)) => {
(*a == *b).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(_), _) => Err(Error::NotAssignable()),
(Pattern::Rest(Some(pat)), value) => match (pat.as_ref(), value) {
(Pattern::Literal(Literal::Int(a)), ConValue::Int(b)) => {
(b < *a as _).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::Char(a)), ConValue::Char(b)) => {
(b < *a as _).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::Bool(a)), ConValue::Bool(b)) => {
(!b & *a).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::Float(a)), ConValue::Float(b)) => {
(b < *a as _).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::String(a)), ConValue::Str(b)) => {
(&*b < a).then_some(()).ok_or(Error::NotAssignable())
}
(Pattern::Literal(Literal::String(a)), ConValue::String(b)) => {
(&*b < a).then_some(()).ok_or(Error::NotAssignable())
}
_ => Err(Error::NotAssignable()),
},
(Pattern::Name(name), _) if "_".eq(&**name) => Ok(()),
(Pattern::Name(name), value) => {
sub.insert(*name, value);
Ok(())
}
(Pattern::Ref(_, pat), ConValue::Ref(r)) => match env.get_id(r) {
Some(value) => append_sub(env, sub, pat, value.clone()),
None => Err(Error::PatFailed(pat.clone())),
},
(Pattern::Ref(_, pat), ConValue::Slice(head, len)) => {
let mut values = Vec::with_capacity(len);
for idx in head..(head + len) {
values.push(env.get_id(idx).cloned().ok_or(Error::StackOverflow(idx))?);
}
append_sub(env, sub, pat, ConValue::Array(values.into_boxed_slice()))
}
(Pattern::RangeExc(head, tail), value) => match (head.as_ref(), tail.as_ref(), value) {
(
Pattern::Literal(Literal::Int(a)),
Pattern::Literal(Literal::Int(c)),
ConValue::Int(b),
) => (*a as isize <= b as _ && b < *c as isize)
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::Char(a)),
Pattern::Literal(Literal::Char(c)),
ConValue::Char(b),
) => (*a <= b && b < *c)
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::Float(a)),
Pattern::Literal(Literal::Float(c)),
ConValue::Float(b),
) => (f64::from_bits(*a) <= b && b < f64::from_bits(*c))
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::String(a)),
Pattern::Literal(Literal::String(c)),
ConValue::Str(b),
) => (a.as_str() <= b.to_ref() && b.to_ref() < c.as_str())
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::String(a)),
Pattern::Literal(Literal::String(c)),
ConValue::String(b),
) => (a.as_str() <= b.as_str() && b.as_str() < c.as_str())
.then_some(())
.ok_or(Error::NotAssignable()),
_ => Err(Error::NotAssignable()),
},
(Pattern::RangeInc(head, tail), value) => match (head.as_ref(), tail.as_ref(), value) {
(
Pattern::Literal(Literal::Int(a)),
Pattern::Literal(Literal::Int(c)),
ConValue::Int(b),
) => (*a as isize <= b && b <= *c as isize)
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::Char(a)),
Pattern::Literal(Literal::Char(c)),
ConValue::Char(b),
) => (*a <= b && b <= *c)
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::Float(a)),
Pattern::Literal(Literal::Float(c)),
ConValue::Float(b),
) => (f64::from_bits(*a) <= b && b <= f64::from_bits(*c))
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::String(a)),
Pattern::Literal(Literal::String(c)),
ConValue::Str(b),
) => (a.as_str() <= b.to_ref() && b.to_ref() <= c.as_str())
.then_some(())
.ok_or(Error::NotAssignable()),
(
Pattern::Literal(Literal::String(a)),
Pattern::Literal(Literal::String(c)),
ConValue::String(b),
) => (a.as_str() <= b.as_str() && b.as_str() <= c.as_str())
.then_some(())
.ok_or(Error::NotAssignable()),
_ => Err(Error::NotAssignable()),
},
(Pattern::Array(patterns), ConValue::Array(values)) => {
match rest_binding(env, sub, patterns, values.into_vec().into())? {
Some((pattern, values)) => {
append_sub(env, sub, pattern, ConValue::Array(Vec::from(values).into()))
}
_ => Ok(()),
}
}
(Pattern::Array(patterns), ConValue::Slice(head, len)) => {
match rest_binding_ref(env, sub, patterns, head, head + len)? {
Some((pat, head, tail)) => {
append_sub(env, sub, pat, ConValue::Slice(head, tail - head))
}
None => Ok(()),
}
}
(Pattern::Tuple(patterns), ConValue::Empty) if patterns.is_empty() => Ok(()),
(Pattern::Tuple(patterns), ConValue::Tuple(values)) => {
match rest_binding(env, sub, patterns, values.into_vec().into())? {
Some((pattern, values)) => {
append_sub(env, sub, pattern, ConValue::Tuple(Vec::from(values).into()))
}
_ => Ok(()),
}
}
(Pattern::TupleStruct(path, patterns), ConValue::TupleStruct(id, values)) => {
let tid = path
.as_sym()
.ok_or_else(|| Error::PatFailed(pat.clone().into()))?;
if id != tid {
return Err(Error::PatFailed(pat.clone().into()));
}
match rest_binding(env, sub, patterns, values.into_vec().into())? {
Some((pattern, values)) => {
append_sub(env, sub, pattern, ConValue::Tuple(Vec::from(values).into()))
}
_ => Ok(()),
}
}
(Pattern::Struct(path, patterns), ConValue::Struct(id, mut values)) => {
let tid = path
.as_sym()
.ok_or_else(|| Error::PatFailed(pat.clone().into()))?;
if id != tid {
return Err(Error::PatFailed(pat.clone().into()));
}
for (name, pat) in patterns {
let value = values.remove(name).ok_or(Error::TypeError())?;
match pat {
Some(pat) => append_sub(env, sub, pat, value)?,
None => {
sub.insert(*name, value);
}
}
}
Ok(())
}
_ => {
// eprintln!("Could not match pattern `{pat}` with value `{value}`!");
Err(Error::NotAssignable())
}
}
}
/// Constructs a substitution from a pattern and a value
pub fn substitution(
env: &Environment,
pat: &Pattern,
value: ConValue,
) -> IResult<HashMap<Sym, ConValue>> {
let mut sub = HashMap::new();
append_sub(env, &mut sub, pat, value)?;
Ok(sub)
}

View File

@@ -1,10 +1,8 @@
#![allow(unused_imports)]
use crate::{
ast::*,
interpreter::{env::Environment, temp_type_impl::ConValue, Interpret},
lexer::Lexer,
parser::Parser,
};
use crate::{Interpret, convalue::ConValue, env::Environment};
use cl_ast::*;
use cl_lexer::Lexer;
use cl_parser::Parser;
pub use macros::*;
mod macros {
@@ -49,7 +47,8 @@ mod macros {
//! env_eq!(env.x, 10); // like assert_eq! for Environments
//! ```
#![allow(unused_macros)]
use crate::interpreter::IResult;
use crate::IResult;
use cl_parser::parser::Parse;
use super::*;
@@ -65,14 +64,14 @@ mod macros {
///
/// Returns a `Result<`[`File`]`, ParseError>`
pub macro file($($t:tt)*) {
Parser::new(Lexer::new(stringify!( $($t)* ))).file()
File::parse(&mut Parser::new(Lexer::new(stringify!( $($t)* ))))
}
/// Stringifies, lexes, and parses everything you give to it
///
/// Returns a `Result<`[`Block`]`, ParseError>`
pub macro block($($t:tt)*) {
Parser::new(Lexer::new(stringify!({ $($t)* }))).block()
Block::parse(&mut Parser::new("test", Lexer::new(stringify!({ $($t)* }))))
}
/// Evaluates a block of code in the given environment
@@ -129,7 +128,7 @@ mod macros {
}
pub macro env_ne($env:ident.$var:ident, $expr:expr) {{
let evaluated = $env.get(stringify!($var))
let evaluated = $env.get(stringify!($var).into())
.expect(stringify!($var should be defined and initialized));
if !conv_cmp!(neq, evaluated, $expr) {
panic!("assertion {} ({evaluated}) != {} failed.", stringify!($var), stringify!($expr))
@@ -137,7 +136,7 @@ mod macros {
}}
pub macro env_eq($env:ident.$var:ident, $expr:expr) {{
let evaluated = $env.get(stringify!($var))
let evaluated = $env.get(stringify!($var).into())
.expect(stringify!($var should be defined and initialized));
if !conv_cmp!(eq, evaluated, $expr) {
panic!("assertion {} ({evaluated}) == {} failed.", stringify!($var), stringify!($expr))
@@ -179,6 +178,45 @@ mod let_declarations {
env_eq!(env.x, 10);
env_eq!(env.y, 10);
}
#[test]
fn let_destructuring_tuple() {
let mut env = Environment::new();
assert_eval!(env,
let (x, y) = (10, 20);
);
env_eq!(env.x, 10);
env_eq!(env.y, 20);
}
#[test]
fn let_destructuring_array() {
let mut env = Environment::new();
assert_eval!(env,
let [x, y] = [10, 20];
);
env_eq!(env.x, 10);
env_eq!(env.y, 20);
}
#[test]
fn let_destructuring_nested() {
let mut env = Environment::new();
assert_eval!(env,
let (x, [one, two, three], (a, b, c))
= ('x', [1, 2, 3], ('a', 'b', 'c'));
);
env_eq!(env.x, 'x');
env_eq!(env.one, 1);
env_eq!(env.two, 2);
env_eq!(env.three, 3);
env_eq!(env.a, 'a');
env_eq!(env.b, 'b');
env_eq!(env.c, 'c');
}
}
mod fn_declarations {
@@ -189,10 +227,10 @@ mod fn_declarations {
assert_eval!(env, fn empty_fn() {});
// TODO: true equality for functions
assert_eq!(
"fn empty_fn",
"fn empty_fn () {}",
format!(
"{}",
env.get("empty_fn")
env.get("empty_fn".into())
.expect(stringify!(empty_fn should be defined and initialized))
)
)
@@ -212,7 +250,7 @@ mod fn_declarations {
}
mod operators {
use crate::ast::Tuple;
use cl_ast::Tuple;
use super::*;
#[test]
@@ -365,7 +403,7 @@ mod operators {
env_eq!(env.is_10_ne_20, true); // !=
env_eq!(env.is_10_ge_20, false); // >=
env_eq!(env.is_10_gt_20, false); // >
// Equal to
// Equal to
env_eq!(env.is_10_lt_10, false);
env_eq!(env.is_10_le_10, true);
env_eq!(env.is_10_eq_10, true);
@@ -438,16 +476,17 @@ mod operators {
env_eq!(env.y, 10);
env_eq!(env.z, 10);
}
#[test]
#[should_panic]
fn assignment_accounts_for_type() {
let mut env = Default::default();
assert_eval!(env,
let x = "a string";
let y = 0xdeadbeef;
y = x; // should crash: type error
);
}
// Test is disabled, since new assignment system intentionally does not care.
// #[test]
// #[should_panic]
// fn assignment_accounts_for_type() {
// let mut env = Default::default();
// assert_eval!(env,
// let x = "a string";
// let y = 0xdeadbeef;
// y = x; // should crash: type error
// );
// }
#[test]
fn precedence() {
let mut env = Default::default();
@@ -470,6 +509,75 @@ mod operators {
}
}
mod control_flow {
use super::*;
#[test]
fn if_evaluates_pass_block_on_true() {
let mut env = Default::default();
assert_eval!(env,
let evaluated = if true { "pass" } else { "fail" }
);
env_eq!(env.evaluated, "pass");
}
#[test]
fn if_evaluates_fail_block_on_false() {
let mut env = Default::default();
assert_eval!(env,
let evaluated = if false { "pass" } else { "fail" }
);
env_eq!(env.evaluated, "fail");
}
#[test]
fn while_evaluates_fail_block_on_false() {
let mut env = Default::default();
assert_eval!(env,
let cond = true;
let evaluated = while cond { cond = false } else { true }
);
env_eq!(env.evaluated, true);
}
#[test]
fn while_does_not_evaluate_fail_block_on_break() {
let mut env = Default::default();
assert_eval!(env,
let evaluated = while true { break true } else { false }
);
env_eq!(env.evaluated, true);
}
#[test]
fn match_evaluates_in_order() {
let mut env = Default::default();
assert_eval!(env,
let x = '\u{1f988}';
let passed = match x {
'\u{1f988}' => true,
_ => false,
};
);
env_eq!(env.passed, true);
}
#[test]
fn match_sinkoles_underscore_patterns() {
let mut env = Default::default();
assert_eval!(env,
let x = '\u{1f988}';
let passed = match x {
_ => true,
'\u{1f988}' => false,
};
);
env_eq!(env.passed, true);
}
//TODO: test other control flow constructs like loops, while-else, etc.
}
#[allow(dead_code)]
fn test_template() {
let mut env = Default::default();

View File

@@ -0,0 +1,13 @@
[package]
name = "cl-lexer"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
cl-token = { path = "../cl-token" }
cl-structures = { path = "../cl-structures" }
unicode-ident = "1.0.12"

View File

@@ -0,0 +1,522 @@
//! Converts a text file into tokens
#![warn(clippy::all)]
#![feature(decl_macro)]
use cl_structures::span::Loc;
use cl_token::{TokenKind as Kind, *};
use std::{
iter::Peekable,
str::{CharIndices, FromStr},
};
use unicode_ident::*;
#[cfg(test)]
mod tests;
pub mod lexer_iter {
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
use super::{
Lexer, Token,
error::{LResult, Reason},
};
/// Iterator over a [`Lexer`], returning [`LResult<Token>`]s
pub struct LexerIter<'t> {
lexer: Lexer<'t>,
}
impl Iterator for LexerIter<'_> {
type Item = LResult<Token>;
fn next(&mut self) -> Option<Self::Item> {
match self.lexer.scan() {
Ok(v) => Some(Ok(v)),
Err(e) => {
if e.reason == Reason::EndOfFile {
None
} else {
Some(Err(e))
}
}
}
}
}
impl<'t> IntoIterator for Lexer<'t> {
type Item = LResult<Token>;
type IntoIter = LexerIter<'t>;
fn into_iter(self) -> Self::IntoIter {
LexerIter { lexer: self }
}
}
}
/// The Lexer iterates over the characters in a body of text, searching for [Tokens](Token).
///
/// # Examples
/// ```rust
/// # use cl_lexer::Lexer;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// // Read in your code from somewhere
/// let some_code = "
/// fn main () {
/// // TODO: code goes here!
/// }
/// ";
/// // Create a lexer over your code
/// let mut lexer = Lexer::new(some_code);
/// // Scan for a single token
/// let first_token = lexer.scan()?;
/// println!("{first_token:?}");
/// // Loop over all the rest of the tokens
/// for token in lexer {
/// # let token: Result<_,()> = Ok(token?);
/// match token {
/// Ok(token) => println!("{token:?}"),
/// Err(e) => eprintln!("{e:?}"),
/// }
/// }
/// # Ok(()) }
/// ```
#[derive(Clone, Debug)]
pub struct Lexer<'t> {
/// The source text
text: &'t str,
/// A peekable iterator over the source text
iter: Peekable<CharIndices<'t>>,
/// The end of the current token
head: usize,
/// The (line, col) end of the current token
head_loc: (u32, u32),
/// The start of the current token
tail: usize,
/// The (line, col) start of the current token
tail_loc: (u32, u32),
}
impl<'t> Lexer<'t> {
/// Creates a new [Lexer] over a [str]
pub fn new(text: &'t str) -> Self {
Self {
text,
iter: text.char_indices().peekable(),
head: 0,
head_loc: (1, 1),
tail: 0,
tail_loc: (1, 1),
}
}
/// Returns the current line
pub fn line(&self) -> u32 {
self.tail_loc.0
}
/// Returns the current column
pub fn col(&self) -> u32 {
self.tail_loc.1
}
/// Returns the current token's lexeme
fn lexeme(&mut self) -> &'t str {
&self.text[self.tail..self.head]
}
/// Peeks the next character without advancing the lexer
fn peek(&mut self) -> Option<char> {
self.iter.peek().map(|(_, c)| *c)
}
/// Advances the 'tail' (current position)
fn advance_tail(&mut self) {
let (idx, c) = self.iter.peek().copied().unwrap_or((self.text.len(), '\0'));
let (line, col) = &mut self.head_loc;
let diff = idx - self.head;
self.head = idx;
match c {
'\n' => {
*line += 1;
*col = 1;
}
_ => *col += diff as u32,
}
}
/// Takes the last-peeked character, or the next character if none peeked.
pub fn take(&mut self) -> Option<char> {
let (_, c) = self.iter.next()?;
self.advance_tail();
Some(c)
}
/// Takes the next char if it matches the `expected` char
pub fn next_if(&mut self, expected: char) -> Option<char> {
let (_, c) = self.iter.next_if(|&(_, c)| c == expected)?;
self.advance_tail();
Some(c)
}
/// Consumes the last-peeked character, advancing the tail
pub fn consume(&mut self) -> &mut Self {
self.iter.next();
self.advance_tail();
self
}
/// Produces an [Error] at the start of the current token
fn error(&self, reason: Reason) -> Error {
Error { reason, line: self.line(), col: self.col() }
}
/// Produces a token with the current [lexeme](Lexer::lexeme) as its data
fn produce(&mut self, kind: Kind) -> LResult<Token> {
let lexeme = self.lexeme().to_owned();
self.produce_with(kind, lexeme)
}
/// Produces a token with the provided `data`
fn produce_with(&mut self, kind: Kind, data: impl Into<TokenData>) -> LResult<Token> {
let loc = self.tail_loc;
self.tail_loc = self.head_loc;
self.tail = self.head;
Ok(Token::new(kind, data, loc.0, loc.1))
}
/// Produces a token with no `data`
fn produce_op(&mut self, kind: Kind) -> LResult<Token> {
self.produce_with(kind, ())
}
/// Consumes 0 or more whitespace
fn skip_whitespace(&mut self) -> &mut Self {
while self.peek().is_some_and(char::is_whitespace) {
let _ = self.consume();
}
self
}
/// Starts a new token
fn start_token(&mut self) -> &mut Self {
self.tail_loc = self.head_loc;
self.tail = self.head;
self
}
/// Scans through the text, searching for the next [Token]
pub fn scan(&mut self) -> LResult<Token> {
use TokenKind::*;
// !"#%&'()*+,-./:;<=>?@[\\]^`{|}~
let tok = match self
.skip_whitespace()
.start_token()
.peek()
.ok_or_else(|| self.error(Reason::EndOfFile))?
{
'!' => Bang,
'"' => return self.string(),
'#' => Hash,
'%' => Rem,
'&' => Amp,
'\'' => return self.character(),
'(' => LParen,
')' => RParen,
'*' => Star,
'+' => Plus,
',' => Comma,
'-' => Minus,
'.' => Dot,
'/' => Slash,
'0' => TokenKind::Literal,
'1'..='9' => return self.digits::<10>(),
':' => Colon,
';' => Semi,
'<' => Lt,
'=' => Eq,
'>' => Gt,
'?' => Question,
'@' => At,
'[' => LBrack,
'\\' => Backslash,
']' => RBrack,
'^' => Xor,
'`' => Grave,
'{' => LCurly,
'|' => Bar,
'}' => RCurly,
'~' => Tilde,
'_' => return self.identifier(),
c if is_xid_start(c) => return self.identifier(),
e => {
let err = Err(self.error(Reason::UnexpectedChar(e)));
let _ = self.consume();
err?
}
};
// Handle digraphs
let tok = match (tok, self.consume().peek()) {
(Literal, Some('b')) => return self.consume().digits::<2>(),
(Literal, Some('d')) => return self.consume().digits::<10>(),
(Literal, Some('o')) => return self.consume().digits::<8>(),
(Literal, Some('x')) => return self.consume().digits::<16>(),
(Literal, Some('~')) => return self.consume().digits::<36>(),
(Literal, _) => return self.digits::<10>(),
(Amp, Some('&')) => AmpAmp,
(Amp, Some('=')) => AmpEq,
(Bang, Some('!')) => BangBang,
(Bang, Some('=')) => BangEq,
(Bar, Some('|')) => BarBar,
(Bar, Some('=')) => BarEq,
(Colon, Some(':')) => ColonColon,
(Dot, Some('.')) => DotDot,
(Eq, Some('=')) => EqEq,
(Eq, Some('>')) => FatArrow,
(Gt, Some('=')) => GtEq,
(Gt, Some('>')) => GtGt,
(Hash, Some('!')) => HashBang,
(Lt, Some('=')) => LtEq,
(Lt, Some('<')) => LtLt,
(Minus, Some('=')) => MinusEq,
(Minus, Some('>')) => Arrow,
(Plus, Some('=')) => PlusEq,
(Rem, Some('=')) => RemEq,
(Slash, Some('*')) => return self.block_comment()?.produce(Kind::Comment),
(Slash, Some('/')) => return self.line_comment(),
(Slash, Some('=')) => SlashEq,
(Star, Some('=')) => StarEq,
(Xor, Some('=')) => XorEq,
(Xor, Some('^')) => XorXor,
_ => return self.produce_op(tok),
};
// Handle trigraphs
let tok = match (tok, self.consume().peek()) {
(HashBang, Some('/')) => return self.line_comment(),
(DotDot, Some('=')) => DotDotEq,
(GtGt, Some('=')) => GtGtEq,
(LtLt, Some('=')) => LtLtEq,
_ => return self.produce_op(tok),
};
self.consume().produce_op(tok)
}
}
/// Comments
impl Lexer<'_> {
/// Consumes until the next newline '\n', producing a [Comment](Kind::Comment)
fn line_comment(&mut self) -> LResult<Token> {
while self.consume().peek().is_some_and(|c| c != '\n') {}
self.produce(Kind::Comment)
}
/// Consumes nested block-comments. Does not produce by itself.
fn block_comment(&mut self) -> LResult<&mut Self> {
self.consume();
while let Some(c) = self.take() {
match (c, self.peek()) {
('/', Some('*')) => self.block_comment()?,
('*', Some('/')) => return Ok(self.consume()),
_ => continue,
};
}
Err(self.error(Reason::UnmatchedDelimiters('/')))
}
}
/// Identifiers
impl Lexer<'_> {
/// Produces an [Identifier](Kind::Identifier) or keyword
fn identifier(&mut self) -> LResult<Token> {
while self.consume().peek().is_some_and(is_xid_continue) {}
if let Ok(keyword) = Kind::from_str(self.lexeme()) {
self.produce_with(keyword, ())
} else {
self.produce(Kind::Identifier)
}
}
}
/// Integers
impl Lexer<'_> {
/// Produces a [Literal](Kind::Literal) with an integer or float value.
fn digits<const B: u32>(&mut self) -> LResult<Token> {
let mut value = 0;
while let Some(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) {
value = value * B as u128 + self.digit::<B>()? as u128;
}
// TODO: find a better way to handle floats in the tokenizer
match self.peek() {
Some('.') => {
// FIXME: hack: 0.. is not [0.0, '.']
if let Some('.') = self.clone().consume().take() {
return self.produce_with(Kind::Literal, value);
}
let mut float = format!("{value}.");
self.consume();
while let Some(true) = self.peek().as_ref().map(char::is_ascii_digit) {
float.push(self.iter.next().map(|(_, c)| c).unwrap_or_default());
}
let float = f64::from_str(&float).expect("must be parsable as float");
self.produce_with(Kind::Literal, float)
}
_ => self.produce_with(Kind::Literal, value),
}
}
/// Consumes a single digit of base [B](Lexer::digit)
fn digit<const B: u32>(&mut self) -> LResult<u32> {
let digit = self.take().ok_or_else(|| self.error(Reason::EndOfFile))?;
digit
.to_digit(B)
.ok_or_else(|| self.error(Reason::InvalidDigit(digit)))
}
}
/// Strings and characters
impl Lexer<'_> {
/// Produces a [Literal](Kind::Literal) with a pre-escaped [String]
pub fn string(&mut self) -> Result<Token, Error> {
let mut lexeme = String::new();
let mut depth = 0;
self.consume();
loop {
lexeme.push(match self.take() {
None => Err(self.error(Reason::UnmatchedDelimiters('"')))?,
Some('\\') => self.unescape()?,
Some('"') if depth == 0 => break,
Some(c @ '{') => {
depth += 1;
c
}
Some(c @ '}') => {
depth -= 1;
c
}
Some(c) => c,
})
}
lexeme.shrink_to_fit();
self.produce_with(Kind::Literal, lexeme)
}
/// Produces a [Literal](Kind::Literal) with a pre-escaped [char]
fn character(&mut self) -> Result<Token, Error> {
let c = match self.consume().take() {
Some('\\') => self.unescape()?,
Some(c) => c,
None => '\0',
};
if self.take().is_some_and(|c| c == '\'') {
self.produce_with(Kind::Literal, c)
} else {
Err(self.error(Reason::UnmatchedDelimiters('\'')))
}
}
/// Unescapes a single character
#[rustfmt::skip]
fn unescape(&mut self) -> LResult<char> {
Ok(match self.take().ok_or_else(|| self.error(Reason::EndOfFile))? {
' ' => '\u{a0}',
'0' => '\0',
'a' => '\x07',
'b' => '\x08',
'e' => '\x1b',
'f' => '\x0c',
'n' => '\n',
'r' => '\r',
't' => '\t',
'u' => self.unicode_escape()?,
'x' => self.hex_escape()?,
chr => chr,
})
}
/// Unescapes a single 2-digit hex escape
fn hex_escape(&mut self) -> LResult<char> {
let out = (self.digit::<16>()? << 4) + self.digit::<16>()?;
char::from_u32(out).ok_or_else(|| self.error(Reason::BadUnicode(out)))
}
/// Unescapes a single \u{} unicode escape
pub fn unicode_escape(&mut self) -> Result<char, Error> {
self.next_if('{')
.ok_or_else(|| self.error(Reason::InvalidEscape('u')))?;
let mut out = 0;
while let Some(c) = self.take() {
if c == '}' {
return char::from_u32(out).ok_or_else(|| self.error(Reason::BadUnicode(out)));
}
out = out * 16
+ c.to_digit(16)
.ok_or_else(|| self.error(Reason::InvalidDigit(c)))?;
}
Err(self.error(Reason::UnmatchedDelimiters('}')))
}
}
impl<'t> From<&Lexer<'t>> for Loc {
fn from(value: &Lexer<'t>) -> Self {
Loc(value.line(), value.col())
}
}
use error::{Error, LResult, Reason};
pub mod error {
//! [Error] type for the [Lexer](super::Lexer)
use std::fmt::Display;
/// Result type with [Err] = [Error]
pub type LResult<T> = Result<T, Error>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub reason: Reason,
pub line: u32,
pub col: u32,
}
/// The reason for the [Error]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Reason {
/// Found an opening delimiter of type [char], but not the expected closing delimiter
UnmatchedDelimiters(char),
/// Found a character that doesn't belong to any [TokenKind](cl_token::TokenKind)
UnexpectedChar(char),
/// Found a character that's not valid in an escape sequence while looking for an escape
/// sequence
UnknownEscape(char),
/// Escape sequence contains invalid hexadecimal digit or unmatched braces
InvalidEscape(char),
/// Character is not a valid digit in the requested base
InvalidDigit(char),
/// Unicode escape does not map to a valid unicode code-point
BadUnicode(u32),
/// Reached end of input
EndOfFile,
}
impl Error {
/// Returns the [Reason] for this error
pub fn reason(&self) -> &Reason {
&self.reason
}
/// Returns the (line, col) where the error happened
pub fn location(&self) -> (u32, u32) {
(self.line, self.col)
}
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}: {}", self.line, self.col, self.reason)
}
}
impl Display for Reason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Reason::UnmatchedDelimiters(c) => write! {f, "Unmatched `{c:?}` in input"},
Reason::UnexpectedChar(c) => write!(f, "Character `{c:?}` not expected"),
Reason::UnknownEscape(c) => write!(f, "`\\{c}` is not a known escape sequence"),
Reason::InvalidEscape(c) => write!(f, "Escape sequence `\\{c}`... is malformed"),
Reason::InvalidDigit(c) => write!(f, "`{c:?}` is not a valid digit"),
Reason::BadUnicode(c) => write!(f, "`\\u{{{c:x}}}` is not valid unicode"),
Reason::EndOfFile => write!(f, "Reached end of input"),
}
}
}
}

View File

@@ -0,0 +1,171 @@
use crate::Lexer;
use cl_token::*;
macro test_lexer_output_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
#[test]
fn $f() {$(
assert_eq!(
Lexer::new($test)
.into_iter()
.map(|t| t.unwrap().ty())
.collect::<Vec<_>>(),
dbg!($expect)
);
)*}
)*}
macro test_lexer_data_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
#[test]
fn $f() {$(
assert_eq!(
Lexer::new($test)
.into_iter()
.map(|t| t.unwrap().into_data())
.collect::<Vec<_>>(),
dbg!($expect)
);
)*}
)*}
/// Convert an `[ expr, ... ]` into a `[ *, ... ]`
macro td ($($id:expr),*) {
[$($id.into()),*]
}
mod ident {
use super::*;
macro ident ($($id:literal),*) {
[$(TokenData::String($id.into())),*]
}
test_lexer_data_type! {
underscore { "_ _" => ident!["_", "_"] }
unicode { "_ε ε_" => ident!["", "ε_"] }
many_underscore { "____________________________________" =>
ident!["____________________________________"] }
}
}
mod keyword {
use super::*;
macro kw($($k:ident),*) {
[ $(TokenKind::$k,)* ]
}
test_lexer_output_type! {
kw_break { "break break" => kw![Break, Break] }
kw_continue { "continue continue" => kw![Continue, Continue] }
kw_else { "else else" => kw![Else, Else] }
kw_false { "false false" => kw![False, False] }
kw_for { "for for" => kw![For, For] }
kw_fn { "fn fn" => kw![Fn, Fn] }
kw_if { "if if" => kw![If, If] }
kw_in { "in in" => kw![In, In] }
kw_let { "let let" => kw![Let, Let] }
kw_return { "return return" => kw![Return, Return] }
kw_true { "true true" => kw![True, True] }
kw_while { "while while" => kw![While, While] }
keywords { "break continue else false for fn if in let return true while" =>
kw![Break, Continue, Else, False, For, Fn, If, In, Let, Return, True, While] }
}
}
mod integer {
use super::*;
test_lexer_data_type! {
hex {
"0x0 0x1 0x15 0x2100 0x8000" =>
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
}
dec {
"0d0 0d1 0d21 0d8448 0d32768" =>
td![0, 0x1, 0x15, 0x2100, 0x8000]
}
oct {
"0o0 0o1 0o25 0o20400 0o100000" =>
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
}
bin {
"0b0 0b1 0b10101 0b10000100000000 0b1000000000000000" =>
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
}
baseless {
"0 1 21 8448 32768" =>
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
}
}
}
mod string {
use super::*;
test_lexer_data_type! {
empty_string {
"\"\"" =>
td![String::from("")]
}
unicode_string {
"\"I 💙 🦈!\"" =>
td![String::from("I 💙 🦈!")]
}
escape_string {
" \"This is a shark: \\u{1f988}\" " =>
td![String::from("This is a shark: 🦈")]
}
}
}
mod punct {
macro op($op:ident) {
TokenKind::$op
}
use super::*;
test_lexer_output_type! {
l_curly { "{ {" => [ op!(LCurly), op!(LCurly) ] }
r_curly { "} }" => [ op!(RCurly), op!(RCurly) ] }
l_brack { "[ [" => [ op!(LBrack), op!(LBrack) ] }
r_brack { "] ]" => [ op!(RBrack), op!(RBrack) ] }
l_paren { "( (" => [ op!(LParen), op!(LParen) ] }
r_paren { ") )" => [ op!(RParen), op!(RParen) ] }
amp { "& &" => [ op!(Amp), op!(Amp) ] }
amp_amp { "&& &&" => [ op!(AmpAmp), op!(AmpAmp) ] }
amp_eq { "&= &=" => [ op!(AmpEq), op!(AmpEq) ] }
arrow { "-> ->" => [ op!(Arrow), op!(Arrow)] }
at { "@ @" => [ op!(At), op!(At)] }
backslash { "\\ \\" => [ op!(Backslash), op!(Backslash)] }
bang { "! !" => [ op!(Bang), op!(Bang)] }
bangbang { "!! !!" => [ op!(BangBang), op!(BangBang)] }
bangeq { "!= !=" => [ op!(BangEq), op!(BangEq)] }
bar { "| |" => [ op!(Bar), op!(Bar)] }
barbar { "|| ||" => [ op!(BarBar), op!(BarBar)] }
bareq { "|= |=" => [ op!(BarEq), op!(BarEq)] }
colon { ": :" => [ op!(Colon), op!(Colon)] }
comma { ", ," => [ op!(Comma), op!(Comma)] }
dot { ". ." => [ op!(Dot), op!(Dot)] }
dotdot { ".. .." => [ op!(DotDot), op!(DotDot)] }
dotdoteq { "..= ..=" => [ op!(DotDotEq), op!(DotDotEq)] }
eq { "= =" => [ op!(Eq), op!(Eq)] }
eqeq { "== ==" => [ op!(EqEq), op!(EqEq)] }
fatarrow { "=> =>" => [ op!(FatArrow), op!(FatArrow)] }
grave { "` `" => [ op!(Grave), op!(Grave)] }
gt { "> >" => [ op!(Gt), op!(Gt)] }
gteq { ">= >=" => [ op!(GtEq), op!(GtEq)] }
gtgt { ">> >>" => [ op!(GtGt), op!(GtGt)] }
gtgteq { ">>= >>=" => [ op!(GtGtEq), op!(GtGtEq)] }
hash { "# #" => [ op!(Hash), op!(Hash)] }
lt { "< <" => [ op!(Lt), op!(Lt)] }
lteq { "<= <=" => [ op!(LtEq), op!(LtEq)] }
ltlt { "<< <<" => [ op!(LtLt), op!(LtLt)] }
ltlteq { "<<= <<=" => [ op!(LtLtEq), op!(LtLtEq)] }
minus { "- -" => [ op!(Minus), op!(Minus)] }
minuseq { "-= -=" => [ op!(MinusEq), op!(MinusEq)] }
plus { "+ +" => [ op!(Plus), op!(Plus)] }
pluseq { "+= +=" => [ op!(PlusEq), op!(PlusEq)] }
question { "? ?" => [ op!(Question), op!(Question)] }
rem { "% %" => [ op!(Rem), op!(Rem)] }
remeq { "%= %=" => [ op!(RemEq), op!(RemEq)] }
semi { "; ;" => [ op!(Semi), op!(Semi)] }
slash { "/ /" => [ op!(Slash), op!(Slash)] }
slasheq { "/= /=" => [ op!(SlashEq), op!(SlashEq)] }
star { "* *" => [ op!(Star), op!(Star)] }
stareq { "*= *=" => [ op!(StarEq), op!(StarEq)] }
tilde { "~ ~" => [ op!(Tilde), op!(Tilde)] }
xor { "^ ^" => [ op!(Xor), op!(Xor)] }
xoreq { "^= ^=" => [ op!(XorEq), op!(XorEq)] }
xorxor { "^^ ^^" => [ op!(XorXor), op!(XorXor)] }
}
}

View File

@@ -0,0 +1,14 @@
[package]
name = "cl-parser"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
cl-ast = { path = "../cl-ast" }
cl-lexer = { path = "../cl-lexer" }
cl-token = { path = "../cl-token" }
cl-structures = { path = "../cl-structures" }

View File

@@ -0,0 +1,252 @@
use super::*;
use cl_ast::{Expr, Sym};
use cl_lexer::error::{Error as LexError, Reason};
use std::fmt::Display;
pub type PResult<T> = Result<T, Error>;
/// Contains information about [Parser] errors
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub in_file: Sym,
pub reason: ErrorKind,
pub while_parsing: Parsing,
pub loc: Loc,
}
impl std::error::Error for Error {}
/// Represents the reason for parse failure
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ErrorKind {
Lexical(LexError),
EndOfInput,
UnmatchedParentheses,
UnmatchedCurlyBraces,
UnmatchedSquareBrackets,
Unexpected(TokenKind),
ExpectedToken {
want: TokenKind,
got: TokenKind,
},
ExpectedParsing {
want: Parsing,
},
InvalidPattern(Box<Expr>),
/// Indicates unfinished code
Todo(&'static str),
}
impl From<LexError> for ErrorKind {
fn from(value: LexError) -> Self {
match value.reason() {
Reason::EndOfFile => Self::EndOfInput,
_ => Self::Lexical(value),
}
}
}
/// Compactly represents the stage of parsing an [Error] originated in
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Parsing {
Mutability,
Visibility,
Identifier,
Literal,
File,
Attrs,
Meta,
MetaKind,
Item,
ItemKind,
Generics,
Alias,
Const,
Static,
Module,
ModuleKind,
Function,
Param,
Struct,
StructKind,
StructMember,
Enum,
EnumKind,
Variant,
VariantKind,
Impl,
ImplKind,
Use,
UseTree,
Ty,
TyKind,
TySlice,
TyArray,
TyTuple,
TyRef,
TyFn,
Path,
PathPart,
Stmt,
StmtKind,
Let,
Expr,
ExprKind,
Closure,
Assign,
AssignKind,
Binary,
BinaryKind,
Unary,
UnaryKind,
Cast,
Index,
Structor,
Fielder,
Call,
Member,
Array,
ArrayRep,
AddrOf,
Block,
Group,
Tuple,
Loop,
While,
If,
For,
Else,
Break,
Return,
Continue,
Pattern,
Match,
MatchArm,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { in_file, reason, while_parsing, loc } = self;
match reason {
// TODO entries are debug-printed
ErrorKind::Todo(_) => write!(f, "{in_file}:{loc} {reason} {while_parsing:?}"),
// lexical errors print their own higher-resolution loc info
ErrorKind::Lexical(e) => write!(f, "{e} (while parsing {while_parsing})"),
_ => {
if !in_file.is_empty() {
write!(f, "{in_file}:")?
}
write!(f, "{loc}: {reason} while parsing {while_parsing}")
}
}
}
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::Lexical(e) => e.fmt(f),
ErrorKind::EndOfInput => write!(f, "End of input"),
ErrorKind::UnmatchedParentheses => write!(f, "Unmatched parentheses"),
ErrorKind::UnmatchedCurlyBraces => write!(f, "Unmatched curly braces"),
ErrorKind::UnmatchedSquareBrackets => write!(f, "Unmatched square brackets"),
ErrorKind::Unexpected(t) => write!(f, "Encountered unexpected token `{t}`"),
ErrorKind::ExpectedToken { want: e, got: g } => write!(f, "Expected `{e}`, got `{g}`"),
ErrorKind::ExpectedParsing { want } => write!(f, "Expected {want}"),
ErrorKind::InvalidPattern(got) => write!(f, "Got invalid `{got}`"),
ErrorKind::Todo(unfinished) => write!(f, "TODO: {unfinished}"),
}
}
}
impl Display for Parsing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Parsing::Visibility => "a visibility qualifier",
Parsing::Mutability => "a mutability qualifier",
Parsing::Identifier => "an identifier",
Parsing::Literal => "a literal",
Parsing::File => "a file",
Parsing::Attrs => "an attribute-set",
Parsing::Meta => "an attribute",
Parsing::MetaKind => "an attribute's arguments",
Parsing::Item => "an item",
Parsing::ItemKind => "an item",
Parsing::Generics => "a list of type arguments",
Parsing::Alias => "a type alias",
Parsing::Const => "a const item",
Parsing::Static => "a static variable",
Parsing::Module => "a module",
Parsing::ModuleKind => "a module",
Parsing::Function => "a function",
Parsing::Param => "a function parameter",
Parsing::Struct => "a struct",
Parsing::StructKind => "a struct",
Parsing::StructMember => "a struct member",
Parsing::Enum => "an enum",
Parsing::EnumKind => "an enum",
Parsing::Variant => "an enum variant",
Parsing::VariantKind => "an enum variant",
Parsing::Impl => "an impl block",
Parsing::ImplKind => "the target of an impl block",
Parsing::Use => "a use item",
Parsing::UseTree => "a use-tree",
Parsing::Ty => "a type",
Parsing::TyKind => "a type",
Parsing::TySlice => "a slice type",
Parsing::TyArray => "an array type",
Parsing::TyTuple => "a tuple of types",
Parsing::TyRef => "a reference type",
Parsing::TyFn => "a function pointer type",
Parsing::Path => "a path",
Parsing::PathPart => "a path component",
Parsing::Stmt => "a statement",
Parsing::StmtKind => "a statement",
Parsing::Let => "a local variable declaration",
Parsing::Expr => "an expression",
Parsing::ExprKind => "an expression",
Parsing::Closure => "an anonymous function",
Parsing::Assign => "an assignment",
Parsing::AssignKind => "an assignment operator",
Parsing::Binary => "a binary expression",
Parsing::BinaryKind => "a binary operator",
Parsing::Unary => "a unary expression",
Parsing::UnaryKind => "a unary operator",
Parsing::Cast => "an `as`-casting expression",
Parsing::Index => "an indexing expression",
Parsing::Structor => "a struct constructor expression",
Parsing::Fielder => "a struct field expression",
Parsing::Call => "a call expression",
Parsing::Member => "a member access expression",
Parsing::Array => "an array",
Parsing::ArrayRep => "an array of form [k;N]",
Parsing::AddrOf => "a borrow op",
Parsing::Block => "a block",
Parsing::Group => "a grouped expression",
Parsing::Tuple => "a tuple",
Parsing::Loop => "an unconditional loop expression",
Parsing::While => "a while expression",
Parsing::If => "an if expression",
Parsing::For => "a for expression",
Parsing::Else => "an else block",
Parsing::Break => "a break expression",
Parsing::Return => "a return expression",
Parsing::Continue => "a continue expression",
Parsing::Pattern => "a pattern",
Parsing::Match => "a match expression",
Parsing::MatchArm => "a match arm",
}
.fmt(f)
}
}

View File

@@ -0,0 +1,118 @@
//! The [ModuleInliner] reads files described in the module structure of the
use crate::Parser;
use cl_ast::{ast_visitor::Fold, *};
use cl_lexer::Lexer;
use std::path::{Path, PathBuf};
pub type IoErrs = Vec<(PathBuf, std::io::Error)>;
pub type ParseErrs = Vec<(PathBuf, crate::error::Error)>;
pub struct ModuleInliner {
path: PathBuf,
io_errs: IoErrs,
parse_errs: ParseErrs,
}
impl ModuleInliner {
/// Creates a new [ModuleInliner]
pub fn new(root: impl AsRef<Path>) -> Self {
Self {
path: root.as_ref().to_path_buf(),
io_errs: Default::default(),
parse_errs: Default::default(),
}
}
/// Returns true when the [ModuleInliner] has errors to report
pub fn has_errors(&self) -> bool {
!(self.io_errs.is_empty() && self.parse_errs.is_empty())
}
/// Returns the [IO Errors](IoErrs) and [parse Errors](ParseErrs)
pub fn into_errs(self) -> Option<(IoErrs, ParseErrs)> {
self.has_errors().then_some((self.io_errs, self.parse_errs))
}
/// Traverses a [File], attempting to inline all submodules.
///
/// This is a simple wrapper around [ModuleInliner::fold_file()] and
/// [ModuleInliner::into_errs()]
pub fn inline(mut self, file: File) -> Result<File, (File, IoErrs, ParseErrs)> {
let file = self.fold_file(file);
match self.into_errs() {
Some((io, parse)) => Err((file, io, parse)),
None => Ok(file),
}
}
/// Records an [I/O error](std::io::Error) for later
fn handle_io_error(&mut self, error: std::io::Error) -> Option<File> {
self.io_errs.push((self.path.clone(), error));
None
}
/// Records a [parse error](crate::error::Error) for later
fn handle_parse_error(&mut self, error: crate::error::Error) -> Option<File> {
self.parse_errs.push((self.path.clone(), error));
None
}
}
impl Fold for ModuleInliner {
/// Traverses down the module tree, entering ever nested directories
fn fold_module(&mut self, m: Module) -> Module {
let Module { name, file } = m;
self.path.push(&*name); // cd ./name
let file = self.fold_module_kind(file);
self.path.pop(); // cd ..
Module { name, file }
}
}
impl ModuleInliner {
/// Attempts to read and parse a file for every module in the tree
fn fold_module_kind(&mut self, m: Option<File>) -> Option<File> {
use std::borrow::Cow;
if let Some(f) = m {
return Some(self.fold_file(f));
}
// cd path/mod.cl
self.path.set_extension("cl");
let mut used_path: Cow<Path> = Cow::Borrowed(&self.path);
let file = match std::fs::read_to_string(&self.path) {
Err(error) => {
let Some(basename) = self.path.file_name() else {
return self.handle_io_error(error);
};
used_path = Cow::Owned(
self.path
.parent()
.and_then(Path::parent)
.map(|path| path.join(basename))
.unwrap_or_default(),
);
match std::fs::read_to_string(&used_path) {
Err(error) => return self.handle_io_error(error),
Ok(file) => file,
}
}
Ok(file) => file,
};
match Parser::new(used_path.display().to_string(), Lexer::new(&file)).parse() {
Err(e) => self.handle_parse_error(e),
Ok(file) => {
self.path.set_extension("");
// The newly loaded module may need further inlining
Some(self.fold_file(file))
}
}
}
}

View File

@@ -0,0 +1,18 @@
//! Parses [tokens](cl_token::token) into an [AST](cl_ast)
//!
//! For the full grammar, see [grammar.ebnf][1]
//!
//! [1]: https://git.soft.fish/j/Conlang/src/branch/main/grammar.ebnf
#![warn(clippy::all)]
#![feature(decl_macro)]
pub use parser::Parser;
use cl_structures::span::*;
use cl_token::*;
pub mod error;
pub mod parser;
pub mod inliner;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,425 @@
//! Parses an [ExprKind] using a modified pratt parser
//!
//! See also: [Expr::parse], [ExprKind::parse]
//!
//! Implementer's note: [ExprKind::parse] is the public API for parsing [ExprKind]s.
//! Do not call it from within this function.
use super::{Parse, *};
/// Parses an [ExprKind]
pub fn expr(p: &mut Parser, power: u8) -> PResult<Expr> {
let parsing = Parsing::ExprKind;
let start = p.loc();
// Prefix expressions
let mut head = Expr {
kind: match p.peek_kind(Parsing::Unary)? {
literal_like!() => Literal::parse(p)?.into(),
path_like!() => exprkind_pathlike(p)?,
TokenKind::Amp | TokenKind::AmpAmp => AddrOf::parse(p)?.into(),
TokenKind::Bar | TokenKind::BarBar => Closure::parse(p)?.into(),
TokenKind::Grave => Quote::parse(p)?.into(),
TokenKind::LCurly => Block::parse(p)?.into(),
TokenKind::LBrack => exprkind_arraylike(p)?,
TokenKind::LParen => exprkind_tuplelike(p)?,
TokenKind::Let => Let::parse(p)?.into(),
TokenKind::Match => Match::parse(p)?.into(),
TokenKind::While => ExprKind::While(While::parse(p)?),
TokenKind::If => ExprKind::If(If::parse(p)?),
TokenKind::For => ExprKind::For(For::parse(p)?),
TokenKind::Break => ExprKind::Break(Break::parse(p)?),
TokenKind::Return => ExprKind::Return(Return::parse(p)?),
TokenKind::Continue => {
p.consume_peeked();
ExprKind::Continue
}
op => {
let (kind, prec) =
from_prefix(op).ok_or_else(|| p.error(Unexpected(op), parsing))?;
let ((), after) = prec.prefix().expect("should have a precedence");
p.consume_peeked();
Unary { kind, tail: expr(p, after)?.into() }.into()
}
},
span: Span(start, p.loc()),
};
fn from_postfix(op: TokenKind) -> Option<Precedence> {
Some(match op {
TokenKind::LBrack => Precedence::Index,
TokenKind::LParen => Precedence::Call,
TokenKind::LCurly => Precedence::Structor,
TokenKind::Dot => Precedence::Member,
TokenKind::As => Precedence::Cast,
_ => None?,
})
}
while let Ok(op) = p.peek_kind(parsing) {
// Postfix expressions
if let Some((before, ())) = from_postfix(op).and_then(Precedence::postfix) {
if before < power {
break;
}
head = Expr {
kind: match op {
TokenKind::LBrack => {
p.consume_peeked();
let indices =
sep(Expr::parse, TokenKind::Comma, TokenKind::RBrack, parsing)(p)?;
p.match_type(TokenKind::RBrack, parsing)?;
ExprKind::Index(Index { head: head.into(), indices })
}
TokenKind::LParen => {
p.consume_peeked();
let exprs =
sep(Expr::parse, TokenKind::Comma, TokenKind::RParen, parsing)(p)?;
p.match_type(TokenKind::RParen, parsing)?;
Binary {
kind: BinaryKind::Call,
parts: (
head,
Expr { kind: Tuple { exprs }.into(), span: Span(start, p.loc()) },
)
.into(),
}
.into()
}
TokenKind::LCurly => match head.kind {
ExprKind::Path(path) => ExprKind::Structor(structor_body(p, path)?),
_ => break,
},
TokenKind::Dot => {
p.consume_peeked();
let kind = MemberKind::parse(p)?;
Member { head: Box::new(head), kind }.into()
}
TokenKind::As => {
p.consume_peeked();
let ty = Ty::parse(p)?;
Cast { head: head.into(), ty }.into()
}
_ => Err(p.error(Unexpected(op), parsing))?,
},
span: Span(start, p.loc()),
};
continue;
}
// infix expressions
if let Some((kind, prec)) = from_infix(op) {
let (before, after) = prec.infix().expect("should have a precedence");
if before < power {
break;
}
p.consume_peeked();
let tail = expr(p, after)?;
head = Expr {
kind: Binary { kind, parts: (head, tail).into() }.into(),
span: Span(start, p.loc()),
};
continue;
}
if let Some((kind, prec)) = from_modify(op) {
let (before, after) = prec.infix().expect("should have a precedence");
if before < power {
break;
}
p.consume_peeked();
let tail = expr(p, after)?;
head = Expr {
kind: Modify { kind, parts: (head, tail).into() }.into(),
span: Span(start, p.loc()),
};
continue;
}
if let TokenKind::Eq = op {
let (before, after) = Precedence::Assign
.infix()
.expect("should have a precedence");
if before < power {
break;
}
p.consume_peeked();
let tail = expr(p, after)?;
head = Expr {
kind: Assign { parts: (head, tail).into() }.into(),
span: Span(start, p.loc()),
};
continue;
}
if let TokenKind::As = op {
let before = Precedence::Cast.level();
if before < power {
break;
}
p.consume_peeked();
let ty = Ty::parse(p)?;
head = Expr { kind: Cast { head: head.into(), ty }.into(), span: Span(start, p.loc()) };
continue;
}
break;
}
Ok(head)
}
/// [Array] = '[' ([Expr] ',')* [Expr]? ']'
///
/// Array and ArrayRef are ambiguous until the second token,
/// so they can't be independent subexpressions
fn exprkind_arraylike(p: &mut Parser) -> PResult<ExprKind> {
const P: Parsing = Parsing::Array;
const START: TokenKind = TokenKind::LBrack;
const END: TokenKind = TokenKind::RBrack;
p.match_type(START, P)?;
let out = match p.peek_kind(P)? {
END => Array { values: vec![] }.into(),
_ => exprkind_array_rep(p)?,
};
p.match_type(END, P)?;
Ok(out)
}
/// [ArrayRep] = `[` [Expr] `;` [Expr] `]`
fn exprkind_array_rep(p: &mut Parser) -> PResult<ExprKind> {
const P: Parsing = Parsing::Array;
const END: TokenKind = TokenKind::RBrack;
let first = Expr::parse(p)?;
Ok(match p.peek_kind(P)? {
TokenKind::Semi => ArrayRep {
value: first.into(),
repeat: {
p.consume_peeked();
p.parse()?
},
}
.into(),
TokenKind::RBrack => Array { values: vec![first] }.into(),
TokenKind::Comma => Array {
values: {
p.consume_peeked();
let mut out = vec![first];
out.extend(sep(Expr::parse, TokenKind::Comma, END, P)(p)?);
out
},
}
.into(),
ty => Err(p.error(Unexpected(ty), P))?,
})
}
/// [Group] = `(`([Empty](ExprKind::Empty)|[Expr]|[Tuple])`)`
///
/// [ExprKind::Empty] and [Group] are special cases of [Tuple]
fn exprkind_tuplelike(p: &mut Parser) -> PResult<ExprKind> {
p.match_type(TokenKind::LParen, Parsing::Group)?;
let out = match p.peek_kind(Parsing::Group)? {
TokenKind::RParen => Ok(ExprKind::Empty),
_ => exprkind_group(p),
};
p.match_type(TokenKind::RParen, Parsing::Group)?;
out
}
/// [Group] = `(`([Empty](ExprKind::Empty)|[Expr]|[Tuple])`)`
fn exprkind_group(p: &mut Parser) -> PResult<ExprKind> {
let first = Expr::parse(p)?;
match p.peek_kind(Parsing::Group)? {
TokenKind::Comma => {
let mut exprs = vec![first];
p.consume_peeked();
while TokenKind::RParen != p.peek_kind(Parsing::Tuple)? {
exprs.push(Expr::parse(p)?);
match p.peek_kind(Parsing::Tuple)? {
TokenKind::Comma => p.consume_peeked(),
_ => break,
};
}
Ok(Tuple { exprs }.into())
}
_ => Ok(Group { expr: first.into() }.into()),
}
}
/// Parses an expression beginning with a [Path] (i.e. [Path] or [Structor])
fn exprkind_pathlike(p: &mut Parser) -> PResult<ExprKind> {
Path::parse(p).map(Into::into)
}
/// [Structor]Body = `{` ([Fielder] `,`)* [Fielder]? `}`
fn structor_body(p: &mut Parser, to: Path) -> PResult<Structor> {
let init = delim(
sep(
Fielder::parse,
TokenKind::Comma,
CURLIES.1,
Parsing::Structor,
),
CURLIES,
Parsing::Structor,
)(p)?;
Ok(Structor { to, init })
}
/// Precedence provides a total ordering among operators
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Precedence {
Assign,
Structor, // A structor is never a valid conditional
Condition, // Anything that syntactically needs a block following it
Logic,
Compare,
Range,
Bitwise,
Shift,
Factor,
Term,
Unary,
Index,
Cast,
Member, // left-associative
Call,
Deref,
}
impl Precedence {
#[inline]
pub const fn level(self) -> u8 {
(self as u8) << 1
}
pub fn prefix(self) -> Option<((), u8)> {
match self {
Self::Assign => Some(((), self.level())),
Self::Unary => Some(((), self.level())),
Self::Deref => Some(((), self.level())),
_ => None,
}
}
pub fn infix(self) -> Option<(u8, u8)> {
let level = self.level();
match self {
Self::Unary => None,
Self::Assign => Some((level + 1, level)),
_ => Some((level, level + 1)),
}
}
pub fn postfix(self) -> Option<(u8, ())> {
match self {
Self::Structor | Self::Index | Self::Call | Self::Member | Self::Cast => {
Some((self.level(), ()))
}
_ => None,
}
}
}
impl From<ModifyKind> for Precedence {
fn from(_value: ModifyKind) -> Self {
Precedence::Assign
}
}
impl From<BinaryKind> for Precedence {
fn from(value: BinaryKind) -> Self {
use BinaryKind as Op;
match value {
Op::Call => Precedence::Call,
Op::Mul | Op::Div | Op::Rem => Precedence::Term,
Op::Add | Op::Sub => Precedence::Factor,
Op::Shl | Op::Shr => Precedence::Shift,
Op::BitAnd | Op::BitOr | Op::BitXor => Precedence::Bitwise,
Op::LogAnd | Op::LogOr | Op::LogXor => Precedence::Logic,
Op::RangeExc | Op::RangeInc => Precedence::Range,
Op::Lt | Op::LtEq | Op::Equal | Op::NotEq | Op::GtEq | Op::Gt => Precedence::Compare,
}
}
}
impl From<UnaryKind> for Precedence {
fn from(value: UnaryKind) -> Self {
use UnaryKind as Op;
match value {
Op::Loop => Precedence::Assign,
Op::Deref => Precedence::Deref,
_ => Precedence::Unary,
}
}
}
/// Creates helper functions for turning TokenKinds into AST operators
macro operator($($name:ident ($takes:ident => $returns:ident) {$($t:ident => $p:ident),*$(,)?};)*) {$(
pub fn $name (value: $takes) -> Option<($returns, Precedence)> {
match value {
$($takes::$t => Some(($returns::$p, Precedence::from($returns::$p))),)*
_ => None?,
}
})*
}
operator! {
from_prefix (TokenKind => UnaryKind) {
Loop => Loop,
Star => Deref,
Minus => Neg,
Bang => Not,
DotDot => RangeExc,
DotDotEq => RangeInc,
At => At,
Tilde => Tilde,
};
from_modify(TokenKind => ModifyKind) {
AmpEq => And,
BarEq => Or,
XorEq => Xor,
LtLtEq => Shl,
GtGtEq => Shr,
PlusEq => Add,
MinusEq => Sub,
StarEq => Mul,
SlashEq => Div,
RemEq => Rem,
};
from_infix (TokenKind => BinaryKind) {
Lt => Lt,
LtEq => LtEq,
EqEq => Equal,
BangEq => NotEq,
GtEq => GtEq,
Gt => Gt,
DotDot => RangeExc,
DotDotEq => RangeInc,
AmpAmp => LogAnd,
BarBar => LogOr,
XorXor => LogXor,
Amp => BitAnd,
Bar => BitOr,
Xor => BitXor,
LtLt => Shl,
GtGt => Shr,
Plus => Add,
Minus => Sub,
Star => Mul,
Slash => Div,
Rem => Rem,
};
}

View File

@@ -0,0 +1,22 @@
[package]
name = "cl-repl"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cl-ast = { path = "../cl-ast" }
cl-lexer = { path = "../cl-lexer" }
cl-token = { path = "../cl-token" }
cl-parser = { path = "../cl-parser" }
cl-typeck = { path = "../cl-typeck" }
cl-interpret = { path = "../cl-interpret" }
cl-structures = { path = "../cl-structures" }
cl-arena = { version = "0", registry = "soft-fish" }
repline = { version = "*", registry = "soft-fish" }
argwerk = "0.20.4"

View File

@@ -1,9 +1,10 @@
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
#![allow(unused_imports)]
use conlang::lexer::Lexer;
use cl_lexer::Lexer;
use cl_token::Token;
use std::{
error::Error,
io::{stdin, IsTerminal, Read},
io::{IsTerminal, Read, stdin},
path::{Path, PathBuf},
};
@@ -57,7 +58,7 @@ fn lex_tokens(file: &str, path: Option<&Path>) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn print_token(t: conlang::token::Token) {
fn print_token(t: Token) {
println!(
"{:02}:{:02}: {:#19} │{}│",
t.line(),

View File

@@ -0,0 +1,951 @@
//! Pretty prints a conlang AST in yaml
use cl_ast::{File, Stmt};
use cl_lexer::Lexer;
use cl_parser::Parser;
use repline::{Repline, error::Error as RlError};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
if let Some(path) = std::env::args().nth(1) {
let f = std::fs::read_to_string(&path).expect("Path must be valid.");
let mut parser = Parser::new(path, Lexer::new(&f));
let code: File = match parser.parse() {
Ok(f) => f,
Err(e) => {
eprintln!("{e}");
return Ok(());
}
};
CLangifier::new().p(&code);
println!();
return Ok(());
}
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
loop {
let mut line = match rl.read() {
Err(RlError::CtrlC(_)) => break,
Err(RlError::CtrlD(line)) => {
rl.deny();
line
}
Ok(line) => line,
Err(e) => Err(e)?,
};
if !line.ends_with(';') {
line.push(';');
}
let mut parser = Parser::new("stdin", Lexer::new(&line));
let code = match parser.parse::<Stmt>() {
Ok(code) => {
rl.accept();
code
}
Err(e) => {
print!("\x1b[40G\x1bJ\x1b[91m{e}\x1b[0m");
continue;
}
};
print!("\x1b[G\x1b[J");
CLangifier::new().p(&code);
println!();
}
Ok(())
}
pub use clangifier::CLangifier;
pub mod clangifier {
use crate::clangify::CLangify;
use std::{
fmt::Display,
io::Write,
ops::{Add, Deref, DerefMut},
};
#[derive(Debug, Default)]
pub struct CLangifier {
depth: usize,
}
impl CLangifier {
pub fn new() -> Self {
Self::default()
}
pub fn indent(&mut self) -> Section<'_> {
Section::new(self)
}
/// Prints a [Yamlify] value
#[inline]
pub fn p<T: CLangify + ?Sized>(&mut self, yaml: &T) -> &mut Self {
yaml.print(self);
self
}
fn increase(&mut self) {
self.depth += 1;
}
fn decrease(&mut self) {
self.depth -= 1;
}
fn print_indentation(&self, writer: &mut impl Write) {
for _ in 0..self.depth {
let _ = write!(writer, " ");
}
}
pub fn endl(&mut self) -> &mut Self {
self.p("\n")
.print_indentation(&mut std::io::stdout().lock());
self
}
/// Prints a section header and increases indentation
pub fn nest(&mut self, name: impl Display) -> Section<'_> {
print!("{name}");
self.indent()
}
}
impl<C: CLangify + ?Sized> Add<&C> for &mut CLangifier {
type Output = Self;
fn add(self, rhs: &C) -> Self::Output {
self.p(rhs)
}
}
/// Tracks the start and end of an indented block (a "section")
pub struct Section<'y> {
yamler: &'y mut CLangifier,
}
impl<'y> Section<'y> {
pub fn new(yamler: &'y mut CLangifier) -> Self {
yamler.increase();
Self { yamler }
}
}
impl Deref for Section<'_> {
type Target = CLangifier;
fn deref(&self) -> &Self::Target {
self.yamler
}
}
impl DerefMut for Section<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.yamler
}
}
impl Drop for Section<'_> {
fn drop(&mut self) {
let Self { yamler } = self;
yamler.decrease();
}
}
}
pub mod clangify {
use core::panic;
use std::iter;
use super::clangifier::CLangifier;
use cl_ast::*;
pub trait CLangify {
fn print(&self, y: &mut CLangifier);
}
impl CLangify for File {
fn print(&self, mut y: &mut CLangifier) {
let File { name, items } = self;
// TODO: turn name into include guard
y = (y + "// Generated from " + name).endl();
for (idx, item) in items.iter().enumerate() {
if idx > 0 {
y.endl().endl();
}
y.p(item);
}
y.endl();
}
}
impl CLangify for Visibility {
fn print(&self, _y: &mut CLangifier) {}
}
impl CLangify for Mutability {
fn print(&self, y: &mut CLangifier) {
if let Mutability::Not = self {
y.p("const ");
}
}
}
impl CLangify for Attrs {
fn print(&self, y: &mut CLangifier) {
let Self { meta } = self;
y.nest("Attrs").p(meta);
todo!("Attributes");
}
}
impl CLangify for Meta {
fn print(&self, y: &mut CLangifier) {
let Self { name, kind } = self;
y.nest("Meta").p(name).p(kind);
todo!("Attributes");
}
}
impl CLangify for MetaKind {
fn print(&self, y: &mut CLangifier) {
match self {
MetaKind::Plain => y,
MetaKind::Equals(value) => y.p(value),
MetaKind::Func(args) => y.p(args),
};
todo!("Attributes");
}
}
impl CLangify for Item {
fn print(&self, y: &mut CLangifier) {
let Self { span: _, attrs: _, vis, kind } = self;
y.p(vis).p(kind);
}
}
impl CLangify for ItemKind {
fn print(&self, y: &mut CLangifier) {
match self {
ItemKind::Alias(f) => y.p(f),
ItemKind::Const(f) => y.p(f),
ItemKind::Static(f) => y.p(f),
ItemKind::Module(f) => y.p(f),
ItemKind::Function(f) => y.p(f),
ItemKind::Struct(f) => y.p(f),
ItemKind::Enum(f) => y.p(f),
ItemKind::Impl(f) => y.p(f),
ItemKind::Use(f) => y.p(f),
};
}
}
impl CLangify for Generics {
fn print(&self, _y: &mut CLangifier) {
let Self { vars } = self;
if !vars.is_empty() {
panic!("C doesn't have generics, dumbass.")
}
}
}
impl CLangify for Alias {
fn print(&self, y: &mut CLangifier) {
let Self { name, from } = self;
y.p("typedef ").p(from).p(" ");
y.p(name).p("; ");
}
}
impl CLangify for Const {
fn print(&self, y: &mut CLangifier) {
let Self { name, ty, init } = self;
y.p("const ").p(ty).p(" ");
y.p(name).p(" = ").p(init);
}
}
impl CLangify for Static {
fn print(&self, y: &mut CLangifier) {
let Self { mutable, name, ty, init } = self;
y.p(mutable).p(ty).p(" ");
y.p(name).p(" = ").p(init);
}
}
impl CLangify for Module {
fn print(&self, y: &mut CLangifier) {
let Self { name, file } = self;
y.nest("// mod ").p(name).p(" {").endl();
y.p(file);
y.endl().p("// } mod ").p(name);
}
}
impl CLangify for Function {
fn print(&self, y: &mut CLangifier) {
let Self { name, gens: _, sign, bind, body } = self;
let TyFn { args, rety } = sign;
let types = match &args.kind {
TyKind::Tuple(TyTuple { types }) => types.as_slice(),
_ => panic!("Unsupported function args: {args}"),
};
let bind = match bind {
Pattern::Tuple(tup) => tup.as_slice(),
_ => panic!("Unsupported function binders: {args}"),
};
y.p(rety).p(" ").p(name).p(" (");
for (idx, (bind, ty)) in bind.iter().zip(types).enumerate() {
if idx > 0 {
y.p(", ");
}
// y.print("/* TODO: desugar pat match args */");
y.p(ty).p(" ").p(bind);
}
y.p(") ").p(body);
}
}
impl CLangify for Struct {
fn print(&self, y: &mut CLangifier) {
let Self { name, gens: _, kind } = self;
y.p("struct ").p(name).nest(" {").p(kind);
y.endl().p("}");
}
}
impl CLangify for StructKind {
fn print(&self, y: &mut CLangifier) {
match self {
StructKind::Empty => y.endl().p("char _zero_sized_t;"),
StructKind::Tuple(k) => {
for (idx, ty) in k.iter().enumerate() {
y.endl().p(ty).p(" _").p(&idx).p(";");
}
y
}
StructKind::Struct(k) => y.p(k),
};
}
}
impl CLangify for StructMember {
fn print(&self, y: &mut CLangifier) {
let Self { vis, name, ty } = self;
y.p(vis).p(ty).p(" ").p(name).p(";");
}
}
impl CLangify for Enum {
fn print(&self, y: &mut CLangifier) {
let Self { name, gens: _, variants } = self;
y.nest("enum ").p(name).p(" {").endl();
for (idx, variant) in variants.iter().enumerate() {
if idx > 0 {
y.p(",").endl();
}
y.p(variant);
}
y.endl().p("\n}");
}
}
impl CLangify for Variant {
fn print(&self, y: &mut CLangifier) {
let Self { name, kind, body } = self;
y.p(name).p(kind).p(body);
}
}
impl CLangify for Impl {
fn print(&self, y: &mut CLangifier) {
let Self { gens, target, body } = self;
y.nest("/* TODO: impl ").p(gens).p(target).p(" { */ ");
y.p(body);
y.p("/* } // impl ").p(target).p(" */ ");
}
}
impl CLangify for ImplKind {
fn print(&self, y: &mut CLangifier) {
match self {
ImplKind::Type(t) => y.p(t),
ImplKind::Trait { impl_trait, for_type } => {
todo!("impl {impl_trait} for {for_type}")
}
};
}
}
impl CLangify for Use {
fn print(&self, y: &mut CLangifier) {
let Self { absolute: _, tree } = self;
y.p(tree);
}
}
impl CLangify for UseTree {
fn print(&self, y: &mut CLangifier) {
match self {
UseTree::Tree(trees) => y.p(trees),
UseTree::Path(path, tree) => y.p("/* ").p(path).p(" */").p(tree),
UseTree::Alias(from, to) => y.p("#import <").p(from).p(">.h// ").p(to).p(" "),
UseTree::Name(name) => y.p("#import <").p(name).p(".h> "),
UseTree::Glob => y.p("/* TODO: use globbing */"),
};
}
}
impl CLangify for Block {
fn print(&self, y: &mut CLangifier) {
let Self { stmts } = self;
{
let mut y = y.nest("{");
y.endl();
if let [
stmts @ ..,
Stmt { span: _, kind: StmtKind::Expr(expr), semi: Semi::Unterminated },
] = stmts.as_slice()
{
y.p(stmts).p("return ").p(expr).p(";");
} else {
y.p(stmts);
}
}
y.endl().p("}");
}
}
impl CLangify for Stmt {
fn print(&self, y: &mut CLangifier) {
let Self { span: _, kind, semi: _ } = self;
y.p(kind).p(";").endl();
}
}
impl CLangify for Semi {
fn print(&self, y: &mut CLangifier) {
y.p(";");
}
}
impl CLangify for StmtKind {
fn print(&self, y: &mut CLangifier) {
match self {
StmtKind::Empty => y,
StmtKind::Item(s) => y.p(s),
StmtKind::Expr(s) => y.p(s),
};
}
}
impl CLangify for Expr {
fn print(&self, y: &mut CLangifier) {
let Self { span: _, kind } = self;
y.p(kind);
}
}
impl CLangify for ExprKind {
fn print(&self, y: &mut CLangifier) {
match self {
ExprKind::Closure(k) => todo!("Downgrade {k}"),
ExprKind::Quote(k) => k.print(y),
ExprKind::Let(k) => k.print(y),
ExprKind::Match(k) => k.print(y),
ExprKind::Assign(k) => k.print(y),
ExprKind::Modify(k) => k.print(y),
ExprKind::Binary(k) => k.print(y),
ExprKind::Unary(k) => k.print(y),
ExprKind::Cast(k) => k.print(y),
ExprKind::Member(k) => k.print(y),
ExprKind::Index(k) => k.print(y),
ExprKind::Structor(k) => k.print(y),
ExprKind::Path(k) => k.print(y),
ExprKind::Literal(k) => k.print(y),
ExprKind::Array(k) => k.print(y),
ExprKind::ArrayRep(k) => k.print(y),
ExprKind::AddrOf(k) => k.print(y),
ExprKind::Block(k) => k.print(y),
ExprKind::Empty => {}
ExprKind::Group(k) => k.print(y),
ExprKind::Tuple(k) => k.print(y),
ExprKind::While(k) => k.print(y),
ExprKind::If(k) => k.print(y),
ExprKind::For(k) => k.print(y),
ExprKind::Break(k) => k.print(y),
ExprKind::Return(k) => k.print(y),
ExprKind::Continue => {
y.nest("continue");
}
}
}
}
impl CLangify for Quote {
fn print(&self, y: &mut CLangifier) {
y.nest("\"");
print!("{self}");
y.p("\"");
}
}
impl CLangify for Let {
fn print(&self, y: &mut CLangifier) {
let Self { mutable, name, ty, init } = self;
let ty = ty.as_deref().map(|ty| &ty.kind).unwrap_or(&TyKind::Infer);
match ty {
TyKind::Array(TyArray { ty, count }) => {
y.p(ty).p(" ").p(mutable).p(name).p("[").p(count).p("]");
}
TyKind::Fn(TyFn { args, rety }) => {
y.nest("(").p(rety).p(" *").p(mutable).p(name).p(")(");
match &args.kind {
TyKind::Tuple(TyTuple { types }) => {
for (idx, ty) in types.iter().enumerate() {
if idx > 0 {
y.p(", ");
}
y.p(ty);
}
}
_ => {
y.p(args);
}
}
y.p(")");
}
_ => {
y.indent().p(ty).p(" ").p(mutable).p(name);
}
}
if let Some(init) = init {
y.p(" = ").p(init);
}
}
}
impl CLangify for Pattern {
fn print(&self, y: &mut CLangifier) {
// TODO: Pattern match desugaring!!!
match self {
Pattern::Name(name) => y.p(name),
Pattern::Path(path) => y.p(path),
Pattern::Literal(literal) => y.p(literal),
Pattern::Rest(name) => y.p("..").p(name),
Pattern::Ref(mutability, pattern) => y.p("&").p(mutability).p(pattern),
Pattern::RangeExc(head, tail) => y.p("RangeExc").p(head).p(tail),
Pattern::RangeInc(head, tail) => y.p("RangeExc").p(head).p(tail),
Pattern::Tuple(patterns) => y.nest("Tuple").p(patterns),
Pattern::Array(patterns) => y.nest("Array").p(patterns),
Pattern::Struct(path, items) => {
{
let mut y = y.nest("Struct");
y.p(path);
for (name, item) in items {
y.p(name).p(item);
}
}
y
}
Pattern::TupleStruct(path, items) => {
{
let mut y = y.nest("TupleStruct");
y.p(path).p(items);
}
y
}
};
}
}
impl CLangify for Match {
fn print(&self, y: &mut CLangifier) {
let Self { scrutinee, arms } = self;
y.p("/* match ").p(scrutinee);
y.nest(" { ").p(arms);
y.p(" } */");
}
}
impl CLangify for MatchArm {
fn print(&self, y: &mut CLangifier) {
let Self(pat, expr) = self;
y.p(pat).p(" => ").p(expr).p(", ");
}
}
impl CLangify for Assign {
fn print(&self, y: &mut CLangifier) {
let Self { parts } = self;
y.p(&parts.0).p(" = ").p(&parts.1);
}
}
impl CLangify for Modify {
fn print(&self, y: &mut CLangifier) {
let Self { kind, parts } = self;
y.p(&parts.0).p(kind).p(&parts.1);
}
}
impl CLangify for ModifyKind {
fn print(&self, _y: &mut CLangifier) {
print!(" {self} ");
}
}
impl CLangify for Binary {
fn print(&self, y: &mut CLangifier) {
let Self { kind, parts } = self;
match kind {
BinaryKind::Call => y.p(&parts.0).p(&parts.1),
_ => y.p("(").p(&parts.0).p(kind).p(&parts.1).p(")"),
};
}
}
impl CLangify for BinaryKind {
fn print(&self, _y: &mut CLangifier) {
print!(" {self} ");
}
}
impl CLangify for Unary {
fn print(&self, y: &mut CLangifier) {
let Self { kind, tail } = self;
match kind {
UnaryKind::Deref => y.p("*").p(tail),
UnaryKind::Neg => y.p("-").p(tail),
UnaryKind::Not => y.p("!").p(tail),
UnaryKind::RangeInc => todo!("Unary RangeInc in C"),
UnaryKind::RangeExc => todo!("Unary RangeExc in C"),
UnaryKind::Loop => y.nest("while (1) { ").p(tail).p(" }"),
UnaryKind::At => todo!(),
UnaryKind::Tilde => todo!(),
};
}
}
impl CLangify for Cast {
fn print(&self, y: &mut CLangifier) {
let Self { head, ty } = self;
y.nest("(").p(ty).p(")");
y.p(head);
}
}
impl CLangify for Member {
fn print(&self, y: &mut CLangifier) {
let Self { head, kind } = self;
match kind {
MemberKind::Call(name, Tuple { exprs }) => {
y.p(name);
y.p("(");
for (idx, expr) in iter::once(head.as_ref()).chain(exprs).enumerate() {
if idx > 0 {
y.p(", ");
}
y.p(expr);
}
y.p(")")
}
MemberKind::Struct(name) => y.p(head).p(".").p(name),
MemberKind::Tuple(idx) => y.p(head).p("._").p(idx),
};
}
}
impl CLangify for Tuple {
fn print(&self, y: &mut CLangifier) {
let Self { exprs } = self;
let mut y = y.nest("( ");
for (idx, expr) in exprs.iter().enumerate() {
if idx > 0 {
y.p(", ");
}
y.p(expr);
}
y.p(" )");
}
}
impl CLangify for Index {
fn print(&self, y: &mut CLangifier) {
let Self { head, indices } = self;
y.p(head);
for index in indices {
y.p("[").p(index).p("]");
}
}
}
impl CLangify for Structor {
fn print(&self, y: &mut CLangifier) {
let Self { to, init } = self;
y.nest("(").p(to).p(")");
{
let mut y = y.nest("{ ");
for (idx, field) in init.iter().enumerate() {
if idx > 0 {
y.p(", ");
}
y.p(field);
}
y.p(init);
}
y.p("}");
}
}
impl CLangify for Fielder {
fn print(&self, y: &mut CLangifier) {
let Self { name, init } = self;
y.p(".").p(name).p(" = ").p(init);
}
}
impl CLangify for Array {
fn print(&self, y: &mut CLangifier) {
let Self { values } = self;
{
let mut y = y.nest("{");
y.endl();
for (idx, value) in values.iter().enumerate() {
if idx > 0 {
y.p(", ");
}
y.p(value);
}
}
y.endl().p("}");
}
}
impl CLangify for ArrayRep {
fn print(&self, y: &mut CLangifier) {
let Self { value, repeat } = self;
let ExprKind::Literal(Literal::Int(repeat)) = &repeat.kind else {
eprintln!("Constant needs folding: {repeat}");
return;
};
{
let mut y = y.nest("{");
for _ in 0..*repeat {
y.endl().p(value).p(",");
}
}
y.endl().p("}");
}
}
impl CLangify for AddrOf {
fn print(&self, y: &mut CLangifier) {
let Self { mutable: _, expr } = self;
y.p("&").p(expr);
}
}
impl CLangify for Group {
fn print(&self, y: &mut CLangifier) {
let Self { expr } = self;
y.p("(").p(expr).p(")");
}
}
impl CLangify for While {
fn print(&self, y: &mut CLangifier) {
// TODO: to properly propagate intermediate values, a new temp variable needs to be
// declared on every line lmao. This will require type info.
let Self { cond, pass, fail } = self;
let Else { body: fail } = fail;
y.nest("while(1) {")
.endl()
.p("if (")
.p(cond)
.p(") ")
.p(pass);
{
let mut y = y.nest(" else {");
y.endl();
if let Some(fail) = fail {
y.p(fail).p(";").endl();
}
y.p("break;");
}
y.endl().p("}");
}
}
impl CLangify for Else {
fn print(&self, y: &mut CLangifier) {
let Self { body } = self;
if let Some(body) = body {
y.p(" else ").p(body);
}
}
}
impl CLangify for If {
fn print(&self, y: &mut CLangifier) {
let Self { cond, pass, fail } = self;
y.p("if (").p(cond).p(")");
y.p(pass).p(fail);
}
}
impl CLangify for For {
#[rustfmt::skip]
fn print(&self, y: &mut CLangifier) {
let Self { bind, cond, pass, fail: _ } = self;
let (mode, (head, tail)) = match &cond.kind {
ExprKind::Binary(Binary { kind: BinaryKind::RangeExc, parts }) => (false, &**parts),
ExprKind::Binary(Binary { kind: BinaryKind::RangeInc, parts }) => (true, &**parts),
_ => todo!("Clangify for loops"),
};
// for (int bind = head; bind mode? < : <= tail; bind++);
y.p("for ( int ").p(bind).p(" = ").p(head).p("; ");
y.p(bind).p(if mode {"<="} else {"<"}).p(tail).p("; ");
y.p("++").p(bind).p(" ) ").p(pass);
}
}
impl CLangify for Break {
fn print(&self, y: &mut CLangifier) {
let Self { body } = self;
y.nest("break ").p(body);
}
}
impl CLangify for Return {
fn print(&self, y: &mut CLangifier) {
let Self { body } = self;
y.nest("return ").p(body);
}
}
impl CLangify for Literal {
fn print(&self, y: &mut CLangifier) {
match self {
Literal::Float(l) => y.p(l),
Literal::Bool(l) => y.p(l),
Literal::Int(l) => y.p(l),
Literal::Char(l) => y.p("'").p(l).p("'"),
Literal::String(l) => y.p(&'"').p(l).p(&'"'),
};
}
}
impl CLangify for Sym {
fn print(&self, y: &mut CLangifier) {
y.p(self.to_ref());
}
}
impl CLangify for Ty {
fn print(&self, y: &mut CLangifier) {
let Self { span: _, kind, gens: _ } = self;
y.p(kind);
}
}
impl CLangify for TyKind {
fn print(&self, y: &mut CLangifier) {
match self {
TyKind::Never => y.p("Never"),
TyKind::Infer => y.p("auto"),
TyKind::Path(t) => y.p(t),
TyKind::Tuple(t) => y.p(t),
TyKind::Ref(t) => y.p(t),
TyKind::Ptr(t) => y.p(t),
TyKind::Fn(t) => y.p(t),
TyKind::Slice(t) => y.p(t),
TyKind::Array(t) => y.p(t),
};
}
}
impl CLangify for Path {
fn print(&self, y: &mut CLangifier) {
let Self { absolute: _, parts } = self;
for (idx, part) in parts.iter().enumerate() {
if idx > 0 {
y.p("_");
}
y.p(part);
}
}
}
impl CLangify for PathPart {
fn print(&self, y: &mut CLangifier) {
match self {
PathPart::SuperKw => y.p("super"),
PathPart::SelfTy => y.p("Self"),
PathPart::Ident(i) => y.p(i),
};
}
}
impl CLangify for TyArray {
fn print(&self, y: &mut CLangifier) {
let Self { ty, count } = self;
y.p(ty).p("[").p(count).p("]");
}
}
impl CLangify for TySlice {
fn print(&self, y: &mut CLangifier) {
let Self { ty } = self;
y.p(ty).p("* ");
}
}
impl CLangify for TyTuple {
fn print(&self, y: &mut CLangifier) {
let Self { types } = self;
{
let mut y = y.nest("struct {");
y.endl();
for (idx, ty) in types.iter().enumerate() {
if idx > 0 {
y.p(",").endl();
}
y.p(ty);
}
}
y.endl().p("}");
}
}
impl CLangify for TyRef {
fn print(&self, y: &mut CLangifier) {
let Self { count, mutable, to } = self;
y.p(mutable).p(to);
for _ in 0..*count {
y.p("*");
}
}
}
impl CLangify for TyPtr {
fn print(&self, y: &mut CLangifier) {
let Self { to } = self;
y.p(to).p("*");
}
}
impl CLangify for TyFn {
fn print(&self, y: &mut CLangifier) {
let Self { args, rety } = self;
// TODO: function pointer syntax
y.nest("(").p(rety).p(" *)(");
match &args.kind {
TyKind::Tuple(TyTuple { types }) => {
for (idx, ty) in types.iter().enumerate() {
if idx > 0 {
y.p(", ");
}
y.p(ty);
}
y
}
_ => y.p(args),
}
.p(")");
}
}
impl<T: CLangify> CLangify for Option<T> {
fn print(&self, y: &mut CLangifier) {
if let Some(v) = self {
y.p(v);
}
}
}
impl<T: CLangify> CLangify for Box<T> {
fn print(&self, y: &mut CLangifier) {
y.p(&**self);
}
}
impl<T: CLangify> CLangify for Vec<T> {
fn print(&self, y: &mut CLangifier) {
for thing in self {
y.p(thing);
}
}
}
impl<T: CLangify> CLangify for [T] {
fn print(&self, y: &mut CLangifier) {
for thing in self {
y.p(thing);
}
}
}
impl CLangify for () {
fn print(&self, _y: &mut CLangifier) {
// TODO: C has no language support for zst
}
}
impl<T: CLangify> CLangify for &T {
fn print(&self, y: &mut CLangifier) {
(*self).print(y)
}
}
impl CLangify for std::fmt::Arguments<'_> {
fn print(&self, _y: &mut CLangifier) {
print!("{self}")
}
}
macro_rules! scalar {
($($t:ty),*$(,)?) => {
$(impl CLangify for $t {
fn print(&self, _y: &mut CLangifier) {
print!("{self}");
}
})*
};
}
scalar! {
bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, str, &str, String
}
}

View File

@@ -0,0 +1,768 @@
//! Pretty prints a conlang AST in yaml
use cl_ast::Stmt;
use cl_lexer::Lexer;
use cl_parser::Parser;
use repline::{Repline, error::Error as RlError};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
loop {
let line = match rl.read() {
Err(RlError::CtrlC(_)) => break,
Err(RlError::CtrlD(line)) => {
rl.deny();
line
}
Ok(line) => line,
Err(e) => Err(e)?,
};
let mut parser = Parser::new("", Lexer::new(&line));
let code = match parser.parse::<Stmt>() {
Ok(code) => {
rl.accept();
code
}
Err(e) => {
print!("\x1b[40G\x1bJ\x1b[91m{e}\x1b[0m");
continue;
}
};
print!("\x1b[G\x1b[J\x1b[A");
Yamler::new().yaml(&code);
println!();
}
Ok(())
}
pub use yamler::Yamler;
pub mod yamler {
use crate::yamlify::Yamlify;
use std::{
io::Write,
ops::{Deref, DerefMut},
};
#[derive(Debug, Default)]
pub struct Yamler {
depth: usize,
}
impl Yamler {
pub fn new() -> Self {
Self::default()
}
pub fn indent(&mut self) -> Section<'_> {
Section::new(self)
}
/// Prints a [Yamlify] value
#[inline]
pub fn yaml<T: Yamlify>(&mut self, yaml: &T) -> &mut Self {
yaml.yaml(self);
self
}
fn increase(&mut self) {
self.depth += 1;
}
fn decrease(&mut self) {
self.depth -= 1;
}
fn print_indentation(&self, writer: &mut impl Write) {
for _ in 0..self.depth {
let _ = write!(writer, " ");
}
}
/// Prints a section header and increases indentation
pub fn key(&mut self, name: impl Yamlify) -> Section<'_> {
println!();
self.print_indentation(&mut std::io::stdout().lock());
print!(" ");
name.yaml(self);
print!(":");
self.indent()
}
/// Prints a yaml key value pair: `- name: "value"`
pub fn pair<D: Yamlify, T: Yamlify>(&mut self, name: D, value: T) -> &mut Self {
self.key(name).value(value);
self
}
/// Prints a yaml scalar value: `"name"``
pub fn value<D: Yamlify>(&mut self, value: D) -> &mut Self {
print!(" ");
value.yaml(self);
self
}
pub fn list<D: Yamlify>(&mut self, list: &[D]) -> &mut Self {
for value in list {
println!();
self.print_indentation(&mut std::io::stdout().lock());
self.yaml(&"- ").yaml(value);
}
self
}
}
/// Tracks the start and end of an indented block (a "section")
pub struct Section<'y> {
yamler: &'y mut Yamler,
}
impl<'y> Section<'y> {
pub fn new(yamler: &'y mut Yamler) -> Self {
yamler.increase();
Self { yamler }
}
}
impl Deref for Section<'_> {
type Target = Yamler;
fn deref(&self) -> &Self::Target {
self.yamler
}
}
impl DerefMut for Section<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.yamler
}
}
impl Drop for Section<'_> {
fn drop(&mut self) {
let Self { yamler } = self;
yamler.decrease();
}
}
}
pub mod yamlify {
use super::yamler::Yamler;
use cl_ast::*;
pub trait Yamlify {
fn yaml(&self, y: &mut Yamler);
}
impl Yamlify for File {
fn yaml(&self, y: &mut Yamler) {
let File { name, items } = self;
y.key("File").pair("name", name).yaml(items);
}
}
impl Yamlify for Visibility {
fn yaml(&self, y: &mut Yamler) {
if let Visibility::Public = self {
y.pair("vis", "pub");
}
}
}
impl Yamlify for Mutability {
fn yaml(&self, y: &mut Yamler) {
if let Mutability::Mut = self {
y.pair("mut", true);
}
}
}
impl Yamlify for Attrs {
fn yaml(&self, y: &mut Yamler) {
let Self { meta } = self;
y.key("Attrs").yaml(meta);
}
}
impl Yamlify for Meta {
fn yaml(&self, y: &mut Yamler) {
let Self { name, kind } = self;
y.key("Meta").pair("name", name).yaml(kind);
}
}
impl Yamlify for MetaKind {
fn yaml(&self, y: &mut Yamler) {
match self {
MetaKind::Plain => y,
MetaKind::Equals(value) => y.pair("equals", value),
MetaKind::Func(args) => y.pair("args", args),
};
}
}
impl Yamlify for Item {
fn yaml(&self, y: &mut Yamler) {
let Self { span: _, attrs, vis, kind } = self;
y.key("Item").yaml(attrs).yaml(vis).yaml(kind);
}
}
impl Yamlify for ItemKind {
fn yaml(&self, y: &mut Yamler) {
match self {
ItemKind::Alias(f) => y.yaml(f),
ItemKind::Const(f) => y.yaml(f),
ItemKind::Static(f) => y.yaml(f),
ItemKind::Module(f) => y.yaml(f),
ItemKind::Function(f) => y.yaml(f),
ItemKind::Struct(f) => y.yaml(f),
ItemKind::Enum(f) => y.yaml(f),
ItemKind::Impl(f) => y.yaml(f),
ItemKind::Use(f) => y.yaml(f),
};
}
}
impl Yamlify for Generics {
fn yaml(&self, y: &mut Yamler) {
let Self { vars } = self;
y.key("Generics").value(vars);
}
}
impl Yamlify for Alias {
fn yaml(&self, y: &mut Yamler) {
let Self { name, from } = self;
y.key("Alias").pair("to", name).pair("from", from);
}
}
impl Yamlify for Const {
fn yaml(&self, y: &mut Yamler) {
let Self { name, ty, init } = self;
y.key("Const")
.pair("name", name)
.pair("ty", ty)
.pair("init", init);
}
}
impl Yamlify for Static {
fn yaml(&self, y: &mut Yamler) {
let Self { mutable, name, ty, init } = self;
y.key(name).yaml(mutable).pair("ty", ty).pair("init", init);
}
}
impl Yamlify for Module {
fn yaml(&self, y: &mut Yamler) {
let Self { name, file } = self;
y.key("Module").pair("name", name).yaml(file);
}
}
impl Yamlify for Function {
fn yaml(&self, y: &mut Yamler) {
let Self { name, gens, sign, bind, body } = self;
y.key("Function")
.pair("name", name)
.pair("gens", gens)
.pair("sign", sign)
.pair("bind", bind)
.pair("body", body);
}
}
impl Yamlify for Struct {
fn yaml(&self, y: &mut Yamler) {
let Self { name, gens, kind } = self;
y.key("Struct")
.pair("gens", gens)
.pair("name", name)
.yaml(kind);
}
}
impl Yamlify for StructKind {
fn yaml(&self, y: &mut Yamler) {
match self {
StructKind::Empty => y,
StructKind::Tuple(k) => y.yaml(k),
StructKind::Struct(k) => y.yaml(k),
};
}
}
impl Yamlify for StructMember {
fn yaml(&self, y: &mut Yamler) {
let Self { vis, name, ty } = self;
y.key("StructMember").yaml(vis).pair("name", name).yaml(ty);
}
}
impl Yamlify for Enum {
fn yaml(&self, y: &mut Yamler) {
let Self { name, gens, variants: kind } = self;
y.key("Enum")
.pair("gens", gens)
.pair("name", name)
.yaml(kind);
}
}
impl Yamlify for Variant {
fn yaml(&self, y: &mut Yamler) {
let Self { name, kind, body } = self;
y.key("Variant")
.pair("name", name)
.pair("kind", kind)
.pair("body", body);
}
}
impl Yamlify for Impl {
fn yaml(&self, y: &mut Yamler) {
let Self { gens, target, body } = self;
y.key("Impl")
.pair("gens", gens)
.pair("target", target)
.pair("body", body);
}
}
impl Yamlify for ImplKind {
fn yaml(&self, y: &mut Yamler) {
match self {
ImplKind::Type(t) => y.value(t),
ImplKind::Trait { impl_trait, for_type } => {
y.pair("trait", impl_trait).pair("for_type", for_type)
}
};
}
}
impl Yamlify for Use {
fn yaml(&self, y: &mut Yamler) {
let Self { absolute, tree } = self;
y.key("Use").pair("absolute", absolute).yaml(tree);
}
}
impl Yamlify for UseTree {
fn yaml(&self, y: &mut Yamler) {
match self {
UseTree::Tree(trees) => y.pair("trees", trees),
UseTree::Path(path, tree) => y.pair("path", path).pair("tree", tree),
UseTree::Alias(from, to) => y.pair("from", from).pair("to", to),
UseTree::Name(name) => y.pair("name", name),
UseTree::Glob => y.value("Glob"),
};
}
}
impl Yamlify for Block {
fn yaml(&self, y: &mut Yamler) {
let Self { stmts } = self;
y.key("Block").yaml(stmts);
}
}
impl Yamlify for Stmt {
fn yaml(&self, y: &mut Yamler) {
let Self { span: _, kind, semi } = self;
y.key("Stmt").value(kind).yaml(semi);
}
}
impl Yamlify for Semi {
fn yaml(&self, y: &mut Yamler) {
if let Semi::Terminated = self {
y.pair("terminated", true);
}
}
}
impl Yamlify for StmtKind {
fn yaml(&self, y: &mut Yamler) {
match self {
StmtKind::Empty => y,
StmtKind::Item(s) => y.yaml(s),
StmtKind::Expr(s) => y.yaml(s),
};
}
}
impl Yamlify for Expr {
fn yaml(&self, y: &mut Yamler) {
let Self { span: _, kind } = self;
y.yaml(kind);
}
}
impl Yamlify for ExprKind {
fn yaml(&self, y: &mut Yamler) {
match self {
ExprKind::Closure(k) => k.yaml(y),
ExprKind::Quote(k) => k.yaml(y),
ExprKind::Let(k) => k.yaml(y),
ExprKind::Match(k) => k.yaml(y),
ExprKind::Assign(k) => k.yaml(y),
ExprKind::Modify(k) => k.yaml(y),
ExprKind::Binary(k) => k.yaml(y),
ExprKind::Unary(k) => k.yaml(y),
ExprKind::Cast(k) => k.yaml(y),
ExprKind::Member(k) => k.yaml(y),
ExprKind::Index(k) => k.yaml(y),
ExprKind::Structor(k) => k.yaml(y),
ExprKind::Path(k) => k.yaml(y),
ExprKind::Literal(k) => k.yaml(y),
ExprKind::Array(k) => k.yaml(y),
ExprKind::ArrayRep(k) => k.yaml(y),
ExprKind::AddrOf(k) => k.yaml(y),
ExprKind::Block(k) => k.yaml(y),
ExprKind::Empty => {}
ExprKind::Group(k) => k.yaml(y),
ExprKind::Tuple(k) => k.yaml(y),
ExprKind::While(k) => k.yaml(y),
ExprKind::If(k) => k.yaml(y),
ExprKind::For(k) => k.yaml(y),
ExprKind::Break(k) => k.yaml(y),
ExprKind::Return(k) => k.yaml(y),
ExprKind::Continue => {
y.key("Continue");
}
}
}
}
impl Yamlify for Closure {
fn yaml(&self, y: &mut Yamler) {
let Self { arg, body } = self;
y.key("Closure").pair("arg", arg).pair("body", body);
}
}
impl Yamlify for Quote {
fn yaml(&self, y: &mut Yamler) {
y.key("Quote").value(self);
}
}
impl Yamlify for Let {
fn yaml(&self, y: &mut Yamler) {
let Self { mutable, name, ty, init } = self;
y.key("Let")
.pair("name", name)
.yaml(mutable)
.pair("ty", ty)
.pair("init", init);
}
}
impl Yamlify for Pattern {
fn yaml(&self, y: &mut Yamler) {
match self {
Pattern::Name(name) => y.value(name),
Pattern::Path(path) => y.value(path),
Pattern::Literal(literal) => y.value(literal),
Pattern::Rest(name) => y.pair("Rest", name),
Pattern::Ref(mutability, pattern) => y.yaml(mutability).pair("Pat", pattern),
Pattern::RangeInc(head, tail) => {
y.key("RangeInc").pair("head", head).pair("tail", tail);
y
}
Pattern::RangeExc(head, tail) => {
y.key("RangeExc").pair("head", head).pair("tail", tail);
y
}
Pattern::Tuple(patterns) => y.key("Tuple").list(patterns),
Pattern::Array(patterns) => y.key("Array").list(patterns),
Pattern::Struct(path, items) => {
{
let mut y = y.key("Struct");
y.yaml(path);
for (name, item) in items {
y.pair(name, item);
}
}
y
}
Pattern::TupleStruct(path, items) => {
y.key("TupleStruct").yaml(path).list(items);
y
}
};
}
}
impl Yamlify for Match {
fn yaml(&self, y: &mut Yamler) {
let Self { scrutinee, arms } = self;
y.key("Match")
.pair("scrutinee", scrutinee)
.pair("arms", arms);
}
}
impl Yamlify for MatchArm {
fn yaml(&self, y: &mut Yamler) {
let Self(pat, expr) = self;
y.pair("pat", pat).pair("expr", expr);
}
}
impl Yamlify for Assign {
fn yaml(&self, y: &mut Yamler) {
let Self { parts } = self;
y.key("Assign")
.pair("head", &parts.0)
.pair("tail", &parts.1);
}
}
impl Yamlify for Modify {
fn yaml(&self, y: &mut Yamler) {
let Self { kind, parts } = self;
y.key("Modify")
.pair("kind", kind)
.pair("head", &parts.0)
.pair("tail", &parts.1);
}
}
impl Yamlify for Binary {
fn yaml(&self, y: &mut Yamler) {
let Self { kind, parts } = self;
y.key("Binary")
.pair("kind", kind)
.pair("head", &parts.0)
.pair("tail", &parts.1);
}
}
impl Yamlify for Unary {
fn yaml(&self, y: &mut Yamler) {
let Self { kind, tail } = self;
y.key("Unary").pair("kind", kind).pair("tail", tail);
}
}
impl Yamlify for Cast {
fn yaml(&self, y: &mut Yamler) {
let Self { head, ty } = self;
y.key("Cast").pair("head", head).pair("ty", ty);
}
}
impl Yamlify for Member {
fn yaml(&self, y: &mut Yamler) {
let Self { head, kind } = self;
y.key("Member").pair("head", head).pair("kind", kind);
}
}
impl Yamlify for MemberKind {
fn yaml(&self, y: &mut Yamler) {
match self {
MemberKind::Call(id, args) => y.pair("id", id).pair("args", args),
MemberKind::Struct(id) => y.pair("id", id),
MemberKind::Tuple(id) => y.pair("id", id),
};
}
}
impl Yamlify for Tuple {
fn yaml(&self, y: &mut Yamler) {
let Self { exprs } = self;
y.key("Tuple").list(exprs);
}
}
impl Yamlify for Index {
fn yaml(&self, y: &mut Yamler) {
let Self { head, indices } = self;
y.key("Index")
.pair("head", head)
.key("indices")
.list(indices);
}
}
impl Yamlify for Structor {
fn yaml(&self, y: &mut Yamler) {
let Self { to, init } = self;
y.key("Structor").pair("to", to).list(init);
}
}
impl Yamlify for Fielder {
fn yaml(&self, y: &mut Yamler) {
let Self { name, init } = self;
y.key("Fielder").pair("name", name).pair("init", init);
}
}
impl Yamlify for Array {
fn yaml(&self, y: &mut Yamler) {
let Self { values } = self;
y.key("Array").list(values);
}
}
impl Yamlify for ArrayRep {
fn yaml(&self, y: &mut Yamler) {
let Self { value, repeat } = self;
y.key("ArrayRep")
.pair("value", value)
.pair("repeat", repeat);
}
}
impl Yamlify for AddrOf {
fn yaml(&self, y: &mut Yamler) {
let Self { mutable, expr } = self;
y.key("AddrOf").yaml(mutable).pair("expr", expr);
}
}
impl Yamlify for Group {
fn yaml(&self, y: &mut Yamler) {
let Self { expr } = self;
y.key("Group").yaml(expr);
}
}
impl Yamlify for While {
fn yaml(&self, y: &mut Yamler) {
let Self { cond, pass, fail } = self;
y.key("While")
.pair("cond", cond)
.pair("pass", pass)
.yaml(fail);
}
}
impl Yamlify for Else {
fn yaml(&self, y: &mut Yamler) {
let Self { body } = self;
y.key("fail").yaml(body);
}
}
impl Yamlify for If {
fn yaml(&self, y: &mut Yamler) {
let Self { cond, pass, fail } = self;
y.key("If").pair("cond", cond).pair("pass", pass).yaml(fail);
}
}
impl Yamlify for For {
fn yaml(&self, y: &mut Yamler) {
let Self { bind, cond, pass, fail } = self;
y.key("For")
.pair("bind", bind)
.pair("cond", cond)
.pair("pass", pass)
.yaml(fail);
}
}
impl Yamlify for Break {
fn yaml(&self, y: &mut Yamler) {
let Self { body } = self;
y.key("Break").yaml(body);
}
}
impl Yamlify for Return {
fn yaml(&self, y: &mut Yamler) {
let Self { body } = self;
y.key("Return").yaml(body);
}
}
impl Yamlify for Literal {
fn yaml(&self, _y: &mut Yamler) {
match self {
Literal::Bool(v) => print!("{v}"),
Literal::Char(v) => print!("'{}'", v.escape_debug()),
Literal::Int(v) => print!("{v}"),
Literal::Float(v) => print!("{v}"),
Literal::String(v) => print!("{}", v.escape_debug()),
}
}
}
impl Yamlify for Ty {
fn yaml(&self, y: &mut Yamler) {
let Self { span: _, kind, gens } = self;
y.key("Ty").yaml(kind).yaml(gens);
}
}
impl Yamlify for TyKind {
fn yaml(&self, y: &mut Yamler) {
match self {
TyKind::Never => y.value("Never"),
TyKind::Infer => y.value("_"),
TyKind::Path(t) => y.yaml(t),
TyKind::Tuple(t) => y.yaml(t),
TyKind::Ref(t) => y.yaml(t),
TyKind::Ptr(t) => y.yaml(t),
TyKind::Fn(t) => y.yaml(t),
TyKind::Slice(t) => y.yaml(t),
TyKind::Array(t) => y.yaml(t),
};
}
}
impl Yamlify for Path {
fn yaml(&self, y: &mut Yamler) {
let Self { absolute, parts } = self;
let mut y = y.key("Path");
if *absolute {
y.pair("absolute", absolute);
}
y.yaml(parts);
}
}
impl Yamlify for PathPart {
fn yaml(&self, y: &mut Yamler) {
match self {
PathPart::SuperKw => y.value("super"),
PathPart::SelfTy => y.value("Self"),
PathPart::Ident(i) => y.yaml(i),
};
}
}
impl Yamlify for TyArray {
fn yaml(&self, y: &mut Yamler) {
let Self { ty, count } = self;
y.key("TyArray").pair("ty", ty).pair("count", count);
}
}
impl Yamlify for TySlice {
fn yaml(&self, y: &mut Yamler) {
let Self { ty } = self;
y.key("TyArray").pair("ty", ty);
}
}
impl Yamlify for TyTuple {
fn yaml(&self, y: &mut Yamler) {
let Self { types } = self;
let mut y = y.key("TyTuple");
for ty in types {
y.yaml(ty);
}
}
}
impl Yamlify for TyRef {
fn yaml(&self, y: &mut Yamler) {
let Self { count, mutable, to } = self;
y.key("TyRef")
.pair("count", count)
.yaml(mutable)
.pair("to", to);
}
}
impl Yamlify for TyPtr {
fn yaml(&self, y: &mut Yamler) {
let Self { to } = self;
y.key("TyPtr").pair("to", to);
}
}
impl Yamlify for TyFn {
fn yaml(&self, y: &mut Yamler) {
let Self { args, rety } = self;
y.key("TyFn").pair("args", args).pair("rety", rety);
}
}
impl<T: Yamlify> Yamlify for Option<T> {
fn yaml(&self, y: &mut Yamler) {
if let Some(v) = self {
y.yaml(v);
} else {
y.value("");
}
}
}
impl<T: Yamlify> Yamlify for Box<T> {
fn yaml(&self, y: &mut Yamler) {
y.yaml(&**self);
}
}
impl<T: Yamlify> Yamlify for Vec<T> {
fn yaml(&self, y: &mut Yamler) {
y.list(self);
}
}
impl Yamlify for () {
fn yaml(&self, _y: &mut Yamler) {}
}
impl<T: Yamlify> Yamlify for &T {
fn yaml(&self, y: &mut Yamler) {
(*self).yaml(y)
}
}
macro_rules! scalar {
($($t:ty),*$(,)?) => {
$(impl Yamlify for $t {
fn yaml(&self, _y: &mut Yamler) {
print!("{self}");
}
})*
};
}
scalar! {
bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, &str, String,
BinaryKind, UnaryKind, ModifyKind, Sym,
}
}

View File

@@ -0,0 +1,14 @@
//! ANSI escape sequences
pub const RED: &str = "\x1b[31m";
pub const GREEN: &str = "\x1b[32m"; // the color of type checker mode
pub const CYAN: &str = "\x1b[36m";
pub const BRIGHT_GREEN: &str = "\x1b[92m";
pub const BRIGHT_BLUE: &str = "\x1b[94m";
pub const BRIGHT_MAGENTA: &str = "\x1b[95m";
pub const BRIGHT_CYAN: &str = "\x1b[96m";
pub const RESET: &str = "\x1b[0m";
pub const OUTPUT: &str = "\x1b[38;5;117m";
pub const CLEAR_LINES: &str = "\x1b[G\x1b[J";
pub const CLEAR_ALL: &str = "\x1b[H\x1b[2J\x1b[3J";

View File

@@ -0,0 +1,68 @@
//! Handles argument parsing (currently using the [argwerk] crate)
use std::{io::IsTerminal, path::PathBuf, str::FromStr};
argwerk::define! {
///
///The Conlang prototype debug interface
#[usage = "conlang [<file>] [-I <include...>] [-m <mode>] [-r <repl>]"]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Args {
pub file: Option<PathBuf>,
pub include: Vec<PathBuf>,
pub mode: Mode,
pub repl: bool = is_terminal(),
}
///files to include
["-I" | "--include", path] => {
include.push(path.into());
}
///the CLI operating mode (`f`mt | `l`ex | `r`un)
["-m" | "--mode", flr] => {
mode = flr.parse()?;
}
///whether to start the repl (`true` or `false`)
["-r" | "--repl", bool] => {
repl = bool.parse()?;
}
///display usage information
["-h" | "--help"] => {
println!("{}", Args::help());
if true { std::process::exit(0); }
}
///the main source file
[#[option] path] if file.is_none() => {
file = path.map(Into::into);
}
[path] if file.is_some() => {
include.push(path.into());
}
}
/// gets whether stdin AND stdout are a terminal, for pipelining
pub fn is_terminal() -> bool {
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
}
/// The CLI's operating mode
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mode {
Lex,
Fmt,
#[default]
Run,
}
impl FromStr for Mode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, &'static str> {
Ok(match s {
"f" | "fmt" | "p" | "pretty" => Mode::Fmt,
"l" | "lex" | "tokenize" | "token" => Mode::Lex,
"r" | "run" => Mode::Run,
_ => Err("Recognized modes are: 'r' \"run\", 'f' \"fmt\", 'l' \"lex\"")?,
})
}
}

View File

@@ -0,0 +1,5 @@
use cl_repl::{args, cli::run};
fn main() -> Result<(), Box<dyn std::error::Error>> {
run(args::Args::args()?)
}

171
compiler/cl-repl/src/cli.rs Normal file
View File

@@ -0,0 +1,171 @@
//! Implement's the command line interface
use crate::{
args::{Args, Mode},
ctx::Context,
menu,
tools::print_token,
};
use cl_ast::File;
use cl_interpret::{builtin::builtins, convalue::ConValue, env::Environment, interpret::Interpret};
use cl_lexer::Lexer;
use cl_parser::Parser;
use std::{borrow::Cow, error::Error, path::Path};
/// Run the command line interface
pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
let Args { file, include, mode, repl } = args;
let mut env = Environment::new();
env.add_builtins(&builtins! {
/// Lexes, parses, and evaluates an expression in the current env
fn eval(string) @env {
use cl_interpret::error::Error;
let string = match string {
ConValue::Str(string) => string.to_ref(),
ConValue::String(string) => string.as_str(),
ConValue::Ref(v) => {
let string = env.get_id(*v).cloned().unwrap_or_default();
return eval(env, &[string])
}
_ => Err(Error::TypeError())?
};
match Parser::new("eval", Lexer::new(string)).parse::<cl_ast::Stmt>() {
Err(e) => Ok(ConValue::Str(format!("{e}").into())),
Ok(v) => v.interpret(env),
}
}
/// Executes a file
fn import(path) @env {
use cl_interpret::error::Error;
match path {
ConValue::Str(path) => load_file(env, &**path).or(Ok(ConValue::Empty)),
ConValue::String(path) => load_file(env, &**path).or(Ok(ConValue::Empty)),
_ => Err(Error::TypeError())
}
}
fn putchar(ConValue::Char(c)) {
print!("{c}");
Ok(ConValue::Empty)
}
/// Gets a line of input from stdin
fn get_line(prompt) {
use cl_interpret::error::Error;
let prompt = match prompt {
ConValue::Str(prompt) => prompt.to_ref(),
ConValue::String(prompt) => prompt.as_str(),
_ => Err(Error::TypeError())?,
};
match repline::Repline::new("", prompt, "").read() {
Ok(line) => Ok(ConValue::String(line)),
Err(repline::Error::CtrlD(line)) => Ok(ConValue::String(line)),
Err(repline::Error::CtrlC(_)) => Err(cl_interpret::error::Error::Break(ConValue::Empty)),
Err(e) => Ok(ConValue::Str(e.to_string().into())),
}
}
});
for path in include {
load_file(&mut env, path)?;
}
if repl {
if let Some(file) = file
&& let Err(e) = load_file(&mut env, file)
{
eprintln!("{e}")
}
let mut ctx = Context::with_env(env);
menu::main_menu(mode, &mut ctx)?;
} else {
let path = format_path_for_display(file.as_deref());
let code = match &file {
Some(file) => std::fs::read_to_string(file)?,
None => std::io::read_to_string(std::io::stdin())?,
};
match mode {
Mode::Lex => lex_code(&path, &code),
Mode::Fmt => fmt_code(&path, &code),
Mode::Run => run_code(&path, &code, &mut env),
}?;
}
Ok(())
}
fn format_path_for_display(path: Option<&Path>) -> Cow<'_, str> {
match path {
Some(file) => file
.to_str()
.map(Cow::Borrowed)
.unwrap_or_else(|| Cow::Owned(file.display().to_string())),
None => Cow::Borrowed(""),
}
}
fn load_file(env: &mut Environment, path: impl AsRef<Path>) -> Result<ConValue, Box<dyn Error>> {
let path = path.as_ref();
let inliner = cl_parser::inliner::ModuleInliner::new(path.with_extension(""));
let file = std::fs::read_to_string(path)?;
let code = Parser::new(path.display().to_string(), Lexer::new(&file)).parse()?;
let code = match inliner.inline(code) {
Ok(a) => a,
Err((code, io_errs, parse_errs)) => {
for (file, err) in io_errs {
eprintln!("{}:{err}", file.display());
}
for (file, err) in parse_errs {
eprintln!("{}:{err}", file.display());
}
code
}
};
use cl_ast::WeightOf;
eprintln!("File {} weighs {} units", code.name, code.weight_of());
match env.eval(&code) {
Ok(v) => Ok(v),
Err(e) => {
eprintln!("{e}");
Ok(ConValue::Empty)
}
}
}
fn lex_code(path: &str, code: &str) -> Result<(), Box<dyn Error>> {
for token in Lexer::new(code) {
if !path.is_empty() {
print!("{}:", path);
}
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
Ok(())
}
fn fmt_code(path: &str, code: &str) -> Result<(), Box<dyn Error>> {
let code = Parser::new(path, Lexer::new(code)).parse::<File>()?;
println!("{code}");
Ok(())
}
fn run_code(path: &str, code: &str, env: &mut Environment) -> Result<(), Box<dyn Error>> {
let code = Parser::new(path, Lexer::new(code)).parse::<File>()?;
match code.interpret(env)? {
ConValue::Empty => {}
ret => println!("{ret}"),
}
if env.get("main".into()).is_ok() {
match env.call("main".into(), &[]) {
Ok(ConValue::Empty) => {}
Ok(ret) => println!("{ret}"),
Err(e) => println!("Error: {e}"),
}
}
Ok(())
}

View File

@@ -0,0 +1,24 @@
use cl_interpret::{convalue::ConValue, env::Environment, error::IResult, interpret::Interpret};
#[derive(Clone, Debug)]
pub struct Context {
pub env: Environment,
}
impl Context {
pub fn new() -> Self {
Self { env: Environment::new() }
}
pub fn with_env(env: Environment) -> Self {
Self { env }
}
pub fn run(&mut self, code: &impl Interpret) -> IResult<ConValue> {
code.interpret(&mut self.env)
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,11 @@
//! The Conlang REPL, based on [repline]
//!
//! Uses [argwerk] for argument parsing.
#![warn(clippy::all)]
pub mod ansi;
pub mod args;
pub mod cli;
pub mod ctx;
pub mod menu;
pub mod tools;

View File

@@ -0,0 +1,126 @@
use std::error::Error;
use crate::{ansi, args::Mode, ctx};
use cl_ast::Stmt;
use cl_interpret::convalue::ConValue;
use cl_lexer::Lexer;
use cl_parser::Parser;
use repline::{Error as RlError, error::ReplResult, prebaked::*};
pub fn clear() {
print!("{}", ansi::CLEAR_ALL);
banner()
}
pub fn banner() {
println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION"))
}
type ReplCallback = fn(&mut ctx::Context, &str) -> Result<Response, Box<dyn Error>>;
type ReplMode = (&'static str, &'static str, &'static str, ReplCallback);
#[rustfmt::skip]
const MODES: &[ReplMode] = &[
(ansi::CYAN, ".>", " >", mode_run),
(ansi::BRIGHT_BLUE, "l>", " >", mode_lex),
(ansi::BRIGHT_MAGENTA, "f>", " >", mode_fmt),
];
const fn get_mode(mode: Mode) -> ReplMode {
match mode {
Mode::Lex => MODES[1],
Mode::Fmt => MODES[2],
Mode::Run => MODES[0],
}
}
/// Presents a selection interface to the user
pub fn main_menu(mode: Mode, ctx: &mut ctx::Context) -> ReplResult<()> {
let mut mode = get_mode(mode);
banner();
let mut rl = repline::Repline::new(mode.0, mode.1, mode.2);
loop {
rl.set_prompt(mode.0, mode.1, mode.2);
let line = match rl.read() {
Err(RlError::CtrlC(_)) => return Ok(()),
Err(RlError::CtrlD(line)) => {
rl.deny();
line
}
Ok(line) => line,
Err(e) => Err(e)?,
};
print!("\x1b[G\x1b[J");
match line.trim() {
"" => continue,
"clear" => clear(),
"mode run" => mode = get_mode(Mode::Run),
"mode lex" => mode = get_mode(Mode::Lex),
"mode fmt" => mode = get_mode(Mode::Fmt),
"quit" => return Ok(()),
"help" => println!(
"Valid commands
help : Print this list
clear : Clear the screen
quit : Exit the program
mode lex : Lex the input
mode fmt : Format the input
mode run : Evaluate some expressions"
),
_ => match mode.3(ctx, &line) {
Ok(Response::Accept) => {
rl.accept();
continue;
}
Ok(Response::Deny) => {}
Ok(Response::Break) => return Ok(()),
Ok(Response::Continue) => continue,
Err(e) => rl.print_inline(format_args!("\t\x1b[91m{e}\x1b[0m"))?,
},
}
rl.deny();
}
}
pub fn mode_run(ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
use cl_ast::ast_visitor::Fold;
use cl_parser::inliner::ModuleInliner;
if line.trim().is_empty() {
return Ok(Response::Deny);
}
let code = Parser::new("", Lexer::new(line)).parse::<Stmt>()?;
let code = ModuleInliner::new(".").fold_stmt(code);
print!("{}", ansi::OUTPUT);
match ctx.run(&code) {
Ok(ConValue::Empty) => print!("{}", ansi::RESET),
Ok(v) => println!("{}{v}", ansi::RESET),
Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET),
}
Ok(Response::Accept)
}
pub fn mode_lex(_ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
for token in Lexer::new(line) {
match token {
Ok(token) => crate::tools::print_token(&token),
Err(e) => eprintln!("! > {}{e}{}", ansi::RED, ansi::RESET),
}
}
Ok(Response::Accept)
}
pub fn mode_fmt(_ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
let mut p = Parser::new("", Lexer::new(line));
match p.parse::<Stmt>() {
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
Err(e) => Err(e)?,
}
Ok(Response::Accept)
}

View File

@@ -0,0 +1,11 @@
use cl_token::Token;
/// Prints a token in the particular way [cl-repl](crate) does
pub fn print_token(t: &Token) {
println!(
"{:02}:{:02}: {:#19}{}",
t.line(),
t.col(),
t.ty(),
t.data(),
)
}

View File

@@ -0,0 +1,11 @@
[package]
name = "cl-structures"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
cl-arena = { version = "0", registry = "soft-fish" }

View File

@@ -0,0 +1,219 @@
//! Trivially-copyable, easily comparable typed [indices](MapIndex),
//! and an [IndexMap] to contain them.
//!
//! # Examples
//!
//! ```rust
//! # use cl_structures::index_map::*;
//! // first, create a new MapIndex type (this ensures type safety)
//! make_index! {
//! Number
//! }
//!
//! // then, create a map with that type
//! let mut numbers: IndexMap<Number, i32> = IndexMap::new();
//! let first = numbers.insert(1);
//! let second = numbers.insert(2);
//! let third = numbers.insert(3);
//!
//! // You can access elements immutably with `get`
//! assert_eq!(Some(&3), numbers.get(third));
//! assert_eq!(Some(&2), numbers.get(second));
//! // or by indexing
//! assert_eq!(1, numbers[first]);
//!
//! // Or mutably
//! *numbers.get_mut(first).unwrap() = 100000;
//!
//! assert_eq!(Some(&100000), numbers.get(first));
//! ```
/// Creates newtype indices over [`usize`] for use as [IndexMap] keys.
///
/// Generated key types implement [Clone], [Copy],
/// [Debug](core::fmt::Debug), [PartialEq], [Eq], [PartialOrd], [Ord], [Hash](core::hash::Hash),
/// and [MapIndex].
#[macro_export]
macro_rules! make_index {($($(#[$meta:meta])* $name:ident),*$(,)?) => {$(
$(#[$meta])*
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(usize);
impl $crate::index_map::MapIndex for $name {
#[doc = concat!("Constructs a [`", stringify!($name), "`] from a [`usize`] without checking bounds.\n")]
/// The provided value should be within the bounds of its associated container
#[inline]
fn from_usize(value: usize) -> Self {
Self(value)
}
#[inline]
fn get(&self) -> usize {
self.0
}
}
impl From< $name > for usize {
fn from(value: $name) -> Self {
value.0
}
}
)*}}
use self::iter::MapIndexIter;
use std::{
ops::{Index, IndexMut},
slice::GetDisjointMutError,
};
pub use make_index;
/// An index into a [IndexMap]. For full type-safety,
/// there should be a unique [MapIndex] for each [IndexMap].
pub trait MapIndex: std::fmt::Debug {
/// Constructs an [`MapIndex`] from a [`usize`] without checking bounds.
///
/// The provided value should be within the bounds of its associated container.
fn from_usize(value: usize) -> Self;
/// Gets the index of the [`MapIndex`] by value
fn get(&self) -> usize;
}
/// It's an array. Lmao.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IndexMap<K: MapIndex, V> {
map: Vec<V>,
id_type: std::marker::PhantomData<K>,
}
impl<V, K: MapIndex> IndexMap<K, V> {
/// Constructs an empty IndexMap.
pub fn new() -> Self {
Self::default()
}
/// Gets a reference to the value in slot `index`.
pub fn get(&self, index: K) -> Option<&V> {
self.map.get(index.get())
}
/// Gets a mutable reference to the value in slot `index`.
pub fn get_mut(&mut self, index: K) -> Option<&mut V> {
self.map.get_mut(index.get())
}
/// Returns mutable references to many indices at once.
///
/// Returns an error if any index is out of bounds, or if the same index was passed twice.
pub fn get_disjoint_mut<const N: usize>(
&mut self,
indices: [K; N],
) -> Result<[&mut V; N], GetDisjointMutError> {
self.map.get_disjoint_mut(indices.map(|id| id.get()))
}
/// Returns an iterator over the IndexMap.
pub fn values(&self) -> impl Iterator<Item = &V> {
self.map.iter()
}
/// Returns an iterator that allows modifying each value.
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.map.iter_mut()
}
/// Returns an iterator over all keys in the IndexMap.
pub fn keys(&self) -> iter::MapIndexIter<K> {
// Safety: IndexMap currently has map.len() entries, and data cannot be removed
MapIndexIter::new(0..self.map.len())
}
/// Constructs an [ID](MapIndex) from a [usize], if it's within bounds
#[doc(hidden)]
pub fn try_key_from(&self, value: usize) -> Option<K> {
(value < self.map.len()).then(|| K::from_usize(value))
}
/// Inserts a new item into the IndexMap, returning the key associated with it.
pub fn insert(&mut self, value: V) -> K {
let id = self.map.len();
self.map.push(value);
// Safety: value was pushed to `self.map[id]`
K::from_usize(id)
}
/// Replaces a value in the IndexMap, returning the old value.
pub fn replace(&mut self, key: K, value: V) -> V {
std::mem::replace(&mut self[key], value)
}
}
impl<K: MapIndex, V> Default for IndexMap<K, V> {
fn default() -> Self {
Self { map: vec![], id_type: std::marker::PhantomData }
}
}
impl<K: MapIndex, V> Index<K> for IndexMap<K, V> {
type Output = V;
fn index(&self, index: K) -> &Self::Output {
match self.map.get(index.get()) {
None => panic!("Index {:?} out of bounds in IndexMap!", index),
Some(value) => value,
}
}
}
impl<K: MapIndex, V> IndexMut<K> for IndexMap<K, V> {
fn index_mut(&mut self, index: K) -> &mut Self::Output {
match self.map.get_mut(index.get()) {
None => panic!("Index {:?} out of bounds in IndexMap!", index),
Some(value) => value,
}
}
}
mod iter {
//! Iterators for [IndexMap](super::IndexMap)
use super::MapIndex;
use std::{marker::PhantomData, ops::Range};
/// Iterates over the keys of an [IndexMap](super::IndexMap), independently of the map.
///
/// This is guaranteed to never overrun the length of the map, but is *NOT* guaranteed
/// to iterate over all elements of the map if the map is extended during iteration.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MapIndexIter<K: MapIndex> {
range: Range<usize>,
_id: PhantomData<K>,
}
impl<K: MapIndex> MapIndexIter<K> {
/// Creates a new [MapIndexIter] producing the given [MapIndex]
pub(super) fn new(range: Range<usize>) -> Self {
Self { range, _id: PhantomData }
}
}
impl<ID: MapIndex> Iterator for MapIndexIter<ID> {
type Item = ID;
fn next(&mut self) -> Option<Self::Item> {
Some(ID::from_usize(self.range.next()?))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.range.size_hint()
}
}
impl<ID: MapIndex> DoubleEndedIterator for MapIndexIter<ID> {
fn next_back(&mut self) -> Option<Self::Item> {
// Safety: see above
Some(ID::from_usize(self.range.next_back()?))
}
}
impl<ID: MapIndex> ExactSizeIterator for MapIndexIter<ID> {}
}

View File

@@ -0,0 +1,319 @@
//! Interners for [strings](string_interner) and arbitrary [types](typed_interner).
//!
//! An object is [Interned][1] if it is allocated within one of the interners
//! in this module. [Interned][1] values have referential equality semantics, and
//! [Deref](std::ops::Deref) to the value within their respective intern pool.
//!
//! This means, of course, that the same value interned in two different pools will be
//! considered *not equal* by [Eq] and [Hash](std::hash::Hash).
//!
//! [1]: interned::Interned
pub mod interned {
//! An [Interned] reference asserts its wrapped value has referential equality.
use super::string_interner::StringInterner;
use std::{
fmt::{Debug, Display},
hash::Hash,
ops::Deref,
};
/// An [Interned] value is one that is *referentially comparable*.
/// That is, the interned value is unique in memory, simplifying
/// its equality and hashing implementation.
///
/// Comparing [Interned] values via [PartialOrd] or [Ord] will still
/// dereference to the wrapped pointers, and as such, may produce
/// results inconsistent with [PartialEq] or [Eq].
#[repr(transparent)]
pub struct Interned<'a, T: ?Sized> {
value: &'a T,
}
impl<'a, T: ?Sized> Interned<'a, T> {
/// Gets the internal value as a pointer
pub fn as_ptr(interned: &Self) -> *const T {
interned.value
}
/// Gets the internal value as a reference with the interner's lifetime
pub fn to_ref(&self) -> &'a T {
self.value
}
}
impl<T: ?Sized + Debug> Debug for Interned<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "~")?;
self.value.fmt(f)
}
}
impl<'a, T: ?Sized> Interned<'a, T> {
pub(super) fn new(value: &'a T) -> Self {
Self { value }
}
}
impl<T: ?Sized> Deref for Interned<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<T: ?Sized> Copy for Interned<'_, T> {}
impl<T: ?Sized> Clone for Interned<'_, T> {
fn clone(&self) -> Self {
*self
}
}
// TODO: These implementations are subtly incorrect, as they do not line up with `eq`
// impl<'a, T: ?Sized + PartialOrd> PartialOrd for Interned<'a, T> {
// fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// match self == other {
// true => Some(std::cmp::Ordering::Equal),
// false => self.value.partial_cmp(other.value),
// }
// }
// }
// impl<'a, T: ?Sized + Ord> Ord for Interned<'a, T> {
// fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// match self == other {
// true => std::cmp::Ordering::Equal,
// false => self.value.cmp(other.value),
// }
// }
// }
impl<T: ?Sized> Eq for Interned<'_, T> {}
impl<T: ?Sized> PartialEq for Interned<'_, T> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.value, other.value)
}
}
impl<T: ?Sized> Hash for Interned<'_, T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Self::as_ptr(self).hash(state)
}
}
impl<T: ?Sized + Display> Display for Interned<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl<T: AsRef<str>> From<T> for Interned<'static, str> {
/// Types which implement [`AsRef<str>`] will be stored in the global [StringInterner]
fn from(value: T) -> Self {
from_str(value.as_ref())
}
}
fn from_str(value: &str) -> Interned<'static, str> {
let global_interner = StringInterner::global();
global_interner.get_or_insert(value)
}
}
pub mod string_interner {
//! A [StringInterner] hands out [Interned] copies of each unique string given to it.
use super::interned::Interned;
use cl_arena::dropless_arena::DroplessArena;
use std::{
collections::HashSet,
sync::{OnceLock, RwLock},
};
/// A string interner hands out [Interned] copies of each unique string given to it.
#[derive(Default)]
pub struct StringInterner<'a> {
arena: DroplessArena<'a>,
keys: RwLock<HashSet<&'a str>>,
}
impl StringInterner<'static> {
/// Gets a reference to a global string interner whose [Interned] strings are `'static`
pub fn global() -> &'static Self {
static GLOBAL_INTERNER: OnceLock<StringInterner<'static>> = OnceLock::new();
// SAFETY: The RwLock within the interner's `keys` protects the arena
// from being modified concurrently.
GLOBAL_INTERNER.get_or_init(|| StringInterner {
arena: DroplessArena::new(),
keys: Default::default(),
})
}
}
impl<'a> StringInterner<'a> {
/// Creates a new [StringInterner] backed by the provided [DroplessArena]
pub fn new(arena: DroplessArena<'a>) -> Self {
Self { arena, keys: RwLock::new(HashSet::new()) }
}
/// Returns an [Interned] copy of the given string,
/// allocating a new one if it doesn't already exist.
///
/// # Blocks
/// This function blocks when the interner is held by another thread.
pub fn get_or_insert(&'a self, value: &str) -> Interned<'a, str> {
let Self { arena, keys } = self;
// Safety: Holding this write guard for the entire duration of this
// function enforces a safety invariant. See StringInterner::global.
let mut keys = keys.write().expect("should not be poisoned");
Interned::new(match keys.get(value) {
Some(value) => value,
None => {
let value = match value {
"" => "", // Arena will panic if passed an empty string
_ => arena.alloc_str(value),
};
keys.insert(value);
value
}
})
}
/// Gets a reference to the interned copy of the given value, if it exists
/// # Blocks
/// This function blocks when the interner is held by another thread.
pub fn get(&'a self, value: &str) -> Option<Interned<'a, str>> {
let keys = self.keys.read().expect("should not be poisoned");
keys.get(value).copied().map(Interned::new)
}
}
impl std::fmt::Debug for StringInterner<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Interner")
.field("keys", &self.keys)
.finish()
}
}
impl std::fmt::Display for StringInterner<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Ok(keys) = self.keys.read() else {
return write!(f, "Could not lock StringInterner key map.");
};
let mut keys: Vec<_> = keys.iter().collect();
keys.sort();
writeln!(f, "Keys:")?;
for (idx, key) in keys.iter().enumerate() {
writeln!(f, "{idx}:\t\"{key}\"")?
}
writeln!(f, "Count: {}", keys.len())?;
Ok(())
}
}
// # Safety:
// This is fine because StringInterner::get_or_insert(v) holds a RwLock
// for its entire duration, and doesn't touch the non-(Send+Sync) arena
// unless the lock is held by a write guard.
unsafe impl Send for StringInterner<'_> {}
unsafe impl Sync for StringInterner<'_> {}
#[cfg(test)]
mod tests {
use super::StringInterner;
macro_rules! ptr_eq {
($a: expr, $b: expr $(, $($t:tt)*)?) => {
assert_eq!(std::ptr::addr_of!($a), std::ptr::addr_of!($b) $(, $($t)*)?)
};
}
macro_rules! ptr_ne {
($a: expr, $b: expr $(, $($t:tt)*)?) => {
assert_ne!(std::ptr::addr_of!($a), std::ptr::addr_of!($b) $(, $($t)*)?)
};
}
#[test]
fn empties_is_unique() {
let interner = StringInterner::global();
let empty = interner.get_or_insert("");
let empty2 = interner.get_or_insert("");
ptr_eq!(*empty, *empty2);
}
#[test]
fn non_empty_is_unique() {
let interner = StringInterner::global();
let nonempty1 = interner.get_or_insert("not empty!");
let nonempty2 = interner.get_or_insert("not empty!");
let different = interner.get_or_insert("different!");
ptr_eq!(*nonempty1, *nonempty2);
ptr_ne!(*nonempty1, *different);
}
}
}
pub mod typed_interner {
//! A [TypedInterner] hands out [Interned] references for arbitrary types.
//!
//! Note: It is a *logic error* to modify the returned reference via interior mutability
//! in a way that changes the values produced by [Eq] and [Hash].
//!
//! See the standard library [HashSet] for more details.
use super::interned::Interned;
use cl_arena::typed_arena::TypedArena;
use std::{collections::HashSet, hash::Hash, sync::RwLock};
/// A [TypedInterner] hands out [Interned] references for arbitrary types.
///
/// See the [module-level documentation](self) for more information.
pub struct TypedInterner<'a, T: Eq + Hash> {
arena: TypedArena<'a, T>,
keys: RwLock<HashSet<&'a T>>,
}
impl<'a, T: Eq + Hash> Default for TypedInterner<'a, T> {
fn default() -> Self {
Self { arena: Default::default(), keys: Default::default() }
}
}
impl<'a, T: Eq + Hash> TypedInterner<'a, T> {
/// Creates a new [TypedInterner] backed by the provided [TypedArena]
pub fn new(arena: TypedArena<'a, T>) -> Self {
Self { arena, keys: RwLock::new(HashSet::new()) }
}
/// Converts the given value into an [Interned] value.
///
/// # Blocks
/// This function blocks when the interner is held by another thread.
pub fn get_or_insert(&'a self, value: T) -> Interned<'a, T> {
let Self { arena, keys } = self;
// Safety: Locking the keyset for the entire duration of this function
// enforces a safety invariant when the interner is stored in a global.
let mut keys = keys.write().expect("should not be poisoned");
Interned::new(match keys.get(&value) {
Some(value) => value,
None => {
let value = arena.alloc(value);
keys.insert(value);
value
}
})
}
/// Returns the [Interned] copy of the given value, if one already exists
///
/// # Blocks
/// This function blocks when the interner is being written to by another thread.
pub fn get(&self, value: &T) -> Option<Interned<'a, T>> {
let keys = self.keys.read().expect("should not be poisoned");
keys.get(value).copied().map(Interned::new)
}
}
/// # Safety
/// This should be safe because references yielded by
/// [get_or_insert](TypedInterner::get_or_insert) are unique, and the function uses
/// the [RwLock] around the [HashSet] to ensure mutual exclusion
unsafe impl<'a, T: Eq + Hash + Send> Send for TypedInterner<'a, T> where &'a T: Send {}
unsafe impl<T: Eq + Hash + Send + Sync> Sync for TypedInterner<'_, T> {}
}

View File

@@ -0,0 +1,24 @@
//! # Universally useful structures
//! - [Span](struct@span::Span): Stores a start and end [Loc](struct@span::Loc)
//! - [Loc](struct@span::Loc): Stores the index in a stream
//! - [TypedInterner][ti] & [StringInterner][si]: Provies stable, unique allocations
//! - [Stack](stack::Stack): Contiguous collections with constant capacity
//! - [IndexMap][im]: A map from [map indices][mi] to values
//!
//! [ti]: intern::typed_interner::TypedInterner
//! [si]: intern::string_interner::StringInterner
//! [im]: index_map::IndexMap
//! [mi]: index_map::MapIndex
#![warn(clippy::all)]
#![feature(dropck_eyepatch, decl_macro)]
#![deny(unsafe_op_in_unsafe_fn)]
pub mod intern;
pub mod span;
pub mod tree;
pub mod stack;
pub mod index_map;

View File

@@ -1,8 +1,6 @@
//! # Universally useful structures
//! - [struct@Span]: Stores the start and end [struct@Loc] of a notable AST node
//! - [struct@Loc]: Stores the line/column of a notable AST node
#![allow(non_snake_case)]
use crate::lexer::Lexer;
/// Stores the start and end [locations](struct@Loc) within the token stream
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -10,24 +8,33 @@ pub struct Span {
pub head: Loc,
pub tail: Loc,
}
pub fn Span(head: Loc, tail: Loc) -> Span {
pub const fn Span(head: Loc, tail: Loc) -> Span {
Span { head, tail }
}
impl Span {
pub const fn dummy() -> Self {
Span { head: Loc::dummy(), tail: Loc::dummy() }
}
}
/// Stores a read-only (line, column) location in a token stream
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Loc {
line: u32,
col: u32,
}
pub fn Loc(line: u32, col: u32) -> Loc {
pub const fn Loc(line: u32, col: u32) -> Loc {
Loc { line, col }
}
impl Loc {
pub fn line(self) -> u32 {
pub const fn dummy() -> Self {
Loc { line: 0, col: 0 }
}
pub const fn line(self) -> u32 {
self.line
}
pub fn col(self) -> u32 {
pub const fn col(self) -> u32 {
self.col
}
}
@@ -35,12 +42,6 @@ impl Loc {
impl std::fmt::Display for Loc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Loc { line, col } = self;
write!(f, "{line}:{col}:")
}
}
impl<'t> From<&Lexer<'t>> for Loc {
fn from(value: &Lexer<'t>) -> Self {
Loc(value.line(), value.col())
write!(f, "{line}:{col}")
}
}

View File

@@ -0,0 +1,777 @@
//! A contiguous collection with constant capacity.
//!
//! Since the capacity of a [Stack] may be [*known at compile time*](Sized),
//! it may live on the call stack.
//!
//!
//! # Examples
//!
//! Unlike a [Vec], the [Stack] doesn't grow when it reaches capacity.
//! ```should_panic
//! # use cl_structures::stack::*;
//! let mut v = stack![1];
//! v.push("This should work");
//! v.push("This will panic!");
//! ```
//! To get around this limitation, the methods [try_push](Stack::try_push) and
//! [try_insert](Stack::try_insert) are provided:
//! ```
//! # use cl_structures::stack::*;
//! let mut v = stack![1];
//! v.push("This should work");
//! v.try_push("This should produce an err").unwrap_err();
//! ```
//!
//! As the name suggests, a [Stack] enforces a stack discipline:
//! ```
//! # use cl_structures::stack::*;
//! let mut v = stack![100];
//!
//! assert_eq!(100, v.capacity());
//! assert_eq!(0, v.len());
//!
//! // Elements are pushed one at a time onto the stack
//! v.push("foo");
//! v.push("bar");
//! assert_eq!(2, v.len());
//!
//! // The stack can be used anywhere a slice is expected
//! assert_eq!(Some(&"foo"), v.get(0));
//! assert_eq!(Some(&"bar"), v.last());
//!
//! // Elements are popped from the stack in reverse order
//! assert_eq!(Some("bar"), v.pop());
//! assert_eq!(Some("foo"), v.pop());
//! assert_eq!(None, v.pop());
//! ```
// yar har! here there be unsafe code! Tread carefully.
use core::slice;
use std::{
fmt::Debug,
marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
ops::{Deref, DerefMut},
ptr,
};
/// Creates a [`stack`] containing the arguments
///
/// # Examples
///
/// Creates a *full* [`Stack`] containing a list of elements
/// ```
/// # use cl_structures::stack::stack;
/// let mut v = stack![1, 2, 3];
///
/// assert_eq!(Some(3), v.pop());
/// assert_eq!(Some(2), v.pop());
/// assert_eq!(Some(1), v.pop());
/// assert_eq!(None, v.pop());
/// ```
///
/// Creates a *full* [`Stack`] from a given element and size
/// ```
/// # use cl_structures::stack::stack;
/// let mut v = stack![1; 2];
///
/// assert_eq!(Some(1), v.pop());
/// assert_eq!(Some(1), v.pop());
/// assert_eq!(None, v.pop());
/// ```
///
/// Creates an *empty* [`Stack`] from a given size
/// ```
/// # use cl_structures::stack::{Stack, stack};
/// let mut v = stack![10];
///
/// assert_eq!(0, v.len());
/// assert_eq!(10, v.capacity());
///
/// v.push(10);
/// assert_eq!(Some(&10), v.last());
/// ```
pub macro stack {
($capacity:literal) => {
Stack::<_, $capacity>::new()
},
($value:expr ; $count:literal) => {{
let mut stack: Stack<_, $count> = Stack::new();
for _ in 0..$count {
stack.push($value)
}
stack
}},
($value:expr ; $count:literal ; $capacity:literal) => {{
let mut stack: Stack<_, $capacity> = Stack::new();
for _ in 0..$count {
stack.push($value)
}
stack
}},
($($values:expr),* $(,)?) => {
Stack::from([$($values),*])
}
}
/// A contiguous collection with constant capacity
pub struct Stack<T, const N: usize> {
_data: PhantomData<T>,
buf: [MaybeUninit<T>; N],
len: usize,
}
impl<T: Clone, const N: usize> Clone for Stack<T, N> {
fn clone(&self) -> Self {
let mut new = Self::new();
for value in self.iter() {
new.push(value.clone())
}
new
}
}
impl<T: Debug, const N: usize> Debug for Stack<T, N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl<T, const N: usize> Default for Stack<T, N> {
fn default() -> Self {
Self::new()
}
}
impl<T, const N: usize> Deref for Stack<T, N> {
type Target = [T];
#[inline]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for Stack<T, N> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
// requires dropck-eyepatch for elements with contravariant lifetimes
unsafe impl<#[may_dangle] T, const N: usize> Drop for Stack<T, N> {
#[inline]
fn drop(&mut self) {
// Safety: Elements in [0..self.len] are initialized
if std::mem::needs_drop::<T>() {
unsafe { core::ptr::drop_in_place(self.as_mut_slice()) };
}
}
}
impl<T, const N: usize> Extend<T> for Stack<T, N> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for value in iter {
self.push(value)
}
}
}
impl<T, const N: usize> From<[T; N]> for Stack<T, N> {
fn from(value: [T; N]) -> Self {
let value = ManuallyDrop::new(value);
if std::mem::size_of::<[T; N]>() == 0 {
// Safety: since [T; N] is zero-sized, and there are no other fields,
// it should be okay to interpret N as Self
unsafe { ptr::read(&N as *const _ as *const _) }
} else {
// Safety:
// - `value` is ManuallyDrop, so its destructor won't run
// - All elements are assumed to be initialized (so len is N)
Self {
buf: unsafe { ptr::read(&value as *const _ as *const _) },
len: N,
..Default::default()
}
}
}
}
impl<T, const N: usize> Stack<T, N> {
/// Constructs a new, empty [Stack]
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// let mut v: Stack<_, 3> = Stack::new();
///
/// v.try_push(1).unwrap();
/// v.try_push(2).unwrap();
/// v.try_push(3).unwrap();
/// // Trying to push a 4th element will fail, and return the failed element
/// assert_eq!(4, v.try_push(4).unwrap_err());
///
/// assert_eq!(Some(3), v.pop());
/// ```
pub const fn new() -> Self {
Self { buf: [const { MaybeUninit::uninit() }; N], len: 0, _data: PhantomData }
}
/// Constructs a new [Stack] from an array of [`MaybeUninit<T>`] and an initialized length
///
/// # Safety
///
/// - Elements from `0..len` must be initialized
/// - len must not exceed the length of the array
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// # use core::mem::MaybeUninit;
/// let mut v = unsafe { Stack::from_raw_parts([MaybeUninit::new(100)], 1) };
///
/// assert_eq!(1, v.len());
/// assert_eq!(1, v.capacity());
/// assert_eq!(Some(100), v.pop());
/// assert_eq!(None, v.pop());
/// ```
pub const unsafe fn from_raw_parts(buf: [MaybeUninit<T>; N], len: usize) -> Self {
Self { buf, len, _data: PhantomData }
}
/// Converts a [Stack] into an array of [`MaybeUninit<T>`] and the initialized length
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// let mut v: Stack<_, 10> = Stack::new();
/// v.push(0);
/// v.push(1);
///
/// let (buf, len) = v.into_raw_parts();
///
/// assert_eq!(0, unsafe { buf[0].assume_init() });
/// assert_eq!(1, unsafe { buf[1].assume_init() });
/// assert_eq!(2, len);
/// ```
#[inline]
pub fn into_raw_parts(self) -> ([MaybeUninit<T>; N], usize) {
let this = ManuallyDrop::new(self);
// Safety: since
(unsafe { ptr::read(&this.buf) }, this.len)
}
/// Returns a raw pointer to the stack's buffer
pub const fn as_ptr(&self) -> *const T {
self.buf.as_ptr().cast()
}
/// Returns an unsafe mutable pointer to the stack's buffer
pub const fn as_mut_ptr(&mut self) -> *mut T {
self.buf.as_mut_ptr().cast()
}
/// Extracts a slice containing the entire vector
pub const fn as_slice(&self) -> &[T] {
// Safety:
// - We have ensured all elements from 0 to len have been initialized
// - self.elem[0] came from a reference, and so is aligned to T
// unsafe { &*(&self.buf[0..self.len] as *const [_] as *const [T]) }
unsafe { slice::from_raw_parts(self.buf.as_ptr().cast(), self.len) }
}
/// Extracts a mutable slice containing the entire vector
pub const fn as_mut_slice(&mut self) -> &mut [T] {
// Safety:
// - See Stack::as_slice
unsafe { slice::from_raw_parts_mut(self.buf.as_mut_ptr().cast(), self.len) }
}
/// Returns the total number of elements the stack can hold
pub const fn capacity(&self) -> usize {
N
}
/// Moves an existing stack into an allocation of a (potentially) different size,
/// truncating if necessary.
///
/// This can be used to easily construct a half-empty stack
///
/// # Examples
///
/// You can grow a stack to fit more elements
/// ```
/// # use cl_structures::stack::Stack;
/// let v = Stack::from([0, 1, 2, 3, 4]);
/// assert_eq!(5, v.capacity());
///
/// let mut v = v.resize::<10>();
/// assert_eq!(10, v.capacity());
///
/// v.push(5);
/// ```
///
/// You can truncate a stack, dropping elements off the end
/// ```
/// # use cl_structures::stack::Stack;
/// let v = Stack::from([0, 1, 2, 3, 4, 5, 6, 7]);
/// assert_eq!(8, v.capacity());
///
/// let v = v.resize::<5>();
/// assert_eq!(5, v.capacity());
/// ```
pub fn resize<const M: usize>(mut self) -> Stack<T, M> {
// Drop elements until new length is reached
while self.len > M {
drop(self.pop());
}
let (old, len) = self.into_raw_parts();
let mut new: Stack<T, M> = Stack::new();
// Safety:
// - new and old are separate allocations
// - len <= M
unsafe {
ptr::copy_nonoverlapping(old.as_ptr(), new.buf.as_mut_ptr(), len);
}
new.len = len;
new
}
/// Push a new element onto the end of the stack
///
/// # May Panic
///
/// Panics if the new length exceeds capacity
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// let mut v: Stack<_, 4> = Stack::new();
///
/// v.push(0);
/// v.push(1);
/// v.push(2);
/// v.push(3);
/// assert_eq!(&[0, 1, 2, 3], v.as_slice());
/// ```
pub const fn push(&mut self, value: T) {
if self.len >= N {
panic!("Attempted to push into full stack")
}
// Safety: len is confirmed to be less than capacity
unsafe { self.push_unchecked(value) };
}
/// Push a new element onto the end of the stack
///
/// Returns [`Err(value)`](Result::Err) if the new length would exceed capacity
pub const fn try_push(&mut self, value: T) -> Result<(), T> {
if self.len >= N {
return Err(value);
}
// Safety: len is confirmed to be less than capacity
unsafe { self.push_unchecked(value) };
Ok(())
}
/// Push a new element onto the end of the stack, without checking capacity
///
/// # Safety
///
/// len after push must not exceed capacity N
#[inline]
const unsafe fn push_unchecked(&mut self, value: T) {
unsafe {
// self.buf.get_unchecked_mut(self.len).write(value); // TODO: This is non-const
ptr::write(self.as_mut_ptr().add(self.len), value)
}
self.len += 1; // post inc
}
/// Pops the last element off the end of the stack, and returns it
///
/// Returns None if the stack is empty
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// let mut v = Stack::from([0, 1, 2, 3]);
///
/// assert_eq!(Some(3), v.pop());
/// assert_eq!(Some(2), v.pop());
/// assert_eq!(Some(1), v.pop());
/// assert_eq!(Some(0), v.pop());
/// assert_eq!(None, v.pop());
/// ```
pub const fn pop(&mut self) -> Option<T> {
if self.len == 0 {
None
} else {
self.len -= 1;
// Safety: MaybeUninit<T> implies ManuallyDrop<T>,
// therefore should not get dropped twice
// Some(unsafe { self.buf.get_unchecked_mut(self.len).assume_init_read() })
Some(unsafe { ptr::read(self.as_ptr().add(self.len).cast()) })
}
}
/// Removes and returns the element at the given index,
/// shifting other elements toward the start
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
///
/// assert_eq!(2, v.remove(2));
/// assert_eq!(&[0, 1, 3, 4], v.as_slice());
/// ```
pub fn remove(&mut self, index: usize) -> T {
if index >= self.len {
panic!("Index {index} exceeded length {}", self.len)
}
let len = self.len - 1;
let base = self.as_mut_ptr();
let out = unsafe { ptr::read(base.add(index)) };
unsafe { ptr::copy(base.add(index + 1), base.add(index), len - index) };
self.len = len;
out
}
/// Removes and returns the element at the given index,
/// swapping it with the last element.
///
/// # May Panic
///
/// Panics if `index >= len`.
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
///
/// assert_eq!(2, v.swap_remove(2));
///
/// assert_eq!(&[0, 1, 4, 3], v.as_slice());
/// ```
pub fn swap_remove(&mut self, index: usize) -> T {
if index >= self.len {
panic!("Index {index} exceeds length {}", self.len);
}
let len = self.len - 1;
let ptr = self.as_mut_ptr();
let out = unsafe { ptr::read(ptr.add(index)) };
unsafe { ptr::copy(ptr.add(len), ptr.add(index), 1) };
self.len = len;
out
}
/// Inserts an element at position `index` in the stack,
/// shifting all elements after it to the right.
///
/// # May Panic
///
/// Panics if `index > len` or [`self.is_full()`](Stack::is_full)
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
/// let mut v = Stack::from([0, 1, 2, 3, 4]).resize::<6>();
///
/// v.insert(3, 0xbeef);
/// assert_eq!(&[0, 1, 2, 0xbeef, 3, 4], v.as_slice());
/// ```
pub fn insert(&mut self, index: usize, data: T) {
if index > self.len {
panic!("Index {index} exceeded length {}", self.len)
}
if self.is_full() {
panic!("Attempted to insert into full stack")
}
unsafe { self.insert_unchecked(index, data) };
}
/// Attempts to insert an element at position `index` in the stack,
/// shifting all elements after it to the right.
///
/// If the stack is at capacity, returns the original element and an [InsertFailed] error.
///
/// # Examples
///
/// ```
/// use cl_structures::stack::Stack;
/// let mut v: Stack<_, 2> = Stack::new();
///
/// assert_eq!(Ok(()), v.try_insert(0, 0));
/// ```
pub const fn try_insert(&mut self, index: usize, data: T) -> Result<(), (T, InsertFailed<N>)> {
if index > self.len {
return Err((data, InsertFailed::Bounds(index)));
}
if self.is_full() {
return Err((data, InsertFailed::Full));
}
// Safety: index < self.len && !self.is_full()
unsafe { self.insert_unchecked(index, data) };
Ok(())
}
/// # Safety:
/// - index must be less than self.len
/// - length after insertion must be <= N
#[inline]
const unsafe fn insert_unchecked(&mut self, index: usize, data: T) {
let base = self.as_mut_ptr();
unsafe { ptr::copy(base.add(index), base.add(index + 1), self.len - index) }
self.len += 1;
self.buf[index] = MaybeUninit::new(data);
}
/// Clears the stack
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::Stack;
///
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
/// assert_eq!(v.as_slice(), &[0, 1, 2, 3, 4]);
///
/// v.clear();
/// assert_eq!(v.as_slice(), &[]);
/// ```
pub fn clear(&mut self) {
// Hopefully copy elision takes care of this lmao
while !self.is_empty() {
drop(self.pop());
}
}
/// Returns the number of elements in the stack
/// ```
/// # use cl_structures::stack::*;
/// let v = Stack::from([0, 1, 2, 3, 4]);
///
/// assert_eq!(5, v.len());
/// ```
pub const fn len(&self) -> usize {
self.len
}
/// Returns true if the stack is at (or over) capacity
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::*;
/// let v = Stack::from([(); 10]);
///
/// assert!(v.is_full());
/// ```
#[inline]
pub const fn is_full(&self) -> bool {
self.len >= N
}
/// Returns true if the stack contains no elements
///
/// # Examples
///
/// ```
/// # use cl_structures::stack::*;
/// let v: Stack<(), 10> = Stack::new();
///
/// assert!(v.is_empty());
/// ```
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InsertFailed<const N: usize> {
Bounds(usize),
Full,
}
impl<const N: usize> std::error::Error for InsertFailed<N> {}
impl<const N: usize> std::fmt::Display for InsertFailed<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InsertFailed::Bounds(idx) => write!(f, "Index {idx} exceeded length {N}"),
InsertFailed::Full => {
write!(f, "Attempt to insert into full stack (length {N})")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_sized() {
let v: Stack<(), { usize::MAX }> = Stack::new();
assert_eq!(usize::MAX, v.capacity());
assert_eq!(std::mem::size_of::<usize>(), std::mem::size_of_val(&v))
}
#[test]
fn from_usize_max_zst_array() {
let mut v = Stack::from([(); usize::MAX]);
assert_eq!(v.len(), usize::MAX);
v.pop();
assert_eq!(v.len(), usize::MAX - 1);
}
#[test]
fn new() {
let v: Stack<(), 255> = Stack::new();
assert_eq!(0, v.len());
assert_eq!(255, v.capacity());
}
#[test]
fn push() {
let mut v: Stack<_, 64> = Stack::new();
v.push(10);
}
#[test]
#[should_panic = "Attempted to push into full stack"]
fn push_overflow() {
let mut v = Stack::from([]);
v.push(10);
}
#[test]
fn pop() {
let mut v = Stack::from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(Some(9), v.pop());
assert_eq!(Some(8), v.pop());
assert_eq!(Some(7), v.pop());
assert_eq!(Some(6), v.pop());
assert_eq!(Some(5), v.pop());
assert_eq!(Some(4), v.pop());
assert_eq!(Some(3), v.pop());
assert_eq!(Some(2), v.pop());
assert_eq!(Some(1), v.pop());
assert_eq!(None, v.pop());
}
#[test]
fn resize_smaller() {
let v = Stack::from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
let mut v = v.resize::<2>();
assert_eq!(2, v.capacity());
assert_eq!(Some(2), v.pop());
assert_eq!(Some(1), v.pop());
assert_eq!(None, v.pop());
}
#[test]
fn resize_bigger() {
let v = Stack::from([1, 2, 3, 4]);
let mut v: Stack<_, 10> = v.resize();
assert_eq!(Some(4), v.pop());
assert_eq!(Some(3), v.pop());
assert_eq!(Some(2), v.pop());
assert_eq!(Some(1), v.pop());
assert_eq!(None, v.pop());
}
#[test]
fn dangle() {
let mut v: Stack<_, 2> = Stack::new();
let a = 0;
let b = 1;
v.push(&a);
v.push(&b);
println!("{v:?}");
}
#[test]
fn remove() {
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
assert_eq!(3, v.remove(3));
assert_eq!(4, v.remove(3));
assert_eq!(5, v.remove(3));
assert_eq!(Some(2), v.pop());
assert_eq!(Some(1), v.pop());
assert_eq!(Some(0), v.pop());
assert_eq!(None, v.pop());
}
#[test]
fn swap_remove() {
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
assert_eq!(3, v.swap_remove(3));
assert_eq!(&[0, 1, 2, 5, 4], v.as_slice());
}
#[test]
fn swap_remove_last() {
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
assert_eq!(5, v.swap_remove(5));
assert_eq!(&[0, 1, 2, 3, 4], v.as_slice())
}
#[test]
fn insert() {
let mut v = Stack::from([0, 1, 2, 4, 5, 0x41414141]);
v.pop();
v.insert(3, 3);
assert_eq!(&[0, 1, 2, 3, 4, 5], v.as_slice())
}
#[test]
#[should_panic = "Attempted to insert into full stack"]
fn insert_overflow() {
let mut v = Stack::from([0]);
v.insert(0, 1);
}
#[test]
fn drop() {
let v = Stack::from([
Box::new(0),
Box::new(1),
Box::new(2),
Box::new(3),
Box::new(4),
]);
std::mem::drop(std::hint::black_box(v));
}
#[test]
fn drop_zst() {
struct Droppable;
impl Drop for Droppable {
fn drop(&mut self) {
use std::sync::atomic::{AtomicU32, Ordering};
static V: AtomicU32 = AtomicU32::new(1);
eprintln!("{}", V.fetch_add(1, Ordering::Relaxed));
}
}
let v = Stack::from([const { Droppable }; 10]);
std::mem::drop(v);
}
}

View File

@@ -0,0 +1,221 @@
//! An insert-only unordered tree, backed by a [Vec]
//!
//! # Examples
//! ```
//! use cl_structures::tree::{Tree, Node};
//! // A tree can be created
//! let mut tree = Tree::new();
//! // Provided with a root node
//! let root = tree.root("This is the root node").unwrap();
//!
//! // Nodes can be accessed by indexing
//! assert_eq!(*tree[root].as_ref(), "This is the root node");
//! // Nodes' data can be accessed directly by calling `get`/`get_mut`
//! assert_eq!(tree.get(root).unwrap(), &"This is the root node")
//! ```
// TODO: implement an Entry-style API for doing traversal algorithms
pub use self::tree_ref::Ref;
use std::ops::{Index, IndexMut};
pub mod tree_ref;
/// An insert-only unordered tree, backed by a [Vec]
#[derive(Debug)]
pub struct Tree<T> {
nodes: Vec<Node<T>>,
}
impl<T> Default for Tree<T> {
fn default() -> Self {
Self { nodes: Default::default() }
}
}
/// Getters
impl<T> Tree<T> {
pub fn get(&self, index: Ref<T>) -> Option<&T> {
self.get_node(index).map(|node| &node.value)
}
pub fn get_mut(&mut self, index: Ref<T>) -> Option<&mut T> {
self.get_node_mut(index).map(|node| &mut node.value)
}
pub fn get_node(&self, index: Ref<T>) -> Option<&Node<T>> {
self.nodes.get(usize::from(index))
}
pub fn get_node_mut(&mut self, index: Ref<T>) -> Option<&mut Node<T>> {
self.nodes.get_mut(usize::from(index))
}
}
/// Tree operations
impl<T> Tree<T> {
pub fn new() -> Self {
Self { nodes: Default::default() }
}
/// Creates a new root for the tree.
///
/// If the tree already has a root, the value will be returned.
pub fn root(&mut self, value: T) -> Result<Ref<T>, T> {
if self.is_empty() {
// Create an index for the new node
let node = Ref::new_unchecked(self.nodes.len());
// add child to tree
self.nodes.push(Node::from(value));
Ok(node)
} else {
Err(value)
}
}
pub fn get_root(&mut self) -> Option<Ref<T>> {
match self.nodes.is_empty() {
true => None,
false => Some(Ref::new_unchecked(0)),
}
}
/// Insert a value into the tree as a child of the parent node
///
/// # Panics
/// May panic if the node [Ref] is from a different tree
pub fn insert(&mut self, value: T, parent: Ref<T>) -> Ref<T> {
let child = Ref::new_unchecked(self.nodes.len());
// add child to tree before parent
self.nodes.push(Node::with_parent(value, parent));
// add child to parent
self[parent].children.push(child);
child
}
/// Gets the depth of a node
///
/// # Panics
/// May panic if the node [Ref] is from a different tree
pub fn depth(&self, node: Ref<T>) -> usize {
match self[node].parent {
Some(node) => self.depth(node) + 1,
None => 0,
}
}
/// Gets the number of branches in the tree
pub fn branches(&self) -> usize {
self.nodes.iter().fold(0, |edges, node| edges + node.len())
}
}
/// Standard data structure functions
impl<T> Tree<T> {
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
}
impl<T> Index<Ref<T>> for Tree<T> {
type Output = Node<T>;
fn index(&self, index: Ref<T>) -> &Self::Output {
self.get_node(index).expect("Ref should be inside Tree")
}
}
impl<T> IndexMut<Ref<T>> for Tree<T> {
fn index_mut(&mut self, index: Ref<T>) -> &mut Self::Output {
self.get_node_mut(index).expect("Ref should be inside Tree")
}
}
/// A node in a [Tree]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Node<T> {
value: T,
/// The parent
parent: Option<Ref<T>>,
/// The children
children: Vec<Ref<T>>,
}
impl<T> Node<T> {
pub const fn new(value: T) -> Self {
Self { value, parent: None, children: vec![] }
}
pub const fn with_parent(value: T, parent: Ref<T>) -> Self {
Self { value, parent: Some(parent), children: vec![] }
}
pub fn get(&self) -> &T {
self.as_ref()
}
pub fn get_mut(&mut self) -> &mut T {
self.as_mut()
}
pub fn swap(&mut self, value: T) -> T {
std::mem::replace(&mut self.value, value)
}
pub fn parent(&self) -> Option<Ref<T>> {
self.parent
}
pub fn children(&self) -> &[Ref<T>] {
&self.children
}
pub fn len(&self) -> usize {
self.children.len()
}
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
}
impl<T> AsRef<T> for Node<T> {
fn as_ref(&self) -> &T {
&self.value
}
}
impl<T> AsMut<T> for Node<T> {
fn as_mut(&mut self) -> &mut T {
&mut self.value
}
}
impl<T> From<T> for Node<T> {
#[inline]
fn from(value: T) -> Self {
Self::new(value)
}
}
#[cfg(test)]
mod test {
#[allow(unused)]
use super::*;
#[test]
fn add_children() {
let mut tree = Tree::new();
let root = tree.root(0).unwrap();
let one = tree.insert(1, root);
let two = tree.insert(2, root);
assert_eq!([one, two].as_slice(), tree[root].children());
}
#[test]
fn nest_children() {
let mut tree = Tree::new();
let root = tree.root(0).unwrap();
let one = tree.insert(1, root);
let two = tree.insert(2, one);
assert_eq!(&[one], tree[root].children());
assert_eq!(&[two], tree[one].children());
assert_eq!(tree[two].children(), &[]);
}
#[test]
fn compares_equal() {}
}

View File

@@ -0,0 +1,70 @@
//! An element in a [Tree](super::Tree)
///
/// Contains a niche, and as such, [`Option<TreeRef<T>>`] is free :D
use std::{marker::PhantomData, num::NonZeroUsize};
/// An element of in a [Tree](super::Tree).
//? The index of the node is stored as a [NonZeroUsize] for space savings
//? Making Refs T-specific helps the user keep track of which Refs belong to which trees.
//? This isn't bulletproof, of course, but it'll keep Ref<Foo> from being used on Tree<Bar>
pub struct Ref<T: ?Sized>(NonZeroUsize, PhantomData<T>);
impl<T: ?Sized> Ref<T> {
/// Constructs a new [Ref] with the given index
pub fn new_unchecked(index: usize) -> Self {
// Safety: index cannot be zero because we use saturating addition on unsigned type.
Self(
unsafe { NonZeroUsize::new_unchecked(index.saturating_add(1)) },
PhantomData,
)
}
}
impl<T: ?Sized> From<Ref<T>> for usize {
fn from(value: Ref<T>) -> Self {
usize::from(value.0) - 1
}
}
/* --- implementations of derivable traits, because we don't need bounds here --- */
impl<T: ?Sized> std::fmt::Debug for Ref<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TreeRef").field(&self.0).finish()
}
}
impl<T: ?Sized> std::hash::Hash for Ref<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
self.1.hash(state);
}
}
impl<T: ?Sized> PartialEq for Ref<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0 && self.1 == other.1
}
}
impl<T: ?Sized> Eq for Ref<T> {}
impl<T: ?Sized> PartialOrd for Ref<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T: ?Sized> Ord for Ref<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T: ?Sized> Clone for Ref<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized> Copy for Ref<T> {}

View File

@@ -0,0 +1,10 @@
[package]
name = "cl-token"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]

View File

@@ -0,0 +1,13 @@
//! # Token
//!
//! Stores a component of a file as a [TokenKind], some [TokenData], and a line and column number
#![warn(clippy::all)]
#![feature(decl_macro)]
pub mod token;
pub mod token_data;
pub mod token_type;
pub use token::Token;
pub use token_data::TokenData;
pub use token_type::TokenKind;

View File

@@ -0,0 +1,42 @@
//! A [Token] contains a single unit of lexical information, and an optional bit of [TokenData]
use super::{TokenData, TokenKind};
/// Contains a single unit of lexical information,
/// and an optional bit of [TokenData]
#[derive(Clone, Debug, PartialEq)]
pub struct Token {
pub ty: TokenKind,
pub data: TokenData,
pub line: u32,
pub col: u32,
}
impl Token {
/// Creates a new [Token] out of a [TokenKind], [TokenData], line, and column.
pub fn new(ty: TokenKind, data: impl Into<TokenData>, line: u32, col: u32) -> Self {
Self { ty, data: data.into(), line, col }
}
/// Casts this token to a new [TokenKind]
pub fn cast(self, ty: TokenKind) -> Self {
Self { ty, ..self }
}
/// Returns the [TokenKind] of this token
pub fn ty(&self) -> TokenKind {
self.ty
}
/// Returns a reference to this token's [TokenData]
pub fn data(&self) -> &TokenData {
&self.data
}
/// Converts this token into its inner [TokenData]
pub fn into_data(self) -> TokenData {
self.data
}
/// Returns the line where this token originated
pub fn line(&self) -> u32 {
self.line
}
/// Returns the column where this token originated
pub fn col(&self) -> u32 {
self.col
}
}

View File

@@ -1,11 +1,9 @@
//! Additional data stored within a [Token](super::Token),
//! external to its [Type](super::token_type::Type)
//! external to its [TokenKind](super::token_type::TokenKind)
/// Additional data stored within a [Token](super::Token),
/// external to its [Type](super::token_type::Type)
/// external to its [TokenKind](super::token_type::TokenKind)
#[derive(Clone, Debug, PartialEq)]
pub enum Data {
/// [Token](super::Token) contains an [identifier](str)
Identifier(Box<str>),
pub enum TokenData {
/// [Token](super::Token) contains a [String]
String(String),
/// [Token](super::Token) contains a [character](char)
@@ -18,7 +16,6 @@ pub enum Data {
None,
}
from! {
value: &str => Self::Identifier(value.into()),
value: String => Self::String(value),
value: u128 => Self::Integer(value),
value: f64 => Self::Float(value),
@@ -27,19 +24,18 @@ from! {
}
/// Implements [From] for an enum
macro from($($value:ident: $src:ty => $dst:expr),*$(,)?) {
$(impl From<$src> for Data {
$(impl From<$src> for TokenData {
fn from($value: $src) -> Self { $dst }
})*
}
impl std::fmt::Display for Data {
impl std::fmt::Display for TokenData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Data::Identifier(v) => v.fmt(f),
Data::String(v) => write!(f, "\"{v}\""),
Data::Character(v) => write!(f, "'{v}'"),
Data::Integer(v) => v.fmt(f),
Data::Float(v) => v.fmt(f),
Data::None => "None".fmt(f),
TokenData::String(v) => write!(f, "\"{v}\""),
TokenData::Character(v) => write!(f, "'{v}'"),
TokenData::Integer(v) => v.fmt(f),
TokenData::Float(v) => v.fmt(f),
TokenData::None => "None".fmt(f),
}
}
}

View File

@@ -0,0 +1,232 @@
//! Stores a [Token's](super::Token) lexical information
use std::{fmt::Display, str::FromStr};
/// Stores a [Token's](super::Token) lexical information
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TokenKind {
/// Invalid sequence
Invalid,
/// Any kind of comment
Comment,
/// Any tokenizable literal (See [TokenData](super::TokenData))
Literal,
/// A non-keyword identifier
Identifier,
// A keyword
As, // as
Break, // "break"
Cl, // "cl"
Const, // "const"
Continue, // "continue"
Else, // "else"
Enum, // "enum"
False, // "false"
Fn, // "fn"
For, // "for"
If, // "if"
Impl, // "impl"
In, // "in"
Let, // "let"
Loop, // "loop"
Match, // "match"
Mod, // "mod"
Mut, // "mut"
Pub, // "pub"
Return, // "return"
SelfTy, // "Self"
Static, // "static"
Struct, // "struct"
Super, // "super"
True, // "true"
Type, // "type"
Use, // "use"
While, // "while"
// Delimiter or punctuation
LCurly, // {
RCurly, // }
LBrack, // [
RBrack, // ]
LParen, // (
RParen, // )
Amp, // &
AmpAmp, // &&
AmpEq, // &=
Arrow, // ->
At, // @
Backslash, // \
Bang, // !
BangBang, // !!
BangEq, // !=
Bar, // |
BarBar, // ||
BarEq, // |=
Colon, // :
ColonColon, // ::
Comma, // ,
Dot, // .
DotDot, // ..
DotDotEq, // ..=
Eq, // =
EqEq, // ==
FatArrow, // =>
Grave, // `
Gt, // >
GtEq, // >=
GtGt, // >>
GtGtEq, // >>=
Hash, // #
HashBang, // #!
Lt, // <
LtEq, // <=
LtLt, // <<
LtLtEq, // <<=
Minus, // -
MinusEq, // -=
Plus, // +
PlusEq, // +=
Question, // ?
Rem, // %
RemEq, // %=
Semi, // ;
Slash, // /
SlashEq, // /=
Star, // *
StarEq, // *=
Tilde, // ~
Xor, // ^
XorEq, // ^=
XorXor, // ^^
}
impl Display for TokenKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TokenKind::Invalid => "invalid".fmt(f),
TokenKind::Comment => "comment".fmt(f),
TokenKind::Literal => "literal".fmt(f),
TokenKind::Identifier => "identifier".fmt(f),
TokenKind::As => "as".fmt(f),
TokenKind::Break => "break".fmt(f),
TokenKind::Cl => "cl".fmt(f),
TokenKind::Const => "const".fmt(f),
TokenKind::Continue => "continue".fmt(f),
TokenKind::Else => "else".fmt(f),
TokenKind::Enum => "enum".fmt(f),
TokenKind::False => "false".fmt(f),
TokenKind::Fn => "fn".fmt(f),
TokenKind::For => "for".fmt(f),
TokenKind::If => "if".fmt(f),
TokenKind::Impl => "impl".fmt(f),
TokenKind::In => "in".fmt(f),
TokenKind::Let => "let".fmt(f),
TokenKind::Loop => "loop".fmt(f),
TokenKind::Match => "match".fmt(f),
TokenKind::Mod => "mod".fmt(f),
TokenKind::Mut => "mut".fmt(f),
TokenKind::Pub => "pub".fmt(f),
TokenKind::Return => "return".fmt(f),
TokenKind::SelfTy => "Self".fmt(f),
TokenKind::Static => "static".fmt(f),
TokenKind::Struct => "struct".fmt(f),
TokenKind::Super => "super".fmt(f),
TokenKind::True => "true".fmt(f),
TokenKind::Type => "type".fmt(f),
TokenKind::Use => "use".fmt(f),
TokenKind::While => "while".fmt(f),
TokenKind::LCurly => "{".fmt(f),
TokenKind::RCurly => "}".fmt(f),
TokenKind::LBrack => "[".fmt(f),
TokenKind::RBrack => "]".fmt(f),
TokenKind::LParen => "(".fmt(f),
TokenKind::RParen => ")".fmt(f),
TokenKind::Amp => "&".fmt(f),
TokenKind::AmpAmp => "&&".fmt(f),
TokenKind::AmpEq => "&=".fmt(f),
TokenKind::Arrow => "->".fmt(f),
TokenKind::At => "@".fmt(f),
TokenKind::Backslash => "\\".fmt(f),
TokenKind::Bang => "!".fmt(f),
TokenKind::BangBang => "!!".fmt(f),
TokenKind::BangEq => "!=".fmt(f),
TokenKind::Bar => "|".fmt(f),
TokenKind::BarBar => "||".fmt(f),
TokenKind::BarEq => "|=".fmt(f),
TokenKind::Colon => ":".fmt(f),
TokenKind::ColonColon => "::".fmt(f),
TokenKind::Comma => ",".fmt(f),
TokenKind::Dot => ".".fmt(f),
TokenKind::DotDot => "..".fmt(f),
TokenKind::DotDotEq => "..=".fmt(f),
TokenKind::Eq => "=".fmt(f),
TokenKind::EqEq => "==".fmt(f),
TokenKind::FatArrow => "=>".fmt(f),
TokenKind::Grave => "`".fmt(f),
TokenKind::Gt => ">".fmt(f),
TokenKind::GtEq => ">=".fmt(f),
TokenKind::GtGt => ">>".fmt(f),
TokenKind::GtGtEq => ">>=".fmt(f),
TokenKind::Hash => "#".fmt(f),
TokenKind::HashBang => "#!".fmt(f),
TokenKind::Lt => "<".fmt(f),
TokenKind::LtEq => "<=".fmt(f),
TokenKind::LtLt => "<<".fmt(f),
TokenKind::LtLtEq => "<<=".fmt(f),
TokenKind::Minus => "-".fmt(f),
TokenKind::MinusEq => "-=".fmt(f),
TokenKind::Plus => "+".fmt(f),
TokenKind::PlusEq => "+=".fmt(f),
TokenKind::Question => "?".fmt(f),
TokenKind::Rem => "%".fmt(f),
TokenKind::RemEq => "%=".fmt(f),
TokenKind::Semi => ";".fmt(f),
TokenKind::Slash => "/".fmt(f),
TokenKind::SlashEq => "/=".fmt(f),
TokenKind::Star => "*".fmt(f),
TokenKind::StarEq => "*=".fmt(f),
TokenKind::Tilde => "~".fmt(f),
TokenKind::Xor => "^".fmt(f),
TokenKind::XorEq => "^=".fmt(f),
TokenKind::XorXor => "^^".fmt(f),
}
}
}
impl FromStr for TokenKind {
/// [FromStr] can only fail when an identifier isn't a keyword
type Err = ();
/// Parses a string s to return a Keyword
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"as" => Self::As,
"break" => Self::Break,
"cl" => Self::Cl,
"const" => Self::Const,
"continue" => Self::Continue,
"else" => Self::Else,
"enum" => Self::Enum,
"false" => Self::False,
"fn" => Self::Fn,
"for" => Self::For,
"if" => Self::If,
"impl" => Self::Impl,
"in" => Self::In,
"let" => Self::Let,
"loop" => Self::Loop,
"match" => Self::Match,
"mod" => Self::Mod,
"mut" => Self::Mut,
"pub" => Self::Pub,
"return" => Self::Return,
"Self" => Self::SelfTy,
"static" => Self::Static,
"struct" => Self::Struct,
"super" => Self::Super,
"true" => Self::True,
"type" => Self::Type,
"use" => Self::Use,
"while" => Self::While,
_ => Err(())?,
})
}
}

View File

@@ -0,0 +1,17 @@
[package]
name = "cl-typeck"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
cl-ast = { path = "../cl-ast" }
cl-structures = { path = "../cl-structures" }
[dev-dependencies]
repline = { version = "*", registry = "soft-fish" }
cl-lexer = { path = "../cl-lexer" }
cl-parser = { path = "../cl-parser" }

View File

@@ -0,0 +1,462 @@
use cl_typeck::{
entry::Entry,
stage::{
infer::{engine::InferenceEngine, error::InferenceError, inference::Inference},
*,
},
table::Table,
type_expression::TypeExpression,
};
use cl_ast::{
Expr, Path, Stmt, Ty,
ast_visitor::{Fold, Visit},
desugar::*,
};
use cl_lexer::Lexer;
use cl_parser::{Parser, inliner::ModuleInliner};
use cl_structures::intern::string_interner::StringInterner;
use repline::{error::Error as RlError, prebaked::*};
use std::{
error::Error,
path::{self, PathBuf},
sync::LazyLock,
};
// Path to display in standard library errors
const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
// Statically included standard library
const PREAMBLE: &str = r"
pub mod std;
pub use std::preamble::*;
";
// Colors
const C_MAIN: &str = C_LISTING;
const C_RESV: &str = "\x1b[35m";
const C_CODE: &str = "\x1b[36m";
const C_BYID: &str = "\x1b[95m";
const C_ERROR: &str = "\x1b[31m";
const C_LISTING: &str = "\x1b[38;5;117m";
fn main() -> Result<(), Box<dyn Error>> {
let mut prj = Table::default();
let mut parser = Parser::new("PREAMBLE", Lexer::new(PREAMBLE));
let code = match parser.parse() {
Ok(code) => code,
Err(e) => {
eprintln!("{STDLIB_DISPLAY_PATH}:{e}");
Err(e)?
}
};
// This code is special - it gets loaded from a hard-coded project directory (for now)
let code = inline_modules(code, concat!(env!("CARGO_MANIFEST_DIR"), "/../../stdlib"));
let code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
Populator::new(&mut prj).visit_file(interned(code));
for arg in std::env::args().skip(1) {
import_file(&mut prj, arg)?;
}
resolve_all(&mut prj)?;
main_menu(&mut prj)?;
Ok(())
}
fn main_menu(prj: &mut Table) -> Result<(), RlError> {
banner();
read_and(C_MAIN, "mu>", "? >", |line| {
for line in line.trim().split_ascii_whitespace() {
match line {
"c" | "code" => enter_code(prj)?,
"clear" => clear()?,
"dump" => dump(prj)?,
"d" | "desugar" => live_desugar()?,
"e" | "exit" => return Ok(Response::Break),
"f" | "file" => import_files(prj)?,
"i" | "id" => get_by_id(prj)?,
"l" | "list" => list_types(prj),
"q" | "query" => query_type_expression(prj)?,
"r" | "resolve" => resolve_all(prj)?,
"s" | "strings" => print_strings(),
"a" | "all" => infer_all(prj)?,
"t" | "test" => infer_expression(prj)?,
"h" | "help" | "" => {
println!(
"Valid commands are:
clear : Clear the screen
code (c): Enter code to type-check
desugar (d): WIP: Test the experimental desugaring passes
file (f): Load files from disk
id (i): Get a type by its type ID
list (l): List all known types
query (q): Query the type system
resolve (r): Perform type resolution
help (h): Print this list
exit (e): Exit the program"
);
return Ok(Response::Deny);
}
_ => Err(r#"Invalid command. Type "help" to see the list of valid commands."#)?,
}
}
Ok(Response::Accept)
})
}
fn enter_code(prj: &mut Table) -> Result<(), RlError> {
read_and(C_CODE, "cl>", "? >", |line| {
if line.trim().is_empty() {
return Ok(Response::Break);
}
let code = Parser::new("", Lexer::new(line)).parse()?;
let code = inline_modules(code, "");
let code = WhileElseDesugar.fold_file(code);
Populator::new(prj).visit_file(interned(code));
Ok(Response::Accept)
})
}
fn live_desugar() -> Result<(), RlError> {
read_and(C_RESV, "se>", "? >", |line| {
let code = Parser::new("", Lexer::new(line)).parse::<Stmt>()?;
println!("Raw, as parsed:\n{C_LISTING}{code}\x1b[0m");
let code = ConstantFolder.fold_stmt(code);
println!("ConstantFolder\n{C_LISTING}{code}\x1b[0m");
let code = SquashGroups.fold_stmt(code);
println!("SquashGroups\n{C_LISTING}{code}\x1b[0m");
let code = WhileElseDesugar.fold_stmt(code);
println!("WhileElseDesugar\n{C_LISTING}{code}\x1b[0m");
let code = NormalizePaths::new().fold_stmt(code);
println!("NormalizePaths\n{C_LISTING}{code}\x1b[0m");
Ok(Response::Accept)
})
}
fn print_strings() {
println!("{}", StringInterner::global());
}
fn query_type_expression(prj: &mut Table) -> Result<(), RlError> {
read_and(C_RESV, "ty>", "? >", |line| {
if line.trim().is_empty() {
return Ok(Response::Break);
}
// A query is comprised of a Ty and a relative Path
let mut p = Parser::new("", Lexer::new(line));
let ty: Ty = p.parse()?;
let path: Path = p
.parse()
.map(|p| Path { absolute: false, ..p })
.unwrap_or_default();
let id = ty.evaluate(prj, prj.root())?;
let id = path.evaluate(prj, id)?;
pretty_handle(id.to_entry(prj))?;
Ok(Response::Accept)
})
}
#[allow(dead_code)]
fn infer_expression(prj: &mut Table) -> Result<(), RlError> {
read_and(C_RESV, "ex>", "!?>", |line| {
if line.trim().is_empty() {
return Ok(Response::Break);
}
let mut p = Parser::new("", Lexer::new(line));
let e: Expr = p.parse()?;
let mut inf = InferenceEngine::new(prj, prj.root());
let ty = match exp_terned(e).infer(&mut inf) {
Ok(ty) => ty,
Err(e) => match e {
InferenceError::Mismatch(a, b) => {
eprintln!("Mismatched types: {}, {}", prj.entry(a), prj.entry(b));
return Ok(Response::Deny);
}
InferenceError::Recursive(a, b) => {
eprintln!("Recursive types: {}, {}", prj.entry(a), prj.entry(b));
return Ok(Response::Deny);
}
e => Err(e)?,
},
};
eprintln!("--> {}", prj.entry(ty));
Ok(Response::Accept)
})
}
fn get_by_id(prj: &mut Table) -> Result<(), RlError> {
use cl_parser::parser::Parse;
use cl_structures::index_map::MapIndex;
use cl_typeck::handle::Handle;
read_and(C_BYID, "id>", "? >", |line| {
if line.trim().is_empty() {
return Ok(Response::Break);
}
let mut parser = Parser::new("", Lexer::new(line));
let def_id = match Parse::parse(&mut parser)? {
cl_ast::Literal::Int(int) => int as _,
other => Err(format!("Expected integer, got {other}"))?,
};
let mut path = parser.parse::<cl_ast::Path>().unwrap_or_default();
path.absolute = false;
let handle = Handle::from_usize(def_id).to_entry(prj);
print!(" > {{{C_LISTING}{handle}\x1b[0m}}");
if !path.parts.is_empty() {
print!("::{path}")
}
println!();
let Some(entry) = handle.nav(&path.parts) else {
Err("No results.")?
};
pretty_handle(entry)?;
Ok(Response::Accept)
})
}
fn resolve_all(table: &mut Table) -> Result<(), Box<dyn Error>> {
for (id, error) in import(table) {
eprintln!("{error} in {} ({id})", id.to_entry(table))
}
for handle in table.handle_iter() {
if let Err(error) = handle.to_entry_mut(table).categorize() {
eprintln!("{error}");
}
}
for handle in implement(table) {
eprintln!("Unable to reparent {} ({handle})", handle.to_entry(table))
}
println!("...Resolved!");
Ok(())
}
fn infer_all(table: &mut Table) -> Result<(), Box<dyn Error>> {
for (id, error) in InferenceEngine::new(table, table.root()).infer_all() {
match error {
InferenceError::Mismatch(a, b) => {
eprint!("Mismatched types: {}, {}", table.entry(a), table.entry(b));
}
InferenceError::Recursive(a, b) => {
eprint!("Recursive types: {}, {}", table.entry(a), table.entry(b));
}
e => eprint!("{e}"),
}
eprintln!(" in {id}\n({})\n", id.to_entry(table).source().unwrap())
}
println!("...Inferred!");
Ok(())
}
fn list_types(table: &mut Table) {
for handle in table.debug_entry_iter() {
let id = handle.id();
let kind = handle.kind().unwrap();
let name = handle.name().unwrap_or("".into());
println!("{id:3}: {name:16}| {kind}: {handle}");
}
}
fn import_file(table: &mut Table, path: impl AsRef<std::path::Path>) -> Result<(), Box<dyn Error>> {
let Ok(file) = std::fs::read_to_string(path.as_ref()) else {
for file in std::fs::read_dir(path)? {
println!("{}", file?.path().display())
}
return Ok(());
};
let mut parser = Parser::new("", Lexer::new(&file));
let code = match parser.parse() {
Ok(code) => inline_modules(
code,
PathBuf::from(path.as_ref()).parent().unwrap_or("".as_ref()),
),
Err(e) => {
eprintln!("{C_ERROR}{}:{e}\x1b[0m", path.as_ref().display());
return Ok(());
}
};
let code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
Populator::new(table).visit_file(interned(code));
Ok(())
}
fn import_files(table: &mut Table) -> Result<(), RlError> {
read_and(C_RESV, "fi>", "? >", |line| {
let line = line.trim();
if line.is_empty() {
return Ok(Response::Break);
}
let Ok(file) = std::fs::read_to_string(line) else {
for file in std::fs::read_dir(line)? {
println!("{}", file?.path().display())
}
return Ok(Response::Accept);
};
let mut parser = Parser::new("", Lexer::new(&file));
let code = match parser.parse() {
Ok(code) => inline_modules(code, PathBuf::from(line).parent().unwrap_or("".as_ref())),
Err(e) => {
eprintln!("{C_ERROR}{line}:{e}\x1b[0m");
return Ok(Response::Deny);
}
};
Populator::new(table).visit_file(interned(code));
println!("...Imported!");
Ok(Response::Accept)
})
}
fn pretty_handle(entry: Entry) -> Result<(), std::io::Error> {
use std::io::Write;
let mut out = std::io::stdout().lock();
let Some(kind) = entry.kind() else {
return writeln!(out, "{entry}");
};
write!(out, "{C_LISTING}{kind}")?;
if let Some(name) = entry.name() {
write!(out, " {name}")?;
}
writeln!(out, "\x1b[0m ({}): {entry}", entry.id())?;
if let Some(parent) = entry.parent() {
writeln!(
out,
"- {C_LISTING}Parent\x1b[0m: {parent} ({})",
parent.id()
)?;
}
if let Some(span) = entry.span() {
writeln!(
out,
"- {C_LISTING}Span:\x1b[0m ({}, {})",
span.head, span.tail
)?;
}
match entry.meta() {
Some(meta) if !meta.is_empty() => {
writeln!(out, "- {C_LISTING}Meta:\x1b[0m")?;
for meta in meta {
writeln!(out, " - {meta}")?;
}
}
_ => {}
}
if let Some(children) = entry.children() {
writeln!(out, "- {C_LISTING}Children:\x1b[0m")?;
for (name, child) in children {
writeln!(
out,
" - {C_LISTING}{name}\x1b[0m ({child}): {}",
entry.with_id(*child)
)?
}
}
if let Some(imports) = entry.imports() {
writeln!(out, "- {C_LISTING}Imports:\x1b[0m")?;
for (name, child) in imports {
writeln!(
out,
" - {C_LISTING}{name}\x1b[0m ({child}): {}",
entry.with_id(*child)
)?
}
}
Ok(())
}
fn inline_modules(code: cl_ast::File, path: impl AsRef<path::Path>) -> cl_ast::File {
match ModuleInliner::new(path).inline(code) {
Err((code, io, parse)) => {
for (file, error) in io {
eprintln!("{}:{error}", file.display());
}
for (file, error) in parse {
eprintln!("{}:{error}", file.display());
}
code
}
Ok(code) => code,
}
}
fn dump(table: &Table) -> Result<(), Box<dyn Error>> {
fn dump_recursive(
name: cl_ast::Sym,
entry: Entry,
depth: usize,
to_file: &mut std::fs::File,
) -> std::io::Result<()> {
use std::io::Write;
write!(to_file, "{:w$}{name}: {entry}", "", w = depth)?;
if let Some(children) = entry.children() {
writeln!(to_file, " {{")?;
for (name, child) in children {
dump_recursive(*name, entry.with_id(*child), depth + 2, to_file)?;
}
write!(to_file, "{:w$}}}", "", w = depth)?;
}
writeln!(to_file)
}
let mut file = std::fs::File::create("typeck-table.ron")?;
dump_recursive("root".into(), table.root_entry(), 0, &mut file)?;
Ok(())
}
fn clear() -> Result<(), Box<dyn Error>> {
println!("\x1b[H\x1b[2J");
banner();
Ok(())
}
fn banner() {
println!(
"--- {} v{} 💪🦈 ---",
env!("CARGO_BIN_NAME"),
env!("CARGO_PKG_VERSION"),
);
}
/// Interns a [File](cl_ast::File), returning a static reference to it.
fn interned(file: cl_ast::File) -> &'static cl_ast::File {
use cl_structures::intern::typed_interner::TypedInterner;
static INTERNER: LazyLock<TypedInterner<'static, cl_ast::File>> =
LazyLock::new(Default::default);
INTERNER.get_or_insert(file).to_ref()
}
/// Interns an [Expr](cl_ast::Expr), returning a static reference to it.
fn exp_terned(expr: cl_ast::Expr) -> &'static cl_ast::Expr {
use cl_structures::intern::typed_interner::TypedInterner;
static INTERNER: LazyLock<TypedInterner<'static, cl_ast::Expr>> =
LazyLock::new(Default::default);
INTERNER.get_or_insert(expr).to_ref()
}

View File

@@ -0,0 +1,212 @@
//! An [Entry] is an accessor for [nodes](Handle) in a [Table].
//!
//! There are two kinds of entry:
//! - [Entry]: Provides getters for an entry's fields, and an implementation of
//! [Display](std::fmt::Display)
//! - [EntryMut]: Provides setters for an entry's fields, and an [`as_ref`](EntryMut::as_ref) method
//! to demote to an [Entry].
use std::collections::HashMap;
use cl_ast::{Expr, Meta, PathPart, Sym};
use cl_structures::span::Span;
use crate::{
handle::Handle,
source::Source,
stage::categorize as cat,
table::{NodeKind, Table},
type_expression::{self as tex, TypeExpression},
type_kind::TypeKind,
};
mod debug;
mod display;
impl Handle {
pub const fn to_entry<'t, 'a>(self, table: &'t Table<'a>) -> Entry<'t, 'a> {
Entry { id: self, table }
}
pub fn to_entry_mut<'t, 'a>(self, table: &'t mut Table<'a>) -> EntryMut<'t, 'a> {
EntryMut { id: self, table }
}
}
pub struct Entry<'t, 'a> {
table: &'t Table<'a>,
id: Handle,
}
macro_rules! impl_entry_ {
() => {
pub const fn id(&self) -> Handle {
self.id
}
pub const fn inner(&'t self) -> &'t Table<'a> {
self.table
}
pub fn kind(&self) -> Option<&NodeKind> {
self.table.kind(self.id)
}
pub const fn root(&self) -> Handle {
self.table.root()
}
pub fn children(&self) -> Option<&HashMap<Sym, Handle>> {
self.table.children(self.id)
}
pub fn imports(&self) -> Option<&HashMap<Sym, Handle>> {
self.table.imports(self.id)
}
pub fn bodies(&self) -> Option<&'a Expr> {
self.table.body(self.id)
}
pub fn span(&self) -> Option<&Span> {
self.table.span(self.id)
}
pub fn meta(&self) -> Option<&[Meta]> {
self.table.meta(self.id)
}
pub fn source(&self) -> Option<&Source<'a>> {
self.table.source(self.id)
}
pub fn name(&self) -> Option<Sym> {
self.table.name(self.id)
}
};
}
impl<'t, 'a> Entry<'t, 'a> {
pub const fn new(table: &'t Table<'a>, id: Handle) -> Self {
Self { table, id }
}
impl_entry_!();
pub const fn with_id(&self, id: Handle) -> Entry<'t, 'a> {
Self { table: self.table, id }
}
pub fn nav(&self, path: &[PathPart]) -> Option<Entry<'t, 'a>> {
Some(Entry { id: self.table.nav(self.id, path)?, table: self.table })
}
pub fn parent(&self) -> Option<Entry<'t, 'a>> {
Some(Entry { id: *self.table.parent(self.id)?, ..*self })
}
pub fn ty(&self) -> Option<&'t TypeKind> {
self.table.ty(self.id)
}
pub fn impl_target(&self) -> Option<Entry<'_, 'a>> {
Some(Entry { id: self.table.impl_target(self.id)?, ..*self })
}
pub fn selfty(&self) -> Option<Entry<'_, 'a>> {
Some(Entry { id: self.table.selfty(self.id)?, ..*self })
}
}
#[derive(Debug)]
pub struct EntryMut<'t, 'a> {
table: &'t mut Table<'a>,
id: Handle,
}
impl<'t, 'a> EntryMut<'t, 'a> {
pub fn new(table: &'t mut Table<'a>, id: Handle) -> Self {
Self { table, id }
}
impl_entry_!();
pub fn ty(&self) -> Option<&TypeKind> {
self.table.ty(self.id)
}
pub fn inner_mut(&mut self) -> &mut Table<'a> {
self.table
}
pub fn as_ref(&self) -> Entry<'_, 'a> {
Entry { table: self.table, id: self.id }
}
/// Evaluates a [TypeExpression] in this entry's context
pub fn evaluate<Out>(&mut self, ty: &impl TypeExpression<Out>) -> Result<Out, tex::Error> {
let Self { table, id } = self;
ty.evaluate(table, *id)
}
pub fn categorize(&mut self) -> Result<(), cat::Error> {
cat::categorize(self.table, self.id)
}
/// Constructs a new Handle with the provided parent [Handle]
pub fn with_id(&mut self, parent: Handle) -> EntryMut<'_, 'a> {
EntryMut { table: self.table, id: parent }
}
pub fn nav(&mut self, path: &[PathPart]) -> Option<EntryMut<'_, 'a>> {
Some(EntryMut { id: self.table.nav(self.id, path)?, table: self.table })
}
pub fn new_entry(&mut self, kind: NodeKind) -> EntryMut<'_, 'a> {
let id = self.table.new_entry(self.id, kind);
self.with_id(id)
}
pub fn add_child(&mut self, name: Sym, child: Handle) -> Option<Handle> {
self.table.add_child(self.id, name, child)
}
pub fn set_body(&mut self, body: &'a Expr) -> Option<&'a Expr> {
self.table.set_body(self.id, body)
}
pub fn set_ty(&mut self, kind: TypeKind) -> Option<TypeKind> {
self.table.set_ty(self.id, kind)
}
pub fn set_span(&mut self, span: Span) -> Option<Span> {
self.table.set_span(self.id, span)
}
pub fn set_meta(&mut self, meta: &'a [Meta]) -> Option<&'a [Meta]> {
self.table.set_meta(self.id, meta)
}
pub fn set_source(&mut self, source: Source<'a>) -> Option<Source<'a>> {
self.table.set_source(self.id, source)
}
pub fn set_impl_target(&mut self, target: Handle) -> Option<Handle> {
self.table.set_impl_target(self.id, target)
}
pub fn mark_unchecked(&mut self) {
self.table.mark_unchecked(self.id)
}
pub fn mark_use_item(&mut self) {
self.table.mark_use_item(self.id)
}
pub fn mark_impl_item(&mut self) {
self.table.mark_impl_item(self.id)
}
pub fn mark_lang_item(&mut self, lang_item: &'static str) {
self.table.mark_lang_item(lang_item, self.id)
}
}

View File

@@ -0,0 +1,33 @@
//! [std::fmt::Debug] implementation for [Entry]
use super::Entry;
impl std::fmt::Debug for Entry<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// virtual fields
let mut ds = f.debug_struct("Entry");
if let Some(name) = self.name() {
ds.field("name", &name.to_ref());
}
ds.field("kind", &self.kind());
if let Some(ty) = self.ty() {
ds.field("type", ty);
}
if let Some(meta) = self.meta() {
ds.field("meta", &meta);
}
if let Some(body) = self.bodies() {
ds.field("body", body);
}
if let Some(children) = self.children() {
ds.field("children", children);
}
if let Some(imports) = self.imports() {
ds.field("imports", imports);
}
// if let Some(source) = self.source() {
// ds.field("source", source);
// }
ds.field("implements", &self.impl_target()).finish()
}
}

View File

@@ -0,0 +1,100 @@
use super::*;
use crate::{format_utils::*, type_kind::Adt};
use std::fmt::{self, Write};
/// Printing the name of a named type stops infinite recursion
fn write_name_or(h: Entry, f: &mut impl Write) -> fmt::Result {
match h.name() {
Some(name) => write!(f, "{name}"),
None => write!(f, "{h}"),
}
}
impl fmt::Display for Entry<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(&kind) = self.kind() else {
return write!(f, "<invalid type: {}>", self.id);
};
if let Some(ty) = self.ty() {
match ty {
TypeKind::Inferred => write!(f, "<_{}>", self.id),
TypeKind::Variable => write!(f, "<?{}>", self.id),
TypeKind::Instance(id) => write!(f, "{}", self.with_id(*id)),
TypeKind::Primitive(kind) => write!(f, "{kind}"),
TypeKind::Adt(adt) => write_adt(adt, self, f),
&TypeKind::Ref(id) => {
f.write_str("&")?;
let h_id = self.with_id(id);
write_name_or(h_id, f)
}
&TypeKind::Ptr(id) => {
f.write_str("*")?;
let h_id = self.with_id(id);
write_name_or(h_id, f)
}
TypeKind::Slice(id) => {
write_name_or(self.with_id(*id), &mut f.delimit_with("[", "]"))
}
&TypeKind::Array(t, cnt) => {
let mut f = f.delimit_with("[", "]");
write_name_or(self.with_id(t), &mut f)?;
write!(f, "; {cnt}")
}
TypeKind::Tuple(ids) => {
let mut f = f.delimit_with("(", ")");
for (index, &id) in ids.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write_name_or(self.with_id(id), &mut f)?;
}
Ok(())
}
TypeKind::FnSig { args, rety } => {
write!(f, "fn {} -> ", self.with_id(*args))?;
write_name_or(self.with_id(*rety), f)
}
TypeKind::Module => write!(f, "module?"),
}
} else {
match kind {
NodeKind::Type
| NodeKind::Const
| NodeKind::Static
| NodeKind::Temporary
| NodeKind::Let => write!(f, "WARNING: NO TYPE ASSIGNED FOR {}", self.id),
_ => write!(f, "{kind}"),
}
}
}
}
fn write_adt(adt: &Adt, h: &Entry, f: &mut impl Write) -> fmt::Result {
match adt {
Adt::Enum(variants) => {
let mut variants = variants.iter();
separate(", ", || {
variants.next().map(|(name, def)| {
move |f: &mut Delimit<_>| write!(f, "{name}: {}", h.with_id(*def))
})
})(f.delimit_with("enum {", "}"))
}
Adt::Struct(members) => {
let mut members = members.iter();
separate(", ", || {
let (name, vis, id) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{vis}{name}: {}", h.with_id(*id)))
})(f.delimit_with("struct {", "}"))
}
Adt::TupleStruct(members) => {
let mut members = members.iter();
separate(", ", || {
let (vis, def) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{vis}{}", h.with_id(*def)))
})(f.delimit_with("struct (", ")"))
}
Adt::UnitStruct => write!(f, "struct"),
Adt::Union(_) => todo!("Display union types"),
}
}

View File

@@ -0,0 +1,20 @@
pub use cl_ast::format::*;
use std::{fmt, iter};
/// Separates the items yielded by iterating the provided function
pub const fn separate<'f, 's, Item, F, W>(sep: &'s str, t: F) -> impl FnOnce(W) -> fmt::Result + 's
where
Item: FnMut(&mut W) -> fmt::Result,
F: FnMut() -> Option<Item> + 's,
W: fmt::Write,
{
move |mut f| {
for (idx, mut disp) in iter::from_fn(t).enumerate() {
if idx > 0 {
f.write_str(sep)?;
}
disp(&mut f)?;
}
Ok(())
}
}

View File

@@ -0,0 +1,15 @@
//! A [Handle] uniquely represents an entry in the [Table](crate::table::Table)
use cl_structures::index_map::*;
// define the index types
make_index! {
/// Uniquely represents an entry in the [Table](crate::table::Table)
Handle,
}
impl std::fmt::Display for Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

View File

@@ -0,0 +1,74 @@
//! # The Conlang Type Checker
//!
//! As a statically typed language, Conlang requires a robust type checker to enforce correctness.
//!
//! This crate is a major work-in-progress.
//!
//! # The [Table](table::Table)™
//! A directed graph of nodes and their dependencies.
//!
//! Contains [item definitions](handle) and [type expression](type_expression) information.
//!
//! *Every* item is itself a module, and can contain arbitrarily nested items
//! as part of the item graph
//!
//! The table, additionally, has some queues for use in external algorithms,
//! detailed in the [stage] module.
//!
//! # Namespaces
//! Each item in the graph is given its own namespace, which is further separated into
//! two distinct parts:
//! - Children of an item are direct descendents (i.e. their `parent` is a handle to the item)
//! - Imports of an item are indirect descendents created by `use` or `impl` directives. They are
//! shadowed by Children with the same name.
//!
//! # Order of operations:
//! For order-of-operations information, see the [stage] module.
#![warn(clippy::all)]
pub(crate) mod format_utils;
pub mod table;
pub mod handle;
pub mod entry;
pub mod source;
pub mod type_kind;
pub mod type_expression;
pub mod stage {
//! Type collection, evaluation, checking, and inference passes.
//!
//! # Order of operations
//! 1. [mod@populate]: Populate the graph with nodes for every named item.
//! 2. [mod@import]: Import the `use` nodes discovered in [Stage 1](populate).
//! 3. [mod@categorize]: Categorize the nodes according to textual type information.
//! - Creates anonymous types (`fn(T) -> U`, `&T`, `[T]`, etc.) as necessary to fill in the
//! type graph
//! - Creates a new struct type for every enum struct-variant.
//! 4. [mod@implement]: Import members of implementation modules into types.
pub use populate::Populator;
/// Stage 1: Populate the graph with nodes.
pub mod populate;
pub use import::import;
/// Stage 2: Import the `use` nodes discovered in Stage 1.
pub mod import;
pub use categorize::categorize;
/// Stage 3: Categorize the nodes according to textual type information.
pub mod categorize;
pub use implement::implement;
/// Stage 4: Import members of `impl` blocks into their corresponding types.
pub mod implement;
// TODO: Make type inference stage 5
// TODO: Use the type information stored in the [table]
pub mod infer;
}

View File

@@ -0,0 +1,87 @@
//! Holds the [Source] of a definition in the AST
use cl_ast::ast::*;
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Source<'a> {
Root,
Module(&'a Module),
Alias(&'a Alias),
Enum(&'a Enum),
Variant(&'a Variant),
Struct(&'a Struct),
Const(&'a Const),
Static(&'a Static),
Function(&'a Function),
Local(&'a Let),
Impl(&'a Impl),
Use(&'a Use),
Ty(&'a TyKind),
}
impl Source<'_> {
pub fn name(&self) -> Option<Sym> {
match self {
Source::Root => None,
Source::Module(v) => Some(v.name),
Source::Alias(v) => Some(v.name),
Source::Enum(v) => Some(v.name),
Source::Variant(v) => Some(v.name),
Source::Struct(v) => Some(v.name),
Source::Const(v) => Some(v.name),
Source::Static(v) => Some(v.name),
Source::Function(v) => Some(v.name),
Source::Local(_) => None,
Source::Impl(_) | Source::Use(_) | Source::Ty(_) => None,
}
}
/// Returns `true` if this [Source] defines a named value
pub fn is_named_value(&self) -> bool {
matches!(self, Self::Const(_) | Self::Static(_) | Self::Function(_))
}
/// Returns `true` if this [Source] defines a named type
pub fn is_named_type(&self) -> bool {
matches!(
self,
Self::Module(_) | Self::Alias(_) | Self::Enum(_) | Self::Struct(_)
)
}
/// Returns `true` if this [Source] refers to a [Ty] with no name
pub fn is_anon_type(&self) -> bool {
matches!(self, Self::Ty(_))
}
/// Returns `true` if this [Source] refers to an [Impl] block
pub fn is_impl(&self) -> bool {
matches!(self, Self::Impl(_))
}
/// Returns `true` if this [Source] refers to a [Use] import
pub fn is_use_import(&self) -> bool {
matches!(self, Self::Use(_))
}
}
impl fmt::Display for Source<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Root => "🌳 root 🌳".fmt(f),
Self::Module(arg0) => arg0.fmt(f),
Self::Alias(arg0) => arg0.fmt(f),
Self::Enum(arg0) => arg0.fmt(f),
Self::Variant(arg0) => arg0.fmt(f),
Self::Struct(arg0) => arg0.fmt(f),
Self::Const(arg0) => arg0.fmt(f),
Self::Static(arg0) => arg0.fmt(f),
Self::Function(arg0) => arg0.fmt(f),
Self::Impl(arg0) => arg0.fmt(f),
Self::Use(arg0) => arg0.fmt(f),
Self::Ty(arg0) => arg0.fmt(f),
Self::Local(arg0) => arg0.fmt(f),
}
}
}

View File

@@ -0,0 +1,187 @@
//! Categorizes an entry in a table according to its embedded type information
#![allow(unused)]
use crate::{
entry::EntryMut,
handle::Handle,
source::Source,
table::{NodeKind, Table},
type_expression::{Error as TypeEval, TypeExpression},
type_kind::{Adt, TypeKind},
};
use cl_ast::*;
/// Ensures a type entry exists for the provided handle in the table
pub fn categorize(table: &mut Table, node: Handle) -> CatResult<()> {
let Some(source) = table.source(node) else {
return Ok(());
};
match source {
Source::Variant(v) => cat_variant(table, node, v)?,
Source::Struct(s) => cat_struct(table, node, s)?,
Source::Const(c) => cat_const(table, node, c)?,
Source::Static(s) => cat_static(table, node, s)?,
Source::Function(f) => cat_function(table, node, f)?,
Source::Local(l) => cat_local(table, node, l)?,
Source::Impl(i) => cat_impl(table, node, i)?,
// Source::Alias(_) => {table.mark_unchecked(node)},
_ => return Ok(()),
}
Ok(())
}
fn parent(table: &Table, node: Handle) -> Handle {
table.parent(node).copied().unwrap_or(node)
}
fn cat_struct(table: &mut Table, node: Handle, s: &Struct) -> CatResult<()> {
let Struct { name: _, gens: _, kind } = s;
// TODO: Generics
let kind = match kind {
StructKind::Empty => TypeKind::Adt(Adt::UnitStruct),
StructKind::Tuple(types) => {
let mut out = vec![];
for ty in types {
out.push((Visibility::Public, ty.evaluate(table, node)?))
}
TypeKind::Adt(Adt::TupleStruct(out))
}
StructKind::Struct(members) => {
let mut out = vec![];
for m in members {
out.push(cat_member(table, node, m)?)
}
TypeKind::Adt(Adt::Struct(out))
}
};
table.set_ty(node, kind);
Ok(())
}
fn cat_member(
table: &mut Table,
node: Handle,
m: &StructMember,
) -> CatResult<(Sym, Visibility, Handle)> {
let StructMember { vis, name, ty } = m;
Ok((*name, *vis, ty.evaluate(table, node)?))
}
fn cat_enum<'a>(_table: &mut Table<'a>, _node: Handle, e: &'a Enum) -> CatResult<()> {
let Enum { name: _, gens: _, variants: _ } = e;
// table.set_ty(node, kind);
Ok(())
}
fn cat_variant<'a>(table: &mut Table<'a>, node: Handle, v: &'a Variant) -> CatResult<()> {
let Variant { name, kind, body } = v;
let parent = table.parent(node).copied().unwrap_or(table.root());
match (kind) {
(StructKind::Empty) => Ok(()),
(StructKind::Empty) => Ok(()),
(StructKind::Tuple(ty)) => {
let ty = TypeKind::Adt(Adt::TupleStruct(
ty.iter()
.map(|ty| ty.evaluate(table, node).map(|ty| (Visibility::Public, ty)))
.collect::<Result<_, _>>()?,
));
table.set_ty(node, ty);
Ok(())
}
(StructKind::Struct(members)) => {
let mut out = vec![];
for StructMember { vis, name, ty } in members {
let ty = ty.evaluate(table, node)?;
out.push((*name, *vis, ty));
let mut this = node.to_entry_mut(table);
let mut child = this.new_entry(NodeKind::Type);
child.set_source(Source::Variant(v));
child.set_ty(TypeKind::Instance(ty));
let child = child.id();
this.add_child(*name, child);
}
table.set_ty(node, TypeKind::Adt(Adt::Struct(out)));
Ok(())
}
}
}
fn cat_const(table: &mut Table, node: Handle, c: &Const) -> CatResult<()> {
let parent = parent(table, node);
let kind = TypeKind::Instance(
c.ty.evaluate(table, parent)
.map_err(|e| Error::TypeEval(e, " while categorizing a const"))?,
);
table.set_ty(node, kind);
Ok(())
}
fn cat_static(table: &mut Table, node: Handle, s: &Static) -> CatResult<()> {
let parent = parent(table, node);
let kind = TypeKind::Instance(
s.ty.evaluate(table, parent)
.map_err(|e| Error::TypeEval(e, " while categorizing a static"))?,
);
table.set_ty(node, kind);
Ok(())
}
fn cat_function(table: &mut Table, node: Handle, f: &Function) -> CatResult<()> {
let kind = TypeKind::Instance(
f.sign
.evaluate(table, node)
.map_err(|e| Error::TypeEval(e, " while categorizing a function"))?,
);
table.set_ty(node, kind);
Ok(())
}
fn cat_local(table: &mut Table, node: Handle, l: &Let) -> CatResult<()> {
let parent = parent(table, node);
if let Some(ty) = &l.ty {
let kind = ty
.evaluate(table, parent)
.map_err(|e| Error::TypeEval(e, " while categorizing a let binding"))?;
table.set_ty(node, TypeKind::Instance(kind));
}
Ok(())
}
fn cat_impl(table: &mut Table, node: Handle, i: &Impl) -> CatResult<()> {
let parent = parent(table, node);
let Impl { gens, target, body: _ } = i;
let target = match target {
ImplKind::Type(t) => t.evaluate(table, parent),
ImplKind::Trait { impl_trait: _, for_type: t } => t.evaluate(table, parent),
}?;
table.set_impl_target(node, target);
Ok(())
}
type CatResult<T> = Result<T, Error>;
#[derive(Clone, Debug)]
pub enum Error {
BadMeta(Meta),
TypeEval(TypeEval, &'static str),
}
impl From<TypeEval> for Error {
fn from(value: TypeEval) -> Self {
Error::TypeEval(value, "")
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::BadMeta(meta) => write!(f, "Unknown attribute: #[{meta}]"),
Error::TypeEval(e, during) => write!(f, "{e}{during}"),
}
}
}

View File

@@ -0,0 +1,23 @@
use crate::{handle::Handle, table::Table};
pub fn implement(table: &mut Table) -> Vec<Handle> {
let pending = std::mem::take(&mut table.impls);
let mut errors = vec![];
for node in pending {
if let Err(e) = impl_one(table, node) {
errors.push(e);
}
}
errors
}
pub fn impl_one(table: &mut Table, node: Handle) -> Result<(), Handle> {
let Some(target) = table.impl_target(node) else {
Err(node)?
};
if let Some(children) = table.children.get_mut(&node) {
let children = children.clone();
table.children.entry(target).or_default().extend(children);
}
Ok(())
}

View File

@@ -0,0 +1,158 @@
//! An algorithm for importing external nodes
use crate::{
handle::Handle,
source::Source,
table::{NodeKind, Table},
};
use cl_ast::{PathPart, Sym, Use, UseTree};
use core::slice;
use std::{collections::HashSet, mem};
type Seen = HashSet<Handle>;
pub fn import<'a>(table: &mut Table<'a>) -> Vec<(Handle, Error<'a>)> {
let pending = mem::take(&mut table.uses);
let mut seen = Seen::new();
let mut failed = vec![];
for import in pending {
let Err(e) = import_one(table, import, &mut seen) else {
continue;
};
if let Error::NotFound(_, _) = e {
table.mark_use_item(import)
}
failed.push((import, e));
}
failed
}
fn import_one<'a>(table: &mut Table<'a>, item: Handle, seen: &mut Seen) -> UseResult<'a, ()> {
if !seen.insert(item) {
return Ok(());
}
let Some(NodeKind::Use) = table.kind(item) else {
Err(Error::ItsNoUse)?
};
let Some(&dst) = table.parent(item) else {
Err(Error::NoParents)?
};
let Some(code) = table.source(item) else {
Err(Error::NoSource)?
};
let &Source::Use(tree) = code else {
Err(Error::BadSource(*code))?
};
let Use { absolute, tree } = tree;
import_tree(
table,
if !absolute { dst } else { table.root() },
dst,
tree,
seen,
)
}
fn import_tree<'a>(
table: &mut Table<'a>,
src: Handle,
dst: Handle,
tree: &UseTree,
seen: &mut Seen,
) -> UseResult<'a, ()> {
match tree {
UseTree::Tree(trees) => trees
.iter()
.try_for_each(|tree| import_tree(table, src, dst, tree, seen)),
UseTree::Path(part, rest) => {
let source = table
.nav(src, slice::from_ref(part))
.ok_or(Error::NotFound(src, *part))?;
import_tree(table, source, dst, rest, seen)
}
UseTree::Alias(src_name, dst_name) => {
import_name(table, src, src_name, dst, dst_name, seen)
}
UseTree::Name(src_name) => import_name(table, src, src_name, dst, src_name, seen),
UseTree::Glob => import_glob(table, src, dst, seen),
}
}
fn import_glob<'a>(
table: &mut Table<'a>,
src: Handle,
dst: Handle,
seen: &mut Seen,
) -> UseResult<'a, ()> {
let Table { children, imports, .. } = table;
if let Some(c) = children.get(&src) {
imports.entry(dst).or_default().extend(c)
}
import_deps(table, src, seen)?;
let Table { imports, .. } = table;
// Importing imports requires some extra work, since we can't `get_many_mut`
if let Some(i) = imports.get(&src) {
let uses: Vec<_> = i.iter().map(|(&k, &v)| (k, v)).collect();
imports.entry(dst).or_default().extend(uses);
}
Ok(())
}
fn import_name<'a>(
table: &mut Table<'a>,
src: Handle,
src_name: &Sym,
dst: Handle,
dst_name: &Sym,
seen: &mut Seen,
) -> UseResult<'a, ()> {
import_deps(table, src, seen)?;
match table.get_by_sym(src, src_name) {
// TODO: check for new imports clobbering existing imports
Some(src_id) => table.add_import(dst, *dst_name, src_id),
None => Err(Error::NotFound(src, PathPart::Ident(*src_name)))?,
};
Ok(())
}
/// Imports the dependencies of this node
fn import_deps<'a>(table: &mut Table<'a>, node: Handle, seen: &mut Seen) -> UseResult<'a, ()> {
if let Some(items) = table.use_items.get(&node) {
let out = items.clone();
for item in out {
import_one(table, item, seen)?;
}
}
Ok(())
}
pub type UseResult<'a, T> = Result<T, Error<'a>>;
#[derive(Debug)]
pub enum Error<'a> {
ItsNoUse,
NoParents,
NoSource,
BadSource(Source<'a>),
NotFound(Handle, PathPart),
}
impl std::fmt::Display for Error<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::ItsNoUse => write!(f, "Entry is not use"),
Error::NoParents => write!(f, "Entry has no parents"),
Error::NoSource => write!(f, "Entry has no source"),
Error::BadSource(s) => write!(f, "Entry incorrectly marked as use item: {s}"),
Error::NotFound(id, part) => write!(f, "Could not traverse {id}::{part}"),
}
}
}

View File

@@ -0,0 +1,12 @@
//! Implements type unification, used by the Hindley-Milner type inference algorithm
//!
//! Inspired by [rust-hindley-milner][1] and [hindley-milner-python][2]
//!
//! [1]: https://github.com/tcr/rust-hindley-milner/
//! [2]: https://github.com/rob-smallshire/hindley-milner-python
pub mod engine;
pub mod inference;
pub mod error;

View File

@@ -0,0 +1,621 @@
use std::collections::HashSet;
use super::error::InferenceError;
use crate::{
entry::Entry,
handle::Handle,
source::Source,
stage::infer::inference::Inference,
table::{NodeKind, Table},
type_expression::TypeExpression,
type_kind::{Adt, Primitive, TypeKind},
};
use cl_ast::Sym;
/*
Types in Conlang:
- Never type: !
- type !
- for<A> ! -> A
- Primitive types: bool, i32, (), ...
- type bool; ...
- Reference types: &T, *T
- for<T> type ref<T>; for<T> type ptr<T>
- Slice type: [T]
- for<T> type slice<T>
- Array type: [T;usize]
- for<T> type array<T, instanceof<usize>>
- Tuple type: (T, ...Z)
- for<T, ..> type tuple<T, ..> // on a per-case basis!
- Funct type: fn Tuple -> R
- for<T, R> type T -> R // on a per-case basis!
*/
type HandleSet<'h> = Option<&'h mut Option<Handle>>;
pub struct InferenceEngine<'table, 'a, 'b, 'r> {
pub(super) table: &'table mut Table<'a>,
/// The current working node
pub(crate) at: Handle,
/// The current breakset
pub(crate) bset: HandleSet<'b>,
/// The current returnset
pub(crate) rset: HandleSet<'r>,
}
impl<'table, 'a, 'b, 'r> InferenceEngine<'table, 'a, 'b, 'r> {
/// Infers the type of an object by deferring to [`Inference::infer()`]
pub fn infer(&mut self, inferrable: &'a impl Inference<'a>) -> Result<Handle, InferenceError> {
inferrable.infer(self)
}
/// Constructs a new [`InferenceEngine`], scoped around a [`Handle`] in a [`Table`].
pub fn new(table: &'table mut Table<'a>, at: Handle) -> Self {
Self { at, table, bset: Default::default(), rset: Default::default() }
}
/// Constructs an [`InferenceEngine`] that borrows the same table as `self`,
/// but with a shortened lifetime.
pub fn scoped(&mut self) -> InferenceEngine<'_, 'a, '_, '_> {
InferenceEngine {
at: self.at,
table: self.table,
bset: self.bset.as_deref_mut(),
rset: self.rset.as_deref_mut(),
}
}
pub fn infer_all(&mut self) -> Vec<(Handle, InferenceError)> {
let queue = std::mem::take(&mut self.table.unchecked);
let mut res = Vec::new();
for handle in queue {
let mut eng = self.at(handle);
let Some(source) = eng.table.source(handle) else {
eprintln!("No source found for {handle}");
continue;
};
println!("Inferring {source}");
let ret = match source {
Source::Module(v) => v.infer(&mut eng),
Source::Alias(v) => v.infer(&mut eng),
Source::Enum(v) => v.infer(&mut eng),
// Source::Variant(v) => v.infer(&mut eng),
Source::Struct(v) => v.infer(&mut eng),
Source::Const(v) => v.infer(&mut eng),
Source::Static(v) => v.infer(&mut eng),
Source::Function(v) => v.infer(&mut eng),
Source::Local(v) => v.infer(&mut eng),
Source::Impl(v) => v.infer(&mut eng),
_ => Ok(eng.empty()),
};
match &ret {
Ok(handle) => println!("=> {}", eng.entry(*handle)),
Err(err @ InferenceError::AnnotationEval(_)) => eprintln!("=> ERROR: {err}"),
Err(InferenceError::FieldCount(h, want, got)) => {
eprintln!("=> ERROR: Field count {want} != {got} in {}", eng.entry(*h))
}
Err(err @ InferenceError::NotFound(_)) => eprintln!("=> ERROR: {err}"),
Err(InferenceError::Mismatch(h1, h2)) => eprintln!(
"=> ERROR: Type mismatch {} != {}",
eng.entry(*h1),
eng.entry(*h2),
),
Err(InferenceError::Recursive(h1, h2)) => eprintln!(
"=> ERROR: Cycle found in types {}, {}",
eng.entry(*h1),
eng.entry(*h2),
),
Err(InferenceError::NoBreak | InferenceError::NoReturn) => {}
}
println!();
if let Err(err) = ret {
res.push((handle, err));
eng.table.mark_unchecked(handle);
}
}
res
}
/// Constructs a new InferenceEngine with the
pub fn at(&mut self, at: Handle) -> InferenceEngine<'_, 'a, '_, '_> {
InferenceEngine { at, ..self.scoped() }
}
pub fn open_bset<'ob>(
&mut self,
bset: &'ob mut Option<Handle>,
) -> InferenceEngine<'_, 'a, 'ob, '_> {
InferenceEngine { bset: Some(bset), ..self.scoped() }
}
pub fn open_rset<'or>(
&mut self,
rset: &'or mut Option<Handle>,
) -> InferenceEngine<'_, 'a, '_, 'or> {
InferenceEngine { rset: Some(rset), ..self.scoped() }
}
pub fn bset(&mut self, ty: Handle) -> Result<(), InferenceError> {
match self.bset.as_mut() {
Some(&mut &mut Some(bset)) => self.unify(ty, bset),
Some(none) => {
let _ = none.insert(ty);
Ok(())
}
None => Err(InferenceError::NoBreak),
}
}
pub fn rset(&mut self, ty: Handle) -> Result<(), InferenceError> {
match self.rset.as_mut() {
Some(&mut &mut Some(rset)) => self.unify(ty, rset),
Some(none) => {
let _ = none.insert(ty);
Ok(())
}
None => Err(InferenceError::NoReturn),
}
}
/// Constructs an [Entry] out of a [Handle], for ease of use
pub fn entry(&self, of: Handle) -> Entry<'_, 'a> {
self.table.entry(of)
}
pub fn by_name<Out, N: TypeExpression<Out>>(
&mut self,
name: &N,
) -> Result<Out, crate::type_expression::Error> {
name.evaluate(self.table, self.at)
}
/// Creates a new unbound [type variable](Handle)
pub fn new_var(&mut self) -> Handle {
self.table.type_variable()
}
pub fn new_inferred(&mut self) -> Handle {
self.table.inferred_type()
}
/// Creates a variable that is a new instance of another [Type](Handle)
pub fn new_inst(&mut self, of: Handle) -> Handle {
self.table.anon_type(TypeKind::Instance(of))
}
/// Gets the defining usage of a type without collapsing intermediates
pub fn def_usage(&self, to: Handle) -> Handle {
match self.table.entry(to).ty() {
Some(TypeKind::Instance(id)) => self.def_usage(*id),
_ => to,
}
}
pub fn get_fn(&self, at: Handle, name: Sym) -> Option<(Handle, Handle)> {
use cl_ast::PathPart;
if let Some(&TypeKind::FnSig { args, rety }) = self
.entry(at)
.nav(&[PathPart::Ident(name)])
.as_ref()
.and_then(Entry::ty)
{
Some((args, rety))
} else {
None
}
}
/// Creates a new type variable representing a tuple
pub fn new_tuple(&mut self, tys: Vec<Handle>) -> Handle {
self.table.anon_type(TypeKind::Tuple(tys))
}
/// Creates a new type variable representing an array
pub fn new_array(&mut self, ty: Handle, size: usize) -> Handle {
self.table.anon_type(TypeKind::Array(ty, size))
}
/// Creates a new type variable representing a slice of contiguous memory
pub fn new_slice(&mut self, ty: Handle) -> Handle {
self.table.anon_type(TypeKind::Slice(ty))
}
/// Creates a new reference to a type
pub fn new_ref(&mut self, to: Handle) -> Handle {
self.table.anon_type(TypeKind::Ref(to))
}
/// All primitives must be predefined in the standard library.
pub fn primitive(&self, name: &'static str) -> Handle {
// TODO: keep a map of primitives in the table root
self.table.get_lang_item(name)
}
pub fn never(&mut self) -> Handle {
self.table.get_lang_item("never")
}
pub fn empty(&mut self) -> Handle {
self.table.anon_type(TypeKind::Tuple(vec![]))
}
pub fn bool(&self) -> Handle {
self.primitive("bool")
}
pub fn char(&self) -> Handle {
self.primitive("char")
}
pub fn str(&self) -> Handle {
self.primitive("str")
}
pub fn u32(&self) -> Handle {
self.primitive("u32")
}
pub fn usize(&self) -> Handle {
self.primitive("usize")
}
/// Creates a new inferred-integer literal
pub fn integer_literal(&mut self) -> Handle {
let h = self.table.new_entry(self.at, NodeKind::Temporary);
self.table
.set_ty(h, TypeKind::Primitive(Primitive::Integer));
h
}
/// Creates a new inferred-float literal
pub fn float_literal(&mut self) -> Handle {
let h = self.table.new_entry(self.at, NodeKind::Temporary);
self.table.set_ty(h, TypeKind::Primitive(Primitive::Float));
h
}
/// Enters a new scope
pub fn local_scope(&mut self, name: Sym) {
let scope = self.table.new_entry(self.at, NodeKind::Scope);
self.table.add_child(self.at, name, scope);
self.at = scope;
}
/// Creates a new locally-scoped InferenceEngine.
pub fn block_scope(&mut self) -> InferenceEngine<'_, 'a, '_, '_> {
let scope = self.table.new_entry(self.at, NodeKind::Scope);
self.table.add_child(self.at, "".into(), scope);
self.at(scope)
}
/// Sets this type variable `to` be an instance `of` the other
/// # Panics
/// Panics if `to` is not a type variable
pub fn set_instance(&mut self, to: Handle, of: Handle) {
let mut e = self.table.entry_mut(to);
match e.as_ref().ty() {
Some(TypeKind::Inferred) => {
if let Some(ty) = self.table.ty(of) {
self.table.set_ty(to, ty.clone());
}
None
}
Some(TypeKind::Variable)
| Some(TypeKind::Primitive(Primitive::Float | Primitive::Integer)) => {
e.set_ty(TypeKind::Instance(of))
}
other => todo!("Cannot set {} to instance of: {other:?}", e.as_ref()),
};
}
/// Checks whether there are any unbound type variables in this type
pub fn is_generic(&self, ty: Handle) -> bool {
fn is_generic_rec(this: &InferenceEngine, ty: Handle, seen: &mut HashSet<Handle>) -> bool {
if !seen.insert(ty) {
return false;
}
let entry = this.table.entry(ty);
let Some(ty) = entry.ty() else {
return false;
};
match ty {
TypeKind::Inferred => false,
TypeKind::Variable => true,
&TypeKind::Array(ty, _) => is_generic_rec(this, ty, seen),
&TypeKind::Instance(ty) => is_generic_rec(this, ty, seen),
TypeKind::Primitive(_) => false,
TypeKind::Adt(Adt::Enum(tys)) => {
tys.iter().any(|&(_, ty)| is_generic_rec(this, ty, seen))
}
TypeKind::Adt(Adt::Struct(tys)) => {
tys.iter().any(|&(_, _, ty)| is_generic_rec(this, ty, seen))
}
TypeKind::Adt(Adt::TupleStruct(tys)) => {
tys.iter().any(|&(_, ty)| is_generic_rec(this, ty, seen))
}
TypeKind::Adt(Adt::UnitStruct) => false,
TypeKind::Adt(Adt::Union(tys)) => {
tys.iter().any(|&(_, ty)| is_generic_rec(this, ty, seen))
}
&TypeKind::Ref(ty) => is_generic_rec(this, ty, seen),
&TypeKind::Ptr(ty) => is_generic_rec(this, ty, seen),
&TypeKind::Slice(ty) => is_generic_rec(this, ty, seen),
TypeKind::Tuple(tys) => tys.iter().any(|&ty| is_generic_rec(this, ty, seen)),
&TypeKind::FnSig { args, rety } => {
is_generic_rec(this, args, seen) || is_generic_rec(this, rety, seen)
}
TypeKind::Module => false,
}
}
is_generic_rec(self, ty, &mut HashSet::new())
}
/// Makes a deep copy of a type expression.
///
/// Bound variables are shared, unbound variables are duplicated.
pub fn deep_clone(&mut self, ty: Handle) -> Handle {
if !self.is_generic(ty) {
return ty;
};
let entry = self.table.entry(ty);
let Some(tykind) = entry.ty().cloned() else {
return ty;
};
// TODO: Parent the deep clone into a new "monomorphs" branch of tree
match tykind {
TypeKind::Variable => self.new_inferred(),
TypeKind::Array(h, s) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Array(ty, s))
}
TypeKind::Instance(h) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Instance(ty))
}
TypeKind::Adt(Adt::Enum(tys)) => {
let tys = tys
.into_iter()
.map(|(name, ty)| (name, self.deep_clone(ty)))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::Enum(tys)))
}
TypeKind::Adt(Adt::Struct(tys)) => {
let tys = tys
.into_iter()
.map(|(n, v, ty)| (n, v, self.deep_clone(ty)))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::Struct(tys)))
}
TypeKind::Adt(Adt::TupleStruct(tys)) => {
let tys = tys
.into_iter()
.map(|(v, ty)| (v, self.deep_clone(ty)))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::TupleStruct(tys)))
}
TypeKind::Adt(Adt::Union(tys)) => {
let tys = tys
.into_iter()
.map(|(n, ty)| (n, self.deep_clone(ty)))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::Union(tys)))
}
TypeKind::Ref(h) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Ref(ty))
}
TypeKind::Ptr(handle) => {
let ty = self.deep_clone(handle);
self.table.anon_type(TypeKind::Ptr(ty))
}
TypeKind::Slice(h) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Slice(ty))
}
TypeKind::Tuple(tys) => {
let tys = tys.into_iter().map(|ty| self.deep_clone(ty)).collect();
self.table.anon_type(TypeKind::Tuple(tys))
}
TypeKind::FnSig { args, rety } => {
let args = self.deep_clone(args);
let rety = self.deep_clone(rety);
self.table.anon_type(TypeKind::FnSig { args, rety })
}
_ => ty,
}
}
/// Returns the defining instance of `self`,
/// collapsing type instances along the way.
pub fn prune(&mut self, ty: Handle) -> Handle {
if let Some(TypeKind::Instance(new_ty)) = self.table.ty(ty) {
let new_ty = self.prune(*new_ty);
self.table.set_ty(ty, TypeKind::Instance(new_ty));
new_ty
} else {
ty
}
}
/// Checks whether a type occurs in another type
///
/// # Note:
/// - Since the test uses strict equality, `self` should be pruned prior to testing.
/// - The test is *not guaranteed to terminate* for recursive types.
pub fn occurs_in(&self, this: Handle, other: Handle) -> bool {
if this == other {
return true;
}
let Some(ty) = self.table.ty(other) else {
return false;
};
match ty {
TypeKind::Instance(other) => self.occurs_in(this, *other),
TypeKind::Adt(Adt::Enum(items)) => {
items.iter().any(|(_, other)| self.occurs_in(this, *other))
}
TypeKind::Adt(Adt::Struct(items)) => items
.iter()
.any(|(_, _, other)| self.occurs_in(this, *other)),
TypeKind::Adt(Adt::TupleStruct(items)) => {
items.iter().any(|(_, other)| self.occurs_in(this, *other))
}
TypeKind::Adt(Adt::Union(items)) => {
items.iter().any(|(_, other)| self.occurs_in(this, *other))
}
TypeKind::Ref(_) => false,
TypeKind::Ptr(_) => false,
TypeKind::Slice(other) => self.occurs_in(this, *other),
TypeKind::Array(other, _) => self.occurs_in(this, *other),
TypeKind::Tuple(handles) => handles.iter().any(|&other| self.occurs_in(this, other)),
TypeKind::FnSig { args, rety } => {
self.occurs_in(this, *args) || self.occurs_in(this, *rety)
}
TypeKind::Inferred
| TypeKind::Variable
| TypeKind::Adt(Adt::UnitStruct)
| TypeKind::Primitive(_)
| TypeKind::Module => false,
}
}
/// Unifies two types
pub fn unify(&mut self, this: Handle, other: Handle) -> Result<(), InferenceError> {
let (ah, bh) = (self.prune(this), self.prune(other));
if ah == bh {
return Ok(());
}
let (a, b) = (self.table.entry(ah), self.table.entry(bh));
let (Some(a), Some(b)) = (a.ty(), b.ty()) else {
return Err(InferenceError::Mismatch(ah, bh));
};
match (a, b) {
(TypeKind::Variable, TypeKind::Variable) => {
self.set_instance(ah, bh);
Ok(())
}
(TypeKind::Inferred, _) => {
self.set_instance(ah, bh);
Ok(())
}
(_, TypeKind::Inferred) => self.unify(bh, ah),
(TypeKind::Variable, _) => Err(InferenceError::Mismatch(ah, bh)),
(TypeKind::Instance(a), TypeKind::Instance(b)) if !self.occurs_in(*a, *b) => {
self.set_instance(*a, *b);
Ok(())
}
(TypeKind::Instance(_), _) => Err(InferenceError::Recursive(ah, bh)),
(TypeKind::Primitive(Primitive::Float), TypeKind::Primitive(Primitive::Integer))
| (TypeKind::Primitive(Primitive::Integer), TypeKind::Primitive(Primitive::Float)) => {
Err(InferenceError::Mismatch(ah, bh))
}
// Primitives have their own set of vars which only unify with primitives.
(TypeKind::Primitive(Primitive::Integer), TypeKind::Primitive(i)) if i.is_integer() => {
self.set_instance(ah, bh);
Ok(())
}
(TypeKind::Primitive(Primitive::Float), TypeKind::Primitive(f)) if f.is_float() => {
self.set_instance(ah, bh);
Ok(())
}
(_, TypeKind::Variable)
| (_, TypeKind::Instance(_))
| (TypeKind::Primitive(_), TypeKind::Primitive(Primitive::Integer))
| (TypeKind::Primitive(_), TypeKind::Primitive(Primitive::Float)) => self.unify(bh, ah),
(TypeKind::Adt(Adt::Enum(ia)), TypeKind::Adt(Adt::Enum(ib)))
if ia.len() == ib.len() =>
{
for ((na, a), (nb, b)) in ia.clone().into_iter().zip(ib.clone().into_iter()) {
if na != nb {
return Err(InferenceError::Mismatch(ah, bh));
}
self.unify(a, b)?;
}
Ok(())
}
(TypeKind::Adt(Adt::Enum(en)), TypeKind::Adt(_)) => {
#[allow(unused)]
let Some(other_parent) = self.table.parent(bh) else {
Err(InferenceError::Mismatch(ah, bh))?
};
if ah != *other_parent {
Err(InferenceError::Mismatch(ah, *other_parent))?
}
#[allow(unused)]
for (sym, handle) in en {
let handle = self.def_usage(*handle);
if handle == bh {
return Ok(());
}
}
Err(InferenceError::Mismatch(ah, bh))
}
(TypeKind::Adt(Adt::Struct(ia)), TypeKind::Adt(Adt::Struct(ib)))
if ia.len() == ib.len() =>
{
for ((na, va, a), (nb, vb, b)) in ia.clone().into_iter().zip(ib.clone().into_iter())
{
if na != nb || va != vb {
return Err(InferenceError::Mismatch(ah, bh));
}
self.unify(a, b)?;
}
Ok(())
}
(TypeKind::Adt(Adt::TupleStruct(ia)), TypeKind::Adt(Adt::TupleStruct(ib)))
if ia.len() == ib.len() =>
{
for ((va, a), (vb, b)) in ia.clone().into_iter().zip(ib.clone().into_iter()) {
if va != vb {
return Err(InferenceError::Mismatch(ah, bh));
}
self.unify(a, b)?;
}
Ok(())
}
(TypeKind::Adt(Adt::Union(ia)), TypeKind::Adt(Adt::Union(ib)))
if ia.len() == ib.len() =>
{
todo!()
}
(TypeKind::Ref(a), TypeKind::Ref(b)) => self.unify(*a, *b),
(TypeKind::Ptr(a), TypeKind::Ptr(b)) => self.unify(*a, *b),
(TypeKind::Slice(a), TypeKind::Slice(b)) => self.unify(*a, *b),
// Slice unifies with array
(TypeKind::Array(a, _), TypeKind::Slice(b)) => self.unify(*a, *b),
(TypeKind::Slice(_), TypeKind::Array(_, _)) => self.unify(bh, ah),
(TypeKind::Array(a, sa), TypeKind::Array(b, sb)) if sa == sb => self.unify(*a, *b),
(TypeKind::Tuple(a), TypeKind::Tuple(b)) => {
if a.len() != b.len() {
return Err(InferenceError::Mismatch(ah, bh));
}
let (a, b) = (a.clone(), b.clone());
for (a, b) in a.iter().zip(b.iter()) {
self.unify(*a, *b)?;
}
Ok(())
}
(&TypeKind::FnSig { args: a1, rety: r1 }, &TypeKind::FnSig { args: a2, rety: r2 }) => {
self.unify(a1, a2)?;
self.unify(r1, r2)
}
(TypeKind::Primitive(Primitive::Never), _)
| (_, TypeKind::Primitive(Primitive::Never)) => Ok(()),
(a, b) if a == b => Ok(()),
_ => Err(InferenceError::Mismatch(ah, bh)),
}
}
}

View File

@@ -0,0 +1,43 @@
use cl_ast::Path;
use crate::handle::Handle;
use core::fmt;
/// An error produced during type inference
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InferenceError {
AnnotationEval(crate::type_expression::Error),
FieldCount(Handle, usize, usize),
NotFound(Path),
Mismatch(Handle, Handle),
Recursive(Handle, Handle),
NoBreak,
NoReturn,
}
impl std::error::Error for InferenceError {}
#[rustfmt::skip]
impl fmt::Display for InferenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InferenceError::AnnotationEval(error) => write!(f, "{error}"),
InferenceError::FieldCount(name, want, got) => {
write!(f,
"Struct {name} {} fields! Expected {want}, got {got}",
if want < got { "has too many" } else { "is missing" }
)
}
InferenceError::NotFound(p) => write!(f, "Path not visible in scope: {p}"),
InferenceError::Mismatch(a, b) => write!(f, "Type mismatch: {a:?} != {b:?}"),
InferenceError::Recursive(_, _) => write!(f, "Recursive type!"),
InferenceError::NoBreak => write!(f, "Encountered break outside loop!"),
InferenceError::NoReturn => write!(f, "Encountered return outside function!"),
}
}
}
impl From<crate::type_expression::Error> for InferenceError {
fn from(value: crate::type_expression::Error) -> Self {
Self::AnnotationEval(value)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,193 @@
//! The [Populator] populates entries in the sym table, including span info
use crate::{
entry::EntryMut,
handle::Handle,
source::Source,
table::{NodeKind, Table},
type_kind::TypeKind,
};
use cl_ast::{
ItemKind, Literal, Meta, MetaKind, Sym,
ast_visitor::{Visit, Walk},
};
#[derive(Debug)]
pub struct Populator<'t, 'a> {
inner: EntryMut<'t, 'a>,
name: Option<Sym>, // this is a hack to get around the Visitor interface
}
impl<'t, 'a> Populator<'t, 'a> {
pub fn new(table: &'t mut Table<'a>) -> Self {
Self { inner: table.root_entry_mut(), name: None }
}
/// Constructs a new Populator with the provided parent Handle
pub fn with_id(&mut self, parent: Handle) -> Populator<'_, 'a> {
Populator { inner: self.inner.with_id(parent), name: None }
}
pub fn new_entry(&mut self, kind: NodeKind) -> Populator<'_, 'a> {
Populator { inner: self.inner.new_entry(kind), name: None }
}
pub fn set_name(&mut self, name: Sym) {
self.name = Some(name);
}
}
impl<'a> Visit<'a> for Populator<'_, 'a> {
fn visit_item(&mut self, i: &'a cl_ast::Item) {
let cl_ast::Item { span, attrs, vis: _, kind } = i;
// TODO: this, better, better.
let entry_kind = match kind {
ItemKind::Alias(_) => NodeKind::Type,
ItemKind::Enum(_) => NodeKind::Type,
ItemKind::Struct(_) => NodeKind::Type,
ItemKind::Const(_) => NodeKind::Const,
ItemKind::Static(_) => NodeKind::Static,
ItemKind::Function(_) => NodeKind::Function,
ItemKind::Module(_) => NodeKind::Module,
ItemKind::Impl(_) => NodeKind::Impl,
ItemKind::Use(_) => NodeKind::Use,
};
let mut entry = self.new_entry(entry_kind);
entry.inner.set_span(*span);
entry.inner.set_meta(&attrs.meta);
for Meta { name, kind } in &attrs.meta {
if let ("lang", MetaKind::Equals(Literal::String(s))) = (name.to_ref(), kind) {
if let Ok(prim) = s.parse() {
entry.inner.set_ty(TypeKind::Primitive(prim));
}
entry.inner.mark_lang_item(Sym::from(s).to_ref());
}
}
entry.visit_children(i);
if let (Some(name), child) = (entry.name, entry.inner.id()) {
self.inner.add_child(name, child);
}
}
fn visit_generics(&mut self, value: &'a cl_ast::Generics) {
let cl_ast::Generics { vars } = value;
for var in vars {
let mut entry = self.inner.new_entry(NodeKind::Type);
entry.set_ty(TypeKind::Variable);
let id = entry.id();
self.inner.add_child(*var, id);
}
}
fn visit_alias(&mut self, a: &'a cl_ast::Alias) {
let cl_ast::Alias { name, from } = a;
self.inner.set_source(Source::Alias(a));
self.set_name(*name);
self.visit(from);
}
fn visit_const(&mut self, c: &'a cl_ast::Const) {
let cl_ast::Const { name, ty, init } = c;
self.inner.set_source(Source::Const(c));
self.inner.set_body(init);
self.set_name(*name);
self.visit(ty);
self.visit(init);
}
fn visit_static(&mut self, s: &'a cl_ast::Static) {
let cl_ast::Static { name, init, .. } = s;
self.inner.set_source(Source::Static(s));
self.inner.set_body(init);
self.set_name(*name);
s.children(self);
}
fn visit_function(&mut self, f: &'a cl_ast::Function) {
self.inner.set_source(Source::Function(f));
self.set_name(f.name);
if let Some(body) = &f.body {
self.inner.set_body(body);
}
f.children(self);
}
fn visit_module(&mut self, m: &'a cl_ast::Module) {
self.inner.set_source(Source::Module(m));
self.set_name(m.name);
m.children(self);
}
fn visit_struct(&mut self, s: &'a cl_ast::Struct) {
self.inner.set_source(Source::Struct(s));
self.set_name(s.name);
s.children(self);
}
fn visit_enum(&mut self, e: &'a cl_ast::Enum) {
let cl_ast::Enum { name, gens, variants } = e;
self.inner.set_source(Source::Enum(e));
self.set_name(*name);
self.visit(gens);
let mut children = Vec::new();
for variant in variants.iter() {
let mut entry = self.new_entry(NodeKind::Type);
variant.visit_in(&mut entry);
let child = entry.inner.id();
children.push((variant.name, child));
self.inner.add_child(variant.name, child);
}
self.inner
.set_ty(TypeKind::Adt(crate::type_kind::Adt::Enum(children)));
}
fn visit_variant(&mut self, value: &'a cl_ast::Variant) {
let cl_ast::Variant { name, kind, body } = value;
self.inner.set_source(Source::Variant(value));
self.set_name(*name);
self.visit(kind);
match (kind, body) {
(cl_ast::StructKind::Empty, None) => {
self.inner.set_ty(TypeKind::Inferred);
}
(cl_ast::StructKind::Empty, Some(body)) => {
self.inner.set_body(body);
}
(cl_ast::StructKind::Tuple(_items), None) => {}
(cl_ast::StructKind::Struct(_struct_members), None) => {}
(_, Some(body)) => panic!("Unexpected body {body} in enum variant `{value}`"),
}
}
fn visit_impl(&mut self, i: &'a cl_ast::Impl) {
let cl_ast::Impl { gens: _, target: _, body } = i;
self.inner.set_source(Source::Impl(i));
self.inner.mark_impl_item();
// We don't know if target is generic yet -- that's checked later.
self.visit(body);
}
fn visit_use(&mut self, u: &'a cl_ast::Use) {
let cl_ast::Use { absolute: _, tree } = u;
self.inner.set_source(Source::Use(u));
self.inner.mark_use_item();
self.visit(tree);
}
}

View File

@@ -0,0 +1,364 @@
//! The [Table] is a monolithic data structure representing everything the type checker
//! knows about a program.
//!
//! Individual nodes in the table can be queried using the [Entry] API ([Table::entry])
//! or modified using the [EntryMut] API ([Table::entry_mut]).
//!
//! # Contents of a "node"
//! Always present:
//! - [NodeKind]: Determines how this node will be treated during the [stages](crate::stage) of
//! compilation
//! - [Parent node](Handle): Arranges this node in the hierarchical graph structure
//!
//! Populated as needed:
//! - Children: An associative array of [names](Sym) to child nodes in the graph. Child nodes are
//! arranged in a *strict* tree structure, with no back edges
//! - Imports: An associative array of [names](Sym) to other nodes in the graph. Not all import
//! nodes are back edges, but all back edges *must be* import nodes.
//! - [Types](TypeKind): Contains type information populated through type checking and inference.
//! Nodes with unpopulated types may be considered type variables in the future.
//! - [Spans][span]: Positional information from the source text. See [cl_structures::span].
//! - [Metas](Meta): Metadata decorators. These may have an effect throughout the compiler.
//! - [Sources](Source): Pointers back into the AST, for future analysis.
//! - Impl Targets: Sparse mapping of `impl` nodes to their corresponding targets.
//! - etc.
//!
//! [span]: struct@Span
use crate::{
entry::{Entry, EntryMut},
handle::Handle,
source::Source,
type_kind::TypeKind,
};
use cl_ast::{Expr, Meta, PathPart, Sym};
use cl_structures::{index_map::IndexMap, span::Span};
use std::collections::HashMap;
// TODO: Cycle detection external to this module
/// The table is a monolithic data structure representing everything the type checker
/// knows about a program.
///
/// See [module documentation](self).
#[derive(Debug)]
pub struct Table<'a> {
root: Handle,
/// This is the source of truth for handles
kinds: IndexMap<Handle, NodeKind>,
parents: IndexMap<Handle, Handle>,
pub(crate) children: HashMap<Handle, HashMap<Sym, Handle>>,
pub(crate) imports: HashMap<Handle, HashMap<Sym, Handle>>,
pub(crate) use_items: HashMap<Handle, Vec<Handle>>,
bodies: HashMap<Handle, &'a Expr>,
types: HashMap<Handle, TypeKind>,
spans: HashMap<Handle, Span>,
metas: HashMap<Handle, &'a [Meta]>,
sources: HashMap<Handle, Source<'a>>,
impl_targets: HashMap<Handle, Handle>,
anon_types: HashMap<TypeKind, Handle>,
lang_items: HashMap<&'static str, Handle>,
// --- Queues for algorithms ---
pub(crate) unchecked: Vec<Handle>,
pub(crate) impls: Vec<Handle>,
pub(crate) uses: Vec<Handle>,
}
impl<'a> Table<'a> {
pub fn new() -> Self {
let mut kinds = IndexMap::new();
let mut parents = IndexMap::new();
let root = kinds.insert(NodeKind::Root);
assert_eq!(root, parents.insert(root));
Self {
root,
kinds,
parents,
children: HashMap::new(),
imports: HashMap::new(),
use_items: HashMap::new(),
bodies: HashMap::new(),
types: HashMap::new(),
spans: HashMap::new(),
metas: HashMap::new(),
sources: HashMap::new(),
impl_targets: HashMap::new(),
anon_types: HashMap::new(),
lang_items: HashMap::new(),
unchecked: Vec::new(),
impls: Vec::new(),
uses: Vec::new(),
}
}
pub fn entry(&self, handle: Handle) -> Entry<'_, 'a> {
handle.to_entry(self)
}
pub fn entry_mut(&mut self, handle: Handle) -> EntryMut<'_, 'a> {
handle.to_entry_mut(self)
}
pub fn new_entry(&mut self, parent: Handle, kind: NodeKind) -> Handle {
let entry = self.kinds.insert(kind);
assert_eq!(entry, self.parents.insert(parent));
entry
}
pub fn add_child(&mut self, parent: Handle, name: Sym, child: Handle) -> Option<Handle> {
self.children.entry(parent).or_default().insert(name, child)
}
pub fn add_import(&mut self, parent: Handle, name: Sym, import: Handle) -> Option<Handle> {
self.imports.entry(parent).or_default().insert(name, import)
}
pub fn mark_unchecked(&mut self, item: Handle) {
self.unchecked.push(item);
}
pub fn mark_use_item(&mut self, item: Handle) {
let parent = self.parents[item];
self.use_items.entry(parent).or_default().push(item);
self.uses.push(item);
}
pub fn mark_impl_item(&mut self, item: Handle) {
self.impls.push(item);
}
pub fn mark_lang_item(&mut self, name: &'static str, item: Handle) {
self.lang_items.insert(name, item);
}
pub fn get_lang_item(&self, name: &str) -> Handle {
match self.lang_items.get(name).copied() {
Some(handle) => handle,
None => todo!(),
}
}
pub fn handle_iter(&self) -> impl Iterator<Item = Handle> + use<> {
self.kinds.keys()
}
/// Returns handles to all nodes sequentially by [Entry]
pub fn debug_entry_iter(&self) -> impl Iterator<Item = Entry<'_, 'a>> {
self.kinds.keys().map(|key| key.to_entry(self))
}
/// Gets the [Handle] of an anonymous type with the provided [TypeKind].
/// If not already present, a new one is created.
pub(crate) fn anon_type(&mut self, kind: TypeKind) -> Handle {
if let Some(id) = self.anon_types.get(&kind) {
return *id;
}
let entry = self.new_entry(self.root, NodeKind::Type);
// Anonymous types require a bijective map (anon_types => Def => types)
self.types.insert(entry, kind.clone());
self.anon_types.insert(kind, entry);
entry
}
pub(crate) fn inferred_type(&mut self) -> Handle {
let handle = self.new_entry(self.root, NodeKind::Type);
self.types.insert(handle, TypeKind::Inferred);
handle
}
pub(crate) fn type_variable(&mut self) -> Handle {
let handle = self.new_entry(self.root, NodeKind::Type);
self.types.insert(handle, TypeKind::Variable);
handle
}
pub const fn root_entry(&self) -> Entry<'_, 'a> {
self.root.to_entry(self)
}
pub fn root_entry_mut(&mut self) -> crate::entry::EntryMut<'_, 'a> {
self.root.to_entry_mut(self)
}
// --- inherent properties ---
pub const fn root(&self) -> Handle {
self.root
}
pub fn kind(&self, node: Handle) -> Option<&NodeKind> {
self.kinds.get(node)
}
pub fn parent(&self, node: Handle) -> Option<&Handle> {
self.parents.get(node)
}
pub fn children(&self, node: Handle) -> Option<&HashMap<Sym, Handle>> {
self.children.get(&node)
}
pub fn imports(&self, node: Handle) -> Option<&HashMap<Sym, Handle>> {
self.imports.get(&node)
}
pub fn body(&self, node: Handle) -> Option<&'a Expr> {
self.bodies.get(&node).copied()
}
pub fn ty(&self, node: Handle) -> Option<&TypeKind> {
self.types.get(&node)
}
pub fn span(&self, node: Handle) -> Option<&Span> {
self.spans.get(&node)
}
pub fn meta(&self, node: Handle) -> Option<&'a [Meta]> {
self.metas.get(&node).copied()
}
pub fn source(&self, node: Handle) -> Option<&Source<'a>> {
self.sources.get(&node)
}
pub fn impl_target(&self, node: Handle) -> Option<Handle> {
self.impl_targets.get(&node).copied()
}
pub fn reparent(&mut self, node: Handle, parent: Handle) -> Handle {
self.parents.replace(node, parent)
}
pub fn set_body(&mut self, node: Handle, body: &'a Expr) -> Option<&'a Expr> {
self.mark_unchecked(node);
self.bodies.insert(node, body)
}
pub fn set_ty(&mut self, node: Handle, kind: TypeKind) -> Option<TypeKind> {
self.types.insert(node, kind)
}
pub fn set_span(&mut self, node: Handle, span: Span) -> Option<Span> {
self.spans.insert(node, span)
}
pub fn set_meta(&mut self, node: Handle, meta: &'a [Meta]) -> Option<&'a [Meta]> {
self.metas.insert(node, meta)
}
pub fn set_source(&mut self, node: Handle, source: Source<'a>) -> Option<Source<'a>> {
self.sources.insert(node, source)
}
pub fn set_impl_target(&mut self, node: Handle, target: Handle) -> Option<Handle> {
self.impl_targets.insert(node, target)
}
// --- derived properties ---
/// Gets a handle to the local `Self` type, if one exists
pub fn selfty(&self, node: Handle) -> Option<Handle> {
match self.kinds.get(node)? {
NodeKind::Root | NodeKind::Use => None,
NodeKind::Type => Some(node),
NodeKind::Impl => self.impl_target(node),
_ => self.selfty(*self.parent(node)?),
}
}
pub fn super_of(&self, node: Handle) -> Option<Handle> {
match self.kinds.get(node)? {
NodeKind::Root => None,
NodeKind::Module => self.parent(node).copied(),
_ => self.super_of(*self.parent(node)?),
}
}
pub fn name(&self, node: Handle) -> Option<Sym> {
self.source(node).and_then(|s| s.name())
}
pub fn is_transparent(&self, node: Handle) -> bool {
!matches!(
self.kind(node),
None | Some(NodeKind::Root | NodeKind::Module)
)
}
pub fn get_child(&self, node: Handle, name: &Sym) -> Option<Handle> {
self.children.get(&node).and_then(|c| c.get(name)).copied()
}
pub fn get_import(&self, node: Handle, name: &Sym) -> Option<Handle> {
self.imports.get(&node).and_then(|i| i.get(name)).copied()
}
pub fn get_by_sym(&self, node: Handle, name: &Sym) -> Option<Handle> {
self.get_child(node, name)
.or_else(|| self.get_import(node, name))
.or_else(|| {
self.is_transparent(node)
.then(|| {
self.parent(node)
.and_then(|node| self.get_by_sym(*node, name))
})
.flatten()
})
}
/// Does path traversal relative to the provided `node`.
pub fn nav(&self, node: Handle, path: &[PathPart]) -> Option<Handle> {
match path {
[PathPart::SuperKw, rest @ ..] => self.nav(self.super_of(node)?, rest),
[PathPart::SelfTy, rest @ ..] => self.nav(self.selfty(node)?, rest),
[PathPart::Ident(name), rest @ ..] => self.nav(self.get_by_sym(node, name)?, rest),
[] => Some(node),
}
}
}
impl Default for Table<'_> {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug)]
pub enum NodeKind {
Root,
Module,
Type,
Const,
Static,
Function,
Temporary,
Let,
Scope,
Impl,
Use,
}
mod display {
use super::*;
use std::fmt;
impl fmt::Display for NodeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NodeKind::Root => write!(f, "root"),
NodeKind::Module => write!(f, "mod"),
NodeKind::Type => write!(f, "type"),
NodeKind::Const => write!(f, "const"),
NodeKind::Static => write!(f, "static"),
NodeKind::Function => write!(f, "fn"),
NodeKind::Temporary => write!(f, "temp"),
NodeKind::Let => write!(f, "let"),
NodeKind::Scope => write!(f, "scope"),
NodeKind::Use => write!(f, "use"),
NodeKind::Impl => write!(f, "impl"),
}
}
}
}

View File

@@ -0,0 +1,144 @@
//! A [TypeExpression] is a [syntactic](cl_ast) representation of a [TypeKind], and is used to
//! construct type bindings in a [Table]'s typing context.
use crate::{handle::Handle, table::Table, type_kind::TypeKind};
use cl_ast::{PathPart, Sym, Ty, TyArray, TyFn, TyKind, TyPtr, TyRef, TySlice, TyTuple};
#[derive(Clone, Debug, PartialEq, Eq)] // TODO: impl Display and Error
pub enum Error {
BadPath { parent: Handle, path: Vec<PathPart> },
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::BadPath { parent, path } => {
write!(f, "No item at path {parent}")?;
for part in path {
write!(f, "::{part}")?;
}
}
}
Ok(())
}
}
/// A [TypeExpression] is a syntactic representation of a [TypeKind], and is used to construct
/// type bindings in a [Table]'s typing context.
pub trait TypeExpression<Out = Handle> {
/// Evaluates a type expression, recursively creating intermediate bindings.
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Out, Error>;
}
impl TypeExpression for Ty {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
self.kind.evaluate(table, node)
}
}
impl TypeExpression for TyKind {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
match self {
TyKind::Never => Ok(table.get_lang_item("never")),
TyKind::Infer => Ok(table.inferred_type()),
TyKind::Path(p) => p.evaluate(table, node),
TyKind::Array(a) => a.evaluate(table, node),
TyKind::Slice(s) => s.evaluate(table, node),
TyKind::Tuple(t) => t.evaluate(table, node),
TyKind::Ref(r) => r.evaluate(table, node),
TyKind::Ptr(r) => r.evaluate(table, node),
TyKind::Fn(f) => f.evaluate(table, node),
}
}
}
impl TypeExpression for cl_ast::Path {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let Self { absolute, parts } = self;
parts.evaluate(table, if *absolute { table.root() } else { node })
}
}
impl TypeExpression for [PathPart] {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
table
.nav(node, self)
.ok_or_else(|| Error::BadPath { parent: node, path: self.to_owned() })
}
}
impl TypeExpression for Sym {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let path = [PathPart::Ident(*self)];
table
.nav(node, &path)
.ok_or_else(|| Error::BadPath { parent: node, path: path.to_vec() })
}
}
impl TypeExpression for TyArray {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let Self { ty, count } = self;
let kind = TypeKind::Array(ty.evaluate(table, node)?, *count);
Ok(table.anon_type(kind))
}
}
impl TypeExpression for TySlice {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let Self { ty } = self;
let kind = TypeKind::Slice(ty.evaluate(table, node)?);
Ok(table.anon_type(kind))
}
}
impl TypeExpression for TyTuple {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let Self { types } = self;
let kind = TypeKind::Tuple(types.evaluate(table, node)?);
Ok(table.anon_type(kind))
}
}
impl TypeExpression for TyRef {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let Self { mutable: _, count, to } = self;
let mut t = to.evaluate(table, node)?;
for _ in 0..*count {
let kind = TypeKind::Ref(t);
t = table.anon_type(kind)
}
Ok(t)
}
}
impl TypeExpression for TyPtr {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let Self { to } = self;
let mut t = to.evaluate(table, node)?;
t = table.anon_type(TypeKind::Ptr(t));
Ok(t)
}
}
impl TypeExpression for TyFn {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
let Self { args, rety } = self;
let kind = TypeKind::FnSig {
args: args.evaluate(table, node)?,
rety: rety.evaluate(table, node)?,
};
Ok(table.anon_type(kind))
}
}
impl<T: TypeExpression<U>, U> TypeExpression<Vec<U>> for [T] {
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Vec<U>, Error> {
let mut out = Vec::with_capacity(self.len());
for te in self {
out.push(te.evaluate(table, node)?) // try_collect is unstable
}
Ok(out)
}
}

View File

@@ -0,0 +1,125 @@
//! A [TypeKind] is a node in the [Table](crate::table::Table)'s type graph
use crate::handle::Handle;
use cl_ast::{Sym, Visibility};
use std::{fmt::Debug, str::FromStr};
mod display;
/// A [TypeKind] represents an item
/// (a component of a [Table](crate::table::Table))
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TypeKind {
/// A type that is yet to be inferred!
Inferred,
/// A type variable, to be monomorphized
Variable,
/// An alias for an already-defined type
Instance(Handle),
/// A primitive type, built-in to the compiler
Primitive(Primitive),
/// A user-defined aromatic data type
Adt(Adt),
/// A reference to an already-defined type: &T
Ref(Handle),
/// A raw pointer to an already-defined type: &T
Ptr(Handle),
/// A contiguous view of dynamically sized memory
Slice(Handle),
/// A contiguous view of statically sized memory
Array(Handle, usize),
/// A tuple of existing types
Tuple(Vec<Handle>),
/// A function which accepts multiple inputs and produces an output
FnSig { args: Handle, rety: Handle },
/// An untyped module
Module,
}
/// A user-defined Aromatic Data Type
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Adt {
/// A union-like enum type
Enum(Vec<(Sym, Handle)>),
/// A structural product type with named members
Struct(Vec<(Sym, Visibility, Handle)>),
/// A structural product type with unnamed members
TupleStruct(Vec<(Visibility, Handle)>),
/// A structural product type of neither named nor unnamed members
UnitStruct,
/// A choose your own undefined behavior type
/// TODO: should unions be a language feature?
Union(Vec<(Sym, Handle)>),
}
/// The set of compiler-intrinsic types.
/// These primitive types have native implementations of the basic operations.
#[rustfmt::skip]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Primitive {
I8, I16, I32, I64, I128, Isize, // Signed integers
U8, U16, U32, U64, U128, Usize, // Unsigned integers
F8, F16, F32, F64, F128, Fsize, // Floating point numbers
Integer, Float, // Inferred int and float
Bool, // boolean value
Char, // Unicode codepoint
Str, // UTF-8 string
Never, // The never type
}
#[rustfmt::skip]
impl Primitive {
/// Checks whether self is an integer
pub fn is_integer(self) -> bool {
matches!(
self,
| Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 | Self::Isize
| Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128 | Self::Usize
| Self::Integer
)
}
/// Checks whether self is a floating point number
pub fn is_float(self) -> bool {
matches!(
self,
| Self::F8 | Self::F16 | Self::F32 | Self::F64 | Self::F128 | Self::Fsize
| Self::Float
)
}
}
// Author's note: the fsize type is a meme
impl FromStr for Primitive {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"i8" => Primitive::I8,
"i16" => Primitive::I16,
"i32" => Primitive::I32,
"i64" => Primitive::I64,
"i128" => Primitive::I128,
"isize" => Primitive::Isize,
"u8" => Primitive::U8,
"u16" => Primitive::U16,
"u32" => Primitive::U32,
"u64" => Primitive::U64,
"u128" => Primitive::U128,
"usize" => Primitive::Usize,
"f8" => Primitive::F8,
"f16" => Primitive::F16,
"f32" => Primitive::F32,
"f64" => Primitive::F64,
"f128" => Primitive::F128,
"fsize" => Primitive::Fsize,
"bool" => Primitive::Bool,
"char" => Primitive::Char,
"str" => Primitive::Str,
"never" => Primitive::Never,
_ => Err(())?,
})
}
}

View File

@@ -0,0 +1,98 @@
//! [Display] implementations for [TypeKind], [Adt], and [Intrinsic]
use super::{Adt, Primitive, TypeKind};
use crate::format_utils::*;
use cl_ast::format::FmtAdapter;
use std::fmt::{self, Display, Write};
impl Display for TypeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeKind::Inferred => write!(f, "_"),
TypeKind::Variable => write!(f, "?"),
TypeKind::Instance(def) => write!(f, "alias to #{def}"),
TypeKind::Primitive(i) => i.fmt(f),
TypeKind::Adt(a) => a.fmt(f),
TypeKind::Ref(def) => write!(f, "&{def}"),
TypeKind::Ptr(def) => write!(f, "*{def}"),
TypeKind::Slice(def) => write!(f, "slice [#{def}]"),
TypeKind::Array(def, size) => write!(f, "array [#{def}; {size}]"),
TypeKind::Tuple(defs) => {
let mut defs = defs.iter();
separate(", ", || {
let def = defs.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "#{def}"))
})(f.delimit_with("tuple (", ")"))
}
TypeKind::FnSig { args, rety } => write!(f, "fn (#{args}) -> #{rety}"),
TypeKind::Module => f.write_str("mod"),
}
}
}
impl Display for Adt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Adt::Enum(variants) => {
let mut variants = variants.iter();
separate(", ", || {
let (name, def) = variants.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{name}: #{def}"))
})(f.delimit_with("enum {", "}"))
}
Adt::Struct(members) => {
let mut members = members.iter();
separate(", ", || {
let (name, vis, def) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{vis}{name}: #{def}"))
})(f.delimit_with("struct {", "}"))
}
Adt::TupleStruct(members) => {
let mut members = members.iter();
separate(", ", || {
let (vis, def) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{vis}#{def}"))
})(f.delimit_with("struct (", ")"))
}
Adt::UnitStruct => write!(f, "struct"),
Adt::Union(variants) => {
let mut variants = variants.iter();
separate(", ", || {
let (name, def) = variants.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{name}: #{def}"))
})(f.delimit_with("union {", "}"))
}
}
}
}
impl Display for Primitive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Primitive::I8 => f.write_str("i8"),
Primitive::I16 => f.write_str("i16"),
Primitive::I32 => f.write_str("i32"),
Primitive::I64 => f.write_str("i64"),
Primitive::I128 => f.write_str("i128"),
Primitive::Isize => f.write_str("isize"),
Primitive::U8 => f.write_str("u8"),
Primitive::U16 => f.write_str("u16"),
Primitive::U32 => f.write_str("u32"),
Primitive::U64 => f.write_str("u64"),
Primitive::U128 => f.write_str("u128"),
Primitive::Usize => f.write_str("usize"),
Primitive::F8 => f.write_str("f8"),
Primitive::F16 => f.write_str("f16"),
Primitive::F32 => f.write_str("f32"),
Primitive::F64 => f.write_str("f64"),
Primitive::F128 => f.write_str("f128"),
Primitive::Fsize => f.write_str("fsize"),
Primitive::Integer => f.write_str("{integer}"),
Primitive::Float => f.write_str("{float}"),
Primitive::Bool => f.write_str("bool"),
Primitive::Char => f.write_str("char"),
Primitive::Str => f.write_str("str"),
Primitive::Never => f.write_str("!"),
}
}
}

View File

@@ -1,38 +1,38 @@
(* Conlang Expression Grammar *)
Start = File ;
Start = File EOI ;
Mutability = "mut"? ;
Visibility = "pub"? ;
File = Item* EOI ;
File = Item* ;
Attrs = ('#' '[' (Meta ',') Meta? ']')* ;
Attrs = ('#' '[' (Meta ',')* Meta? ']')* ;
Meta = Identifier ('=' Literal | '(' (Literal ',')* Literal? ')')? ;
Item = Attrs* Visibility ItemKind ;
Item = Attrs Visibility ItemKind ;
ItemKind = Const | Static | Module
| Function | Struct | Enum
| Alias | Impl ;
| Alias | Impl | Use ;
(* item *)
Const = "const" Identifier ':' Type = Expr ';' ;
Const = "const" Identifier ':' Ty '=' Expr ';' ;
Static = "static" Mutability Identifier ':' Type = Expr ';' ;
Static = "static" Mutability Identifier ':' Ty '=' Expr ';' ;
Module = "mod" Identifier ModuleKind ;
ModuleKind = '{' Item* '}' | ';' ;
Function = "fn" Identifier '(' (Param ',')* Param? ')' ('->' Type)? Block? ;
Param = Mutability Identifier ':' Type ;
Function = "fn" Identifier '(' (Param ',')* Param? ')' ('->' Ty)? (Expr | ';') ;
Param = Mutability Identifier ':' Ty ;
Struct = "struct" Identifier (StructTuple | StructBody)?;
StructBody = '{' (StructMember ',')* StructMember? '}' ;
StructTuple = TyTuple ;
StructMember = Visibility Identifier ':' Type ;
StructMember = Visibility Identifier ':' Ty ;
Enum = "enum" Identifier '{' (Variant ',')* Variant? '}' ;
Variant = Identifier (VarStruct | VarTuple | VarCLike)? ;
@@ -40,24 +40,31 @@ VarStruct = '{' (StructMember ',')* StructMember? '}' ;
VarTuple = TyTuple ;
VarCLike = '=' INTEGER ;
Alias = "type" Ty ('=' Ty)? ';' ;
Alias = "type" Identifier ('=' Ty)? ';' ;
Impl = "impl" Path '{' Item* '}' ;
(* TODO: Impl Trait for Target*)
Use = "use" '::'? UseTree ';' ;
UseTree = '*' | '{' (UseTree ',')* UseTree? '}'
| PathPart ('::' UseTree | "as" Identifier)? ;
(* type *)
Ty = Never | Empty | Path | TyTuple | TyRef | TyFn ;
Ty = Never | Empty | Path | TyArray | TySlice | TyTuple | TyRef | TyFn ;
Never = '!' ;
Empty = '(' ')' ;
TyTuple = '(' (Ty ',')* Ty? ')' ;
TyRef = ('&' | '&&')* Path ;
TyFn = "fn" TyTuple (-> Ty)? ;
TyArray = '[' Ty ';' INTEGER ']' ;
TySlice = '[' Ty ']' ;
TyRef = Amps* Path ;
Amps = '&' | '&&' ;
TyFn = "fn" TyTuple ('->' Ty)? ;
(* path *)
Path = '::'? PathPart ('::' PathPart)* ;
PathPart = "super" | "self" | Identifier ;
Path = PathPart ('::' PathPart)*
| '::' (PathPart ('::' PathPart)*)? ;
PathPart = "super" | "self" | "Self" | Identifier ;
Identifier = IDENTIFIER ;
@@ -74,51 +81,64 @@ Bool = "true" | "false" ;
(* expr *)
ExprKind = Assign | Compare | Range | Logic | Bitwise | Shift
| Factor | Term | Unary | Member | Call | Index
| Path | Literal | Array | ArrayRep | AddrOf
| Block | Group
| While | If | For | Break | Return | Continue ;
Expr = Assign ;
Assign = Path (AssignKind Assign ) | Compare ;
Assign = Path (AssignKind Assign ) | Modify ;
Modify = Path (ModifyKind Assign ) | Compare ;
Binary = Compare | Range | Logic | Bitwise | Shift | Factor | Term ;
(* Binary = Compare | Range | Logic | Bitwise | Shift | Factor | Term ; *)
Compare = Range (CompareOp Range )* ;
Range = Logic (RangeOp Logic )* ;
Logic = Bitwise (LogicOp Bitwise)* ;
Bitwise = Shift (BitwiseOp Shift )* ;
Shift = Factor (ShiftOp Factor )* ;
Factor = Term (FactorOp Term )* ;
Term = Unary (FactorOp Unary )* ;
Term = Unary (TermOp Unary )* ;
Unary = (UnaryKind)* Member ;
Unary = (UnaryKind)* Cast ;
Member = Call ('.' Call)* ;
Cast = Member ("as" Ty)? ;
Member = Call (Access)* ;
Access = '.' (Identifier ('(' Tuple? ')')? | Literal) ;
Call = Index ('(' Tuple? ')')* ;
Index = Primary ('[' Indices ']')* ;
Indices = (Expr ',')* Expr? ;
Primary = Literal | Path | Array | ArrayRep | AddrOf
| Block | Group
| If | While | For | Break | Return | Continue;
Primary = Literal | PathLike | Array | ArrayRep | AddrOf | Block | Group
| Loop | If | While | For | Break | Return | Continue;
Literal = STRING | CHARACTER | FLOAT | INTEGER | Bool ;
PathLike = Path | Structor ;
Structor = Path ':' '{' (Fielder ',')* Fielder? '}' ;
Fielder = Identifier ('=' Expr)? ;
Array = '[' (Expr ',')* Expr? ']' ;
ArrayRep = '[' Expr ';' Expr ']' ;
AddrOf = ('&' | '&&')* Mutability? Expr ;
AddrOf = Amps Amps* Mutability Expr ;
Block = '{' Stmt* '}';
Group = '(' (Empty | Expr | Tuple) ')' ;
Group = Empty | '(' (Expr | Tuple) ')' ;
Tuple = (Expr ',')* Expr? ;
Empty = ;
Match = "match" { (MatchArm ',')* MatchArm? } ;
MatchArm = Pattern '=>' Expr ;
Pattern = Path
| Literal
| '&' "mut"? Pattern
| '(' (Pattern ',')* (Pattern | '..' )? ')'
| '[' (Pattern ',')* (Pattern | '..' Identifier?)? ']'
| StructPattern
;
Loop = "loop" Block ;
While = "while" Expr Block Else ;
If = "if" Expr Block Else ;
For = "for" Identifier "in" Expr Block Else ;
@@ -127,11 +147,9 @@ Break = "break" Expr ;
Return = "return" Expr ;
Continue = "continue" ;
AssignKind = '=' | '+=' | '-=' | '*=' | '/=' |
'&=' | '|=' | '^=' |'<<=' |'>>=' ;
AssignKind = '=' ;
ModifyKind = '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' |'<<=' |'>>=' ;
BinaryKind = CompareOp | RangeOp | LogicOp | BitwiseOp
| ShiftOp | TermOp | FactorOp ;
CompareOp = '<' | '<=' | '==' | '!=' | '>=' | '>' ;
RangeOp = '..' | '..=' ;
LogicOp = '&&' | '||' | '^^' ;

Some files were not shown because too many files have changed in this diff Show More