178 Commits

Author SHA1 Message Date
61d8cf8550 stdlib: Update num.cl with eased name resolution restrictions 2024-07-21 06:03:16 -05:00
70872d86f9 cl-typeck: Improve path resolution semantics, and DON'T REPARENT IMPLs
- Perform heirarchical resolution through "transparent" nodes
- Reparenting impls broke relative path traversal entirely. To impl something, it must already be in scope anyway.
- TODO: well-formedness checks?
2024-07-21 06:01:54 -05:00
6bf34fdff6 cl-typeck: Add path-resolution relative to an ID
Great for interactive debugging
2024-07-21 05:57:15 -05:00
9d7ab77999 cl-typeck: Add Handle type
- Holds a DefID and a reference to the Project
- Pretty-prints def signatures
- Use handles when printing types (WIP)

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

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

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

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

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

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

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

cl-ast:
- Add nodes for Structor and Fielder

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

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

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

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

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

cl-parser:
- Update to match grammar

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

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

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

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

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

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

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

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

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

cl-lexer:
- Reflect changes from cl-token

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

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

Temporary solution: move the interpreter into a separate crate

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

View File

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

7
.gitignore vendored
View File

@@ -1,3 +1,10 @@
# Visual Studio Code config
.vscode
# Rust
**/Cargo.lock
target
# Pest files generated by Grammatical
*.p*st

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
use super::DroplessArena;
extern crate std;
use core::alloc::Layout;
use std::{prelude::rust_2021::*, vec};
#[test]
fn alloc_raw() {
let arena = DroplessArena::new();
let bytes = arena.alloc_raw(Layout::for_value(&0u128));
let byte2 = arena.alloc_raw(Layout::for_value(&0u128));
assert_ne!(bytes, byte2);
}
#[test]
fn alloc() {
let arena = DroplessArena::new();
let mut allocations = vec![];
for i in 0..0x400 {
allocations.push(arena.alloc(i));
}
}
#[test]
fn alloc_strings() {
const KW: &[&str] = &["pub", "mut", "fn", "mod", "conlang", "sidon", "🦈"];
let arena = DroplessArena::new();
let mut allocations = vec![];
for _ in 0..100 {
for kw in KW {
allocations.push(arena.alloc_str(kw));
}
}
}
#[test]
#[should_panic]
fn alloc_zsts() {
struct Zst;
let arena = DroplessArena::new();
arena.alloc(Zst);
}

View File

@@ -0,0 +1,396 @@
//! Typed and dropless arena allocation, paraphrased from [the Rust Compiler's `rustc_arena`](https://github.com/rust-lang/rust/blob/master/compiler/rustc_arena/src/lib.rs). See [LICENSE][1].
//!
//! An Arena Allocator is a type of allocator which provides stable locations for allocations within
//! itself for the entire duration of its lifetime.
//!
//! [1]: https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT
#![feature(dropck_eyepatch, new_uninit, strict_provenance)]
#![no_std]
extern crate alloc;
pub(crate) mod constants {
//! Size constants for arena chunk growth
pub(crate) const MIN_CHUNK: usize = 4096;
pub(crate) const MAX_CHUNK: usize = 2 * 1024 * 1024;
}
mod chunk {
//! An [ArenaChunk] contains a block of raw memory for use in arena allocators.
use alloc::boxed::Box;
use core::{
mem::{self, MaybeUninit},
ptr::{self, NonNull},
};
pub struct ArenaChunk<T> {
pub(crate) mem: NonNull<[MaybeUninit<T>]>,
pub(crate) filled: usize,
}
impl<T: Sized> ArenaChunk<T> {
pub fn new(cap: usize) -> Self {
let slice = Box::new_uninit_slice(cap);
Self { mem: NonNull::from(Box::leak(slice)), filled: 0 }
}
/// Drops all elements inside self, and resets the filled count to 0
///
/// # Safety
///
/// The caller must ensure that `self.filled` elements of self are currently initialized
pub unsafe fn drop_elements(&mut self) {
if mem::needs_drop::<T>() {
// Safety: the caller has ensured that `filled` elements are initialized
unsafe {
let slice = self.mem.as_mut();
for t in slice[..self.filled].iter_mut() {
t.assume_init_drop();
}
}
self.filled = 0;
}
}
/// Gets a pointer to the start of the arena
pub fn start(&mut self) -> *mut T {
self.mem.as_ptr() as _
}
/// Gets a pointer to the end of the arena
pub fn end(&mut self) -> *mut T {
if mem::size_of::<T>() == 0 {
ptr::without_provenance_mut(usize::MAX) // pointers to ZSTs must be unique
} else {
unsafe { self.start().add(self.mem.len()) }
}
}
}
impl<T> Drop for ArenaChunk<T> {
fn drop(&mut self) {
let _ = unsafe { Box::from_raw(self.mem.as_ptr()) };
}
}
}
pub mod typed_arena {
//! A [TypedArena] can hold many instances of a single type, and will properly [Drop] them.
#![allow(clippy::mut_from_ref)]
use crate::{chunk::ArenaChunk, constants::*};
use alloc::vec::Vec;
use core::{
cell::{Cell, RefCell},
marker::PhantomData,
mem, ptr, slice,
};
/// A [TypedArena] can hold many instances of a single type, and will properly [Drop] them when
/// it falls out of scope.
pub struct TypedArena<'arena, T> {
_lives: PhantomData<&'arena T>,
_drops: PhantomData<T>,
chunks: RefCell<Vec<ArenaChunk<T>>>,
head: Cell<*mut T>,
tail: Cell<*mut T>,
}
impl<'arena, T> Default for TypedArena<'arena, T> {
fn default() -> Self {
Self::new()
}
}
impl<'arena, T> TypedArena<'arena, T> {
pub const fn new() -> Self {
Self {
_lives: PhantomData,
_drops: PhantomData,
chunks: RefCell::new(Vec::new()),
head: Cell::new(ptr::null_mut()),
tail: Cell::new(ptr::null_mut()),
}
}
pub fn alloc(&'arena self, value: T) -> &'arena mut T {
if self.head == self.tail {
self.grow(1);
}
let out = if mem::size_of::<T>() == 0 {
self.head
.set(ptr::without_provenance_mut(self.head.get().addr() + 1));
ptr::NonNull::<T>::dangling().as_ptr()
} else {
let out = self.head.get();
self.head.set(unsafe { out.add(1) });
out
};
unsafe {
ptr::write(out, value);
&mut *out
}
}
fn can_allocate(&self, len: usize) -> bool {
len <= unsafe { self.tail.get().offset_from(self.head.get()) as usize }
}
/// # Panics
/// Panics if size_of::<T> == 0 || len == 0
#[inline]
fn alloc_raw_slice(&self, len: usize) -> *mut T {
assert!(mem::size_of::<T>() != 0);
assert!(len != 0);
if !self.can_allocate(len) {
self.grow(len)
}
let out = self.head.get();
unsafe { self.head.set(out.add(len)) };
out
}
pub fn alloc_from_iter<I>(&'arena self, iter: I) -> &'arena mut [T]
where I: IntoIterator<Item = T> {
// Collect them all into a buffer so they're allocated contiguously
let mut buf = iter.into_iter().collect::<Vec<_>>();
if buf.is_empty() {
return &mut [];
}
let len = buf.len();
// If T is a ZST, calling alloc_raw_slice will panic
let slice = if mem::size_of::<T>() == 0 {
self.head
.set(ptr::without_provenance_mut(self.head.get().addr() + len));
ptr::NonNull::dangling().as_ptr()
} else {
self.alloc_raw_slice(len)
};
unsafe {
buf.as_ptr().copy_to_nonoverlapping(slice, len);
buf.set_len(0);
slice::from_raw_parts_mut(slice, len)
}
}
#[cold]
#[inline(never)]
fn grow(&self, len: usize) {
let size = mem::size_of::<T>().max(1);
let mut chunks = self.chunks.borrow_mut();
let capacity = if let Some(last) = chunks.last_mut() {
last.filled = self.get_filled_of_chunk(last);
last.mem.len().min(MAX_CHUNK / size) * 2
} else {
MIN_CHUNK / size
}
.max(len);
let mut chunk = ArenaChunk::<T>::new(capacity);
self.head.set(chunk.start());
self.tail.set(chunk.end());
chunks.push(chunk);
}
fn get_filled_of_chunk(&self, chunk: &mut ArenaChunk<T>) -> usize {
let Self { head: tail, .. } = self;
let head = chunk.start();
if mem::size_of::<T>() == 0 {
tail.get().addr() - head.addr()
} else {
unsafe { tail.get().offset_from(head) as usize }
}
}
}
unsafe impl<'arena, T: Send> Send for TypedArena<'arena, T> {}
unsafe impl<'arena, #[may_dangle] T> Drop for TypedArena<'arena, T> {
fn drop(&mut self) {
let mut chunks = self.chunks.borrow_mut();
if let Some(last) = chunks.last_mut() {
last.filled = self.get_filled_of_chunk(last);
self.tail.set(self.head.get());
}
for chunk in chunks.iter_mut() {
unsafe { chunk.drop_elements() }
}
}
}
#[cfg(test)]
mod tests;
}
pub mod dropless_arena {
//! A [DroplessArena] can hold *any* combination of types as long as they don't implement
//! [Drop].
use crate::{chunk::ArenaChunk, constants::*};
use alloc::vec::Vec;
use core::{
alloc::Layout,
cell::{Cell, RefCell},
marker::PhantomData,
mem, ptr, slice,
};
pub struct DroplessArena<'arena> {
_lives: PhantomData<&'arena u8>,
chunks: RefCell<Vec<ArenaChunk<u8>>>,
head: Cell<*mut u8>,
tail: Cell<*mut u8>,
}
impl Default for DroplessArena<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'arena> DroplessArena<'arena> {
pub const fn new() -> Self {
Self {
_lives: PhantomData,
chunks: RefCell::new(Vec::new()),
head: Cell::new(ptr::null_mut()),
tail: Cell::new(ptr::null_mut()),
}
}
/// Allocates a `T` in the [DroplessArena], and returns a mutable reference to it.
///
/// # Panics
/// - Panics if T implements [Drop]
/// - Panics if T is zero-sized
#[allow(clippy::mut_from_ref)]
pub fn alloc<T>(&'arena self, value: T) -> &'arena mut T {
assert!(!mem::needs_drop::<T>());
assert!(mem::size_of::<T>() != 0);
let out = self.alloc_raw(Layout::new::<T>()) as *mut T;
unsafe {
ptr::write(out, value);
&mut *out
}
}
/// Allocates a slice of `T`s`, copied from the given slice, returning a mutable reference
/// to it.
///
/// # Panics
/// - Panics if T implements [Drop]
/// - Panics if T is zero-sized
/// - Panics if the slice is empty
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice<T: Copy>(&'arena self, slice: &[T]) -> &'arena mut [T] {
assert!(!mem::needs_drop::<T>());
assert!(mem::size_of::<T>() != 0);
assert!(!slice.is_empty());
let mem = self.alloc_raw(Layout::for_value::<[T]>(slice)) as *mut T;
unsafe {
mem.copy_from_nonoverlapping(slice.as_ptr(), slice.len());
slice::from_raw_parts_mut(mem, slice.len())
}
}
/// Allocates a copy of the given [`&str`](str), returning a reference to the allocation.
///
/// # Panics
/// Panics if the string is empty.
pub fn alloc_str(&'arena self, string: &str) -> &'arena str {
let slice = self.alloc_slice(string.as_bytes());
// Safety: This is a clone of the input string, which was valid
unsafe { core::str::from_utf8_unchecked(slice) }
}
/// Allocates some [bytes](u8) based on the given [Layout].
///
/// # Panics
/// Panics if the provided [Layout] has size 0
pub fn alloc_raw(&'arena self, layout: Layout) -> *mut u8 {
/// Rounds the given size (or pointer value) *up* to the given alignment
fn align_up(size: usize, align: usize) -> usize {
(size + align - 1) & !(align - 1)
}
/// Rounds the given size (or pointer value) *down* to the given alignment
fn align_down(size: usize, align: usize) -> usize {
size & !(align - 1)
}
assert!(layout.size() != 0);
loop {
let Self { head, tail, .. } = self;
let start = head.get().addr();
let end = tail.get().addr();
let align = 8.max(layout.align());
let bytes = align_up(layout.size(), align);
if let Some(end) = end.checked_sub(bytes) {
let end = align_down(end, layout.align());
if start <= end {
tail.set(tail.get().with_addr(end));
return tail.get();
}
}
self.grow(layout.size());
}
}
/// Grows the allocator, doubling the chunk size until it reaches [MAX_CHUNK].
#[cold]
#[inline(never)]
fn grow(&self, len: usize) {
let mut chunks = self.chunks.borrow_mut();
let capacity = if let Some(last) = chunks.last_mut() {
last.mem.len().min(MAX_CHUNK / 2) * 2
} else {
MIN_CHUNK
}
.max(len);
let mut chunk = ArenaChunk::<u8>::new(capacity);
self.head.set(chunk.start());
self.tail.set(chunk.end());
chunks.push(chunk);
}
/// Checks whether the given slice is allocated in this arena
pub fn contains_slice<T>(&self, slice: &[T]) -> bool {
let ptr = slice.as_ptr().cast::<u8>().cast_mut();
for chunk in self.chunks.borrow_mut().iter_mut() {
if chunk.start() <= ptr && ptr <= chunk.end() {
return true;
}
}
false
}
}
unsafe impl<'arena> Send for DroplessArena<'arena> {}
#[cfg(test)]
mod tests;
}

View File

@@ -0,0 +1,61 @@
use super::TypedArena;
extern crate std;
use std::{prelude::rust_2021::*, print, vec};
#[test]
fn pushing_to_arena() {
let arena = TypedArena::new();
let foo = arena.alloc("foo");
let bar = arena.alloc("bar");
let baz = arena.alloc("baz");
assert_eq!("foo", *foo);
assert_eq!("bar", *bar);
assert_eq!("baz", *baz);
}
#[test]
fn pushing_vecs_to_arena() {
let arena = TypedArena::new();
let foo = arena.alloc(vec!["foo"]);
let bar = arena.alloc(vec!["bar"]);
let baz = arena.alloc(vec!["baz"]);
assert_eq!("foo", foo[0]);
assert_eq!("bar", bar[0]);
assert_eq!("baz", baz[0]);
}
#[test]
fn pushing_zsts() {
struct ZeroSized;
impl Drop for ZeroSized {
fn drop(&mut self) {
print!("")
}
}
let arena = TypedArena::new();
for _ in 0..0x100 {
arena.alloc(ZeroSized);
}
}
#[test]
fn pushing_nodrop_zsts() {
struct ZeroSized;
let arena = TypedArena::new();
for _ in 0..0x1000 {
arena.alloc(ZeroSized);
}
}
#[test]
fn resize() {
let arena = TypedArena::new();
for _ in 0..0x780 {
arena.alloc(0u128);
}
}

View File

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

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

@@ -0,0 +1,614 @@
//! # 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
//! - [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 [Literal]: 0x42, 1e123, 2.4, "Hello"
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Literal {
Bool(bool),
Char(char),
Int(u128),
String(String),
}
/// A list of [Item]s
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct File {
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 extents: 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: 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 [import](Use)
Use(Use),
}
/// An alias to another [Ty]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Alias {
pub to: 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>,
}
/// An ordered collection of [Items](Item)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Module {
pub name: Sym,
pub kind: ModuleKind,
}
/// The contents of a [Module], if they're in the same file
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ModuleKind {
Inline(File),
Outline,
}
/// Code, and the interface to that code
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Function {
pub name: Sym,
pub sign: TyFn,
pub bind: Vec<Param>,
pub body: Option<Block>,
}
/// A single parameter for a [Function]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Param {
pub mutability: Mutability,
pub name: Sym,
}
/// A user-defined product type
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Struct {
pub name: Sym,
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 kind: EnumKind,
}
/// An [Enum]'s [Variant]s, if it has a variant block
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum EnumKind {
/// Represents an enum with no variants
NoVariants,
Variants(Vec<Variant>),
}
/// A single [Enum] variant
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Variant {
pub name: Sym,
pub kind: VariantKind,
}
/// Whether the [Variant] has a C-like constant value, a tuple, or [StructMember]s
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum VariantKind {
Plain,
CLike(u128),
Tuple(Ty),
Struct(Vec<StructMember>),
}
/// Sub-[items](Item) (associated functions, etc.) for a [Ty]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Impl {
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 extents: Span,
pub kind: TyKind,
}
/// Information about a [Ty]pe expression
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TyKind {
Never,
Empty,
Path(Path),
Array(TyArray),
Slice(TySlice),
Tuple(TyTuple),
Ref(TyRef),
Fn(TyFn),
// TODO: slice, array types
}
/// An array of [`T`](Ty)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyArray {
pub ty: Box<TyKind>,
pub count: usize,
}
/// A [Ty]pe slice expression: `[T]`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TySlice {
pub ty: Box<TyKind>,
}
/// A tuple of [Ty]pes
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyTuple {
pub types: Vec<TyKind>,
}
/// 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: Path,
}
/// The args and return value for a function pointer [Ty]pe
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyFn {
pub args: Box<TyKind>,
pub rety: Option<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, Debug, PartialEq, Eq, Hash)]
pub enum PathPart {
SuperKw,
SelfKw,
SelfTy,
Ident(Sym),
}
/// An abstract statement, and associated metadata
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Stmt {
pub extents: 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,
Local(Let),
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,
}
/// A local variable declaration [Stmt]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Let {
pub mutable: Mutability,
pub name: Sym,
pub ty: Option<Box<Ty>>,
pub init: Option<Box<Expr>>,
}
/// An expression, the beating heart of the language
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Expr {
pub extents: 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,
/// 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 [Struct creation](Structor) expression: [Path] `{` ([Fielder] `,`)* [Fielder]? `}`
Structor(Structor),
/// 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),
/// A [Grouping](Group) expression `(` [`Expr`] `)`
Group(Group),
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
Tuple(Tuple),
/// A [Loop] expression: `loop` [`Block`]
Loop(Loop),
/// 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`] ([`ModifyKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Assign {
pub parts: Box<(ExprKind, ExprKind)>,
}
/// A [Modify]-assignment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Modify {
pub kind: ModifyKind,
pub parts: Box<(ExprKind, ExprKind)>,
}
#[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<(ExprKind, ExprKind)>,
}
/// 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<ExprKind>,
}
/// A [Unary] operator
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UnaryKind {
Deref,
Neg,
Not,
/// Unused
At,
/// Unused
Tilde,
}
/// A [Member] access expression: [`Expr`] [`MemberKind`]\*
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Member {
pub head: Box<ExprKind>,
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<ExprKind>,
pub indices: 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<ExprKind>,
pub repeat: Box<ExprKind>,
}
/// An address-of expression: `&` `mut`? [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AddrOf {
pub count: usize,
pub mutable: Mutability,
pub expr: Box<ExprKind>,
}
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Block {
pub stmts: Vec<Stmt>,
}
/// A [Grouping](Group) expression `(` [`Expr`] `)`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Group {
pub expr: Box<ExprKind>,
}
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Tuple {
pub exprs: Vec<Expr>,
}
/// A [Loop] expression: `loop` [`Block`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Loop {
pub body: Box<Expr>,
}
/// 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: Sym, // 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, 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>>,
}
/// A continue expression: `continue`
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Continue;

View File

@@ -3,52 +3,24 @@ use super::*;
mod display {
//! Implements [Display] for [AST](super::super) Types
use super::*;
pub use delimiters::*;
use std::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 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() {
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 {
write!(f, "{sep}")?;
f.write_str(sep)?;
}
item.fmt(f)?;
write!(f, "{item}")?;
}
Ok(())
}
@@ -62,6 +34,7 @@ mod display {
}
}
}
impl Display for Visibility {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -71,9 +44,20 @@ mod display {
}
}
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 File {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
separate(&self.items, "\n")(f)
separate(&self.items, "\n\n")(f)
}
}
@@ -84,22 +68,24 @@ mod display {
return Ok(());
}
"#".fmt(f)?;
delimit(separate(meta, ", "), INLINE_SQUARE)(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) => delimit(separate(args, ", "), INLINE_PARENS)(f),
MetaKind::Func(args) => separate(args, ", ")(f.delimit(INLINE_PARENS)),
}
}
}
@@ -109,7 +95,13 @@ mod display {
let Self { extents: _, attrs, vis, kind } = self;
attrs.fmt(f)?;
vis.fmt(f)?;
match kind {
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),
@@ -118,9 +110,11 @@ mod display {
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 Alias {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { to, from } = self;
@@ -130,40 +124,63 @@ mod display {
}
}
}
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)
write!(f.delimit(BRACES), "{items}")
}
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;
let Self { name, sign: sign @ TyFn { args, rety }, bind, body } = self;
let types = match **args {
TyKind::Tuple(TyTuple { ref types }) => types.as_slice(),
TyKind::Empty => Default::default(),
_ => {
write!(f, "Invalid function signature: {sign}")?;
Default::default()
}
};
debug_assert_eq!(bind.len(), types.len());
write!(f, "fn {name} ")?;
delimit(separate(args, ", "), INLINE_PARENS)(f)?;
{
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 Some(rety) = rety {
write!(f, " -> {rety}")?;
}
@@ -173,98 +190,162 @@ mod display {
}
}
}
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}")
let Self { mutability, name } = self;
write!(f, "{mutability}{name}")
}
}
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"), Delimiters { open: " {\n", ..BRACES })(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, 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 => todo!(),
EnumKind::Variants(v) => separate(v, ", ")(f),
EnumKind::NoVariants => ';'.fmt(f),
EnumKind::Variants(v) => separate(v, ",\n")(f.delimit(SPACED_BRACES)),
}
}
}
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) => n.fmt(f),
VariantKind::Tuple(v) => delimit(separate(v, ", "), INLINE_PARENS)(f),
VariantKind::Struct(v) => delimit(separate(v, ",\n"), BRACES)(f),
VariantKind::CLike(n) => write!(f, " = {n}"),
VariantKind::Tuple(v) => v.fmt(f),
VariantKind::Struct(v) => separate(v, ", ")(f.delimit(INLINE_BRACES)),
}
}
}
impl Display for Impl {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!("impl Display for Impl")
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { target, body } = self;
write!(f, "impl {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 {
match &self.kind {
self.kind.fmt(f)
}
}
impl Display for TyKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TyKind::Never => "!".fmt(f),
TyKind::Empty => "()".fmt(f),
TyKind::SelfTy => "Self".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::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 {
delimit(separate(&self.types, ", "), INLINE_PARENS)(f)
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: _, to } = self;
for _ in 0..self.count {
let &Self { count, mutable, ref to } = self;
for _ in 0..count {
f.write_char('&')?;
}
write!(f, "{to}")
write!(f, "{mutable}{to}")
}
}
impl Display for TyFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { args, rety } = self;
@@ -276,21 +357,54 @@ mod display {
}
}
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::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 { extents: _, kind, semi } = self;
match kind {
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::Local(v) => v.fmt(f),
StmtKind::Item(v) => v.fmt(f),
StmtKind::Expr(v) => v.fmt(f),
}?;
match semi {
}
}
}
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 Let {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { mutable, name, ty, init } = self;
@@ -307,22 +421,30 @@ mod display {
impl Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
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::Assign(v) => v.fmt(f),
ExprKind::Modify(v) => v.fmt(f),
ExprKind::Binary(v) => v.fmt(f),
ExprKind::Unary(v) => v.fmt(f),
ExprKind::Index(v) => v.fmt(f),
ExprKind::Call(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::Empty => "()".fmt(f),
ExprKind::Group(v) => v.fmt(f),
ExprKind::Tuple(v) => v.fmt(f),
ExprKind::Loop(v) => v.fmt(f),
ExprKind::While(v) => v.fmt(f),
ExprKind::If(v) => v.fmt(f),
ExprKind::For(v) => v.fmt(f),
@@ -332,40 +454,50 @@ mod display {
}
}
}
impl Display for Assign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head, op, tail } = self;
write!(f, "{head} {op} {tail}")
let Self { parts } = self;
write!(f, "{} = {}", parts.0, parts.1)
}
}
impl Display for AssignKind {
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 {
AssignKind::Plain => "=",
AssignKind::Mul => "*=",
AssignKind::Div => "/=",
AssignKind::Rem => "%=",
AssignKind::Add => "+=",
AssignKind::Sub => "-=",
AssignKind::And => "&=",
AssignKind::Or => "|=",
AssignKind::Xor => "^=",
AssignKind::Shl => "<<=",
AssignKind::Shr => ">>=",
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 { head, tail } = self;
write!(f, "{head}")?;
for (kind, expr) in tail {
write!(f, " {kind} {expr}")?;
let Self { kind, parts } = self;
let (head, tail) = parts.borrow();
match kind {
BinaryKind::Call => write!(f, "{head}{tail}"),
_ => write!(f, "{head} {kind} {tail}"),
}
Ok(())
}
}
impl Display for BinaryKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -390,20 +522,19 @@ mod display {
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 { ops: kinds, tail } = self;
for kind in kinds {
kind.fmt(f)?
}
tail.fmt(f)
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 {
@@ -416,88 +547,64 @@ mod display {
.fmt(f)
}
}
impl Display for Call {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { callee, args } = self;
callee.fmt(f)?;
for args in args {
args.fmt(f)?;
}
Ok(())
}
}
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 Member {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head: parent, tail: children } = self;
write!(f, "{parent}.")?;
separate(children, ".")(f)?;
Ok(())
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}")?;
for indices in indices {
indices.fmt(f)?;
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 Indices {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
delimit(separate(&self.exprs, ", "), INLINE_SQUARE)(f)
}
}
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)
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 { count, mutable, expr } = self;
@@ -507,34 +614,53 @@ mod display {
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)
separate(&self.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 {
separate(&self.exprs, ", ")(f.delimit(INLINE_PARENS))
}
}
impl Display for Loop {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { body } = self;
write!(f, "loop {body}")
}
}
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 {
@@ -543,6 +669,7 @@ mod display {
}
}
}
impl Display for Break {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "break")?;
@@ -552,6 +679,7 @@ mod display {
}
}
}
impl Display for Return {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "return")?;
@@ -561,105 +689,10 @@ mod display {
}
}
}
}
pub mod format {
//! Code formatting for the [AST](super::super)
use std::{
io::{Result, Write as IoWrite},
ops::{Deref, DerefMut},
};
/// Trait which adds a function to [Writers](IoWrite) to turn them into [Prettifier]
pub trait Pretty {
/// Indents code according to the number of matched curly braces
fn pretty(self) -> Prettifier<'static, Self>
where Self: IoWrite + Sized;
}
impl<W: IoWrite> Pretty for W {
fn pretty(self) -> Prettifier<'static, Self>
where Self: IoWrite + Sized {
Prettifier::new(self)
}
}
pub struct Prettifier<'i, T: IoWrite> {
level: isize,
indent: &'i str,
writer: T,
}
impl<'i, W: IoWrite> 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 }
}
pub fn indent<'scope>(&'scope mut self) -> Indent<'scope, 'i, W> {
Indent::new(self)
}
fn write_indentation(&mut self) -> Result<usize> {
let Self { level, indent, writer } = self;
let mut count = 0;
for _ in 0..*level {
count += writer.write(indent.as_bytes())?;
}
Ok(count)
}
}
impl<W: IoWrite> From<W> for Prettifier<'static, W> {
fn from(value: W) -> Self {
Self::new(value)
}
}
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.write_indentation()?;
}
}
}
Ok(size)
}
fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush()
}
}
pub struct Indent<'scope, 'i, T: IoWrite> {
formatter: &'scope mut Prettifier<'i, T>,
}
impl<'s, 'i, T: IoWrite> Indent<'s, 'i, T> {
pub fn new(formatter: &'s mut Prettifier<'i, T>) -> Self {
formatter.level += 1;
Self { formatter }
}
}
impl<'s, 'i, T: IoWrite> Deref for Indent<'s, 'i, T> {
type Target = Prettifier<'i, T>;
fn deref(&self) -> &Self::Target {
self.formatter
}
}
impl<'s, 'i, T: IoWrite> DerefMut for Indent<'s, 'i, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.formatter
}
}
impl<'s, 'i, T: IoWrite> Drop for Indent<'s, 'i, T> {
fn drop(&mut self) {
self.formatter.level -= 1;
impl Display for Continue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"continue".fmt(f)
}
}
}
@@ -668,9 +701,13 @@ mod convert {
//! Converts between major enums and enum variants
use super::*;
impl<T: AsRef<str>> From<T> for Identifier {
impl<T: AsRef<str>> From<T> for PathPart {
fn from(value: T) -> Self {
Identifier(value.as_ref().into())
match value.as_ref() {
"self" => PathPart::SelfKw,
"super" => PathPart::SuperKw,
ident => PathPart::Ident(ident.into()),
}
}
}
@@ -697,6 +734,7 @@ mod convert {
Struct => ItemKind::Struct,
Enum => ItemKind::Enum,
Impl => ItemKind::Impl,
Use => ItemKind::Use,
}
impl From for StructKind {
Vec<Ty> => StructKind::Tuple,
@@ -707,7 +745,7 @@ mod convert {
}
impl From for VariantKind {
u128 => VariantKind::CLike,
Vec<Ty> => VariantKind::Tuple,
Ty => VariantKind::Tuple,
// TODO: enum struct variants
}
impl From for TyKind {
@@ -723,9 +761,9 @@ mod convert {
}
impl From for ExprKind {
Assign => ExprKind::Assign,
Modify => ExprKind::Modify,
Binary => ExprKind::Binary,
Unary => ExprKind::Unary,
Call => ExprKind::Call,
Member => ExprKind::Member,
Index => ExprKind::Index,
Path => ExprKind::Path,
@@ -736,6 +774,7 @@ mod convert {
Block => ExprKind::Block,
Group => ExprKind::Group,
Tuple => ExprKind::Tuple,
Loop => ExprKind::Loop,
While => ExprKind::While,
If => ExprKind::If,
For => ExprKind::For,
@@ -747,18 +786,7 @@ mod convert {
bool => Literal::Bool,
char => Literal::Char,
u128 => Literal::Int,
&str => Literal::String,
}
}
impl From<Tuple> for Indices {
fn from(value: Tuple) -> Self {
Self { exprs: value.exprs }
}
}
impl From<Indices> for Tuple {
fn from(value: Indices) -> Self {
Self { exprs: value.exprs }
String => Literal::String,
}
}
@@ -773,3 +801,34 @@ mod convert {
}
}
}
mod path {
//! Utils for [Path]
use crate::{ast::Path, PathPart, Sym};
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
}
}
}
impl PathPart {
pub fn from_sym(ident: Sym) -> Self {
Self::Ident(ident)
}
}
}

View File

@@ -0,0 +1,8 @@
//! 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 use fold::Fold;
pub use visit::Visit;

View File

@@ -0,0 +1,566 @@
//! 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, extents: Span) -> Span {
extents
}
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_string(&mut self, s: String) -> String {
s
}
fn fold_file(&mut self, f: File) -> File {
let File { items } = f;
File { 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 { extents, attrs, vis, kind } = i;
Item {
extents: self.fold_span(extents),
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_alias(&mut self, a: Alias) -> Alias {
let Alias { to, from } = a;
Alias { to: self.fold_sym(to), 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, kind } = m;
Module { name: self.fold_sym(name), kind: self.fold_module_kind(kind) }
}
fn fold_module_kind(&mut self, m: ModuleKind) -> ModuleKind {
match m {
ModuleKind::Inline(f) => ModuleKind::Inline(self.fold_file(f)),
ModuleKind::Outline => ModuleKind::Outline,
}
}
fn fold_function(&mut self, f: Function) -> Function {
let Function { name, sign, bind, body } = f;
Function {
name: self.fold_sym(name),
sign: self.fold_ty_fn(sign),
bind: bind.into_iter().map(|p| self.fold_param(p)).collect(),
body: body.map(|b| self.fold_block(b)),
}
}
fn fold_param(&mut self, p: Param) -> Param {
let Param { mutability, name } = p;
Param { mutability: self.fold_mutability(mutability), name: self.fold_sym(name) }
}
fn fold_struct(&mut self, s: Struct) -> Struct {
let Struct { name, kind } = s;
Struct { name: self.fold_sym(name), 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, kind } = e;
Enum { name: self.fold_sym(name), kind: self.fold_enum_kind(kind) }
}
fn fold_enum_kind(&mut self, kind: EnumKind) -> EnumKind {
or_fold_enum_kind(self, kind)
}
fn fold_variant(&mut self, v: Variant) -> Variant {
let Variant { name, kind } = v;
Variant { name: self.fold_sym(name), kind: self.fold_variant_kind(kind) }
}
fn fold_variant_kind(&mut self, kind: VariantKind) -> VariantKind {
or_fold_variant_kind(self, kind)
}
fn fold_impl(&mut self, i: Impl) -> Impl {
let Impl { target, body } = i;
Impl { 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 { extents, kind } = t;
Ty { extents: self.fold_span(extents), kind: self.fold_ty_kind(kind) }
}
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_kind(*ty)), count }
}
fn fold_ty_slice(&mut self, s: TySlice) -> TySlice {
let TySlice { ty } = s;
TySlice { ty: Box::new(self.fold_ty_kind(*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(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: self.fold_path(to) }
}
fn fold_ty_fn(&mut self, t: TyFn) -> TyFn {
let TyFn { args, rety } = t;
TyFn {
args: Box::new(self.fold_ty_kind(*args)),
rety: rety.map(|t| Box::new(self.fold_ty(*t))),
}
}
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::SelfKw => PathPart::SelfKw,
PathPart::SelfTy => PathPart::SelfTy,
PathPart::Ident(i) => PathPart::Ident(self.fold_sym(i)),
}
}
fn fold_stmt(&mut self, s: Stmt) -> Stmt {
let Stmt { extents, kind, semi } = s;
Stmt {
extents: self.fold_span(extents),
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_let(&mut self, l: Let) -> Let {
let Let { mutable, name, ty, init } = l;
Let {
mutable: self.fold_mutability(mutable),
name: self.fold_sym(name),
ty: ty.map(|t| Box::new(self.fold_ty(*t))),
init: init.map(|e| Box::new(self.fold_expr(*e))),
}
}
fn fold_expr(&mut self, e: Expr) -> Expr {
let Expr { extents, kind } = e;
Expr { extents: self.fold_span(extents), kind: self.fold_expr_kind(kind) }
}
fn fold_expr_kind(&mut self, kind: ExprKind) -> ExprKind {
or_fold_expr_kind(self, kind)
}
fn fold_assign(&mut self, a: Assign) -> Assign {
let Assign { parts } = a;
let (head, tail) = *parts;
Assign { parts: Box::new((self.fold_expr_kind(head), self.fold_expr_kind(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_kind(head), self.fold_expr_kind(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_kind(head), self.fold_expr_kind(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_kind(*tail)) }
}
fn fold_unary_kind(&mut self, kind: UnaryKind) -> UnaryKind {
kind
}
fn fold_member(&mut self, m: Member) -> Member {
let Member { head, kind } = m;
Member { head: Box::new(self.fold_expr_kind(*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_kind(*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_kind(*value)),
repeat: Box::new(self.fold_expr_kind(*repeat)),
}
}
fn fold_addrof(&mut self, a: AddrOf) -> AddrOf {
let AddrOf { count, mutable, expr } = a;
AddrOf {
count,
mutable: self.fold_mutability(mutable),
expr: Box::new(self.fold_expr_kind(*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_kind(*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_loop(&mut self, l: Loop) -> Loop {
let Loop { body } = l;
Loop { body: Box::new(self.fold_expr(*body)) }
}
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_sym(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))) }
}
fn fold_continue(&mut self, c: Continue) -> Continue {
let Continue = c;
Continue
}
}
#[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::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 [ModuleKind] in the default way
pub fn or_fold_module_kind<F: Fold + ?Sized>(folder: &mut F, kind: ModuleKind) -> ModuleKind {
match kind {
ModuleKind::Inline(f) => ModuleKind::Inline(folder.fold_file(f)),
ModuleKind::Outline => ModuleKind::Outline,
}
}
#[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 [EnumKind] in the default way
pub fn or_fold_enum_kind<F: Fold + ?Sized>(folder: &mut F, kind: EnumKind) -> EnumKind {
match kind {
EnumKind::NoVariants => EnumKind::NoVariants,
EnumKind::Variants(v) => {
EnumKind::Variants(v.into_iter().map(|v| folder.fold_variant(v)).collect())
}
}
}
#[inline]
/// Folds a [VariantKind] in the default way
pub fn or_fold_variant_kind<F: Fold + ?Sized>(folder: &mut F, kind: VariantKind) -> VariantKind {
match kind {
VariantKind::Plain => VariantKind::Plain,
VariantKind::CLike(n) => VariantKind::CLike(n),
VariantKind::Tuple(t) => VariantKind::Tuple(folder.fold_ty(t)),
VariantKind::Struct(mem) => VariantKind::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::Empty => TyKind::Empty,
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::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::Local(l) => StmtKind::Local(folder.fold_let(l)),
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::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::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::Loop(l) => ExprKind::Loop(folder.fold_loop(l)),
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(c) => ExprKind::Continue(folder.fold_continue(c)),
}
}
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,488 @@
//! A [visitor](Visit) (implementer of the [Visit] trait) walks the immutable AST, mutating itself.
use crate::ast::*;
use cl_structures::span::Span;
/// Immutably walks the entire AST
///
/// Each method acts as a customization point.
///
/// There are a set of default implementations for enums
/// under the name [`or_visit_`*](or_visit_expr_kind),
/// provided for ease of use.
///
/// For all other nodes, traversal is *explicit*.
pub trait Visit<'a>: Sized {
fn visit_span(&mut self, _extents: &'a Span) {}
fn visit_mutability(&mut self, _mutable: &'a Mutability) {}
fn visit_visibility(&mut self, _vis: &'a Visibility) {}
fn visit_sym(&mut self, _name: &'a Sym) {}
fn visit_literal(&mut self, l: &'a Literal) {
or_visit_literal(self, l)
}
fn visit_bool(&mut self, _b: &'a bool) {}
fn visit_char(&mut self, _c: &'a char) {}
fn visit_int(&mut self, _i: &'a u128) {}
fn visit_string(&mut self, _s: &'a str) {}
fn visit_file(&mut self, f: &'a File) {
let File { items } = f;
items.iter().for_each(|i| self.visit_item(i));
}
fn visit_attrs(&mut self, a: &'a Attrs) {
let Attrs { meta } = a;
meta.iter().for_each(|m| self.visit_meta(m));
}
fn visit_meta(&mut self, m: &'a Meta) {
let Meta { name, kind } = m;
self.visit_sym(name);
self.visit_meta_kind(kind);
}
fn visit_meta_kind(&mut self, kind: &'a MetaKind) {
or_visit_meta_kind(self, kind)
}
fn visit_item(&mut self, i: &'a Item) {
let Item { extents, attrs, vis, kind } = i;
self.visit_span(extents);
self.visit_attrs(attrs);
self.visit_visibility(vis);
self.visit_item_kind(kind);
}
fn visit_item_kind(&mut self, kind: &'a ItemKind) {
or_visit_item_kind(self, kind)
}
fn visit_alias(&mut self, a: &'a Alias) {
let Alias { to, from } = a;
self.visit_sym(to);
if let Some(t) = from {
self.visit_ty(t)
}
}
fn visit_const(&mut self, c: &'a Const) {
let Const { name, ty, init } = c;
self.visit_sym(name);
self.visit_ty(ty);
self.visit_expr(init);
}
fn visit_static(&mut self, s: &'a Static) {
let Static { mutable, name, ty, init } = s;
self.visit_mutability(mutable);
self.visit_sym(name);
self.visit_ty(ty);
self.visit_expr(init);
}
fn visit_module(&mut self, m: &'a Module) {
let Module { name, kind } = m;
self.visit_sym(name);
self.visit_module_kind(kind);
}
fn visit_module_kind(&mut self, kind: &'a ModuleKind) {
or_visit_module_kind(self, kind)
}
fn visit_function(&mut self, f: &'a Function) {
let Function { name, sign, bind, body } = f;
self.visit_sym(name);
self.visit_ty_fn(sign);
bind.iter().for_each(|p| self.visit_param(p));
if let Some(b) = body {
self.visit_block(b)
}
}
fn visit_param(&mut self, p: &'a Param) {
let Param { mutability, name } = p;
self.visit_mutability(mutability);
self.visit_sym(name);
}
fn visit_struct(&mut self, s: &'a Struct) {
let Struct { name, kind } = s;
self.visit_sym(name);
self.visit_struct_kind(kind);
}
fn visit_struct_kind(&mut self, kind: &'a StructKind) {
or_visit_struct_kind(self, kind)
}
fn visit_struct_member(&mut self, m: &'a StructMember) {
let StructMember { vis, name, ty } = m;
self.visit_visibility(vis);
self.visit_sym(name);
self.visit_ty(ty);
}
fn visit_enum(&mut self, e: &'a Enum) {
let Enum { name, kind } = e;
self.visit_sym(name);
self.visit_enum_kind(kind);
}
fn visit_enum_kind(&mut self, kind: &'a EnumKind) {
or_visit_enum_kind(self, kind)
}
fn visit_variant(&mut self, v: &'a Variant) {
let Variant { name, kind } = v;
self.visit_sym(name);
self.visit_variant_kind(kind);
}
fn visit_variant_kind(&mut self, kind: &'a VariantKind) {
or_visit_variant_kind(self, kind)
}
fn visit_impl(&mut self, i: &'a Impl) {
let Impl { target, body } = i;
self.visit_impl_kind(target);
self.visit_file(body);
}
fn visit_impl_kind(&mut self, target: &'a ImplKind) {
or_visit_impl_kind(self, target)
}
fn visit_use(&mut self, u: &'a Use) {
let Use { absolute: _, tree } = u;
self.visit_use_tree(tree);
}
fn visit_use_tree(&mut self, tree: &'a UseTree) {
or_visit_use_tree(self, tree)
}
fn visit_ty(&mut self, t: &'a Ty) {
let Ty { extents, kind } = t;
self.visit_span(extents);
self.visit_ty_kind(kind);
}
fn visit_ty_kind(&mut self, kind: &'a TyKind) {
or_visit_ty_kind(self, kind)
}
fn visit_ty_array(&mut self, a: &'a TyArray) {
let TyArray { ty, count: _ } = a;
self.visit_ty_kind(ty);
}
fn visit_ty_slice(&mut self, s: &'a TySlice) {
let TySlice { ty } = s;
self.visit_ty_kind(ty)
}
fn visit_ty_tuple(&mut self, t: &'a TyTuple) {
let TyTuple { types } = t;
types.iter().for_each(|kind| self.visit_ty_kind(kind))
}
fn visit_ty_ref(&mut self, t: &'a TyRef) {
let TyRef { mutable, count: _, to } = t;
self.visit_mutability(mutable);
self.visit_path(to);
}
fn visit_ty_fn(&mut self, t: &'a TyFn) {
let TyFn { args, rety } = t;
self.visit_ty_kind(args);
if let Some(rety) = rety {
self.visit_ty(rety);
}
}
fn visit_path(&mut self, p: &'a Path) {
let Path { absolute: _, parts } = p;
parts.iter().for_each(|p| self.visit_path_part(p))
}
fn visit_path_part(&mut self, p: &'a PathPart) {
match p {
PathPart::SuperKw => {}
PathPart::SelfKw => {}
PathPart::SelfTy => {}
PathPart::Ident(i) => self.visit_sym(i),
}
}
fn visit_stmt(&mut self, s: &'a Stmt) {
let Stmt { extents, kind, semi } = s;
self.visit_span(extents);
self.visit_stmt_kind(kind);
self.visit_semi(semi);
}
fn visit_stmt_kind(&mut self, kind: &'a StmtKind) {
or_visit_stmt_kind(self, kind)
}
fn visit_semi(&mut self, _s: &'a Semi) {}
fn visit_let(&mut self, l: &'a Let) {
let Let { mutable, name, ty, init } = l;
self.visit_mutability(mutable);
self.visit_sym(name);
if let Some(ty) = ty {
self.visit_ty(ty);
}
if let Some(init) = init {
self.visit_expr(init)
}
}
fn visit_expr(&mut self, e: &'a Expr) {
let Expr { extents, kind } = e;
self.visit_span(extents);
self.visit_expr_kind(kind)
}
fn visit_expr_kind(&mut self, e: &'a ExprKind) {
or_visit_expr_kind(self, e)
}
fn visit_assign(&mut self, a: &'a Assign) {
let Assign { parts } = a;
let (head, tail) = parts.as_ref();
self.visit_expr_kind(head);
self.visit_expr_kind(tail);
}
fn visit_modify(&mut self, m: &'a Modify) {
let Modify { kind, parts } = m;
let (head, tail) = parts.as_ref();
self.visit_modify_kind(kind);
self.visit_expr_kind(head);
self.visit_expr_kind(tail);
}
fn visit_modify_kind(&mut self, _kind: &'a ModifyKind) {}
fn visit_binary(&mut self, b: &'a Binary) {
let Binary { kind, parts } = b;
let (head, tail) = parts.as_ref();
self.visit_binary_kind(kind);
self.visit_expr_kind(head);
self.visit_expr_kind(tail);
}
fn visit_binary_kind(&mut self, _kind: &'a BinaryKind) {}
fn visit_unary(&mut self, u: &'a Unary) {
let Unary { kind, tail } = u;
self.visit_unary_kind(kind);
self.visit_expr_kind(tail);
}
fn visit_unary_kind(&mut self, _kind: &'a UnaryKind) {}
fn visit_member(&mut self, m: &'a Member) {
let Member { head, kind } = m;
self.visit_expr_kind(head);
self.visit_member_kind(kind);
}
fn visit_member_kind(&mut self, kind: &'a MemberKind) {
or_visit_member_kind(self, kind)
}
fn visit_index(&mut self, i: &'a Index) {
let Index { head, indices } = i;
self.visit_expr_kind(head);
indices.iter().for_each(|e| self.visit_expr(e));
}
fn visit_structor(&mut self, s: &'a Structor) {
let Structor { to, init } = s;
self.visit_path(to);
init.iter().for_each(|e| self.visit_fielder(e))
}
fn visit_fielder(&mut self, f: &'a Fielder) {
let Fielder { name, init } = f;
self.visit_sym(name);
if let Some(init) = init {
self.visit_expr(init);
}
}
fn visit_array(&mut self, a: &'a Array) {
let Array { values } = a;
values.iter().for_each(|e| self.visit_expr(e))
}
fn visit_array_rep(&mut self, a: &'a ArrayRep) {
let ArrayRep { value, repeat } = a;
self.visit_expr_kind(value);
self.visit_expr_kind(repeat);
}
fn visit_addrof(&mut self, a: &'a AddrOf) {
let AddrOf { count: _, mutable, expr } = a;
self.visit_mutability(mutable);
self.visit_expr_kind(expr);
}
fn visit_block(&mut self, b: &'a Block) {
let Block { stmts } = b;
stmts.iter().for_each(|s| self.visit_stmt(s));
}
fn visit_group(&mut self, g: &'a Group) {
let Group { expr } = g;
self.visit_expr_kind(expr)
}
fn visit_tuple(&mut self, t: &'a Tuple) {
let Tuple { exprs } = t;
exprs.iter().for_each(|e| self.visit_expr(e))
}
fn visit_loop(&mut self, l: &'a Loop) {
let Loop { body } = l;
self.visit_expr(body)
}
fn visit_while(&mut self, w: &'a While) {
let While { cond, pass, fail } = w;
self.visit_expr(cond);
self.visit_block(pass);
self.visit_else(fail);
}
fn visit_if(&mut self, i: &'a If) {
let If { cond, pass, fail } = i;
self.visit_expr(cond);
self.visit_block(pass);
self.visit_else(fail);
}
fn visit_for(&mut self, f: &'a For) {
let For { bind, cond, pass, fail } = f;
self.visit_sym(bind);
self.visit_expr(cond);
self.visit_block(pass);
self.visit_else(fail);
}
fn visit_else(&mut self, e: &'a Else) {
let Else { body } = e;
if let Some(body) = body {
self.visit_expr(body)
}
}
fn visit_break(&mut self, b: &'a Break) {
let Break { body } = b;
if let Some(body) = body {
self.visit_expr(body)
}
}
fn visit_return(&mut self, r: &'a Return) {
let Return { body } = r;
if let Some(body) = body {
self.visit_expr(body)
}
}
fn visit_continue(&mut self, c: &'a Continue) {
let Continue = c;
}
}
pub fn or_visit_literal<'a, V: Visit<'a>>(visitor: &mut V, l: &'a Literal) {
match l {
Literal::Bool(b) => visitor.visit_bool(b),
Literal::Char(c) => visitor.visit_char(c),
Literal::Int(i) => visitor.visit_int(i),
Literal::String(s) => visitor.visit_string(s),
}
}
pub fn or_visit_meta_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a MetaKind) {
match kind {
MetaKind::Plain => {}
MetaKind::Equals(l) => visitor.visit_literal(l),
MetaKind::Func(lits) => lits.iter().for_each(|l| visitor.visit_literal(l)),
}
}
pub fn or_visit_item_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a ItemKind) {
match kind {
ItemKind::Module(m) => visitor.visit_module(m),
ItemKind::Alias(a) => visitor.visit_alias(a),
ItemKind::Enum(e) => visitor.visit_enum(e),
ItemKind::Struct(s) => visitor.visit_struct(s),
ItemKind::Const(c) => visitor.visit_const(c),
ItemKind::Static(s) => visitor.visit_static(s),
ItemKind::Function(f) => visitor.visit_function(f),
ItemKind::Impl(i) => visitor.visit_impl(i),
ItemKind::Use(u) => visitor.visit_use(u),
}
}
pub fn or_visit_module_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a ModuleKind) {
match kind {
ModuleKind::Inline(f) => visitor.visit_file(f),
ModuleKind::Outline => {}
}
}
pub fn or_visit_struct_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a StructKind) {
match kind {
StructKind::Empty => {}
StructKind::Tuple(ty) => ty.iter().for_each(|t| visitor.visit_ty(t)),
StructKind::Struct(m) => m.iter().for_each(|m| visitor.visit_struct_member(m)),
}
}
pub fn or_visit_enum_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a EnumKind) {
match kind {
EnumKind::NoVariants => {}
EnumKind::Variants(variants) => variants.iter().for_each(|v| visitor.visit_variant(v)),
}
}
pub fn or_visit_variant_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a VariantKind) {
match kind {
VariantKind::Plain => {}
VariantKind::CLike(_) => {}
VariantKind::Tuple(t) => visitor.visit_ty(t),
VariantKind::Struct(m) => m.iter().for_each(|m| visitor.visit_struct_member(m)),
}
}
pub fn or_visit_impl_kind<'a, V: Visit<'a>>(visitor: &mut V, target: &'a ImplKind) {
match target {
ImplKind::Type(t) => visitor.visit_ty(t),
ImplKind::Trait { impl_trait, for_type } => {
visitor.visit_path(impl_trait);
visitor.visit_ty(for_type)
}
}
}
pub fn or_visit_use_tree<'a, V: Visit<'a>>(visitor: &mut V, tree: &'a UseTree) {
match tree {
UseTree::Tree(tree) => {
tree.iter().for_each(|tree| visitor.visit_use_tree(tree));
}
UseTree::Path(path, rest) => {
visitor.visit_path_part(path);
visitor.visit_use_tree(rest)
}
UseTree::Alias(path, name) => {
visitor.visit_sym(path);
visitor.visit_sym(name);
}
UseTree::Name(name) => {
visitor.visit_sym(name);
}
UseTree::Glob => {}
}
}
pub fn or_visit_ty_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a TyKind) {
match kind {
TyKind::Never => {}
TyKind::Empty => {}
TyKind::Path(p) => visitor.visit_path(p),
TyKind::Array(t) => visitor.visit_ty_array(t),
TyKind::Slice(t) => visitor.visit_ty_slice(t),
TyKind::Tuple(t) => visitor.visit_ty_tuple(t),
TyKind::Ref(t) => visitor.visit_ty_ref(t),
TyKind::Fn(t) => visitor.visit_ty_fn(t),
}
}
pub fn or_visit_stmt_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a StmtKind) {
match kind {
StmtKind::Empty => {}
StmtKind::Local(l) => visitor.visit_let(l),
StmtKind::Item(i) => visitor.visit_item(i),
StmtKind::Expr(e) => visitor.visit_expr(e),
}
}
pub fn or_visit_expr_kind<'a, V: Visit<'a>>(visitor: &mut V, e: &'a ExprKind) {
match e {
ExprKind::Empty => {}
ExprKind::Assign(a) => visitor.visit_assign(a),
ExprKind::Modify(m) => visitor.visit_modify(m),
ExprKind::Binary(b) => visitor.visit_binary(b),
ExprKind::Unary(u) => visitor.visit_unary(u),
ExprKind::Member(m) => visitor.visit_member(m),
ExprKind::Index(i) => visitor.visit_index(i),
ExprKind::Structor(s) => visitor.visit_structor(s),
ExprKind::Path(p) => visitor.visit_path(p),
ExprKind::Literal(l) => visitor.visit_literal(l),
ExprKind::Array(a) => visitor.visit_array(a),
ExprKind::ArrayRep(a) => visitor.visit_array_rep(a),
ExprKind::AddrOf(a) => visitor.visit_addrof(a),
ExprKind::Block(b) => visitor.visit_block(b),
ExprKind::Group(g) => visitor.visit_group(g),
ExprKind::Tuple(t) => visitor.visit_tuple(t),
ExprKind::Loop(l) => visitor.visit_loop(l),
ExprKind::While(w) => visitor.visit_while(w),
ExprKind::If(i) => visitor.visit_if(i),
ExprKind::For(f) => visitor.visit_for(f),
ExprKind::Break(b) => visitor.visit_break(b),
ExprKind::Return(r) => visitor.visit_return(r),
ExprKind::Continue(c) => visitor.visit_continue(c),
}
}
pub fn or_visit_member_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a MemberKind) {
match kind {
MemberKind::Call(field, args) => {
visitor.visit_sym(field);
visitor.visit_tuple(args);
}
MemberKind::Struct(field) => visitor.visit_sym(field),
MemberKind::Tuple(field) => visitor.visit_literal(field),
}
}

View File

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

View File

@@ -0,0 +1,54 @@
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, kind } = m;
self.path.push(PathPart::Ident(name));
let (name, kind) = (self.fold_sym(name), self.fold_module_kind(kind));
self.path.pop();
Module { name, kind }
}
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.clone(), 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_kind(*expr),
_ => or_fold_expr_kind(self, kind),
}
}
}

View File

@@ -0,0 +1,34 @@
//! 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 { extents, kind } = e;
let kind = desugar_while(extents, kind);
Expr { extents: self.fold_span(extents), kind: self.fold_expr_kind(kind) }
}
}
/// Desugars while(-else) expressions into loop-if-else-break expressions
fn desugar_while(extents: 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 extents, if present, or use the parent's extents
let fail_span = body.as_ref().map(|body| body.extents).unwrap_or(extents);
let break_expr = Expr { extents: fail_span, kind: ExprKind::Break(Break { body }) };
let loop_body = If { cond, pass, fail: Else { body: Some(Box::new(break_expr)) } };
let loop_body = Expr { extents, kind: ExprKind::If(loop_body) };
ExprKind::Loop(Loop { body: Box::new(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, F: Write + ?Sized> Write for Indent<'f, 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, F: Write + ?Sized> Drop for Delimit<'f, F> {
fn drop(&mut self) {
let Self { f: Indent { f, .. }, delim } = self;
let _ = f.write_str(delim.close);
}
}
impl<'f, F: Write + ?Sized> Write for Delimit<'f, 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,21 @@
//! # 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
//! - [Path]: Path expressions
#![warn(clippy::all)]
#![feature(decl_macro)]
pub use ast::*;
pub mod ast;
pub mod ast_impl;
pub mod ast_visitor;
pub mod desugar;
pub mod format;

View File

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

View File

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

View File

@@ -0,0 +1,284 @@
//! Implementations of built-in functions
use super::{
convalue::ConValue,
env::Environment,
error::{Error, IResult},
BuiltIn, Callable,
};
use cl_ast::Sym;
use std::{
io::{stdout, Write},
rc::Rc,
};
builtins! {
const MISC;
/// Unstable variadic format function
pub fn format<_, args> () -> IResult<ConValue> {
use std::fmt::Write;
let mut out = String::new();
for arg in args {
write!(out, "{arg}").ok();
}
Ok(ConValue::String(out.into()))
}
/// Unstable variadic print function
pub fn print<_, args> () -> IResult<ConValue> {
let mut out = stdout().lock();
for arg in args {
write!(out, "{arg}").ok();
}
Ok(ConValue::Empty)
}
/// Unstable variadic println function
pub fn println<_, 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)) => (a.to_string() + &b.to_string()).into(),
_ => 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.wrapping_neg()),
_ => 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)?,
})
}
pub fn deref(tail) -> IResult<ConValue> {
Ok(match tail {
ConValue::Ref(v) => Rc::as_ref(v).clone(),
_ => tail.clone(),
})
}
}
/// 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) -> Sym { stringify!($name).into() }
}
)*
}
/// 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

@@ -0,0 +1,559 @@
//! 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, rc::Rc};
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),
ItemKind::Use(_) => todo!("namespaces and imports in the interpreter"),
}
}
}
impl Interpret for Alias {
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
println!("TODO: {self}");
Ok(ConValue::Empty)
}
}
impl Interpret for Const {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Const { name, ty: _, init } = self;
let init = init.as_ref().interpret(env)?;
env.insert(*name, Some(init));
Ok(ConValue::Empty)
}
}
impl Interpret for Static {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Static { mutable: _, name, ty: _, init } = self;
let init = init.as_ref().interpret(env)?;
env.insert(*name, Some(init));
Ok(ConValue::Empty)
}
}
impl Interpret for Module {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { name, kind } = self;
// TODO: Enter this module's namespace
match kind {
ModuleKind::Inline(file) => file.interpret(env),
ModuleKind::Outline => Err(Error::Outlined(*name)),
}
}
}
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> {
println!("TODO: {self}");
Ok(ConValue::Empty)
}
}
impl Interpret for Enum {
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
println!("TODO: {self}");
Ok(ConValue::Empty)
}
}
impl Interpret for Impl {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
println!("TODO: {self}");
let Self { target: _, body } = self;
body.interpret(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, 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::Empty => Ok(ConValue::Empty),
ExprKind::Assign(v) => v.interpret(env),
ExprKind::Modify(v) => v.interpret(env),
ExprKind::Binary(v) => v.interpret(env),
ExprKind::Unary(v) => v.interpret(env),
ExprKind::Member(v) => v.interpret(env),
ExprKind::Index(v) => v.interpret(env),
ExprKind::Structor(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::Group(v) => v.interpret(env),
ExprKind::Tuple(v) => v.interpret(env),
ExprKind::Loop(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),
}
}
}
fn evaluate_place_expr<'e>(
env: &'e mut Environment,
expr: &ExprKind,
) -> IResult<(&'e mut Option<ConValue>, Sym)> {
match expr {
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::SelfTy => todo!("What does it mean to assign to capital-S Self?"),
PathPart::Ident(s) => env.get_mut(*s).map(|v| (v, *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),
}
}
impl Interpret for Assign {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Assign { parts } = self;
let (head, tail) = parts.borrow();
let init = tail.interpret(env)?;
// Resolve the head pattern
let target = evaluate_place_expr(env, head)?;
use std::mem::discriminant as variant;
// runtime typecheck
match target.0 {
Some(value) if variant(value) == variant(&init) => {
*value = init;
}
value @ None => *value = Some(init),
_ => Err(Error::TypeError)?,
}
Ok(ConValue::Empty)
}
}
impl Interpret for Modify {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Modify { kind: op, parts } = self;
let (head, tail) = parts.borrow();
// Get the initializer and the tail
let init = tail.interpret(env)?;
// Resolve the head pattern
let target = evaluate_place_expr(env, head)?;
let (Some(target), _) = target else {
return Err(Error::NotInitialized(target.1));
};
match op {
ModifyKind::Add => target.add_assign(init),
ModifyKind::Sub => target.sub_assign(init),
ModifyKind::Mul => target.mul_assign(init),
ModifyKind::Div => target.div_assign(init),
ModifyKind::Rem => target.rem_assign(init),
ModifyKind::And => target.bitand_assign(init),
ModifyKind::Or => target.bitor_assign(init),
ModifyKind::Xor => target.bitxor_assign(init),
ModifyKind::Shl => target.shl_assign(init),
ModifyKind::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::Lt => head.lt(&tail),
BinaryKind::LtEq => head.lt_eq(&tail),
BinaryKind::Equal => head.eq(&tail),
BinaryKind::NotEq => head.neq(&tail),
BinaryKind::GtEq => head.gt_eq(&tail),
BinaryKind::Gt => head.gt(&tail),
BinaryKind::RangeExc => head.range_exc(tail),
BinaryKind::RangeInc => head.range_inc(tail),
BinaryKind::BitAnd => head & tail,
BinaryKind::BitOr => head | tail,
BinaryKind::BitXor => head ^ tail,
BinaryKind::Shl => head << tail,
BinaryKind::Shr => head >> tail,
BinaryKind::Add => head + tail,
BinaryKind::Sub => head - tail,
BinaryKind::Mul => head * tail,
BinaryKind::Div => head / tail,
BinaryKind::Rem => head % tail,
BinaryKind::Call => match tail {
ConValue::Empty => head.call(env, &[]),
ConValue::Tuple(args) => head.call(env, &args),
_ => Err(Error::TypeError),
},
_ => Ok(head),
}
// // Temporarily disabled, to avoid function dispatch overhead while I screw around
// // Not like it helped much in the first place!
// 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".into(), &[operand]),
UnaryKind::Neg => env.call("neg".into(), &[operand]),
UnaryKind::Not => env.call("not".into(), &[operand]),
UnaryKind::At => {
println!("{operand}");
Ok(operand)
}
UnaryKind::Tilde => unimplemented!("Tilde operator"),
}
}
}
impl Interpret for Member {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Member { head, kind } = self;
let head = head.interpret(env)?;
match (head, kind) {
(ConValue::Tuple(v), MemberKind::Tuple(Literal::Int(id))) => v
.get(*id as usize)
.cloned()
.ok_or(Error::OobIndex(*id as usize, v.len())),
(head, MemberKind::Call(name, args)) => {
let mut values = vec![head];
for arg in &args.exprs {
values.push(arg.interpret(env)?);
}
env.call(*name, &values)
}
_ => Err(Error::TypeError)?,
}
}
}
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 Structor {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("struct construction in {env}")
}
}
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::SelfTy => todo!("Path navigation to Self"),
PathPart::Ident(name) => env.get(*name),
}
} 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.into()))
}
}
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].into()))
}
}
impl Interpret for AddrOf {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { count: _, mutable: _, expr } = self;
match expr.as_ref() {
ExprKind::Index(_) => todo!("AddrOf array index"),
// ExprKind::Path(Path { absolute: false, parts }) => match parts.as_slice() {
// [PathPart::Ident(id)] => env.get_ref(id),
// _ => todo!("Path traversal in addrof"),
// },
ExprKind::Path(_) => todo!("Path traversal in addrof"),
_ => Ok(ConValue::Ref(Rc::new(expr.interpret(env)?))),
}
}
}
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)
})?
.into(),
))
}
}
impl Interpret for Loop {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { body } = self;
loop {
match body.interpret(env) {
Err(Error::Break(value)) => return Ok(value),
Err(Error::Continue) => continue,
e => e?,
};
}
}
}
impl Interpret for While {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { cond, pass, fail } = self;
loop {
if cond.interpret(env)?.truthy()? {
match pass.interpret(env) {
Err(Error::Break(value)) => return Ok(value),
Err(Error::Continue) => continue,
e => e?,
};
} else {
break 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: name, cond, pass, fail } = self;
// TODO: A better iterator model
let mut bounds = match cond.interpret(env)? {
ConValue::RangeExc(a, b) => a..=b,
ConValue::RangeInc(a, b) => a..=b,
_ => Err(Error::TypeError)?,
};
loop {
let mut env = env.frame("loop variable");
if let Some(loop_var) = bounds.next() {
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?,
};
} else {
break fail.interpret(&mut 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

@@ -0,0 +1,632 @@
//! 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, IResult};
use interpret::Interpret;
/// 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) -> Sym;
}
/// [BuiltIn]s are [Callable]s with bespoke definitions
pub trait BuiltIn: std::fmt::Debug + Callable {
fn description(&self) -> &str;
}
pub mod convalue {
//! Values in the dynamically typed AST interpreter.
//!
//! The most permanent fix is a temporary one.
use cl_ast::Sym;
use super::{
error::{Error, IResult},
function::Function,
BuiltIn, Callable, Environment,
};
use std::{ops::*, rc::Rc};
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 boolean
Bool(bool),
/// A unicode character
Char(char),
/// A string
String(Sym),
/// A reference
Ref(Rc<ConValue>),
/// An Array
Array(Rc<[ConValue]>),
/// A tuple
Tuple(Rc<[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) -> Sym {
match self {
ConValue::Function(func) => func.name(),
ConValue::BuiltIn(func) => func.name(),
_ => "".into(),
}
}
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()) }
})*
}
impl From<&Sym> for ConValue {
fn from(value: &Sym) -> Self {
ConValue::String(*value)
}
}
from! {
Integer => ConValue::Int,
bool => ConValue::Bool,
char => ConValue::Char,
Sym => ConValue::String,
&str => ConValue::String,
String => ConValue::String,
Rc<str> => 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.wrapping_add(b)),
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).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>().into())
}
_ => 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
})),
_ => Err(Error::TypeError)?
]
Mul: mul = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(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(|| {
eprintln!("Warning: Divide by zero in {a} % {b}"); a
})),
_ => 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)),
_ => 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::Ref(v) => write!(f, "&{v}"),
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) => {
write!(f, "{}", 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, Param, Sym};
use std::rc::Rc;
/// 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 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) -> Sym {
let FnDecl { name, .. } = *self.decl;
name
}
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
let FnDecl { name, bind, body, sign: _ } = &*self.decl;
// Check arg mapping
if args.len() != bind.len() {
return Err(Error::ArgNumber { want: bind.len(), got: args.len() });
}
let Some(body) = body else {
return Err(Error::NotDefined(*name));
};
// TODO: completely refactor data storage
let mut frame = env.frame("fn args");
for (Param { mutability: _, name }, value) in bind.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},
convalue::ConValue,
error::{Error, IResult},
function::Function,
BuiltIn, Callable, Interpret,
};
use cl_ast::{Function as FnDecl, Sym};
use std::{
collections::HashMap,
fmt::Display,
ops::{Deref, DerefMut},
};
type StackFrame = HashMap<Sym, Option<ConValue>>;
/// Implements a nested lexical scope
#[derive(Clone, Debug)]
pub struct Environment {
frames: Vec<(StackFrame, &'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<Sym, Option<ConValue>> {
from.iter().map(|&v| (v.name(), 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: Sym, 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: Sym) -> 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))
}
/// Resolves a variable immutably.
///
/// Returns a reference to the variable's contents, if it is defined and initialized.
pub fn get(&self, id: Sym) -> IResult<ConValue> {
for (frame, _) in self.frames.iter().rev() {
match frame.get(&id) {
Some(Some(var)) => return Ok(var.clone()),
Some(None) => return Err(Error::NotInitialized(id)),
_ => (),
}
}
Err(Error::NotDefined(id))
}
/// Inserts a new [ConValue] into this [Environment]
pub fn insert(&mut self, id: Sym, value: Option<ConValue>) {
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(id, value);
}
}
/// 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, 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 cl_ast::Sym;
use super::convalue::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(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,
},
Outlined(Sym),
}
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::Outlined(name) => {
write!(f, "Module {name} specified, but not imported.")
}
}
}
}
}
#[cfg(test)]
mod tests;

View File

@@ -1,10 +1,8 @@
#![allow(unused_imports)]
use crate::{
ast::*,
interpreter::{env::Environment, temp_type_impl::ConValue, Interpret},
lexer::Lexer,
parser::Parser,
};
use crate::{env::Environment, convalue::ConValue, Interpret};
use cl_ast::*;
use cl_lexer::Lexer;
use cl_parser::Parser;
pub use macros::*;
mod macros {
@@ -49,7 +47,7 @@ mod macros {
//! env_eq!(env.x, 10); // like assert_eq! for Environments
//! ```
#![allow(unused_macros)]
use crate::interpreter::IResult;
use crate::IResult;
use super::*;
@@ -129,7 +127,7 @@ mod macros {
}
pub macro env_ne($env:ident.$var:ident, $expr:expr) {{
let evaluated = $env.get(stringify!($var))
let evaluated = $env.get(stringify!($var).into())
.expect(stringify!($var should be defined and initialized));
if !conv_cmp!(neq, evaluated, $expr) {
panic!("assertion {} ({evaluated}) != {} failed.", stringify!($var), stringify!($expr))
@@ -137,7 +135,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))
@@ -189,10 +187,10 @@ mod fn_declarations {
assert_eval!(env, fn empty_fn() {});
// TODO: true equality for functions
assert_eq!(
"fn empty_fn",
"fn empty_fn () {\n \n}",
format!(
"{}",
env.get("empty_fn")
env.get("empty_fn".into())
.expect(stringify!(empty_fn should be defined and initialized))
)
)
@@ -212,7 +210,7 @@ mod fn_declarations {
}
mod operators {
use crate::ast::Tuple;
use cl_ast::Tuple;
use super::*;
#[test]

View File

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

View File

@@ -1,10 +1,16 @@
//! Converts a text file into tokens
use crate::token::preamble::*;
#![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_xid::UnicodeXID;
use unicode_ident::*;
#[cfg(test)]
mod tests;
pub mod lexer_iter {
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
@@ -45,7 +51,8 @@ pub mod lexer_iter {
///
/// # Examples
/// ```rust
/// # use conlang::lexer::Lexer;
/// # use cl_lexer::Lexer;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// // Read in your code from somewhere
/// let some_code = "
/// fn main () {
@@ -55,16 +62,17 @@ pub mod lexer_iter {
/// // Create a lexer over your code
/// let mut lexer = Lexer::new(some_code);
/// // Scan for a single token
/// let first_token = lexer.scan().unwrap();
/// 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.unwrap());
/// # let token: Result<_,()> = Ok(token?);
/// match token {
/// Ok(token) => println!("{token:?}"),
/// Err(e) => eprintln!("{e:?}"),
/// }
/// }
/// # Ok(()) }
/// ```
#[derive(Clone, Debug)]
pub struct Lexer<'t> {
@@ -89,40 +97,40 @@ impl<'t> Lexer<'t> {
/// Scans through the text, searching for the next [Token]
pub fn scan(&mut self) -> LResult<Token> {
match self.skip_whitespace().peek()? {
'{' => self.consume()?.produce(Type::LCurly, ()),
'}' => self.consume()?.produce(Type::RCurly, ()),
'[' => self.consume()?.produce(Type::LBrack, ()),
']' => self.consume()?.produce(Type::RBrack, ()),
'(' => self.consume()?.produce(Type::LParen, ()),
')' => self.consume()?.produce(Type::RParen, ()),
'{' => 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(Type::At, ()),
'\\' => self.consume()?.produce(Type::Backslash, ()),
'@' => self.consume()?.produce_op(Punct::At),
'\\' => self.consume()?.produce_op(Punct::Backslash),
'!' => self.consume()?.bang(),
'|' => self.consume()?.bar(),
':' => self.consume()?.colon(),
',' => self.consume()?.produce(Type::Comma, ()),
',' => self.consume()?.produce_op(Punct::Comma),
'.' => self.consume()?.dot(),
'=' => self.consume()?.equal(),
'`' => self.consume()?.produce(Type::Grave, ()),
'`' => self.consume()?.produce_op(Punct::Grave),
'>' => self.consume()?.greater(),
'#' => self.consume()?.hash(),
'<' => self.consume()?.less(),
'-' => self.consume()?.minus(),
'+' => self.consume()?.plus(),
'?' => self.consume()?.produce(Type::Question, ()),
'?' => self.consume()?.produce_op(Punct::Question),
'%' => self.consume()?.rem(),
';' => self.consume()?.produce(Type::Semi, ()),
';' => self.consume()?.produce_op(Punct::Semi),
'/' => self.consume()?.slash(),
'*' => self.consume()?.star(),
'~' => self.consume()?.produce(Type::Tilde, ()),
'~' => 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 i.is_xid_start() => 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();
@@ -149,11 +157,14 @@ impl<'t> Lexer<'t> {
.copied()
.ok_or(Error::end_of_file(self.line(), self.col()))
}
fn produce(&mut self, ty: Type, data: impl Into<Data>) -> LResult<Token> {
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(ty, data, loc.0, loc.1))
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() {
@@ -184,120 +195,120 @@ impl<'t> Lexer<'t> {
impl<'t> Lexer<'t> {
fn amp(&mut self) -> LResult<Token> {
match self.peek() {
Ok('&') => self.consume()?.produce(Type::AmpAmp, ()),
Ok('=') => self.consume()?.produce(Type::AmpEq, ()),
_ => self.produce(Type::Amp, ()),
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(Type::BangBang, ()),
Ok('=') => self.consume()?.produce(Type::BangEq, ()),
_ => self.produce(Type::Bang, ()),
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(Type::BarBar, ()),
Ok('=') => self.consume()?.produce(Type::BarEq, ()),
_ => self.produce(Type::Bar, ()),
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(Type::ColonColon, ()),
_ => self.produce(Type::Colon, ()),
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(Type::DotDotEq, ())
self.consume()?.produce_op(Punct::DotDotEq)
} else {
self.produce(Type::DotDot, ())
self.produce_op(Punct::DotDot)
}
}
_ => self.produce(Type::Dot, ()),
_ => self.produce_op(Punct::Dot),
}
}
fn equal(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce(Type::EqEq, ()),
Ok('>') => self.consume()?.produce(Type::FatArrow, ()),
_ => self.produce(Type::Eq, ()),
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(Type::GtEq, ()),
Ok('=') => self.consume()?.produce_op(Punct::GtEq),
Ok('>') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce(Type::GtGtEq, ())
self.consume()?.produce_op(Punct::GtGtEq)
} else {
self.produce(Type::GtGt, ())
self.produce_op(Punct::GtGt)
}
}
_ => self.produce(Type::Gt, ()),
_ => self.produce_op(Punct::Gt),
}
}
fn hash(&mut self) -> LResult<Token> {
match self.peek() {
Ok('!') => self.consume()?.produce(Type::HashBang, ()),
_ => self.produce(Type::Hash, ()),
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(Type::LtEq, ()),
Ok('=') => self.consume()?.produce_op(Punct::LtEq),
Ok('<') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce(Type::LtLtEq, ())
self.consume()?.produce_op(Punct::LtLtEq)
} else {
self.produce(Type::LtLt, ())
self.produce_op(Punct::LtLt)
}
}
_ => self.produce(Type::Lt, ()),
_ => self.produce_op(Punct::Lt),
}
}
fn minus(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce(Type::MinusEq, ()),
Ok('>') => self.consume()?.produce(Type::Arrow, ()),
_ => self.produce(Type::Minus, ()),
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(Type::PlusEq, ()),
_ => self.produce(Type::Plus, ()),
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(Type::RemEq, ()),
_ => self.produce(Type::Rem, ()),
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(Type::SlashEq, ()),
Ok('=') => self.consume()?.produce_op(Punct::SlashEq),
Ok('/') => self.consume()?.line_comment(),
Ok('*') => self.consume()?.block_comment(),
_ => self.produce(Type::Slash, ()),
_ => self.produce_op(Punct::Slash),
}
}
fn star(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce(Type::StarEq, ()),
_ => self.produce(Type::Star, ()),
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(Type::XorEq, ()),
Ok('^') => self.consume()?.produce(Type::XorXor, ()),
_ => self.produce(Type::Xor, ()),
Ok('=') => self.consume()?.produce_op(Punct::XorEq),
Ok('^') => self.consume()?.produce_op(Punct::XorXor),
_ => self.produce_op(Punct::Xor),
}
}
}
@@ -307,7 +318,7 @@ impl<'t> Lexer<'t> {
while Ok('\n') != self.peek() {
self.consume()?;
}
self.produce(Type::Comment, ())
self.produce(Kind::Comment, ())
}
fn block_comment(&mut self) -> LResult<Token> {
while let Ok(c) = self.next() {
@@ -315,7 +326,7 @@ impl<'t> Lexer<'t> {
break;
}
}
self.produce(Type::Comment, ())
self.produce(Kind::Comment, ())
}
}
/// Identifiers
@@ -325,15 +336,15 @@ impl<'t> Lexer<'t> {
while let Ok(c) = self.xid_continue() {
out.push(c)
}
if let Ok(keyword) = Keyword::from_str(&out) {
self.produce(Type::Keyword(keyword), ())
if let Ok(keyword) = Kind::from_str(&out) {
self.produce(keyword, ())
} else {
self.produce(Type::Identifier, Data::Identifier(out.into()))
self.produce(Kind::Identifier, TokenData::String(out))
}
}
fn xid_start(&mut self) -> LResult<char> {
match self.peek()? {
xid if xid == '_' || xid.is_xid_start() => {
xid if xid == '_' || is_xid_start(xid) => {
self.consume()?;
Ok(xid)
}
@@ -342,7 +353,7 @@ impl<'t> Lexer<'t> {
}
fn xid_continue(&mut self) -> LResult<char> {
match self.peek()? {
xid if xid.is_xid_continue() => {
xid if is_xid_continue(xid) => {
self.consume()?;
Ok(xid)
}
@@ -359,7 +370,7 @@ impl<'t> Lexer<'t> {
Ok('o') => self.consume()?.digits::<8>(),
Ok('b') => self.consume()?.digits::<2>(),
Ok('0'..='9') => self.digits::<10>(),
_ => self.produce(Type::Integer, 0),
_ => self.produce(Kind::Literal, 0),
}
}
fn digits<const B: u32>(&mut self) -> LResult<Token> {
@@ -367,7 +378,7 @@ impl<'t> Lexer<'t> {
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(Type::Integer, value)
self.produce(Kind::Literal, value)
}
fn digit<const B: u32>(&mut self) -> LResult<u32> {
let digit = self.peek()?;
@@ -388,12 +399,12 @@ impl<'t> Lexer<'t> {
{
value.push(self.unescape()?)
}
self.consume()?.produce(Type::String, value)
self.consume()?.produce(Kind::Literal, value)
}
fn character(&mut self) -> LResult<Token> {
let out = self.unescape()?;
match self.peek()? {
'\'' => self.consume()?.produce(Type::Character, out),
'\'' => self.consume()?.produce(Kind::Literal, out),
_ => Err(Error::unmatched_delimiters('\'', self.line(), self.col())),
}
}
@@ -445,6 +456,12 @@ impl<'t> Lexer<'t> {
}
}
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)
@@ -463,7 +480,7 @@ pub mod error {
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 [Type](crate::token::token_type::Type)
/// 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),

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,98 @@
//! 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) -> ModuleKind {
self.io_errs.push((self.path.clone(), error));
ModuleKind::Outline
}
/// Records a [parse error](crate::error::Error) for later
fn handle_parse_error(&mut self, error: crate::error::Error) -> ModuleKind {
self.parse_errs.push((self.path.clone(), error));
ModuleKind::Outline
}
}
impl Fold for ModuleInliner {
/// Traverses down the module tree, entering ever nested directories
fn fold_module(&mut self, m: Module) -> Module {
let Module { name, kind } = m;
self.path.push(&*name); // cd ./name
let kind = self.fold_module_kind(kind);
self.path.pop(); // cd ..
Module { name, kind }
}
/// Attempts to read and parse a file for every module in the tree
fn fold_module_kind(&mut self, m: ModuleKind) -> ModuleKind {
if let ModuleKind::Inline(f) = m {
return ModuleKind::Inline(self.fold_file(f));
}
// cd path/mod.cl
self.path.set_extension("cl");
let file = match std::fs::read_to_string(&self.path) {
Err(error) => return self.handle_io_error(error),
Ok(file) => file,
};
let kind = match Parser::new(Lexer::new(&file)).file() {
Err(e) => return self.handle_parse_error(e),
Ok(file) => ModuleKind::Inline(file),
};
// cd path/mod
self.path.set_extension("");
// The newly loaded module may need further inlining
self.fold_module_kind(kind)
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -10,5 +10,10 @@ publish.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
conlang = { path = "../libconlang" }
crossterm = "0.27.0"
cl-ast = { path = "../cl-ast" }
cl-lexer = { path = "../cl-lexer" }
cl-token = { path = "../cl-token" }
cl-parser = { path = "../cl-parser" }
cl-interpret = { path = "../cl-interpret" }
repline = { path = "../../repline" }
argwerk = "0.20.4"

View File

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

View File

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

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";

View File

@@ -0,0 +1,65 @@
//! 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);
}
}
/// 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 {
#[default]
Menu,
Lex,
Fmt,
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()?)
}

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

@@ -0,0 +1,100 @@
//! Implement's the command line interface
use crate::{
args::{Args, Mode},
ctx::Context,
menu,
tools::print_token,
};
use cl_interpret::{convalue::ConValue, env::Environment, interpret::Interpret};
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)?;
}
let mut ctx = Context::with_env(env);
match mode {
Mode::Menu => menu::main_menu(&mut ctx)?,
Mode::Lex => menu::lex(&mut ctx)?,
Mode::Fmt => menu::fmt(&mut ctx)?,
Mode::Run => menu::run(&mut ctx)?,
}
} else {
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(&code, file),
Mode::Fmt => fmt_code(&code),
Mode::Run | Mode::Menu => run_code(&code, &mut env),
}?;
}
Ok(())
}
fn load_file(env: &mut Environment, path: impl AsRef<Path>) -> Result<ConValue, Box<dyn Error>> {
let inliner =
cl_parser::inliner::ModuleInliner::new(path.as_ref().parent().unwrap_or(Path::new("")));
let file = std::fs::read_to_string(path)?;
let code = Parser::new(Lexer::new(&file)).file()?;
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
}
};
Ok(env.eval(&code)?)
}
fn lex_code(code: &str, path: Option<impl AsRef<Path>>) -> Result<(), Box<dyn Error>> {
for token in Lexer::new(code) {
if let Some(path) = &path {
print!("{}:", path.as_ref().display());
}
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
Ok(())
}
fn fmt_code(code: &str) -> Result<(), Box<dyn Error>> {
let code = Parser::new(Lexer::new(code)).file()?;
println!("{code}");
Ok(())
}
fn run_code(code: &str, env: &mut Environment) -> Result<(), Box<dyn Error>> {
let code = Parser::new(Lexer::new(code)).file()?;
match code.interpret(env)? {
ConValue::Empty => {}
ret => println!("{ret}"),
}
if env.get("main".into()).is_ok() {
match env.call("main".into(), &[])? {
ConValue::Empty => {}
ret => println!("{ret}"),
}
}
Ok(())
}

View File

@@ -0,0 +1,26 @@
use cl_interpret::{
env::Environment, error::IResult, interpret::Interpret, convalue::ConValue,
};
#[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,81 @@
use crate::{ansi, ctx};
use cl_lexer::Lexer;
use cl_parser::Parser;
use repline::{error::ReplResult, prebaked::*};
fn clear() {
println!("{}", ansi::CLEAR_ALL);
banner()
}
pub fn banner() {
println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION"))
}
/// Presents a selection interface to the user
pub fn main_menu(ctx: &mut ctx::Context) -> ReplResult<()> {
banner();
run(ctx)?;
read_and(ansi::GREEN, "mu>", " ?>", |line| {
match line.trim() {
"clear" => clear(),
"l" | "lex" => lex(ctx)?,
"f" | "fmt" => fmt(ctx)?,
"r" | "run" => run(ctx)?,
"q" | "quit" => return Ok(Response::Break),
"h" | "help" => println!(
"Valid commands
lex (l): Spin up a lexer, and lex some lines
fmt (f): Format the input
run (r): Enter the REPL, and evaluate some statements
help (h): Print this list
quit (q): Exit the program"
),
_ => Err("Unknown command. Type \"help\" for help")?,
}
Ok(Response::Accept)
})
}
pub fn run(ctx: &mut ctx::Context) -> ReplResult<()> {
use cl_ast::ast_visitor::Fold;
use cl_parser::inliner::ModuleInliner;
read_and(ansi::CYAN, "cl>", " ?>", |line| {
let code = Parser::new(Lexer::new(line)).stmt()?;
let code = ModuleInliner::new(".").fold_stmt(code);
print!("{}", ansi::OUTPUT);
match ctx.run(&code) {
Ok(v) => println!("{}{v}", ansi::RESET),
Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET),
}
Ok(Response::Accept)
})
}
pub fn lex(_ctx: &mut ctx::Context) -> ReplResult<()> {
read_and(ansi::BRIGHT_BLUE, "lx>", " ?>", |line| {
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 fmt(_ctx: &mut ctx::Context) -> ReplResult<()> {
read_and(ansi::BRIGHT_MAGENTA, "cl>", " ?>", |line| {
let mut p = Parser::new(Lexer::new(line));
match p.stmt() {
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
Err(e) => Err(e)?,
}
Ok(Response::Accept)
})
}

View File

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

View File

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

View File

@@ -0,0 +1,217 @@
//! 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 core::slice::GetManyMutError;
use std::ops::{Index, IndexMut};
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_many_mut<const N: usize>(
&mut self,
indices: [K; N],
) -> Result<[&mut V; N], GetManyMutError<N>> {
self.map.get_many_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,290 @@
//! 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
}
}
impl<'a, T: ?Sized + Debug> Debug for Interned<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Interned")
.field("value", &self.value)
.finish()
}
}
impl<'a, T: ?Sized> Interned<'a, T> {
pub(super) fn new(value: &'a T) -> Self {
Self { value }
}
}
impl<'a, T: ?Sized> Deref for Interned<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a, T: ?Sized> Copy for Interned<'a, T> {}
impl<'a, T: ?Sized> Clone for Interned<'a, 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<'a, T: ?Sized> Eq for Interned<'a, T> {}
impl<'a, T: ?Sized> PartialEq for Interned<'a, T> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.value, other.value)
}
}
impl<'a, T: ?Sized> Hash for Interned<'a, 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.
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()
}
}
// # 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<'a> Send for StringInterner<'a> {}
unsafe impl<'a> Sync for StringInterner<'a> {}
#[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> 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<'a, T: Eq + Hash + Send + Sync> Sync for TypedInterner<'a, 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, get_many_mut)]
#![deny(unsafe_op_in_unsafe_fn)]
pub mod intern;
pub mod span;
pub mod tree;
pub mod stack;
pub mod index_map;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -0,0 +1,266 @@
use cl_ast::{
ast_visitor::{Fold, Visit},
desugar::*,
};
use cl_lexer::Lexer;
use cl_parser::{inliner::ModuleInliner, Parser};
use cl_typeck::{
definition::Def,
handle::Handle,
name_collector::NameCollector,
node::{Node, NodeSource},
project::Project,
type_resolver::resolve,
};
use repline::{error::Error as RlError, prebaked::*};
use std::{error::Error, path};
// Path to display in standard library errors
const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
// Statically included standard library
const STDLIB: &str = include_str!("../../../stdlib/lib.cl");
// Colors
const C_MAIN: &str = "";
const C_RESV: &str = "\x1b[35m";
const C_CODE: &str = "\x1b[36m";
const C_BYID: &str = "\x1b[95m";
const C_LISTING: &str = "\x1b[38;5;117m";
/// A home for immutable intermediate ASTs
///
/// TODO: remove this.
static mut TREES: TreeManager = TreeManager::new();
fn main() -> Result<(), Box<dyn Error>> {
let mut prj = Project::default();
let mut parser = Parser::new(Lexer::new(STDLIB));
let code = match parser.file() {
Ok(code) => code,
Err(e) => {
eprintln!("{STDLIB_DISPLAY_PATH}:{e}");
Err(e)?
}
};
let code = inline_modules(code, concat!(env!("PWD"), "/stdlib"));
NameCollector::new(&mut prj).visit_file(unsafe { TREES.push(code) });
main_menu(&mut prj)?;
Ok(())
}
fn main_menu(prj: &mut Project) -> Result<(), RlError> {
banner();
read_and(C_MAIN, "mu>", "? >", |line| {
match line.trim() {
"c" | "code" => enter_code(prj)?,
"clear" => clear()?,
"e" | "exit" => return Ok(Response::Break),
"l" | "list" => list_types(prj),
"q" | "query" => query_type_expression(prj)?,
"i" | "id" => get_by_id(prj)?,
"r" | "resolve" => resolve_all(prj)?,
"d" | "desugar" => live_desugar()?,
"h" | "help" => {
println!(
"Valid commands are:
code (c): Enter code to type-check
list (l): List all known types
query (q): Query the type system
id (i): Get a type by its type ID
resolve (r): Perform type resolution
desugar (d): WIP: Test the experimental desugaring passes
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 Project) -> Result<(), RlError> {
read_and(C_CODE, "cl>", "? >", |line| {
if line.trim().is_empty() {
return Ok(Response::Break);
}
let code = Parser::new(Lexer::new(line)).file()?;
let code = inline_modules(code, "");
let code = WhileElseDesugar.fold_file(code);
// Safety: this is totally unsafe
NameCollector::new(prj).visit_file(unsafe { TREES.push(code) });
Ok(Response::Accept)
})
}
fn live_desugar() -> Result<(), RlError> {
read_and(C_RESV, "se>", "? >", |line| {
let code = Parser::new(Lexer::new(line)).stmt()?;
println!("Raw, as parsed:\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 query_type_expression(prj: &mut Project) -> Result<(), RlError> {
read_and(C_RESV, "ty>", "? >", |line| {
if line.trim().is_empty() {
return Ok(Response::Break);
}
// parse it as a path, and convert the path into a borrowed path
let ty = Parser::new(Lexer::new(line)).ty()?.kind;
let id = prj.evaluate(&ty, prj.root)?.handle_unchecked(prj);
pretty_handle(id)?;
Ok(Response::Accept)
})
}
fn get_by_id(prj: &mut Project) -> Result<(), RlError> {
use cl_structures::index_map::MapIndex;
use cl_typeck::handle::DefID;
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 parser.literal()? {
cl_ast::Literal::Int(int) => int as _,
other => Err(format!("Expected integer, got {other}"))?,
};
let mut path = parser.path().unwrap_or_default();
path.absolute = false;
let Some(handle) = DefID::from_usize(def_id).handle(prj) else {
return Ok(Response::Deny);
};
print!(" > {{{C_LISTING}{handle}\x1b[0m}}");
if !path.parts.is_empty() {
print!("::{path}")
}
println!();
let (ty, value) = handle.navigate((&path).into());
if let (None, None) = (ty, value) {
Err("No results.")?
}
if let Some(t) = ty {
println!("Result in type namespace: {}", t.id());
pretty_handle(t)?;
}
if let Some(v) = value {
println!("Result in value namespace: {}", v.id());
pretty_handle(v)?;
}
Ok(Response::Accept)
})
}
fn resolve_all(prj: &mut Project) -> Result<(), Box<dyn Error>> {
prj.resolve_imports()?;
for id in prj.pool.keys() {
resolve(prj, id)?;
}
println!("Types resolved successfully!");
Ok(())
}
fn list_types(prj: &mut Project) {
println!(" name\x1b[30G type");
for (idx, key) in prj.pool.keys().enumerate() {
let Def { node: Node { vis, kind: source, .. }, .. } = &prj[key];
let name = match source.as_ref().map(NodeSource::name) {
Some(Some(name)) => name,
_ => "".into(),
};
print!(
"{idx:3}: {vis}{name}\x1b[30G = {}",
key.handle_unchecked(prj)
);
println!();
}
}
fn pretty_handle(handle: Handle) -> Result<(), std::io::Error> {
use std::io::Write;
let mut stdout = std::io::stdout().lock();
let Some(Def { module, node: Node { vis, .. }, .. }) = handle.get() else {
return writeln!(stdout, "Invalid handle: {handle}");
};
writeln!(stdout, "{C_LISTING}{vis}{handle}\x1b[0m: {}", handle.id())?;
if let Some(parent) = module.parent {
writeln!(stdout, "{C_LISTING}Parent\x1b[0m: {}", handle.with(parent))?;
}
if !module.types.is_empty() {
writeln!(stdout, "{C_LISTING}Types:\x1b[0m")?;
for (name, def) in &module.types {
writeln!(stdout, "- {C_LISTING}{name}\x1b[0m: {}", handle.with(*def))?
}
}
if !module.values.is_empty() {
writeln!(stdout, "{C_LISTING}Values:\x1b[0m")?;
for (name, def) in &module.values {
writeln!(stdout, "- {C_LISTING}{name}\x1b[0m: {}", handle.with(*def))?
}
}
write!(stdout, "\x1b[0m")
}
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 clear() -> Result<(), Box<dyn Error>> {
println!("\x1b[H\x1b[2J");
banner();
Ok(())
}
fn banner() {
println!(
"--- {} v{} 💪🦈 ---",
env!("CARGO_BIN_NAME"),
env!("CARGO_PKG_VERSION"),
);
}
/// Keeps leaked references to past ASTs, for posterity:tm:
struct TreeManager {
trees: Vec<&'static cl_ast::File>,
}
impl TreeManager {
const fn new() -> Self {
Self { trees: vec![] }
}
fn push(&mut self, tree: cl_ast::File) -> &'static cl_ast::File {
let ptr = Box::leak(Box::new(tree));
self.trees.push(ptr);
ptr
}
}

View File

@@ -0,0 +1,193 @@
use crate::{
handle::DefID,
module::Module,
node::{Node, NodeSource},
};
use cl_ast::{Meta, Sym, Visibility};
use std::{fmt::Debug, str::FromStr};
mod display;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Def<'a> {
pub node: Node<'a>,
pub kind: DefKind,
pub module: Module,
}
impl<'a> Def<'a> {
pub fn with_node(node: Node<'a>) -> Self {
Self { node, kind: DefKind::Undecided, module: Default::default() }
}
}
impl Def<'_> {
pub fn name(&self) -> Option<Sym> {
match self.node.kind {
Some(source) => source.name(),
None => None,
}
}
pub fn is_transparent(&self) -> bool {
!matches!(self.kind, DefKind::Type(_))
}
}
mod builder_functions {
use super::*;
impl<'a> Def<'a> {
pub fn set_vis(&mut self, vis: Visibility) -> &mut Self {
self.node.vis = vis;
self
}
pub fn set_meta(&mut self, meta: &'a [Meta]) -> &mut Self {
self.node.meta = meta;
self
}
pub fn set_kind(&mut self, kind: DefKind) -> &mut Self {
self.kind = kind;
self
}
pub fn set_source(&mut self, source: NodeSource<'a>) -> &mut Self {
self.node.kind = Some(source);
self
}
pub fn set_module(&mut self, module: Module) -> &mut Self {
self.module = module;
self
}
}
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub enum DefKind {
/// An unevaluated definition
#[default]
Undecided,
/// An impl block
Impl(DefID),
/// A use tree, and its parent
Use(DefID),
/// A type, such as a `type`, `struct`, or `enum`
Type(TypeKind),
/// A value, such as a `const`, `static`, or `fn`
Value(ValueKind),
}
/// A [ValueKind] represents an item in the Value Namespace
/// (a component of a [Project](crate::project::Project)).
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ValueKind {
Const(DefID),
Static(DefID),
Local(DefID),
Fn(DefID),
}
/// A [TypeKind] represents an item in the Type Namespace
/// (a component of a [Project](crate::project::Project)).
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TypeKind {
/// An alias for an already-defined type
Alias(Option<DefID>),
/// A primitive type, built-in to the compiler
Intrinsic(Intrinsic),
/// A user-defined aromatic data type
Adt(Adt),
/// A reference to an already-defined type: &T
Ref(u16, DefID),
/// A contiguous view of dynamically sized memory
Slice(DefID),
/// A contiguous view of statically sized memory
Array(DefID, usize),
/// A tuple of existing types
Tuple(Vec<DefID>),
/// A function which accepts multiple inputs and produces an output
FnSig { args: DefID, rety: DefID },
/// The unit type
Empty,
/// The never type
Never,
/// An untyped module
Module,
}
/// A user-defined Aromatic Data Type
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Adt {
/// A union-like enum type
Enum(Vec<(Sym, Option<DefID>)>),
/// A C-like enum
CLikeEnum(Vec<(Sym, u128)>),
/// An enum with no fields, which can never be constructed
FieldlessEnum,
/// A structural product type with named members
Struct(Vec<(Sym, 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<(Sym, 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, Hash)]
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,
/// A ptr-len signed integer: `#[intrinsic = "isize"]`
Isize,
/// 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 ptr-len unsigned integer: `#[intrinsic = "isize"]`
Usize,
/// A boolean (`true` or `false`): `#[intrinsic = "bool"]`
Bool,
/// The unicode codepoint type: #[intrinsic = "char"]
Char,
}
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,
"isize" => Intrinsic::Isize,
"u8" => Intrinsic::U8,
"u16" => Intrinsic::U16,
"u32" => Intrinsic::U32,
"u64" => Intrinsic::U64,
"usize" => Intrinsic::Usize,
"bool" => Intrinsic::Bool,
"char" => Intrinsic::Char,
_ => Err(())?,
})
}
}

View File

@@ -0,0 +1,147 @@
//! [Display] implementations for [TypeKind], [Adt], and [Intrinsic]
use super::{Adt, Def, DefKind, Intrinsic, TypeKind, ValueKind};
use crate::{format_utils::*, node::Node};
use cl_ast::format::FmtAdapter;
use std::fmt::{self, Display, Write};
impl Display for Def<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { module, node: Node { in_path: _, span: _, meta, vis, kind: source }, kind } =
self;
if !meta.is_empty() {
writeln!(f, "#{meta:?}")?;
}
if let Some(source) = source {
if let Some(name) = source.name() {
writeln!(f, "{vis}{name}:")?;
}
writeln!(f.indent(), "source:\n{source}")?;
} else {
writeln!(f, "{vis}: ")?;
}
writeln!(f, "kind: {kind}")?;
write!(f, "module: {module}")
}
}
impl Display for DefKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DefKind::Undecided => write!(f, "undecided"),
DefKind::Impl(id) => write!(f, "impl {id}"),
DefKind::Use(id) => write!(f, "use (inside {id})"),
DefKind::Type(kind) => write!(f, "{kind}"),
DefKind::Value(kind) => write!(f, "{kind}"),
}
}
}
impl std::fmt::Display for ValueKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValueKind::Const(id) => write!(f, "const ({id})"),
ValueKind::Static(id) => write!(f, "static ({id})"),
ValueKind::Local(id) => write!(f, "let ({id})"),
ValueKind::Fn(id) => write!(f, "fn def ({id})"),
}
}
}
impl Display for TypeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeKind::Alias(def) => match def {
Some(def) => write!(f, "alias to #{def}"),
None => f.write_str("type"),
},
TypeKind::Intrinsic(i) => i.fmt(f),
TypeKind::Adt(a) => a.fmt(f),
TypeKind::Ref(cnt, def) => {
for _ in 0..*cnt {
f.write_str("&")?;
}
def.fmt(f)
}
TypeKind::Slice(def) => write!(f, "slice [#{def}]"),
TypeKind::Array(def, size) => write!(f, "array [#{def}; {size}]"),
TypeKind::Tuple(defs) => {
let mut defs = defs.iter();
separate(", ", || {
let def = defs.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "#{def}"))
})(f.delimit_with("tuple (", ")"))
}
TypeKind::FnSig { args, rety } => write!(f, "fn (#{args}) -> #{rety}"),
TypeKind::Empty => f.write_str("()"),
TypeKind::Never => f.write_str("!"),
TypeKind::Module => f.write_str("mod"),
}
}
}
impl Display for Adt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Adt::Enum(variants) => {
let mut variants = variants.iter();
separate(", ", || {
let (name, def) = variants.next()?;
Some(move |f: &mut Delimit<_>| match def {
Some(def) => write!(f, "{name}: #{def}"),
None => write!(f, "{name}"),
})
})(f.delimit_with("enum {", "}"))
}
Adt::CLikeEnum(variants) => {
let mut variants = variants.iter();
separate(", ", || {
let (name, descrim) = variants.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{name} = {descrim}"))
})(f.delimit_with("enum {", "}"))
}
Adt::FieldlessEnum => write!(f, "enum"),
Adt::Struct(members) => {
let mut members = members.iter();
separate(", ", || {
let (name, vis, def) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{vis}{name}: #{def}"))
})(f.delimit_with("struct {", "}"))
}
Adt::TupleStruct(members) => {
let mut members = members.iter();
separate(", ", || {
let (vis, def) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{vis}#{def}"))
})(f.delimit_with("struct (", ")"))
}
Adt::UnitStruct => write!(f, "struct"),
Adt::Union(variants) => {
let mut variants = variants.iter();
separate(", ", || {
let (name, def) = variants.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{name}: #{def}"))
})(f.delimit_with("union {", "}"))
}
}
}
}
impl Display for Intrinsic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Intrinsic::I8 => f.write_str("i8"),
Intrinsic::I16 => f.write_str("i16"),
Intrinsic::I32 => f.write_str("i32"),
Intrinsic::I64 => f.write_str("i64"),
Intrinsic::Isize => f.write_str("isize"),
Intrinsic::U8 => f.write_str("u8"),
Intrinsic::U16 => f.write_str("u16"),
Intrinsic::U32 => f.write_str("u32"),
Intrinsic::U64 => f.write_str("u64"),
Intrinsic::Usize => f.write_str("usize"),
Intrinsic::Bool => f.write_str("bool"),
Intrinsic::Char => f.write_str("char"),
}
}
}

View File

@@ -0,0 +1,23 @@
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,187 @@
use crate::{definition::Def, path::Path, project::Project};
use cl_structures::index_map::*;
// define the index types
make_index! {
/// Uniquely represents a [Def][1] in the [Def][1] [Pool]
///
/// [1]: crate::definition::Def
DefID,
}
/// A handle to a certain [Def] within a [Project]
#[derive(Clone, Copy, Debug)]
pub struct Handle<'prj, 'code> {
id: DefID,
prj: &'prj Project<'code>,
}
impl DefID {
/// Constructs a new [Handle] from this DefID and the provided [Project].
pub fn handle<'p, 'c>(self, prj: &'p Project<'c>) -> Option<Handle<'p, 'c>> {
Handle::new(self, prj)
}
/// Constructs a new [Handle] from this DefID and the provided [Project]
pub fn handle_unchecked<'p, 'c>(self, prj: &'p Project<'c>) -> Handle<'p, 'c> {
Handle::new_unchecked(self, prj)
}
}
impl<'p, 'c> Handle<'p, 'c> {
/// Constructs a new Handle from the provided [DefID] and [Project].
/// Returns [Some]\(Handle) if the [DefID] exists in the [Project].
pub fn new(id: DefID, prj: &'p Project<'c>) -> Option<Self> {
prj.pool.get(id).is_some().then_some(Self { id, prj })
}
/// Constructs a new Handle from the provided [DefID] and [Project] without checking membership.
/// Using the handle may cause panics or other unwanted (but defined) behavior.
pub fn new_unchecked(id: DefID, prj: &'p Project<'c>) -> Self {
Self { id, prj }
}
/// Gets the [Def] this handle points to.
pub fn get(self) -> Option<&'p Def<'c>> {
self.prj.pool.get(self.id)
}
pub fn navigate(self, path: Path) -> (Option<Self>, Option<Self>) {
match self.prj.get(path, self.id) {
Some((Some(ty), Some(vl), _)) => (Some(self.with(ty)), Some(self.with(vl))),
Some((_, Some(vl), _)) => (None, Some(self.with(vl))),
Some((Some(ty), _, _)) => (Some(self.with(ty)), None),
_ => (None, None),
}
}
/// Gets the [Project] this handle points to.
pub fn project(self) -> &'p Project<'c> {
self.prj
}
pub fn id(self) -> DefID {
self.id
}
// TODO: get parent, children, etc.
/// Gets a handle to the other ID within the same project
pub fn with(self, id: DefID) -> Self {
Self { id, ..self }
}
}
mod display {
use super::*;
use crate::{definition::*, format_utils::*};
use std::fmt::{self, Display, Write};
impl Display for DefID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Display for Handle<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { id, prj } = *self;
let Some(def) = prj.pool.get(id) else {
return write!(f, "<invalid type: {id}>");
};
// Match the type
match &def.kind {
DefKind::Undecided => write!(f, "<undecided>"),
DefKind::Impl(id) => write!(f, "impl {}", self.with(*id)),
DefKind::Use(id) => write!(f, "use inside {}", self.with(*id)),
DefKind::Type(kind) => match kind {
TypeKind::Alias(None) => write!(f, "type"),
TypeKind::Alias(Some(id)) => write!(f, "{}", self.with(*id)),
TypeKind::Intrinsic(intrinsic) => write!(f, "{intrinsic}"),
TypeKind::Adt(adt) => display_adt(self, adt, f),
TypeKind::Ref(count, id) => {
for _ in 0..*count {
f.write_char('&')?;
}
self.with(*id).fmt(f)
}
TypeKind::Array(id, size) => {
write!(f.delimit_with("[", "]"), "{}; {size}", self.with(*id))
}
TypeKind::Slice(id) => write!(f.delimit_with("[", "]"), "{}", self.with(*id)),
TypeKind::Tuple(ids) => {
let mut ids = ids.iter();
separate(", ", || {
let id = ids.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{}", self.with(*id)))
})(f.delimit_with("(", ")"))
}
TypeKind::FnSig { args, rety } => {
write!(f, "fn {} -> {}", self.with(*args), self.with(*rety))
}
TypeKind::Empty => write!(f, "()"),
TypeKind::Never => write!(f, "!"),
TypeKind::Module => match def.name() {
Some(name) => write!(f, "mod {name}"),
None => write!(f, "mod"),
},
},
DefKind::Value(kind) => match kind {
ValueKind::Const(id) => write!(f, "const {}", self.with(*id)),
ValueKind::Static(id) => write!(f, "static {}", self.with(*id)),
ValueKind::Local(id) => write!(f, "local {}", self.with(*id)),
ValueKind::Fn(id) => write!(f, "{}", self.with(*id)),
},
}
}
}
/// Formats an ADT: a continuation of [Handle::fmt]
fn display_adt(handle: &Handle, adt: &Adt, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match adt {
Adt::Enum(variants) => {
let mut variants = variants.iter();
separate(",", || {
variants.next().map(|(name, def)| {
move |f: &mut Delimit<_>| match def {
Some(def) => write!(f, "\n{name}: {}", handle.with(*def)),
None => write!(f, "\n{name}"),
}
})
})(f.delimit_with("enum {", "\n}"))
}
Adt::CLikeEnum(variants) => {
let mut variants = variants.iter();
separate(",", || {
let (name, descrim) = variants.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "\n{name} = {descrim}"))
})(f.delimit_with("enum {", "\n}"))
}
Adt::FieldlessEnum => write!(f, "enum"),
Adt::Struct(members) => {
let mut members = members.iter();
separate(",", || {
let (name, vis, id) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "\n{vis}{name}: {}", handle.with(*id)))
})(f.delimit_with("struct {", "\n}"))
}
Adt::TupleStruct(members) => {
let mut members = members.iter();
separate(", ", || {
let (vis, def) = members.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{vis}{}", handle.with(*def)))
})(f.delimit_with("struct (", ")"))
}
Adt::UnitStruct => write!(f, "struct;"),
Adt::Union(variants) => {
let mut variants = variants.iter();
separate(",", || {
let (name, def) = variants.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "\n{name}: {}", handle.with(*def)))
})(f.delimit_with("union {", "\n}"))
}
}
}
}

View File

@@ -0,0 +1,248 @@
//! Implements type unification, used by the Hindley-Milner type inference algorithm
//!
//! Inspired by [rust-hindley-milner][1] and [hindley-milner-python][2]
//!
//! [1]: https://github.com/tcr/rust-hindley-milner/
//! [2]: https://github.com/rob-smallshire/hindley-milner-python
use cl_ast::Sym;
use core::fmt;
use std::{cell::RefCell, rc::Rc};
/*
Types in Conlang:
- Never type: !
- type !
- for<A> ! -> A
- Primitive types: bool, i32, (), ...
- type bool; ...
- Reference types: &T, *T
- for<T> type ref<T>; for<T> type ptr<T>
- Slice type: [T]
- for<T> type slice<T>
- Array type: [T;usize]
- for<T> type array<T, instanceof<usize>>
- Tuple type: (T, ...Z)
- for<T, ..> type tuple<T, ..> // on a per-case basis!
- Funct type: fn Tuple -> R
- for<T, R> type T -> R // on a per-case basis!
*/
/// A refcounted [Type]
pub type RcType = Rc<Type>;
#[derive(Debug, PartialEq, Eq)]
pub struct Variable {
pub instance: RefCell<Option<RcType>>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Operator {
name: Sym,
types: RefCell<Vec<RcType>>,
}
/// A [Type::Variable] or [Type::Operator]:
/// - A [Type::Variable] can be either bound or unbound (instance: Some(_) | None)
/// - A [Type::Operator] has a name (used to identify the operator) and a list of types.
///
/// A type which contains unbound variables is considered "generic" (see
/// [`Type::is_generic()`]).
#[derive(Debug, PartialEq, Eq)]
pub enum Type {
Variable(Variable),
Operator(Operator),
}
impl Type {
/// Creates a new unbound [type variable](Type::Variable)
pub fn new_var() -> RcType {
Rc::new(Self::Variable(Variable { instance: RefCell::new(None) }))
}
/// Creates a variable that is a new instance of another [Type]
pub fn new_inst(of: &RcType) -> RcType {
Rc::new(Self::Variable(Variable {
instance: RefCell::new(Some(of.clone())),
}))
}
/// Creates a new [type operator](Type::Operator)
pub fn new_op(name: Sym, types: &[RcType]) -> RcType {
Rc::new(Self::Operator(Operator {
name,
types: RefCell::new(types.to_vec()),
}))
}
/// Creates a new [type operator](Type::Operator) representing a lambda
pub fn new_fn(takes: &RcType, returns: &RcType) -> RcType {
Self::new_op("fn".into(), &[takes.clone(), returns.clone()])
}
/// Creates a new [type operator](Type::Operator) representing a primitive type
pub fn new_prim(name: Sym) -> RcType {
Self::new_op(name, &[])
}
/// Creates a new [type operator](Type::Operator) representing a tuple
pub fn new_tuple(members: &[RcType]) -> RcType {
Self::new_op("tuple".into(), members)
}
/// Sets this type variable to be an instance `of` the other
/// # Panics
/// Panics if `self` is not a type variable
pub fn set_instance(self: &RcType, of: &RcType) {
match self.as_ref() {
Type::Operator(_) => unimplemented!("Cannot set instance of a type operator"),
Type::Variable(Variable { instance }) => *instance.borrow_mut() = Some(of.clone()),
}
}
/// Checks whether there are any unbound type variables in this type.
/// ```rust
/// # use cl_typeck::inference::*;
/// let bool = Type::new_op("bool".into(), &[]);
/// let true_v = Type::new_inst(&bool);
/// let unbound = Type::new_var();
/// let id_fun = Type::new_fn(&unbound, &unbound);
/// let truthy = Type::new_fn(&unbound, &bool);
/// assert!(!bool.is_generic()); // bool contains no unbound type variables
/// assert!(!true_v.is_generic()); // true_v is bound to `bool`
/// assert!(unbound.is_generic()); // unbound is an unbound type variable
/// assert!(id_fun.is_generic()); // id_fun is a function with unbound type variables
/// assert!(truthy.is_generic()); // truthy is a function with one unbound type variable
/// ```
pub fn is_generic(self: &RcType) -> bool {
match self.as_ref() {
Type::Variable(Variable { instance }) => match instance.borrow().as_ref() {
// base case: self is an unbound type variable (instance is none)
None => true,
// Variable is bound to a type which may be generic
Some(instance) => instance.is_generic(),
},
Type::Operator(Operator { types, .. }) => {
// Operator may have generic args
types.borrow().iter().any(Self::is_generic)
}
}
}
/// Makes a deep copy of a type expression.
///
/// Bound variables are shared, unbound variables are duplicated.
pub fn deep_clone(self: &RcType) -> RcType {
// If there aren't any unbound variables, it's fine to clone the entire expression
if !self.is_generic() {
return self.clone();
}
// There are unbound type variables, so we make a new one
match self.as_ref() {
Type::Variable { .. } => Self::new_var(),
Type::Operator(Operator { name, types }) => Self::new_op(
*name,
&types
.borrow()
.iter()
.map(Self::deep_clone)
.collect::<Vec<_>>(),
),
}
}
/// Returns the defining instance of `self`,
/// collapsing type instances along the way.
/// # May panic
/// Panics if this type variable's instance field is already borrowed.
/// # Examples
/// ```rust
/// # use cl_typeck::inference::*;
/// let t_bool = Type::new_op("bool".into(), &[]);
/// let t_nest = Type::new_inst(&Type::new_inst(&Type::new_inst(&t_bool)));
/// let pruned = t_nest.prune();
/// assert_eq!(pruned, t_bool);
/// assert_eq!(t_nest, Type::new_inst(&t_bool));
/// ```
pub fn prune(self: &RcType) -> RcType {
if let Type::Variable(Variable { instance }) = self.as_ref() {
if let Some(old_inst) = instance.borrow_mut().as_mut() {
let new_inst = old_inst.prune(); // get defining instance
*old_inst = new_inst.clone(); // collapse
return new_inst;
}
}
self.clone()
}
/// Checks whether a type expression occurs in another type expression
///
/// # Note:
/// - Since the test uses strict equality, `self` should be pruned prior to testing.
/// - The test is *not guaranteed to terminate* for recursive types.
pub fn occurs_in(self: &RcType, other: &RcType) -> bool {
if self == other {
return true;
}
match other.as_ref() {
Type::Variable(Variable { instance }) => match instance.borrow().as_ref() {
Some(t) => self.occurs_in(t),
None => false,
},
Type::Operator(Operator { types, .. }) => {
// Note: this might panic.
// Think about whether it panics for only recursive types?
types.borrow().iter().any(|other| self.occurs_in(other))
}
}
}
/// Unifies two type expressions, propagating changes via interior mutability
pub fn unify(self: &RcType, other: &RcType) -> Result<(), InferenceError> {
let (a, b) = (self.prune(), other.prune()); // trim the hedges
match (a.as_ref(), b.as_ref()) {
(Type::Variable { .. }, _) if !a.occurs_in(&b) => a.set_instance(&b),
(Type::Variable { .. }, _) => Err(InferenceError::Recursive(a, b))?,
(Type::Operator { .. }, Type::Variable { .. }) => b.unify(&a)?,
(
Type::Operator(Operator { name: a_name, types: a_types }),
Type::Operator(Operator { name: b_name, types: b_types }),
) => {
let (a_types, b_types) = (a_types.borrow(), b_types.borrow());
if a_name != b_name || a_types.len() != b_types.len() {
Err(InferenceError::Mismatch(a.clone(), b.clone()))?
}
for (a, b) in a_types.iter().zip(b_types.iter()) {
a.unify(b)?
}
}
}
Ok(())
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Variable(Variable { instance }) => match instance.borrow().as_ref() {
Some(instance) => write!(f, "{instance}"),
None => write!(f, "_"),
},
Type::Operator(Operator { name, types }) => {
write!(f, "({name}")?;
for ty in types.borrow().iter() {
write!(f, " {ty}")?;
}
f.write_str(")")
}
}
}
}
/// An error produced during type inference
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InferenceError {
Mismatch(RcType, RcType),
Recursive(RcType, RcType),
}
impl fmt::Display for InferenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InferenceError::Mismatch(a, b) => write!(f, "Type mismatch: {a:?} != {b:?}"),
InferenceError::Recursive(_, _) => write!(f, "Recursive type!"),
}
}
}

View File

@@ -0,0 +1,157 @@
//! # 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 [Project](project::Project)™
//! Contains [item definitions](definition) and type expression information.
//!
//! *Every* definition is itself a module, and can contain arbitrarily nested items
//! as part of the [Module](module::Module) tree.
//!
//! The Project keeps track of a *global intern pool* of definitions, which are
//! trivially comparable by [DefID](key::DefID). Note that, for item definitions,
//! identical types in different modules DO NOT COMPARE EQUAL under this constraint.
//! However, so-called "anonymous" types *do not* follow this rule, as their
//! definitions are constructed dynamically and ensured to be unique.
// Note: it's a class invariant that named types are not added
// to the anon-types list. Please keep it that way. ♥ Thanks!
//!
//! # Namespaces
//! Within a Project, [definitions](definition::Def) are classified into two namespaces:
//!
//! ## Type Namespace:
//! - Modules
//! - Structs
//! - Enums
//! - Type aliases
//!
//! ## Value Namespace:
//! - Functions
//! - Constants
//! - Static variables
//!
//! There is a *key* distinction between the two namespaces when it comes to
//! [Path](path::Path) traversal: Only items in the Type Namespace will be considered
//! as an intermediate path target. This means items in the Value namespace are
//! entirely *opaque*, and form a one-way barrier. Items outside a Value can be
//! referred to within the Value, but items inside a Value *cannot* be referred to
//! outside the Value.
//!
//! # Order of operations:
//! Currently, the process of type resolution goes as follows:
//!
//! 1. Traverse the AST, [collecting all Items into item definitions](name_collector)
//! 2. Eagerly [resolve `use` imports](use_importer)
//! 3. Traverse all [definitions](definition::Def), and [resolve the types for every
//! item](type_resolver)
//! 4. TODO: Construct a typed AST for expressions, and type-check them
#![warn(clippy::all)]
/*
How do I flesh out modules in an incremental way?
1. Visit all *modules* and create nodes in a module tree for them
- This can be done by holding mutable references to submodules!
Module:
values: Map(name -> Value),
types: Map(name -> Type),
Value: Either
function: { signature: Type, body: FunctionBody }
static: { Mutability, ty: Type, init: ConstEvaluationResult }
const: { ty: Type, init: ConstEvaluationResult }
*/
pub mod handle;
pub mod node;
pub mod definition;
pub mod module;
pub mod path;
pub mod project;
pub mod name_collector;
pub mod use_importer;
pub mod type_resolver;
pub mod inference;
pub(crate) mod format_utils;
/*
LET THERE BE NOTES:
/// 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>> {
}
*/

View File

@@ -0,0 +1,71 @@
//! A [Module] is a node in the Module Tree (a component of a
//! [Project](crate::project::Project))
use cl_ast::Sym;
use cl_structures::index_map::MapIndex;
use crate::handle::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<Sym, DefID>,
pub values: HashMap<Sym, DefID>,
pub imports: Vec<DefID>,
}
impl Module {
pub fn new(parent: DefID) -> Self {
Self { parent: Some(parent), ..Default::default() }
}
pub fn with_optional_parent(parent: Option<DefID>) -> Self {
Self { parent, ..Default::default() }
}
pub fn get(&self, name: Sym) -> (Option<DefID>, Option<DefID>) {
(self.get_type(name), self.get_value(name))
}
pub fn get_type(&self, name: Sym) -> Option<DefID> {
self.types.get(&name).copied()
}
pub fn get_value(&self, name: Sym) -> Option<DefID> {
self.values.get(&name).copied()
}
/// Inserts a type with the provided [name](str) and [id](DefID)
pub fn insert_type(&mut self, name: Sym, id: DefID) -> Option<DefID> {
self.types.insert(name, id)
}
/// Inserts a value with the provided [name](str) and [id](DefID)
pub fn insert_value(&mut self, name: Sym, id: DefID) -> Option<DefID> {
self.values.insert(name, id)
}
}
impl std::fmt::Display for Module {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { parent, types, values, imports } = self;
if let Some(parent) = parent {
writeln!(f, "Parent: {}", parent.get())?;
}
for (name, table) in [("Types", types), ("Values", values)] {
if table.is_empty() {
continue;
}
writeln!(f, "{name}:")?;
for (name, id) in table.iter() {
writeln!(f, " {name} => {id}")?;
}
}
if !imports.is_empty() {
write!(f, "Imports:")?;
for id in imports {
write!(f, "{id},")?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,218 @@
//! Performs step 1 of type checking: Collecting all the names of things into [Module] units
use crate::{
definition::{Def, DefKind},
handle::DefID,
module::Module as Mod,
node::{Node, NodeSource},
project::Project as Prj,
};
use cl_ast::{ast_visitor::Visit, *};
use std::mem;
#[derive(Debug)]
pub struct NameCollector<'prj, 'a> {
path: cl_ast::Path,
prj: &'prj mut Prj<'a>,
parent: DefID,
retval: Option<DefID>,
}
impl<'prj, 'a> NameCollector<'prj, 'a> {
/// Constructs a new [NameCollector] out of a [Project](Prj)
pub fn new(prj: &'prj mut Prj<'a>) -> Self {
Self { parent: prj.root, prj, path: Default::default(), retval: None }
}
/// Constructs a new [NameCollector] out of a [Project](Prj) and a parent [DefID]
pub fn with_root(prj: &'prj mut Prj<'a>, parent: DefID) -> Self {
Self { prj, parent, path: Default::default(), retval: None }
}
/// Runs the provided function with the given parent
pub fn with_parent<F, N>(&mut self, parent: DefID, node: N, f: F)
where F: FnOnce(&mut Self, N) {
let parent = mem::replace(&mut self.parent, parent);
f(self, node);
self.parent = parent;
}
/// Extracts the return value from the provided function
pub fn returns<F, N>(&mut self, node: N, f: F) -> Option<DefID>
where F: FnOnce(&mut Self, N) {
let out = self.retval.take();
f(self, node);
mem::replace(&mut self.retval, out)
}
}
impl<'prj, 'a> Visit<'a> for NameCollector<'prj, 'a> {
fn visit_item(&mut self, i: &'a Item) {
let Item { extents: _, attrs, vis, kind } = i;
if let Some(def) = self.returns(kind, Self::visit_item_kind) {
self.prj[def].set_meta(&attrs.meta).set_vis(*vis);
}
}
fn visit_module(&mut self, m: &'a Module) {
let Self { prj, parent, path, retval: _ } = self;
let Module { name, kind } = m;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Module(m))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_type(*name, id);
self.path.push(PathPart::Ident(*name));
self.with_parent(id, kind, Self::visit_module_kind);
self.path.pop();
self.retval = Some(id);
}
fn visit_alias(&mut self, a: &'a Alias) {
let Self { prj, parent, path, retval: _ } = self;
let Alias { to: name, from: _ } = a;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Alias(a))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_type(*name, id);
self.retval = Some(id);
}
fn visit_enum(&mut self, e: &'a Enum) {
let Self { prj, parent, path, retval: _ } = self;
let Enum { name, kind } = e;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Enum(e))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_type(*name, id);
self.with_parent(id, kind, Self::visit_enum_kind);
self.retval = Some(id);
}
fn visit_variant(&mut self, v: &'a Variant) {
let Self { path, prj, parent, retval: _ } = self;
let Variant { name, kind } = v;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Variant(v))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_type(*name, id);
self.with_parent(id, kind, Self::visit_variant_kind);
self.retval = Some(id);
}
fn visit_struct(&mut self, s: &'a Struct) {
let Self { prj, parent, path, retval: _ } = self;
let Struct { name, kind } = s;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Struct(s))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_type(*name, id);
self.with_parent(id, kind, Self::visit_struct_kind);
self.retval = Some(id);
}
fn visit_const(&mut self, c: &'a Const) {
let Self { prj, parent, path, retval: _ } = self;
let Const { name, ty: _, init } = c;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Const(c))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_value(*name, id);
self.with_parent(id, &**init, Self::visit_expr);
self.retval = Some(id);
}
fn visit_static(&mut self, s: &'a Static) {
let Self { prj, parent, path, retval: _ } = self;
let Static { name, mutable: _, ty: _, init } = s;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Static(s))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_value(*name, id);
self.with_parent(id, &**init, Self::visit_expr);
self.retval = Some(id);
}
fn visit_function(&mut self, f: &'a Function) {
let Self { prj, parent, path, retval: _ } = self;
let Function { name, body, .. } = f;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Function(f))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_value(*name, id);
if let Some(body) = body {
self.with_parent(id, body, Self::visit_block);
}
self.retval = Some(id);
}
fn visit_impl(&mut self, i: &'a Impl) {
let Self { prj, parent, path, retval: _ } = self;
let Impl { target: _, body } = i;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Impl(i))),
};
let id = prj.pool.insert(def);
// items will get reparented after name collection, when target is available
self.with_parent(id, body, Self::visit_file);
self.retval = Some(id);
}
fn visit_use(&mut self, u: &'a Use) {
let Self { prj, parent, path, retval } = self;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Use(*parent),
node: Node::new(path.clone(), Some(NodeSource::Use(u))),
};
let id = prj.pool.insert(def);
prj[*parent].module.imports.push(id);
*retval = Some(id);
}
fn visit_let(&mut self, l: &'a Let) {
let Self { prj, parent, path, retval: _ } = self;
let Let { name, init, .. } = l;
let def = Def {
module: Mod::new(*parent),
kind: DefKind::Undecided,
node: Node::new(path.clone(), Some(NodeSource::Local(l))),
};
let id = prj.pool.insert(def);
prj[*parent].module.insert_value(*name, id);
if let Some(expr) = init {
self.visit_expr(expr)
}
self.retval = Some(id)
}
}

View File

@@ -0,0 +1,214 @@
//! A [Node] contains the [NodeSource] and [Item] metadata for any
//! [Def](crate::definition::Def), as well as the [Path] of the
//! containing [Module].
//!
//! [Node]s are collected by the [Node Sorcerer](sorcerer),
//! an AST visitor that pairs [NodeSource]s with their surrounding
//! context ([Path], [struct@Span], [Meta], [Visibility])
use cl_ast::ast::*;
use cl_structures::span::Span;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Node<'a> {
pub in_path: Path,
pub span: &'a Span,
pub meta: &'a [Meta],
pub vis: Visibility,
pub kind: Option<NodeSource<'a>>,
}
impl<'a> Node<'a> {
pub fn new(path: Path, kind: Option<NodeSource<'a>>) -> Self {
const DUMMY_SPAN: Span = Span::dummy();
Self { in_path: path, span: &DUMMY_SPAN, meta: &[], vis: Visibility::Public, kind }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NodeSource<'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<'a> NodeSource<'a> {
pub fn name(&self) -> Option<Sym> {
match self {
NodeSource::Root => None,
NodeSource::Module(v) => Some(v.name),
NodeSource::Alias(v) => Some(v.to),
NodeSource::Enum(v) => Some(v.name),
NodeSource::Variant(v) => Some(v.name),
NodeSource::Struct(v) => Some(v.name),
NodeSource::Const(v) => Some(v.name),
NodeSource::Static(v) => Some(v.name),
NodeSource::Function(v) => Some(v.name),
NodeSource::Local(l) => Some(l.name),
NodeSource::Impl(_) | NodeSource::Use(_) | NodeSource::Ty(_) => None,
}
}
/// Returns `true` if this [NodeSource] defines a named value
pub fn is_named_value(&self) -> bool {
matches!(self, Self::Const(_) | Self::Static(_) | Self::Function(_))
}
/// Returns `true` if this [NodeSource] 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 [NodeSource] refers to a [Ty] with no name
pub fn is_anon_type(&self) -> bool {
matches!(self, Self::Ty(_))
}
/// Returns `true` if this [NodeSource] refers to an [Impl] block
pub fn is_impl(&self) -> bool {
matches!(self, Self::Impl(_))
}
/// Returns `true` if this [NodeSource] refers to a [Use] import
pub fn is_use_import(&self) -> bool {
matches!(self, Self::Use(_))
}
}
impl fmt::Display for NodeSource<'_> {
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),
}
}
}
pub mod sorcerer {
//! An [AST](cl_ast) analysis pass that collects [Node] entries.
use super::{Node, NodeSource};
use cl_ast::{ast::*, ast_visitor::visit::*};
use cl_structures::span::Span;
use std::mem;
/// An AST analysis pass that collects [Node]s
#[derive(Clone, Debug)]
pub struct NodeSorcerer<'a> {
path: Path,
parts: Parts<'a>,
defs: Vec<Node<'a>>,
}
type Parts<'a> = (&'a Span, &'a [Meta], Visibility);
impl<'a> NodeSorcerer<'a> {
pub fn into_defs(self) -> Vec<Node<'a>> {
self.defs
}
fn with_parts<F>(&mut self, s: &'a Span, a: &'a [Meta], v: Visibility, f: F)
where F: FnOnce(&mut Self) {
let parts = mem::replace(&mut self.parts, (s, a, v));
f(self);
self.parts = parts;
}
fn with_only_span<F>(&mut self, span: &'a Span, f: F)
where F: FnOnce(&mut Self) {
self.with_parts(span, &[], Visibility::Public, f)
}
fn push(&mut self, kind: NodeSource<'a>) {
let Self { path, parts, defs } = self;
let (span, meta, vis) = *parts;
defs.push(Node { in_path: path.clone(), span, meta, vis, kind: Some(kind) })
}
}
impl Default for NodeSorcerer<'_> {
fn default() -> Self {
const DPARTS: Parts = (&Span::dummy(), &[], Visibility::Private);
Self {
path: Path { absolute: true, ..Default::default() },
parts: DPARTS,
defs: Default::default(),
}
}
}
impl<'a> Visit<'a> for NodeSorcerer<'a> {
fn visit_module(&mut self, m: &'a Module) {
let Module { name, kind } = m;
self.path.push(PathPart::Ident(*name));
self.visit_module_kind(kind);
self.path.pop();
}
fn visit_item(&mut self, i: &'a Item) {
let Item { extents, attrs, vis, kind } = i;
self.with_parts(extents, &attrs.meta, *vis, |v| {
v.visit_item_kind(kind);
});
}
fn visit_ty(&mut self, t: &'a Ty) {
let Ty { extents, kind } = t;
self.with_only_span(extents, |v| {
v.push(NodeSource::Ty(kind));
v.visit_ty_kind(kind);
});
}
fn visit_stmt(&mut self, s: &'a Stmt) {
let Stmt { extents, kind, semi } = s;
self.with_only_span(extents, |d| {
d.visit_stmt_kind(kind);
d.visit_semi(semi);
})
}
fn visit_item_kind(&mut self, kind: &'a ItemKind) {
match kind {
ItemKind::Module(i) => self.push(NodeSource::Module(i)),
ItemKind::Alias(i) => self.push(NodeSource::Alias(i)),
ItemKind::Enum(i) => self.push(NodeSource::Enum(i)),
ItemKind::Struct(i) => self.push(NodeSource::Struct(i)),
ItemKind::Const(i) => self.push(NodeSource::Const(i)),
ItemKind::Static(i) => self.push(NodeSource::Static(i)),
ItemKind::Function(i) => self.push(NodeSource::Function(i)),
ItemKind::Impl(i) => self.push(NodeSource::Impl(i)),
ItemKind::Use(i) => self.push(NodeSource::Use(i)),
}
or_visit_item_kind(self, kind);
}
fn visit_stmt_kind(&mut self, kind: &'a StmtKind) {
if let StmtKind::Local(l) = kind {
self.push(NodeSource::Local(l))
}
or_visit_stmt_kind(self, kind);
}
}
}

View File

@@ -0,0 +1,60 @@
//! A [Path] is a borrowed view of an [AST Path](AstPath)
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 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 first(&self) -> Option<&PathPart> {
self.parts.first()
}
}
impl<'p> From<&'p AstPath> for Path<'p> {
fn from(value: &'p AstPath) -> Self {
Self::new(value)
}
}
impl AsRef<[PathPart]> for Path<'_> {
fn as_ref(&self) -> &[PathPart] {
self.parts
}
}
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(())
}
}

View File

@@ -0,0 +1,376 @@
//! A [Project] contains a tree of [Def]initions, referred to by their [Path]
use crate::{
definition::{Def, DefKind, TypeKind},
handle::DefID,
module,
node::{Node, NodeSource},
path::Path,
};
use cl_ast::PathPart;
use cl_structures::index_map::IndexMap;
use std::{
collections::HashMap,
ops::{Index, IndexMut},
};
use self::evaluate::EvaluableTypeExpression;
#[derive(Clone, Debug)]
pub struct Project<'a> {
pub pool: IndexMap<DefID, Def<'a>>,
/// Stores anonymous tuples, function pointer types, etc.
pub anon_types: HashMap<TypeKind, DefID>,
pub root: DefID,
}
impl Project<'_> {
pub fn new() -> Self {
Self::default()
}
}
impl Default for Project<'_> {
fn default() -> Self {
const ROOT_PATH: cl_ast::Path = cl_ast::Path { absolute: true, parts: Vec::new() };
let mut pool = IndexMap::default();
let root = pool.insert(Def {
module: Default::default(),
kind: DefKind::Type(TypeKind::Module),
node: Node::new(ROOT_PATH, Some(NodeSource::Root)),
});
let never = pool.insert(Def {
module: module::Module::new(root),
kind: DefKind::Type(TypeKind::Never),
node: Node::new(ROOT_PATH, None),
});
let empty = pool.insert(Def {
module: module::Module::new(root),
kind: DefKind::Type(TypeKind::Empty),
node: Node::new(ROOT_PATH, None),
});
let mut anon_types = HashMap::new();
anon_types.insert(TypeKind::Empty, empty);
anon_types.insert(TypeKind::Never, never);
Self { pool, root, anon_types }
}
}
impl<'a> Project<'a> {
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,
}
}
/// Returns the DefID of the Self type within the given DefID's context
pub fn selfty_of(&self, node: DefID) -> Option<DefID> {
match self[node].kind {
DefKind::Impl(id) => Some(id),
DefKind::Type(_) => Some(node),
_ => self.selfty_of(self.parent_of(node)?),
}
}
pub fn get<'p>(
&self,
path: Path<'p>,
within: DefID,
) -> Option<(Option<DefID>, Option<DefID>, Path<'p>)> {
if path.absolute {
return self.get(path.relative(), self.root_of(within));
}
match path.as_ref() {
[PathPart::SuperKw, ..] => self.get(path.pop_front()?, self.parent_of(within)?),
[PathPart::SelfTy, ..] => self.get(path.pop_front()?, self.selfty_of(within)?),
[PathPart::SelfKw, ..] => self.get(path.pop_front()?, within),
[PathPart::Ident(name)] => {
let (ty, val) = self[within].module.get(*name);
// Transparent nodes can be looked through in reverse
if self[within].is_transparent() {
let lookback = self.parent_of(within).and_then(|p| self.get(path, p));
if let Some((subty, subval, path)) = lookback {
return Some((ty.or(subty), val.or(subval), path));
}
}
Some((ty, val, path.pop_front()?))
}
[PathPart::Ident(name), ..] => {
// TODO: This is currently too permissive, and treats undecided nodes as if they're
// always transparent, among other issues.
let (tysub, _, _) = match self[within].is_transparent() {
true => self.get(path.front()?, within)?,
false => (None, None, path),
};
let ty = self[within].module.get_type(*name).or(tysub)?;
self.get(path.pop_front()?, ty)
}
[] => Some((Some(within), None, path)),
}
}
/// Resolves a path within a module tree, finding the innermost module.
/// Returns the remaining path parts.
pub fn get_type<'p>(&self, path: Path<'p>, within: DefID) -> Option<(DefID, Path<'p>)> {
if path.absolute {
self.get_type(path.relative(), self.root_of(within))
} else if let Some(front) = path.first() {
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::SelfTy => self.get_type(path.pop_front()?, self.selfty_of(within)?),
PathPart::Ident(name) => match module.types.get(name) {
Some(&submodule) => self.get_type(path.pop_front()?, submodule),
None => Some((within, path)),
},
}
} else {
Some((within, path))
}
}
/// Inserts the type returned by the provided closure iff the TypeKind doesn't already exist
///
/// Assumes `kind` uniquely identifies the type!
pub fn insert_anonymous_type(
&mut self,
kind: TypeKind,
def: impl FnOnce() -> Def<'a>,
) -> DefID {
*(self
.anon_types
.entry(kind)
.or_insert_with(|| self.pool.insert(def())))
}
pub fn evaluate<T>(&mut self, expr: &T, parent: DefID) -> Result<T::Out, String>
where T: EvaluableTypeExpression {
expr.evaluate(self, parent)
}
}
impl<'a> Index<DefID> for Project<'a> {
type Output = Def<'a>;
fn index(&self, index: DefID) -> &Self::Output {
&self.pool[index]
}
}
impl IndexMut<DefID> for Project<'_> {
fn index_mut(&mut self, index: DefID) -> &mut Self::Output {
&mut self.pool[index]
}
}
pub mod evaluate {
//! An [EvaluableTypeExpression] is a component of a type expression tree
//! or an intermediate result of expression evaluation.
use super::*;
use crate::module;
use cl_ast::{Sym, Ty, TyArray, TyFn, TyKind, TyRef, TySlice, TyTuple};
/// Things that can be evaluated as a type expression
pub trait EvaluableTypeExpression {
/// The result of type expression evaluation
type Out;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String>;
}
impl EvaluableTypeExpression for Ty {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, id: DefID) -> Result<DefID, String> {
self.kind.evaluate(prj, id)
}
}
impl EvaluableTypeExpression for TyKind {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<DefID, String> {
let id = match self {
// TODO: reduce duplication here
TyKind::Never => prj.anon_types[&TypeKind::Never],
TyKind::Empty => prj.anon_types[&TypeKind::Empty],
// TyKind::Path must be looked up explicitly
TyKind::Path(path) => path.evaluate(prj, parent)?,
TyKind::Slice(slice) => slice.evaluate(prj, parent)?,
TyKind::Array(array) => array.evaluate(prj, parent)?,
TyKind::Tuple(tup) => tup.evaluate(prj, parent)?,
TyKind::Ref(tyref) => tyref.evaluate(prj, parent)?,
TyKind::Fn(tyfn) => tyfn.evaluate(prj, parent)?,
};
// println!("{self} => {id:?}");
Ok(id)
}
}
impl EvaluableTypeExpression for Sym {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
prj[parent]
.module
.types
.get(self)
.copied()
.ok_or_else(|| format!("{self} is not a member of {:?}", prj[parent].name()))
}
}
impl EvaluableTypeExpression for TySlice {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
let ty = self.ty.evaluate(prj, parent)?;
let root = prj.root;
let id = prj.insert_anonymous_type(TypeKind::Slice(ty), move || Def {
module: module::Module::new(root),
node: Node::new(Default::default(), None),
kind: DefKind::Type(TypeKind::Slice(ty)),
});
Ok(id)
}
}
impl EvaluableTypeExpression for TyArray {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
let kind = TypeKind::Array(self.ty.evaluate(prj, parent)?, self.count);
let root = prj.root;
let id = prj.insert_anonymous_type(kind.clone(), move || Def {
module: module::Module::new(root),
node: Node::new(Default::default(), None),
kind: DefKind::Type(kind),
});
Ok(id)
}
}
impl EvaluableTypeExpression for TyTuple {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<DefID, String> {
let types = self.types.evaluate(prj, parent)?;
let root = prj.root;
let id = prj.insert_anonymous_type(TypeKind::Tuple(types.clone()), move || Def {
module: module::Module::new(root),
node: Node::new(Default::default(), None),
kind: DefKind::Type(TypeKind::Tuple(types)),
});
Ok(id)
}
}
impl EvaluableTypeExpression for TyRef {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<DefID, String> {
let TyRef { count, mutable: _, to } = self;
let to = to.evaluate(prj, parent)?;
let root = prj.root;
let id = prj.insert_anonymous_type(TypeKind::Ref(*count, to), move || Def {
module: module::Module::new(root),
node: Node::new(Default::default(), None),
kind: DefKind::Type(TypeKind::Ref(*count, to)),
});
Ok(id)
}
}
impl EvaluableTypeExpression for TyFn {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<DefID, String> {
let TyFn { args, rety } = self;
let args = args.evaluate(prj, parent)?;
let rety = match rety {
Some(rety) => rety.evaluate(prj, parent)?,
_ => TyKind::Empty.evaluate(prj, parent)?,
};
let root = prj.root;
let id = prj.insert_anonymous_type(TypeKind::FnSig { args, rety }, || Def {
module: module::Module::new(root),
node: Node::new(Default::default(), None),
kind: DefKind::Type(TypeKind::FnSig { args, rety }),
});
Ok(id)
}
}
impl EvaluableTypeExpression for cl_ast::Path {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
Path::from(self).evaluate(prj, parent)
}
}
impl EvaluableTypeExpression for PathPart {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
match self {
PathPart::SuperKw => prj
.parent_of(parent)
.ok_or_else(|| "Attempt to get super of root".into()),
PathPart::SelfKw => Ok(parent),
PathPart::SelfTy => prj
.selfty_of(parent)
.ok_or_else(|| "Attempt to get Self outside a Self-able context".into()),
PathPart::Ident(name) => name.evaluate(prj, parent),
}
}
}
impl<'a> EvaluableTypeExpression for Path<'a> {
type Out = DefID;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
let (tid, vid, path) = prj.get(*self, parent).ok_or("Failed to traverse path")?;
if !path.is_empty() {
Err(format!("Could not traverse past boundary: {path}"))?;
}
match (tid, vid) {
(Some(ty), _) => Ok(ty),
(None, Some(val)) => Ok(val),
(None, None) => Err(format!("No type or value found at path {self}")),
}
}
}
impl<T: EvaluableTypeExpression> EvaluableTypeExpression for [T] {
type Out = Vec<T::Out>;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
let mut types = vec![];
for value in self {
types.push(value.evaluate(prj, parent)?)
}
Ok(types)
}
}
impl<T: EvaluableTypeExpression> EvaluableTypeExpression for Option<T> {
type Out = Option<T::Out>;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
Ok(match self {
Some(v) => Some(v.evaluate(prj, parent)?),
None => None,
})
}
}
impl<T: EvaluableTypeExpression> EvaluableTypeExpression for Box<T> {
type Out = T::Out;
fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result<Self::Out, String> {
self.as_ref().evaluate(prj, parent)
}
}
}

View File

@@ -0,0 +1,336 @@
//! Performs step 2 of type checking: Evaluating type definitions
use crate::{
definition::{Adt, Def, DefKind, TypeKind, ValueKind},
handle::DefID,
node::{Node, NodeSource},
project::{evaluate::EvaluableTypeExpression, Project as Prj},
};
use cl_ast::*;
/// Evaluate a single ID
pub fn resolve(prj: &mut Prj, id: DefID) -> Result<(), &'static str> {
let Def { node: Node { kind: Some(source), meta, .. }, kind: DefKind::Undecided, .. } = prj[id]
else {
return Ok(());
};
let kind = match &source {
NodeSource::Root => "root",
NodeSource::Alias(_) => "type",
NodeSource::Module(_) => "mod",
NodeSource::Enum(_) => "enum",
NodeSource::Variant(_) => "variant",
NodeSource::Struct(_) => "struct",
NodeSource::Const(_) => "const",
NodeSource::Static(_) => "static",
NodeSource::Function(_) => "fn",
NodeSource::Impl(_) => "impl",
NodeSource::Use(_) => "use",
NodeSource::Local(_) => "let",
NodeSource::Ty(_) => "ty",
};
let name = prj[id].name().unwrap_or("".into());
eprintln!("Resolver: \x1b[32mEvaluating\x1b[0m \"\x1b[36m{kind} {name}\x1b[0m\" (`{id:?}`)");
for Meta { name, kind } in meta {
if let ("intrinsic", MetaKind::Equals(Literal::String(s))) = (&**name, kind) {
prj[id].kind = DefKind::Type(TypeKind::Intrinsic(
s.parse().map_err(|_| "Failed to parse intrinsic")?,
));
}
}
if DefKind::Undecided == prj[id].kind {
prj[id].kind = source.resolve_type(prj, id)?;
}
eprintln!("\x1b[33m=> {}\x1b[0m", prj[id].kind);
Ok(())
}
/// Resolves a given node
pub trait TypeResolvable<'a> {
/// The return type upon success
type Out;
/// Resolves type expressions within this node
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str>;
}
impl<'a> TypeResolvable<'a> for NodeSource<'a> {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
match self {
NodeSource::Root => Ok(DefKind::Type(TypeKind::Module)),
NodeSource::Module(v) => v.resolve_type(prj, id),
NodeSource::Alias(v) => v.resolve_type(prj, id),
NodeSource::Enum(v) => v.resolve_type(prj, id),
NodeSource::Variant(v) => v.resolve_type(prj, id),
NodeSource::Struct(v) => v.resolve_type(prj, id),
NodeSource::Const(v) => v.resolve_type(prj, id),
NodeSource::Static(v) => v.resolve_type(prj, id),
NodeSource::Function(v) => v.resolve_type(prj, id),
NodeSource::Local(v) => v.resolve_type(prj, id),
NodeSource::Impl(v) => v.resolve_type(prj, id),
NodeSource::Use(v) => v.resolve_type(prj, id),
NodeSource::Ty(v) => v.resolve_type(prj, id),
}
}
}
impl<'a> TypeResolvable<'a> for &'a Meta {
type Out = DefKind;
#[allow(unused_variables)]
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let Meta { name, kind } = self;
match (name.as_ref(), kind) {
("intrinsic", MetaKind::Equals(Literal::String(intrinsic))) => Ok(DefKind::Type(
TypeKind::Intrinsic(intrinsic.parse().map_err(|_| "unknown intrinsic type")?),
)),
(_, MetaKind::Plain) => Ok(DefKind::Type(TypeKind::Intrinsic(
name.parse().map_err(|_| "Unknown intrinsic type")?,
))),
_ => Err("Unknown meta attribute"),
}
}
}
impl<'a> TypeResolvable<'a> for &'a Module {
type Out = DefKind;
#[allow(unused_variables)]
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
Ok(DefKind::Type(TypeKind::Module))
}
}
impl<'a> TypeResolvable<'a> for &'a Alias {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let parent = prj.parent_of(id).unwrap_or(id);
let alias = if let Some(ty) = &self.from {
Some(
ty.evaluate(prj, parent)
.or_else(|_| ty.evaluate(prj, id))
.map_err(|_| "Unresolved type in alias")?,
)
} else {
None
};
Ok(DefKind::Type(TypeKind::Alias(alias)))
}
}
impl<'a> TypeResolvable<'a> for &'a Enum {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let Enum { name: _, kind } = self;
let EnumKind::Variants(v) = kind else {
return Ok(DefKind::Type(TypeKind::Adt(Adt::FieldlessEnum)));
};
let mut fields = vec![];
for Variant { name, kind: _ } in v {
let id = prj[id].module.get_type(*name);
fields.push((*name, id))
}
Ok(DefKind::Type(TypeKind::Adt(Adt::Enum(fields))))
}
}
impl<'a> TypeResolvable<'a> for &'a Variant {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
// Get the grandparent of this node, for name resolution
let parent = prj.parent_of(id).unwrap_or(id);
let grandparent = prj.parent_of(parent).unwrap_or(parent);
let Variant { name: _, kind } = self;
Ok(DefKind::Type(match kind {
VariantKind::Plain => return Ok(DefKind::Type(TypeKind::Empty)),
VariantKind::CLike(_) => return Ok(DefKind::Undecided),
VariantKind::Tuple(ty) => match &ty.kind {
TyKind::Empty => TypeKind::Tuple(vec![]),
TyKind::Tuple(TyTuple { types }) => {
TypeKind::Tuple(types.evaluate(prj, grandparent).map_err(|e| {
eprintln!("{e}");
""
})?)
}
_ => Err("Unexpected TyKind in tuple variant")?,
},
VariantKind::Struct(members) => {
TypeKind::Adt(Adt::Struct(members.resolve_type(prj, parent)?))
}
}))
}
}
impl<'a> TypeResolvable<'a> for &'a Struct {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let parent = prj.parent_of(id).unwrap_or(id);
let Struct { name: _, kind } = self;
Ok(match kind {
StructKind::Empty => DefKind::Type(TypeKind::Empty),
StructKind::Tuple(types) => DefKind::Type(TypeKind::Adt(Adt::TupleStruct({
let mut out = vec![];
for ty in types {
out.push((
Visibility::Public,
ty.evaluate(prj, parent)
.map_err(|_| "Unresolved type in tuple-struct member")?,
));
}
out
}))),
StructKind::Struct(members) => {
DefKind::Type(TypeKind::Adt(Adt::Struct(members.resolve_type(prj, id)?)))
}
})
}
}
impl<'a> TypeResolvable<'a> for &'a StructMember {
type Out = (Sym, Visibility, DefID);
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let parent = prj.parent_of(id).unwrap_or(id);
let StructMember { name, vis, ty } = self;
let ty = ty
.evaluate(prj, parent)
.map_err(|_| "Invalid type while resolving StructMember")?;
Ok((*name, *vis, ty))
}
}
impl<'a> TypeResolvable<'a> for &'a Const {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let Const { ty, .. } = self;
let ty = ty
.evaluate(prj, id)
.map_err(|_| "Invalid type while resolving const")?;
Ok(DefKind::Value(ValueKind::Const(ty)))
}
}
impl<'a> TypeResolvable<'a> for &'a Static {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let parent = prj.parent_of(id).unwrap_or(id);
let Static { ty, .. } = self;
let ty = ty
.evaluate(prj, parent)
.map_err(|_| "Invalid type while resolving static")?;
Ok(DefKind::Value(ValueKind::Static(ty)))
}
}
impl<'a> TypeResolvable<'a> for &'a Function {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let parent = prj.parent_of(id).unwrap_or(id);
let Function { sign, .. } = self;
let sign = sign
.evaluate(prj, parent)
.map_err(|_| "Invalid type in function signature")?;
Ok(DefKind::Value(ValueKind::Fn(sign)))
}
}
impl<'a> TypeResolvable<'a> for &'a Let {
type Out = DefKind;
#[allow(unused)]
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let Let { mutable, name, ty, init } = self;
Ok(DefKind::Undecided)
}
}
impl<'a> TypeResolvable<'a> for &'a Impl {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let parent = prj.parent_of(id).unwrap_or(id);
let target = match &self.target {
ImplKind::Type(t) => t.evaluate(prj, parent),
ImplKind::Trait { for_type, .. } => for_type.evaluate(prj, parent),
}
.map_err(|_| "Unresolved type in impl target")?;
match prj.pool.get_many_mut([id, target]) {
// TODO: Better error handling
Err(_) => Err(concat!(
file!(),
line!(),
column!(),
"id and target are same"
))?,
Ok([id, target]) => {
for (name, def) in &id.module.types {
target.module.insert_type(*name, *def);
}
for (name, def) in &id.module.values {
target.module.insert_value(*name, *def);
}
}
}
Ok(DefKind::Impl(target))
}
}
impl<'a> TypeResolvable<'a> for &'a Use {
type Out = DefKind;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
todo!("Resolve types for {self} with ID {id} in {prj:?}")
}
}
impl<'a> TypeResolvable<'a> for &'a TyKind {
type Out = DefKind;
#[allow(unused)]
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
todo!()
}
}
impl<'a, T> TypeResolvable<'a> for &'a [T]
where &'a T: TypeResolvable<'a>
{
type Out = Vec<<&'a T as TypeResolvable<'a>>::Out>;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
let mut members = vec![];
for member in self {
members.push(member.resolve_type(prj, id)?);
}
Ok(members)
}
}
impl<'a, T> TypeResolvable<'a> for Option<&'a T>
where &'a T: TypeResolvable<'a>
{
type Out = Option<<&'a T as TypeResolvable<'a>>::Out>;
fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result<Self::Out, &'static str> {
match self {
Some(t) => Some(t.resolve_type(prj, id)).transpose(),
None => Ok(None),
}
}
}

View File

@@ -0,0 +1,125 @@
//! WIP use-item importer. This performs eager import resolution on the AST
//!
//! # TODOs:
//! - [ ] Resolve imports using a graph traversal rather than linear iteration
//! - [ ] Separate imported items from natively declared items
//! - [ ] Separate the use-import pass from the project
//! - [ ] Report errors in a meaningful way
//! - [ ] Lazy import resolution using graph-edge traversal during name lookup?
//! - It doesn't seem to me like the imports in a given scope *can change*.
#![allow(unused)]
use std::fmt::format;
use cl_ast::*;
use crate::{
definition::{Def, DefKind},
handle::DefID,
node::NodeSource,
project::Project,
};
type UseResult = Result<(), String>;
impl<'a> Project<'a> {
pub fn resolve_imports(&mut self) -> UseResult {
for id in self.pool.keys() {
self.visit_def(id)?;
}
Ok(())
}
pub fn visit_def(&mut self, id: DefID) -> UseResult {
let Def { kind, node, module } = &self.pool[id];
if let (DefKind::Use(parent), Some(NodeSource::Use(u))) = (kind, node.kind) {
println!("Importing use item {u}");
self.visit_use(u, *parent);
}
Ok(())
}
pub fn visit_use(&mut self, u: &'a Use, parent: DefID) -> UseResult {
let Use { absolute, tree } = u;
self.visit_use_tree(tree, parent, if *absolute { self.root } else { parent })
}
pub fn visit_use_tree(&mut self, tree: &'a UseTree, parent: DefID, c: DefID) -> UseResult {
match tree {
UseTree::Tree(trees) => {
for tree in trees {
self.visit_use_tree(tree, parent, c)?;
}
}
UseTree::Path(part, rest) => {
let c = self.evaluate(part, c)?;
self.visit_use_tree(rest, parent, c)?;
}
UseTree::Name(name) => self.visit_use_leaf(name, parent, c)?,
UseTree::Alias(from, to) => self.visit_use_alias(from, to, parent, c)?,
UseTree::Glob => self.visit_use_glob(parent, c)?,
}
Ok(())
}
pub fn visit_use_path(&mut self) -> UseResult {
Ok(())
}
pub fn visit_use_leaf(&mut self, name: &'a Sym, parent: DefID, c: DefID) -> UseResult {
self.visit_use_alias(name, name, parent, c)
}
pub fn visit_use_alias(
&mut self,
from: &Sym,
name: &Sym,
parent: DefID,
c: DefID,
) -> UseResult {
let mut imported = false;
let c_mod = &self[c].module;
let (tid, vid) = (
c_mod.types.get(from).copied(),
c_mod.values.get(from).copied(),
);
let parent = &mut self[parent].module;
if let Some(tid) = tid {
parent.types.insert(*name, tid);
imported = true;
}
if let Some(vid) = vid {
parent.values.insert(*name, vid);
imported = true;
}
if imported {
Ok(())
} else {
Err(format!("Identifier {name} not found in module {c}"))
}
}
pub fn visit_use_glob(&mut self, parent: DefID, c: DefID) -> UseResult {
// Loop over all the items in c, and add them as items in the parent
if parent == c {
return Ok(());
}
let [parent, c] = self
.pool
.get_many_mut([parent, c])
.expect("parent and c are not the same");
for (k, v) in &c.module.types {
parent.module.types.entry(*k).or_insert(*v);
}
for (k, v) in &c.module.values {
parent.module.values.entry(*k).or_insert(*v);
}
Ok(())
}
}

View File

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

View File

@@ -1 +0,0 @@
/target

View File

@@ -1,15 +0,0 @@
[package]
name = "conlang"
description = "The Conlang Programming Language"
keywords = ["interpreter", "programming", "language"]
authors.workspace = true
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
unicode-xid = "0.2.4"

View File

@@ -1,515 +0,0 @@
//! # The Abstract Syntax Tree
//! Contains definitions of AST Nodes, to be derived by a [parser](super::parser).
//!
//! # 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
use crate::common::*;
pub mod ast_impl;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Mutability {
#[default]
Not,
Mut,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Visibility {
#[default]
Private,
Public,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct File {
pub items: Vec<Item>,
}
// Metadata decorators
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Attrs {
pub meta: Vec<Meta>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Meta {
pub name: Identifier,
pub kind: MetaKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MetaKind {
Plain,
Equals(Literal),
Func(Vec<Literal>),
}
// Items
/// Stores an [ItemKind] and associated metadata
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Item {
pub extents: Span,
pub attrs: Attrs,
pub vis: Visibility,
pub kind: ItemKind,
}
/// Stores a concrete Item
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ItemKind {
// TODO: Import declaration ("use") item
// TODO: Trait declaration ("trait") item?
/// A [type alias](Alias)
Alias(Alias),
/// A [constant](Const)
Const(Const),
/// A [static](Static) variable
Static(Static),
/// A [module](Module)
Module(Module),
/// A [function definition](Function)
Function(Function),
/// A [structure](Struct)
Struct(Struct),
/// An [enumerated type](Enum)
Enum(Enum),
/// An [implementation](Impl)
Impl(Impl),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Alias {
pub to: Box<Ty>,
pub from: Option<Box<Ty>>,
}
/// Stores a `const` value
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Const {
pub name: Identifier,
pub ty: Box<Ty>,
pub init: Box<Expr>,
}
/// Stores 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>,
}
/// Stores a collection of [Items](Item)
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Module {
pub name: Identifier,
pub kind: ModuleKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ModuleKind {
Inline(File),
Outline,
}
/// Contains 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>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Param {
pub mutability: Mutability,
pub name: Identifier,
pub ty: Box<Ty>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Struct {
pub name: Identifier,
pub kind: StructKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StructKind {
Empty,
Tuple(Vec<Ty>),
Struct(Vec<StructMember>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StructMember {
pub vis: Visibility,
pub name: Identifier,
pub ty: Ty,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Enum {
pub name: Identifier,
pub kind: EnumKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EnumKind {
/// Represents an enum with no variants
NoVariants,
Variants(Vec<Variant>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Variant {
pub name: Identifier,
pub kind: VariantKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VariantKind {
Plain,
CLike(u128),
Tuple(Vec<Ty>),
Struct(Vec<StructMember>),
}
#[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> },
}
/// # Static Type Information
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ty {
pub extents: Span,
pub kind: TyKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TyKind {
Never,
Empty,
SelfTy,
Path(Path),
Tuple(TyTuple),
Ref(TyRef),
Fn(TyFn),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TyTuple {
pub types: Vec<Ty>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TyRef {
pub count: u16,
pub to: Path,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TyFn {
pub args: TyTuple,
pub rety: Option<Box<Ty>>,
}
// Path
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Path {
pub absolute: bool,
pub parts: Vec<PathPart>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PathPart {
SuperKw,
SelfKw,
Ident(Identifier),
}
// TODO: Capture token?
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Identifier(pub String);
/// Stores an abstract statement, and associated metadata
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Stmt {
pub extents: Span,
pub kind: StmtKind,
pub semi: Semi,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Semi {
Terminated,
Unterminated,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StmtKind {
Empty,
Local(Let),
Item(Box<Item>),
Expr(Box<Expr>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Let {
pub mutable: Mutability,
pub name: Identifier,
pub ty: Option<Box<Ty>>,
pub init: Option<Box<Expr>>,
}
/// Stores an abstract expression, and associated metadata
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Expr {
pub extents: Span,
pub kind: ExprKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExprKind {
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
Assign(Box<Assign>),
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
Binary(Binary),
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
Unary(Unary),
/// A [Member] access expression: [`Expr`] (`.` [`Expr`])+
Member(Member),
/// A [Call] expression, with arguments: a(foo, bar)
Call(Call),
/// 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),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Assign {
pub head: Expr,
pub op: AssignKind,
pub tail: Box<Expr>,
}
#[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,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Binary {
pub head: Box<Expr>,
pub tail: Vec<(BinaryKind, Expr)>,
}
#[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,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Unary {
pub ops: Vec<UnaryKind>,
pub tail: Box<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UnaryKind {
Deref,
Neg,
Not,
/// Unused
At,
/// Unused
Tilde,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Member {
pub head: Box<Expr>,
pub tail: Vec<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MemberKind {
Dot,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Call {
pub callee: Box<Expr>,
pub args: Vec<Tuple>,
}
/// Index operator: Member (`[` Expr `]`)*
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Index {
pub head: Box<Expr>,
pub indices: Vec<Indices>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Indices {
pub exprs: Vec<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
Bool(bool),
Char(char),
Int(u128),
String(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Array {
pub values: Vec<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArrayRep {
pub value: Box<Expr>,
pub repeat: Box<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddrOf {
pub count: usize,
pub mutable: Mutability,
pub expr: Box<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Block {
pub stmts: Vec<Stmt>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Group {
pub expr: Box<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tuple {
pub exprs: Vec<Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct While {
pub cond: Box<Expr>,
pub pass: Box<Block>,
pub fail: Else,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct If {
pub cond: Box<Expr>,
pub pass: Box<Block>,
pub fail: 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,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Else {
pub body: Option<Box<Expr>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Break {
pub body: Option<Box<Expr>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Return {
pub body: Option<Box<Expr>>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Continue;

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +0,0 @@
//! Conlang is an expression-based programming language with similarities to Rust and Python
#![warn(clippy::all)]
#![feature(decl_macro)]
pub mod common;
pub mod token;
pub mod ast;
pub mod lexer;
pub mod parser;
pub mod resolver;
pub mod interpreter;
#[cfg(test)]
mod tests;

File diff suppressed because it is too large Load Diff

View File

@@ -1,916 +0,0 @@
//! Extremely early WIP of a static type-checker/resolver
//!
//! This will hopefully become a fully fledged static resolution pass in the future
use std::collections::HashMap;
use scopeguard::Scoped;
pub mod scopeguard {
//! Implements a generic RAII scope-guard
use std::ops::{Deref, DerefMut};
pub trait Scoped: Sized {
fn frame(&mut self) -> Guard<Self> {
Guard::new(self)
}
///
fn enter_scope(&mut self);
fn exit_scope(&mut self);
}
pub struct Guard<'scope, T: Scoped> {
inner: &'scope mut T,
}
impl<'scope, T: Scoped> Guard<'scope, T> {
pub fn new(inner: &'scope mut T) -> Self {
inner.enter_scope();
Self { inner }
}
}
impl<'scope, T: Scoped> Deref for Guard<'scope, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<'scope, T: Scoped> DerefMut for Guard<'scope, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}
impl<'scope, T: Scoped> Drop for Guard<'scope, T> {
fn drop(&mut self) {
self.inner.exit_scope()
}
}
}
/// Prints like [println] if `debug_assertions` are enabled
macro debugln($($t:tt)*) {
if cfg!(debug_assertions) {
println!($($t)*);
}
}
macro debug($($t:tt)*) {
if cfg!(debug_assertions) {
print!($($t)*);
}
}
use ty::Type;
pub mod ty {
//! Describes the type of a [Variable](super::Variable)
use std::fmt::Display;
/// Describes the type of a [Variable](super::Variable)
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Type {
#[default]
Empty,
Int,
Bool,
Char,
String,
Float,
Fn {
args: Vec<Type>,
ret: Box<Type>,
},
Range(Box<Type>),
Tuple(Vec<Type>),
Never,
/// [Inferred](Type::Inferred) is for error messages. DO NOT CONSTRUCT
Inferred,
Generic(String),
/// A function with a single parameter of [Type::ManyInferred]
/// is assumed to always be correct.
ManyInferred,
}
impl Type {
fn is_empty(&self) -> bool {
self == &Type::Empty
}
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Empty => "Empty".fmt(f),
Type::Int => "integer".fmt(f),
Type::Bool => "bool".fmt(f),
Type::Char => "char".fmt(f),
Type::String => "String".fmt(f),
Type::Float => "float".fmt(f),
// TODO: clean this up
Type::Fn { args, ret } => {
"fn (".fmt(f)?;
let mut args = args.iter();
if let Some(arg) = args.next() {
arg.fmt(f)?;
}
for arg in args {
write!(f, ", {arg}")?
}
")".fmt(f)?;
if !ret.is_empty() {
write!(f, " -> {ret}")?;
}
Ok(())
}
Type::Range(t) => write!(f, "{t}..{t}"),
Type::Tuple(t) => {
"(".fmt(f)?;
for (idx, ty) in t.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?;
}
ty.fmt(f)?;
}
")".fmt(f)
}
Type::Never => "!".fmt(f),
Type::Inferred => "_".fmt(f),
Type::Generic(name) => write!(f, "<{name}>"),
Type::ManyInferred => "..".fmt(f),
}
}
}
}
/// Describes the life-cycle of a [Variable]: Whether it's bound, typed, or initialized
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Status {
#[default]
Bound,
Uninitialized(Type),
Initialized(Type),
}
impl Status {
/// Performs type-checking for a [Variable] assignment
pub fn assign(&mut self, ty: &Type) -> TyResult<()> {
match self {
// Variable uninitialized: initialize it
Status::Bound => {
*self = Status::Initialized(ty.clone());
Ok(())
}
// Typecheck ok! Reuse the allocation for t
Status::Uninitialized(t) if t == ty => {
*self = Status::Initialized(std::mem::take(t));
Ok(())
}
Status::Initialized(t) if t == ty => Ok(()),
// Typecheck not ok.
Status::Uninitialized(e) | Status::Initialized(e) => {
Err(Error::TypeMismatch { want: ty.clone(), got: e.clone() })
}
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Variable {
/// The unique, global index of this variable
pub index: usize,
/// The [Status] of this variable
pub status: Status,
/// The mutability qualifier of this variable
pub mutable: bool,
}
impl Variable {
/// Constructs a new variable with the provided index and mutability
pub fn new(index: usize, mutable: bool) -> Self {
Self { index, mutable, ..Default::default() }
}
/// Performs a type-checked assignment on self
pub fn assign(&mut self, name: &str, ty: &Type) -> TyResult<()> {
let Variable { index, status, mutable } = self;
debug!("Typecheck for {name} #{index}: ");
let out = match (status, mutable) {
// Variable uninitialized: initialize it
(Status::Bound, _) => {
self.status = Status::Initialized(ty.clone());
Ok(())
}
// Typecheck ok! Reuse the allocation for t
(Status::Uninitialized(t), _) if t == ty => {
self.status = Status::Initialized(std::mem::take(t));
Ok(())
}
// Reassignment of mutable variable is ok
(Status::Initialized(t), true) if t == ty => Ok(()),
// Reassignment of immutable variable is not ok
(Status::Initialized(_), false) => Err(Error::ImmutableAssign(name.into(), *index)),
// Typecheck not ok.
(Status::Uninitialized(e) | Status::Initialized(e), _) => {
Err(Error::TypeMismatch { want: ty.clone(), got: e.clone() })
}
};
match &out {
Ok(_) => debugln!("Ok! ({ty})"),
Err(e) => debugln!("Error: {e:?}"),
}
out
}
/// Performs the type-checking for a modifying assignment
pub fn modify_assign(&self, name: &str, ty: &Type) -> TyResult<()> {
let Variable { index, status, mutable } = &self;
match (status, mutable) {
(Status::Initialized(t), true) if t == ty => Ok(()),
(Status::Initialized(t), true) => {
Err(Error::TypeMismatch { want: t.clone(), got: ty.clone() })
}
(Status::Initialized(_), false) => Err(Error::ImmutableAssign(name.into(), *index)),
(..) => Err(Error::Uninitialized(name.into(), *index)),
}
}
}
/*
THE BIG IDEA:
- Each `let` statement binds a *different* variable.
- Shadowing is a FEATURE
- Traversing the tree before program launch allows the Resolver to assign
an index to each variable usage in the scope-tree
- These indices allow constant-time variable lookup in the interpreter!!!
- The added type-checking means fewer type errors!
REQUIREMENTS FOR FULL TYPE-CHECKING:
- Meaningful type expressions in function declarations
NECESSARY CONSIDERATIONS:
- Variable binding happens AFTER the initialization expression is run.
- If a variable is *entirely* unbound before being referenced,
it'll still error.
- This is *intentional*, and ALLOWS shadowing previous variables.
- In my experience, this is almost NEVER an error :P
*/
#[derive(Clone, Debug, Default)]
pub struct Scope {
/// A monotonically increasing counter of variable declarations
count: usize,
/// A dictionary keeping track of type and lifetime information
vars: HashMap<String, Variable>,
}
impl Scope {
/// Bind a [Variable] in Scope
pub fn insert(&mut self, name: &str, index: usize, mutable: bool) {
self.count += 1;
self.vars
.insert(name.to_string(), Variable::new(index, mutable));
}
/// Returns a reference to a [Variable], if `name` is bound
pub fn get(&self, name: &str) -> Option<&Variable> {
self.vars.get(name)
}
/// Returns a mutable reference to a [Variable], if `name` is bound
pub fn get_mut(&mut self, name: &str) -> Option<&mut Variable> {
self.vars.get_mut(name)
}
}
/// Implements a dynamically scoped namespace
#[derive(Clone, Debug, Default)]
pub struct Module {
modules: HashMap<String, Module>,
vars: HashMap<String, Variable>,
}
impl Module {
pub fn insert_var(&mut self, name: &str, index: usize, mutable: bool) -> TyResult<()> {
if self
.vars
.insert(name.into(), Variable::new(index, mutable))
.is_some()
{
Err(Error::NonUniqueInModule(name.into()))?;
}
Ok(())
}
pub fn insert_module(&mut self, name: String, module: Module) -> TyResult<()> {
if self.modules.insert(name.clone(), module).is_some() {
Err(Error::NonUniqueInModule(name + "(module)"))?
}
Ok(())
}
/// Returns a reference to a [Variable] in this Module, if `name` is bound
pub fn get(&self, name: &str) -> Option<&Variable> {
self.vars.get(name)
}
/// Returns a mutable reference to a [Variable] in this Module, if `name` is bound
pub fn get_mut(&mut self, name: &str) -> Option<&mut Variable> {
self.vars.get_mut(name)
}
pub fn resolve_get(&self, name: &str, path: &[String]) -> Option<&Variable> {
if path.is_empty() {
return self.get(name);
}
let module = self.modules.get(&path[0])?;
module
.resolve_get(name, &path[1..])
.or_else(|| self.get(name))
}
// Returns a reference to the module at a specified path
pub fn resolve(&self, path: &[String]) -> TyResult<&Module> {
if path.is_empty() {
return Ok(self);
}
let module = self
.modules
.get(&path[0])
.ok_or_else(|| Error::Unbound(path[0].clone()))?;
module.resolve(&path[1..])
}
/// Returns a mutable reference to a Module if one is bound
pub fn resolve_mut(&mut self, path: &[String]) -> TyResult<&mut Module> {
if path.is_empty() {
return Ok(self);
}
let module = self
.modules
.get_mut(&path[0])
.ok_or_else(|| Error::Unbound(path[0].clone()))?;
module.resolve_mut(&path[1..])
}
}
#[derive(Clone, Debug)]
pub struct Resolver {
/// A monotonically increasing counter of variable declarations
count: usize,
/// A stack of nested scopes *inside* a function
scopes: Vec<Scope>,
/// A stack of nested scopes *outside* a function
// TODO: Record the name of the module, and keep a stack of the current module
// for name resolution
modules: Module,
/// Describes the current path
module: Vec<String>,
/// A stack of types
types: Vec<Type>,
}
impl Scoped for Resolver {
fn enter_scope(&mut self) {
self.enter_scope();
}
fn exit_scope(&mut self) {
self.exit_scope();
}
}
impl Default for Resolver {
fn default() -> Self {
let mut new = Self {
count: Default::default(),
scopes: vec![Default::default()],
modules: Default::default(),
module: Default::default(),
types: Default::default(),
};
new.register_builtin("print", &[], &[Type::ManyInferred], Type::Empty)
.expect("print should not be bound in new Resolver");
new
}
}
impl Resolver {
pub fn new() -> Self {
Default::default()
}
/// Register a built-in function into the top-level module
pub fn register_builtin(
&mut self,
name: &str,
path: &[String],
args: &[Type],
ret: Type,
) -> TyResult<()> {
let module = self.modules.resolve_mut(path)?;
module.vars.insert(
name.into(),
Variable {
index: 0,
status: Status::Initialized(Type::Fn { args: args.into(), ret: ret.into() }),
mutable: false,
},
);
Ok(())
}
/// Enters a Module Scope
pub fn enter_module(&mut self, name: &str) -> TyResult<()> {
let module = self.modules.resolve_mut(&self.module)?;
module.insert_module(name.into(), Default::default())?;
self.module.push(name.into());
Ok(())
}
/// Exits a Module Scope
pub fn exit_module(&mut self) -> Option<String> {
// Modules stay registered
self.module.pop()
}
/// Enters a Block Scope
pub fn enter_scope(&mut self) {
self.scopes.push(Default::default());
}
/// Exits a Block Scope, returning the value
pub fn exit_scope(&mut self) -> Option<usize> {
self.scopes.pop().map(|scope| scope.count)
}
/// Resolves a name to a [Variable]
pub fn get(&self, name: &str) -> TyResult<&Variable> {
if let Some(var) = self.scopes.iter().rev().find_map(|s| s.get(name)) {
return Ok(var);
}
self.modules
.resolve_get(name, &self.module)
.ok_or_else(|| Error::Unbound(name.into()))
}
/// Mutably resolves a name to a [Variable]
pub fn get_mut(&mut self, name: &str) -> TyResult<&mut Variable> {
if let Some(var) = self.scopes.iter_mut().rev().find_map(|s| s.get_mut(name)) {
return Ok(var);
}
self.modules
.resolve_mut(&self.module)?
.get_mut(name)
.ok_or_else(|| Error::Unbound(name.into()))
}
/// Binds a name in the current lexical scope
pub fn insert_scope(&mut self, name: &str, mutable: bool) -> TyResult<usize> {
self.count += 1;
self.scopes
.last_mut()
.ok_or_else(|| panic!("Stack underflow in resolver?"))?
.insert(name, self.count, mutable);
Ok(self.count)
}
/// Binds a name in the current module
pub fn insert_module(&mut self, name: &str, mutable: bool) -> TyResult<usize> {
self.count += 1;
self.modules
.resolve_mut(&self.module)?
.insert_var(name, self.count, mutable)?;
Ok(self.count)
}
/// Performs scoped type-checking of variables
pub fn assign(&mut self, name: &str, ty: &Type) -> TyResult<()> {
self.get_mut(name)?.assign(name, ty)
}
}
#[allow(unused_macros)]
/// Manages a module scope
/// ```rust,ignore
/// macro module(self, name: &str, inner: {...}) -> Result<_, Error>
/// ```
macro module($self:ident, $name:tt, $inner:tt) {{
$self.enter_module($name)?;
#[allow(clippy::redundant_closure_call)]
let scope = (|| $inner)(); // This is pretty gross, but hey, try {} syntax is unstable too
$self.exit_module();
scope
}}
impl Resolver {
pub fn visit_empty(&mut self) -> TyResult<()> {
debugln!("Got Empty");
self.types.push(Type::Empty);
Ok(())
}
}
pub trait Resolve {
/// Performs variable resolution on self, and returns the type of self.
///
/// For expressions, this is the type of the expression.
///
/// For declarations, this is Empty.
fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
Ok(Type::Empty)
}
}
mod ast1 {
// #![allow(deprecated)]
// use super::*;
// use crate::ast::preamble::*;
// impl Resolve for Start {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Self(program) = self;
// program.resolve(resolver)
// }
// }
// impl Resolve for Program {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Self(module) = self;
// for decl in module {
// decl.resolve(resolver)?;
// }
// // TODO: record the number of module-level assignments into the AST
// Ok(Type::Empty)
// }
// }
// impl Resolve for Stmt {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// match self {
// Stmt::Let(value) => value.resolve(resolver),
// Stmt::Fn(value) => value.resolve(resolver),
// Stmt::Expr(value) => value.resolve(resolver),
// }
// }
// }
// impl Resolve for Let {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Let { name: Name { symbol: Identifier { name, index }, mutable, ty: _ }, init } =
// self;
// debugln!("ty> let {name} ...");
// if let Some(init) = init {
// let ty = init.resolve(resolver)?;
// *index = Some(resolver.insert_scope(name, *mutable)?);
// resolver.get_mut(name)?.assign(name, &ty)?;
// } else {
// resolver.insert_scope(name, *mutable)?;
// }
// Ok(Type::Empty)
// }
// }
// impl Resolve for FnDecl {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let FnDecl { name: Name { symbol: Identifier { name, index }, .. }, args, body } =
// self; debugln!("ty> fn {name} ...");
// // register the name at module scope
// *index = Some(resolver.insert_module(name, false)?);
// // create a new lexical scope
// let scopes = std::mem::take(&mut resolver.scopes);
// // type-check the function body
// let out = {
// let mut resolver = resolver.frame();
// let mut evaluated_args = vec![];
// for arg in args {
// evaluated_args.push(arg.resolve(&mut resolver)?)
// }
// let fn_decl = Type::Fn { args: evaluated_args.clone(), ret: Box::new(Type::Empty)
// }; resolver.get_mut(name)?.assign(name, &fn_decl)?;
// module!(resolver, name, { body.resolve(&mut resolver) })
// };
// let _ = std::mem::replace(&mut resolver.scopes, scopes);
// out
// }
// }
// impl Resolve for Name {
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
// Ok(Type::Empty)
// }
// }
// impl Resolve for Block {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Block { let_count: _, statements, expr } = self;
// let mut resolver = resolver.frame();
// for stmt in statements {
// stmt.resolve(&mut resolver)?;
// }
// expr.resolve(&mut resolver)
// }
// }
// impl Resolve for Expr {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Expr(expr) = self;
// expr.resolve(resolver)
// }
// }
// impl Resolve for Operation {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// match self {
// Operation::Assign(value) => value.resolve(resolver),
// Operation::Binary(value) => value.resolve(resolver),
// Operation::Unary(value) => value.resolve(resolver),
// Operation::Call(value) => value.resolve(resolver),
// }
// }
// }
// impl Resolve for Assign {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Assign { target, operator, init } = self;
// // Evaluate the initializer expression
// let ty = init.resolve(resolver)?;
// // Resolve the variable
// match (operator, resolver.get_mut(&target.name)?) {
// (
// operator::Assign::Assign,
// Variable { status: Status::Initialized(_), mutable: false, index },
// ) => Err(Error::ImmutableAssign(target.name.clone(), *index)),
// // TODO: make typing more expressive for modifying assignment
// (_, variable) => variable
// .modify_assign(&target.name, &ty)
// .map(|_| Type::Empty),
// }
// }
// }
// impl Resolve for Binary {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Binary { first, other } = self;
// let mut first = first.resolve(resolver)?;
// for (op, other) in other {
// let other = other.resolve(resolver)?;
// first = resolver.resolve_binary_operator(first, other, op)?;
// }
// Ok(first)
// }
// }
// impl Resolve for Unary {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Unary { operators, operand } = self;
// let mut operand = operand.resolve(resolver)?;
// for op in operators {
// operand = resolver.resolve_unary_operator(operand, op)?;
// }
// Ok(operand)
// }
// }
// /// Resolve [operator]s
// impl Resolver {
// fn resolve_binary_operator(
// &mut self,
// first: Type,
// other: Type,
// op: &operator::Binary,
// ) -> TyResult<Type> {
// // TODO: check type compatibility for binary ops
// // TODO: desugar binary ops into function calls, when member functions are a thing
// eprintln!("Resolve binary operators {first} {op:?} {other}");
// if first != other {
// Err(Error::TypeMismatch { want: first, got: other })
// } else {
// Ok(first)
// }
// }
// fn resolve_unary_operator(
// &mut self,
// operand: Type,
// op: &operator::Unary,
// ) -> TyResult<Type> {
// // TODO: Allow more expressive unary operator type conversions
// todo!("Resolve unary operators {op:?} {operand}")
// }
// }
// impl Resolve for Call {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// match self {
// Call::FnCall(value) => value.resolve(resolver),
// Call::Primary(value) => value.resolve(resolver),
// }
// }
// }
// impl Resolve for FnCall {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let FnCall { callee, args } = self;
// let mut callee = callee.resolve(resolver)?;
// for argset in args {
// // arguments should always be a tuple here
// let arguments = argset.resolve(resolver)?;
// let Type::Tuple(arguments) = arguments else {
// Err(Error::TypeMismatch {
// want: Type::Tuple(vec![Type::ManyInferred]),
// got: arguments,
// })?
// };
// // Verify that the callee is a function, and the arguments match.
// // We need the arguments
// let Type::Fn { args, ret } = callee else {
// return Err(Error::TypeMismatch {
// want: Type::Fn { args: arguments, ret: Type::Inferred.into() },
// got: callee,
// })?;
// };
// for (want, got) in args.iter().zip(&arguments) {
// // TODO: verify generics
// if let Type::Generic(_) = want {
// continue;
// }
// if want != got {
// return Err(Error::TypeMismatch {
// want: Type::Fn { args: arguments, ret: Type::Inferred.into() },
// got: Type::Fn { args, ret },
// })?;
// }
// }
// callee = *ret;
// }
// Ok(callee)
// }
// }
// impl Resolve for Primary {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// match self {
// Primary::Identifier(value) => value.resolve(resolver),
// Primary::Literal(value) => value.resolve(resolver),
// Primary::Block(value) => value.resolve(resolver),
// Primary::Group(value) => value.resolve(resolver),
// Primary::Branch(value) => value.resolve(resolver),
// }
// }
// }
// impl Resolve for Group {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// match self {
// Group::Tuple(tuple) => tuple.resolve(resolver),
// Group::Single(expr) => expr.resolve(resolver),
// Group::Empty => Ok(Type::Empty),
// }
// }
// }
// impl Resolve for Tuple {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Tuple { elements } = self;
// let mut types = vec![];
// for expr in elements.iter_mut() {
// types.push(expr.resolve(resolver)?);
// }
// Ok(Type::Tuple(types))
// }
// }
// impl Resolve for Identifier {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Identifier { name, index: id_index } = self;
// let Variable { index, status, .. } = resolver.get(name)?;
// *id_index = Some(*index);
// let ty = match status {
// Status::Initialized(t) => t,
// _ => Err(Error::Uninitialized(name.to_owned(), *index))?,
// };
// debugln!("ty> Resolved {} #{index}: {ty}", name);
// Ok(ty.to_owned())
// }
// }
// impl Resolve for Literal {
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
// Ok(match self {
// Literal::String(_) => Type::String,
// Literal::Char(_) => Type::Char,
// Literal::Bool(_) => Type::Bool,
// Literal::Float(_) => Type::Float,
// Literal::Int(_) => Type::Int,
// })
// }
// }
// impl Resolve for Flow {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// // TODO: Finish this
// match self {
// Flow::While(value) => value.resolve(resolver),
// Flow::If(value) => value.resolve(resolver),
// Flow::For(value) => value.resolve(resolver),
// Flow::Continue(value) => value.resolve(resolver),
// Flow::Return(value) => value.resolve(resolver),
// Flow::Break(value) => value.resolve(resolver),
// }
// }
// }
// impl Resolve for While {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// // TODO: Finish this
// // Visit else first, save that to a break-pattern stack in the Resolver,
// // and check it inside Break::resolve()
// let While { cond, body, else_ } = self;
// cond.resolve(resolver)?; // must be Type::Bool
// body.resolve(resolver)?; // discard
// else_.resolve(resolver) // compare with returns inside body
// }
// }
// impl Resolve for If {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let If { cond, body, else_ } = self;
// let cond = cond.resolve(resolver)?;
// if Type::Bool != cond {
// return Err(Error::TypeMismatch { want: Type::Bool, got: cond });
// }
// let body_ty = body.resolve(resolver)?;
// let else_ty = else_.resolve(resolver)?;
// if body_ty == else_ty {
// Ok(body_ty)
// } else {
// Err(Error::TypeMismatch { want: body_ty, got: else_ty })
// }
// }
// }
// impl Resolve for For {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let For { var: Identifier { name, index }, iter, body, else_ } = self;
// debugln!("> for {name} in ...");
// // Visit the iter expression and get its type
// let range = iter.resolve(resolver)?;
// let ty = match range {
// Type::Range(t) => t,
// got => Err(Error::TypeMismatch { want: Type::Range(Type::Inferred.into()), got
// })?, };
// let body_ty = {
// let mut resolver = resolver.frame();
// // bind the variable in the loop scope
// *index = Some(resolver.insert_scope(name, false)?);
// resolver.get_mut(name)?.assign(name, &ty)?;
// body.resolve(&mut resolver)
// }?;
// // visit the else block
// let else_ty = else_.resolve(resolver)?;
// if body_ty != else_ty {
// Err(Error::TypeMismatch { want: body_ty, got: else_ty })
// } else {
// Ok(body_ty)
// }
// }
// }
// impl Resolve for Else {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// let Else { expr } = self;
// expr.resolve(resolver)
// }
// }
// impl Resolve for Continue {
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
// // TODO: Finish control flow
// Ok(Type::Never)
// }
// }
// impl Resolve for Break {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// // TODO: Finish control flow
// let Break { expr } = self;
// expr.resolve(resolver)
// }
// }
// impl Resolve for Return {
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
// // TODO: Finish control flow
// let Return { expr } = self;
// expr.resolve(resolver)
// }
// }
}
mod ast {
#![allow(unused_imports)]
use crate::ast::*;
}
// heakc yea man, generics
impl<T: Resolve> Resolve for Option<T> {
fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
match self {
Some(t) => t.resolve(resolver),
None => Ok(Type::Empty),
}
}
}
impl<T: Resolve> Resolve for Box<T> {
fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
self.as_mut().resolve(resolver)
}
}
use error::{Error, TyResult};
pub mod error {
use super::Type;
use std::fmt::Display;
pub type TyResult<T> = Result<T, Error>;
impl std::error::Error for Error {}
#[derive(Clone, Debug)]
pub enum Error {
StackUnderflow,
// types
TypeMismatch { want: Type, got: Type },
// modules
NonUniqueInModule(String),
// lifetimes
Uninitialized(String, usize),
ImmutableAssign(String, usize),
Unbound(String),
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::StackUnderflow => "Stack underflow in Resolver".fmt(f),
Error::TypeMismatch { want, got } => {
write!(f, "Type error: {want} != {got}")
}
Error::ImmutableAssign(name, index) => {
write!(f, "Cannot mutate immutable variable {name}(#{index})")
}
Error::Uninitialized(name, index) => {
write!(f, "{name}(#{index}) was accessed before initialization")
}
Error::Unbound(name) => write!(f, "{name} not bound before use."),
Error::NonUniqueInModule(name) => {
write!(f, "Name {name} not unique at module scope!")
}
}
}
}
}

View File

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

View File

@@ -1,57 +0,0 @@
//! # Token
//!
//! Stores a component of a file as a [Type], some [Data], and a line and column number
pub mod token_data;
pub mod token_type;
pub mod preamble {
//! Common imports for working with [tokens](super)
pub use super::{
token_data::Data,
token_type::{Keyword, Type},
Token,
};
}
use token_data::Data;
use token_type::Type;
/// Contains a single unit of lexical information,
/// and an optional bit of [Data]
#[derive(Clone, Debug, PartialEq)]
pub struct Token {
ty: Type,
data: Data,
line: u32,
col: u32,
}
impl Token {
/// Creates a new [Token] out of a [Type], [Data], line, and column.
pub fn new(ty: Type, data: impl Into<Data>, line: u32, col: u32) -> Self {
Self { ty, data: data.into(), line, col }
}
/// Casts this token to a new [Type]
pub fn cast(self, ty: Type) -> Self {
Self { ty, ..self }
}
/// Returns the [Type] of this token
pub fn ty(&self) -> Type {
self.ty
}
/// Returns a reference to this token's [Data]
pub fn data(&self) -> &Data {
&self.data
}
/// Converts this token into its inner [Data]
pub fn into_data(self) -> Data {
self.data
}
/// Returns the line where this token originated
pub fn line(&self) -> u32 {
self.line
}
/// Returns the column where this token originated
pub fn col(&self) -> u32 {
self.col
}
}

View File

@@ -1,239 +0,0 @@
//! Stores a [Token's](super::Token) lexical information
use std::{fmt::Display, str::FromStr};
/// Stores a [Token's](super::Token) lexical information
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Type {
// Invalid syntax
Invalid,
// Any kind of comment
Comment,
// Any identifier
Identifier,
Keyword(Keyword),
// Literals
Integer,
Float,
String,
Character,
// Delimiters and punctuation
LCurly, // {
RCurly, // }
LBrack, // [
RBrack, // ]
LParen, // (
RParen, // )
Amp, // &
AmpAmp, // &&
AmpEq, // &=
Arrow, // ->
At, // @
Backslash, // \
Bang, // !
BangBang, // !!
BangEq, // !=
Bar, // |
BarBar, // ||
BarEq, // |=
Colon, // :
ColonColon, // ::
Comma, // ,
Dot, // .
DotDot, // ..
DotDotEq, // ..=
Eq, // =
EqEq, // ==
FatArrow, // =>
Grave, // `
Gt, // >
GtEq, // >=
GtGt, // >>
GtGtEq, // >>=
Hash, // #
HashBang, // #!
Lt, // <
LtEq, // <=
LtLt, // <<
LtLtEq, // <<=
Minus, // -
MinusEq, // -=
Plus, // +
PlusEq, // +=
Question, // ?
Rem, // %
RemEq, // %=
Semi, // ;
Slash, // /
SlashEq, // /=
Star, // *
StarEq, // *=
Tilde, // ~
Xor, // ^
XorEq, // ^=
XorXor, // ^^
}
/// Represents a reserved word.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum 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,
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Invalid => "invalid".fmt(f),
Type::Comment => "comment".fmt(f),
Type::Identifier => "identifier".fmt(f),
Type::Keyword(k) => k.fmt(f),
Type::Integer => "integer literal".fmt(f),
Type::Float => "float literal".fmt(f),
Type::String => "string literal".fmt(f),
Type::Character => "char literal".fmt(f),
Type::LCurly => "left curly".fmt(f),
Type::RCurly => "right curly".fmt(f),
Type::LBrack => "left brack".fmt(f),
Type::RBrack => "right brack".fmt(f),
Type::LParen => "left paren".fmt(f),
Type::RParen => "right paren".fmt(f),
Type::Amp => "and".fmt(f),
Type::AmpAmp => "and-and".fmt(f),
Type::AmpEq => "and-assign".fmt(f),
Type::Arrow => "arrow".fmt(f),
Type::At => "at".fmt(f),
Type::Backslash => "backslash".fmt(f),
Type::Bang => "bang".fmt(f),
Type::BangBang => "not-not".fmt(f),
Type::BangEq => "not equal to".fmt(f),
Type::Bar => "or".fmt(f),
Type::BarBar => "or-or".fmt(f),
Type::BarEq => "or-assign".fmt(f),
Type::Colon => "colon".fmt(f),
Type::ColonColon => "path separator".fmt(f),
Type::Comma => "comma".fmt(f),
Type::Dot => "dot".fmt(f),
Type::DotDot => "exclusive range".fmt(f),
Type::DotDotEq => "inclusive range".fmt(f),
Type::Eq => "assign".fmt(f),
Type::EqEq => "equal to".fmt(f),
Type::FatArrow => "fat arrow".fmt(f),
Type::Grave => "grave".fmt(f),
Type::Gt => "greater than".fmt(f),
Type::GtEq => "greater than or equal to".fmt(f),
Type::GtGt => "shift right".fmt(f),
Type::GtGtEq => "shift right-assign".fmt(f),
Type::Hash => "hash".fmt(f),
Type::HashBang => "shebang".fmt(f),
Type::Lt => "less than".fmt(f),
Type::LtEq => "less than or equal to".fmt(f),
Type::LtLt => "shift left".fmt(f),
Type::LtLtEq => "shift left-assign".fmt(f),
Type::Minus => "sub".fmt(f),
Type::MinusEq => "sub-assign".fmt(f),
Type::Plus => "add".fmt(f),
Type::PlusEq => "add-assign".fmt(f),
Type::Question => "huh?".fmt(f),
Type::Rem => "rem".fmt(f),
Type::RemEq => "rem-assign".fmt(f),
Type::Semi => "ignore".fmt(f),
Type::Slash => "div".fmt(f),
Type::SlashEq => "div-assign".fmt(f),
Type::Star => "star".fmt(f),
Type::StarEq => "star-assign".fmt(f),
Type::Tilde => "tilde".fmt(f),
Type::Xor => "xor".fmt(f),
Type::XorEq => "xor-assign".fmt(f),
Type::XorXor => "cat-ears".fmt(f),
}
}
}
impl Display for Keyword {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Break => "break".fmt(f),
Self::Cl => "cl".fmt(f),
Self::Const => "const".fmt(f),
Self::Continue => "continue".fmt(f),
Self::Else => "else".fmt(f),
Self::Enum => "enum".fmt(f),
Self::False => "false".fmt(f),
Self::For => "for".fmt(f),
Self::Fn => "fn".fmt(f),
Self::If => "if".fmt(f),
Self::Impl => "impl".fmt(f),
Self::In => "in".fmt(f),
Self::Let => "let".fmt(f),
Self::Mod => "mod".fmt(f),
Self::Mut => "mut".fmt(f),
Self::Pub => "pub".fmt(f),
Self::Return => "return".fmt(f),
Self::SelfKw => "self".fmt(f),
Self::SelfTy => "Self".fmt(f),
Self::Static => "static".fmt(f),
Self::Struct => "struct".fmt(f),
Self::Super => "super".fmt(f),
Self::True => "true".fmt(f),
Self::Type => "type".fmt(f),
Self::While => "while".fmt(f),
}
}
}
impl FromStr for Keyword {
/// [FromStr] can only fail when an identifier isn't a keyword
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"break" => Self::Break,
"cl" => Self::Cl,
"const" => Self::Const,
"continue" => Self::Continue,
"else" => Self::Else,
"enum" => Self::Enum,
"false" => Self::False,
"for" => Self::For,
"fn" => Self::Fn,
"if" => Self::If,
"impl" => Self::Impl,
"in" => Self::In,
"let" => Self::Let,
"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,
"while" => Self::While,
_ => Err(())?,
})
}
}

View File

@@ -1,8 +1,7 @@
# Conlang: Expression-Oriented Programming Language
This project began out of a desire to merge Rust-style control flow expressions
with Python's fun for-else/while-else syntax. I fully intend to devote my spare time
to conlang for the forseeable future, and I livestream development on Twitch for one
Friday each month.
to conlang for the forseeable future.
## Immediate Goals:
- [x] Decide on a minimal set of keywords and operators to support
@@ -20,10 +19,11 @@ Friday each month.
## Short Goals:
- [x] `for` loops and `while` loops can be used on the trailing side of an assignment
- [x] Tree-walk interpreter for prototyping and debugging
- [ ] Data structures and sum-type enums
- [x] Data structures and sum-type enums
- [ ] Expression type-checker
- [ ] Trait/Interface system
- [ ] Pattern destructuring, to take advantage of sum-type enums
- [ ] Three-address bytecode VM for standard library development
- [ ] Trait/Interface system
## Long Goals:
- [ ] Minimize the number of kinds of statements

11
repline/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "repline"
repository.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
crossterm = { version = "0.27.0", default-features = false }

324
repline/src/editor.rs Normal file
View File

@@ -0,0 +1,324 @@
//! The [Editor] is a multi-line buffer of [`char`]s which operates on an ANSI-compatible terminal.
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(())
}
/// A multi-line editor which operates on an un-cleared ANSI terminal.
#[derive(Clone, 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> {
/// Constructs a new Editor with the provided prompt color, begin prompt, and again prompt.
pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
Self { head: Default::default(), tail: Default::default(), color, begin, again }
}
/// Returns an iterator over characters in the editor.
pub fn iter(&self) -> impl Iterator<Item = &char> {
let Self { head, tail, .. } = self;
head.iter().chain(tail.iter())
}
/// Moves up to the first line of the editor, and clears the screen.
///
/// This assumes the screen hasn't moved since the last draw.
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(())
}
/// Redraws the entire editor
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(())
}
/// Prints a context-sensitive prompt (either `begin` if this is the first line,
/// or `again` for subsequent lines)
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(())
}
/// Prints the characters before the cursor on the current line.
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(())
}
/// Prints the characters after the cursor on the current line.
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(())
}
/// Writes a character at the cursor, shifting the text around as necessary.
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(())
}
/// Erases a character at the cursor, shifting the text around as necessary.
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)
}
/// Writes characters into the editor at the location of the cursor.
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(())
}
/// Sets the editor to the contents of a string, placing the cursor at the end.
pub fn restore(&mut self, s: &str) {
self.clear();
self.head.extend(s.chars())
}
/// Clears the editor, removing all characters.
pub fn clear(&mut self) {
self.head.clear();
self.tail.clear();
}
/// Pops the character after the cursor, redrawing if necessary
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)
}
/// Erases a word from the buffer, where a word is any non-whitespace characters
/// preceded by a single whitespace character
pub fn erase_word<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
while self.pop(w)?.filter(|c| !c.is_whitespace()).is_some() {}
Ok(())
}
/// Returns the number of characters in the buffer
pub fn len(&self) -> usize {
self.head.len() + self.tail.len()
}
/// Returns true if the buffer is empty.
pub fn is_empty(&self) -> bool {
self.head.is_empty() && self.tail.is_empty()
}
/// Returns true if the buffer ends with a given pattern
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(())
}
/// Moves the cursor 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)?,
}
}
}
/// Moves the cursor 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;
for c in self.iter() {
f.write_char(*c)?;
}
Ok(())
}
}

42
repline/src/error.rs Normal file
View File

@@ -0,0 +1,42 @@
use crate::iter::chars::BadUnicode;
/// 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, "\\u{{{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)
}
}
impl From<BadUnicode> for Error {
fn from(value: BadUnicode) -> Self {
let BadUnicode(code) = value;
Self::BadUnicode(code)
}
}

68
repline/src/iter.rs Normal file
View File

@@ -0,0 +1,68 @@
//! Shmancy iterator adapters
pub use chars::Chars;
pub use flatten::Flatten;
pub mod chars {
//! Converts an <code>[Iterator]<Item = [u8]></code> into an
//! <code>[Iterator]<Item = [Result]<[char], [BadUnicode]>></code>
/// Invalid unicode codepoint found when iterating over [Chars]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BadUnicode(pub u32);
impl std::error::Error for BadUnicode {}
impl std::fmt::Display for BadUnicode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(code) = self;
write!(f, "Bad unicode: {code}")
}
}
/// 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>> Iterator for Chars<I> {
type Item = Result<char, BadUnicode>;
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(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`
#[derive(Clone, Debug)]
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()?
}
}
}

13
repline/src/lib.rs Normal file
View File

@@ -0,0 +1,13 @@
//! A small pseudo-multiline editing library
mod editor;
mod iter;
mod raw;
pub mod error;
pub mod prebaked;
pub mod repline;
pub use error::Error;
pub use prebaked::{read_and, Response};
pub use repline::Repline;

56
repline/src/prebaked.rs Normal file
View File

@@ -0,0 +1,56 @@
//! Here's a menu I prepared earlier!
//!
//! Constructs a [Repline] and repeatedly runs the provided closure on the input strings,
//! obeying the closure's [Response].
use std::error::Error;
use crate::{error::Error as RlError, repline::Repline};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
/// Control codes for the [prebaked menu](read_and)
pub enum Response {
/// Accept the line, and save it to history
Accept,
/// Reject the line, and clear the buffer
Deny,
/// End the loop
Break,
/// Gather more input and try again
Continue,
}
/// Implements a basic menu loop using an embedded [Repline].
///
/// Repeatedly runs the provided closure on the input strings,
/// obeying the closure's [Response].
///
/// Captures and displays all user [Error]s.
///
/// # Keybinds
/// - `Ctrl+C` exits the loop
/// - `Ctrl+D` clears the input, but *runs the closure* with the old input
pub fn read_and<F>(color: &str, begin: &str, again: &str, mut f: F) -> Result<(), RlError>
where F: FnMut(&str) -> Result<Response, 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,
Ok(Response::Continue) => continue,
Err(e) => print!("\x1b[40G\x1b[A\x1bJ\x1b[91m{e}\x1b[0m\x1b[B"),
}
}
Ok(())
}

21
repline/src/raw.rs Normal file
View File

@@ -0,0 +1,21 @@
//! 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");
}
}

175
repline/src/repline.rs Normal file
View File

@@ -0,0 +1,175 @@
//! Prompts the user, reads the lines. Not much more to it than that.
//!
//! This module is in charge of parsing keyboard input and interpreting it for the line editor.
use crate::{editor::Editor, error::*, iter::*, raw::raw};
use std::{
collections::VecDeque,
io::{stdout, Bytes, Read, Result, Write},
};
/// Prompts the user, reads the lines. Not much more to it than that.
#[derive(Debug)]
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> 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)
}
}
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
}
/// Append line to history and clear it
pub fn accept(&mut self) {
self.history_append(self.ed.to_string());
self.ed.clear();
self.hindex = self.history.len();
}
/// Clear the line
pub fn deny(&mut self) {
self.ed.clear()
}
/// 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.
'\x03' => {
drop(_make_raw);
writeln!(stdout)?;
return Err(Error::CtrlC(self.ed.to_string()));
}
// Ctrl+D: End of Transmission. Ends the current line.
'\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());
}
// Ctrl+Backspace in my terminal
'\x17' => {
self.ed.erase_word(stdout)?;
}
// 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) {
self.ed.extend(c.escape_debug(), stdout)?;
}
}
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(other.escape_debug(), 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());
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)?? {
let _ = self.ed.delete(w);
}
}
other => {
if cfg!(debug_assertions) {
self.ed.extend(other.escape_debug(), w)?;
}
}
}
Ok(())
}
/// Restores the currently selected history
fn restore_history<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
let Self { history, hindex, ed, .. } = self;
ed.undraw(w)?;
ed.clear();
ed.print_head(w)?;
if let Some(history) = history.get(*hindex) {
ed.extend(history.chars(), w)?
}
Ok(())
}
/// Append line to history
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();
}
}
}

29
sample-code/fib.cl Normal file
View File

@@ -0,0 +1,29 @@
// Calculate Fibonacci numbers
fn main() {
for num in 0..=30 {
print("fib(", num, ") = ", fib_iterative(num))
}
for num in 0..=30 {
print("fib(", num, ") = ", fib(num))
}
}
/// The classic recursive definition of fib()
fn fib(a: i64) -> i64 {
if a > 1 {
fib(a - 1) + fib(a - 2)
} else a
}
/// The classic iterative algorithm for fib()
fn fib_iterative(n: i64) -> i64 {
let mut a = 0;
let mut b = 1;
let mut c = 1;
for _ in 0..n {
a = b;
b = c;
c = a + b;
} else a
}

View File

@@ -1,11 +1,11 @@
// FizzBuzz, using the unstable variadic-`print` builtin
fn main() {
fizz_buzz(10, 20)
fizzbuzz(10, 20)
}
// Outputs FizzBuzz for numbers between `start` and `end`, inclusive
fn fizz_buzz(start: i128, end: i128) {
fn fizzbuzz(start: i128, end: i128) {
for x in start..=end {
print(if x % 15 == 0 {
"FizzBuzz"

3
sample-code/hello.cl Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
print("Hello, world!")
}

31
sample-code/hex.cl Normal file
View File

@@ -0,0 +1,31 @@
//! Formats numbers in hexadecimal, octal, or binary
mod math;
// TODO: casting and/or conversion
const HEX_LUT: Array = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
pub fn hex(n: u64) {
let out = "0x";
for xd in min(count_leading_zeroes(n) / 4, 15)..16 {
out += HEX_LUT[(n >> (15 - xd) * 4) & 0xf]
}
out
}
pub fn oct(n: u64) {
let out = "0o";
for xd in min((count_leading_zeroes(n) + 2) / 3, 21)..22 {
out += HEX_LUT[(n >> max(63 - (3 * xd), 0)) & 7]
}
out
}
pub fn bin(n: u64) {
let out = "0b";
for xd in min(count_leading_zeroes(n), 63)..64 {
out += HEX_LUT[(n >> 63 - xd) & 1]
}
out
}

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