128 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
98 changed files with 8158 additions and 3029 deletions

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,13 +1,15 @@
[workspace]
members = [
"cl-repl",
"cl-typeck",
"cl-interpret",
"cl-structures",
"cl-token",
"cl-ast",
"cl-parser",
"cl-lexer",
"compiler/cl-repl",
"compiler/cl-typeck",
"compiler/cl-interpret",
"compiler/cl-structures",
"compiler/cl-token",
"compiler/cl-ast",
"compiler/cl-parser",
"compiler/cl-lexer",
"compiler/cl-arena",
"repline",
]
resolver = "2"
@@ -18,3 +20,6 @@ authors = ["John Breaux <j@soft.fish>"]
edition = "2021"
license = "MIT"
publish = ["soft-fish"]
[profile.dev]
opt-level = 1

View File

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

View File

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

View File

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

View File

@@ -1,462 +0,0 @@
//! Utilities for cl-frontend
//!
//! # TODO
//! - [ ] Readline-like line editing
//! - [ ] Raw mode?
#![warn(clippy::all)]
pub mod ansi {
// ANSI color escape sequences
pub const ANSI_RED: &str = "\x1b[31m";
pub const ANSI_GREEN: &str = "\x1b[32m"; // the color of type checker mode
pub const ANSI_CYAN: &str = "\x1b[36m";
// pub const ANSI_BRIGHT_GREEN: &str = "\x1b[92m";
pub const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
pub const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
pub const ANSI_RESET: &str = "\x1b[0m";
pub const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
pub const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
}
pub mod args {
use crate::tools::is_terminal;
use argh::FromArgs;
use std::{path::PathBuf, str::FromStr};
/// The Conlang prototype debug interface
#[derive(Clone, Debug, FromArgs, PartialEq, Eq, PartialOrd, Ord)]
pub struct Args {
/// the main source file
#[argh(positional)]
pub file: Option<PathBuf>,
/// files to include
#[argh(option, short = 'I')]
pub include: Vec<PathBuf>,
/// the Repl mode to start in
#[argh(option, short = 'm', default = "Default::default()")]
pub mode: Mode,
/// whether to start the repl (`true` or `false`)
#[argh(option, short = 'r', default = "is_terminal()")]
pub repl: bool,
}
/// The CLI's operating mode
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mode {
Tokenize,
Beautify,
#[default]
Interpret,
}
impl Mode {
pub fn ansi_color(self) -> &'static str {
use super::ansi::*;
match self {
Mode::Tokenize => ANSI_BRIGHT_BLUE,
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
// Mode::Resolve => ANSI_GREEN,
Mode::Interpret => ANSI_CYAN,
}
}
}
impl FromStr for Mode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, &'static str> {
Ok(match s {
"i" | "interpret" | "r" | "run" => Mode::Interpret,
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
// "r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
"t" | "tokenize" | "token" => Mode::Tokenize,
_ => Err("Recognized modes are: 'r' \"run\", 'p' \"pretty\", 't' \"token\"")?,
})
}
}
}
pub mod program {
use cl_interpret::{
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
};
use cl_ast::{self as ast, format::*};
use cl_lexer::Lexer;
use cl_parser::{error::PResult, Parser};
// use conlang::resolver::{error::TyResult, Resolver};
use std::{fmt::Display, io::Write};
pub struct Parsable;
pub enum Parsed {
File(ast::File),
Stmt(ast::Stmt),
Expr(ast::Expr),
}
pub struct Program<'t, Variant> {
text: &'t str,
data: Variant,
}
impl<'t, V> Program<'t, V> {
pub fn lex(&self) -> Lexer {
Lexer::new(self.text)
}
}
impl<'t> Program<'t, Parsable> {
pub fn new(text: &'t str) -> Self {
Self { text, data: Parsable }
}
pub fn parse(self) -> PResult<Program<'t, Parsed>> {
self.parse_file().or_else(|_| self.parse_stmt())
}
pub fn parse_expr(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::Expr(Parser::new(self.lex()).expr()?), text: self.text })
}
pub fn parse_stmt(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::Stmt(Parser::new(self.lex()).stmt()?), text: self.text })
}
pub fn parse_file(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::File(Parser::new(self.lex()).file()?), text: self.text })
}
}
impl<'t> Program<'t, Parsed> {
pub fn debug(&self) {
match &self.data {
Parsed::File(v) => eprintln!("{v:?}"),
Parsed::Stmt(v) => eprintln!("{v:?}"),
Parsed::Expr(v) => eprintln!("{v:?}"),
}
}
pub fn print(&self) {
let mut f = std::io::stdout().pretty();
let _ = match &self.data {
Parsed::File(v) => writeln!(f, "{v}"),
Parsed::Stmt(v) => writeln!(f, "{v}"),
Parsed::Expr(v) => writeln!(f, "{v}"),
};
// println!("{self}")
}
pub fn run(&self, env: &mut Environment) -> IResult<ConValue> {
match &self.data {
Parsed::File(v) => v.interpret(env),
Parsed::Stmt(v) => v.interpret(env),
Parsed::Expr(v) => v.interpret(env),
}
}
// pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
// match &mut self.data {
// Parsed::Program(start) => start.resolve(resolver),
// Parsed::Expr(expr) => expr.resolve(resolver),
// }
// .map(|ty| println!("{ty}"))
// }
}
impl<'t> Display for Program<'t, Parsed> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.data {
Parsed::File(v) => write!(f, "{v}"),
Parsed::Stmt(v) => write!(f, "{v}"),
Parsed::Expr(v) => write!(f, "{v}"),
}
}
}
}
pub mod cli {
//! Implement's the command line interface
use crate::{
args::{Args, Mode},
program::{Parsable, Program},
repl::Repl,
tools::print_token,
};
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
use cl_lexer::Lexer;
use cl_parser::Parser;
use std::{error::Error, path::Path};
/// Run the command line interface
pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
let Args { file, include, mode, repl } = args;
let mut env = Environment::new();
for path in include {
load_file(&mut env, path)?;
}
if repl {
if let Some(file) = file {
load_file(&mut env, file)?;
}
Repl::with_env(mode, env).repl()
} else {
let code = match &file {
Some(file) => std::fs::read_to_string(file)?,
None => std::io::read_to_string(std::io::stdin())?,
};
let code = Program::new(&code);
match mode {
Mode::Tokenize => tokenize(code, file),
Mode::Beautify => beautify(code),
Mode::Interpret => interpret(code, &mut env),
}?;
}
Ok(())
}
fn load_file(
env: &mut Environment,
path: impl AsRef<Path>,
) -> Result<ConValue, Box<dyn Error>> {
let file = std::fs::read_to_string(path)?;
let code = Parser::new(Lexer::new(&file)).file()?;
env.eval(&code).map_err(Into::into)
}
fn tokenize(
code: Program<Parsable>,
path: Option<impl AsRef<Path>>,
) -> Result<(), Box<dyn Error>> {
for token in code.lex() {
if let Some(ref path) = path {
print!("{}:", path.as_ref().display());
}
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
Ok(())
}
fn beautify(code: Program<Parsable>) -> Result<(), Box<dyn Error>> {
code.parse()?.print();
Ok(())
}
fn interpret(code: Program<Parsable>, env: &mut Environment) -> Result<(), Box<dyn Error>> {
match code.parse()?.run(env)? {
ConValue::Empty => {}
ret => println!("{ret}"),
}
if env.get("main").is_ok() {
match env.call("main", &[])? {
ConValue::Empty => {}
ret => println!("{ret}"),
}
}
Ok(())
}
}
pub mod repl {
use crate::{
ansi::*,
args::Mode,
program::{Parsable, Parsed, Program},
tools::print_token,
};
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
use std::fmt::Display;
/// Implements the interactive interpreter
#[derive(Clone, Debug)]
pub struct Repl {
prompt_again: &'static str, // " ?>"
prompt_begin: &'static str, // "cl>"
prompt_error: &'static str, // "! >"
prompt_succs: &'static str, // " ->"
env: Environment,
mode: Mode,
}
impl Default for Repl {
fn default() -> Self {
Self {
prompt_begin: "cl>",
prompt_again: " ?>",
prompt_error: "! >",
prompt_succs: " =>",
env: Default::default(),
mode: Default::default(),
}
}
}
/// Prompt functions
impl Repl {
pub fn prompt_result<T: Display, E: Display>(&self, res: Result<T, E>) {
match &res {
Ok(v) => self.prompt_succs(v),
Err(e) => self.prompt_error(e),
}
}
pub fn prompt_error(&self, err: &impl Display) {
let Self { prompt_error: prompt, .. } = self;
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}")
}
pub fn prompt_succs(&self, value: &impl Display) {
let Self { prompt_succs: _prompt, .. } = self;
println!("{ANSI_GREEN}{value}{ANSI_RESET}")
}
/// Resets the cursor to the start of the line, clears the terminal,
/// and sets the output color
pub fn begin_output(&self) {
print!("{ANSI_CLEAR_LINES}{ANSI_OUTPUT}")
}
pub fn clear_line(&self) {}
}
/// The actual REPL
impl Repl {
/// Constructs a new [Repl] with the provided [Mode]
pub fn new(mode: Mode) -> Self {
Self { mode, ..Default::default() }
}
/// Constructs a new [Repl] with the provided [Mode] and [Environment]
pub fn with_env(mode: Mode, env: Environment) -> Self {
Self { mode, env, ..Default::default() }
}
/// Runs the main REPL loop
pub fn repl(&mut self) {
use crate::repline::{error::Error, Repline};
let mut rl = Repline::new(self.mode.ansi_color(), self.prompt_begin, self.prompt_again);
fn clear_line() {
print!("\x1b[G\x1b[J");
}
loop {
let buf = match rl.read() {
Ok(buf) => buf,
// Ctrl-C: break if current line is empty
Err(Error::CtrlC(buf)) => {
if buf.is_empty() || buf.ends_with('\n') {
return;
}
rl.accept();
println!("Cancelled. (Press Ctrl+C again to quit.)");
continue;
}
// Ctrl-D: reset input, and parse it for errors
Err(Error::CtrlD(buf)) => {
rl.deny();
if let Err(e) = Program::new(&buf).parse() {
clear_line();
self.prompt_error(&e);
}
continue;
}
Err(e) => {
self.prompt_error(&e);
return;
}
};
self.begin_output();
if self.command(&buf) {
rl.deny();
rl.set_color(self.mode.ansi_color());
continue;
}
let code = Program::new(&buf);
if self.mode == Mode::Tokenize {
self.tokenize(&code);
rl.deny();
continue;
}
match code.lex().into_iter().find(|l| l.is_err()) {
None => {}
Some(Ok(_)) => unreachable!(),
Some(Err(error)) => {
rl.deny();
self.prompt_error(&error);
continue;
}
}
if let Ok(mut code) = code.parse() {
rl.accept();
self.dispatch(&mut code);
}
}
}
fn help(&self) {
println!(
"Commands:\n- $tokens\n Tokenize Mode:\n Outputs information derived by the Lexer\n- $pretty\n Beautify Mode:\n Pretty-prints the input\n- $type\n Resolve Mode:\n Attempts variable resolution and type-checking on the input\n- $run\n Interpret Mode:\n Interprets the input using Conlang\'s work-in-progress interpreter\n- $mode\n Prints the current mode\n- $help\n Prints this help message"
);
}
fn command(&mut self, line: &str) -> bool {
let Some(line) = line.trim().strip_prefix('$') else {
return false;
};
if let Ok(mode) = line.parse() {
self.mode = mode;
} else {
match line {
"$run" => self.mode = Mode::Interpret,
"mode" => println!("{:?} Mode", self.mode),
"help" => self.help(),
_ => return false,
}
}
true
}
/// Dispatches calls to repl functions based on the program
fn dispatch(&mut self, code: &mut Program<Parsed>) {
match self.mode {
Mode::Tokenize => {}
Mode::Beautify => self.beautify(code),
Mode::Interpret => self.interpret(code),
}
}
fn tokenize(&mut self, code: &Program<Parsable>) {
for token in code.lex() {
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
}
fn interpret(&mut self, code: &Program<Parsed>) {
match code.run(&mut self.env) {
Ok(ConValue::Empty) => {}
res => self.prompt_result(res),
}
}
fn beautify(&mut self, code: &Program<Parsed>) {
code.print()
}
}
}
pub mod tools {
use cl_token::Token;
use std::io::IsTerminal;
/// Prints a token in the particular way cl-repl does
pub fn print_token(t: &Token) {
println!(
"{:02}:{:02}: {:#19}{}",
t.line(),
t.col(),
t.ty(),
t.data(),
)
}
/// gets whether stdin AND stdout are a terminal, for pipelining
pub fn is_terminal() -> bool {
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
}
}
pub mod repline;

View File

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

View File

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

View File

@@ -1,14 +0,0 @@
//! # Universally useful structures
//! - [Span](struct@span::Span): Stores a start and end [Loc](struct@span::Loc)
//! - [Loc](struct@span::Loc): Stores the index in a stream
#![warn(clippy::all)]
#![feature(inline_const, dropck_eyepatch, decl_macro)]
#![deny(unsafe_op_in_unsafe_fn)]
pub mod span;
pub mod tree;
pub mod stack;
pub mod intern_pool;

View File

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

View File

@@ -1,5 +1,5 @@
[package]
name = "cl-structures"
name = "cl-arena"
repository.workspace = true
version.workspace = true
authors.workspace = true

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

@@ -5,20 +5,17 @@
//! - [Item] and [ItemKind]: Top-level constructs
//! - [Stmt] and [StmtKind]: Statements
//! - [Expr] and [ExprKind]: Expressions
//! - [Assign], [Binary], and [Unary] expressions
//! - [AssignKind], [BinaryKind], and [UnaryKind] operators
//! - [Assign], [Modify], [Binary], and [Unary] expressions
//! - [ModifyKind], [BinaryKind], and [UnaryKind] operators
//! - [Ty] and [TyKind]: Type qualifiers
//! - [Path]: Path expressions
#![warn(clippy::all)]
#![feature(decl_macro)]
use cl_structures::{intern::interned::Interned, span::*};
use cl_structures::span::*;
pub mod ast_impl;
pub mod format;
/// 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)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Mutability {
#[default]
Not,
@@ -26,34 +23,43 @@ pub enum Mutability {
}
/// Whether an [Item] is visible outside of the current [Module]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[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)]
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct File {
pub items: Vec<Item>,
}
// Metadata decorators
#[derive(Clone, Debug, Default, PartialEq, Eq)]
/// 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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Meta {
pub name: Identifier,
pub name: Sym,
pub kind: MetaKind,
}
/// Information attached to [Meta]data
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MetaKind {
Plain,
Equals(Literal),
@@ -62,7 +68,7 @@ pub enum MetaKind {
// Items
/// Anything that can appear at the top level of a [File]
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Item {
pub extents: Span,
pub attrs: Attrs,
@@ -71,7 +77,7 @@ pub struct Item {
}
/// What kind of [Item] is this?
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ItemKind {
// TODO: Import declaration ("use") item
// TODO: Trait declaration ("trait") item?
@@ -91,95 +97,96 @@ pub enum ItemKind {
Function(Function),
/// An [implementation](Impl)
Impl(Impl),
/// An [import](Use)
Use(Use),
}
/// An alias to another [Ty]
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Alias {
pub to: Identifier,
pub to: Sym,
pub from: Option<Box<Ty>>,
}
/// A compile-time constant
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Const {
pub name: Identifier,
pub name: Sym,
pub ty: Box<Ty>,
pub init: Box<Expr>,
}
/// A `static` variable
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Static {
pub mutable: Mutability,
pub name: Identifier,
pub name: Sym,
pub ty: Box<Ty>,
pub init: Box<Expr>,
}
/// An ordered collection of [Items](Item)
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Module {
pub name: Identifier,
pub name: Sym,
pub kind: ModuleKind,
}
/// The contents of a [Module], if they're in the same file
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ModuleKind {
Inline(File),
Outline,
}
/// Code, and the interface to that code
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Function {
pub name: Identifier,
pub args: Vec<Param>,
pub name: Sym,
pub sign: TyFn,
pub bind: Vec<Param>,
pub body: Option<Block>,
pub rety: Option<Box<Ty>>,
}
/// A single parameter for a [Function]
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Param {
pub mutability: Mutability,
pub name: Identifier,
pub ty: Box<Ty>,
pub name: Sym,
}
/// A user-defined product type
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Struct {
pub name: Identifier,
pub name: Sym,
pub kind: StructKind,
}
/// Either a [Struct]'s [StructMember]s or tuple [Ty]pes, if present.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum StructKind {
Empty,
Tuple(Vec<Ty>),
Struct(Vec<StructMember>),
}
/// The [Visibility], [Identifier], and [Ty]pe of a single [Struct] member
#[derive(Clone, Debug, PartialEq, Eq)]
/// 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: Identifier,
pub name: Sym,
pub ty: Ty,
}
/// A user-defined sum type
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Enum {
pub name: Identifier,
pub name: Sym,
pub kind: EnumKind,
}
/// An [Enum]'s [Variant]s, if it has a variant block
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum EnumKind {
/// Represents an enum with no variants
NoVariants,
@@ -187,111 +194,133 @@ pub enum EnumKind {
}
/// A single [Enum] variant
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Variant {
pub name: Identifier,
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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum VariantKind {
Plain,
CLike(u128),
Tuple(Vec<Ty>),
Tuple(Ty),
Struct(Vec<StructMember>),
}
/// Sub-[items](Item) (associated functions, etc.) for a [Ty]
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Impl {
pub target: Ty,
pub body: Vec<Item>,
pub target: ImplKind,
pub body: File,
}
// TODO: `impl` Trait for <Target> { }
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ImplKind {
Type(Box<Ty>),
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)]
#[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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TyKind {
Never,
Empty,
SelfTy,
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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyTuple {
pub types: Vec<Ty>,
pub types: Vec<TyKind>,
}
/// A [Ty]pe-reference expression as (number of `&`, [Path])
#[derive(Clone, Debug, PartialEq, Eq)]
#[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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TyFn {
pub args: TyTuple,
pub args: Box<TyKind>,
pub rety: Option<Box<Ty>>,
}
/// A path to an [Item] in the [Module] tree
#[derive(Clone, Debug, PartialEq, Eq)]
#[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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PathPart {
SuperKw,
SelfKw,
Ident(Identifier),
SelfTy,
Ident(Sym),
}
// TODO: Capture token?
/// A name
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Identifier(pub String);
/// An abstract statement, and associated metadata
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Stmt {
pub extents: Span,
pub kind: StmtKind,
pub semi: Semi,
}
/// Whether or not a [Stmt] is followed by a semicolon
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Semi {
Terminated,
Unterminated,
}
/// Whether the [Stmt] is a [Let], [Item], or [Expr] statement
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum StmtKind {
Empty,
Local(Let),
@@ -299,33 +328,49 @@ pub enum StmtKind {
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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Let {
pub mutable: Mutability,
pub name: Identifier,
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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Expr {
pub extents: Span,
pub kind: ExprKind,
}
/// Any of the different [Expr]essions
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
pub enum ExprKind {
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
/// 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"
@@ -339,12 +384,12 @@ pub enum ExprKind {
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 [Loop] expression: `loop` [`Block`]
Loop(Loop),
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
While(While),
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
@@ -359,17 +404,21 @@ pub enum ExprKind {
Continue(Continue),
}
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq)]
/// An [Assign]ment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Assign {
pub kind: AssignKind,
pub parts: Box<(ExprKind, ExprKind)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssignKind {
/// Standard Assignment with no read-back
Plain,
/// 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,
@@ -383,14 +432,14 @@ pub enum AssignKind {
}
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
#[derive(Clone, Debug, PartialEq, Eq)]
#[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)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BinaryKind {
Lt,
LtEq,
@@ -413,19 +462,18 @@ pub enum BinaryKind {
Mul,
Div,
Rem,
Dot,
Call,
}
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
#[derive(Clone, Debug, PartialEq, Eq)]
#[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)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UnaryKind {
Deref,
Neg,
@@ -435,38 +483,59 @@ pub enum UnaryKind {
/// 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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Index {
pub head: Box<ExprKind>,
pub indices: Vec<Expr>,
}
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
Bool(bool),
Char(char),
Int(u128),
String(String),
/// 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)]
#[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)]
#[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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AddrOf {
pub count: usize,
pub mutable: Mutability,
@@ -474,25 +543,31 @@ pub struct AddrOf {
}
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Block {
pub stmts: Vec<Stmt>,
}
/// A [Grouping](Group) expression `(` [`Expr`] `)`
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Group {
pub expr: Box<ExprKind>,
}
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
#[derive(Clone, Debug, PartialEq, Eq)]
#[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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct While {
pub cond: Box<Expr>,
pub pass: Box<Block>,
@@ -500,7 +575,7 @@ pub struct While {
}
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct If {
pub cond: Box<Expr>,
pub pass: Box<Block>,
@@ -508,32 +583,32 @@ pub struct If {
}
/// A [For] expression: `for` Pattern `in` [`Expr`] [`Block`] [`Else`]?
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct For {
pub bind: Identifier, // TODO: Patterns?
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)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Else {
pub body: Option<Box<Expr>>,
}
/// A [Break] expression: `break` [`Expr`]?
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Break {
pub body: Option<Box<Expr>>,
}
/// A [Return] expression `return` [`Expr`]?
#[derive(Clone, Debug, PartialEq, Eq)]
#[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)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Continue;

View File

@@ -3,57 +3,24 @@ use super::*;
mod display {
//! Implements [Display] for [AST](super::super) Types
use super::*;
pub use delimiters::*;
use format::{delimiters::*, *};
use std::{
borrow::Borrow,
fmt::{Display, Write},
};
mod delimiters {
#![allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub struct Delimiters<'t> {
pub open: &'t str,
pub close: &'t str,
}
/// Delimits with braces decorated with spaces `" {n"`, ..., `"\n}"`
pub const SPACED_BRACES: Delimiters = Delimiters { open: " {\n", close: "\n}" };
/// Delimits with braces on separate lines `{\n`, ..., `\n}`
pub const BRACES: Delimiters = Delimiters { open: "{\n", close: "\n}" };
/// Delimits with parentheses on separate lines `{\n`, ..., `\n}`
pub const PARENS: Delimiters = Delimiters { open: "(\n", close: "\n)" };
/// Delimits with square brackets on separate lines `{\n`, ..., `\n}`
pub const SQUARE: Delimiters = Delimiters { open: "[\n", close: "\n]" };
/// Delimits with braces on the same line `{ `, ..., ` }`
pub const INLINE_BRACES: Delimiters = Delimiters { open: "{ ", close: " }" };
/// Delimits with parentheses on the same line `( `, ..., ` )`
pub const INLINE_PARENS: Delimiters = Delimiters { open: "(", close: ")" };
/// Delimits with square brackets on the same line `[ `, ..., ` ]`
pub const INLINE_SQUARE: Delimiters = Delimiters { open: "[", close: "]" };
}
fn delimit<'a>(
func: impl Fn(&mut std::fmt::Formatter<'_>) -> std::fmt::Result + 'a,
delim: Delimiters<'a>,
) -> impl Fn(&mut std::fmt::Formatter<'_>) -> std::fmt::Result + 'a {
move |f| {
write!(f, "{}", delim.open)?;
func(f)?;
write!(f, "{}", delim.close)
}
}
fn separate<'iterable, I>(
iterable: &'iterable [I],
sep: impl Display + 'iterable,
) -> impl Fn(&mut std::fmt::Formatter<'_>) -> std::fmt::Result + 'iterable
where
I: Display,
{
move |f| {
for (idx, item) in iterable.iter().enumerate() {
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(())
}
@@ -67,6 +34,7 @@ mod display {
}
}
}
impl Display for Visibility {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -76,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)
}
}
@@ -89,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)),
}
}
}
@@ -114,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),
@@ -123,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;
@@ -135,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}")?;
}
@@ -178,96 +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"), SPACED_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 => ';'.fmt(f),
EnumKind::Variants(v) => delimit(separate(v, ",\n"), SPACED_BRACES)(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) => write!(f, " = {n}"),
VariantKind::Tuple(v) => delimit(separate(v, ", "), INLINE_PARENS)(f),
VariantKind::Struct(v) => delimit(separate(v, ", "), INLINE_BRACES)(f),
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;
@@ -279,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;
@@ -313,22 +424,27 @@ mod display {
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::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),
@@ -338,41 +454,50 @@ mod display {
}
}
}
impl Display for Assign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { parts } = self;
write!(f, "{} = {}", parts.0, parts.1)
}
}
impl Display for Modify {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { kind, parts } = self;
write!(f, "{} {kind} {}", parts.0, parts.1)
}
}
impl Display for AssignKind {
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 { kind, parts } = self;
let (head, tail) = parts.borrow();
match kind {
BinaryKind::Dot => write!(f, "{head}{kind}{tail}"),
BinaryKind::Call => write!(f, "{head}{tail}"),
_ => write!(f, "{head} {kind} {tail}"),
}
}
}
impl Display for BinaryKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -397,18 +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 { kind, tail } = self;
write!(f, "{kind}{tail}")
}
}
impl Display for UnaryKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -421,65 +547,64 @@ mod display {
.fmt(f)
}
}
impl Display for Tuple {
impl Display for Member {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
delimit(separate(&self.exprs, ", "), INLINE_PARENS)(f)
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 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;
@@ -489,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 {
@@ -525,6 +669,7 @@ mod display {
}
}
}
impl Display for Break {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "break")?;
@@ -534,6 +679,7 @@ mod display {
}
}
}
impl Display for Return {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "return")?;
@@ -543,15 +689,25 @@ mod display {
}
}
}
impl Display for Continue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"continue".fmt(f)
}
}
}
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()),
}
}
}
@@ -578,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,
@@ -588,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 {
@@ -604,8 +761,10 @@ mod convert {
}
impl From for ExprKind {
Assign => ExprKind::Assign,
Modify => ExprKind::Modify,
Binary => ExprKind::Binary,
Unary => ExprKind::Unary,
Member => ExprKind::Member,
Index => ExprKind::Index,
Path => ExprKind::Path,
Literal => ExprKind::Literal,
@@ -615,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,
@@ -641,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

@@ -1,17 +1,40 @@
//! Implementations of built-in functions
use super::{
convalue::ConValue,
env::Environment,
error::{Error, IResult},
temp_type_impl::ConValue,
BuiltIn, Callable,
};
use std::io::{stdout, Write};
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();
@@ -19,6 +42,7 @@ builtins! {
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();
@@ -27,6 +51,7 @@ builtins! {
}
Ok(args.into())
}
/// Dumps info from the environment
pub fn dump<env, _>() -> IResult<ConValue> {
println!("{}", *env);
@@ -43,6 +68,7 @@ builtins! {
_ => Err(Error::TypeError)?
})
}
/// Division `a / b`
pub fn div(lhs, rhs) -> IResult<ConValue> {
Ok(match (lhs, rhs){
@@ -51,6 +77,7 @@ builtins! {
_ => Err(Error::TypeError)?
})
}
/// Remainder `a % b`
pub fn rem(lhs, rhs) -> IResult<ConValue> {
Ok(match (lhs, rhs) {
@@ -65,10 +92,11 @@ builtins! {
Ok(match (lhs, rhs) {
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
(ConValue::String(a), ConValue::String(b)) => ConValue::String(a.to_string() + b),
(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) {
@@ -86,6 +114,7 @@ builtins! {
_ => Err(Error::TypeError)?,
})
}
/// Shift Right `a >> b`
pub fn shr(lhs, rhs) -> IResult<ConValue> {
Ok(match (lhs, rhs) {
@@ -104,6 +133,7 @@ builtins! {
_ => Err(Error::TypeError)?,
})
}
/// Bitwise Or `a | b`
pub fn or(lhs, rhs) -> IResult<ConValue> {
Ok(match (lhs, rhs) {
@@ -113,6 +143,7 @@ builtins! {
_ => Err(Error::TypeError)?,
})
}
/// Bitwise Exclusive Or `a ^ b`
pub fn xor(lhs, rhs) -> IResult<ConValue> {
Ok(match (lhs, rhs) {
@@ -127,22 +158,27 @@ builtins! {
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, >)
@@ -157,6 +193,7 @@ builtins! {
};
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 {
@@ -171,10 +208,11 @@ builtins! {
pub fn neg(tail) -> IResult<ConValue> {
Ok(match tail {
ConValue::Empty => ConValue::Empty,
ConValue::Int(v) => ConValue::Int(-v),
ConValue::Int(v) => ConValue::Int(v.wrapping_neg()),
_ => Err(Error::TypeError)?,
})
}
/// Inverts the ConValue
pub fn not(tail) -> IResult<ConValue> {
Ok(match tail {
@@ -184,6 +222,13 @@ builtins! {
_ => 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
@@ -221,7 +266,7 @@ macro builtins (
$(let [$($arg),*] = to_args(args)?;)?
$body
}
fn name(&self) -> &str { stringify!($name) }
fn name(&self) -> Sym { stringify!($name).into() }
}
)*
}
@@ -233,7 +278,7 @@ macro cmp ($a:expr, $b:expr, $empty:literal, $op:tt) {
(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)),
(ConValue::String(a), ConValue::String(b)) => Ok(ConValue::Bool(&**a $op &**b)),
_ => Err(Error::TypeError)
}
}

View File

@@ -5,7 +5,7 @@
//! meaningless to get a pointer to one, and would be undefined behavior to dereference a pointer to
//! one in any situation.
use std::borrow::Borrow;
use std::{borrow::Borrow, rc::Rc};
use super::*;
use cl_ast::*;
@@ -36,30 +36,41 @@ impl Interpret for Item {
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> {
todo!("Interpret type alias in {env}")
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> {
todo!("interpret const in {env}")
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> {
todo!("interpret static in {env}")
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 &self.kind {
match kind {
ModuleKind::Inline(file) => file.interpret(env),
ModuleKind::Outline => todo!("Load and parse external files"),
ModuleKind::Outline => Err(Error::Outlined(*name)),
}
}
}
@@ -71,18 +82,22 @@ impl Interpret for Function {
}
}
impl Interpret for Struct {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
todo!("Interpret structs in {env}")
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> {
todo!("Interpret enums in {env}")
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> {
todo!("Enter a struct's namespace and insert function definitions into it in {env}");
println!("TODO: {self}");
let Self { target: _, body } = self;
body.interpret(env)
}
}
impl Interpret for Stmt {
@@ -102,9 +117,9 @@ impl Interpret for Stmt {
}
impl Interpret for Let {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Let { mutable: _, name: Identifier(name), ty: _, init } = self;
let Let { mutable: _, name, ty: _, init } = self;
let init = init.as_ref().map(|i| i.interpret(env)).transpose()?;
env.insert(name, init);
env.insert(*name, init);
Ok(ConValue::Empty)
}
}
@@ -118,19 +133,23 @@ impl Interpret for Expr {
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::Empty => Ok(ConValue::Empty),
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),
@@ -140,17 +159,18 @@ impl Interpret for ExprKind {
}
}
}
impl Interpret for Assign {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Assign { kind: op, parts } = self;
let (head, tail) = parts.borrow();
// Resolve the head pattern
let head = match &head {
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::SuperKw => Err(Error::NotAssignable),
PathPart::SelfKw => todo!("Assignment to `self`"),
PathPart::Ident(Identifier(s)) => s,
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"),
@@ -158,41 +178,53 @@ impl Interpret for Assign {
ExprKind::Empty | ExprKind::Group(_) | ExprKind::Tuple(_) => {
todo!("Pattern Destructuring?")
}
_ => Err(Error::NotAssignable)?,
};
// Get the initializer and the tail
let init = tail.interpret(env)?;
let target = env.get_mut(head)?;
_ => Err(Error::NotAssignable),
}
}
if let AssignKind::Plain = op {
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 {
match target.0 {
Some(value) if variant(value) == variant(&init) => {
*value = init;
}
value @ None => *value = Some(init),
_ => Err(Error::TypeError)?,
}
return Ok(ConValue::Empty);
Ok(ConValue::Empty)
}
let Some(target) = target else {
return Err(Error::NotInitialized(head.into()));
}
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 {
AssignKind::Add => target.add_assign(init)?,
AssignKind::Sub => target.sub_assign(init)?,
AssignKind::Mul => target.mul_assign(init)?,
AssignKind::Div => target.div_assign(init)?,
AssignKind::Rem => target.rem_assign(init)?,
AssignKind::And => target.bitand_assign(init)?,
AssignKind::Or => target.bitor_assign(init)?,
AssignKind::Xor => target.bitxor_assign(init)?,
AssignKind::Shl => target.shl_assign(init)?,
AssignKind::Shr => target.shr_assign(init)?,
_ => (),
}
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)
}
}
@@ -228,25 +260,24 @@ impl Interpret for Binary {
}
let tail = tail.interpret(env)?;
match kind {
BinaryKind::Mul => env.call("mul", &[head, tail]),
BinaryKind::Div => env.call("div", &[head, tail]),
BinaryKind::Rem => env.call("rem", &[head, tail]),
BinaryKind::Add => env.call("add", &[head, tail]),
BinaryKind::Sub => env.call("sub", &[head, tail]),
BinaryKind::Shl => env.call("shl", &[head, tail]),
BinaryKind::Shr => env.call("shr", &[head, tail]),
BinaryKind::BitAnd => env.call("and", &[head, tail]),
BinaryKind::BitOr => env.call("or", &[head, tail]),
BinaryKind::BitXor => env.call("xor", &[head, tail]),
BinaryKind::RangeExc => env.call("range_exc", &[head, tail]),
BinaryKind::RangeInc => env.call("range_inc", &[head, tail]),
BinaryKind::Lt => env.call("lt", &[head, tail]),
BinaryKind::LtEq => env.call("lt_eq", &[head, tail]),
BinaryKind::Equal => env.call("eq", &[head, tail]),
BinaryKind::NotEq => env.call("neq", &[head, tail]),
BinaryKind::GtEq => env.call("gt_eq", &[head, tail]),
BinaryKind::Gt => env.call("gt", &[head, tail]),
BinaryKind::Dot => todo!("search within a type's namespace!"),
BinaryKind::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),
@@ -254,6 +285,36 @@ impl Interpret for Binary {
},
_ => 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),
// }
}
}
@@ -262,9 +323,9 @@ impl Interpret for Unary {
let Unary { kind, tail } = self;
let operand = tail.interpret(env)?;
match kind {
UnaryKind::Deref => env.call("deref", &[operand]),
UnaryKind::Neg => env.call("neg", &[operand]),
UnaryKind::Not => env.call("not", &[operand]),
UnaryKind::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)
@@ -273,6 +334,26 @@ impl Interpret for Unary {
}
}
}
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;
@@ -283,6 +364,11 @@ impl Interpret for Index {
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;
@@ -290,7 +376,8 @@ impl Interpret for Path {
if parts.len() == 1 {
match parts.last().expect("parts should not be empty") {
PathPart::SuperKw | PathPart::SelfKw => todo!("Path navigation"),
PathPart::Ident(Identifier(s)) => env.get(s).cloned(),
PathPart::SelfTy => todo!("Path navigation to Self"),
PathPart::Ident(name) => env.get(*name),
}
} else {
todo!("Path navigation!")
@@ -315,7 +402,7 @@ impl Interpret for Array {
for expr in values {
out.push(expr.interpret(env)?)
}
Ok(ConValue::Array(out))
Ok(ConValue::Array(out.into()))
}
}
impl Interpret for ArrayRep {
@@ -326,14 +413,21 @@ impl Interpret for ArrayRep {
_ => Err(Error::TypeError)?,
};
let value = value.interpret(env)?;
Ok(ConValue::Array(vec![value; repeat as usize]))
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;
// this is stupid
todo!("Create reference\nfrom expr: {expr}\nin env:\n{env}\n")
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 {
@@ -356,26 +450,43 @@ impl Interpret for Group {
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| {
Ok(ConValue::Tuple(
exprs
.iter()
.try_fold(vec![], |mut out, element| {
out.push(element.interpret(env)?);
Ok(out)
},
)?))
})?
.into(),
))
}
}
impl Interpret for While {
impl Interpret for Loop {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { cond, pass, fail } = self;
while cond.interpret(env)?.truthy()? {
match pass.interpret(env) {
let Self { body } = self;
loop {
match body.interpret(env) {
Err(Error::Break(value)) => return Ok(value),
Err(Error::Continue) => continue,
e => e?,
};
}
fail.interpret(env)
}
}
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 {
@@ -390,25 +501,26 @@ impl Interpret for If {
}
impl Interpret for For {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { bind: Identifier(name), cond, pass, fail } = self;
let Self { bind: name, cond, pass, fail } = self;
// TODO: A better iterator model
let bounds = match cond.interpret(env)? {
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");
for loop_var in bounds {
env.insert(name, Some(loop_var.into()));
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);
}
}
fail.interpret(env)
}
}
impl Interpret for Else {

View File

@@ -2,10 +2,11 @@
#![warn(clippy::all)]
#![feature(decl_macro)]
use cl_ast::Sym;
use convalue::ConValue;
use env::Environment;
use error::{Error, IResult};
use interpret::Interpret;
use temp_type_impl::ConValue;
/// Callable types can be called from within a Conlang program
pub trait Callable: std::fmt::Debug {
@@ -13,7 +14,7 @@ pub trait Callable: std::fmt::Debug {
/// The Callable is responsible for checking the argument count and validating types
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue>;
/// Returns the common name of this identifier.
fn name(&self) -> &str;
fn name(&self) -> Sym;
}
/// [BuiltIn]s are [Callable]s with bespoke definitions
@@ -21,23 +22,22 @@ pub trait BuiltIn: std::fmt::Debug + Callable {
fn description(&self) -> &str;
}
pub mod temp_type_impl {
//! Temporary implementations of Conlang values
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::*;
use std::{ops::*, rc::Rc};
type Integer = isize;
/// A Conlang value
///
/// This is a hack to work around the fact that Conlang doesn't
/// have a functioning type system yet :(
/// A Conlang value stores data in the interpreter
#[derive(Clone, Debug, Default)]
pub enum ConValue {
/// The empty/unit `()` type
@@ -50,11 +50,13 @@ pub mod temp_type_impl {
/// A unicode character
Char(char),
/// A string
String(String),
String(Sym),
/// A reference
Ref(Rc<ConValue>),
/// An Array
Array(Vec<ConValue>),
Array(Rc<[ConValue]>),
/// A tuple
Tuple(Vec<ConValue>),
Tuple(Rc<[ConValue]>),
/// An exclusive range
RangeExc(Integer, Integer),
/// An inclusive range
@@ -118,11 +120,11 @@ pub mod temp_type_impl {
}
impl Callable for ConValue {
fn name(&self) -> &str {
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> {
@@ -143,7 +145,7 @@ pub mod temp_type_impl {
(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)),
(Self::String(a), Self::String(b)) => Ok(Self::Bool(&**a $op &**b)),
_ => Err(Error::TypeError)
}
}
@@ -160,12 +162,19 @@ pub mod temp_type_impl {
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,
@@ -198,8 +207,12 @@ pub mod temp_type_impl {
ops! {
Add: add = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
(ConValue::String(a), ConValue::String(b)) => ConValue::String(a + &b),
(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 = [
@@ -222,32 +235,36 @@ pub mod temp_type_impl {
]
Div: div = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b),
(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 * b),
(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 % b),
(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 << b),
(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 >> b),
(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 - b),
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(b)),
_ => Err(Error::TypeError)?
]
}
@@ -259,6 +276,7 @@ pub mod temp_type_impl {
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() {
@@ -282,9 +300,7 @@ pub mod temp_type_impl {
')'.fmt(f)
}
ConValue::Function(func) => {
use cl_ast::format::*;
use std::fmt::Write;
write!(f.pretty(), "{}", func.decl())
write!(f, "{}", func.decl())
}
ConValue::BuiltIn(func) => {
write!(f, "{}", func.description())
@@ -298,13 +314,15 @@ pub mod interpret;
pub mod function {
//! Represents a block of code which lives inside the Interpreter
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
use cl_ast::{Function as FnDecl, Identifier, Param};
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: Box<FnDecl>,
decl: Rc<FnDecl>,
// /// Stores the enclosing scope of the function
// env: Box<Environment>,
}
@@ -319,25 +337,23 @@ pub mod function {
}
impl Callable for Function {
fn name(&self) -> &str {
let FnDecl { name: Identifier(ref name), .. } = *self.decl;
fn name(&self) -> Sym {
let FnDecl { name, .. } = *self.decl;
name
}
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
let FnDecl { name: Identifier(name), args: declargs, body, rety: _ } = &*self.decl;
let FnDecl { name, bind, body, sign: _ } = &*self.decl;
// Check arg mapping
if args.len() != declargs.len() {
return Err(Error::ArgNumber { want: declargs.len(), got: args.len() });
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.into()));
return Err(Error::NotDefined(*name));
};
// TODO: completely refactor data storage
let mut frame = env.frame("fn args");
for (Param { mutability: _, name: Identifier(name), ty: _ }, value) in
declargs.iter().zip(args)
{
frame.insert(name, Some(value.clone()));
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),
@@ -354,22 +370,24 @@ pub mod env {
//! Lexical and non-lexical scoping for variables
use super::{
builtin::{BINARY, MISC, RANGE, UNARY},
convalue::ConValue,
error::{Error, IResult},
function::Function,
temp_type_impl::ConValue,
BuiltIn, Callable, Interpret,
};
use cl_ast::{Function as FnDecl, Identifier};
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<(HashMap<String, Option<ConValue>>, &'static str)>,
frames: Vec<(StackFrame, &'static str)>,
}
impl Display for Environment {
@@ -400,10 +418,8 @@ pub mod env {
}
}
}
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<String, Option<ConValue>> {
from.iter()
.map(|&v| (v.name().into(), Some(v.into())))
.collect()
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<Sym, Option<ConValue>> {
from.iter().map(|&v| (v.name(), Some(v.into()))).collect()
}
impl Environment {
@@ -421,7 +437,7 @@ pub mod env {
/// Calls a function inside the interpreter's scope,
/// and returns the result
pub fn call(&mut self, name: &str, args: &[ConValue]) -> IResult<ConValue> {
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)
@@ -435,39 +451,39 @@ pub mod env {
/// Resolves a variable mutably.
///
/// Returns a mutable reference to the variable's record, if it exists.
pub fn get_mut(&mut self, id: &str) -> IResult<&mut Option<ConValue>> {
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) {
if let Some(var) = frame.get_mut(&id) {
return Ok(var);
}
}
Err(Error::NotDefined(id.into()))
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: &str) -> IResult<&ConValue> {
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),
Some(None) => return Err(Error::NotInitialized(id.into())),
match frame.get(&id) {
Some(Some(var)) => return Ok(var.clone()),
Some(None) => return Err(Error::NotInitialized(id)),
_ => (),
}
}
Err(Error::NotDefined(id.into()))
Err(Error::NotDefined(id))
}
/// Inserts a new [ConValue] into this [Environment]
pub fn insert(&mut self, id: &str, value: Option<ConValue>) {
pub fn insert(&mut self, id: Sym, value: Option<ConValue>) {
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(id.into(), value);
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: Identifier(name), .. } = decl;
let (name, function) = (name.clone(), Some(Function::new(decl).into()));
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);
frame.insert(*name, function);
}
}
}
@@ -521,7 +537,9 @@ pub mod env {
pub mod error {
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
use super::temp_type_impl::ConValue;
use cl_ast::Sym;
use super::convalue::ConValue;
pub type IResult<T> = Result<T, Error>;
@@ -552,9 +570,9 @@ pub mod error {
/// An expression is not assignable
NotAssignable,
/// A name was not defined in scope before being used
NotDefined(String),
NotDefined(Sym),
/// A name was defined but not initialized
NotInitialized(String),
NotInitialized(Sym),
/// A value was called, but is not callable
NotCallable(ConValue),
/// A function was called with the wrong number of arguments
@@ -562,7 +580,7 @@ pub mod error {
want: usize,
got: usize,
},
NullPointer,
Outlined(Sym),
}
impl std::error::Error for Error {}
@@ -602,8 +620,8 @@ pub mod error {
if *want == 1 { "" } else { "s" }
)
}
Error::NullPointer => {
write!(f, "Attempted to dereference a null pointer?")
Error::Outlined(name) => {
write!(f, "Module {name} specified, but not imported.")
}
}
}

View File

@@ -1,5 +1,5 @@
#![allow(unused_imports)]
use crate::{env::Environment, temp_type_impl::ConValue, Interpret};
use crate::{env::Environment, convalue::ConValue, Interpret};
use cl_ast::*;
use cl_lexer::Lexer;
use cl_parser::Parser;
@@ -127,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))
@@ -135,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))
@@ -190,7 +190,7 @@ mod fn_declarations {
"fn empty_fn () {\n \n}",
format!(
"{}",
env.get("empty_fn")
env.get("empty_fn".into())
.expect(stringify!(empty_fn should be defined and initialized))
)
)

View File

@@ -22,14 +22,15 @@ pub enum ErrorKind {
UnmatchedCurlyBraces,
UnmatchedSquareBrackets,
Unexpected(TokenKind),
Expected {
ExpectedToken {
want: TokenKind,
got: TokenKind,
},
/// No rules matched
Nothing,
ExpectedParsing {
want: Parsing,
},
/// Indicates unfinished code
Todo,
Todo(&'static str),
}
impl From<LexError> for ErrorKind {
fn from(value: LexError) -> Self {
@@ -43,14 +44,18 @@ impl From<LexError> for ErrorKind {
/// Compactly represents the stage of parsing an [Error] originated in
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Parsing {
Mutability,
Visibility,
Identifier,
Literal,
File,
Attrs,
Meta,
MetaKind,
Item,
Visibility,
Mutability,
ItemKind,
Alias,
Const,
@@ -67,13 +72,21 @@ pub enum Parsing {
Variant,
VariantKind,
Impl,
ImplKind,
Use,
UseTree,
Ty,
TyKind,
TySlice,
TyArray,
TyTuple,
TyRef,
TyFn,
Path,
PathPart,
Stmt,
StmtKind,
Let,
@@ -87,18 +100,17 @@ pub enum Parsing {
Unary,
UnaryKind,
Index,
Structor,
Fielder,
Call,
Member,
PathExpr,
PathPart,
Identifier,
Literal,
Array,
ArrayRep,
AddrOf,
Block,
Group,
Tuple,
Loop,
While,
If,
For,
@@ -113,7 +125,7 @@ impl Display for Error {
let Self { reason, while_parsing, loc } = self;
match reason {
// TODO entries are debug-printed
ErrorKind::Todo => write!(f, "{loc} {reason} {while_parsing:?}"),
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}"),
@@ -129,25 +141,26 @@ impl Display for ErrorKind {
ErrorKind::UnmatchedCurlyBraces => write!(f, "Unmatched curly braces"),
ErrorKind::UnmatchedSquareBrackets => write!(f, "Unmatched square brackets"),
ErrorKind::Unexpected(t) => write!(f, "Encountered unexpected token `{t}`"),
ErrorKind::Expected { want: e, got: g } => {
write!(f, "Expected `{e}`, got `{g}`")
}
ErrorKind::Nothing => write!(f, "Nothing found"),
ErrorKind::Todo => write!(f, "TODO:"),
ErrorKind::ExpectedToken { want: e, got: g } => write!(f, "Expected `{e}`, got `{g}`"),
ErrorKind::ExpectedParsing { want } => write!(f, "Expected {want}"),
ErrorKind::Todo(unfinished) => write!(f, "TODO: {unfinished}"),
}
}
}
impl Display for Parsing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Parsing::Visibility => "a visibility qualifier",
Parsing::Mutability => "a mutability qualifier",
Parsing::Identifier => "an identifier",
Parsing::Literal => "a literal",
Parsing::File => "a file",
Parsing::Attrs => "an attribute-set",
Parsing::Meta => "an attribute",
Parsing::MetaKind => "an attribute's arguments",
Parsing::Item => "an item",
Parsing::Visibility => "a visibility qualifier",
Parsing::Mutability => "a mutability qualifier",
Parsing::ItemKind => "an item",
Parsing::Alias => "a type alias",
Parsing::Const => "a const item",
@@ -164,13 +177,21 @@ impl Display for Parsing {
Parsing::Variant => "an enum variant",
Parsing::VariantKind => "an enum variant",
Parsing::Impl => "an impl block",
Parsing::ImplKind => "the target of an impl block",
Parsing::Use => "a use item",
Parsing::UseTree => "a use-tree",
Parsing::Ty => "a type",
Parsing::TyKind => "a type",
Parsing::TySlice => "a slice type",
Parsing::TyArray => "an array type",
Parsing::TyTuple => "a tuple of types",
Parsing::TyRef => "a reference type",
Parsing::TyFn => "a function pointer type",
Parsing::Path => "a path",
Parsing::PathPart => "a path component",
Parsing::Stmt => "a statement",
Parsing::StmtKind => "a statement",
Parsing::Let => "a local variable declaration",
@@ -184,18 +205,17 @@ impl Display for Parsing {
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::PathExpr => "a path",
Parsing::PathPart => "a path component",
Parsing::Identifier => "an identifier",
Parsing::Literal => "a literal",
Parsing::Array => "an array",
Parsing::ArrayRep => "an array of form [k;N]",
Parsing::AddrOf => "a borrow op",
Parsing::Block => "a block",
Parsing::Group => "a grouped expression",
Parsing::Tuple => "a tuple",
Parsing::Loop => "an unconditional loop expression",
Parsing::While => "a while expression",
Parsing::If => "an if expression",
Parsing::For => "a for expression",

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

@@ -14,3 +14,5 @@ use cl_token::*;
pub mod error;
pub mod parser;
pub mod inliner;

View File

@@ -80,7 +80,7 @@ impl<'t> Parser<'t> {
if got == want {
Ok(self.consume_peeked().expect("should not fail after peek"))
} else {
Err(self.error(Expected { want, got }, while_parsing))
Err(self.error(ExpectedToken { want, got }, while_parsing))
}
}
#[inline]
@@ -136,7 +136,6 @@ const fn sep<'t, T>(
/// Parses constructions of the form `(f ~until)*`
///
/// where `~until` is a negative lookahead assertion
#[allow(dead_code)]
const fn rep<'t, T>(
f: impl Fn(&mut Parser<'t>) -> PResult<T>,
until: Punct,
@@ -163,6 +162,7 @@ macro item_like() {
| TokenKind::Struct
| TokenKind::Enum
| TokenKind::Impl
| TokenKind::Use
}
/// Top level parsing
@@ -186,8 +186,8 @@ impl<'t> Parser<'t> {
pub fn item(&mut self) -> PResult<Item> {
let start = self.loc();
Ok(Item {
vis: self.visibility()?,
attrs: self.attributes()?,
vis: self.visibility(),
kind: self.itemkind()?,
extents: Span(start, self.loc()),
})
@@ -204,21 +204,27 @@ impl<'t> Parser<'t> {
/// Parses a [Path]
///
/// See also: [Parser::path_part], [Parser::identifier]
///
/// [Path] = `::` *RelativePath*? | *RelativePath* \
/// *RelativePath* = [PathPart] (`::` [PathPart])*
pub fn path(&mut self) -> PResult<Path> {
const PARSING: Parsing = Parsing::PathExpr;
let absolute = matches!(
self.peek_kind(PARSING)?,
TokenKind::Punct(Punct::ColonColon)
);
const PARSING: Parsing = Parsing::Path;
let absolute = self.match_op(Punct::ColonColon, PARSING).is_ok();
let mut parts = vec![];
if absolute {
self.consume_peeked();
match self.path_part() {
Ok(part) => parts.push(part),
Err(_) => return Ok(Path { absolute, parts }),
}
} else {
parts.push(self.path_part()?)
};
while self.match_op(Punct::ColonColon, Parsing::Path).is_ok() {
parts.push(self.path_part()?)
}
let mut parts = vec![self.path_part()?];
while let Ok(TokenKind::Punct(Punct::ColonColon)) = self.peek_kind(PARSING) {
self.consume_peeked();
parts.push(self.path_part()?);
}
Ok(Path { absolute, parts })
}
@@ -226,15 +232,11 @@ impl<'t> Parser<'t> {
///
/// See also: [Parser::stmtkind]
pub fn stmt(&mut self) -> PResult<Stmt> {
const PARSING: Parsing = Parsing::Stmt;
let start = self.loc();
Ok(Stmt {
kind: self.stmtkind()?,
semi: match self.peek_kind(PARSING) {
Ok(TokenKind::Punct(Punct::Semi)) => {
self.consume_peeked();
Semi::Terminated
}
semi: match self.match_op(Punct::Semi, Parsing::Stmt) {
Ok(_) => Semi::Terminated,
_ => Semi::Unterminated,
},
extents: Span(start, self.loc()),
@@ -260,12 +262,14 @@ impl<'t> Parser<'t> {
sep(Self::meta, Punct::Comma, BRACKETS.1, Parsing::Attrs),
BRACKETS,
Parsing::Attrs,
);
Ok(Attrs { meta: meta(self)? })
)(self)?;
Ok(Attrs { meta })
}
/// Parses a single [attribute](Meta)
pub fn meta(&mut self) -> PResult<Meta> {
Ok(Meta { name: self.identifier()?, kind: self.meta_kind()? })
}
/// Parses data associated with a [Meta] attribute
pub fn meta_kind(&mut self) -> PResult<MetaKind> {
const PARSING: Parsing = Parsing::Meta;
let lit_tuple = delim(
@@ -299,13 +303,16 @@ impl<'t> Parser<'t> {
TokenKind::Struct => self.parse_struct()?.into(),
TokenKind::Enum => self.parse_enum()?.into(),
TokenKind::Impl => self.parse_impl()?.into(),
TokenKind::Use => self.parse_use()?.into(),
t => Err(self.error(Unexpected(t), Parsing::Item))?,
})
}
/// Parses a [`type` alias](Alias)
pub fn parse_alias(&mut self) -> PResult<Alias> {
const PARSING: Parsing = Parsing::Alias;
self.match_type(TokenKind::Type, PARSING)?;
self.consume_peeked();
let out = Ok(Alias {
to: self.identifier()?,
from: if self.match_op(Punct::Eq, PARSING).is_ok() {
@@ -318,9 +325,11 @@ impl<'t> Parser<'t> {
out
}
/// Parses a [compile-time constant](Const)
pub fn parse_const(&mut self) -> PResult<Const> {
const PARSING: Parsing = Parsing::Const;
self.match_type(TokenKind::Const, PARSING)?;
self.consume_peeked();
let out = Ok(Const {
name: self.identifier()?,
ty: {
@@ -335,11 +344,14 @@ impl<'t> Parser<'t> {
self.match_op(Punct::Semi, PARSING)?;
out
}
/// Parses a [`static` item](Static)
pub fn parse_static(&mut self) -> PResult<Static> {
const PARSING: Parsing = Parsing::Static;
self.match_type(TokenKind::Static, PARSING)?;
self.consume_peeked();
let out = Ok(Static {
mutable: self.mutability()?,
mutable: self.mutability(),
name: self.identifier()?,
ty: {
self.match_op(Punct::Colon, PARSING)?;
@@ -353,11 +365,15 @@ impl<'t> Parser<'t> {
self.match_op(Punct::Semi, PARSING)?;
out
}
/// Parses a [Module]
pub fn parse_module(&mut self) -> PResult<Module> {
const PARSING: Parsing = Parsing::Module;
self.match_type(TokenKind::Mod, PARSING)?;
self.consume_peeked();
Ok(Module { name: self.identifier()?, kind: self.modulekind()? })
}
/// Parses the item list associated with a [Module], if present
pub fn modulekind(&mut self) -> PResult<ModuleKind> {
const PARSING: Parsing = Parsing::ModuleKind;
let inline = delim(Self::file, CURLIES, PARSING);
@@ -369,28 +385,30 @@ impl<'t> Parser<'t> {
Ok(ModuleKind::Outline)
}
got => Err(self.error(
Expected { want: TokenKind::Punct(Punct::Semi), got },
ExpectedToken { want: TokenKind::Punct(Punct::Semi), got },
PARSING,
)),
}
}
/// Parses a [Function] definition
pub fn parse_function(&mut self) -> PResult<Function> {
const PARSING: Parsing = Parsing::Function;
self.match_type(TokenKind::Fn, PARSING)?;
Ok(Function {
name: self.identifier()?,
args: self.parse_params()?,
rety: match self.peek_kind(PARSING)? {
TokenKind::Punct(Punct::LCurly) | TokenKind::Punct(Punct::Semi) => None,
TokenKind::Punct(Punct::Arrow) => {
self.consume_peeked();
Some(self.ty()?.into())
}
got => Err(self.error(
Expected { want: TokenKind::Punct(Punct::Arrow), got },
PARSING,
))?,
},
let name = self.identifier()?;
let (bind, types) = delim(Self::parse_params, PARENS, PARSING)(self)?;
let sign = TyFn {
args: Box::new(match types.len() {
0 => TyKind::Empty,
_ => TyKind::Tuple(TyTuple { types }),
}),
rety: self.parse_rety()?.map(Box::new),
};
Ok(Function {
name,
sign,
bind,
body: match self.peek_kind(PARSING)? {
TokenKind::Punct(Punct::LCurly) => Some(self.block()?),
TokenKind::Punct(Punct::Semi) => {
@@ -401,27 +419,38 @@ impl<'t> Parser<'t> {
},
})
}
pub fn parse_params(&mut self) -> PResult<Vec<Param>> {
/// Parses the [parameters](Param) associated with a Function
pub fn parse_params(&mut self) -> PResult<(Vec<Param>, Vec<TyKind>)> {
const PARSING: Parsing = Parsing::Function;
delim(
sep(Self::parse_param, Punct::Comma, PARENS.1, PARSING),
PARENS,
PARSING,
)(self)
let (mut params, mut types) = (vec![], vec![]);
while Ok(TokenKind::Punct(Punct::RParen)) != self.peek_kind(PARSING) {
let (param, ty) = self.parse_param()?;
params.push(param);
types.push(ty);
if self.match_op(Punct::Comma, PARSING).is_err() {
break;
}
pub fn parse_param(&mut self) -> PResult<Param> {
Ok(Param {
mutability: self.mutability()?,
name: self.identifier()?,
ty: {
}
Ok((params, types))
}
/// Parses a single function [parameter](Param)
pub fn parse_param(&mut self) -> PResult<(Param, TyKind)> {
Ok((
Param { mutability: self.mutability(), name: self.identifier()? },
{
self.match_op(Punct::Colon, Parsing::Param)?;
self.ty()?.into()
self.tykind()?
},
})
))
}
/// Parses a [`struct` definition](Struct)
pub fn parse_struct(&mut self) -> PResult<Struct> {
const PARSING: Parsing = Parsing::Struct;
self.match_type(TokenKind::Struct, PARSING)?;
self.consume_peeked();
Ok(Struct {
name: self.identifier()?,
kind: match self.peek_kind(PARSING)? {
@@ -432,12 +461,14 @@ impl<'t> Parser<'t> {
StructKind::Empty
}
got => Err(self.error(
Expected { want: TokenKind::Punct(Punct::Semi), got },
ExpectedToken { want: TokenKind::Punct(Punct::Semi), got },
PARSING,
))?,
},
})
}
/// Parses a [tuple-`struct`](StructKind::Tuple)'s members
pub fn structkind_tuple(&mut self) -> PResult<StructKind> {
const PARSING: Parsing = Parsing::StructKind;
@@ -447,18 +478,23 @@ impl<'t> Parser<'t> {
PARSING,
)(self)?))
}
/// Parses a [`struct`](StructKind::Struct)s members
pub fn structkind_struct(&mut self) -> PResult<StructKind> {
const PARSING: Parsing = Parsing::StructKind;
Ok(StructKind::Struct(delim(
sep(Self::struct_member, Punct::Comma, CURLIES.1, PARSING),
CURLIES,
PARSING,
)(self)?))
}
/// Parses a single [StructMember]
pub fn struct_member(&mut self) -> PResult<StructMember> {
const PARSING: Parsing = Parsing::StructMember;
Ok(StructMember {
vis: self.visibility()?,
vis: self.visibility(),
name: self.identifier()?,
ty: {
self.match_op(Punct::Colon, PARSING)?;
@@ -466,10 +502,12 @@ impl<'t> Parser<'t> {
},
})
}
/// Parses an [`enum`](Enum) definition
pub fn parse_enum(&mut self) -> PResult<Enum> {
// Enum = "enum" Identifier '{' (Variant ',')* Variant? '}' ;
const PARSING: Parsing = Parsing::Enum;
self.match_type(TokenKind::Enum, PARSING)?;
self.consume_peeked();
Ok(Enum {
name: self.identifier()?,
kind: match self.peek_kind(PARSING)? {
@@ -487,8 +525,10 @@ impl<'t> Parser<'t> {
})
}
/// Parses an [`enum`](Enum) [Variant]
pub fn enum_variant(&mut self) -> PResult<Variant> {
const PARSING: Parsing = Parsing::Variant;
Ok(Variant {
name: self.identifier()?,
kind: match self.peek_kind(PARSING)? {
@@ -499,15 +539,21 @@ impl<'t> Parser<'t> {
},
})
}
/// Parses a [C-like](VariantKind::CLike) [`enum`](Enum) [Variant]
pub fn variantkind_clike(&mut self) -> PResult<VariantKind> {
const PARSING: Parsing = Parsing::VariantKind;
self.match_op(Punct::Eq, PARSING)?;
let tok = self.match_type(TokenKind::Literal, PARSING)?;
Ok(VariantKind::CLike(match tok.data() {
TokenData::Integer(i) => *i,
_ => panic!("Expected token data for {tok:?} while parsing {PARSING}"),
}))
}
/// Parses a [struct-like](VariantKind::Struct) [`enum`](Enum) [Variant]
pub fn variantkind_struct(&mut self) -> PResult<VariantKind> {
const PARSING: Parsing = Parsing::VariantKind;
Ok(VariantKind::Struct(delim(
@@ -516,34 +562,101 @@ impl<'t> Parser<'t> {
PARSING,
)(self)?))
}
/// Parses a [tuple-like](VariantKind::Tuple) [`enum`](Enum) [Variant]
pub fn variantkind_tuple(&mut self) -> PResult<VariantKind> {
const PARSING: Parsing = Parsing::VariantKind;
Ok(VariantKind::Tuple(delim(
sep(Self::ty, Punct::Comma, Punct::RParen, PARSING),
PARENS,
let tup = self.ty()?;
if !matches!(tup.kind, TyKind::Tuple(_) | TyKind::Empty) {
Err(self.error(
ErrorKind::ExpectedParsing { want: Parsing::TyTuple },
PARSING,
)(self)?))
))?
}
Ok(VariantKind::Tuple(tup))
}
pub fn parse_impl(&mut self) -> PResult<Impl> {
const PARSING: Parsing = Parsing::Impl;
self.match_type(TokenKind::Impl, PARSING)?;
Err(self.error(Todo, PARSING))
self.consume_peeked();
Ok(Impl {
target: self.parse_impl_kind()?,
body: delim(Self::file, CURLIES, PARSING)(self)?,
})
}
pub fn visibility(&mut self) -> PResult<Visibility> {
if let TokenKind::Pub = self.peek_kind(Parsing::Visibility)? {
self.consume_peeked();
return Ok(Visibility::Public);
};
Ok(Visibility::Private)
pub fn parse_impl_kind(&mut self) -> PResult<ImplKind> {
const PARSING: Parsing = Parsing::ImplKind;
let target = self.ty()?;
if self.match_type(TokenKind::For, PARSING).is_err() {
Ok(ImplKind::Type(target))
} else if let TyKind::Path(impl_trait) = target.kind {
Ok(ImplKind::Trait { impl_trait, for_type: self.ty()?.into() })
} else {
Err(Error {
reason: ExpectedParsing { want: Parsing::Path },
while_parsing: PARSING,
loc: target.extents.head,
})?
}
pub fn mutability(&mut self) -> PResult<Mutability> {
if let TokenKind::Mut = self.peek_kind(Parsing::Mutability)? {
}
pub fn parse_use(&mut self) -> PResult<Use> {
self.consume_peeked();
return Ok(Mutability::Mut);
let absolute = self.match_op(Punct::ColonColon, Parsing::Use).is_ok();
let tree = self.parse_use_tree()?;
self.match_op(Punct::Semi, Parsing::Use)?;
Ok(Use { tree, absolute })
}
pub fn parse_use_tree(&mut self) -> PResult<UseTree> {
const PARSING: Parsing = Parsing::UseTree;
// glob import
Ok(match self.peek_kind(PARSING)? {
TokenKind::Punct(Punct::Star) => {
self.consume_peeked();
UseTree::Glob
}
TokenKind::Punct(Punct::LCurly) => UseTree::Tree(delim(
sep(Self::parse_use_tree, Punct::Comma, CURLIES.1, PARSING),
CURLIES,
PARSING,
)(self)?),
TokenKind::SelfKw | TokenKind::Super | TokenKind::Identifier => {
let name = self.path_part()?;
if self.match_op(Punct::ColonColon, PARSING).is_ok() {
UseTree::Path(name, Box::new(self.parse_use_tree()?))
} else {
let PathPart::Ident(name) = name else {
Err(self.error(
ErrorKind::ExpectedParsing { want: Parsing::Identifier },
PARSING,
))?
};
Ok(Mutability::Not)
UseTree::Name(name)
}
}
t => Err(self.error(Unexpected(t), Parsing::UseTree))?,
})
}
/// [Visibility] = `pub`?
pub fn visibility(&mut self) -> Visibility {
match self.match_type(TokenKind::Pub, Parsing::Visibility) {
Ok(_) => Visibility::Public,
Err(_) => Visibility::Private,
}
}
/// [Mutability] = `mut`?
pub fn mutability(&mut self) -> Mutability {
match self.match_type(TokenKind::Mut, Parsing::Mutability) {
Ok(_) => Mutability::Mut,
Err(_) => Mutability::Not,
}
}
}
@@ -559,12 +672,15 @@ impl<'t> Parser<'t> {
self.consume_peeked();
TyKind::Never
}
TokenKind::SelfTy => {
self.consume_peeked();
TyKind::SelfTy
}
TokenKind::Punct(Punct::Amp) | TokenKind::Punct(Punct::AmpAmp) => self.tyref()?.into(),
TokenKind::Punct(Punct::LParen) => self.tytuple()?.into(),
TokenKind::Punct(Punct::LBrack) => self.tyslice_or_array()?,
TokenKind::Punct(Punct::LParen) => {
let out = self.tytuple()?;
match out.types.is_empty() {
true => TyKind::Empty,
false => TyKind::Tuple(out),
}
}
TokenKind::Fn => self.tyfn()?.into(),
path_like!() => self.path()?.into(),
t => Err(self.error(Unexpected(t), PARSING))?,
@@ -572,12 +688,38 @@ impl<'t> Parser<'t> {
Ok(out)
}
/// [`TySlice`] = `[` [Ty] `]` \
/// [`TyArray`] = `[` [Ty] `;` [usize] `]`
pub fn tyslice_or_array(&mut self) -> PResult<TyKind> {
self.match_op(BRACKETS.0, Parsing::TySlice)?;
let ty = self.tykind()?;
let (out, kind) = match self.match_op(Punct::Semi, Parsing::TyArray).is_ok() {
true => {
let literal = self.match_type(TokenKind::Literal, Parsing::TyArray)?;
let &TokenData::Integer(count) = literal.data() else {
Err(self.error(Unexpected(TokenKind::Literal), Parsing::TyArray))?
};
(
TyKind::Array(TyArray { ty: Box::new(ty), count: count as _ }),
Parsing::TyArray,
)
}
false => (
TyKind::Slice(TySlice { ty: Box::new(ty) }),
Parsing::TySlice,
),
};
self.match_op(BRACKETS.1, kind)?;
Ok(out)
}
/// [TyTuple] = `(` ([Ty] `,`)* [Ty]? `)`
pub fn tytuple(&mut self) -> PResult<TyTuple> {
const PARSING: Parsing = Parsing::TyTuple;
Ok(TyTuple {
types: delim(
sep(Self::ty, Punct::Comma, PARENS.1, PARSING),
sep(Self::tykind, Punct::Comma, PARENS.1, PARSING),
PARENS,
PARSING,
)(self)?,
@@ -595,25 +737,33 @@ impl<'t> Parser<'t> {
}
self.consume_peeked();
}
Ok(TyRef { count, to: self.path()? })
Ok(TyRef { count, mutable: self.mutability(), to: self.path()? })
}
/// [TyFn] = `fn` [TyTuple] (-> [Ty])?
pub fn tyfn(&mut self) -> PResult<TyFn> {
const PARSING: Parsing = Parsing::TyFn;
self.match_type(TokenKind::Fn, PARSING)?;
Ok(TyFn {
args: self.tytuple()?,
rety: {
match self.peek_kind(PARSING)? {
TokenKind::Punct(Punct::Arrow) => {
self.consume_peeked();
Some(self.ty()?.into())
}
_ => None,
}
},
args: Box::new(match self.tyfn_args()? {
t if t.is_empty() => TyKind::Empty,
types => TyKind::Tuple(TyTuple { types }),
}),
rety: self.parse_rety()?.map(Into::into),
})
}
pub fn parse_rety(&mut self) -> PResult<Option<Ty>> {
Ok(match self.match_op(Punct::Arrow, Parsing::TyFn) {
Ok(_) => Some(self.ty()?),
Err(_) => None,
})
}
pub fn tyfn_args(&mut self) -> PResult<Vec<TyKind>> {
const P: Parsing = Parsing::TyFn;
delim(sep(Self::tykind, Punct::Comma, PARENS.1, P), PARENS, P)(self)
}
}
/// Expands to a pattern which matches literal-like [TokenKind]s
@@ -624,25 +774,28 @@ macro literal_like() {
macro path_like() {
TokenKind::Super
| TokenKind::SelfKw
| TokenKind::SelfTy
| TokenKind::Identifier
| TokenKind::Punct(Punct::ColonColon)
}
/// # Path parsing
impl<'t> Parser<'t> {
/// [PathPart] = `super` | `self` | [Identifier]
/// [PathPart] = `super` | `self` | [`Identifier`](TokenKind::Identifier)
pub fn path_part(&mut self) -> PResult<PathPart> {
const PARSING: Parsing = Parsing::PathPart;
let out = match self.peek_kind(PARSING)? {
TokenKind::Super => PathPart::SuperKw,
TokenKind::SelfKw => PathPart::SelfKw,
TokenKind::SelfTy => PathPart::SelfTy,
TokenKind::Identifier => PathPart::Ident(self.identifier()?),
t => return Err(self.error(Unexpected(t), PARSING)),
};
// Note: this relies on identifier not peeking
self.consume_peeked();
Ok(out)
}
/// [Identifier] = [`Identifier`](TokenKind::Identifier)
pub fn identifier(&mut self) -> PResult<Identifier> {
/// [Sym] = [`Identifier`](TokenKind::Identifier)
pub fn identifier(&mut self) -> PResult<Sym> {
let tok = self.match_type(TokenKind::Identifier, Parsing::Identifier)?;
match tok.data() {
TokenData::String(ident) => Ok(ident.into()),
@@ -666,9 +819,9 @@ impl<'t> Parser<'t> {
}
pub fn parse_let(&mut self) -> PResult<Let> {
self.match_type(TokenKind::Let, Parsing::Let)?;
self.consume_peeked();
Ok(Let {
mutable: self.mutability()?,
mutable: self.mutability(),
name: self.identifier()?,
ty: if Ok(TokenKind::Punct(Punct::Colon)) == self.peek_kind(Parsing::Let) {
self.consume_peeked();
@@ -693,23 +846,16 @@ impl<'t> Parser<'t> {
let start = self.loc();
Ok(Expr { kind: f(self)?, extents: Span(start, self.loc()) })
}
pub fn optional_expr(&mut self) -> PResult<Option<Expr>> {
match self.expr() {
Ok(v) => Ok(Some(v)),
Err(Error { reason: Nothing, .. }) => Ok(None),
Err(e) => Err(e),
}
}
/// Parses an [ExprKind]
///
/// See also: [Parser::expr]
pub fn exprkind(&mut self, power: u8) -> PResult<ExprKind> {
let parsing = Parsing::ExprKind;
//
// Prefix expressions
let mut head = match self.peek_kind(Parsing::Unary)? {
literal_like!() => self.literal()?.into(),
path_like!() => self.path()?.into(),
path_like!() => self.exprkind_pathlike()?,
TokenKind::Punct(Punct::Amp | Punct::AmpAmp) => self.addrof()?.into(),
TokenKind::Punct(Punct::LCurly) => self.block()?.into(),
TokenKind::Punct(Punct::LBrack) => self.exprkind_arraylike()?,
@@ -721,17 +867,15 @@ impl<'t> Parser<'t> {
self.consume_peeked();
Unary { kind, tail: self.exprkind(after)?.into() }.into()
}
TokenKind::Loop => {
self.consume_peeked();
Loop { body: self.expr()?.into() }.into()
}
TokenKind::While => ExprKind::While(self.parse_while()?),
TokenKind::If => ExprKind::If(self.parse_if()?),
TokenKind::For => ExprKind::For(self.parse_for()?),
TokenKind::Break => {
self.consume_peeked();
Break { body: self.optional_expr()?.map(Into::into) }.into()
}
TokenKind::Return => {
self.consume_peeked();
Return { body: self.optional_expr()?.map(Into::into) }.into()
}
TokenKind::Break => ExprKind::Break(self.parse_break()?),
TokenKind::Return => ExprKind::Return(self.parse_return()?),
TokenKind::Continue => {
self.consume_peeked();
Continue.into()
@@ -742,12 +886,14 @@ impl<'t> Parser<'t> {
fn from_postfix(op: Punct) -> Option<Precedence> {
Some(match op {
Punct::LBrack => Precedence::Index,
Punct::LParen => Precedence::Postfix,
Punct::LParen => Precedence::Call,
Punct::Dot => Precedence::Member,
_ => None?,
})
}
while let Ok(TokenKind::Punct(op)) = self.peek_kind(parsing) {
// Postfix expressions
if let Some((before, ())) = from_postfix(op).and_then(Precedence::postfix) {
if before < power {
break;
@@ -769,6 +915,10 @@ impl<'t> Parser<'t> {
}
.into()
}
Punct::Dot => {
let kind = self.access()?;
Member { head: Box::new(head), kind }.into()
}
_ => Err(self.error(Unexpected(TokenKind::Punct(op)), parsing))?,
};
continue;
@@ -786,7 +936,7 @@ impl<'t> Parser<'t> {
continue;
}
if let Some((kind, prec)) = from_assign(op) {
if let Some((kind, prec)) = from_modify(op) {
let (before, after) = prec.infix().expect("should have a precedence");
if before < power {
break;
@@ -794,14 +944,80 @@ impl<'t> Parser<'t> {
self.consume_peeked();
let tail = self.exprkind(after)?;
head = Assign { kind, parts: (head, tail).into() }.into();
head = Modify { kind, parts: (head, tail).into() }.into();
continue;
}
if let Punct::Eq = op {
let (before, after) = Precedence::Assign
.infix()
.expect("should have a precedence");
if before < power {
break;
}
self.consume_peeked();
let tail = self.exprkind(after)?;
head = Assign { parts: (head, tail).into() }.into();
}
break;
}
Ok(head)
}
pub fn access(&mut self) -> PResult<MemberKind> {
const PARSING: Parsing = Parsing::Member;
const DEL: (Punct, Punct) = PARENS; // delimiter
match self.peek_kind(PARSING)? {
TokenKind::Identifier => {
let name = self.identifier()?;
if self.match_op(DEL.0, PARSING).is_err() {
Ok(MemberKind::Struct(name))
} else {
let exprs = sep(Self::expr, Punct::Comma, DEL.1, PARSING)(self)?;
self.match_op(DEL.1, PARSING)?; // should succeed
Ok(MemberKind::Call(name, Tuple { exprs }))
}
}
TokenKind::Literal => {
let name = self.literal()?; // TODO: Maybe restrict this to just
Ok(MemberKind::Tuple(name))
}
t => Err(self.error(Unexpected(t), PARSING)),
}
}
/// Parses an expression beginning with a [Path] (i.e. [Path] or [Structor])
pub fn exprkind_pathlike(&mut self) -> PResult<ExprKind> {
let head = self.path()?;
Ok(match self.match_op(Punct::Colon, Parsing::Path) {
Ok(_) => ExprKind::Structor(self.structor_body(head)?),
Err(_) => ExprKind::Path(head),
})
}
/// [Structor]Body = `{` ([Fielder] `,`)* [Fielder]? `}`
pub fn structor_body(&mut self, to: Path) -> PResult<Structor> {
let init = delim(
sep(Self::fielder, Punct::Comma, CURLIES.1, Parsing::Structor),
CURLIES,
Parsing::Structor,
)(self)?;
Ok(Structor { to, init })
}
/// [Fielder] = [`Identifier`](TokenKind::Identifier) (`:` [Expr])?
pub fn fielder(&mut self) -> PResult<Fielder> {
const PARSING: Parsing = Parsing::Fielder;
Ok(Fielder {
name: self.identifier()?,
init: match self.match_op(Punct::Colon, PARSING) {
Ok(_) => Some(Box::new(self.expr()?)),
Err(_) => None,
},
})
}
/// [Array] = '[' ([Expr] ',')* [Expr]? ']'
///
/// Array and ArrayRef are ambiguous until the second token,
@@ -895,7 +1111,7 @@ impl<'t> Parser<'t> {
};
self.consume_peeked();
}
Ok(AddrOf { count, mutable: self.mutability()?, expr: self.exprkind(0)?.into() })
Ok(AddrOf { count, mutable: self.mutability(), expr: self.exprkind(0)?.into() })
}
/// [Literal] = [LITERAL](TokenKind::Literal) | `true` | `false`
pub fn literal(&mut self) -> PResult<Literal> {
@@ -922,9 +1138,27 @@ impl<'t> Parser<'t> {
}
/// ## Control flow subexpressions
impl<'t> Parser<'t> {
/// [Break] = `break` (*unconsumed* `;` | [Expr])
pub fn parse_break(&mut self) -> PResult<Break> {
self.consume_peeked();
Ok(Break { body: self.ret_body(Parsing::Break)? })
}
/// [Return] = `return` (*unconsumed* `;` | [Expr])
pub fn parse_return(&mut self) -> PResult<Return> {
self.consume_peeked();
Ok(Return { body: self.ret_body(Parsing::Return)? })
}
/// ret_body = (*unconsumed* `;` | [Expr])
fn ret_body(&mut self, while_parsing: Parsing) -> PResult<Option<Box<Expr>>> {
Ok(match self.peek_kind(while_parsing)? {
TokenKind::Punct(Punct::Semi) => None,
_ => Some(self.expr()?.into()),
})
}
/// [While] = `while` [Expr] [Block] [Else]?
pub fn parse_while(&mut self) -> PResult<While> {
self.match_type(TokenKind::While, Parsing::While)?;
self.consume_peeked();
Ok(While {
cond: self.expr()?.into(),
pass: self.block()?.into(),
@@ -934,7 +1168,7 @@ impl<'t> Parser<'t> {
/// [If] = <code>`if` [Expr] [Block] [Else]?</code>
#[rustfmt::skip] // second line is barely not long enough
pub fn parse_if(&mut self) -> PResult<If> {
self.match_type(TokenKind::If, Parsing::If)?;
self.consume_peeked();
Ok(If {
cond: self.expr()?.into(),
pass: self.block()?.into(),
@@ -943,7 +1177,7 @@ impl<'t> Parser<'t> {
}
/// [For]: `for` Pattern (TODO) `in` [Expr] [Block] [Else]?
pub fn parse_for(&mut self) -> PResult<For> {
self.match_type(TokenKind::For, Parsing::For)?;
self.consume_peeked();
let bind = self.identifier()?;
self.match_type(TokenKind::In, Parsing::For)?;
Ok(For {
@@ -979,8 +1213,8 @@ pub enum Precedence {
Factor,
Term,
Unary,
Postfix,
Member, // left-associative
Call,
}
impl Precedence {
@@ -998,19 +1232,19 @@ impl Precedence {
let level = self.level();
match self {
Self::Unary => None,
Self::Member | Self::Assign => Some((level + 1, level)),
Self::Assign => Some((level + 1, level)),
_ => Some((level, level + 1)),
}
}
pub fn postfix(self) -> Option<(u8, ())> {
match self {
Self::Index | Self::Postfix => Some((self.level(), ())),
Self::Index | Self::Call | Self::Member => Some((self.level(), ())),
_ => None,
}
}
}
impl From<AssignKind> for Precedence {
fn from(_value: AssignKind) -> Self {
impl From<ModifyKind> for Precedence {
fn from(_value: ModifyKind) -> Self {
Precedence::Assign
}
}
@@ -1019,8 +1253,7 @@ impl From<BinaryKind> for Precedence {
fn from(value: BinaryKind) -> Self {
use BinaryKind as Op;
match value {
Op::Call => Precedence::Postfix,
Op::Dot => Precedence::Member,
Op::Call => Precedence::Call,
Op::Mul | Op::Div | Op::Rem => Precedence::Term,
Op::Add | Op::Sub => Precedence::Factor,
Op::Shl | Op::Shr => Precedence::Shift,
@@ -1058,8 +1291,7 @@ operator! {
At => At,
Tilde => Tilde,
};
from_assign(Punct => AssignKind) {
Eq => Plain,
from_modify(Punct => ModifyKind) {
AmpEq => And,
BarEq => Or,
XorEq => Xor,
@@ -1094,6 +1326,5 @@ operator! {
Star => Mul,
Slash => Div,
Rem => Rem,
Dot => Dot,
};
}

View File

@@ -15,9 +15,5 @@ cl-lexer = { path = "../cl-lexer" }
cl-token = { path = "../cl-token" }
cl-parser = { path = "../cl-parser" }
cl-interpret = { path = "../cl-interpret" }
crossterm = "0.27.0"
argh = "0.1.12"
[dev-dependencies]
cl-structures = { path = "../cl-structures" }
cl-typeck = { path = "../cl-typeck" }
repline = { path = "../../repline" }
argwerk = "0.20.4"

View File

@@ -2,7 +2,7 @@
use cl_lexer::Lexer;
use cl_parser::Parser;
use cl_repl::repline::{error::Error as RlError, Repline};
use repline::{error::Error as RlError, Repline};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
@@ -207,6 +207,7 @@ pub mod yamlify {
ItemKind::Struct(f) => y.yaml(f),
ItemKind::Enum(f) => y.yaml(f),
ItemKind::Impl(f) => y.yaml(f),
ItemKind::Use(f) => y.yaml(f),
};
}
}
@@ -247,12 +248,12 @@ pub mod yamlify {
}
impl Yamlify for Function {
fn yaml(&self, y: &mut Yamler) {
let Self { name, args, body, rety } = self;
let Self { name, sign, bind, body } = self;
y.key("Function")
.pair("name", name)
.pair("args", args)
.pair("body", body)
.pair("rety", rety);
.pair("sign", sign)
.pair("bind", bind)
.pair("body", body);
}
}
impl Yamlify for Struct {
@@ -312,6 +313,33 @@ pub mod yamlify {
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;
@@ -361,9 +389,12 @@ pub mod yamlify {
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),
@@ -373,6 +404,7 @@ pub mod yamlify {
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),
@@ -384,14 +416,22 @@ pub mod yamlify {
}
impl Yamlify for Assign {
fn yaml(&self, y: &mut Yamler) {
let Self { kind, parts } = self;
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 AssignKind {
impl Yamlify for ModifyKind {
fn yaml(&self, y: &mut Yamler) {
y.value(self);
}
@@ -421,6 +461,21 @@ pub mod yamlify {
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;
@@ -433,6 +488,18 @@ pub mod yamlify {
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;
@@ -451,8 +518,8 @@ pub mod yamlify {
fn yaml(&self, y: &mut Yamler) {
let Self { count, mutable, expr } = self;
y.key("AddrOf")
.yaml(mutable)
.pair("count", count)
.yaml(mutable)
.pair("expr", expr);
}
}
@@ -462,13 +529,19 @@ pub mod yamlify {
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)
.pair("fail", fail);
.yaml(fail);
}
}
impl Yamlify for Else {
@@ -515,19 +588,15 @@ pub mod yamlify {
y.value(format_args!("\"{self}\""));
}
}
impl Yamlify for Identifier {
impl Yamlify for Sym {
fn yaml(&self, y: &mut Yamler) {
let Self(name) = self;
y.value(name);
y.value(self);
}
}
impl Yamlify for Param {
fn yaml(&self, y: &mut Yamler) {
let Self { mutability, name, ty } = self;
y.key("Param")
.yaml(mutability)
.pair("name", name)
.pair("ty", ty);
let Self { mutability, name } = self;
y.key("Param").yaml(mutability).pair("name", name);
}
}
impl Yamlify for Ty {
@@ -541,11 +610,12 @@ pub mod yamlify {
match self {
TyKind::Never => y.value("Never"),
TyKind::Empty => y.value("Empty"),
TyKind::SelfTy => y.value("Self"),
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!(),
};
}
}
@@ -566,10 +636,23 @@ pub mod yamlify {
match self {
PathPart::SuperKw => y.value("super"),
PathPart::SelfKw => y.value("self"),
PathPart::SelfTy => y.value("Self"),
PathPart::Ident(i) => y.yaml(i),
};
}
}
impl Yamlify for TyArray {
fn yaml(&self, y: &mut Yamler) {
let Self { ty, count } = self;
y.key("TyArray").pair("ty", ty).pair("count", count);
}
}
impl Yamlify for TySlice {
fn yaml(&self, y: &mut Yamler) {
let Self { ty } = self;
y.key("TyArray").pair("ty", ty);
}
}
impl Yamlify for TyTuple {
fn yaml(&self, y: &mut Yamler) {
let Self { types } = self;
@@ -581,8 +664,11 @@ pub mod yamlify {
}
impl Yamlify for TyRef {
fn yaml(&self, y: &mut Yamler) {
let Self { count, to } = self;
y.key("TyRef").pair("count", count).pair("to", to);
let Self { count, mutable, to } = self;
y.key("TyRef")
.pair("count", count)
.yaml(mutable)
.pair("to", to);
}
}
impl Yamlify for TyFn {

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

@@ -8,24 +8,33 @@ pub struct Span {
pub head: Loc,
pub tail: Loc,
}
pub fn Span(head: Loc, tail: Loc) -> Span {
pub const fn Span(head: Loc, tail: Loc) -> Span {
Span { head, tail }
}
impl Span {
pub const fn dummy() -> Self {
Span { head: Loc::dummy(), tail: Loc::dummy() }
}
}
/// Stores a read-only (line, column) location in a token stream
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Loc {
line: u32,
col: u32,
}
pub fn Loc(line: u32, col: u32) -> Loc {
pub const fn Loc(line: u32, col: u32) -> Loc {
Loc { line, col }
}
impl Loc {
pub fn line(self) -> u32 {
pub const fn dummy() -> Self {
Loc { line: 0, col: 0 }
}
pub const fn line(self) -> u32 {
self.line
}
pub fn col(self) -> u32 {
pub const fn col(self) -> u32 {
self.col
}
}

View File

@@ -164,8 +164,10 @@ 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> {
@@ -617,7 +619,6 @@ mod tests {
assert_eq!(std::mem::size_of::<usize>(), std::mem::size_of_val(&v))
}
#[test]
#[cfg_attr(debug_assertions, ignore = "calls ().drop() usize::MAX times")]
fn from_usize_max_zst_array() {
let mut v = Stack::from([(); usize::MAX]);
assert_eq!(v.len(), usize::MAX);

View File

@@ -13,6 +13,7 @@ pub enum TokenKind {
/// A non-keyword identifier
Identifier,
// A keyword
As,
Break,
Cl,
Const,
@@ -26,6 +27,7 @@ pub enum TokenKind {
Impl,
In,
Let,
Loop,
Mod,
Mut,
Pub,
@@ -37,6 +39,7 @@ pub enum TokenKind {
Super,
True,
Type,
Use,
While,
/// Delimiter or punctuation
Punct(Punct),
@@ -109,6 +112,7 @@ impl Display for TokenKind {
TokenKind::Literal => "literal".fmt(f),
TokenKind::Identifier => "identifier".fmt(f),
TokenKind::As => "as".fmt(f),
TokenKind::Break => "break".fmt(f),
TokenKind::Cl => "cl".fmt(f),
TokenKind::Const => "const".fmt(f),
@@ -122,6 +126,7 @@ impl Display for TokenKind {
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),
@@ -133,6 +138,7 @@ impl Display for TokenKind {
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),
@@ -145,6 +151,7 @@ impl FromStr for TokenKind {
/// Parses a string s to return a Keyword
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"as" => Self::As,
"break" => Self::Break,
"cl" => Self::Cl,
"const" => Self::Const,
@@ -158,6 +165,7 @@ impl FromStr for TokenKind {
"impl" => Self::Impl,
"in" => Self::In,
"let" => Self::Let,
"loop" => Self::Loop,
"mod" => Self::Mod,
"mut" => Self::Mut,
"pub" => Self::Pub,
@@ -169,6 +177,7 @@ impl FromStr for TokenKind {
"super" => Self::Super,
"true" => Self::True,
"type" => Self::Type,
"use" => Self::Use,
"while" => Self::While,
_ => Err(())?,
})

View File

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

@@ -15,7 +15,7 @@ Meta = Identifier ('=' Literal | '(' (Literal ',')* Literal? ')')? ;
Item = Attrs Visibility ItemKind ;
ItemKind = Const | Static | Module
| Function | Struct | Enum
| Alias | Impl ;
| Alias | Impl | Use ;
(* item *)
@@ -45,20 +45,26 @@ 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? ')' ;
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 ;
@@ -78,7 +84,8 @@ Bool = "true" | "false" ;
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 ; *)
Compare = Range (CompareOp Range )* ;
@@ -91,19 +98,23 @@ 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 ']' ;
@@ -114,6 +125,7 @@ Block = '{' Stmt* '}';
Group = Empty | '(' (Expr | Tuple) ')' ;
Tuple = (Expr ',')* Expr? ;
Loop = "loop" Block ;
While = "while" Expr Block Else ;
If = "if" Expr Block Else ;
For = "for" Identifier "in" Expr Block Else ;
@@ -122,7 +134,8 @@ Break = "break" Expr ;
Return = "return" Expr ;
Continue = "continue" ;
AssignKind = '=' | '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' |'<<=' |'>>=' ;
AssignKind = '=' ;
ModifyKind = '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' |'<<=' |'>>=' ;
CompareOp = '<' | '<=' | '==' | '!=' | '>=' | '>' ;
RangeOp = '..' | '..=' ;

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();
}
}
}

View File

@@ -1,16 +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))
}
}
/// Implements the classic recursive definition of fib()
/// The classic recursive definition of fib()
fn fib(a: i64) -> i64 {
if a > 1 {
fib(a - 1) + fib(a - 2)
} else {
1
}
} 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
}

36
sample-code/math.cl Normal file
View File

@@ -0,0 +1,36 @@
//! Useful math functions
pub fn max(a: T, b: T) -> T {
(if a < b { b } else { a })
}
pub fn min(a: T, b: T) -> T {
(if a > b { b } else { a })
}
pub fn count_leading_zeroes(n: u64) -> u64 {
let mut xd = 64;
if n < 0 {
return 0;
}
while n != 0 {
xd -= 1;
n >>= 1;
}
xd
}
pub fn count_trailing_zeroes(n: u64) -> u64 {
let mut xd = 0;
if n == 0 {
64
} else if n < 0 {
0
} else {
while n & 1 == 0 {
xd += 1;
n >>= 1;
}
xd
}
}

View File

@@ -0,0 +1,17 @@
//! Demonstrates modules
fn main() {
use foo::bar::baz; // -> use super::baz::qux as baz: 10 -> fn baz::qux: 12
baz()
}
mod foo {
mod bar {
use super::baz::qux as baz; // -> fn qux: 14
}
mod baz {
fn qux() {
print("foo, bar, baz, qux")
}
}
}

View File

@@ -1,11 +1,10 @@
//! # The Conlang Standard Library
/// 32-bit signed integer type
#[intrinsic = "i32"]
type i32;
/// 32-bit unsigned integer type
#[intrinsic = "u32"]
type u32;
pub mod preamble {
pub use super::num::*;
}
pub mod num;
#[cfg("test")]
mod test;

462
stdlib/num.cl Normal file
View File

@@ -0,0 +1,462 @@
//! The primitive numeric types
#[intrinsic = "bool"]
pub type bool;
#[intrinsic = "char"]
pub type char;
#[intrinsic = "i8"]
pub type i8;
#[intrinsic = "i16"]
pub type i16;
#[intrinsic = "i32"]
pub type i32;
#[intrinsic = "i64"]
pub type i64;
#[intrinsic = "isize"]
pub type isize;
#[intrinsic = "u8"]
pub type u8;
#[intrinsic = "u16"]
pub type u16;
#[intrinsic = "u32"]
pub type u32;
#[intrinsic = "u64"]
pub type u64;
#[intrinsic = "usize"]
pub type usize;
// Contains implementations for (TODO) overloaded operators on num types
pub mod ops {
use super::*;
// Represents an ordering comparison verdict
pub enum Ordering {
Less,
Equal,
Greater,
}
impl bool {
pub const MIN: Self = false;
pub const MAX: Self = true;
pub const BIT_WIDTH: u32 = 1;
pub fn default() -> Self {
false
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl char {
pub fn default() -> Self {
'\u{1f988}'
}
}
impl u8 {
pub const MIN: Self = 0;
pub const MAX: Self = !0;
pub const BIT_WIDTH: u32 = 1;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl u16 {
pub const MIN: Self = 0;
pub const MAX: Self = !0;
pub const BIT_WIDTH: u32 = 2;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl u32 {
pub const MIN: Self = 0;
pub const MAX: Self = !0;
pub const BIT_WIDTH: u32 = 4;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl u64 {
pub const MIN: Self = 0;
pub const MAX: Self = !0;
pub const BIT_WIDTH: u32 = 8;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl usize {
pub const MIN: Self = __march_ptr_width_unsigned_min();
pub const MAX: Self = __march_ptr_width_unsigned_max();
pub const BIT_WIDTH: u32 = __march_ptr_width_bits();
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl i8 {
pub const MIN: Self = -128;
pub const MAX: Self = 127;
pub const BIT_WIDTH: u32 = 1;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl i16 {
pub const MIN: Self = -32768;
pub const MAX: Self = 32767;
pub const BIT_WIDTH: u32 = 2;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl i32 {
pub const MIN: Self = -2147483648;
pub const MAX: Self = 2147483647;
pub const BIT_WIDTH: u32 = 4;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl i64 {
pub const MIN: Self = -9223372036854775808;
pub const MAX: Self = 9223372036854775807;
pub const BIT_WIDTH: u32 = 8;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl isize {
pub const MIN: Self = __march_ptr_width_signed_min();
pub const MAX: Self = __march_ptr_width_signed_max();
pub const BIT_WIDTH: u32 = __march_ptr_width_bits();
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
}

64
stdlib/test.cl Normal file
View File

@@ -0,0 +1,64 @@
//! Tests for funky behavior
use super::preamble::*;
struct UnitLike;
struct TupleLike(super::num::i32, super::self::test::char)
struct StructLike {
pub member1: UnitLike,
member2: TupleLike,
}
enum NeverLike;
enum EmptyLike {
Empty,
}
enum Hodgepodge {
Empty,
UnitLike (),
Tuple (TupleLike),
StructEmpty {},
StructLike { member1: UnitLike, member2: TupleLike },
}
fn noop () -> bool {
loop if false {
} else break loop if false {
} else break loop if false {
} else break true;
}
fn while_else() -> i32 {
while conditional {
pass
} else {
fail
}
}
fn if_else() -> i32 {
// block 1
let x = 10;
let y = x + 2;
if x > 10
// compare x and 10
// end block 1, goto block 2 else goto block 3
{
// block 2
4
// end block 2, goto block 4
} else {
// block 3
5
// end block 3, goto block 4
}
// block 4
}