340 Commits
v0.0.5 ... 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
138 changed files with 18817 additions and 6884 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"

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,20 +1,24 @@
[workspace]
members = [
"cl-repl",
"cl-typeck",
"cl-interpret",
"cl-structures",
"cl-token",
"cl-ast",
"cl-parser",
"cl-lexer",
"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.5"
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,643 +0,0 @@
//! Implementations of AST nodes and traits
use super::*;
mod display {
//! Implements [Display] for [AST](super::super) Types
use super::*;
pub use delimiters::*;
use std::{
borrow::Borrow,
fmt::{Display, Write},
};
mod delimiters {
#![allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub struct Delimiters<'t> {
pub open: &'t str,
pub close: &'t 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: "]" };
}
fn delimit<'a>(
func: impl Fn(&mut std::fmt::Formatter<'_>) -> std::fmt::Result + 'a,
delim: Delimiters<'a>,
) -> impl Fn(&mut std::fmt::Formatter<'_>) -> std::fmt::Result + 'a {
move |f| {
write!(f, "{}", delim.open)?;
func(f)?;
write!(f, "{}", delim.close)
}
}
fn separate<'iterable, I>(
iterable: &'iterable [I],
sep: impl Display + 'iterable,
) -> impl Fn(&mut std::fmt::Formatter<'_>) -> std::fmt::Result + 'iterable
where
I: Display,
{
move |f| {
for (idx, item) in iterable.iter().enumerate() {
if idx > 0 {
write!(f, "{sep}")?;
}
item.fmt(f)?;
}
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 File {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
separate(&self.items, "\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)?;
delimit(separate(meta, ", "), INLINE_SQUARE)(f)?;
"\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) => delimit(separate(args, ", "), INLINE_PARENS)(f),
}
}
}
impl Display for Item {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { extents: _, attrs, vis, kind } = self;
attrs.fmt(f)?;
vis.fmt(f)?;
match kind {
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),
}
}
}
impl Display for Alias {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { to, from } = self;
match from {
Some(from) => write!(f, "type {to} = {from};"),
None => write!(f, "type {to};"),
}
}
}
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, kind } = self;
write!(f, "mod {name}{kind}")
}
}
impl Display for ModuleKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ModuleKind::Inline(items) => {
' '.fmt(f)?;
delimit(|f| items.fmt(f), BRACES)(f)
}
ModuleKind::Outline => ';'.fmt(f),
}
}
}
impl Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, args, body, rety } = self;
write!(f, "fn {name} ")?;
delimit(separate(args, ", "), INLINE_PARENS)(f)?;
if let Some(rety) = rety {
write!(f, " -> {rety}")?;
}
match body {
Some(body) => write!(f, " {body}"),
None => ';'.fmt(f),
}
}
}
impl Display for Param {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { mutability, name, ty } = self;
write!(f, "{mutability}{name}: {ty}")
}
}
impl Display for Struct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, kind } = self;
write!(f, "struct {name}{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) => delimit(separate(v, ", "), INLINE_PARENS)(f),
StructKind::Struct(v) => delimit(separate(v, ",\n"), SPACED_BRACES)(f),
}
}
}
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, kind } = self;
write!(f, "enum {name}{kind}")
}
}
impl Display for EnumKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EnumKind::NoVariants => ';'.fmt(f),
EnumKind::Variants(v) => delimit(separate(v, ",\n"), SPACED_BRACES)(f),
}
}
}
impl Display for Variant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, kind } = self;
write!(f, "{name}{kind}")
}
}
impl Display for VariantKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VariantKind::Plain => Ok(()),
VariantKind::CLike(n) => write!(f, " = {n}"),
VariantKind::Tuple(v) => delimit(separate(v, ", "), INLINE_PARENS)(f),
VariantKind::Struct(v) => delimit(separate(v, ", "), INLINE_BRACES)(f),
}
}
}
impl Display for Impl {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!("impl Display for Impl")
}
}
impl Display for Ty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
TyKind::Never => "!".fmt(f),
TyKind::Empty => "()".fmt(f),
TyKind::SelfTy => "Self".fmt(f),
TyKind::Path(v) => v.fmt(f),
TyKind::Tuple(v) => v.fmt(f),
TyKind::Ref(v) => v.fmt(f),
TyKind::Fn(v) => v.fmt(f),
}
}
}
impl Display for TyTuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
delimit(separate(&self.types, ", "), INLINE_PARENS)(f)
}
}
impl Display for TyRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { count: _, to } = self;
for _ in 0..self.count {
f.write_char('&')?;
}
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}")?;
match rety {
Some(v) => write!(f, " -> {v}"),
None => Ok(()),
}
}
}
impl Display for Stmt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Stmt { extents: _, kind, semi } = self;
match kind {
StmtKind::Empty => Ok(()),
StmtKind::Local(v) => v.fmt(f),
StmtKind::Item(v) => v.fmt(f),
StmtKind::Expr(v) => v.fmt(f),
}?;
match semi {
Semi::Terminated => ';'.fmt(f),
Semi::Unterminated => Ok(()),
}
}
}
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 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::Assign(v) => v.fmt(f),
ExprKind::Binary(v) => v.fmt(f),
ExprKind::Unary(v) => v.fmt(f),
ExprKind::Index(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::Empty => "()".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 Assign {
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 AssignKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AssignKind::Plain => "=",
AssignKind::Mul => "*=",
AssignKind::Div => "/=",
AssignKind::Rem => "%=",
AssignKind::Add => "+=",
AssignKind::Sub => "-=",
AssignKind::And => "&=",
AssignKind::Or => "|=",
AssignKind::Xor => "^=",
AssignKind::Shl => "<<=",
AssignKind::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::Dot => write!(f, "{head}{kind}{tail}"),
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::Dot => ".",
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::Deref => "*",
UnaryKind::Neg => "-",
UnaryKind::Not => "!",
UnaryKind::At => "@",
UnaryKind::Tilde => "~",
}
.fmt(f)
}
}
impl Display for Tuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
delimit(separate(&self.exprs, ", "), INLINE_PARENS)(f)
}
}
impl Display for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head, indices } = self;
write!(f, "{head}")?;
for indices in indices {
indices.fmt(f)?;
}
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::SelfKw => "self".fmt(f),
PathPart::Ident(id) => id.fmt(f),
}
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.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}'"),
Literal::Int(v) => v.fmt(f),
Literal::String(v) => write!(f, "\"{v}\""),
}
}
}
impl Display for Array {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
delimit(separate(&self.values, ", "), INLINE_SQUARE)(f)
}
}
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 { count, mutable, expr } = self;
for _ in 0..*count {
f.write_char('&')?;
}
write!(f, "{mutable}{expr}")
}
}
impl Display for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
delimit(separate(&self.stmts, "\n"), BRACES)(f)
}
}
impl Display for Group {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({})", self.expr)
}
}
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(()),
}
}
}
}
mod convert {
//! Converts between major enums and enum variants
use super::*;
impl<T: AsRef<str>> From<T> for Identifier {
fn from(value: T) -> Self {
Identifier(value.as_ref().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,
}
impl From for StructKind {
Vec<Ty> => StructKind::Tuple,
// TODO: Struct members in struct
}
impl From for EnumKind {
Vec<Variant> => EnumKind::Variants,
}
impl From for VariantKind {
u128 => VariantKind::CLike,
Vec<Ty> => VariantKind::Tuple,
// TODO: enum struct variants
}
impl From for TyKind {
Path => TyKind::Path,
TyTuple => TyKind::Tuple,
TyRef => TyKind::Ref,
TyFn => TyKind::Fn,
}
impl From for StmtKind {
Let => StmtKind::Local,
Item => StmtKind::Item,
Expr => StmtKind::Expr,
}
impl From for ExprKind {
Assign => ExprKind::Assign,
Binary => ExprKind::Binary,
Unary => ExprKind::Unary,
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,
Continue => ExprKind::Continue,
}
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()) }
}
}
}

View File

@@ -1,106 +0,0 @@
use std::{
fmt::{Result as FmtResult, Write as FmtWrite},
io::{Result as IoResult, Write as IoWrite},
};
/// Trait which adds a function to [fmt Writers](FmtWrite) to turn them into [Prettifier]
pub trait FmtPretty: FmtWrite {
/// Indents code according to the number of matched curly braces
fn pretty(self) -> Prettifier<'static, Self>
where Self: Sized {
Prettifier::new(self)
}
}
/// Trait which adds a function to [io Writers](IoWrite) to turn them into [Prettifier]
pub trait IoPretty: IoWrite {
/// Indents code according to the number of matched curly braces
fn pretty(self) -> Prettifier<'static, Self>
where Self: Sized;
}
impl<W: FmtWrite> FmtPretty for W {}
impl<W: IoWrite> IoPretty for W {
fn pretty(self) -> Prettifier<'static, Self> {
Prettifier::new(self)
}
}
/// Intercepts calls to either [std::io::Write] or [std::fmt::Write],
/// and inserts indentation between matched parentheses
pub struct Prettifier<'i, T: ?Sized> {
level: isize,
indent: &'i str,
writer: T,
}
impl<'i, W> Prettifier<'i, W> {
pub fn new(writer: W) -> Self {
Self { level: 0, indent: " ", writer }
}
pub fn with_indent(indent: &'i str, writer: W) -> Self {
Self { level: 0, indent, writer }
}
}
impl<'i, W: FmtWrite> Prettifier<'i, W> {
#[inline]
fn fmt_write_indentation(&mut self) -> FmtResult {
let Self { level, indent, writer } = self;
for _ in 0..*level {
writer.write_str(indent)?;
}
Ok(())
}
}
impl<'i, W: IoWrite> Prettifier<'i, W> {
pub fn io_write_indentation(&mut self) -> IoResult<usize> {
let Self { level, indent, writer } = self;
let mut count = 0;
for _ in 0..*level {
count += writer.write(indent.as_bytes())?;
}
Ok(count)
}
}
impl<'i, W: FmtWrite> FmtWrite for Prettifier<'i, W> {
fn write_str(&mut self, s: &str) -> FmtResult {
for s in s.split_inclusive(['{', '}']) {
match s.as_bytes().last() {
Some(b'{') => self.level += 1,
Some(b'}') => self.level -= 1,
_ => (),
}
for s in s.split_inclusive('\n') {
self.writer.write_str(s)?;
if let Some(b'\n') = s.as_bytes().last() {
self.fmt_write_indentation()?;
}
}
}
Ok(())
}
}
impl<'i, W: IoWrite> IoWrite for Prettifier<'i, W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut size = 0;
for buf in buf.split_inclusive(|b| b"{}".contains(b)) {
match buf.last() {
Some(b'{') => self.level += 1,
Some(b'}') => self.level -= 1,
_ => (),
}
for buf in buf.split_inclusive(|b| b'\n' == *b) {
size += self.writer.write(buf)?;
if let Some(b'\n') = buf.last() {
self.io_write_indentation()?;
}
}
}
Ok(size)
}
fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush()
}
}

View File

@@ -1,539 +0,0 @@
//! # 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
//! - [AssignKind], [BinaryKind], and [UnaryKind] operators
//! - [Ty] and [TyKind]: Type qualifiers
//! - [Path]: Path expressions
#![warn(clippy::all)]
#![feature(decl_macro)]
use cl_structures::span::*;
pub mod ast_impl;
pub mod format;
/// Whether a binding ([Static] or [Let]) or reference is mutable or not
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Mutability {
#[default]
Not,
Mut,
}
/// Whether an [Item] is visible outside of the current [Module]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Visibility {
#[default]
Private,
Public,
}
/// A list of [Item]s
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct File {
pub items: Vec<Item>,
}
// Metadata decorators
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Attrs {
pub meta: Vec<Meta>,
}
/// A metadata decorator
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Meta {
pub name: Identifier,
pub kind: MetaKind,
}
/// Information attached to [Meta]data
#[derive(Clone, Debug, PartialEq, Eq)]
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)]
pub struct Item {
pub extents: Span,
pub attrs: Attrs,
pub vis: Visibility,
pub kind: ItemKind,
}
/// What kind of [Item] is this?
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ItemKind {
// TODO: Import declaration ("use") item
// 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 alias to another [Ty]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Alias {
pub to: Identifier,
pub from: Option<Box<Ty>>,
}
/// A compile-time constant
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Const {
pub name: Identifier,
pub ty: Box<Ty>,
pub init: Box<Expr>,
}
/// A `static` variable
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Static {
pub mutable: Mutability,
pub name: Identifier,
pub ty: Box<Ty>,
pub init: Box<Expr>,
}
/// An ordered collection of [Items](Item)
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Module {
pub name: Identifier,
pub kind: ModuleKind,
}
/// The contents of a [Module], if they're in the same file
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ModuleKind {
Inline(File),
Outline,
}
/// Code, and the interface to that code
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Function {
pub name: Identifier,
pub args: Vec<Param>,
pub body: Option<Block>,
pub rety: Option<Box<Ty>>,
}
/// A single parameter for a [Function]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Param {
pub mutability: Mutability,
pub name: Identifier,
pub ty: Box<Ty>,
}
/// A user-defined product type
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Struct {
pub name: Identifier,
pub kind: StructKind,
}
/// Either a [Struct]'s [StructMember]s or tuple [Ty]pes, if present.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StructKind {
Empty,
Tuple(Vec<Ty>),
Struct(Vec<StructMember>),
}
/// The [Visibility], [Identifier], and [Ty]pe of a single [Struct] member
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StructMember {
pub vis: Visibility,
pub name: Identifier,
pub ty: Ty,
}
/// A user-defined sum type
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Enum {
pub name: Identifier,
pub kind: EnumKind,
}
/// An [Enum]'s [Variant]s, if it has a variant block
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EnumKind {
/// Represents an enum with no variants
NoVariants,
Variants(Vec<Variant>),
}
/// A single [Enum] variant
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Variant {
pub name: Identifier,
pub kind: VariantKind,
}
/// Whether the [Variant] has a C-like constant value, a tuple, or [StructMember]s
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VariantKind {
Plain,
CLike(u128),
Tuple(Vec<Ty>),
Struct(Vec<StructMember>),
}
/// Sub-[items](Item) (associated functions, etc.) for a [Ty]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Impl {
pub target: Ty,
pub body: Vec<Item>,
}
// TODO: `impl` Trait for <Target> { }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ImplKind {
Type(Box<Ty>),
Trait { impl_trait: Path, for_type: Box<Ty> },
}
/// A type expression
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ty {
pub extents: Span,
pub kind: TyKind,
}
/// Information about a [Ty]pe expression
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TyKind {
Never,
Empty,
SelfTy,
Path(Path),
Tuple(TyTuple),
Ref(TyRef),
Fn(TyFn),
}
/// A tuple of [Ty]pes
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TyTuple {
pub types: Vec<Ty>,
}
/// A [Ty]pe-reference expression as (number of `&`, [Path])
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TyRef {
pub count: u16,
pub to: Path,
}
/// The args and return value for a function pointer [Ty]pe
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TyFn {
pub args: TyTuple,
pub rety: Option<Box<Ty>>,
}
/// A path to an [Item] in the [Module] tree
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Path {
pub absolute: bool,
pub parts: Vec<PathPart>,
}
/// A single component of a [Path]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PathPart {
SuperKw,
SelfKw,
Ident(Identifier),
}
// TODO: Capture token?
/// A name
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Identifier(pub String);
/// An abstract statement, and associated metadata
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Stmt {
pub extents: Span,
pub kind: StmtKind,
pub semi: Semi,
}
/// Whether or not a [Stmt] is followed by a semicolon
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Semi {
Terminated,
Unterminated,
}
/// Whether the [Stmt] is a [Let], [Item], or [Expr] statement
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StmtKind {
Empty,
Local(Let),
Item(Box<Item>),
Expr(Box<Expr>),
}
/// A local variable declaration [Stmt]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Let {
pub mutable: Mutability,
pub name: Identifier,
pub ty: Option<Box<Ty>>,
pub init: Option<Box<Expr>>,
}
/// An expression, the beating heart of the language
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Expr {
pub extents: Span,
pub kind: ExprKind,
}
/// Any of the different [Expr]essions
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExprKind {
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
Assign(Assign),
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
Binary(Binary),
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
Unary(Unary),
/// An Array [Index] expression: a[10, 20, 30]
Index(Index),
/// A [path expression](Path): `::`? [PathPart] (`::` [PathPart])*
Path(Path),
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
Literal(Literal),
/// 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 [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
Block(Block),
/// An empty expression: `(` `)`
Empty,
/// A [Grouping](Group) expression `(` [`Expr`] `)`
Group(Group),
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
Tuple(Tuple),
/// 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(Continue),
}
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Assign {
pub kind: AssignKind,
pub parts: Box<(ExprKind, ExprKind)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssignKind {
/// Standard Assignment with no read-back
Plain,
And,
Or,
Xor,
Shl,
Shr,
Add,
Sub,
Mul,
Div,
Rem,
}
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Binary {
pub kind: BinaryKind,
pub parts: Box<(ExprKind, ExprKind)>,
}
/// A [Binary] operator
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryKind {
Lt,
LtEq,
Equal,
NotEq,
GtEq,
Gt,
RangeExc,
RangeInc,
LogAnd,
LogOr,
LogXor,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
Add,
Sub,
Mul,
Div,
Rem,
Dot,
Call,
}
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Unary {
pub kind: UnaryKind,
pub tail: Box<ExprKind>,
}
/// A [Unary] operator
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnaryKind {
Deref,
Neg,
Not,
/// Unused
At,
/// Unused
Tilde,
}
/// A repeated [Index] expression: a[10, 20, 30][40, 50, 60]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Index {
pub head: Box<ExprKind>,
pub indices: Vec<Expr>,
}
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
Bool(bool),
Char(char),
Int(u128),
String(String),
}
/// An [Array] literal: `[` [`Expr`] (`,` [`Expr`])\* `]`
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Array {
pub values: Vec<Expr>,
}
/// An Array literal constructed with [repeat syntax](ArrayRep)
/// `[` [Expr] `;` [Literal] `]`
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArrayRep {
pub value: Box<ExprKind>,
pub repeat: Box<ExprKind>,
}
/// An address-of expression: `&` `mut`? [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddrOf {
pub count: usize,
pub mutable: Mutability,
pub expr: Box<ExprKind>,
}
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Block {
pub stmts: Vec<Stmt>,
}
/// A [Grouping](Group) expression `(` [`Expr`] `)`
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Group {
pub expr: Box<ExprKind>,
}
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tuple {
pub exprs: Vec<Expr>,
}
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
#[derive(Clone, Debug, PartialEq, Eq)]
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)]
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)]
pub struct For {
pub bind: Identifier, // TODO: Patterns?
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)]
pub struct Else {
pub body: Option<Box<Expr>>,
}
/// A [Break] expression: `break` [`Expr`]?
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Break {
pub body: Option<Box<Expr>>,
}
/// A [Return] expression `return` [`Expr`]?
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Return {
pub body: Option<Box<Expr>>,
}
/// A continue expression: `continue`
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Continue;

View File

@@ -1,239 +0,0 @@
//! Implementations of built-in functions
use super::{
env::Environment,
error::{Error, IResult},
temp_type_impl::ConValue,
BuiltIn, Callable,
};
use std::io::{stdout, Write};
builtins! {
const MISC;
/// Unstable variadic print function
pub fn print<_, args> () -> IResult<ConValue> {
let mut out = stdout().lock();
for arg in args {
write!(out, "{arg}").ok();
}
writeln!(out).ok();
Ok(ConValue::Empty)
}
/// Prints the [Debug](std::fmt::Debug) version of the input values
pub fn dbg<_, args> () -> IResult<ConValue> {
let mut out = stdout().lock();
for arg in args {
writeln!(out, "{arg:?}").ok();
}
Ok(args.into())
}
/// Dumps info from the environment
pub fn dump<env, _>() -> IResult<ConValue> {
println!("{}", *env);
Ok(ConValue::Empty)
}
}
builtins! {
const BINARY;
/// Multiplication `a * b`
pub fn mul(lhs, rhs) -> IResult<ConValue> {
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`
pub fn div(lhs, rhs) -> IResult<ConValue> {
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`
pub fn rem(lhs, rhs) -> IResult<ConValue> {
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`
pub fn add(lhs, rhs) -> IResult<ConValue> {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
(ConValue::String(a), ConValue::String(b)) => ConValue::String(a.to_string() + b),
_ => Err(Error::TypeError)?
})
}
/// Subtraction `a - b`
pub fn sub(lhs, rhs) -> IResult<ConValue> {
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`
pub fn shl(lhs, rhs) -> IResult<ConValue> {
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`
pub fn shr(lhs, rhs) -> IResult<ConValue> {
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`
pub fn and(lhs, rhs) -> IResult<ConValue> {
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`
pub fn or(lhs, rhs) -> IResult<ConValue> {
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`
pub fn xor(lhs, rhs) -> IResult<ConValue> {
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)?,
})
}
/// Tests whether `a < b`
pub fn lt(lhs, rhs) -> IResult<ConValue> {
cmp!(lhs, rhs, false, <)
}
/// Tests whether `a <= b`
pub fn lt_eq(lhs, rhs) -> IResult<ConValue> {
cmp!(lhs, rhs, true, <=)
}
/// Tests whether `a == b`
pub fn eq(lhs, rhs) -> IResult<ConValue> {
cmp!(lhs, rhs, true, ==)
}
/// Tests whether `a != b`
pub fn neq(lhs, rhs) -> IResult<ConValue> {
cmp!(lhs, rhs, false, !=)
}
/// Tests whether `a <= b`
pub fn gt_eq(lhs, rhs) -> IResult<ConValue> {
cmp!(lhs, rhs, true, >=)
}
/// Tests whether `a < b`
pub fn gt(lhs, rhs) -> IResult<ConValue> {
cmp!(lhs, rhs, false, >)
}
}
builtins! {
const RANGE;
/// Exclusive Range `a..b`
pub fn range_exc(lhs, rhs) -> IResult<ConValue> {
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
Err(Error::TypeError)?
};
Ok(ConValue::RangeExc(lhs, rhs.saturating_sub(1)))
}
/// Inclusive Range `a..=b`
pub fn range_inc(lhs, rhs) -> IResult<ConValue> {
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
Err(Error::TypeError)?
};
Ok(ConValue::RangeInc(lhs, rhs))
}
}
builtins! {
const UNARY;
/// Negates the ConValue
pub fn neg(tail) -> IResult<ConValue> {
Ok(match tail {
ConValue::Empty => ConValue::Empty,
ConValue::Int(v) => ConValue::Int(-v),
_ => Err(Error::TypeError)?,
})
}
/// Inverts the ConValue
pub fn not(tail) -> IResult<ConValue> {
Ok(match tail {
ConValue::Empty => ConValue::Empty,
ConValue::Int(v) => ConValue::Int(!v),
ConValue::Bool(v) => ConValue::Bool(!v),
_ => Err(Error::TypeError)?,
})
}
}
/// Turns an argument slice into an array with the (inferred) correct number of elements
pub fn to_args<const N: usize>(args: &[ConValue]) -> IResult<&[ConValue; N]> {
args.try_into()
.map_err(|_| Error::ArgNumber { want: N, got: args.len() })
}
/// Turns function definitions into ZSTs which implement [Callable] and [BuiltIn]
macro builtins (
$(prefix = $prefix:literal)?
const $defaults:ident $( = [$($additional_builtins:expr),*$(,)?])?;
$(
$(#[$meta:meta])*$vis:vis fn $name:ident$(<$env:tt, $args:tt>)? ( $($($arg:tt),+$(,)?)? ) $(-> $rety:ty)?
$body:block
)*
) {
/// Builtins to load when a new interpreter is created
pub const $defaults: &[&dyn BuiltIn] = &[$(&$name,)* $($additional_builtins)*];
$(
$(#[$meta])* #[allow(non_camel_case_types)] #[derive(Clone, Debug)]
/// ```rust,ignore
#[doc = stringify!(builtin! fn $name($($($arg),*)?) $(-> $rety)? $body)]
/// ```
$vis struct $name;
impl BuiltIn for $name {
fn description(&self) -> &str { concat!("builtin ", stringify!($name), stringify!(($($($arg),*)?) )) }
}
impl Callable for $name {
#[allow(unused)]
fn call(&self, env: &mut Environment, args: &[ConValue]) $(-> $rety)? {
// println!("{}", stringify!($name), );
$(let $env = env;
let $args = args;)?
$(let [$($arg),*] = to_args(args)?;)?
$body
}
fn name(&self) -> &str { stringify!($name) }
}
)*
}
/// Templates comparison functions for [ConValue]
macro cmp ($a:expr, $b:expr, $empty:literal, $op:tt) {
match ($a, $b) {
(ConValue::Empty, ConValue::Empty) => Ok(ConValue::Bool($empty)),
(ConValue::Int(a), ConValue::Int(b)) => Ok(ConValue::Bool(a $op b)),
(ConValue::Bool(a), ConValue::Bool(b)) => Ok(ConValue::Bool(a $op b)),
(ConValue::Char(a), ConValue::Char(b)) => Ok(ConValue::Bool(a $op b)),
(ConValue::String(a), ConValue::String(b)) => Ok(ConValue::Bool(a $op b)),
_ => Err(Error::TypeError)
}
}

View File

@@ -1,447 +0,0 @@
//! A work-in-progress tree walk interpreter for Conlang
//!
//! Currently, major parts of the interpreter are not yet implemented, and major parts will never be
//! implemented in its current form. Namely, since no [ConValue] has a stable location, it's
//! meaningless to get a pointer to one, and would be undefined behavior to dereference a pointer to
//! one in any situation.
use std::borrow::Borrow;
use super::*;
use cl_ast::*;
/// A work-in-progress tree walk interpreter for Conlang
pub trait Interpret {
/// Interprets this thing in the given [`Environment`].
///
/// Everything returns a value!™
fn interpret(&self, env: &mut Environment) -> IResult<ConValue>;
}
impl Interpret for File {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
for item in &self.items {
item.interpret(env)?;
}
Ok(ConValue::Empty)
}
}
impl Interpret for Item {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
match &self.kind {
ItemKind::Alias(item) => item.interpret(env),
ItemKind::Const(item) => item.interpret(env),
ItemKind::Static(item) => item.interpret(env),
ItemKind::Module(item) => item.interpret(env),
ItemKind::Function(item) => item.interpret(env),
ItemKind::Struct(item) => item.interpret(env),
ItemKind::Enum(item) => item.interpret(env),
ItemKind::Impl(item) => item.interpret(env),
}
}
}
impl Interpret for Alias {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("Interpret type alias in {env}")
}
}
impl Interpret for Const {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("interpret const in {env}")
}
}
impl Interpret for Static {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("interpret static in {env}")
}
}
impl Interpret for Module {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
// TODO: Enter this module's namespace
match &self.kind {
ModuleKind::Inline(file) => file.interpret(env),
ModuleKind::Outline => todo!("Load and parse external files"),
}
}
}
impl Interpret for Function {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
// register the function in the current environment
env.insert_fn(self);
Ok(ConValue::Empty)
}
}
impl Interpret for Struct {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("Interpret structs in {env}")
}
}
impl Interpret for Enum {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("Interpret enums in {env}")
}
}
impl Interpret for Impl {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("Enter a struct's namespace and insert function definitions into it in {env}");
}
}
impl Interpret for Stmt {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { extents: _, kind, semi } = self;
let out = match kind {
StmtKind::Empty => ConValue::Empty,
StmtKind::Local(stmt) => stmt.interpret(env)?,
StmtKind::Item(stmt) => stmt.interpret(env)?,
StmtKind::Expr(stmt) => stmt.interpret(env)?,
};
Ok(match semi {
Semi::Terminated => ConValue::Empty,
Semi::Unterminated => out,
})
}
}
impl Interpret for Let {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Let { mutable: _, name: Identifier(name), ty: _, init } = self;
let init = init.as_ref().map(|i| i.interpret(env)).transpose()?;
env.insert(name, init);
Ok(ConValue::Empty)
}
}
impl Interpret for Expr {
#[inline]
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { extents: _, kind } = self;
kind.interpret(env)
}
}
impl Interpret for ExprKind {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
match self {
ExprKind::Assign(v) => v.interpret(env),
ExprKind::Binary(v) => v.interpret(env),
ExprKind::Unary(v) => v.interpret(env),
ExprKind::Index(v) => v.interpret(env),
ExprKind::Path(v) => v.interpret(env),
ExprKind::Literal(v) => v.interpret(env),
ExprKind::Array(v) => v.interpret(env),
ExprKind::ArrayRep(v) => v.interpret(env),
ExprKind::AddrOf(v) => v.interpret(env),
ExprKind::Block(v) => v.interpret(env),
ExprKind::Empty => Ok(ConValue::Empty),
ExprKind::Group(v) => v.interpret(env),
ExprKind::Tuple(v) => v.interpret(env),
ExprKind::While(v) => v.interpret(env),
ExprKind::If(v) => v.interpret(env),
ExprKind::For(v) => v.interpret(env),
ExprKind::Break(v) => v.interpret(env),
ExprKind::Return(v) => v.interpret(env),
ExprKind::Continue(v) => v.interpret(env),
}
}
}
impl Interpret for Assign {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Assign { kind: op, parts } = self;
let (head, tail) = parts.borrow();
// Resolve the head pattern
let head = match &head {
ExprKind::Path(Path { parts, .. }) if parts.len() == 1 => {
match parts.last().expect("parts should not be empty") {
PathPart::SuperKw => Err(Error::NotAssignable)?,
PathPart::SelfKw => todo!("Assignment to `self`"),
PathPart::Ident(Identifier(s)) => s,
}
}
ExprKind::Index(_) => todo!("Assignment to an index operation"),
ExprKind::Path(_) => todo!("Path expression resolution (IMPORTANT)"),
ExprKind::Empty | ExprKind::Group(_) | ExprKind::Tuple(_) => {
todo!("Pattern Destructuring?")
}
_ => Err(Error::NotAssignable)?,
};
// Get the initializer and the tail
let init = tail.interpret(env)?;
let target = env.get_mut(head)?;
if let AssignKind::Plain = op {
use std::mem::discriminant as variant;
// runtime typecheck
match target {
Some(value) if variant(value) == variant(&init) => {
*value = init;
}
value @ None => *value = Some(init),
_ => Err(Error::TypeError)?,
}
return Ok(ConValue::Empty);
}
let Some(target) = target else {
return Err(Error::NotInitialized(head.into()));
};
match op {
AssignKind::Add => target.add_assign(init)?,
AssignKind::Sub => target.sub_assign(init)?,
AssignKind::Mul => target.mul_assign(init)?,
AssignKind::Div => target.div_assign(init)?,
AssignKind::Rem => target.rem_assign(init)?,
AssignKind::And => target.bitand_assign(init)?,
AssignKind::Or => target.bitor_assign(init)?,
AssignKind::Xor => target.bitxor_assign(init)?,
AssignKind::Shl => target.shl_assign(init)?,
AssignKind::Shr => target.shr_assign(init)?,
_ => (),
}
Ok(ConValue::Empty)
}
}
impl Interpret for Binary {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Binary { kind, parts } = self;
let (head, tail) = parts.borrow();
let head = head.interpret(env)?;
// Short-circuiting ops
match kind {
BinaryKind::LogAnd => {
return if head.truthy()? {
tail.interpret(env)
} else {
Ok(head)
}; // Short circuiting
}
BinaryKind::LogOr => {
return if !head.truthy()? {
tail.interpret(env)
} else {
Ok(head)
}; // Short circuiting
}
BinaryKind::LogXor => {
return Ok(ConValue::Bool(
head.truthy()? ^ tail.interpret(env)?.truthy()?,
));
}
_ => {}
}
let tail = tail.interpret(env)?;
match kind {
BinaryKind::Mul => env.call("mul", &[head, tail]),
BinaryKind::Div => env.call("div", &[head, tail]),
BinaryKind::Rem => env.call("rem", &[head, tail]),
BinaryKind::Add => env.call("add", &[head, tail]),
BinaryKind::Sub => env.call("sub", &[head, tail]),
BinaryKind::Shl => env.call("shl", &[head, tail]),
BinaryKind::Shr => env.call("shr", &[head, tail]),
BinaryKind::BitAnd => env.call("and", &[head, tail]),
BinaryKind::BitOr => env.call("or", &[head, tail]),
BinaryKind::BitXor => env.call("xor", &[head, tail]),
BinaryKind::RangeExc => env.call("range_exc", &[head, tail]),
BinaryKind::RangeInc => env.call("range_inc", &[head, tail]),
BinaryKind::Lt => env.call("lt", &[head, tail]),
BinaryKind::LtEq => env.call("lt_eq", &[head, tail]),
BinaryKind::Equal => env.call("eq", &[head, tail]),
BinaryKind::NotEq => env.call("neq", &[head, tail]),
BinaryKind::GtEq => env.call("gt_eq", &[head, tail]),
BinaryKind::Gt => env.call("gt", &[head, tail]),
BinaryKind::Dot => todo!("search within a type's namespace!"),
BinaryKind::Call => match tail {
ConValue::Empty => head.call(env, &[]),
ConValue::Tuple(args) => head.call(env, &args),
_ => Err(Error::TypeError),
},
_ => Ok(head),
}
}
}
impl Interpret for Unary {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Unary { kind, tail } = self;
let operand = tail.interpret(env)?;
match kind {
UnaryKind::Deref => env.call("deref", &[operand]),
UnaryKind::Neg => env.call("neg", &[operand]),
UnaryKind::Not => env.call("not", &[operand]),
UnaryKind::At => {
println!("{operand}");
Ok(operand)
}
UnaryKind::Tilde => unimplemented!("Tilde operator"),
}
}
}
impl Interpret for Index {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { head, indices } = self;
let mut head = head.interpret(env)?;
for index in indices {
head = head.index(&index.interpret(env)?)?;
}
Ok(head)
}
}
impl Interpret for Path {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { absolute: _, parts } = self;
if parts.len() == 1 {
match parts.last().expect("parts should not be empty") {
PathPart::SuperKw | PathPart::SelfKw => todo!("Path navigation"),
PathPart::Ident(Identifier(s)) => env.get(s).cloned(),
}
} else {
todo!("Path navigation!")
}
}
}
impl Interpret for Literal {
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
Ok(match self {
Literal::String(value) => ConValue::from(value.as_str()),
Literal::Char(value) => ConValue::Char(*value),
Literal::Bool(value) => ConValue::Bool(*value),
// Literal::Float(value) => todo!("Float values in interpreter: {value:?}"),
Literal::Int(value) => ConValue::Int(*value as _),
})
}
}
impl Interpret for Array {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { values } = self;
let mut out = vec![];
for expr in values {
out.push(expr.interpret(env)?)
}
Ok(ConValue::Array(out))
}
}
impl Interpret for ArrayRep {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { value, repeat } = self;
let repeat = match repeat.interpret(env)? {
ConValue::Int(v) => v,
_ => Err(Error::TypeError)?,
};
let value = value.interpret(env)?;
Ok(ConValue::Array(vec![value; repeat as usize]))
}
}
impl Interpret for AddrOf {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { count: _, mutable: _, expr } = self;
// this is stupid
todo!("Create reference\nfrom expr: {expr}\nin env:\n{env}\n")
}
}
impl Interpret for Block {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { stmts } = self;
let mut env = env.frame("block");
let mut out = ConValue::Empty;
for stmt in stmts {
out = stmt.interpret(&mut env)?;
}
Ok(out)
}
}
impl Interpret for Group {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { expr } = self;
expr.interpret(env)
}
}
impl Interpret for Tuple {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { exprs } = self;
Ok(ConValue::Tuple(exprs.iter().try_fold(
vec![],
|mut out, element| {
out.push(element.interpret(env)?);
Ok(out)
},
)?))
}
}
impl Interpret for While {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { cond, pass, fail } = self;
while cond.interpret(env)?.truthy()? {
match pass.interpret(env) {
Err(Error::Break(value)) => return Ok(value),
Err(Error::Continue) => continue,
e => e?,
};
}
fail.interpret(env)
}
}
impl Interpret for If {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { cond, pass, fail } = self;
if cond.interpret(env)?.truthy()? {
pass.interpret(env)
} else {
fail.interpret(env)
}
}
}
impl Interpret for For {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { bind: Identifier(name), cond, pass, fail } = self;
// TODO: A better iterator model
let bounds = match cond.interpret(env)? {
ConValue::RangeExc(a, b) => a..=b,
ConValue::RangeInc(a, b) => a..=b,
_ => Err(Error::TypeError)?,
};
{
let mut env = env.frame("loop variable");
for loop_var in bounds {
env.insert(name, Some(loop_var.into()));
match pass.interpret(&mut env) {
Err(Error::Break(value)) => return Ok(value),
Err(Error::Continue) => continue,
result => result?,
};
}
}
fail.interpret(env)
}
}
impl Interpret for Else {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { body } = self;
match body {
Some(body) => body.interpret(env),
None => Ok(ConValue::Empty),
}
}
}
impl Interpret for Continue {
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
Err(Error::Continue)
}
}
impl Interpret for Return {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { body } = self;
Err(Error::Return(
body.as_ref()
.map(|body| body.interpret(env))
.unwrap_or(Ok(ConValue::Empty))?,
))
}
}
impl Interpret for Break {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { body } = self;
Err(Error::Break(
body.as_ref()
.map(|body| body.interpret(env))
.unwrap_or(Ok(ConValue::Empty))?,
))
}
}

View File

@@ -1,614 +0,0 @@
//! Walks a Conlang AST, interpreting it as a program.
#![warn(clippy::all)]
#![feature(decl_macro)]
use env::Environment;
use error::{Error, IResult};
use interpret::Interpret;
use temp_type_impl::ConValue;
/// Callable types can be called from within a Conlang program
pub trait Callable: std::fmt::Debug {
/// 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) -> &str;
}
/// [BuiltIn]s are [Callable]s with bespoke definitions
pub trait BuiltIn: std::fmt::Debug + Callable {
fn description(&self) -> &str;
}
pub mod temp_type_impl {
//! Temporary implementations of Conlang values
//!
//! The most permanent fix is a temporary one.
use super::{
error::{Error, IResult},
function::Function,
BuiltIn, Callable, Environment,
};
use std::ops::*;
type Integer = isize;
/// A Conlang value
///
/// This is a hack to work around the fact that Conlang doesn't
/// have a functioning type system yet :(
#[derive(Clone, Debug, Default)]
pub enum ConValue {
/// The empty/unit `()` type
#[default]
Empty,
/// An integer
Int(Integer),
/// A boolean
Bool(bool),
/// A unicode character
Char(char),
/// A string
String(String),
/// An Array
Array(Vec<ConValue>),
/// A tuple
Tuple(Vec<ConValue>),
/// An exclusive range
RangeExc(Integer, Integer),
/// An inclusive range
RangeInc(Integer, Integer),
/// A callable thing
Function(Function),
/// A built-in function
BuiltIn(&'static dyn 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 range_exc(self, other: Self) -> IResult<Self> {
let (Self::Int(a), Self::Int(b)) = (self, other) else {
Err(Error::TypeError)?
};
Ok(Self::RangeExc(a, b.saturating_sub(1)))
}
pub fn range_inc(self, other: Self) -> IResult<Self> {
let (Self::Int(a), Self::Int(b)) = (self, other) else {
Err(Error::TypeError)?
};
Ok(Self::RangeInc(a, b))
}
pub fn index(&self, index: &Self) -> IResult<ConValue> {
let Self::Int(index) = index else {
Err(Error::TypeError)?
};
let Self::Array(arr) = self else {
Err(Error::TypeError)?
};
arr.get(*index as usize)
.cloned()
.ok_or(Error::OobIndex(*index as usize, arr.len()))
}
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) -> &str {
match self {
ConValue::Function(func) => func.name(),
ConValue::BuiltIn(func) => func.name(),
_ => "",
}
}
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
match self {
Self::Function(func) => func.call(interpreter, args),
Self::BuiltIn(func) => func.call(interpreter, 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::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)),
(Self::Char(a), Self::Char(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()) }
})*
}
from! {
Integer => ConValue::Int,
bool => ConValue::Bool,
char => ConValue::Char,
&str => ConValue::String,
String => ConValue::String,
Function => ConValue::Function,
Vec<ConValue> => ConValue::Tuple,
&'static dyn BuiltIn => ConValue::BuiltIn,
}
impl From<()> for ConValue {
fn from(_: ()) -> Self {
Self::Empty
}
}
impl From<&[ConValue]> for ConValue {
fn from(value: &[ConValue]) -> Self {
match value.len() {
0 => Self::Empty,
1 => value[0].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 + b),
(ConValue::String(a), ConValue::String(b)) => ConValue::String(a + &b),
_ => 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 / b),
_ => Err(Error::TypeError)?
]
Mul: mul = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b),
_ => Err(Error::TypeError)?
]
Rem: rem = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b),
_ => Err(Error::TypeError)?
]
Shl: shl = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b),
_ => Err(Error::TypeError)?
]
Shr: shr = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b),
_ => Err(Error::TypeError)?
]
Sub: sub = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(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::Bool(v) => v.fmt(f),
ConValue::Char(v) => v.fmt(f),
ConValue::String(v) => v.fmt(f),
ConValue::Array(array) => {
'['.fmt(f)?;
for (idx, element) in array.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?
}
element.fmt(f)?
}
']'.fmt(f)
}
ConValue::RangeExc(a, b) => write!(f, "{a}..{}", b + 1),
ConValue::RangeInc(a, b) => write!(f, "{a}..={b}"),
ConValue::Tuple(tuple) => {
'('.fmt(f)?;
for (idx, element) in tuple.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?
}
element.fmt(f)?
}
')'.fmt(f)
}
ConValue::Function(func) => {
use cl_ast::format::*;
use std::fmt::Write;
write!(f.pretty(), "{}", func.decl())
}
ConValue::BuiltIn(func) => {
write!(f, "{}", func.description())
}
}
}
}
}
pub mod interpret;
pub mod function {
//! Represents a block of code which lives inside the Interpreter
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
use cl_ast::{Function as FnDecl, Identifier, Param};
/// Represents a block of code which persists inside the Interpreter
#[derive(Clone, Debug)]
pub struct Function {
/// Stores the contents of the function declaration
decl: Box<FnDecl>,
// /// Stores the enclosing scope of the function
// env: Box<Environment>,
}
impl Function {
pub fn new(decl: &FnDecl) -> Self {
Self { decl: decl.clone().into() }
}
pub fn decl(&self) -> &FnDecl {
&self.decl
}
}
impl Callable for Function {
fn name(&self) -> &str {
let FnDecl { name: Identifier(ref name), .. } = *self.decl;
name
}
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
let FnDecl { name: Identifier(name), args: declargs, body, rety: _ } = &*self.decl;
// Check arg mapping
if args.len() != declargs.len() {
return Err(Error::ArgNumber { want: declargs.len(), got: args.len() });
}
let Some(body) = body else {
return Err(Error::NotDefined(name.into()));
};
// TODO: completely refactor data storage
let mut frame = env.frame("fn args");
for (Param { mutability: _, name: Identifier(name), ty: _ }, value) in
declargs.iter().zip(args)
{
frame.insert(name, Some(value.clone()));
}
match body.interpret(&mut frame) {
Err(Error::Return(value)) => Ok(value),
Err(Error::Break(value)) => Err(Error::BadBreak(value)),
result => result,
}
}
}
}
pub mod builtin;
pub mod env {
//! Lexical and non-lexical scoping for variables
use super::{
builtin::{BINARY, MISC, RANGE, UNARY},
error::{Error, IResult},
function::Function,
temp_type_impl::ConValue,
BuiltIn, Callable, Interpret,
};
use cl_ast::{Function as FnDecl, Identifier};
use std::{
collections::HashMap,
fmt::Display,
ops::{Deref, DerefMut},
};
/// Implements a nested lexical scope
#[derive(Clone, Debug)]
pub struct Environment {
frames: Vec<(HashMap<String, Option<ConValue>>, &'static str)>,
}
impl Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (frame, name) in self.frames.iter().rev() {
writeln!(f, "--- {name} ---")?;
for (var, val) in frame {
write!(f, "{var}: ")?;
match val {
Some(value) => writeln!(f, "\t{value}"),
None => writeln!(f, "<undefined>"),
}?
}
}
Ok(())
}
}
impl Default for Environment {
fn default() -> Self {
Self {
frames: vec![
(to_hashmap(RANGE), "range ops"),
(to_hashmap(UNARY), "unary ops"),
(to_hashmap(BINARY), "binary ops"),
(to_hashmap(MISC), "builtins"),
(HashMap::new(), "globals"),
],
}
}
}
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<String, Option<ConValue>> {
from.iter()
.map(|&v| (v.name().into(), Some(v.into())))
.collect()
}
impl Environment {
pub fn new() -> Self {
Self::default()
}
/// Creates an [Environment] with no [builtins](super::builtin)
pub fn no_builtins(name: &'static str) -> Self {
Self { frames: vec![(Default::default(), name)] }
}
pub fn eval(&mut self, node: &impl Interpret) -> IResult<ConValue> {
node.interpret(self)
}
/// Calls a function inside the interpreter's scope,
/// and returns the result
pub fn call(&mut self, name: &str, args: &[ConValue]) -> IResult<ConValue> {
// FIXME: Clone to satisfy the borrow checker
let function = self.get(name)?.clone();
function.call(self, args)
}
/// 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)
}
/// Resolves a variable mutably.
///
/// Returns a mutable reference to the variable's record, if it exists.
pub fn get_mut(&mut self, id: &str) -> IResult<&mut Option<ConValue>> {
for (frame, _) in self.frames.iter_mut().rev() {
if let Some(var) = frame.get_mut(id) {
return Ok(var);
}
}
Err(Error::NotDefined(id.into()))
}
/// Resolves a variable immutably.
///
/// Returns a reference to the variable's contents, if it is defined and initialized.
pub fn get(&self, id: &str) -> IResult<&ConValue> {
for (frame, _) in self.frames.iter().rev() {
match frame.get(id) {
Some(Some(var)) => return Ok(var),
Some(None) => return Err(Error::NotInitialized(id.into())),
_ => (),
}
}
Err(Error::NotDefined(id.into()))
}
/// Inserts a new [ConValue] into this [Environment]
pub fn insert(&mut self, id: &str, value: Option<ConValue>) {
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(id.into(), value);
}
}
/// A convenience function for registering a [FnDecl] as a [Function]
pub fn insert_fn(&mut self, decl: &FnDecl) {
let FnDecl { name: Identifier(name), .. } = decl;
let (name, function) = (name.clone(), Some(Function::new(decl).into()));
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(name, function);
}
}
}
/// Functions which aid in the implementation of [`Frame`]
impl Environment {
/// Enters a scope, creating a new namespace for variables
fn enter(&mut self, name: &'static str) -> &mut Self {
self.frames.push((Default::default(), name));
self
}
/// Exits the scope, destroying all local variables and
/// returning the outer scope, if there is one
fn exit(&mut self) -> &mut Self {
if self.frames.len() > 2 {
self.frames.pop();
}
self
}
}
/// 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 {
Self { scope: scope.enter(name) }
}
}
impl<'scope> Deref for Frame<'scope> {
type Target = Environment;
fn deref(&self) -> &Self::Target {
self.scope
}
}
impl<'scope> DerefMut for Frame<'scope> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.scope
}
}
impl<'scope> Drop for Frame<'scope> {
fn drop(&mut self) {
self.scope.exit();
}
}
}
pub mod error {
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
use super::temp_type_impl::ConValue;
pub type IResult<T> = Result<T, Error>;
/// Represents any error thrown by the [Environment](super::Environment)
#[derive(Clone, Debug)]
pub enum Error {
/// 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,
/// 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(String),
/// A name was defined but not initialized
NotInitialized(String),
/// 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,
},
NullPointer,
}
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::Return(value) => write!(f, "return {value}"),
Error::Break(value) => write!(f, "break {value}"),
Error::BadBreak(value) => write!(f, "rogue break: {value}"),
Error::Continue => "continue".fmt(f),
Error::StackUnderflow => "Stack underflow".fmt(f),
Error::ScopeExit => "Exited the last scope. This is a logic bug.".fmt(f),
Error::TypeError => "Incompatible types".fmt(f),
Error::NotIterable => "`in` clause of `for` loop did not yield an iterable".fmt(f),
Error::NotIndexable => {
write!(f, "expression cannot be indexed")
}
Error::OobIndex(idx, len) => {
write!(f, "Index out of bounds: index was {idx}. but len is {len}")
}
Error::NotAssignable => {
write!(f, "expression is not assignable")
}
Error::NotDefined(value) => {
write!(f, "{value} not bound. Did you mean `let {value};`?")
}
Error::NotInitialized(value) => {
write!(f, "{value} bound, but not initialized")
}
Error::NotCallable(value) => {
write!(f, "{value} is not callable.")
}
Error::ArgNumber { want, got } => {
write!(
f,
"Expected {want} argument{}, got {got}",
if *want == 1 { "" } else { "s" }
)
}
Error::NullPointer => {
write!(f, "Attempted to dereference a null pointer?")
}
}
}
}
}
#[cfg(test)]
mod tests;

View File

@@ -1,556 +0,0 @@
//! 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::{Chars, FromStr},
};
use unicode_ident::*;
#[cfg(test)]
mod tests;
pub mod lexer_iter {
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
use super::{
error::{LResult, Reason},
Lexer, Token,
};
/// Iterator over a [`Lexer`], returning [`LResult<Token>`]s
pub struct LexerIter<'t> {
lexer: Lexer<'t>,
}
impl<'t> Iterator for LexerIter<'t> {
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> {
iter: Peekable<Chars<'t>>,
start: usize,
start_loc: (u32, u32),
current: usize,
current_loc: (u32, u32),
}
impl<'t> Lexer<'t> {
/// Creates a new [Lexer] over a [str]
pub fn new(text: &'t str) -> Self {
Self {
iter: text.chars().peekable(),
start: 0,
start_loc: (1, 1),
current: 0,
current_loc: (1, 1),
}
}
/// Scans through the text, searching for the next [Token]
pub fn scan(&mut self) -> LResult<Token> {
match self.skip_whitespace().peek()? {
'{' => self.consume()?.produce_op(Punct::LCurly),
'}' => self.consume()?.produce_op(Punct::RCurly),
'[' => self.consume()?.produce_op(Punct::LBrack),
']' => self.consume()?.produce_op(Punct::RBrack),
'(' => self.consume()?.produce_op(Punct::LParen),
')' => self.consume()?.produce_op(Punct::RParen),
'&' => self.consume()?.amp(),
'@' => self.consume()?.produce_op(Punct::At),
'\\' => self.consume()?.produce_op(Punct::Backslash),
'!' => self.consume()?.bang(),
'|' => self.consume()?.bar(),
':' => self.consume()?.colon(),
',' => self.consume()?.produce_op(Punct::Comma),
'.' => self.consume()?.dot(),
'=' => self.consume()?.equal(),
'`' => self.consume()?.produce_op(Punct::Grave),
'>' => self.consume()?.greater(),
'#' => self.consume()?.hash(),
'<' => self.consume()?.less(),
'-' => self.consume()?.minus(),
'+' => self.consume()?.plus(),
'?' => self.consume()?.produce_op(Punct::Question),
'%' => self.consume()?.rem(),
';' => self.consume()?.produce_op(Punct::Semi),
'/' => self.consume()?.slash(),
'*' => self.consume()?.star(),
'~' => self.consume()?.produce_op(Punct::Tilde),
'^' => self.consume()?.xor(),
'0' => self.consume()?.int_with_base(),
'1'..='9' => self.digits::<10>(),
'"' => self.consume()?.string(),
'\'' => self.consume()?.character(),
'_' => self.identifier(),
i if is_xid_start(i) => self.identifier(),
e => {
let err = Err(Error::unexpected_char(e, self.line(), self.col()));
let _ = self.consume();
err
}
}
}
/// Returns the current line
pub fn line(&self) -> u32 {
self.start_loc.0
}
/// Returns the current column
pub fn col(&self) -> u32 {
self.start_loc.1
}
fn next(&mut self) -> LResult<char> {
let out = self.peek();
self.consume()?;
out
}
fn peek(&mut self) -> LResult<char> {
self.iter
.peek()
.copied()
.ok_or(Error::end_of_file(self.line(), self.col()))
}
fn produce(&mut self, kind: TokenKind, data: impl Into<TokenData>) -> LResult<Token> {
let loc = self.start_loc;
self.start_loc = self.current_loc;
self.start = self.current;
Ok(Token::new(kind, data, loc.0, loc.1))
}
fn produce_op(&mut self, kind: Punct) -> LResult<Token> {
self.produce(TokenKind::Punct(kind), ())
}
fn skip_whitespace(&mut self) -> &mut Self {
while let Ok(c) = self.peek() {
if !c.is_whitespace() {
break;
}
let _ = self.consume();
}
self.start = self.current;
self.start_loc = self.current_loc;
self
}
fn consume(&mut self) -> LResult<&mut Self> {
self.current += 1;
match self.iter.next() {
Some('\n') => {
let (line, col) = &mut self.current_loc;
*line += 1;
*col = 1;
}
Some(_) => self.current_loc.1 += 1,
None => Err(Error::end_of_file(self.line(), self.col()))?,
}
Ok(self)
}
}
/// Digraphs and trigraphs
impl<'t> Lexer<'t> {
fn amp(&mut self) -> LResult<Token> {
match self.peek() {
Ok('&') => self.consume()?.produce_op(Punct::AmpAmp),
Ok('=') => self.consume()?.produce_op(Punct::AmpEq),
_ => self.produce_op(Punct::Amp),
}
}
fn bang(&mut self) -> LResult<Token> {
match self.peek() {
Ok('!') => self.consume()?.produce_op(Punct::BangBang),
Ok('=') => self.consume()?.produce_op(Punct::BangEq),
_ => self.produce_op(Punct::Bang),
}
}
fn bar(&mut self) -> LResult<Token> {
match self.peek() {
Ok('|') => self.consume()?.produce_op(Punct::BarBar),
Ok('=') => self.consume()?.produce_op(Punct::BarEq),
_ => self.produce_op(Punct::Bar),
}
}
fn colon(&mut self) -> LResult<Token> {
match self.peek() {
Ok(':') => self.consume()?.produce_op(Punct::ColonColon),
_ => self.produce_op(Punct::Colon),
}
}
fn dot(&mut self) -> LResult<Token> {
match self.peek() {
Ok('.') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce_op(Punct::DotDotEq)
} else {
self.produce_op(Punct::DotDot)
}
}
_ => self.produce_op(Punct::Dot),
}
}
fn equal(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::EqEq),
Ok('>') => self.consume()?.produce_op(Punct::FatArrow),
_ => self.produce_op(Punct::Eq),
}
}
fn greater(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::GtEq),
Ok('>') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce_op(Punct::GtGtEq)
} else {
self.produce_op(Punct::GtGt)
}
}
_ => self.produce_op(Punct::Gt),
}
}
fn hash(&mut self) -> LResult<Token> {
match self.peek() {
Ok('!') => self.consume()?.produce_op(Punct::HashBang),
_ => self.produce_op(Punct::Hash),
}
}
fn less(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::LtEq),
Ok('<') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce_op(Punct::LtLtEq)
} else {
self.produce_op(Punct::LtLt)
}
}
_ => self.produce_op(Punct::Lt),
}
}
fn minus(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::MinusEq),
Ok('>') => self.consume()?.produce_op(Punct::Arrow),
_ => self.produce_op(Punct::Minus),
}
}
fn plus(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::PlusEq),
_ => self.produce_op(Punct::Plus),
}
}
fn rem(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::RemEq),
_ => self.produce_op(Punct::Rem),
}
}
fn slash(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::SlashEq),
Ok('/') => self.consume()?.line_comment(),
Ok('*') => self.consume()?.block_comment(),
_ => self.produce_op(Punct::Slash),
}
}
fn star(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::StarEq),
_ => self.produce_op(Punct::Star),
}
}
fn xor(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::XorEq),
Ok('^') => self.consume()?.produce_op(Punct::XorXor),
_ => self.produce_op(Punct::Xor),
}
}
}
/// Comments
impl<'t> Lexer<'t> {
fn line_comment(&mut self) -> LResult<Token> {
while Ok('\n') != self.peek() {
self.consume()?;
}
self.produce(Kind::Comment, ())
}
fn block_comment(&mut self) -> LResult<Token> {
while let Ok(c) = self.next() {
if '*' == c && Ok('/') == self.next() {
break;
}
}
self.produce(Kind::Comment, ())
}
}
/// Identifiers
impl<'t> Lexer<'t> {
fn identifier(&mut self) -> LResult<Token> {
let mut out = String::from(self.xid_start()?);
while let Ok(c) = self.xid_continue() {
out.push(c)
}
if let Ok(keyword) = Kind::from_str(&out) {
self.produce(keyword, ())
} else {
self.produce(Kind::Identifier, TokenData::String(out))
}
}
fn xid_start(&mut self) -> LResult<char> {
match self.peek()? {
xid if xid == '_' || is_xid_start(xid) => {
self.consume()?;
Ok(xid)
}
bad => Err(Error::not_identifier(bad, self.line(), self.col())),
}
}
fn xid_continue(&mut self) -> LResult<char> {
match self.peek()? {
xid if is_xid_continue(xid) => {
self.consume()?;
Ok(xid)
}
bad => Err(Error::not_identifier(bad, self.line(), self.col())),
}
}
}
/// Integers
impl<'t> Lexer<'t> {
fn int_with_base(&mut self) -> LResult<Token> {
match self.peek() {
Ok('x') => self.consume()?.digits::<16>(),
Ok('d') => self.consume()?.digits::<10>(),
Ok('o') => self.consume()?.digits::<8>(),
Ok('b') => self.consume()?.digits::<2>(),
Ok('0'..='9') => self.digits::<10>(),
_ => self.produce(Kind::Literal, 0),
}
}
fn digits<const B: u32>(&mut self) -> LResult<Token> {
let mut value = self.digit::<B>()? as u128;
while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) {
value = value * B as u128 + self.digit::<B>()? as u128;
}
self.produce(Kind::Literal, value)
}
fn digit<const B: u32>(&mut self) -> LResult<u32> {
let digit = self.peek()?;
self.consume()?;
digit
.to_digit(B)
.ok_or(Error::invalid_digit(digit, self.line(), self.col()))
}
}
/// Strings and characters
impl<'t> Lexer<'t> {
fn string(&mut self) -> LResult<Token> {
let mut value = String::new();
while '"'
!= self
.peek()
.map_err(|e| e.mask_reason(Reason::UnmatchedDelimiters('"')))?
{
value.push(self.unescape()?)
}
self.consume()?.produce(Kind::Literal, value)
}
fn character(&mut self) -> LResult<Token> {
let out = self.unescape()?;
match self.peek()? {
'\'' => self.consume()?.produce(Kind::Literal, out),
_ => Err(Error::unmatched_delimiters('\'', self.line(), self.col())),
}
}
/// Unescape a single character
fn unescape(&mut self) -> LResult<char> {
match self.next() {
Ok('\\') => (),
other => return other,
}
Ok(match self.next()? {
'a' => '\x07',
'b' => '\x08',
'f' => '\x0c',
'n' => '\n',
'r' => '\r',
't' => '\t',
'x' => self.hex_escape()?,
'u' => self.unicode_escape()?,
'0' => '\0',
chr => chr,
})
}
/// unescape 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(Error::bad_unicode(out, self.line(), self.col()))
}
/// unescape a single \u{} unicode escape
fn unicode_escape(&mut self) -> LResult<char> {
let mut out = 0;
let Ok('{') = self.peek() else {
return Err(Error::invalid_escape('u', self.line(), self.col()));
};
self.consume()?;
while let Ok(c) = self.peek() {
match c {
'}' => {
self.consume()?;
return char::from_u32(out).ok_or(Error::bad_unicode(
out,
self.line(),
self.col(),
));
}
_ => out = (out << 4) + self.digit::<16>()?,
}
}
Err(Error::invalid_escape('u', self.line(), self.col()))
}
}
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 identifiers while looking for an identifier
NotIdentifier(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),
/// Base conversion requested, but the base character was not in the set of known
/// characters
UnknownBase(char),
/// Unicode escape does not map to a valid unicode code-point
BadUnicode(u32),
/// Reached end of input
EndOfFile,
}
error_impl! {
unmatched_delimiters(c: char) => Reason::UnmatchedDelimiters(c),
unexpected_char(c: char) => Reason::UnexpectedChar(c),
not_identifier(c: char) => Reason::NotIdentifier(c),
unknown_escape(e: char) => Reason::UnknownEscape(e),
invalid_escape(e: char) => Reason::InvalidEscape(e),
invalid_digit(digit: char) => Reason::InvalidDigit(digit),
unknown_base(base: char) => Reason::UnknownBase(base),
bad_unicode(value: u32) => Reason::BadUnicode(value),
end_of_file => Reason::EndOfFile,
}
impl Error {
/// Changes the [Reason] of this error
pub(super) fn mask_reason(self, reason: Reason) -> Self {
Self { reason, ..self }
}
/// 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)
}
}
macro error_impl ($($fn:ident$(( $($p:ident: $t:ty),* ))? => $reason:expr),*$(,)?) {
#[allow(dead_code)]
impl Error {
$(pub(super) fn $fn ($($($p: $t),*,)? line: u32, col: u32) -> Self {
Self { reason: $reason, line, 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::NotIdentifier(c) => write!(f, "Character `{c}` not valid in identifiers"),
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::UnknownBase(c) => write!(f, "`0{c}`... is not a valid base"),
Reason::BadUnicode(c) => write!(f, "`{c}` is not a valid unicode code-point"),
Reason::EndOfFile => write!(f, "Reached end of input"),
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,109 +0,0 @@
use cl_lexer::Lexer;
use cl_parser::Parser;
use cl_repl::repline::{error::Error as RlError, Repline};
use cl_typeck::{name_collector::NameCollector, project::Project};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let mut prj = Project::default();
let mut tcol = NameCollector::new(&mut prj);
println!(
"--- {} v{} 💪🦈 ---",
env!("CARGO_BIN_NAME"),
env!("CARGO_PKG_VERSION"),
);
read_and(
"\x1b[33m",
"cl>",
"? >",
|line| -> Result<_, Box<dyn Error>> {
if line.trim_start().is_empty() {
query(&tcol)?;
return Ok(Response::Deny);
}
let mut parser = Parser::new(Lexer::new(line));
let code = match parser.file() {
Ok(code) => code,
Err(e) => Err(e)?,
};
tcol.file(&code)?;
Ok(Response::Accept)
},
)
}
pub enum Response {
Accept,
Deny,
Break,
}
fn read_and(
color: &str,
begin: &str,
again: &str,
mut f: impl FnMut(&str) -> Result<Response, Box<dyn Error>>,
) -> Result<(), Box<dyn Error>> {
let mut rl = Repline::new(color, begin, again);
loop {
let line = match rl.read() {
Err(RlError::CtrlC(_)) => break,
Err(RlError::CtrlD(line)) => {
rl.deny();
line
}
Ok(line) => line,
Err(e) => Err(e)?,
};
print!("\x1b[G\x1b[J");
match f(&line) {
Ok(Response::Accept) => rl.accept(),
Ok(Response::Deny) => rl.deny(),
Ok(Response::Break) => break,
Err(e) => print!("\x1b[40G\x1bJ\x1b[91m{e}\x1b[0m"),
}
}
Ok(())
}
fn query(prj: &Project) -> Result<(), Box<dyn Error>> {
use cl_typeck::{
definition::{Def, DefKind},
type_kind::TypeKind,
};
read_and("\x1b[35m", "qy>", "? >", |line| {
if line.trim_start().is_empty() {
return Ok(Response::Break);
}
match line {
"$all\n" => println!("{prj:#?}"),
_ => {
// parse it as a path, and convert the path into a borrowed path
let path = Parser::new(Lexer::new(line)).path()?;
let Some((type_id, path)) = prj.get_type((&path).into(), prj.module_root) else {
return Ok(Response::Deny);
};
let Def { name, vis, meta: _, kind, source: _, module } = &prj[type_id];
match (kind, prj.get_value(path, type_id)) {
(_, Some((val, path))) => {
println!("value {}; {path}\n{:#?}", usize::from(val), prj[val])
}
(DefKind::Type(TypeKind::Module), None) => println!(
"{vis}mod \"{name}\" (#{}); {path}\n{:#?}",
usize::from(type_id),
module
),
(_, None) => println!(
"type {name}(#{}); {path}\n{:#?}",
usize::from(type_id),
prj.pool[type_id]
),
};
}
}
Ok(Response::Accept)
})
}

View File

@@ -1,13 +0,0 @@
use cl_repl::{cli::run, tools::is_terminal};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
if is_terminal() {
println!(
"--- {} v{} 💪🦈 ---",
env!("CARGO_BIN_NAME"),
env!("CARGO_PKG_VERSION"),
);
}
run(argh::from_env())
}

View File

@@ -1,462 +0,0 @@
//! Utilities for cl-frontend
//!
//! # TODO
//! - [ ] Readline-like line editing
//! - [ ] Raw mode?
#![warn(clippy::all)]
pub mod ansi {
// ANSI color escape sequences
pub const ANSI_RED: &str = "\x1b[31m";
pub const ANSI_GREEN: &str = "\x1b[32m"; // the color of type checker mode
pub const ANSI_CYAN: &str = "\x1b[36m";
// pub const ANSI_BRIGHT_GREEN: &str = "\x1b[92m";
pub const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
pub const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
pub const ANSI_RESET: &str = "\x1b[0m";
pub const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
pub const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
}
pub mod args {
use crate::tools::is_terminal;
use argh::FromArgs;
use std::{path::PathBuf, str::FromStr};
/// The Conlang prototype debug interface
#[derive(Clone, Debug, FromArgs, PartialEq, Eq, PartialOrd, Ord)]
pub struct Args {
/// the main source file
#[argh(positional)]
pub file: Option<PathBuf>,
/// files to include
#[argh(option, short = 'I')]
pub include: Vec<PathBuf>,
/// the Repl mode to start in
#[argh(option, short = 'm', default = "Default::default()")]
pub mode: Mode,
/// whether to start the repl (`true` or `false`)
#[argh(option, short = 'r', default = "is_terminal()")]
pub repl: bool,
}
/// The CLI's operating mode
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mode {
Tokenize,
Beautify,
#[default]
Interpret,
}
impl Mode {
pub fn ansi_color(self) -> &'static str {
use super::ansi::*;
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 = &'static str;
fn from_str(s: &str) -> Result<Self, &'static str> {
Ok(match s {
"i" | "interpret" | "r" | "run" => Mode::Interpret,
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
// "r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
"t" | "tokenize" | "token" => Mode::Tokenize,
_ => Err("Recognized modes are: 'r' \"run\", 'p' \"pretty\", 't' \"token\"")?,
})
}
}
}
pub mod program {
use cl_interpret::{
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
};
use cl_ast::{self as ast, format::*};
use cl_lexer::Lexer;
use cl_parser::{error::PResult, Parser};
// use conlang::resolver::{error::TyResult, Resolver};
use std::{fmt::Display, io::Write};
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 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}"))
// }
}
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}"),
}
}
}
}
pub mod cli {
//! Implement's the command line interface
use crate::{
args::{Args, Mode},
program::{Parsable, Program},
repl::Repl,
tools::print_token,
};
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
use cl_lexer::Lexer;
use cl_parser::Parser;
use std::{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();
for path in include {
load_file(&mut env, path)?;
}
if repl {
if let Some(file) = file {
load_file(&mut env, file)?;
}
Repl::with_env(mode, env).repl()
} else {
let code = match &file {
Some(file) => std::fs::read_to_string(file)?,
None => std::io::read_to_string(std::io::stdin())?,
};
let code = Program::new(&code);
match mode {
Mode::Tokenize => tokenize(code, file),
Mode::Beautify => beautify(code),
Mode::Interpret => interpret(code, &mut env),
}?;
}
Ok(())
}
fn load_file(
env: &mut Environment,
path: impl AsRef<Path>,
) -> Result<ConValue, Box<dyn Error>> {
let file = std::fs::read_to_string(path)?;
let code = Parser::new(Lexer::new(&file)).file()?;
env.eval(&code).map_err(Into::into)
}
fn tokenize(
code: Program<Parsable>,
path: Option<impl AsRef<Path>>,
) -> Result<(), Box<dyn Error>> {
for token in code.lex() {
if let Some(ref path) = path {
print!("{}:", path.as_ref().display());
}
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
Ok(())
}
fn beautify(code: Program<Parsable>) -> Result<(), Box<dyn Error>> {
code.parse()?.print();
Ok(())
}
fn interpret(code: Program<Parsable>, env: &mut Environment) -> Result<(), Box<dyn Error>> {
match code.parse()?.run(env)? {
ConValue::Empty => {}
ret => println!("{ret}"),
}
if env.get("main").is_ok() {
match env.call("main", &[])? {
ConValue::Empty => {}
ret => println!("{ret}"),
}
}
Ok(())
}
}
pub mod repl {
use crate::{
ansi::*,
args::Mode,
program::{Parsable, Parsed, Program},
tools::print_token,
};
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
use std::fmt::Display;
/// Implements the interactive interpreter
#[derive(Clone, Debug)]
pub struct Repl {
prompt_again: &'static str, // " ?>"
prompt_begin: &'static str, // "cl>"
prompt_error: &'static str, // "! >"
prompt_succs: &'static str, // " ->"
env: Environment,
mode: Mode,
}
impl Default for Repl {
fn default() -> Self {
Self {
prompt_begin: "cl>",
prompt_again: " ?>",
prompt_error: "! >",
prompt_succs: " =>",
env: Default::default(),
mode: Default::default(),
}
}
}
/// Prompt functions
impl Repl {
pub fn prompt_result<T: Display, E: Display>(&self, res: Result<T, E>) {
match &res {
Ok(v) => self.prompt_succs(v),
Err(e) => self.prompt_error(e),
}
}
pub fn prompt_error(&self, err: &impl Display) {
let Self { prompt_error: prompt, .. } = self;
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}")
}
pub fn prompt_succs(&self, value: &impl Display) {
let Self { prompt_succs: _prompt, .. } = self;
println!("{ANSI_GREEN}{value}{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() }
}
/// Constructs a new [Repl] with the provided [Mode] and [Environment]
pub fn with_env(mode: Mode, env: Environment) -> Self {
Self { mode, env, ..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 {
let Some(line) = line.trim().strip_prefix('$') else {
return false;
};
if let Ok(mode) = line.parse() {
self.mode = mode;
} else {
match line {
"$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::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>) {
match code.run(&mut self.env) {
Ok(ConValue::Empty) => {}
res => self.prompt_result(res),
}
}
fn beautify(&mut self, code: &Program<Parsed>) {
code.print()
}
}
}
pub mod tools {
use cl_token::Token;
use std::io::IsTerminal;
/// Prints a token in the particular way cl-repl does
pub fn print_token(t: &Token) {
println!(
"{:02}:{:02}: {:#19}{}",
t.line(),
t.col(),
t.ty(),
t.data(),
)
}
/// 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()
}
}
pub mod repline;

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_repl::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,131 +0,0 @@
//! Trivially-copyable, easily comparable typed indices, and a [Pool] to contain them
//!
//! # Examples
//!
//! ```rust
//! # use cl_structures::intern_pool::*;
//! // first, create a new InternKey type (this ensures type safety)
//! make_intern_key!{
//! NumbersKey
//! }
//!
//! // then, create a pool with that type
//! let mut numbers: Pool<i32, NumbersKey> = Pool::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 [Pool] keys.
#[macro_export]
macro_rules! make_intern_key {($($(#[$meta:meta])* $name:ident),*$(,)?) => {$(
$(#[$meta])*
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(usize);
impl $crate::intern_pool::InternKey for $name {
#[doc = concat!("Constructs a [`", stringify!($name), "`] from a [`usize`] without checking bounds.\n")]
/// # Safety
///
/// The provided value should be within the bounds of its associated container
unsafe fn from_raw_unchecked(value: usize) -> Self {
Self(value)
}
fn get(&self) -> usize {
self.0
}
}
impl From< $name > for usize {
fn from(value: $name) -> Self {
value.0
}
}
)*}}
use std::ops::{Index, IndexMut};
pub use make_intern_key;
/// An index into a [Pool]. For full type-safety,
/// there should be a unique [InternKey] for each [Pool]
pub trait InternKey: std::fmt::Debug {
/// Constructs an [`InternKey`] from a [`usize`] without checking bounds.
///
/// # Safety
///
/// The provided value should be within the bounds of its associated container.
// ID::from_raw_unchecked here isn't *actually* unsafe, since bounds should always be
// checked, however, the function has unverifiable preconditions.
unsafe fn from_raw_unchecked(value: usize) -> Self;
/// Gets the index of the [`InternKey`] by value
fn get(&self) -> usize;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Pool<T, ID: InternKey> {
pool: Vec<T>,
id_type: std::marker::PhantomData<ID>,
}
impl<T, ID: InternKey> Pool<T, ID> {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, index: ID) -> Option<&T> {
self.pool.get(index.get())
}
pub fn get_mut(&mut self, index: ID) -> Option<&mut T> {
self.pool.get_mut(index.get())
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.pool.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.pool.iter_mut()
}
pub fn insert(&mut self, value: T) -> ID {
let id = self.pool.len();
self.pool.push(value);
// Safety: value was pushed to `self.pool[id]`
unsafe { ID::from_raw_unchecked(id) }
}
}
impl<T, ID: InternKey> Default for Pool<T, ID> {
fn default() -> Self {
Self { pool: vec![], id_type: std::marker::PhantomData }
}
}
impl<T, ID: InternKey> Index<ID> for Pool<T, ID> {
type Output = T;
fn index(&self, index: ID) -> &Self::Output {
match self.pool.get(index.get()) {
None => panic!("Index {:?} out of bounds in pool!", index),
Some(value) => value,
}
}
}
impl<T, ID: InternKey> IndexMut<ID> for Pool<T, ID> {
fn index_mut(&mut self, index: ID) -> &mut Self::Output {
match self.pool.get_mut(index.get()) {
None => panic!("Index {:?} out of bounds in pool!", index),
Some(value) => value,
}
}
}

View File

@@ -1,14 +0,0 @@
//! # 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
#![warn(clippy::all)]
#![feature(inline_const, dropck_eyepatch, decl_macro)]
#![deny(unsafe_op_in_unsafe_fn)]
pub mod span;
pub mod tree;
pub mod stack;
pub mod intern_pool;

View File

@@ -1,909 +0,0 @@
//! # The Conlang Type Checker
//!
//! As a statically typed language, Conlang requires a robust type checker to enforce correctness.
#![feature(debug_closure_helpers)]
#![warn(clippy::all)]
/*
The type checker keeps track of a *global intern pool* for Types and Values
References to the intern pool are held by ID, and items cannot be freed from the pool EVER.
Items are inserted into their respective pools,
*/
pub mod key {
use cl_structures::intern_pool::*;
// define the index types
make_intern_key! {
/// Uniquely represents a [Def][1] in the [Def][1] [Pool]
///
/// [1]: crate::definition::Def
DefID,
}
}
pub mod definition {
use crate::{key::DefID, module::Module, type_kind::TypeKind, value_kind::ValueKind};
use cl_ast::{format::FmtPretty, Item, Meta, Visibility};
use std::fmt::Write;
#[derive(Clone, PartialEq, Eq)]
pub struct Def {
pub name: String,
pub vis: Visibility,
pub meta: Vec<Meta>,
pub kind: DefKind,
pub source: Option<Item>,
pub module: Module,
}
impl Default for Def {
fn default() -> Self {
Self {
name: Default::default(),
vis: Default::default(),
meta: Default::default(),
kind: DefKind::Type(TypeKind::Module),
source: Default::default(),
module: Default::default(),
}
}
}
impl Def {
pub fn new_module(
name: String,
vis: Visibility,
meta: Vec<Meta>,
parent: Option<DefID>,
) -> Self {
Self {
name,
vis,
meta,
kind: DefKind::Type(TypeKind::Module),
source: None,
module: Module { parent, ..Default::default() },
}
}
}
impl std::fmt::Debug for Def {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, vis, meta, kind, source, module } = self;
f.debug_struct("Def")
.field("name", &name)
.field("vis", &vis)
.field_with("meta", |f| write!(f, "{meta:?}"))
.field("kind", &kind)
.field_with("source", |f| match source {
Some(item) => write!(f.pretty(), "{{\n{item}\n}}"),
None => todo!(),
})
.field("module", &module)
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DefKind {
/// A type, such as a ``
Type(TypeKind),
/// A value, such as a `const`, `static`, or `fn`
Value(ValueKind),
}
impl DefKind {
pub fn is_type(&self) -> bool {
matches!(self, Self::Type(_))
}
pub fn ty(&self) -> Option<&TypeKind> {
match self {
DefKind::Type(t) => Some(t),
_ => None,
}
}
pub fn is_value(&self) -> bool {
matches!(self, Self::Value(_))
}
pub fn value(&self) -> Option<&ValueKind> {
match self {
DefKind::Value(v) => Some(v),
_ => None,
}
}
}
}
pub mod type_kind {
//! A [TypeKind] represents an item in the Type Namespace
//! (a component of a [Project](crate::project::Project)).
use cl_ast::Visibility;
use std::{fmt::Debug, str::FromStr};
use crate::key::DefID;
/// The kind of a type
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TypeKind {
/// A type which has not yet been resolved
Undecided,
/// An alias for an already-defined type
Alias(Option<DefID>),
/// A primitive type, built-in to the compiler
Intrinsic(Intrinsic),
/// A user-defined abstract data type
Adt(Adt),
/// A reference to an already-defined type: &T
Ref(DefID),
/// A contiguous view of dynamically sized memory
Slice(DefID),
/// A function pointer which accepts multiple inputs and produces an output
FnPtr { args: Vec<DefID>, rety: DefID },
/// The unit type
Empty,
/// The never type
Never,
/// The Self type
SelfTy,
/// An untyped module
Module,
}
/// A user-defined Abstract Data Type
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Adt {
/// A union-like enum type
Enum(Vec<(String, DefID)>),
CLikeEnum(Vec<(String, u128)>),
/// An enum with no fields, which can never be constructed
FieldlessEnum,
/// A structural product type with named members
Struct(Vec<(String, Visibility, DefID)>),
/// A structural product type with unnamed members
TupleStruct(Vec<(Visibility, DefID)>),
/// 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<(String, DefID)>),
}
/// The set of compiler-intrinsic types.
/// These primitive types have native implementations of the basic operations.
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Intrinsic {
/// An 8-bit signed integer: `#[intrinsic = "i8"]`
I8,
/// A 16-bit signed integer: `#[intrinsic = "i16"]`
I16,
/// A 32-bit signed integer: `#[intrinsic = "i32"]`
I32,
/// A 64-bit signed integer: `#[intrinsic = "i32"]`
I64,
// /// A 128-bit signed integer: `#[intrinsic = "i32"]`
// I128,
/// An 8-bit unsigned integer: `#[intrinsic = "u8"]`
U8,
/// A 16-bit unsigned integer: `#[intrinsic = "u16"]`
U16,
/// A 32-bit unsigned integer: `#[intrinsic = "u32"]`
U32,
/// A 64-bit unsigned integer: `#[intrinsic = "u64"]`
U64,
// /// A 128-bit unsigned integer: `#[intrinsic = "u128"]`
// U128,
/// A boolean (`true` or `false`): `#[intrinsic = "bool"]`
Bool,
}
impl FromStr for Intrinsic {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"i8" => Intrinsic::I8,
"i16" => Intrinsic::I16,
"i32" => Intrinsic::I32,
"i64" => Intrinsic::I64,
"u8" => Intrinsic::U8,
"u16" => Intrinsic::U16,
"u32" => Intrinsic::U32,
"u64" => Intrinsic::U64,
"bool" => Intrinsic::Bool,
_ => Err(())?,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Float {
F32 = 0x20,
F64,
}
}
pub mod value_kind {
//! A [ValueKind] represents an item in the Value Namespace
//! (a component of a [Project](crate::project::Project)).
use crate::typeref::TypeRef;
use cl_ast::Block;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ValueKind {
Undecided,
Const(TypeRef),
Static(TypeRef),
Fn {
// TODO: Store the variable bindings here!
args: Vec<TypeRef>,
rety: TypeRef,
body: Block,
},
}
}
pub mod module {
//! A [Module] is a node in the Module Tree (a component of a
//! [Project](crate::project::Project))
use crate::key::DefID;
use std::collections::HashMap;
/// A [Module] is a node in the Module Tree (a component of a
/// [Project](crate::project::Project)).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Module {
pub parent: Option<DefID>,
pub types: HashMap<String, DefID>,
pub values: HashMap<String, DefID>,
}
impl Module {
pub fn new(parent: DefID) -> Self {
Self { parent: Some(parent), ..Default::default() }
}
}
}
pub mod path {
use cl_ast::{Path as AstPath, PathPart};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Path<'p> {
pub absolute: bool,
pub parts: &'p [PathPart],
}
impl<'p> Path<'p> {
pub fn new(path: &'p AstPath) -> Self {
let AstPath { absolute, parts } = path;
Self { absolute: *absolute, parts }
}
pub fn relative(self) -> Self {
Self { absolute: false, ..self }
}
pub fn pop_front(self) -> Option<Self> {
let Self { absolute, parts } = self;
Some(Self { absolute, parts: parts.get(1..)? })
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn len(&self) -> usize {
self.parts.len()
}
pub fn front(&self) -> Option<&PathPart> {
self.parts.first()
}
}
impl<'p> From<&'p AstPath> for Path<'p> {
fn from(value: &'p AstPath) -> Self {
Self::new(value)
}
}
impl std::fmt::Display for Path<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const SEPARATOR: &str = "::";
let Self { absolute, parts } = self;
if *absolute {
write!(f, "{SEPARATOR}")?
}
for (idx, part) in parts.iter().enumerate() {
write!(f, "{}{part}", if idx > 0 { SEPARATOR } else { "" })?;
}
Ok(())
}
}
}
pub mod project {
use crate::{
definition::{Def, DefKind},
key::DefID,
path::Path,
type_kind::TypeKind,
};
use cl_ast::{Identifier, PathPart, Visibility};
use cl_structures::intern_pool::Pool;
use std::ops::{Index, IndexMut};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Project {
pub pool: Pool<Def, DefID>,
pub module_root: DefID,
}
impl Project {
pub fn new() -> Self {
Self::default()
}
}
impl Default for Project {
fn default() -> Self {
let mut pool = Pool::default();
let module_root = pool.insert(Def::default());
// Insert the Never(!) type
let never = pool.insert(Def {
name: String::from("!"),
vis: Visibility::Public,
kind: DefKind::Type(TypeKind::Never),
..Default::default()
});
pool[module_root]
.module
.types
.insert(String::from("!"), never);
Self { pool, module_root }
}
}
impl Project {
pub fn parent_of(&self, module: DefID) -> Option<DefID> {
self[module].module.parent
}
pub fn root_of(&self, module: DefID) -> DefID {
match self.parent_of(module) {
Some(module) => self.root_of(module),
None => module,
}
}
/// Resolves a path within a module tree, finding the innermost module.
/// Returns the remaining path parts.
pub fn get_type<'a>(&self, path: Path<'a>, within: DefID) -> Option<(DefID, Path<'a>)> {
// TODO: Cache module lookups
if path.absolute {
self.get_type(path.relative(), self.root_of(within))
} else if let Some(front) = path.front() {
let module = &self[within].module;
match front {
PathPart::SelfKw => self.get_type(path.pop_front()?, within),
PathPart::SuperKw => self.get_type(path.pop_front()?, module.parent?),
PathPart::Ident(Identifier(name)) => match module.types.get(name) {
Some(&submodule) => self.get_type(path.pop_front()?, submodule),
None => Some((within, path)),
},
}
} else {
Some((within, path))
}
}
pub fn get_value<'a>(&self, path: Path<'a>, within: DefID) -> Option<(DefID, Path<'a>)> {
match path.front()? {
PathPart::Ident(Identifier(name)) => Some((
self[within].module.values.get(name).copied()?,
path.pop_front()?,
)),
_ => None,
}
}
#[rustfmt::skip]
pub fn insert_type(&mut self, name: String, value: Def, parent: DefID) -> Option<DefID> {
let id = self.pool.insert(value);
self[parent].module.types.insert(name, id)
}
#[rustfmt::skip]
pub fn insert_value(&mut self, name: String, value: Def, parent: DefID) -> Option<DefID> {
let id = self.pool.insert(value);
self[parent].module.values.insert(name, id)
}
}
/// Implements [Index] and [IndexMut] for [Project]: `self.table[ID] -> Definition`
macro_rules! impl_index {
($(self.$table:ident[$idx:ty] -> $out:ty),*$(,)?) => {$(
impl Index<$idx> for Project {
type Output = $out;
fn index(&self, index: $idx) -> &Self::Output {
&self.$table[index]
}
}
impl IndexMut<$idx> for Project {
fn index_mut(&mut self, index: $idx) -> &mut Self::Output {
&mut self.$table[index]
}
}
)*};
}
impl_index! {
self.pool[DefID] -> Def,
// self.types[TypeID] -> TypeDef,
// self.values[ValueID] -> ValueDef,
}
}
pub mod name_collector {
//! Performs step 1 of type checking: Collecting all the names of things into [Module] units
use crate::{
definition::{Def, DefKind},
key,
project::Project,
type_kind::{Adt, TypeKind},
value_kind::ValueKind,
};
use cl_ast::*;
use std::ops::{Deref, DerefMut};
/// Collects types for future use
#[derive(Debug, PartialEq, Eq)]
pub struct NameCollector<'prj> {
/// A stack of the current modules
pub mod_stack: Vec<key::DefID>,
/// The [Project], the type checker and resolver's central data store
pub project: &'prj mut Project,
}
impl<'prj> NameCollector<'prj> {
pub fn new(project: &'prj mut Project) -> Self {
// create a root module
Self { mod_stack: vec![project.module_root], project }
}
/// Gets the currently traversed parent module
pub fn parent(&self) -> Option<key::DefID> {
self.mod_stack.last().copied()
}
}
impl Deref for NameCollector<'_> {
type Target = Project;
fn deref(&self) -> &Self::Target {
self.project
}
}
impl DerefMut for NameCollector<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.project
}
}
impl NameCollector<'_> {
pub fn file(&mut self, f: &File) -> Result<(), &'static str> {
let parent = self.parent().ok_or("No parent to add item to")?;
for item in &f.items {
let def = match &item.kind {
// modules
// types
ItemKind::Module(_) => {
self.module(item)?;
continue;
}
ItemKind::Enum(_) => Some(self.ty_enum(item)?),
ItemKind::Alias(_) => Some(self.ty_alias(item)?),
ItemKind::Struct(_) => Some(self.ty_struct(item)?),
// values processed by the value collector
ItemKind::Const(_) => Some(self.val_const(item)?),
ItemKind::Static(_) => Some(self.val_static(item)?),
ItemKind::Function(_) => Some(self.val_function(item)?),
ItemKind::Impl(_) => None,
};
let Some(def) = def else { continue };
match def.kind {
DefKind::Type(_) => {
if let Some(v) = self.insert_type(def.name.clone(), def, parent) {
panic!("Redefinition of type {} ({v:?})!", self[v].name)
}
}
DefKind::Value(_) => {
if let Some(v) = self.insert_value(def.name.clone(), def, parent) {
panic!("Redefinition of value {} ({v:?})!", self[v].name)
}
}
}
}
Ok(())
}
/// Collects a [Module]
pub fn module(&mut self, m: &Item) -> Result<(), &'static str> {
let Item { kind: ItemKind::Module(Module { name, kind }), vis, attrs, .. } = m else {
Err("module called on Item which was not an ItemKind::Module")?
};
let ModuleKind::Inline(kind) = kind else {
Err("Out-of-line modules not yet supported")?
};
let parent = self.parent().ok_or("No parent to add module to")?;
let module = self.pool.insert(Def::new_module(
name.0.clone(),
*vis,
attrs.meta.clone(),
Some(parent),
));
self[parent]
.module
.types
.insert(name.0.clone(), module)
.is_some()
.then(|| panic!("Error: redefinition of module {name}"));
self.mod_stack.push(module);
let out = self.file(kind);
self.mod_stack.pop();
out
}
}
/// Type collection
impl NameCollector<'_> {
/// Collects an [Item] of type [ItemKind::Enum]
pub fn ty_enum(&mut self, item: &Item) -> Result<Def, &'static str> {
let Item { kind: ItemKind::Enum(Enum { name, kind }), vis, attrs, .. } = item else {
Err("Enum called on item which was not ItemKind::Enum")?
};
let kind = match kind {
EnumKind::NoVariants => DefKind::Type(TypeKind::Adt(Adt::FieldlessEnum)),
EnumKind::Variants(_) => DefKind::Type(TypeKind::Undecided),
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind,
source: Some(item.clone()),
module: Default::default(),
})
}
/// Collects an [Item] of type [ItemKind::Alias]
pub fn ty_alias(&mut self, item: &Item) -> Result<Def, &'static str> {
let Item { kind: ItemKind::Alias(Alias { to: name, from }), vis, attrs, .. } = item
else {
Err("Alias called on Item which was not ItemKind::Alias")?
};
let mut kind = match from {
Some(_) => DefKind::Type(TypeKind::Undecided),
None => DefKind::Type(TypeKind::Alias(None)),
};
for meta in &attrs.meta {
let Meta { name: meta_name, kind: meta_kind } = meta;
match (meta_name.0.as_str(), meta_kind) {
("intrinsic", MetaKind::Equals(Literal::String(intrinsic))) => {
kind = DefKind::Type(TypeKind::Intrinsic(
intrinsic.parse().map_err(|_| "unknown intrinsic type")?,
));
}
("intrinsic", MetaKind::Plain) => {
kind = DefKind::Type(TypeKind::Intrinsic(
name.0.parse().map_err(|_| "Unknown intrinsic type")?,
))
}
_ => {}
}
}
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind,
source: Some(item.clone()),
module: Default::default(),
})
}
/// Collects an [Item] of type [ItemKind::Struct]
pub fn ty_struct(&mut self, item: &Item) -> Result<Def, &'static str> {
let Item { kind: ItemKind::Struct(Struct { name, kind }), vis, attrs, .. } = item
else {
Err("Struct called on item which was not ItemKind::Struct")?
};
let kind = match kind {
StructKind::Empty => DefKind::Type(TypeKind::Adt(Adt::UnitStruct)),
StructKind::Tuple(_) => DefKind::Type(TypeKind::Undecided),
StructKind::Struct(_) => DefKind::Type(TypeKind::Undecided),
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind,
source: Some(item.clone()),
module: Default::default(),
})
}
}
/// Value collection
impl NameCollector<'_> {
pub fn val_const(&mut self, item: &Item) -> Result<Def, &'static str> {
let Item { kind: ItemKind::Const(Const { name, .. }), vis, attrs, .. } = item else {
Err("Const called on Item which was not ItemKind::Const")?
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind: DefKind::Value(ValueKind::Undecided),
source: Some(item.clone()),
module: Default::default(),
})
}
pub fn val_static(&mut self, item: &Item) -> Result<Def, &'static str> {
let Item { kind: ItemKind::Static(Static { name, .. }), vis, attrs, .. } = item else {
Err("Static called on Item which was not ItemKind::Static")?
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind: DefKind::Type(TypeKind::Undecided),
source: Some(item.clone()),
module: Default::default(),
})
}
pub fn val_function(&mut self, item: &Item) -> Result<Def, &'static str> {
// TODO: treat function bodies like modules with internal items
let Item { kind: ItemKind::Function(Function { name, .. }), vis, attrs, .. } = item
else {
Err("val_function called on Item which was not ItemKind::Function")?
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind: DefKind::Value(ValueKind::Undecided),
source: Some(item.clone()),
module: Default::default(),
})
}
}
}
pub mod type_resolver {
//! Performs step 2 of type checking: Evaluating type definitions
#![allow(unused)]
use std::ops::{Deref, DerefMut};
use cl_ast::*;
use crate::{definition::Def, key::DefID, project::Project};
pub struct TypeResolver<'prj> {
pub project: &'prj mut Project,
}
impl Deref for TypeResolver<'_> {
type Target = Project;
fn deref(&self) -> &Self::Target {
self.project
}
}
impl DerefMut for TypeResolver<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.project
}
}
impl TypeResolver<'_> {
pub fn resolve(&mut self) -> Result<bool, &str> {
#![allow(unused)]
for typedef in self.pool.iter_mut().filter(|v| v.kind.is_type()) {
let Def { name, vis, meta: attr, kind, source: Some(ref definition), module: _ } =
typedef
else {
continue;
};
match &definition.kind {
ItemKind::Alias(Alias { to: _, from: Some(from) }) => match &from.kind {
TyKind::Never => todo!(),
TyKind::Empty => todo!(),
TyKind::SelfTy => todo!(),
TyKind::Path(_) => todo!(),
TyKind::Tuple(_) => todo!(),
TyKind::Ref(_) => todo!(),
TyKind::Fn(_) => todo!(),
},
ItemKind::Alias(_) => {}
ItemKind::Const(_) => todo!(),
ItemKind::Static(_) => todo!(),
ItemKind::Module(_) => todo!(),
ItemKind::Function(_) => {}
ItemKind::Struct(_) => {}
ItemKind::Enum(_) => {}
ItemKind::Impl(_) => {}
}
}
Ok(true)
}
pub fn get_type(&self, kind: &TyKind) -> Option<DefID> {
match kind {
TyKind::Never => todo!(),
TyKind::Empty => todo!(),
TyKind::SelfTy => todo!(),
TyKind::Path(_) => todo!(),
TyKind::Tuple(_) => todo!(),
TyKind::Ref(_) => todo!(),
TyKind::Fn(_) => todo!(),
}
None
}
}
}
pub mod typeref {
//! Stores type and reference info
use crate::key::DefID;
/// The Type struct represents all valid types, and can be trivially equality-compared
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TypeRef {
/// You can only have a pointer chain 65535 pointers long.
ref_depth: u16,
/// Types can be [Generic](RefKind::Generic) or [Concrete](RefKind::Concrete)
kind: RefKind,
}
/// Types can be [Generic](RefKind::Generic) or [Concrete](RefKind::Concrete)
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RefKind {
/// A Concrete type has an associated [Def](super::definition::Def)
Concrete(DefID),
/// A Generic type is a *locally unique* comparable value,
/// valid only until the end of its typing context.
/// This is usually the surrounding function.
Generic(usize),
}
}
/*
/// What is an inference rule?
/// An inference rule is a specification with a set of predicates and a judgement
/// Let's give every type an ID
struct TypeID(usize);
/// Let's give every type some data:
struct TypeDef<'def> {
name: String,
definition: &'def Item,
}
and store them in a big vector of type descriptions:
struct TypeMap<'def> {
types: Vec<TypeDef<'def>>,
}
// todo: insertion of a type should yield a TypeID
// todo: impl index with TypeID
Let's store type information as either a concrete type or a generic type:
/// The Type struct represents all valid types, and can be trivially equality-compared
pub struct Type {
/// You can only have a pointer chain 65535 pointers long.
ref_depth: u16,
kind: TKind,
}
pub enum TKind {
Concrete(TypeID),
Generic(usize),
}
And assume I can specify a rule based on its inputs and outputs:
Rule {
operation: If,
/// The inputs field is populated by
inputs: [Concrete(BOOL), Generic(0), Generic(0)],
outputs: Generic(0),
/// This rule is compiler-intrinsic!
through: None,
}
Rule {
operation: Add,
inputs: [Concrete(I32), Concrete(I32)],
outputs: Concrete(I32),
/// This rule is not compiler-intrinsic (it is overloaded!)
through: Some(&ImplAddForI32::Add),
}
These rules can be stored in some kind of rule database:
let rules: Hashmap<Operation, Vec<Rule>> {
}
*/
pub mod rule {
use crate::{key::DefID, typeref::TypeRef};
pub struct Rule {
/// What is this Rule for?
pub operation: (),
/// What inputs does it take?
pub inputs: Vec<TypeRef>,
/// What output does it produce?
pub output: TypeRef,
/// Where did this rule come from?
pub through: Origin,
}
// TODO: Genericize
pub enum Operation {
Mul,
Div,
Rem,
Add,
Sub,
Deref,
Neg,
Not,
At,
Tilde,
Index,
If,
While,
For,
}
pub enum Origin {
/// This rule is built into the compiler
Intrinsic,
/// This rule is derived from an implementation on a type
Extrinsic(DefID),
}
}
pub mod typeck {
#![allow(unused)]
use cl_ast::*;
pub struct Context {
rules: (),
}
trait TypeCheck {}
}
//

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

@@ -8,9 +8,5 @@ fn main() {
/// Implements the classic recursive definition of fib()
fn fib(a: i64) -> i64 {
if a > 1 {
fib(a - 1) + fib(a - 2)
} else {
1
}
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,5 +1,5 @@
#![allow(unused_imports)]
use crate::{env::Environment, temp_type_impl::ConValue, Interpret};
use crate::{Interpret, convalue::ConValue, env::Environment};
use cl_ast::*;
use cl_lexer::Lexer;
use cl_parser::Parser;
@@ -48,6 +48,7 @@ mod macros {
//! ```
#![allow(unused_macros)]
use crate::IResult;
use cl_parser::parser::Parse;
use super::*;
@@ -63,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
@@ -127,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))
@@ -135,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))
@@ -177,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 {
@@ -187,10 +227,10 @@ mod fn_declarations {
assert_eval!(env, fn empty_fn() {});
// TODO: true equality for functions
assert_eq!(
"fn empty_fn () {\n \n}",
"fn empty_fn () {}",
format!(
"{}",
env.get("empty_fn")
env.get("empty_fn".into())
.expect(stringify!(empty_fn should be defined and initialized))
)
)
@@ -363,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);
@@ -436,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();
@@ -468,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,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

@@ -110,7 +110,7 @@ mod string {
}
mod punct {
macro op($op:ident) {
TokenKind::Punct(Punct::$op)
TokenKind::$op
}
use super::*;

View File

@@ -1,5 +1,6 @@
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>;
@@ -7,6 +8,7 @@ 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,
@@ -22,14 +24,16 @@ pub enum ErrorKind {
UnmatchedCurlyBraces,
UnmatchedSquareBrackets,
Unexpected(TokenKind),
Expected {
ExpectedToken {
want: TokenKind,
got: TokenKind,
},
/// No rules matched
Nothing,
ExpectedParsing {
want: Parsing,
},
InvalidPattern(Box<Expr>),
/// Indicates unfinished code
Todo,
Todo(&'static str),
}
impl From<LexError> for ErrorKind {
fn from(value: LexError) -> Self {
@@ -43,15 +47,20 @@ impl From<LexError> for ErrorKind {
/// 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,
Visibility,
Mutability,
ItemKind,
Generics,
Alias,
Const,
Static,
@@ -67,38 +76,47 @@ pub enum Parsing {
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,
PathExpr,
PathPart,
Identifier,
Literal,
Array,
ArrayRep,
AddrOf,
Block,
Group,
Tuple,
Loop,
While,
If,
For,
@@ -106,17 +124,26 @@ pub enum Parsing {
Break,
Return,
Continue,
Pattern,
Match,
MatchArm,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { reason, while_parsing, loc } = self;
let Self { in_file, reason, while_parsing, loc } = self;
match reason {
// TODO entries are debug-printed
ErrorKind::Todo => write!(f, "{loc} {reason} {while_parsing:?}"),
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})"),
_ => write!(f, "{loc} {reason} while parsing {while_parsing}"),
_ => {
if !in_file.is_empty() {
write!(f, "{in_file}:")?
}
write!(f, "{loc}: {reason} while parsing {while_parsing}")
}
}
}
}
@@ -129,26 +156,29 @@ impl Display for ErrorKind {
ErrorKind::UnmatchedCurlyBraces => write!(f, "Unmatched curly braces"),
ErrorKind::UnmatchedSquareBrackets => write!(f, "Unmatched square brackets"),
ErrorKind::Unexpected(t) => write!(f, "Encountered unexpected token `{t}`"),
ErrorKind::Expected { want: e, got: g } => {
write!(f, "Expected `{e}`, got `{g}`")
}
ErrorKind::Nothing => write!(f, "Nothing found"),
ErrorKind::Todo => write!(f, "TODO:"),
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::Visibility => "a visibility qualifier",
Parsing::Mutability => "a mutability qualifier",
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",
@@ -164,38 +194,47 @@ impl Display for Parsing {
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::PathExpr => "a path",
Parsing::PathPart => "a path component",
Parsing::Identifier => "an identifier",
Parsing::Literal => "a literal",
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",
@@ -203,6 +242,10 @@ impl Display for Parsing {
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

@@ -14,3 +14,5 @@ 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

@@ -14,10 +14,9 @@ cl-ast = { path = "../cl-ast" }
cl-lexer = { path = "../cl-lexer" }
cl-token = { path = "../cl-token" }
cl-parser = { path = "../cl-parser" }
cl-interpret = { path = "../cl-interpret" }
crossterm = "0.27.0"
argh = "0.1.12"
[dev-dependencies]
cl-structures = { path = "../cl-structures" }
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

@@ -4,7 +4,7 @@ use cl_lexer::Lexer;
use cl_token::Token;
use std::{
error::Error,
io::{stdin, IsTerminal, Read},
io::{IsTerminal, Read, stdin},
path::{Path, PathBuf},
};

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

@@ -1,8 +1,9 @@
//! Pretty prints a conlang AST in yaml
use cl_ast::Stmt;
use cl_lexer::Lexer;
use cl_parser::Parser;
use cl_repl::repline::{error::Error as RlError, Repline};
use repline::{Repline, error::Error as RlError};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
@@ -18,8 +19,8 @@ fn main() -> Result<(), Box<dyn Error>> {
Err(e) => Err(e)?,
};
let mut parser = Parser::new(Lexer::new(&line));
let code = match parser.stmt() {
let mut parser = Parser::new("", Lexer::new(&line));
let code = match parser.parse::<Stmt>() {
Ok(code) => {
rl.accept();
code
@@ -40,7 +41,6 @@ pub use yamler::Yamler;
pub mod yamler {
use crate::yamlify::Yamlify;
use std::{
fmt::Display,
io::Write,
ops::{Deref, DerefMut},
};
@@ -54,7 +54,7 @@ pub mod yamler {
Self::default()
}
pub fn indent(&mut self) -> Section {
pub fn indent(&mut self) -> Section<'_> {
Section::new(self)
}
@@ -80,28 +80,33 @@ pub mod yamler {
}
/// Prints a section header and increases indentation
pub fn key(&mut self, name: impl Display) -> Section {
pub fn key(&mut self, name: impl Yamlify) -> Section<'_> {
println!();
self.print_indentation(&mut std::io::stdout().lock());
print!("- {name}:");
print!(" ");
name.yaml(self);
print!(":");
self.indent()
}
/// Prints a yaml key value pair: `- name: "value"`
pub fn pair<D: Display, T: Yamlify>(&mut self, name: D, value: T) -> &mut Self {
self.key(name).yaml(&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: Display>(&mut self, value: D) -> &mut Self {
print!(" {value}");
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 (idx, value) in list.iter().enumerate() {
self.pair(idx, value);
for value in list {
println!();
self.print_indentation(&mut std::io::stdout().lock());
self.yaml(&"- ").yaml(value);
}
self
}
@@ -119,19 +124,19 @@ pub mod yamler {
}
}
impl<'y> Deref for Section<'y> {
impl Deref for Section<'_> {
type Target = Yamler;
fn deref(&self) -> &Self::Target {
self.yamler
}
}
impl<'y> DerefMut for Section<'y> {
impl DerefMut for Section<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.yamler
}
}
impl<'y> Drop for Section<'y> {
impl Drop for Section<'_> {
fn drop(&mut self) {
let Self { yamler } = self;
yamler.decrease();
@@ -149,8 +154,8 @@ pub mod yamlify {
impl Yamlify for File {
fn yaml(&self, y: &mut Yamler) {
let File { items } = self;
y.key("File").yaml(items);
let File { name, items } = self;
y.key("File").pair("name", name).yaml(items);
}
}
impl Yamlify for Visibility {
@@ -192,7 +197,7 @@ pub mod yamlify {
impl Yamlify for Item {
fn yaml(&self, y: &mut Yamler) {
let Self { extents: _, attrs, vis, kind } = self;
let Self { span: _, attrs, vis, kind } = self;
y.key("Item").yaml(attrs).yaml(vis).yaml(kind);
}
}
@@ -207,13 +212,20 @@ pub mod yamlify {
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 { to, from } = self;
y.key("Alias").pair("to", to).pair("from", from);
let Self { name, from } = self;
y.key("Alias").pair("to", name).pair("from", from);
}
}
impl Yamlify for Const {
@@ -233,32 +245,28 @@ pub mod yamlify {
}
impl Yamlify for Module {
fn yaml(&self, y: &mut Yamler) {
let Self { name, kind } = self;
y.key("Module").pair("name", name).yaml(kind);
}
}
impl Yamlify for ModuleKind {
fn yaml(&self, y: &mut Yamler) {
match self {
ModuleKind::Inline(f) => y.yaml(f),
ModuleKind::Outline => y,
};
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, args, body, rety } = self;
let Self { name, gens, sign, bind, body } = self;
y.key("Function")
.pair("name", name)
.pair("args", args)
.pair("body", body)
.pair("rety", rety);
.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, kind } = self;
y.key("Struct").pair("name", name).yaml(kind);
let Self { name, gens, kind } = self;
y.key("Struct")
.pair("gens", gens)
.pair("name", name)
.yaml(kind);
}
}
impl Yamlify for StructKind {
@@ -278,38 +286,56 @@ pub mod yamlify {
}
impl Yamlify for Enum {
fn yaml(&self, y: &mut Yamler) {
let Self { name, kind } = self;
y.key("Enum").pair("name", name).yaml(kind);
}
}
impl Yamlify for EnumKind {
fn yaml(&self, y: &mut Yamler) {
match self {
EnumKind::NoVariants => y,
EnumKind::Variants(v) => y.yaml(v),
};
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 } = self;
y.key("Variant").pair("name", name).yaml(kind);
}
}
impl Yamlify for VariantKind {
fn yaml(&self, y: &mut Yamler) {
match self {
VariantKind::Plain => y,
VariantKind::CLike(v) => y.yaml(v),
VariantKind::Tuple(v) => y.yaml(v),
VariantKind::Struct(v) => y.yaml(v),
};
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 { target, body } = self;
y.key("Impl").pair("target", target).pair("body", body);
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 {
@@ -320,8 +346,8 @@ pub mod yamlify {
}
impl Yamlify for Stmt {
fn yaml(&self, y: &mut Yamler) {
let Self { extents: _, kind, semi } = self;
y.key("Stmt").yaml(kind).yaml(semi);
let Self { span: _, kind, semi } = self;
y.key("Stmt").value(kind).yaml(semi);
}
}
impl Yamlify for Semi {
@@ -335,35 +361,32 @@ pub mod yamlify {
fn yaml(&self, y: &mut Yamler) {
match self {
StmtKind::Empty => y,
StmtKind::Local(s) => y.yaml(s),
StmtKind::Item(s) => y.yaml(s),
StmtKind::Expr(s) => y.yaml(s),
};
}
}
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 Expr {
fn yaml(&self, y: &mut Yamler) {
let Self { extents: _, kind } = self;
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),
@@ -378,22 +401,99 @@ pub mod yamlify {
ExprKind::For(k) => k.yaml(y),
ExprKind::Break(k) => k.yaml(y),
ExprKind::Return(k) => k.yaml(y),
ExprKind::Continue(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 { kind, parts } = self;
let Self { parts } = self;
y.key("Assign")
.pair("kind", kind)
.pair("head", &parts.0)
.pair("tail", &parts.1);
}
}
impl Yamlify for AssignKind {
impl Yamlify for Modify {
fn yaml(&self, y: &mut Yamler) {
y.value(self);
let Self { kind, parts } = self;
y.key("Modify")
.pair("kind", kind)
.pair("head", &parts.0)
.pair("tail", &parts.1);
}
}
impl Yamlify for Binary {
@@ -405,20 +505,31 @@ pub mod yamlify {
.pair("tail", &parts.1);
}
}
impl Yamlify for BinaryKind {
fn yaml(&self, y: &mut Yamler) {
y.value(self);
}
}
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 UnaryKind {
impl Yamlify for Cast {
fn yaml(&self, y: &mut Yamler) {
y.value(self);
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 {
@@ -430,7 +541,22 @@ pub mod yamlify {
impl Yamlify for Index {
fn yaml(&self, y: &mut Yamler) {
let Self { head, indices } = self;
y.key("Index").pair("head", head).list(indices);
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 {
@@ -449,11 +575,8 @@ pub mod yamlify {
}
impl Yamlify for AddrOf {
fn yaml(&self, y: &mut Yamler) {
let Self { count, mutable, expr } = self;
y.key("AddrOf")
.yaml(mutable)
.pair("count", count)
.pair("expr", expr);
let Self { mutable, expr } = self;
y.key("AddrOf").yaml(mutable).pair("expr", expr);
}
}
impl Yamlify for Group {
@@ -468,13 +591,13 @@ pub mod yamlify {
y.key("While")
.pair("cond", cond)
.pair("pass", pass)
.pair("fail", fail);
.yaml(fail);
}
}
impl Yamlify for Else {
fn yaml(&self, y: &mut Yamler) {
let Self { body } = self;
y.key("Else").yaml(body);
y.key("fail").yaml(body);
}
}
impl Yamlify for If {
@@ -505,47 +628,35 @@ pub mod yamlify {
y.key("Return").yaml(body);
}
}
impl Yamlify for Continue {
fn yaml(&self, y: &mut Yamler) {
y.key("Continue");
}
}
impl Yamlify for Literal {
fn yaml(&self, y: &mut Yamler) {
y.value(format_args!("\"{self}\""));
}
}
impl Yamlify for Identifier {
fn yaml(&self, y: &mut Yamler) {
let Self(name) = self;
y.value(name);
}
}
impl Yamlify for Param {
fn yaml(&self, y: &mut Yamler) {
let Self { mutability, name, ty } = self;
y.key("Param")
.yaml(mutability)
.pair("name", name)
.pair("ty", ty);
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 { extents: _, kind } = self;
y.key("Ty").yaml(kind);
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::Empty => y.value("Empty"),
TyKind::SelfTy => y.value("Self"),
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),
};
}
}
@@ -556,20 +667,30 @@ pub mod yamlify {
if *absolute {
y.pair("absolute", absolute);
}
for part in parts {
y.pair("part", part);
}
y.yaml(parts);
}
}
impl Yamlify for PathPart {
fn yaml(&self, y: &mut Yamler) {
match self {
PathPart::SuperKw => y.value("super"),
PathPart::SelfKw => y.value("self"),
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;
@@ -581,8 +702,17 @@ pub mod yamlify {
}
impl Yamlify for TyRef {
fn yaml(&self, y: &mut Yamler) {
let Self { count, to } = self;
y.key("TyRef").pair("count", count).pair("to", to);
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 {
@@ -608,9 +738,7 @@ pub mod yamlify {
}
impl<T: Yamlify> Yamlify for Vec<T> {
fn yaml(&self, y: &mut Yamler) {
for thing in self {
y.yaml(thing);
}
y.list(self);
}
}
impl Yamlify for () {
@@ -626,14 +754,15 @@ pub mod yamlify {
macro_rules! scalar {
($($t:ty),*$(,)?) => {
$(impl Yamlify for $t {
fn yaml(&self, y: &mut Yamler) {
y.value(self);
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
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

@@ -8,3 +8,4 @@ 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

@@ -8,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
}
}
@@ -33,6 +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}:")
write!(f, "{line}:{col}")
}
}

View File

@@ -93,8 +93,8 @@ use std::{
/// assert_eq!(Some(&10), v.last());
/// ```
pub macro stack {
($count:literal) => {
Stack::<_, $count>::new()
($capacity:literal) => {
Stack::<_, $capacity>::new()
},
($value:expr ; $count:literal) => {{
let mut stack: Stack<_, $count> = Stack::new();
@@ -103,6 +103,13 @@ pub macro stack {
}
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),*])
}
@@ -142,20 +149,14 @@ impl<T, const N: usize> Deref for Stack<T, N> {
#[inline]
fn deref(&self) -> &Self::Target {
// 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) }
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for Stack<T, N> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
// Safety:
// - See Deref
unsafe { slice::from_raw_parts_mut(self.buf.as_mut_ptr().cast(), self.len) }
self.as_mut_slice()
}
}
@@ -163,8 +164,10 @@ impl<T, const N: usize> DerefMut for Stack<T, N> {
unsafe impl<#[may_dangle] T, const N: usize> Drop for Stack<T, N> {
#[inline]
fn drop(&mut self) {
// Safety: We have ensured that all elements in the list are
unsafe { core::ptr::drop_in_place(self.as_mut_slice()) };
// Safety: Elements in [0..self.len] are initialized
if std::mem::needs_drop::<T>() {
unsafe { core::ptr::drop_in_place(self.as_mut_slice()) };
}
}
}
@@ -269,18 +272,24 @@ impl<T, const N: usize> Stack<T, N> {
}
/// Returns an unsafe mutable pointer to the stack's buffer
pub fn as_mut_ptr(&mut self) -> *mut T {
pub const fn as_mut_ptr(&mut self) -> *mut T {
self.buf.as_mut_ptr().cast()
}
/// Extracts a slice containing the entire vector
pub fn as_slice(&self) -> &[T] {
self
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 fn as_mut_slice(&mut self) -> &mut [T] {
self
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
@@ -353,7 +362,7 @@ impl<T, const N: usize> Stack<T, N> {
/// v.push(3);
/// assert_eq!(&[0, 1, 2, 3], v.as_slice());
/// ```
pub fn push(&mut self, value: T) {
pub const fn push(&mut self, value: T) {
if self.len >= N {
panic!("Attempted to push into full stack")
}
@@ -364,7 +373,7 @@ impl<T, const N: usize> Stack<T, N> {
/// Push a new element onto the end of the stack
///
/// Returns [`Err(value)`](Result::Err) if the new length would exceed capacity
pub fn try_push(&mut self, value: T) -> Result<(), T> {
pub const fn try_push(&mut self, value: T) -> Result<(), T> {
if self.len >= N {
return Err(value);
}
@@ -379,8 +388,11 @@ impl<T, const N: usize> Stack<T, N> {
///
/// len after push must not exceed capacity N
#[inline]
unsafe fn push_unchecked(&mut self, value: T) {
unsafe { ptr::write(self.as_mut_ptr().add(self.len), value) }
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
}
@@ -400,13 +412,14 @@ impl<T, const N: usize> Stack<T, N> {
/// assert_eq!(Some(0), v.pop());
/// assert_eq!(None, v.pop());
/// ```
pub fn pop(&mut self) -> Option<T> {
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()) })
}
}
@@ -505,7 +518,7 @@ impl<T, const N: usize> Stack<T, N> {
///
/// assert_eq!(Ok(()), v.try_insert(0, 0));
/// ```
pub fn try_insert(&mut self, index: usize, data: T) -> Result<(), (T, InsertFailed<N>)> {
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)));
}
@@ -521,7 +534,7 @@ impl<T, const N: usize> Stack<T, N> {
/// - index must be less than self.len
/// - length after insertion must be <= N
#[inline]
unsafe fn insert_unchecked(&mut self, index: usize, data: T) {
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) }
@@ -545,7 +558,9 @@ impl<T, const N: usize> Stack<T, N> {
/// ```
pub fn clear(&mut self) {
// Hopefully copy elision takes care of this lmao
drop(std::mem::take(self))
while !self.is_empty() {
drop(self.pop());
}
}
/// Returns the number of elements in the stack
@@ -555,7 +570,7 @@ impl<T, const N: usize> Stack<T, N> {
///
/// assert_eq!(5, v.len());
/// ```
pub fn len(&self) -> usize {
pub const fn len(&self) -> usize {
self.len
}
@@ -570,7 +585,7 @@ impl<T, const N: usize> Stack<T, N> {
/// assert!(v.is_full());
/// ```
#[inline]
pub fn is_full(&self) -> bool {
pub const fn is_full(&self) -> bool {
self.len >= N
}
@@ -585,7 +600,7 @@ impl<T, const N: usize> Stack<T, N> {
/// assert!(v.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
@@ -617,13 +632,13 @@ mod tests {
assert_eq!(std::mem::size_of::<usize>(), std::mem::size_of_val(&v))
}
#[test]
#[cfg_attr(debug_assertions, ignore = "calls ().drop() usize::MAX times")]
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();
@@ -744,4 +759,19 @@ mod tests {
]);
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

@@ -10,4 +10,4 @@ pub mod token_type;
pub use token::Token;
pub use token_data::TokenData;
pub use token_type::{Punct, TokenKind};
pub use token_type::TokenKind;

View File

@@ -13,38 +13,35 @@ pub enum TokenKind {
/// A non-keyword identifier
Identifier,
// A keyword
Break,
Cl,
Const,
Continue,
Else,
Enum,
False,
For,
Fn,
If,
Impl,
In,
Let,
Mod,
Mut,
Pub,
Return,
SelfKw,
SelfTy,
Static,
Struct,
Super,
True,
Type,
While,
/// Delimiter or punctuation
Punct(Punct),
}
/// An operator character (delimiter, punctuation)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Punct {
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, // [
@@ -109,6 +106,7 @@ impl Display for TokenKind {
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),
@@ -116,26 +114,81 @@ impl Display for TokenKind {
TokenKind::Else => "else".fmt(f),
TokenKind::Enum => "enum".fmt(f),
TokenKind::False => "false".fmt(f),
TokenKind::For => "for".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::SelfKw => "self".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::Punct(op) => op.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),
}
}
}
@@ -145,6 +198,7 @@ impl FromStr for TokenKind {
/// 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,
@@ -152,86 +206,27 @@ impl FromStr for TokenKind {
"else" => Self::Else,
"enum" => Self::Enum,
"false" => Self::False,
"for" => Self::For,
"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::SelfKw,
"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(())?,
})
}
}
impl Display for Punct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Punct::LCurly => "{".fmt(f),
Punct::RCurly => "}".fmt(f),
Punct::LBrack => "[".fmt(f),
Punct::RBrack => "]".fmt(f),
Punct::LParen => "(".fmt(f),
Punct::RParen => ")".fmt(f),
Punct::Amp => "&".fmt(f),
Punct::AmpAmp => "&&".fmt(f),
Punct::AmpEq => "&=".fmt(f),
Punct::Arrow => "->".fmt(f),
Punct::At => "@".fmt(f),
Punct::Backslash => "\\".fmt(f),
Punct::Bang => "!".fmt(f),
Punct::BangBang => "!!".fmt(f),
Punct::BangEq => "!=".fmt(f),
Punct::Bar => "|".fmt(f),
Punct::BarBar => "||".fmt(f),
Punct::BarEq => "|=".fmt(f),
Punct::Colon => ":".fmt(f),
Punct::ColonColon => "::".fmt(f),
Punct::Comma => ",".fmt(f),
Punct::Dot => ".".fmt(f),
Punct::DotDot => "..".fmt(f),
Punct::DotDotEq => "..=".fmt(f),
Punct::Eq => "=".fmt(f),
Punct::EqEq => "==".fmt(f),
Punct::FatArrow => "=>".fmt(f),
Punct::Grave => "`".fmt(f),
Punct::Gt => ">".fmt(f),
Punct::GtEq => ">=".fmt(f),
Punct::GtGt => ">>".fmt(f),
Punct::GtGtEq => ">>=".fmt(f),
Punct::Hash => "#".fmt(f),
Punct::HashBang => "#!".fmt(f),
Punct::Lt => "<".fmt(f),
Punct::LtEq => "<=".fmt(f),
Punct::LtLt => "<<".fmt(f),
Punct::LtLtEq => "<<=".fmt(f),
Punct::Minus => "-".fmt(f),
Punct::MinusEq => "-=".fmt(f),
Punct::Plus => "+".fmt(f),
Punct::PlusEq => "+=".fmt(f),
Punct::Question => "?".fmt(f),
Punct::Rem => "%".fmt(f),
Punct::RemEq => "%=".fmt(f),
Punct::Semi => ";".fmt(f),
Punct::Slash => "/".fmt(f),
Punct::SlashEq => "/=".fmt(f),
Punct::Star => "*".fmt(f),
Punct::StarEq => "*=".fmt(f),
Punct::Tilde => "~".fmt(f),
Punct::Xor => "^".fmt(f),
Punct::XorEq => "^=".fmt(f),
Punct::XorXor => "^^".fmt(f),
}
}
}

View File

@@ -10,3 +10,8 @@ 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}"),
}
}
}

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