Compare commits
225 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8675f91aca | |||
| de63a8c123 | |||
| 533436afc1 | |||
| 1eb0516baf | |||
| 97808fd855 | |||
| 388a69948e | |||
| 5e7ba6de24 | |||
| adb0fd229c | |||
| 0e545077c6 | |||
| b64cc232f9 | |||
| b0341f06fd | |||
| a3e383b53f | |||
| 1b217b2e75 | |||
| 5662bd8524 | |||
| 28f9048087 | |||
| b17164b68b | |||
| ecebefe218 | |||
| fc374e0108 | |||
| 4295982876 | |||
| 729155d3a4 | |||
| 8c0ae02a71 | |||
| 7f7836877e | |||
| b2733aa171 | |||
| a233bb18bc | |||
| e06a27a5b1 | |||
| 3f5c5480ae | |||
| 53cf71608a | |||
| 883c2677d9 | |||
| 7d98ef87d5 | |||
| a188c5b65e | |||
| 872818fe7c | |||
| 3aef055739 | |||
| 38a5d31b08 | |||
| e43847bbd4 | |||
| a8b8a91c79 | |||
| 695c812bf5 | |||
| 524c84be9e | |||
| 4096442f75 | |||
| 03a4e76292 | |||
| 46a1639990 | |||
| 5ea8039a8a | |||
| 479efbad73 | |||
| a462dd2be3 | |||
| 4d6b94b570 | |||
| fe2b816f27 | |||
| e19127facc | |||
| b7ad285a11 | |||
| 61d8cf8550 | |||
| 70872d86f9 | |||
| 6bf34fdff6 | |||
| 9d7ab77999 | |||
| 82b71e2517 | |||
| 46bd44bd99 | |||
| 3511575669 | |||
| b3d62c09aa | |||
| ded100bf71 | |||
| c9ddebb946 | |||
| 15c4b89bce | |||
| aa7612926e | |||
| fffc370380 | |||
| a646a9e521 | |||
| 5f57924f23 | |||
| d692f6bb80 | |||
| 58c5a01312 | |||
| 16baaa32f1 | |||
| 3c4d31c473 | |||
| d723f7cece | |||
| b446677eda | |||
| 0beb121f32 | |||
| 6b16c55d97 | |||
| c16dbca55c | |||
| 4c883d87a4 | |||
| 1c3a56f5b5 | |||
| 406bfb8882 | |||
| e0f54aea97 | |||
| fa8a71addc | |||
| 0cc0cb5cfb | |||
| f330a7eaa5 | |||
| 8d8928b8a8 | |||
| a033e9f33b | |||
| be81221895 | |||
| 33b7cd3971 | |||
| c9266d971f | |||
| f76756e0e4 | |||
| a89f45aa58 | |||
| d2eb165759 | |||
| edf175e53b | |||
| 6aea23c8ba | |||
| db0b791b24 | |||
| 7c73fd335c | |||
| d7ce33e457 | |||
| 0d937728ed | |||
| a8ef989084 | |||
| e7c5a02afa | |||
| 12046fa9f7 | |||
| fb7de717d0 | |||
| 3fe5916a4f | |||
| 2c57f848ea | |||
| 81cf05cc69 | |||
| 83423f37be | |||
| ecf97801d6 | |||
| 71745161c4 | |||
| 9566f098ac | |||
| b9085551e1 | |||
| a877c0d726 | |||
| 893b716c86 | |||
| e49b171bea | |||
| 901e9d1d5b | |||
| aa3f357fca | |||
| d4432cda7a | |||
| 40ec9b30e4 | |||
| ede00c3c86 | |||
| be604b7b45 | |||
| e70ffd1895 | |||
| f24bd10c53 | |||
| 8453b092f1 | |||
| 42307d2ab4 | |||
| 45d75bb552 | |||
| b74c4cd5bf | |||
| 0c518b47e6 | |||
| 169f61144b | |||
| a3a87e0b67 | |||
| ed9b73a1a3 | |||
| 9b11543396 | |||
| 2ed8481489 | |||
| a3bb1ef447 | |||
| f483d690e2 | |||
| 087969e117 | |||
| 116d98437c | |||
| 8121c1c8bb | |||
| 2a5e965edf | |||
| bf16338166 | |||
| 9449e5ba06 | |||
| b796411742 | |||
| ef190f2d66 | |||
| 9c3c2e8674 | |||
| 02323ae6f2 | |||
| e36a684422 | |||
| 5341631781 | |||
| efd442bbfa | |||
| 9dc0cc7841 | |||
| 90a3818ca0 | |||
| 2a62a1c714 | |||
| 01ffdb67a6 | |||
| de024b6cb7 | |||
| 2834e4a8ea | |||
| 4ff101f0ee | |||
| 1fa027a0c2 | |||
| 9a687624fc | |||
| e102ae25b4 | |||
| a56ee38b15 | |||
| f315fb5af7 | |||
| e4f270da17 | |||
| 17a522b633 | |||
| 736fc37a81 | |||
| 02b775259e | |||
| 00d72b823a | |||
| ec1a1255ad | |||
| 0e8b4f68c3 | |||
| eee9e99aed | |||
| f6e44f3773 | |||
| 9e90eea7b6 | |||
| 83694988c3 | |||
| 98868d3960 | |||
| 75adbd6473 | |||
| d0ed8309f4 | |||
| 0fab11c11b | |||
| f958bbcb79 | |||
| d07a3e1455 | |||
| 489a1f7944 | |||
| bc33b60265 | |||
| 89cd1393ed | |||
| 3bebac6798 | |||
| 6ea99fc6f5 | |||
| 6589376870 | |||
| fc3cbbf450 | |||
| 2c36ccc0cf | |||
| 265db668ed | |||
| fa51f14db5 | |||
| 3b0190b389 | |||
| 21c9909f0c | |||
| 290ede2fa3 | |||
| 2091cce570 | |||
| 902494e95a | |||
| a213c7f70a | |||
| 8dfddb739e | |||
| a31d285d99 | |||
| a036ce260d | |||
| 4a52d2bc6a | |||
| 614d20ea2c | |||
| 7b40ddc845 | |||
| bdf0bb68ca | |||
| 8ee318f26b | |||
| ba148ef5de | |||
| 8cbe570811 | |||
| 66c29d601c | |||
| 9f9a21b4c3 | |||
| 2cdf112aa6 | |||
| af35dd1bb3 | |||
| ecde44910f | |||
| a74cd0b8ac | |||
| 2eade74d3a | |||
| 9cae7e4eb8 | |||
| a07312bf92 | |||
| a9b834e012 | |||
| 57dada7aba | |||
| ba6285e006 | |||
| c7fdeaf37a | |||
| 09737aa40b | |||
| 1eec1b06ce | |||
| 276f0b1031 | |||
| d7604ba039 | |||
| c71f68eb55 | |||
| c665e52782 | |||
| 50b473cd55 | |||
| abf00f383c | |||
| ab17ebbadc | |||
| cc281fc6ab | |||
| 1afde9ce35 | |||
| 6e1d5af134 | |||
| ee27095fb3 | |||
| 69f5035a8b | |||
| 362817e512 | |||
| 421aab3aa2 | |||
| 5eb6411d53 |
@@ -7,7 +7,7 @@ labels:
|
|||||||
- enhancement
|
- enhancement
|
||||||
---
|
---
|
||||||
# Feature Progress
|
# Feature Progress
|
||||||
<!-- Describe the steps for implementing this feature in libconlang -->
|
<!-- Describe the steps for implementing this feature -->
|
||||||
- [ ] <!-- Step 1 of implementing a feature -->
|
- [ ] <!-- Step 1 of implementing a feature -->
|
||||||
|
|
||||||
# Feature description
|
# Feature description
|
||||||
@@ -21,4 +21,4 @@ since it most closely matches what I'm currently aiming for
|
|||||||
-->
|
-->
|
||||||
```rust
|
```rust
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,3 +1,10 @@
|
|||||||
|
|
||||||
|
# Visual Studio Code config
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Rust
|
||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
target
|
target
|
||||||
|
|
||||||
|
# Pest files generated by Grammatical
|
||||||
*.p*st
|
*.p*st
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -1,11 +1,25 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["libconlang", "cl-repl"]
|
members = [
|
||||||
|
"compiler/cl-repl",
|
||||||
|
"compiler/cl-typeck",
|
||||||
|
"compiler/cl-interpret",
|
||||||
|
"compiler/cl-structures",
|
||||||
|
"compiler/cl-token",
|
||||||
|
"compiler/cl-ast",
|
||||||
|
"compiler/cl-parser",
|
||||||
|
"compiler/cl-lexer",
|
||||||
|
"compiler/cl-arena",
|
||||||
|
"repline",
|
||||||
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
repository = "https://git.soft.fish/j/Conlang"
|
repository = "https://git.soft.fish/j/Conlang"
|
||||||
version = "0.0.3"
|
version = "0.0.7"
|
||||||
authors = ["John Breaux <j@soft.fish>"]
|
authors = ["John Breaux <j@soft.fish>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
publish = ["soft-fish"]
|
publish = ["soft-fish"]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|||||||
@@ -1,523 +0,0 @@
|
|||||||
//! Collects identifiers into a list
|
|
||||||
|
|
||||||
use cl_repl::repline::Repline;
|
|
||||||
use conlang::{common::Loc, lexer::Lexer, parser::Parser};
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
error::Error,
|
|
||||||
fmt::Display,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
|
|
||||||
while let Ok(line) = rl.read() {
|
|
||||||
let mut parser = Parser::new(Lexer::new(&line));
|
|
||||||
let code = match parser.stmt() {
|
|
||||||
Ok(code) => {
|
|
||||||
rl.accept();
|
|
||||||
code
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let c = Collector::from(&code);
|
|
||||||
print!("\x1b[G\x1b[J{c}");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ties the [Collector] location stack to the program stack
|
|
||||||
pub struct CollectorLoc<'loc, 'code> {
|
|
||||||
inner: &'loc mut Collector<'code>,
|
|
||||||
}
|
|
||||||
impl<'l, 'c> CollectorLoc<'l, 'c> {
|
|
||||||
pub fn new(c: &'l mut Collector<'c>, loc: Loc) -> Self {
|
|
||||||
c.location.push(loc);
|
|
||||||
Self { inner: c }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'l, 'c> Deref for CollectorLoc<'l, 'c> {
|
|
||||||
type Target = Collector<'c>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'l, 'c> DerefMut for CollectorLoc<'l, 'c> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'l, 'c> Drop for CollectorLoc<'l, 'c> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let Self { inner: c } = self;
|
|
||||||
c.location.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Collector<'code> {
|
|
||||||
location: Vec<Loc>,
|
|
||||||
defs: HashMap<&'code str, Vec<Loc>>,
|
|
||||||
refs: HashMap<&'code str, Vec<Loc>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c> Collector<'c> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
pub fn location<'loc>(&'loc mut self, loc: Loc) -> CollectorLoc<'loc, 'c> {
|
|
||||||
CollectorLoc::new(self, loc)
|
|
||||||
}
|
|
||||||
pub fn collect(&mut self, collectible: &'c impl Collectible<'c>) -> &mut Self {
|
|
||||||
collectible.collect(self);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn define(&mut self, name: &'c str) -> &mut Self {
|
|
||||||
// println!("Inserted definition of {name}");
|
|
||||||
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
|
|
||||||
self.defs
|
|
||||||
.entry(name)
|
|
||||||
.and_modify(|c| c.push(loc))
|
|
||||||
.or_insert_with(|| vec![loc]);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn reference(&mut self, name: &'c str) -> &mut Self {
|
|
||||||
// println!("Inserted usage of {name}");
|
|
||||||
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
|
|
||||||
self.refs
|
|
||||||
.entry(name)
|
|
||||||
.and_modify(|c| c.push(loc))
|
|
||||||
.or_insert_with(|| vec![loc]);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Display for Collector<'c> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let Self { location: _, defs, refs } = self;
|
|
||||||
writeln!(f, "Definitions:")?;
|
|
||||||
for (name, locs) in defs {
|
|
||||||
for loc in locs {
|
|
||||||
writeln!(f, "{loc} {name}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeln!(f, "Usages:")?;
|
|
||||||
for (name, locs) in refs {
|
|
||||||
for loc in locs {
|
|
||||||
writeln!(f, "{loc} {name}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c, C: Collectible<'c>> From<&'c C> for Collector<'c> {
|
|
||||||
fn from(value: &'c C) -> Self {
|
|
||||||
let mut c = Self::new();
|
|
||||||
c.collect(value);
|
|
||||||
c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use collectible::Collectible;
|
|
||||||
pub mod collectible {
|
|
||||||
|
|
||||||
use super::Collector;
|
|
||||||
use conlang::ast::*;
|
|
||||||
pub trait Collectible<'code> {
|
|
||||||
fn collect(&'code self, c: &mut Collector<'code>);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c> Collectible<'c> for File {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let File { items } = self;
|
|
||||||
for item in items {
|
|
||||||
item.collect(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Item {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { extents, attrs: _, vis: _, kind } = self;
|
|
||||||
kind.collect(&mut c.location(extents.head));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for ItemKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
ItemKind::Alias(f) => f.collect(c),
|
|
||||||
ItemKind::Const(f) => f.collect(c),
|
|
||||||
ItemKind::Static(f) => f.collect(c),
|
|
||||||
ItemKind::Module(f) => f.collect(c),
|
|
||||||
ItemKind::Function(f) => f.collect(c),
|
|
||||||
ItemKind::Struct(f) => f.collect(c),
|
|
||||||
ItemKind::Enum(f) => f.collect(c),
|
|
||||||
ItemKind::Impl(f) => f.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Alias {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { to, from } = self;
|
|
||||||
c.collect(to).collect(from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Const {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { name: Identifier(name), ty, init } = self;
|
|
||||||
c.define(name).collect(init).collect(ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Static {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { mutable: _, name: Identifier(name), ty, init } = self;
|
|
||||||
c.define(name).collect(init).collect(ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Module {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { name: Identifier(name), kind } = self;
|
|
||||||
c.define(name).collect(kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for ModuleKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
ModuleKind::Inline(f) => f.collect(c),
|
|
||||||
ModuleKind::Outline => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Function {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { name: Identifier(name), args, body, rety } = self;
|
|
||||||
c.define(name).collect(args).collect(body).collect(rety);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Struct {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { name: Identifier(name), kind } = self;
|
|
||||||
c.define(name).collect(kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for StructKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
StructKind::Empty => {}
|
|
||||||
StructKind::Tuple(k) => k.collect(c),
|
|
||||||
StructKind::Struct(k) => k.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for StructMember {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { vis: _, name: Identifier(name), ty } = self;
|
|
||||||
c.define(name).collect(ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Enum {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { name: Identifier(name), kind } = self;
|
|
||||||
c.define(name).collect(kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for EnumKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
EnumKind::NoVariants => {}
|
|
||||||
EnumKind::Variants(v) => v.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Variant {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { name: Identifier(name), kind } = self;
|
|
||||||
c.define(name).collect(kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for VariantKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
VariantKind::Plain => {}
|
|
||||||
VariantKind::CLike(_) => {}
|
|
||||||
VariantKind::Tuple(v) => v.collect(c),
|
|
||||||
VariantKind::Struct(v) => v.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Impl {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { target, body } = self;
|
|
||||||
c.collect(target).collect(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Block {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { stmts } = self;
|
|
||||||
for stmt in stmts {
|
|
||||||
stmt.collect(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Stmt {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { extents, kind, semi: _ } = self;
|
|
||||||
c.location(extents.head).collect(kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for StmtKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
StmtKind::Empty => {}
|
|
||||||
StmtKind::Local(s) => s.collect(c),
|
|
||||||
StmtKind::Item(s) => s.collect(c),
|
|
||||||
StmtKind::Expr(s) => s.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Let {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { mutable: _, name: Identifier(name), ty, init } = self;
|
|
||||||
c.collect(init).collect(ty).define(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Expr {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { extents, kind } = self;
|
|
||||||
c.location(extents.head).collect(kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for ExprKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
ExprKind::Assign(k) => k.collect(c),
|
|
||||||
ExprKind::Binary(k) => k.collect(c),
|
|
||||||
ExprKind::Unary(k) => k.collect(c),
|
|
||||||
ExprKind::Member(k) => k.collect(c),
|
|
||||||
ExprKind::Call(k) => k.collect(c),
|
|
||||||
ExprKind::Index(k) => k.collect(c),
|
|
||||||
ExprKind::Path(k) => k.collect(c),
|
|
||||||
ExprKind::Literal(k) => k.collect(c),
|
|
||||||
ExprKind::Array(k) => k.collect(c),
|
|
||||||
ExprKind::ArrayRep(k) => k.collect(c),
|
|
||||||
ExprKind::AddrOf(k) => k.collect(c),
|
|
||||||
ExprKind::Block(k) => k.collect(c),
|
|
||||||
ExprKind::Empty => {}
|
|
||||||
ExprKind::Group(k) => k.collect(c),
|
|
||||||
ExprKind::Tuple(k) => k.collect(c),
|
|
||||||
ExprKind::While(k) => k.collect(c),
|
|
||||||
ExprKind::If(k) => k.collect(c),
|
|
||||||
ExprKind::For(k) => k.collect(c),
|
|
||||||
ExprKind::Break(k) => k.collect(c),
|
|
||||||
ExprKind::Return(k) => k.collect(c),
|
|
||||||
ExprKind::Continue(k) => k.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Assign {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { head, op: _, tail } = self;
|
|
||||||
c.collect(head).collect(tail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Binary {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { head, tail } = self;
|
|
||||||
c.collect(head);
|
|
||||||
for (_, tail) in tail {
|
|
||||||
c.collect(tail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Unary {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { ops: _, tail } = self;
|
|
||||||
c.collect(tail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Member {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { head, tail } = self;
|
|
||||||
c.collect(head).collect(tail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Call {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { callee, args } = self;
|
|
||||||
c.collect(callee).collect(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Tuple {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { exprs } = self;
|
|
||||||
c.collect(exprs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Index {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { head, indices } = self;
|
|
||||||
c.collect(head).collect(indices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Indices {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { exprs } = self;
|
|
||||||
c.collect(exprs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Array {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { values } = self;
|
|
||||||
c.collect(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for ArrayRep {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { value, repeat } = self;
|
|
||||||
c.collect(value).collect(repeat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for AddrOf {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { count: _, mutable: _, expr } = self;
|
|
||||||
c.collect(expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Group {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { expr } = self;
|
|
||||||
c.collect(expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for While {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { cond, pass, fail } = self;
|
|
||||||
c.collect(cond).collect(pass).collect(fail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Else {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { body } = self;
|
|
||||||
c.collect(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for If {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { cond, pass, fail } = self;
|
|
||||||
c.collect(cond).collect(pass).collect(fail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for For {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { bind: Identifier(name), cond, pass, fail } = self;
|
|
||||||
c.collect(cond).define(name).collect(pass).collect(fail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Break {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { body } = self;
|
|
||||||
c.collect(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Return {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { body } = self;
|
|
||||||
c.collect(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Continue {
|
|
||||||
fn collect(&'c self, _c: &mut Collector<'c>) {}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Literal {
|
|
||||||
fn collect(&'c self, _c: &mut Collector<'c>) {}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Identifier {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self(name) = self;
|
|
||||||
c.reference(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Param {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { mutability: _, name, ty } = self;
|
|
||||||
c.collect(name).collect(ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Ty {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { extents, kind } = self;
|
|
||||||
c.location(extents.head).collect(kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for TyKind {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
TyKind::Never => {}
|
|
||||||
TyKind::Empty => {}
|
|
||||||
TyKind::SelfTy => {}
|
|
||||||
TyKind::Path(t) => t.collect(c),
|
|
||||||
TyKind::Tuple(t) => t.collect(c),
|
|
||||||
TyKind::Ref(t) => t.collect(c),
|
|
||||||
TyKind::Fn(t) => t.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for Path {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { absolute: _, parts } = self;
|
|
||||||
for part in parts {
|
|
||||||
c.collect(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for PathPart {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
match self {
|
|
||||||
PathPart::SuperKw => {}
|
|
||||||
PathPart::SelfKw => {}
|
|
||||||
PathPart::Ident(i) => i.collect(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for TyTuple {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { types } = self;
|
|
||||||
for ty in types {
|
|
||||||
c.collect(ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for TyRef {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { count: _, to } = self;
|
|
||||||
c.collect(to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c> Collectible<'c> for TyFn {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
let Self { args, rety } = self;
|
|
||||||
c.collect(args).collect(rety);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Vec<C> {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
for i in self {
|
|
||||||
c.collect(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Option<C> {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
if let Some(i) = self {
|
|
||||||
c.collect(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'c, C: Collectible<'c>> Collectible<'c> for Box<C> {
|
|
||||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
|
||||||
c.collect(self.as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
//! Utilities for cl-frontend
|
|
||||||
//!
|
|
||||||
//! # TODO
|
|
||||||
//! - [ ] Readline-like line editing
|
|
||||||
//! - [ ] Raw mode?
|
|
||||||
|
|
||||||
pub mod args {
|
|
||||||
use crate::cli::Mode;
|
|
||||||
use std::{
|
|
||||||
io::{stdin, IsTerminal},
|
|
||||||
ops::Deref,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct Args {
|
|
||||||
pub path: Option<PathBuf>, // defaults None
|
|
||||||
pub repl: bool, // defaults true if stdin is terminal
|
|
||||||
pub mode: Mode, // defaults Interpret
|
|
||||||
}
|
|
||||||
const HELP: &str = "[( --repl | --no-repl )] [--mode (tokens | pretty | type | run)] [( -f | --file ) <filename>]";
|
|
||||||
|
|
||||||
impl Args {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret }
|
|
||||||
}
|
|
||||||
pub fn parse(mut self) -> Option<Self> {
|
|
||||||
let mut args = std::env::args();
|
|
||||||
let name = args.next().unwrap_or_default();
|
|
||||||
let mut unknown = false;
|
|
||||||
while let Some(arg) = args.next() {
|
|
||||||
match arg.deref() {
|
|
||||||
"--repl" => self.repl = true,
|
|
||||||
"--no-repl" => self.repl = false,
|
|
||||||
"-f" | "--file" => self.path = args.next().map(PathBuf::from),
|
|
||||||
"-m" | "--mode" => {
|
|
||||||
self.mode = args.next().unwrap_or_default().parse().unwrap_or_default()
|
|
||||||
}
|
|
||||||
arg => {
|
|
||||||
eprintln!("Unknown argument: {arg}");
|
|
||||||
unknown = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if unknown {
|
|
||||||
println!("Usage: {name} {HELP}");
|
|
||||||
None?
|
|
||||||
}
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
/// Returns the path to a file, if one was specified
|
|
||||||
pub fn path(&self) -> Option<&Path> {
|
|
||||||
self.path.as_deref()
|
|
||||||
}
|
|
||||||
/// Returns whether to start a REPL session or not
|
|
||||||
pub fn repl(&self) -> bool {
|
|
||||||
self.repl
|
|
||||||
}
|
|
||||||
/// Returns the repl Mode
|
|
||||||
pub fn mode(&self) -> Mode {
|
|
||||||
self.mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Default for Args {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod program {
|
|
||||||
use std::{fmt::Display, io::Write};
|
|
||||||
|
|
||||||
use conlang::{
|
|
||||||
ast::{self, ast_impl::format::Pretty},
|
|
||||||
interpreter::{
|
|
||||||
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
|
|
||||||
},
|
|
||||||
// pretty_printer::{PrettyPrintable, Printer},
|
|
||||||
lexer::Lexer,
|
|
||||||
parser::{error::PResult, Parser},
|
|
||||||
resolver::{error::TyResult, Resolver},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Parsable;
|
|
||||||
|
|
||||||
pub enum Parsed {
|
|
||||||
File(ast::File),
|
|
||||||
Stmt(ast::Stmt),
|
|
||||||
Expr(ast::Expr),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Program<'t, Variant> {
|
|
||||||
text: &'t str,
|
|
||||||
data: Variant,
|
|
||||||
}
|
|
||||||
impl<'t, V> Program<'t, V> {
|
|
||||||
pub fn lex(&self) -> Lexer {
|
|
||||||
Lexer::new(self.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'t> Program<'t, Parsable> {
|
|
||||||
pub fn new(text: &'t str) -> Self {
|
|
||||||
Self { text, data: Parsable }
|
|
||||||
}
|
|
||||||
pub fn parse(self) -> PResult<Program<'t, Parsed>> {
|
|
||||||
self.parse_file().or_else(|_| self.parse_stmt())
|
|
||||||
}
|
|
||||||
pub fn parse_expr(&self) -> PResult<Program<'t, Parsed>> {
|
|
||||||
Ok(Program { data: Parsed::Expr(Parser::new(self.lex()).expr()?), text: self.text })
|
|
||||||
}
|
|
||||||
pub fn parse_stmt(&self) -> PResult<Program<'t, Parsed>> {
|
|
||||||
Ok(Program { data: Parsed::Stmt(Parser::new(self.lex()).stmt()?), text: self.text })
|
|
||||||
}
|
|
||||||
pub fn parse_file(&self) -> PResult<Program<'t, Parsed>> {
|
|
||||||
Ok(Program { data: Parsed::File(Parser::new(self.lex()).file()?), text: self.text })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'t> Program<'t, Parsed> {
|
|
||||||
pub fn debug(&self) {
|
|
||||||
match &self.data {
|
|
||||||
Parsed::File(v) => eprintln!("{v:?}"),
|
|
||||||
Parsed::Stmt(v) => eprintln!("{v:?}"),
|
|
||||||
Parsed::Expr(v) => eprintln!("{v:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn print(&self) {
|
|
||||||
let mut f = std::io::stdout().pretty();
|
|
||||||
let _ = match &self.data {
|
|
||||||
Parsed::File(v) => writeln!(f, "{v}"),
|
|
||||||
Parsed::Stmt(v) => writeln!(f, "{v}"),
|
|
||||||
Parsed::Expr(v) => writeln!(f, "{v}"),
|
|
||||||
};
|
|
||||||
// println!("{self}")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
|
|
||||||
todo!("Program::resolve(\n{self},\n{resolver:?}\n)")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&self, env: &mut Environment) -> IResult<ConValue> {
|
|
||||||
match &self.data {
|
|
||||||
Parsed::File(v) => v.interpret(env),
|
|
||||||
Parsed::Stmt(v) => v.interpret(env),
|
|
||||||
Parsed::Expr(v) => v.interpret(env),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
|
|
||||||
// match &mut self.data {
|
|
||||||
// Parsed::Program(start) => start.resolve(resolver),
|
|
||||||
// Parsed::Expr(expr) => expr.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// .map(|ty| println!("{ty}"))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Runs the [Program] in the specified [Environment]
|
|
||||||
// pub fn run(&self, env: &mut Environment) -> IResult<()> {
|
|
||||||
// println!(
|
|
||||||
// "{}",
|
|
||||||
// match &self.data {
|
|
||||||
// Parsed::Program(start) => env.eval(start)?,
|
|
||||||
// Parsed::Expr(expr) => env.eval(expr)?,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'t> Display for Program<'t, Parsed> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match &self.data {
|
|
||||||
Parsed::File(v) => write!(f, "{v}"),
|
|
||||||
Parsed::Stmt(v) => write!(f, "{v}"),
|
|
||||||
Parsed::Expr(v) => write!(f, "{v}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl PrettyPrintable for Program<Parsed> {
|
|
||||||
// fn visit<W: Write>(&self, p: &mut Printer<W>) -> IOResult<()> {
|
|
||||||
// match &self.data {
|
|
||||||
// Parsed::Program(value) => value.visit(p),
|
|
||||||
// Parsed::Expr(value) => value.visit(p),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod cli {
|
|
||||||
use conlang::{interpreter::env::Environment, resolver::Resolver, token::Token};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
args::Args,
|
|
||||||
program::{Parsable, Parsed, Program},
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
convert::Infallible,
|
|
||||||
error::Error,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ANSI color escape sequences
|
|
||||||
const ANSI_RED: &str = "\x1b[31m";
|
|
||||||
const ANSI_GREEN: &str = "\x1b[32m";
|
|
||||||
const ANSI_CYAN: &str = "\x1b[36m";
|
|
||||||
const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
|
|
||||||
const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
|
|
||||||
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
|
|
||||||
const ANSI_RESET: &str = "\x1b[0m";
|
|
||||||
const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
|
|
||||||
|
|
||||||
const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum CLI {
|
|
||||||
Repl(Repl),
|
|
||||||
File { mode: Mode, path: PathBuf },
|
|
||||||
Stdin { mode: Mode },
|
|
||||||
}
|
|
||||||
impl From<Args> for CLI {
|
|
||||||
fn from(value: Args) -> Self {
|
|
||||||
let Args { path, repl, mode } = value;
|
|
||||||
match (repl, path) {
|
|
||||||
(true, Some(path)) => {
|
|
||||||
let prog = std::fs::read_to_string(path).unwrap();
|
|
||||||
let code = conlang::parser::Parser::new(conlang::lexer::Lexer::new(&prog))
|
|
||||||
.file()
|
|
||||||
.unwrap();
|
|
||||||
let mut env = conlang::interpreter::env::Environment::new();
|
|
||||||
env.eval(&code).unwrap();
|
|
||||||
env.call("dump", &[])
|
|
||||||
.expect("calling dump in the environment shouldn't fail");
|
|
||||||
|
|
||||||
Self::Repl(Repl { mode, env, ..Default::default() })
|
|
||||||
}
|
|
||||||
(_, Some(path)) => Self::File { mode, path },
|
|
||||||
(true, None) => Self::Repl(Repl { mode, ..Default::default() }),
|
|
||||||
(false, None) => Self::Stdin { mode },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl CLI {
|
|
||||||
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
|
|
||||||
use std::{fs, io};
|
|
||||||
match self {
|
|
||||||
CLI::Repl(repl) => repl.repl(),
|
|
||||||
CLI::File { mode, ref path } => {
|
|
||||||
// read file
|
|
||||||
Self::no_repl(*mode, Some(path), &fs::read_to_string(path)?)
|
|
||||||
}
|
|
||||||
CLI::Stdin { mode } => {
|
|
||||||
Self::no_repl(*mode, None, &io::read_to_string(io::stdin())?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn no_repl(mode: Mode, path: Option<&Path>, code: &str) {
|
|
||||||
let program = Program::new(code);
|
|
||||||
match mode {
|
|
||||||
Mode::Tokenize => {
|
|
||||||
for token in program.lex() {
|
|
||||||
if let Some(path) = path {
|
|
||||||
print!("{}:", path.display());
|
|
||||||
}
|
|
||||||
match token {
|
|
||||||
Ok(token) => print_token(&token),
|
|
||||||
Err(e) => println!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Mode::Beautify => Self::beautify(program),
|
|
||||||
Mode::Resolve => Self::resolve(program, Default::default()),
|
|
||||||
Mode::Interpret => Self::interpret(program, Environment::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn beautify(program: Program<Parsable>) {
|
|
||||||
match program.parse() {
|
|
||||||
Ok(program) => program.print(),
|
|
||||||
Err(e) => eprintln!("{e}"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn resolve(program: Program<Parsable>, mut resolver: Resolver) {
|
|
||||||
let mut program = match program.parse() {
|
|
||||||
Ok(program) => program,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(e) = program.resolve(&mut resolver) {
|
|
||||||
eprintln!("{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn interpret(program: Program<Parsable>, mut interpreter: Environment) {
|
|
||||||
let program = match program.parse() {
|
|
||||||
Ok(program) => program,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(e) = program.run(&mut interpreter) {
|
|
||||||
eprintln!("{e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Err(e) = interpreter.call("main", &[]) {
|
|
||||||
eprintln!("{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The CLI's operating mode
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Mode {
|
|
||||||
Tokenize,
|
|
||||||
Beautify,
|
|
||||||
Resolve,
|
|
||||||
#[default]
|
|
||||||
Interpret,
|
|
||||||
}
|
|
||||||
impl Mode {
|
|
||||||
pub fn ansi_color(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Mode::Tokenize => ANSI_BRIGHT_BLUE,
|
|
||||||
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
|
|
||||||
Mode::Resolve => ANSI_GREEN,
|
|
||||||
Mode::Interpret => ANSI_CYAN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for Mode {
|
|
||||||
type Err = Infallible;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Infallible> {
|
|
||||||
Ok(match s {
|
|
||||||
"i" | "interpret" | "run" => Mode::Interpret,
|
|
||||||
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
|
|
||||||
"r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
|
|
||||||
"t" | "tokenize" | "tokens" => Mode::Tokenize,
|
|
||||||
_ => Mode::Interpret,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implements the interactive interpreter
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Repl {
|
|
||||||
prompt_again: &'static str, // " ?>"
|
|
||||||
prompt_begin: &'static str, // "cl>"
|
|
||||||
prompt_error: &'static str, // "! >"
|
|
||||||
env: Environment,
|
|
||||||
resolver: Resolver,
|
|
||||||
mode: Mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Repl {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
prompt_begin: "cl>",
|
|
||||||
prompt_again: " ?>",
|
|
||||||
prompt_error: "! >",
|
|
||||||
env: Default::default(),
|
|
||||||
resolver: Default::default(),
|
|
||||||
mode: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prompt functions
|
|
||||||
impl Repl {
|
|
||||||
pub fn prompt_error(&self, err: &impl Error) {
|
|
||||||
let Self { prompt_error: prompt, .. } = self;
|
|
||||||
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}",)
|
|
||||||
}
|
|
||||||
/// Resets the cursor to the start of the line, clears the terminal,
|
|
||||||
/// and sets the output color
|
|
||||||
pub fn begin_output(&self) {
|
|
||||||
print!("{ANSI_CLEAR_LINES}{ANSI_OUTPUT}")
|
|
||||||
}
|
|
||||||
pub fn clear_line(&self) {}
|
|
||||||
}
|
|
||||||
/// The actual REPL
|
|
||||||
impl Repl {
|
|
||||||
/// Constructs a new [Repl] with the provided [Mode]
|
|
||||||
pub fn new(mode: Mode) -> Self {
|
|
||||||
Self { mode, ..Default::default() }
|
|
||||||
}
|
|
||||||
/// Runs the main REPL loop
|
|
||||||
pub fn repl(&mut self) {
|
|
||||||
use crate::repline::{error::Error, Repline};
|
|
||||||
let mut rl = Repline::new(self.mode.ansi_color(), self.prompt_begin, self.prompt_again);
|
|
||||||
fn clear_line() {
|
|
||||||
print!("\x1b[G\x1b[J");
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
let buf = match rl.read() {
|
|
||||||
Ok(buf) => buf,
|
|
||||||
// Ctrl-C: break if current line is empty
|
|
||||||
Err(Error::CtrlC(buf)) => {
|
|
||||||
if buf.is_empty() || buf.ends_with('\n') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rl.accept();
|
|
||||||
println!("Cancelled. (Press Ctrl+C again to quit.)");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Ctrl-D: reset input, and parse it for errors
|
|
||||||
Err(Error::CtrlD(buf)) => {
|
|
||||||
rl.deny();
|
|
||||||
if let Err(e) = Program::new(&buf).parse() {
|
|
||||||
clear_line();
|
|
||||||
self.prompt_error(&e);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.prompt_error(&e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.begin_output();
|
|
||||||
if self.command(&buf) {
|
|
||||||
rl.deny();
|
|
||||||
rl.set_color(self.mode.ansi_color());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let code = Program::new(&buf);
|
|
||||||
if self.mode == Mode::Tokenize {
|
|
||||||
self.tokenize(&code);
|
|
||||||
rl.deny();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match code.lex().into_iter().find(|l| l.is_err()) {
|
|
||||||
None => {}
|
|
||||||
Some(Ok(_)) => unreachable!(),
|
|
||||||
Some(Err(error)) => {
|
|
||||||
rl.deny();
|
|
||||||
self.prompt_error(&error);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Ok(mut code) = code.parse() {
|
|
||||||
rl.accept();
|
|
||||||
self.dispatch(&mut code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn help(&self) {
|
|
||||||
println!(
|
|
||||||
"Commands:\n- $tokens\n Tokenize Mode:\n Outputs information derived by the Lexer\n- $pretty\n Beautify Mode:\n Pretty-prints the input\n- $type\n Resolve Mode:\n Attempts variable resolution and type-checking on the input\n- $run\n Interpret Mode:\n Interprets the input using Conlang\'s work-in-progress interpreter\n- $mode\n Prints the current mode\n- $help\n Prints this help message"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
fn command(&mut self, line: &str) -> bool {
|
|
||||||
match line.trim() {
|
|
||||||
"$pretty" => self.mode = Mode::Beautify,
|
|
||||||
"$tokens" => self.mode = Mode::Tokenize,
|
|
||||||
"$type" => self.mode = Mode::Resolve,
|
|
||||||
"$run" => self.mode = Mode::Interpret,
|
|
||||||
"$mode" => println!("{:?} Mode", self.mode),
|
|
||||||
"$help" => self.help(),
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
/// Dispatches calls to repl functions based on the program
|
|
||||||
fn dispatch(&mut self, code: &mut Program<Parsed>) {
|
|
||||||
match self.mode {
|
|
||||||
Mode::Tokenize => (),
|
|
||||||
Mode::Beautify => self.beautify(code),
|
|
||||||
Mode::Resolve => self.typecheck(code),
|
|
||||||
Mode::Interpret => self.interpret(code),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn tokenize(&mut self, code: &Program<Parsable>) {
|
|
||||||
for token in code.lex() {
|
|
||||||
match token {
|
|
||||||
Ok(token) => print_token(&token),
|
|
||||||
Err(e) => println!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn interpret(&mut self, code: &Program<Parsed>) {
|
|
||||||
if let Err(e) = code.run(&mut self.env) {
|
|
||||||
self.prompt_error(&e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn typecheck(&mut self, code: &mut Program<Parsed>) {
|
|
||||||
if let Err(e) = code.resolve(&mut self.resolver) {
|
|
||||||
self.prompt_error(&e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn beautify(&mut self, code: &Program<Parsed>) {
|
|
||||||
code.print()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_token(t: &Token) {
|
|
||||||
println!(
|
|
||||||
"{:02}:{:02}: {:#19} │{}│",
|
|
||||||
t.line(),
|
|
||||||
t.col(),
|
|
||||||
t.ty(),
|
|
||||||
t.data(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod repline;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
use cl_repl::{args::Args, cli::CLI};
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
CLI::from(Args::new().parse().unwrap_or_default()).run()
|
|
||||||
}
|
|
||||||
@@ -1,636 +0,0 @@
|
|||||||
//! A small pseudo-multiline editing library
|
|
||||||
// #![allow(unused)]
|
|
||||||
|
|
||||||
pub mod error {
|
|
||||||
/// Result type for Repline
|
|
||||||
pub type ReplResult<T> = std::result::Result<T, Error>;
|
|
||||||
/// Borrowed error (does not implement [Error](std::error::Error)!)
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
/// User broke with Ctrl+C
|
|
||||||
CtrlC(String),
|
|
||||||
/// User broke with Ctrl+D
|
|
||||||
CtrlD(String),
|
|
||||||
/// Invalid unicode codepoint
|
|
||||||
BadUnicode(u32),
|
|
||||||
/// Error came from [std::io]
|
|
||||||
IoFailure(std::io::Error),
|
|
||||||
/// End of input
|
|
||||||
EndOfInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
impl std::fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::CtrlC(_) => write!(f, "Ctrl+C"),
|
|
||||||
Error::CtrlD(_) => write!(f, "Ctrl+D"),
|
|
||||||
Error::BadUnicode(u) => write!(f, "0x{u:x} is not a valid unicode codepoint"),
|
|
||||||
Error::IoFailure(s) => write!(f, "{s}"),
|
|
||||||
Error::EndOfInput => write!(f, "End of input"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::io::Error> for Error {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IoFailure(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod ignore {
|
|
||||||
//! Does nothing, universally.
|
|
||||||
//!
|
|
||||||
//! Introduces the [Ignore] trait, and its singular function, [ignore](Ignore::ignore),
|
|
||||||
//! which does nothing.
|
|
||||||
impl<T> Ignore for T {}
|
|
||||||
/// Does nothing
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```rust
|
|
||||||
/// #![deny(unused_must_use)]
|
|
||||||
/// # use cl_frontend::repline::ignore::Ignore;
|
|
||||||
/// ().ignore();
|
|
||||||
/// Err::<(), &str>("Foo").ignore();
|
|
||||||
/// Some("Bar").ignore();
|
|
||||||
/// 42.ignore();
|
|
||||||
///
|
|
||||||
/// #[must_use]
|
|
||||||
/// fn the_meaning() -> usize {
|
|
||||||
/// 42
|
|
||||||
/// }
|
|
||||||
/// the_meaning().ignore();
|
|
||||||
/// ```
|
|
||||||
pub trait Ignore {
|
|
||||||
/// Does nothing
|
|
||||||
fn ignore(&self) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod chars {
|
|
||||||
//! Converts an <code>[Iterator]<Item = [u8]></code> into an
|
|
||||||
//! <code>[Iterator]<Item = [char]></code>
|
|
||||||
|
|
||||||
use super::error::*;
|
|
||||||
|
|
||||||
/// Converts an <code>[Iterator]<Item = [u8]></code> into an
|
|
||||||
/// <code>[Iterator]<Item = [char]></code>
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Chars<I: Iterator<Item = u8>>(pub I);
|
|
||||||
impl<I: Iterator<Item = u8>> Chars<I> {
|
|
||||||
pub fn new(bytes: I) -> Self {
|
|
||||||
Self(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<I: Iterator<Item = u8>> Iterator for Chars<I> {
|
|
||||||
type Item = ReplResult<char>;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let Self(bytes) = self;
|
|
||||||
let start = bytes.next()? as u32;
|
|
||||||
let (mut out, count) = match start {
|
|
||||||
start if start & 0x80 == 0x00 => (start, 0), // ASCII valid range
|
|
||||||
start if start & 0xe0 == 0xc0 => (start & 0x1f, 1), // 1 continuation byte
|
|
||||||
start if start & 0xf0 == 0xe0 => (start & 0x0f, 2), // 2 continuation bytes
|
|
||||||
start if start & 0xf8 == 0xf0 => (start & 0x07, 3), // 3 continuation bytes
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
for _ in 0..count {
|
|
||||||
let cont = bytes.next()? as u32;
|
|
||||||
if cont & 0xc0 != 0x80 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
out = out << 6 | (cont & 0x3f);
|
|
||||||
}
|
|
||||||
Some(char::from_u32(out).ok_or(Error::BadUnicode(out)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod flatten {
|
|
||||||
//! Flattens an [Iterator] returning [`Result<T, E>`](Result) or [`Option<T>`](Option)
|
|
||||||
//! into a *non-[FusedIterator](std::iter::FusedIterator)* over `T`
|
|
||||||
|
|
||||||
/// Flattens an [Iterator] returning [`Result<T, E>`](Result) or [`Option<T>`](Option)
|
|
||||||
/// into a *non-[FusedIterator](std::iter::FusedIterator)* over `T`
|
|
||||||
pub struct Flatten<T, I: Iterator<Item = T>>(pub I);
|
|
||||||
impl<T, E, I: Iterator<Item = Result<T, E>>> Iterator for Flatten<Result<T, E>, I> {
|
|
||||||
type Item = T;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.0.next()?.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T, I: Iterator<Item = Option<T>>> Iterator for Flatten<Option<T>, I> {
|
|
||||||
type Item = T;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.0.next()?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod raw {
|
|
||||||
//! Sets the terminal to [`raw`] mode for the duration of the returned object's lifetime.
|
|
||||||
|
|
||||||
/// Sets the terminal to raw mode for the duration of the returned object's lifetime.
|
|
||||||
pub fn raw() -> impl Drop {
|
|
||||||
Raw::default()
|
|
||||||
}
|
|
||||||
struct Raw();
|
|
||||||
impl Default for Raw {
|
|
||||||
fn default() -> Self {
|
|
||||||
std::thread::yield_now();
|
|
||||||
crossterm::terminal::enable_raw_mode()
|
|
||||||
.expect("should be able to transition into raw mode");
|
|
||||||
Raw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for Raw {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
crossterm::terminal::disable_raw_mode()
|
|
||||||
.expect("should be able to transition out of raw mode");
|
|
||||||
// std::thread::yield_now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod out {
|
|
||||||
#![allow(unused)]
|
|
||||||
use std::io::{Result, Write};
|
|
||||||
|
|
||||||
/// A [Writer](Write) that flushes after every wipe
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(super) struct EagerWriter<W: Write> {
|
|
||||||
out: W,
|
|
||||||
}
|
|
||||||
impl<W: Write> EagerWriter<W> {
|
|
||||||
pub fn new(writer: W) -> Self {
|
|
||||||
Self { out: writer }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<W: Write> Write for EagerWriter<W> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
|
||||||
let out = self.out.write(buf)?;
|
|
||||||
self.out.flush()?;
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
fn flush(&mut self) -> Result<()> {
|
|
||||||
self.out.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use self::{chars::Chars, editor::Editor, error::*, flatten::Flatten, ignore::Ignore, raw::raw};
|
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
io::{stdout, Bytes, Read, Result, Write},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Repline<'a, R: Read> {
|
|
||||||
input: Chars<Flatten<Result<u8>, Bytes<R>>>,
|
|
||||||
|
|
||||||
history: VecDeque<String>, // previous lines
|
|
||||||
hindex: usize, // current index into the history buffer
|
|
||||||
|
|
||||||
ed: Editor<'a>, // the current line buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, R: Read> Repline<'a, R> {
|
|
||||||
/// Constructs a [Repline] with the given [Reader](Read), color, begin, and again prompts.
|
|
||||||
pub fn with_input(input: R, color: &'a str, begin: &'a str, again: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
input: Chars(Flatten(input.bytes())),
|
|
||||||
history: Default::default(),
|
|
||||||
hindex: 0,
|
|
||||||
ed: Editor::new(color, begin, again),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Set the terminal prompt color
|
|
||||||
pub fn set_color(&mut self, color: &'a str) {
|
|
||||||
self.ed.color = color
|
|
||||||
}
|
|
||||||
/// Reads in a line, and returns it for validation
|
|
||||||
pub fn read(&mut self) -> ReplResult<String> {
|
|
||||||
const INDENT: &str = " ";
|
|
||||||
let mut stdout = stdout().lock();
|
|
||||||
let stdout = &mut stdout;
|
|
||||||
let _make_raw = raw();
|
|
||||||
// self.ed.begin_frame(stdout)?;
|
|
||||||
// self.ed.redraw_frame(stdout)?;
|
|
||||||
self.ed.print_head(stdout)?;
|
|
||||||
loop {
|
|
||||||
stdout.flush()?;
|
|
||||||
match self.input.next().ok_or(Error::EndOfInput)?? {
|
|
||||||
// Ctrl+C: End of Text. Immediately exits.
|
|
||||||
// Ctrl+D: End of Transmission. Ends the current line.
|
|
||||||
'\x03' => {
|
|
||||||
drop(_make_raw);
|
|
||||||
writeln!(stdout)?;
|
|
||||||
return Err(Error::CtrlC(self.ed.to_string()));
|
|
||||||
}
|
|
||||||
'\x04' => {
|
|
||||||
drop(_make_raw);
|
|
||||||
writeln!(stdout)?;
|
|
||||||
return Err(Error::CtrlD(self.ed.to_string()));
|
|
||||||
}
|
|
||||||
// Tab: extend line by 4 spaces
|
|
||||||
'\t' => {
|
|
||||||
self.ed.extend(INDENT.chars(), stdout)?;
|
|
||||||
}
|
|
||||||
// ignore newlines, process line feeds. Not sure how cross-platform this is.
|
|
||||||
'\n' => {}
|
|
||||||
'\r' => {
|
|
||||||
self.ed.push('\n', stdout)?;
|
|
||||||
return Ok(self.ed.to_string());
|
|
||||||
}
|
|
||||||
// Escape sequence
|
|
||||||
'\x1b' => self.escape(stdout)?,
|
|
||||||
// backspace
|
|
||||||
'\x08' | '\x7f' => {
|
|
||||||
let ed = &mut self.ed;
|
|
||||||
if ed.ends_with(INDENT.chars()) {
|
|
||||||
for _ in 0..INDENT.len() {
|
|
||||||
ed.pop(stdout)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ed.pop(stdout)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c if c.is_ascii_control() => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprint!("\\x{:02x}", c as u32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
self.ed.push(c, stdout)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Handle ANSI Escape
|
|
||||||
fn escape<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
|
||||||
match self.input.next().ok_or(Error::EndOfInput)?? {
|
|
||||||
'[' => self.csi(w)?,
|
|
||||||
'O' => todo!("Process alternate character mode"),
|
|
||||||
other => self.ed.extend(['\x1b', other], w)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Handle ANSI Control Sequence Introducer
|
|
||||||
fn csi<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
|
||||||
match self.input.next().ok_or(Error::EndOfInput)?? {
|
|
||||||
'A' => {
|
|
||||||
self.hindex = self.hindex.saturating_sub(1);
|
|
||||||
self.restore_history(w)?
|
|
||||||
}
|
|
||||||
'B' => {
|
|
||||||
self.hindex = self
|
|
||||||
.hindex
|
|
||||||
.saturating_add(1)
|
|
||||||
.min(self.history.len().saturating_sub(1));
|
|
||||||
self.restore_history(w)?
|
|
||||||
}
|
|
||||||
'C' => self.ed.cursor_forward(1, w)?,
|
|
||||||
'D' => self.ed.cursor_back(1, w)?,
|
|
||||||
'H' => self.ed.home(w)?,
|
|
||||||
'F' => self.ed.end(w)?,
|
|
||||||
'3' => {
|
|
||||||
if let '~' = self.input.next().ok_or(Error::EndOfInput)?? {
|
|
||||||
self.ed.delete(w).ignore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprint!("{}", other.escape_unicode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Restores the currently selected history
|
|
||||||
pub fn restore_history<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
|
||||||
let Self { history, hindex, ed, .. } = self;
|
|
||||||
if !(0..history.len()).contains(hindex) {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
ed.undraw(w)?;
|
|
||||||
ed.clear();
|
|
||||||
ed.print_head(w)?;
|
|
||||||
ed.extend(
|
|
||||||
history
|
|
||||||
.get(*hindex)
|
|
||||||
.expect("history should contain index")
|
|
||||||
.chars(),
|
|
||||||
w,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append line to history and clear it
|
|
||||||
pub fn accept(&mut self) {
|
|
||||||
self.history_append(self.ed.iter().collect());
|
|
||||||
self.ed.clear();
|
|
||||||
self.hindex = self.history.len();
|
|
||||||
}
|
|
||||||
/// Append line to history
|
|
||||||
pub fn history_append(&mut self, mut buf: String) {
|
|
||||||
while buf.ends_with(char::is_whitespace) {
|
|
||||||
buf.pop();
|
|
||||||
}
|
|
||||||
if !self.history.contains(&buf) {
|
|
||||||
self.history.push_back(buf)
|
|
||||||
}
|
|
||||||
while self.history.len() > 20 {
|
|
||||||
self.history.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Clear the line
|
|
||||||
pub fn deny(&mut self) {
|
|
||||||
self.ed.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Repline<'a, std::io::Stdin> {
|
|
||||||
pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
|
|
||||||
Self::with_input(std::io::stdin(), color, begin, again)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod editor {
|
|
||||||
use crossterm::{cursor::*, execute, queue, style::*, terminal::*};
|
|
||||||
use std::{collections::VecDeque, fmt::Display, io::Write};
|
|
||||||
|
|
||||||
use super::error::{Error, ReplResult};
|
|
||||||
|
|
||||||
fn is_newline(c: &char) -> bool {
|
|
||||||
*c == '\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_chars<'a, W: Write>(
|
|
||||||
c: impl IntoIterator<Item = &'a char>,
|
|
||||||
w: &mut W,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
for c in c {
|
|
||||||
write!(w, "{c}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Editor<'a> {
|
|
||||||
head: VecDeque<char>,
|
|
||||||
tail: VecDeque<char>,
|
|
||||||
|
|
||||||
pub color: &'a str,
|
|
||||||
begin: &'a str,
|
|
||||||
again: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Editor<'a> {
|
|
||||||
pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
|
|
||||||
Self { head: Default::default(), tail: Default::default(), color, begin, again }
|
|
||||||
}
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &char> {
|
|
||||||
self.head.iter()
|
|
||||||
}
|
|
||||||
pub fn undraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
|
||||||
let Self { head, .. } = self;
|
|
||||||
match head.iter().copied().filter(is_newline).count() {
|
|
||||||
0 => write!(w, "\x1b[0G"),
|
|
||||||
lines => write!(w, "\x1b[{}F", lines),
|
|
||||||
}?;
|
|
||||||
queue!(w, Clear(ClearType::FromCursorDown))?;
|
|
||||||
// write!(w, "\x1b[0J")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn redraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
|
||||||
let Self { head, tail, color, begin, again } = self;
|
|
||||||
write!(w, "{color}{begin}\x1b[0m ")?;
|
|
||||||
// draw head
|
|
||||||
for c in head {
|
|
||||||
match c {
|
|
||||||
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
|
|
||||||
_ => w.write_all({ *c as u32 }.to_le_bytes().as_slice()),
|
|
||||||
}?
|
|
||||||
}
|
|
||||||
// save cursor
|
|
||||||
execute!(w, SavePosition)?;
|
|
||||||
// draw tail
|
|
||||||
for c in tail {
|
|
||||||
match c {
|
|
||||||
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
|
|
||||||
_ => write!(w, "{c}"),
|
|
||||||
}?
|
|
||||||
}
|
|
||||||
// restore cursor
|
|
||||||
execute!(w, RestorePosition)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn prompt<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
|
||||||
let Self { head, color, begin, again, .. } = self;
|
|
||||||
queue!(
|
|
||||||
w,
|
|
||||||
MoveToColumn(0),
|
|
||||||
Print(color),
|
|
||||||
Print(if head.is_empty() { begin } else { again }),
|
|
||||||
ResetColor,
|
|
||||||
Print(' '),
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn print_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
|
||||||
self.prompt(w)?;
|
|
||||||
write_chars(
|
|
||||||
self.head.iter().skip(
|
|
||||||
self.head
|
|
||||||
.iter()
|
|
||||||
.rposition(is_newline)
|
|
||||||
.unwrap_or(self.head.len())
|
|
||||||
+ 1,
|
|
||||||
),
|
|
||||||
w,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn print_tail<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
|
||||||
let Self { tail, .. } = self;
|
|
||||||
queue!(w, SavePosition, Clear(ClearType::UntilNewLine))?;
|
|
||||||
write_chars(tail.iter().take_while(|&c| !is_newline(c)), w)?;
|
|
||||||
queue!(w, RestorePosition)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn push<W: Write>(&mut self, c: char, w: &mut W) -> ReplResult<()> {
|
|
||||||
// Tail optimization: if the tail is empty,
|
|
||||||
//we don't have to undraw and redraw on newline
|
|
||||||
if self.tail.is_empty() {
|
|
||||||
self.head.push_back(c);
|
|
||||||
match c {
|
|
||||||
'\n' => {
|
|
||||||
write!(w, "\r\n")?;
|
|
||||||
self.print_head(w)?;
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
queue!(w, Print(c))?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if '\n' == c {
|
|
||||||
self.undraw(w)?;
|
|
||||||
}
|
|
||||||
self.head.push_back(c);
|
|
||||||
match c {
|
|
||||||
'\n' => self.redraw(w)?,
|
|
||||||
_ => {
|
|
||||||
write!(w, "{c}")?;
|
|
||||||
self.print_tail(w)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn pop<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
|
|
||||||
if let Some('\n') = self.head.back() {
|
|
||||||
self.undraw(w)?;
|
|
||||||
}
|
|
||||||
let c = self.head.pop_back();
|
|
||||||
// if the character was a newline, we need to go back a line
|
|
||||||
match c {
|
|
||||||
Some('\n') => self.redraw(w)?,
|
|
||||||
Some(_) => {
|
|
||||||
// go back a char
|
|
||||||
queue!(w, MoveLeft(1), Print(' '), MoveLeft(1))?;
|
|
||||||
self.print_tail(w)?;
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
Ok(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extend<T: IntoIterator<Item = char>, W: Write>(
|
|
||||||
&mut self,
|
|
||||||
iter: T,
|
|
||||||
w: &mut W,
|
|
||||||
) -> ReplResult<()> {
|
|
||||||
for c in iter {
|
|
||||||
self.push(c, w)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn restore(&mut self, s: &str) {
|
|
||||||
self.clear();
|
|
||||||
self.head.extend(s.chars())
|
|
||||||
}
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.head.clear();
|
|
||||||
self.tail.clear();
|
|
||||||
}
|
|
||||||
pub fn delete<W: Write>(&mut self, w: &mut W) -> ReplResult<char> {
|
|
||||||
match self.tail.front() {
|
|
||||||
Some('\n') => {
|
|
||||||
self.undraw(w)?;
|
|
||||||
let out = self.tail.pop_front();
|
|
||||||
self.redraw(w)?;
|
|
||||||
out
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let out = self.tail.pop_front();
|
|
||||||
self.print_tail(w)?;
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ok_or(Error::EndOfInput)
|
|
||||||
}
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.head.len() + self.tail.len()
|
|
||||||
}
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.head.is_empty() && self.tail.is_empty()
|
|
||||||
}
|
|
||||||
pub fn ends_with(&self, iter: impl DoubleEndedIterator<Item = char>) -> bool {
|
|
||||||
let mut iter = iter.rev();
|
|
||||||
let mut head = self.head.iter().rev();
|
|
||||||
loop {
|
|
||||||
match (iter.next(), head.next()) {
|
|
||||||
(None, _) => break true,
|
|
||||||
(Some(_), None) => break false,
|
|
||||||
(Some(a), Some(b)) if a != *b => break false,
|
|
||||||
(Some(_), Some(_)) => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Moves the cursor back `steps` steps
|
|
||||||
pub fn cursor_back<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
|
|
||||||
for _ in 0..steps {
|
|
||||||
if let Some('\n') = self.head.back() {
|
|
||||||
self.undraw(w)?;
|
|
||||||
}
|
|
||||||
let Some(c) = self.head.pop_back() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
self.tail.push_front(c);
|
|
||||||
match c {
|
|
||||||
'\n' => self.redraw(w)?,
|
|
||||||
_ => queue!(w, MoveLeft(1))?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Moves the cursor forward `steps` steps
|
|
||||||
pub fn cursor_forward<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
|
|
||||||
for _ in 0..steps {
|
|
||||||
if let Some('\n') = self.tail.front() {
|
|
||||||
self.undraw(w)?
|
|
||||||
}
|
|
||||||
let Some(c) = self.tail.pop_front() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
self.head.push_back(c);
|
|
||||||
match c {
|
|
||||||
'\n' => self.redraw(w)?,
|
|
||||||
_ => queue!(w, MoveRight(1))?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Goes to the beginning of the current line
|
|
||||||
pub fn home<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
|
||||||
loop {
|
|
||||||
match self.head.back() {
|
|
||||||
Some('\n') | None => break Ok(()),
|
|
||||||
Some(_) => self.cursor_back(1, w)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Goes to the end of the current line
|
|
||||||
pub fn end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
|
||||||
loop {
|
|
||||||
match self.tail.front() {
|
|
||||||
Some('\n') | None => break Ok(()),
|
|
||||||
Some(_) => self.cursor_forward(1, w)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'e> IntoIterator for &'e Editor<'a> {
|
|
||||||
type Item = &'e char;
|
|
||||||
type IntoIter = std::iter::Chain<
|
|
||||||
std::collections::vec_deque::Iter<'e, char>,
|
|
||||||
std::collections::vec_deque::Iter<'e, char>,
|
|
||||||
>;
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
self.head.iter().chain(self.tail.iter())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Display for Editor<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
use std::fmt::Write;
|
|
||||||
let Self { head, tail, .. } = self;
|
|
||||||
for c in head {
|
|
||||||
f.write_char(*c)?;
|
|
||||||
}
|
|
||||||
for c in tail {
|
|
||||||
f.write_char(*c)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
compiler/cl-arena/Cargo.toml
Normal file
10
compiler/cl-arena/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-arena"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
42
compiler/cl-arena/src/dropless_arena/tests.rs
Normal file
42
compiler/cl-arena/src/dropless_arena/tests.rs
Normal 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);
|
||||||
|
}
|
||||||
396
compiler/cl-arena/src/lib.rs
Normal file
396
compiler/cl-arena/src/lib.rs
Normal 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;
|
||||||
|
}
|
||||||
61
compiler/cl-arena/src/typed_arena/tests.rs
Normal file
61
compiler/cl-arena/src/typed_arena/tests.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
compiler/cl-ast/Cargo.toml
Normal file
11
compiler/cl-ast/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-ast"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
612
compiler/cl-ast/src/ast.rs
Normal file
612
compiler/cl-ast/src/ast.rs
Normal file
@@ -0,0 +1,612 @@
|
|||||||
|
//! # The Abstract Syntax Tree
|
||||||
|
//! Contains definitions of Conlang AST Nodes.
|
||||||
|
//!
|
||||||
|
//! # Notable nodes
|
||||||
|
//! - [Item] and [ItemKind]: Top-level constructs
|
||||||
|
//! - [Stmt] and [StmtKind]: Statements
|
||||||
|
//! - [Expr] and [ExprKind]: Expressions
|
||||||
|
//! - [Assign], [Modify], [Binary], and [Unary] expressions
|
||||||
|
//! - [ModifyKind], [BinaryKind], and [UnaryKind] operators
|
||||||
|
//! - [Ty] and [TyKind]: Type qualifiers
|
||||||
|
//! - [Path]: Path expressions
|
||||||
|
use cl_structures::{intern::interned::Interned, span::*};
|
||||||
|
|
||||||
|
/// An [Interned] static [str], used in place of an identifier
|
||||||
|
pub type Sym = Interned<'static, str>;
|
||||||
|
|
||||||
|
/// Whether a binding ([Static] or [Let]) or reference is mutable or not
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Mutability {
|
||||||
|
#[default]
|
||||||
|
Not,
|
||||||
|
Mut,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether an [Item] is visible outside of the current [Module]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Visibility {
|
||||||
|
#[default]
|
||||||
|
Private,
|
||||||
|
Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Literal {
|
||||||
|
Bool(bool),
|
||||||
|
Char(char),
|
||||||
|
Int(u128),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of [Item]s
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub struct File {
|
||||||
|
pub items: Vec<Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of [Meta] decorators
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Attrs {
|
||||||
|
pub meta: Vec<Meta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A metadata decorator
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Meta {
|
||||||
|
pub name: Sym,
|
||||||
|
pub kind: MetaKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information attached to [Meta]data
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MetaKind {
|
||||||
|
Plain,
|
||||||
|
Equals(Literal),
|
||||||
|
Func(Vec<Literal>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items
|
||||||
|
/// Anything that can appear at the top level of a [File]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Item {
|
||||||
|
pub extents: Span,
|
||||||
|
pub attrs: Attrs,
|
||||||
|
pub vis: Visibility,
|
||||||
|
pub kind: ItemKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What kind of [Item] is this?
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ItemKind {
|
||||||
|
// TODO: Import declaration ("use") item
|
||||||
|
// TODO: Trait declaration ("trait") item?
|
||||||
|
/// A [module](Module)
|
||||||
|
Module(Module),
|
||||||
|
/// A [type alias](Alias)
|
||||||
|
Alias(Alias),
|
||||||
|
/// An [enumerated type](Enum), with a discriminant and optional data
|
||||||
|
Enum(Enum),
|
||||||
|
/// A [structure](Struct)
|
||||||
|
Struct(Struct),
|
||||||
|
/// A [constant](Const)
|
||||||
|
Const(Const),
|
||||||
|
/// A [static](Static) variable
|
||||||
|
Static(Static),
|
||||||
|
/// A [function definition](Function)
|
||||||
|
Function(Function),
|
||||||
|
/// An [implementation](Impl)
|
||||||
|
Impl(Impl),
|
||||||
|
/// An [import](Use)
|
||||||
|
Use(Use),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An alias to another [Ty]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Alias {
|
||||||
|
pub to: Sym,
|
||||||
|
pub from: Option<Box<Ty>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A compile-time constant
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Const {
|
||||||
|
pub name: Sym,
|
||||||
|
pub ty: Box<Ty>,
|
||||||
|
pub init: Box<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `static` variable
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Static {
|
||||||
|
pub mutable: Mutability,
|
||||||
|
pub name: Sym,
|
||||||
|
pub ty: Box<Ty>,
|
||||||
|
pub init: Box<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ordered collection of [Items](Item)
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Module {
|
||||||
|
pub name: Sym,
|
||||||
|
pub kind: ModuleKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The contents of a [Module], if they're in the same file
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ModuleKind {
|
||||||
|
Inline(File),
|
||||||
|
Outline,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Code, and the interface to that code
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Function {
|
||||||
|
pub name: Sym,
|
||||||
|
pub sign: TyFn,
|
||||||
|
pub bind: Vec<Param>,
|
||||||
|
pub body: Option<Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single parameter for a [Function]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Param {
|
||||||
|
pub mutability: Mutability,
|
||||||
|
pub name: Sym,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A user-defined product type
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Struct {
|
||||||
|
pub name: Sym,
|
||||||
|
pub kind: StructKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Either a [Struct]'s [StructMember]s or tuple [Ty]pes, if present.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum StructKind {
|
||||||
|
Empty,
|
||||||
|
Tuple(Vec<Ty>),
|
||||||
|
Struct(Vec<StructMember>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [Visibility], [Sym], and [Ty]pe of a single [Struct] member
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct StructMember {
|
||||||
|
pub vis: Visibility,
|
||||||
|
pub name: Sym,
|
||||||
|
pub ty: Ty,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A user-defined sum type
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Enum {
|
||||||
|
pub name: Sym,
|
||||||
|
pub kind: EnumKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Enum]'s [Variant]s, if it has a variant block
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum EnumKind {
|
||||||
|
/// Represents an enum with no variants
|
||||||
|
NoVariants,
|
||||||
|
Variants(Vec<Variant>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single [Enum] variant
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Variant {
|
||||||
|
pub name: Sym,
|
||||||
|
pub kind: VariantKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the [Variant] has a C-like constant value, a tuple, or [StructMember]s
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum VariantKind {
|
||||||
|
Plain,
|
||||||
|
CLike(u128),
|
||||||
|
Tuple(Ty),
|
||||||
|
Struct(Vec<StructMember>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sub-[items](Item) (associated functions, etc.) for a [Ty]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Impl {
|
||||||
|
pub target: ImplKind,
|
||||||
|
pub body: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: `impl` Trait for <Target> { }
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ImplKind {
|
||||||
|
Type(Ty),
|
||||||
|
Trait { impl_trait: Path, for_type: Box<Ty> },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An import of nonlocal [Item]s
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Use {
|
||||||
|
pub absolute: bool,
|
||||||
|
pub tree: UseTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tree of [Item] imports
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum UseTree {
|
||||||
|
Tree(Vec<UseTree>),
|
||||||
|
Path(PathPart, Box<UseTree>),
|
||||||
|
Alias(Sym, Sym),
|
||||||
|
Name(Sym),
|
||||||
|
Glob,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type expression
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Ty {
|
||||||
|
pub extents: Span,
|
||||||
|
pub kind: TyKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a [Ty]pe expression
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum TyKind {
|
||||||
|
Never,
|
||||||
|
Empty,
|
||||||
|
Path(Path),
|
||||||
|
Array(TyArray),
|
||||||
|
Slice(TySlice),
|
||||||
|
Tuple(TyTuple),
|
||||||
|
Ref(TyRef),
|
||||||
|
Fn(TyFn),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An array of [`T`](Ty)
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TyArray {
|
||||||
|
pub ty: Box<TyKind>,
|
||||||
|
pub count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Ty]pe slice expression: `[T]`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TySlice {
|
||||||
|
pub ty: Box<TyKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tuple of [Ty]pes
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TyTuple {
|
||||||
|
pub types: Vec<TyKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Ty]pe-reference expression as (number of `&`, [Path])
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TyRef {
|
||||||
|
pub mutable: Mutability,
|
||||||
|
pub count: u16,
|
||||||
|
pub to: Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The args and return value for a function pointer [Ty]pe
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TyFn {
|
||||||
|
pub args: Box<TyKind>,
|
||||||
|
pub rety: Option<Box<Ty>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A path to an [Item] in the [Module] tree
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Path {
|
||||||
|
pub absolute: bool,
|
||||||
|
pub parts: Vec<PathPart>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single component of a [Path]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum PathPart {
|
||||||
|
SuperKw,
|
||||||
|
SelfKw,
|
||||||
|
SelfTy,
|
||||||
|
Ident(Sym),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An abstract statement, and associated metadata
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Stmt {
|
||||||
|
pub extents: Span,
|
||||||
|
pub kind: StmtKind,
|
||||||
|
pub semi: Semi,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the [Stmt] is a [Let], [Item], or [Expr] statement
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum StmtKind {
|
||||||
|
Empty,
|
||||||
|
Item(Box<Item>),
|
||||||
|
Expr(Box<Expr>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether or not a [Stmt] is followed by a semicolon
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Semi {
|
||||||
|
Terminated,
|
||||||
|
Unterminated,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An expression, the beating heart of the language
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Expr {
|
||||||
|
pub extents: Span,
|
||||||
|
pub kind: ExprKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Any of the different [Expr]essions
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ExprKind {
|
||||||
|
/// An empty expression: `(` `)`
|
||||||
|
#[default]
|
||||||
|
Empty,
|
||||||
|
/// A local bind instruction, `let` [`Sym`] `=` [`Expr`]
|
||||||
|
Let(Let),
|
||||||
|
/// 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 [Cast] expression: [`Expr`] `as` [`Ty`]
|
||||||
|
Cast(Cast),
|
||||||
|
/// A [Member] access expression: [`Expr`] [`MemberKind`]\*
|
||||||
|
Member(Member),
|
||||||
|
/// An Array [Index] expression: a[10, 20, 30]
|
||||||
|
Index(Index),
|
||||||
|
/// A [Struct creation](Structor) expression: [Path] `{` ([Fielder] `,`)* [Fielder]? `}`
|
||||||
|
Structor(Structor),
|
||||||
|
/// A [path expression](Path): `::`? [PathPart] (`::` [PathPart])*
|
||||||
|
Path(Path),
|
||||||
|
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
|
||||||
|
Literal(Literal),
|
||||||
|
/// An [Array] literal: `[` [`Expr`] (`,` [`Expr`])\* `]`
|
||||||
|
Array(Array),
|
||||||
|
/// An Array literal constructed with [repeat syntax](ArrayRep)
|
||||||
|
/// `[` [Expr] `;` [Literal] `]`
|
||||||
|
ArrayRep(ArrayRep),
|
||||||
|
/// An address-of expression: `&` `mut`? [`Expr`]
|
||||||
|
AddrOf(AddrOf),
|
||||||
|
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
|
||||||
|
Block(Block),
|
||||||
|
/// A [Grouping](Group) expression `(` [`Expr`] `)`
|
||||||
|
Group(Group),
|
||||||
|
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
|
||||||
|
Tuple(Tuple),
|
||||||
|
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
|
||||||
|
While(While),
|
||||||
|
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
|
||||||
|
If(If),
|
||||||
|
/// A [For] expression: `for` Pattern `in` [`Expr`] [`Block`] [`Else`]?
|
||||||
|
For(For),
|
||||||
|
/// A [Break] expression: `break` [`Expr`]?
|
||||||
|
Break(Break),
|
||||||
|
/// A [Return] expression `return` [`Expr`]?
|
||||||
|
Return(Return),
|
||||||
|
/// A continue expression: `continue`
|
||||||
|
Continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A local variable declaration [Stmt]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Let {
|
||||||
|
pub mutable: Mutability,
|
||||||
|
pub name: Sym,
|
||||||
|
pub ty: Option<Box<Ty>>,
|
||||||
|
pub init: Option<Box<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Assign]ment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Assign {
|
||||||
|
pub parts: Box<(ExprKind, ExprKind)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Modify]-assignment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Modify {
|
||||||
|
pub kind: ModifyKind,
|
||||||
|
pub parts: Box<(ExprKind, ExprKind)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ModifyKind {
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Xor,
|
||||||
|
Shl,
|
||||||
|
Shr,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Rem,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Binary {
|
||||||
|
pub kind: BinaryKind,
|
||||||
|
pub parts: Box<(ExprKind, ExprKind)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Binary] operator
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum BinaryKind {
|
||||||
|
Lt,
|
||||||
|
LtEq,
|
||||||
|
Equal,
|
||||||
|
NotEq,
|
||||||
|
GtEq,
|
||||||
|
Gt,
|
||||||
|
RangeExc,
|
||||||
|
RangeInc,
|
||||||
|
LogAnd,
|
||||||
|
LogOr,
|
||||||
|
LogXor,
|
||||||
|
BitAnd,
|
||||||
|
BitOr,
|
||||||
|
BitXor,
|
||||||
|
Shl,
|
||||||
|
Shr,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Rem,
|
||||||
|
Call,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Unary {
|
||||||
|
pub kind: UnaryKind,
|
||||||
|
pub tail: Box<ExprKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Unary] operator
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum UnaryKind {
|
||||||
|
Deref,
|
||||||
|
Neg,
|
||||||
|
Not,
|
||||||
|
/// A Loop expression: `loop` [`Block`]
|
||||||
|
Loop,
|
||||||
|
/// Unused
|
||||||
|
At,
|
||||||
|
/// Unused
|
||||||
|
Tilde,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Cast {
|
||||||
|
pub head: Box<ExprKind>,
|
||||||
|
pub ty: Ty,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Member] access expression: [`Expr`] [`MemberKind`]\*
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Member {
|
||||||
|
pub head: Box<ExprKind>,
|
||||||
|
pub kind: MemberKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of [Member] access
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MemberKind {
|
||||||
|
Call(Sym, Tuple),
|
||||||
|
Struct(Sym),
|
||||||
|
Tuple(Literal),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A repeated [Index] expression: a[10, 20, 30][40, 50, 60]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Index {
|
||||||
|
pub head: Box<ExprKind>,
|
||||||
|
pub indices: Vec<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Struct creation](Structor) expression: [Path] `{` ([Fielder] `,`)* [Fielder]? `}`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Structor {
|
||||||
|
pub to: Path,
|
||||||
|
pub init: Vec<Fielder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Struct field initializer] expression: [Sym] (`=` [Expr])?
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Fielder {
|
||||||
|
pub name: Sym,
|
||||||
|
pub init: Option<Box<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Array] literal: `[` [`Expr`] (`,` [`Expr`])\* `]`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Array {
|
||||||
|
pub values: Vec<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An Array literal constructed with [repeat syntax](ArrayRep)
|
||||||
|
/// `[` [Expr] `;` [Literal] `]`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ArrayRep {
|
||||||
|
pub value: Box<ExprKind>,
|
||||||
|
pub repeat: Box<ExprKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An address-of expression: `&` `mut`? [`Expr`]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct AddrOf {
|
||||||
|
pub count: usize,
|
||||||
|
pub mutable: Mutability,
|
||||||
|
pub expr: Box<ExprKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Block {
|
||||||
|
pub stmts: Vec<Stmt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Grouping](Group) expression `(` [`Expr`] `)`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Group {
|
||||||
|
pub expr: Box<ExprKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Tuple {
|
||||||
|
pub exprs: Vec<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct While {
|
||||||
|
pub cond: Box<Expr>,
|
||||||
|
pub pass: Box<Block>,
|
||||||
|
pub fail: Else,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct If {
|
||||||
|
pub cond: Box<Expr>,
|
||||||
|
pub pass: Box<Block>,
|
||||||
|
pub fail: Else,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [For] expression: `for` Pattern `in` [`Expr`] [`Block`] [`Else`]?
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct For {
|
||||||
|
pub bind: Sym, // TODO: Patterns?
|
||||||
|
pub cond: Box<Expr>,
|
||||||
|
pub pass: Box<Block>,
|
||||||
|
pub fail: Else,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The (optional) `else` clause of a [While], [If], or [For] expression
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Else {
|
||||||
|
pub body: Option<Box<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Break] expression: `break` [`Expr`]?
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Break {
|
||||||
|
pub body: Option<Box<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Return] expression `return` [`Expr`]?
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Return {
|
||||||
|
pub body: Option<Box<Expr>>,
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
8
compiler/cl-ast/src/ast_visitor.rs
Normal file
8
compiler/cl-ast/src/ast_visitor.rs
Normal 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;
|
||||||
562
compiler/cl-ast/src/ast_visitor/fold.rs
Normal file
562
compiler/cl-ast/src/ast_visitor/fold.rs
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
//! 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_cast(&mut self, cast: Cast) -> Cast {
|
||||||
|
let Cast { head, ty } = cast;
|
||||||
|
Cast { head: Box::new(self.fold_expr_kind(*head)), ty: self.fold_ty(ty) }
|
||||||
|
}
|
||||||
|
fn fold_member(&mut self, m: Member) -> Member {
|
||||||
|
let Member { head, kind } = m;
|
||||||
|
Member { head: Box::new(self.fold_expr_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_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))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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::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::Let(l) => ExprKind::Let(folder.fold_let(l)),
|
||||||
|
ExprKind::Assign(a) => ExprKind::Assign(folder.fold_assign(a)),
|
||||||
|
ExprKind::Modify(m) => ExprKind::Modify(folder.fold_modify(m)),
|
||||||
|
ExprKind::Binary(b) => ExprKind::Binary(folder.fold_binary(b)),
|
||||||
|
ExprKind::Unary(u) => ExprKind::Unary(folder.fold_unary(u)),
|
||||||
|
ExprKind::Cast(c) => ExprKind::Cast(folder.fold_cast(c)),
|
||||||
|
ExprKind::Member(m) => ExprKind::Member(folder.fold_member(m)),
|
||||||
|
ExprKind::Index(i) => ExprKind::Index(folder.fold_index(i)),
|
||||||
|
ExprKind::Structor(s) => ExprKind::Structor(folder.fold_structor(s)),
|
||||||
|
ExprKind::Path(p) => ExprKind::Path(folder.fold_path(p)),
|
||||||
|
ExprKind::Literal(l) => ExprKind::Literal(folder.fold_literal(l)),
|
||||||
|
ExprKind::Array(a) => ExprKind::Array(folder.fold_array(a)),
|
||||||
|
ExprKind::ArrayRep(a) => ExprKind::ArrayRep(folder.fold_array_rep(a)),
|
||||||
|
ExprKind::AddrOf(a) => ExprKind::AddrOf(folder.fold_addrof(a)),
|
||||||
|
ExprKind::Block(b) => ExprKind::Block(folder.fold_block(b)),
|
||||||
|
ExprKind::Group(g) => ExprKind::Group(folder.fold_group(g)),
|
||||||
|
ExprKind::Tuple(t) => ExprKind::Tuple(folder.fold_tuple(t)),
|
||||||
|
ExprKind::While(w) => ExprKind::While(folder.fold_while(w)),
|
||||||
|
ExprKind::If(i) => ExprKind::If(folder.fold_if(i)),
|
||||||
|
ExprKind::For(f) => ExprKind::For(folder.fold_for(f)),
|
||||||
|
ExprKind::Break(b) => ExprKind::Break(folder.fold_break(b)),
|
||||||
|
ExprKind::Return(r) => ExprKind::Return(folder.fold_return(r)),
|
||||||
|
ExprKind::Continue => ExprKind::Continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn or_fold_member_kind<F: Fold + ?Sized>(folder: &mut F, kind: MemberKind) -> MemberKind {
|
||||||
|
match kind {
|
||||||
|
MemberKind::Call(name, args) => {
|
||||||
|
MemberKind::Call(folder.fold_sym(name), folder.fold_tuple(args))
|
||||||
|
}
|
||||||
|
MemberKind::Struct(name) => MemberKind::Struct(folder.fold_sym(name)),
|
||||||
|
MemberKind::Tuple(name) => MemberKind::Tuple(folder.fold_literal(name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
487
compiler/cl-ast/src/ast_visitor/visit.rs
Normal file
487
compiler/cl-ast/src/ast_visitor/visit.rs
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
//! 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_cast(&mut self, cast: &'a Cast) {
|
||||||
|
let Cast { head, ty } = cast;
|
||||||
|
self.visit_expr_kind(head);
|
||||||
|
self.visit_ty(ty);
|
||||||
|
}
|
||||||
|
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_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) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::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::Let(l) => visitor.visit_let(l),
|
||||||
|
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::Cast(c) => visitor.visit_cast(c),
|
||||||
|
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::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 => visitor.visit_continue(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
9
compiler/cl-ast/src/desugar.rs
Normal file
9
compiler/cl-ast/src/desugar.rs
Normal 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;
|
||||||
54
compiler/cl-ast/src/desugar/path_absoluter.rs
Normal file
54
compiler/cl-ast/src/desugar/path_absoluter.rs
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
compiler/cl-ast/src/desugar/squash_groups.rs
Normal file
14
compiler/cl-ast/src/desugar/squash_groups.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
compiler/cl-ast/src/desugar/while_else.rs
Normal file
34
compiler/cl-ast/src/desugar/while_else.rs
Normal 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 = ExprKind::If(loop_body);
|
||||||
|
ExprKind::Unary(Unary { kind: UnaryKind::Loop, tail: Box::new(loop_body) })
|
||||||
|
}
|
||||||
|
_ => kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
82
compiler/cl-ast/src/format.rs
Normal file
82
compiler/cl-ast/src/format.rs
Normal 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: "]" };
|
||||||
|
}
|
||||||
21
compiler/cl-ast/src/lib.rs
Normal file
21
compiler/cl-ast/src/lib.rs
Normal 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;
|
||||||
17
compiler/cl-interpret/Cargo.toml
Normal file
17
compiler/cl-interpret/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-interpret"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-ast = { path = "../cl-ast" }
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
cl-lexer = { path = "../cl-lexer" }
|
||||||
|
cl-parser = { path = "../cl-parser" }
|
||||||
56
compiler/cl-interpret/examples/conlang-run.rs
Normal file
56
compiler/cl-interpret/examples/conlang-run.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//! A bare-minimum harness to evaluate a Conlang program
|
||||||
|
|
||||||
|
use std::{error::Error, path::PathBuf};
|
||||||
|
|
||||||
|
use cl_ast::Expr;
|
||||||
|
use cl_interpret::{convalue::ConValue, env::Environment};
|
||||||
|
use cl_lexer::Lexer;
|
||||||
|
use cl_parser::{inliner::ModuleInliner, Parser};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut args = std::env::args();
|
||||||
|
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let Some(path) = args.next().map(PathBuf::from) else {
|
||||||
|
println!("Usage: {prog} `file.cl` [ args... ]");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent = path.parent().unwrap_or("".as_ref());
|
||||||
|
|
||||||
|
let code = std::fs::read_to_string(&path)?;
|
||||||
|
let code = Parser::new(Lexer::new(&code)).parse()?;
|
||||||
|
let code = match ModuleInliner::new(parent).inline(code) {
|
||||||
|
Ok(code) => code,
|
||||||
|
Err((code, ioerrs, perrs)) => {
|
||||||
|
for (p, err) in ioerrs {
|
||||||
|
eprintln!("{}:{err}", p.display());
|
||||||
|
}
|
||||||
|
for (p, err) in perrs {
|
||||||
|
eprintln!("{}:{err}", p.display());
|
||||||
|
}
|
||||||
|
code
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut env = Environment::new();
|
||||||
|
env.eval(&code)?;
|
||||||
|
|
||||||
|
let main = "main".into();
|
||||||
|
if env.get(main).is_ok() {
|
||||||
|
let args = args
|
||||||
|
.flat_map(|arg| {
|
||||||
|
Parser::new(Lexer::new(&arg))
|
||||||
|
.parse::<Expr>()
|
||||||
|
.map(|arg| env.eval(&arg))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
match env.call(main, &args)? {
|
||||||
|
ConValue::Empty => {}
|
||||||
|
retval => println!("{retval}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
// Calculate Fibonacci numbers
|
// Calculate Fibonacci numbers
|
||||||
|
|
||||||
fn main() -> i128 {
|
fn main() {
|
||||||
let num = 10;
|
for num in 0..=30 {
|
||||||
print("fib(", num, "): ", fib(num));
|
println!("fib({num}) = {}", fib(num))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements the classic recursive definition of fib()
|
/// Implements the classic recursive definition of fib()
|
||||||
fn fib(a: i128) -> i128 {
|
fn fib(a: i64) -> i64 {
|
||||||
if a > 1 {
|
if a > 1 {
|
||||||
fib(a - 1) + fib(a - 2)
|
fib(a - 1) + fib(a - 2)
|
||||||
} else {
|
} else {
|
||||||
298
compiler/cl-interpret/src/builtin.rs
Normal file
298
compiler/cl-interpret/src/builtin.rs
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
//! Implementations of built-in functions
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
convalue::ConValue,
|
||||||
|
env::Environment,
|
||||||
|
error::{Error, IResult},
|
||||||
|
BuiltIn, Callable,
|
||||||
|
};
|
||||||
|
use cl_ast::Sym;
|
||||||
|
use std::{
|
||||||
|
io::{stdout, Write},
|
||||||
|
rc::Rc,
|
||||||
|
slice,
|
||||||
|
};
|
||||||
|
|
||||||
|
builtins! {
|
||||||
|
const MISC;
|
||||||
|
/// Unstable variadic format function
|
||||||
|
pub fn format<_, args> () -> IResult<ConValue> {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut out = String::new();
|
||||||
|
for arg in args {
|
||||||
|
write!(out, "{arg}").ok();
|
||||||
|
}
|
||||||
|
Ok(ConValue::String(out.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unstable variadic print function
|
||||||
|
pub fn print<_, args> () -> IResult<ConValue> {
|
||||||
|
let mut out = stdout().lock();
|
||||||
|
for arg in args {
|
||||||
|
write!(out, "{arg}").ok();
|
||||||
|
}
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unstable variadic println function
|
||||||
|
pub fn println<_, args> () -> IResult<ConValue> {
|
||||||
|
let mut out = stdout().lock();
|
||||||
|
for arg in args {
|
||||||
|
write!(out, "{arg}").ok();
|
||||||
|
}
|
||||||
|
writeln!(out).ok();
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints the [Debug](std::fmt::Debug) version of the input values
|
||||||
|
pub fn dbg<_, args> () -> IResult<ConValue> {
|
||||||
|
let mut out = stdout().lock();
|
||||||
|
for arg in args {
|
||||||
|
writeln!(out, "{arg:?}").ok();
|
||||||
|
}
|
||||||
|
Ok(args.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dumps info from the environment
|
||||||
|
pub fn dump<env, _>() -> IResult<ConValue> {
|
||||||
|
println!("{}", *env);
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len<env, _>(list) -> IResult<ConValue> {
|
||||||
|
Ok(ConValue::Int(match list {
|
||||||
|
ConValue::Empty => 0,
|
||||||
|
ConValue::String(s) => s.chars().count() as _,
|
||||||
|
ConValue::Ref(r) => return len.call(env, slice::from_ref(r.as_ref())),
|
||||||
|
ConValue::Array(t) => t.len() as _,
|
||||||
|
ConValue::Tuple(t) => t.len() as _,
|
||||||
|
ConValue::RangeExc(start, end) => (end - start) as _,
|
||||||
|
ConValue::RangeInc(start, end) => (end - start + 1) as _,
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builtins! {
|
||||||
|
const BINARY;
|
||||||
|
/// Multiplication `a * b`
|
||||||
|
pub fn mul(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Division `a / b`
|
||||||
|
pub fn div(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs){
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remainder `a % b`
|
||||||
|
pub fn rem(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Addition `a + b`
|
||||||
|
pub fn add(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
|
||||||
|
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).into(),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtraction `a - b`
|
||||||
|
pub fn sub(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shift Left `a << b`
|
||||||
|
pub fn shl(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shift Right `a >> b`
|
||||||
|
pub fn shr(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise And `a & b`
|
||||||
|
pub fn and(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise Or `a | b`
|
||||||
|
pub fn or(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise Exclusive Or `a ^ b`
|
||||||
|
pub fn xor(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether `a < b`
|
||||||
|
pub fn lt(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, false, <)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether `a <= b`
|
||||||
|
pub fn lt_eq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, true, <=)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether `a == b`
|
||||||
|
pub fn eq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, true, ==)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether `a != b`
|
||||||
|
pub fn neq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, false, !=)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether `a <= b`
|
||||||
|
pub fn gt_eq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, true, >=)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether `a < b`
|
||||||
|
pub fn gt(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, false, >)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builtins! {
|
||||||
|
const RANGE;
|
||||||
|
/// Exclusive Range `a..b`
|
||||||
|
pub fn range_exc(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(ConValue::RangeExc(lhs, rhs.saturating_sub(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inclusive Range `a..=b`
|
||||||
|
pub fn range_inc(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(ConValue::RangeInc(lhs, rhs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builtins! {
|
||||||
|
const UNARY;
|
||||||
|
/// Negates the ConValue
|
||||||
|
pub fn neg(tail) -> IResult<ConValue> {
|
||||||
|
Ok(match tail {
|
||||||
|
ConValue::Empty => ConValue::Empty,
|
||||||
|
ConValue::Int(v) => ConValue::Int(v.wrapping_neg()),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inverts the ConValue
|
||||||
|
pub fn not(tail) -> IResult<ConValue> {
|
||||||
|
Ok(match tail {
|
||||||
|
ConValue::Empty => ConValue::Empty,
|
||||||
|
ConValue::Int(v) => ConValue::Int(!v),
|
||||||
|
ConValue::Bool(v) => ConValue::Bool(!v),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deref(tail) -> IResult<ConValue> {
|
||||||
|
Ok(match tail {
|
||||||
|
ConValue::Ref(v) => Rc::as_ref(v).clone(),
|
||||||
|
_ => tail.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns an argument slice into an array with the (inferred) correct number of elements
|
||||||
|
pub fn to_args<const N: usize>(args: &[ConValue]) -> IResult<&[ConValue; N]> {
|
||||||
|
args.try_into()
|
||||||
|
.map_err(|_| Error::ArgNumber { want: N, got: args.len() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns function definitions into ZSTs which implement [Callable] and [BuiltIn]
|
||||||
|
macro builtins (
|
||||||
|
$(prefix = $prefix:literal)?
|
||||||
|
const $defaults:ident $( = [$($additional_builtins:expr),*$(,)?])?;
|
||||||
|
$(
|
||||||
|
$(#[$meta:meta])*$vis:vis fn $name:ident$(<$env:tt, $args:tt>)? ( $($($arg:tt),+$(,)?)? ) $(-> $rety:ty)?
|
||||||
|
$body:block
|
||||||
|
)*
|
||||||
|
) {
|
||||||
|
/// Builtins to load when a new interpreter is created
|
||||||
|
pub const $defaults: &[&dyn BuiltIn] = &[$(&$name,)* $($additional_builtins)*];
|
||||||
|
$(
|
||||||
|
$(#[$meta])* #[allow(non_camel_case_types)] #[derive(Clone, Debug)]
|
||||||
|
/// ```rust,ignore
|
||||||
|
#[doc = stringify!(builtin! fn $name($($($arg),*)?) $(-> $rety)? $body)]
|
||||||
|
/// ```
|
||||||
|
$vis struct $name;
|
||||||
|
impl BuiltIn for $name {
|
||||||
|
fn description(&self) -> &str { concat!("builtin ", stringify!($name), stringify!(($($($arg),*)?) )) }
|
||||||
|
}
|
||||||
|
impl Callable for $name {
|
||||||
|
#[allow(unused)]
|
||||||
|
fn call(&self, env: &mut Environment, args: &[ConValue]) $(-> $rety)? {
|
||||||
|
// println!("{}", stringify!($name), );
|
||||||
|
$(let $env = env;
|
||||||
|
let $args = args;)?
|
||||||
|
$(let [$($arg),*] = to_args(args)?;)?
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
fn name(&self) -> Sym { stringify!($name).into() }
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Templates comparison functions for [ConValue]
|
||||||
|
macro cmp ($a:expr, $b:expr, $empty:literal, $op:tt) {
|
||||||
|
match ($a, $b) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => Ok(ConValue::Bool($empty)),
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => Ok(ConValue::Bool(a $op b)),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => Ok(ConValue::Bool(a $op b)),
|
||||||
|
(ConValue::Char(a), ConValue::Char(b)) => Ok(ConValue::Bool(a $op b)),
|
||||||
|
(ConValue::String(a), ConValue::String(b)) => Ok(ConValue::Bool(&**a $op &**b)),
|
||||||
|
_ => Err(Error::TypeError)
|
||||||
|
}
|
||||||
|
}
|
||||||
291
compiler/cl-interpret/src/convalue.rs
Normal file
291
compiler/cl-interpret/src/convalue.rs
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
//! Values in the dynamically typed AST interpreter.
|
||||||
|
//!
|
||||||
|
//! The most permanent fix is a temporary one.
|
||||||
|
use cl_ast::Sym;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
error::{Error, IResult},
|
||||||
|
function::Function,
|
||||||
|
BuiltIn, Callable, Environment,
|
||||||
|
};
|
||||||
|
use std::{ops::*, rc::Rc};
|
||||||
|
|
||||||
|
type Integer = isize;
|
||||||
|
|
||||||
|
/// A Conlang value stores data in the interpreter
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub enum ConValue {
|
||||||
|
/// The empty/unit `()` type
|
||||||
|
#[default]
|
||||||
|
Empty,
|
||||||
|
/// An integer
|
||||||
|
Int(Integer),
|
||||||
|
/// A boolean
|
||||||
|
Bool(bool),
|
||||||
|
/// A unicode character
|
||||||
|
Char(char),
|
||||||
|
/// A string
|
||||||
|
String(Sym),
|
||||||
|
/// A reference
|
||||||
|
Ref(Rc<ConValue>),
|
||||||
|
/// An Array
|
||||||
|
Array(Rc<[ConValue]>),
|
||||||
|
/// A tuple
|
||||||
|
Tuple(Rc<[ConValue]>),
|
||||||
|
/// An exclusive range
|
||||||
|
RangeExc(Integer, Integer),
|
||||||
|
/// An inclusive range
|
||||||
|
RangeInc(Integer, Integer),
|
||||||
|
/// A callable thing
|
||||||
|
Function(Function),
|
||||||
|
/// A built-in function
|
||||||
|
BuiltIn(&'static dyn BuiltIn),
|
||||||
|
}
|
||||||
|
impl ConValue {
|
||||||
|
/// Gets whether the current value is true or false
|
||||||
|
pub fn truthy(&self) -> IResult<bool> {
|
||||||
|
match self {
|
||||||
|
ConValue::Bool(v) => Ok(*v),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn range_exc(self, other: Self) -> IResult<Self> {
|
||||||
|
let (Self::Int(a), Self::Int(b)) = (self, other) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(Self::RangeExc(a, b.saturating_sub(1)))
|
||||||
|
}
|
||||||
|
pub fn range_inc(self, other: Self) -> IResult<Self> {
|
||||||
|
let (Self::Int(a), Self::Int(b)) = (self, other) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(Self::RangeInc(a, b))
|
||||||
|
}
|
||||||
|
pub fn index(&self, index: &Self) -> IResult<ConValue> {
|
||||||
|
let Self::Int(index) = index else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
ConValue::String(string) => string
|
||||||
|
.chars()
|
||||||
|
.nth(*index as _)
|
||||||
|
.map(ConValue::Char)
|
||||||
|
.ok_or(Error::OobIndex(*index as usize, string.chars().count())),
|
||||||
|
ConValue::Array(arr) => arr
|
||||||
|
.get(*index as usize)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(Error::OobIndex(*index as usize, arr.len())),
|
||||||
|
_ => Err(Error::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmp! {
|
||||||
|
lt: false, <;
|
||||||
|
lt_eq: true, <=;
|
||||||
|
eq: true, ==;
|
||||||
|
neq: false, !=;
|
||||||
|
gt_eq: true, >=;
|
||||||
|
gt: false, >;
|
||||||
|
}
|
||||||
|
assign! {
|
||||||
|
add_assign: +;
|
||||||
|
bitand_assign: &;
|
||||||
|
bitor_assign: |;
|
||||||
|
bitxor_assign: ^;
|
||||||
|
div_assign: /;
|
||||||
|
mul_assign: *;
|
||||||
|
rem_assign: %;
|
||||||
|
shl_assign: <<;
|
||||||
|
shr_assign: >>;
|
||||||
|
sub_assign: -;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for ConValue {
|
||||||
|
fn name(&self) -> Sym {
|
||||||
|
match self {
|
||||||
|
ConValue::Function(func) => func.name(),
|
||||||
|
ConValue::BuiltIn(func) => func.name(),
|
||||||
|
_ => "".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||||
|
match self {
|
||||||
|
Self::Function(func) => func.call(interpreter, args),
|
||||||
|
Self::BuiltIn(func) => func.call(interpreter, args),
|
||||||
|
_ => Err(Error::NotCallable(self.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Templates comparison functions for [ConValue]
|
||||||
|
macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$(
|
||||||
|
/// TODO: Remove when functions are implemented:
|
||||||
|
/// Desugar into function calls
|
||||||
|
pub fn $fn(&self, other: &Self) -> IResult<Self> {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Empty, Self::Empty) => Ok(Self::Bool($empty)),
|
||||||
|
(Self::Int(a), Self::Int(b)) => Ok(Self::Bool(a $op b)),
|
||||||
|
(Self::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)),
|
||||||
|
(Self::Char(a), Self::Char(b)) => Ok(Self::Bool(a $op b)),
|
||||||
|
(Self::String(a), Self::String(b)) => Ok(Self::Bool(&**a $op &**b)),
|
||||||
|
_ => Err(Error::TypeError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*}
|
||||||
|
macro assign($( $fn: ident: $op: tt );*$(;)?) {$(
|
||||||
|
pub fn $fn(&mut self, other: Self) -> IResult<()> {
|
||||||
|
*self = (std::mem::take(self) $op other)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)*}
|
||||||
|
/// Implements [From] for an enum with 1-tuple variants
|
||||||
|
macro from ($($T:ty => $v:expr),*$(,)?) {
|
||||||
|
$(impl From<$T> for ConValue {
|
||||||
|
fn from(value: $T) -> Self { $v(value.into()) }
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
impl From<&Sym> for ConValue {
|
||||||
|
fn from(value: &Sym) -> Self {
|
||||||
|
ConValue::String(*value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
from! {
|
||||||
|
Integer => ConValue::Int,
|
||||||
|
bool => ConValue::Bool,
|
||||||
|
char => ConValue::Char,
|
||||||
|
Sym => ConValue::String,
|
||||||
|
&str => ConValue::String,
|
||||||
|
String => ConValue::String,
|
||||||
|
Rc<str> => ConValue::String,
|
||||||
|
Function => ConValue::Function,
|
||||||
|
Vec<ConValue> => ConValue::Tuple,
|
||||||
|
&'static dyn BuiltIn => ConValue::BuiltIn,
|
||||||
|
}
|
||||||
|
impl From<()> for ConValue {
|
||||||
|
fn from(_: ()) -> Self {
|
||||||
|
Self::Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&[ConValue]> for ConValue {
|
||||||
|
fn from(value: &[ConValue]) -> Self {
|
||||||
|
match value.len() {
|
||||||
|
0 => Self::Empty,
|
||||||
|
1 => value[0].clone(),
|
||||||
|
_ => Self::Tuple(value.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements binary [std::ops] traits for [ConValue]
|
||||||
|
///
|
||||||
|
/// TODO: Desugar operators into function calls
|
||||||
|
macro ops($($trait:ty: $fn:ident = [$($match:tt)*])*) {
|
||||||
|
$(impl $trait for ConValue {
|
||||||
|
type Output = IResult<Self>;
|
||||||
|
/// TODO: Desugar operators into function calls
|
||||||
|
fn $fn(self, rhs: Self) -> Self::Output {Ok(match (self, rhs) {$($match)*})}
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
ops! {
|
||||||
|
Add: add = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_add(b)),
|
||||||
|
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).into(),
|
||||||
|
(ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() }
|
||||||
|
(ConValue::Char(a), ConValue::Char(b)) => {
|
||||||
|
ConValue::String([a, b].into_iter().collect::<String>().into())
|
||||||
|
}
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
BitAnd: bitand = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
BitOr: bitor = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
BitXor: bitxor = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Div: div = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_div(b).unwrap_or_else(|| {
|
||||||
|
eprintln!("Warning: Divide by zero in {a} / {b}"); a
|
||||||
|
})),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Mul: mul = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(b)),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Rem: rem = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_rem(b).unwrap_or_else(|| {
|
||||||
|
eprintln!("Warning: Divide by zero in {a} % {b}"); a
|
||||||
|
})),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Shl: shl = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shl(b as _)),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Shr: shr = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shr(b as _)),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Sub: sub = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(b)),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ConValue {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ConValue::Empty => "Empty".fmt(f),
|
||||||
|
ConValue::Int(v) => v.fmt(f),
|
||||||
|
ConValue::Bool(v) => v.fmt(f),
|
||||||
|
ConValue::Char(v) => v.fmt(f),
|
||||||
|
ConValue::String(v) => v.fmt(f),
|
||||||
|
ConValue::Ref(v) => write!(f, "&{v}"),
|
||||||
|
ConValue::Array(array) => {
|
||||||
|
'['.fmt(f)?;
|
||||||
|
for (idx, element) in array.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
", ".fmt(f)?
|
||||||
|
}
|
||||||
|
element.fmt(f)?
|
||||||
|
}
|
||||||
|
']'.fmt(f)
|
||||||
|
}
|
||||||
|
ConValue::RangeExc(a, b) => write!(f, "{a}..{}", b + 1),
|
||||||
|
ConValue::RangeInc(a, b) => write!(f, "{a}..={b}"),
|
||||||
|
ConValue::Tuple(tuple) => {
|
||||||
|
'('.fmt(f)?;
|
||||||
|
for (idx, element) in tuple.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
", ".fmt(f)?
|
||||||
|
}
|
||||||
|
element.fmt(f)?
|
||||||
|
}
|
||||||
|
')'.fmt(f)
|
||||||
|
}
|
||||||
|
ConValue::Function(func) => {
|
||||||
|
write!(f, "{}", func.decl())
|
||||||
|
}
|
||||||
|
ConValue::BuiltIn(func) => {
|
||||||
|
write!(f, "{}", func.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
165
compiler/cl-interpret/src/env.rs
Normal file
165
compiler/cl-interpret/src/env.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
//! Lexical and non-lexical scoping for variables
|
||||||
|
use super::{
|
||||||
|
builtin::{BINARY, MISC, RANGE, UNARY},
|
||||||
|
convalue::ConValue,
|
||||||
|
error::{Error, IResult},
|
||||||
|
function::Function,
|
||||||
|
BuiltIn, Callable, Interpret,
|
||||||
|
};
|
||||||
|
use cl_ast::{Function as FnDecl, Sym};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::Display,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
type StackFrame = HashMap<Sym, Option<ConValue>>;
|
||||||
|
|
||||||
|
/// Implements a nested lexical scope
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Environment {
|
||||||
|
frames: Vec<(StackFrame, &'static str)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Environment {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for (frame, name) in self.frames.iter().rev() {
|
||||||
|
writeln!(f, "--- {name} ---")?;
|
||||||
|
for (var, val) in frame {
|
||||||
|
write!(f, "{var}: ")?;
|
||||||
|
match val {
|
||||||
|
Some(value) => writeln!(f, "\t{value}"),
|
||||||
|
None => writeln!(f, "<undefined>"),
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for Environment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
frames: vec![
|
||||||
|
(to_hashmap(RANGE), "range ops"),
|
||||||
|
(to_hashmap(UNARY), "unary ops"),
|
||||||
|
(to_hashmap(BINARY), "binary ops"),
|
||||||
|
(to_hashmap(MISC), "builtins"),
|
||||||
|
(HashMap::new(), "globals"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<Sym, Option<ConValue>> {
|
||||||
|
from.iter().map(|&v| (v.name(), Some(v.into()))).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Environment {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
/// Creates an [Environment] with no [builtins](super::builtin)
|
||||||
|
pub fn no_builtins(name: &'static str) -> Self {
|
||||||
|
Self { frames: vec![(Default::default(), name)] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(&mut self, node: &impl Interpret) -> IResult<ConValue> {
|
||||||
|
node.interpret(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls a function inside the interpreter's scope,
|
||||||
|
/// and returns the result
|
||||||
|
pub fn call(&mut self, name: Sym, args: &[ConValue]) -> IResult<ConValue> {
|
||||||
|
// FIXME: Clone to satisfy the borrow checker
|
||||||
|
let function = self.get(name)?.clone();
|
||||||
|
function.call(self, args)
|
||||||
|
}
|
||||||
|
/// Enters a nested scope, returning a [`Frame`] stack-guard.
|
||||||
|
///
|
||||||
|
/// [`Frame`] implements Deref/DerefMut for [`Environment`].
|
||||||
|
pub fn frame(&mut self, name: &'static str) -> Frame {
|
||||||
|
Frame::new(self, name)
|
||||||
|
}
|
||||||
|
/// Resolves a variable mutably.
|
||||||
|
///
|
||||||
|
/// Returns a mutable reference to the variable's record, if it exists.
|
||||||
|
pub fn get_mut(&mut self, id: Sym) -> IResult<&mut Option<ConValue>> {
|
||||||
|
for (frame, _) in self.frames.iter_mut().rev() {
|
||||||
|
if let Some(var) = frame.get_mut(&id) {
|
||||||
|
return Ok(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::NotDefined(id))
|
||||||
|
}
|
||||||
|
/// Resolves a variable immutably.
|
||||||
|
///
|
||||||
|
/// Returns a reference to the variable's contents, if it is defined and initialized.
|
||||||
|
pub fn get(&self, id: Sym) -> IResult<ConValue> {
|
||||||
|
for (frame, _) in self.frames.iter().rev() {
|
||||||
|
match frame.get(&id) {
|
||||||
|
Some(Some(var)) => return Ok(var.clone()),
|
||||||
|
Some(None) => return Err(Error::NotInitialized(id)),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::NotDefined(id))
|
||||||
|
}
|
||||||
|
/// Inserts a new [ConValue] into this [Environment]
|
||||||
|
pub fn insert(&mut self, id: Sym, value: Option<ConValue>) {
|
||||||
|
if let Some((frame, _)) = self.frames.last_mut() {
|
||||||
|
frame.insert(id, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A convenience function for registering a [FnDecl] as a [Function]
|
||||||
|
pub fn insert_fn(&mut self, decl: &FnDecl) {
|
||||||
|
let FnDecl { name, .. } = decl;
|
||||||
|
let (name, function) = (name, Some(Function::new(decl).into()));
|
||||||
|
if let Some((frame, _)) = self.frames.last_mut() {
|
||||||
|
frame.insert(*name, function);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions which aid in the implementation of [`Frame`]
|
||||||
|
impl Environment {
|
||||||
|
/// Enters a scope, creating a new namespace for variables
|
||||||
|
fn enter(&mut self, name: &'static str) -> &mut Self {
|
||||||
|
self.frames.push((Default::default(), name));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exits the scope, destroying all local variables and
|
||||||
|
/// returning the outer scope, if there is one
|
||||||
|
fn exit(&mut self) -> &mut Self {
|
||||||
|
if self.frames.len() > 2 {
|
||||||
|
self.frames.pop();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a stack frame
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Frame<'scope> {
|
||||||
|
scope: &'scope mut Environment,
|
||||||
|
}
|
||||||
|
impl<'scope> Frame<'scope> {
|
||||||
|
fn new(scope: &'scope mut Environment, name: &'static str) -> Self {
|
||||||
|
Self { scope: scope.enter(name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'scope> Deref for Frame<'scope> {
|
||||||
|
type Target = Environment;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'scope> DerefMut for Frame<'scope> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'scope> Drop for Frame<'scope> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.scope.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
91
compiler/cl-interpret/src/error.rs
Normal file
91
compiler/cl-interpret/src/error.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
|
||||||
|
|
||||||
|
use cl_ast::Sym;
|
||||||
|
|
||||||
|
use super::convalue::ConValue;
|
||||||
|
|
||||||
|
pub type IResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
|
/// Represents any error thrown by the [Environment](super::Environment)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Propagate a Return value
|
||||||
|
Return(ConValue),
|
||||||
|
/// Propagate a Break value
|
||||||
|
Break(ConValue),
|
||||||
|
/// Break propagated across function bounds
|
||||||
|
BadBreak(ConValue),
|
||||||
|
/// Continue to the next iteration of a loop
|
||||||
|
Continue,
|
||||||
|
/// Underflowed the stack
|
||||||
|
StackUnderflow,
|
||||||
|
/// Exited the last scope
|
||||||
|
ScopeExit,
|
||||||
|
/// Type incompatibility
|
||||||
|
// TODO: store the type information in this error
|
||||||
|
TypeError,
|
||||||
|
/// In clause of For loop didn't yield a Range
|
||||||
|
NotIterable,
|
||||||
|
/// A value could not be indexed
|
||||||
|
NotIndexable,
|
||||||
|
/// An array index went out of bounds
|
||||||
|
OobIndex(usize, usize),
|
||||||
|
/// An expression is not assignable
|
||||||
|
NotAssignable,
|
||||||
|
/// A name was not defined in scope before being used
|
||||||
|
NotDefined(Sym),
|
||||||
|
/// A name was defined but not initialized
|
||||||
|
NotInitialized(Sym),
|
||||||
|
/// A value was called, but is not callable
|
||||||
|
NotCallable(ConValue),
|
||||||
|
/// A function was called with the wrong number of arguments
|
||||||
|
ArgNumber {
|
||||||
|
want: usize,
|
||||||
|
got: usize,
|
||||||
|
},
|
||||||
|
Outlined(Sym),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Return(value) => write!(f, "return {value}"),
|
||||||
|
Error::Break(value) => write!(f, "break {value}"),
|
||||||
|
Error::BadBreak(value) => write!(f, "rogue break: {value}"),
|
||||||
|
Error::Continue => "continue".fmt(f),
|
||||||
|
Error::StackUnderflow => "Stack underflow".fmt(f),
|
||||||
|
Error::ScopeExit => "Exited the last scope. This is a logic bug.".fmt(f),
|
||||||
|
Error::TypeError => "Incompatible types".fmt(f),
|
||||||
|
Error::NotIterable => "`in` clause of `for` loop did not yield an iterable".fmt(f),
|
||||||
|
Error::NotIndexable => {
|
||||||
|
write!(f, "expression cannot be indexed")
|
||||||
|
}
|
||||||
|
Error::OobIndex(idx, len) => {
|
||||||
|
write!(f, "Index out of bounds: index was {idx}. but len is {len}")
|
||||||
|
}
|
||||||
|
Error::NotAssignable => {
|
||||||
|
write!(f, "expression is not assignable")
|
||||||
|
}
|
||||||
|
Error::NotDefined(value) => {
|
||||||
|
write!(f, "{value} not bound. Did you mean `let {value};`?")
|
||||||
|
}
|
||||||
|
Error::NotInitialized(value) => {
|
||||||
|
write!(f, "{value} bound, but not initialized")
|
||||||
|
}
|
||||||
|
Error::NotCallable(value) => {
|
||||||
|
write!(f, "{value} is not callable.")
|
||||||
|
}
|
||||||
|
Error::ArgNumber { want, got } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Expected {want} argument{}, got {got}",
|
||||||
|
if *want == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Error::Outlined(name) => {
|
||||||
|
write!(f, "Module {name} specified, but not imported.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
compiler/cl-interpret/src/function.rs
Normal file
49
compiler/cl-interpret/src/function.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//! Represents a block of code which lives inside the Interpreter
|
||||||
|
|
||||||
|
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
|
||||||
|
use cl_ast::{Function as FnDecl, Param, Sym};
|
||||||
|
use std::rc::Rc;
|
||||||
|
/// Represents a block of code which persists inside the Interpreter
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Function {
|
||||||
|
/// Stores the contents of the function declaration
|
||||||
|
decl: Rc<FnDecl>,
|
||||||
|
// /// Stores the enclosing scope of the function
|
||||||
|
// env: Box<Environment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
pub fn new(decl: &FnDecl) -> Self {
|
||||||
|
Self { decl: decl.clone().into() }
|
||||||
|
}
|
||||||
|
pub fn decl(&self) -> &FnDecl {
|
||||||
|
&self.decl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for Function {
|
||||||
|
fn name(&self) -> Sym {
|
||||||
|
let FnDecl { name, .. } = *self.decl;
|
||||||
|
name
|
||||||
|
}
|
||||||
|
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||||
|
let FnDecl { name, bind, body, sign: _ } = &*self.decl;
|
||||||
|
// Check arg mapping
|
||||||
|
if args.len() != bind.len() {
|
||||||
|
return Err(Error::ArgNumber { want: bind.len(), got: args.len() });
|
||||||
|
}
|
||||||
|
let Some(body) = body else {
|
||||||
|
return Err(Error::NotDefined(*name));
|
||||||
|
};
|
||||||
|
// TODO: completely refactor data storage
|
||||||
|
let mut frame = env.frame("fn args");
|
||||||
|
for (Param { mutability: _, name }, value) in bind.iter().zip(args) {
|
||||||
|
frame.insert(*name, Some(value.clone()));
|
||||||
|
}
|
||||||
|
match body.interpret(&mut frame) {
|
||||||
|
Err(Error::Return(value)) => Ok(value),
|
||||||
|
Err(Error::Break(value)) => Err(Error::BadBreak(value)),
|
||||||
|
result => result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
600
compiler/cl-interpret/src/interpret.rs
Normal file
600
compiler/cl-interpret/src/interpret.rs
Normal file
@@ -0,0 +1,600 @@
|
|||||||
|
//! A work-in-progress tree walk interpreter for Conlang
|
||||||
|
//!
|
||||||
|
//! Currently, major parts of the interpreter are not yet implemented, and major parts will never be
|
||||||
|
//! implemented in its current form. Namely, since no [ConValue] has a stable location, it's
|
||||||
|
//! meaningless to get a pointer to one, and would be undefined behavior to dereference a pointer to
|
||||||
|
//! one in any situation.
|
||||||
|
|
||||||
|
use std::{borrow::Borrow, rc::Rc};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use cl_ast::*;
|
||||||
|
/// A work-in-progress tree walk interpreter for Conlang
|
||||||
|
pub trait Interpret {
|
||||||
|
/// Interprets this thing in the given [`Environment`].
|
||||||
|
///
|
||||||
|
/// Everything returns a value!™
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpret for File {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
for item in &self.items {
|
||||||
|
item.interpret(env)?;
|
||||||
|
}
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Item {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
match &self.kind {
|
||||||
|
ItemKind::Alias(item) => item.interpret(env),
|
||||||
|
ItemKind::Const(item) => item.interpret(env),
|
||||||
|
ItemKind::Static(item) => item.interpret(env),
|
||||||
|
ItemKind::Module(item) => item.interpret(env),
|
||||||
|
ItemKind::Function(item) => item.interpret(env),
|
||||||
|
ItemKind::Struct(item) => item.interpret(env),
|
||||||
|
ItemKind::Enum(item) => item.interpret(env),
|
||||||
|
ItemKind::Impl(item) => item.interpret(env),
|
||||||
|
ItemKind::Use(_) => todo!("namespaces and imports in the interpreter"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Alias {
|
||||||
|
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
println!("TODO: {self}");
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Const {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Const { name, ty: _, init } = self;
|
||||||
|
|
||||||
|
let init = init.as_ref().interpret(env)?;
|
||||||
|
env.insert(*name, Some(init));
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Static {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Static { mutable: _, name, ty: _, init } = self;
|
||||||
|
|
||||||
|
let init = init.as_ref().interpret(env)?;
|
||||||
|
env.insert(*name, Some(init));
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Module {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { name, kind } = self;
|
||||||
|
// TODO: Enter this module's namespace
|
||||||
|
match kind {
|
||||||
|
ModuleKind::Inline(file) => file.interpret(env),
|
||||||
|
ModuleKind::Outline => Err(Error::Outlined(*name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Function {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
// register the function in the current environment
|
||||||
|
env.insert_fn(self);
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Struct {
|
||||||
|
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
println!("TODO: {self}");
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Enum {
|
||||||
|
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
println!("TODO: {self}");
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Impl {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
println!("TODO: {self}");
|
||||||
|
let Self { target: _, body } = self;
|
||||||
|
body.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Stmt {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { extents: _, kind, semi } = self;
|
||||||
|
let out = match kind {
|
||||||
|
StmtKind::Empty => ConValue::Empty,
|
||||||
|
StmtKind::Item(stmt) => stmt.interpret(env)?,
|
||||||
|
StmtKind::Expr(stmt) => stmt.interpret(env)?,
|
||||||
|
};
|
||||||
|
Ok(match semi {
|
||||||
|
Semi::Terminated => ConValue::Empty,
|
||||||
|
Semi::Unterminated => out,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Let {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Let { mutable: _, name, ty: _, init } = self;
|
||||||
|
let init = init.as_ref().map(|i| i.interpret(env)).transpose()?;
|
||||||
|
env.insert(*name, init);
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Expr {
|
||||||
|
#[inline]
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { extents: _, kind } = self;
|
||||||
|
kind.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for ExprKind {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
match self {
|
||||||
|
ExprKind::Empty => Ok(ConValue::Empty),
|
||||||
|
ExprKind::Let(v) => v.interpret(env),
|
||||||
|
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::Cast(v) => v.interpret(env),
|
||||||
|
ExprKind::Member(v) => v.interpret(env),
|
||||||
|
ExprKind::Index(v) => v.interpret(env),
|
||||||
|
ExprKind::Structor(v) => v.interpret(env),
|
||||||
|
ExprKind::Path(v) => v.interpret(env),
|
||||||
|
ExprKind::Literal(v) => v.interpret(env),
|
||||||
|
ExprKind::Array(v) => v.interpret(env),
|
||||||
|
ExprKind::ArrayRep(v) => v.interpret(env),
|
||||||
|
ExprKind::AddrOf(v) => v.interpret(env),
|
||||||
|
ExprKind::Block(v) => v.interpret(env),
|
||||||
|
ExprKind::Group(v) => v.interpret(env),
|
||||||
|
ExprKind::Tuple(v) => v.interpret(env),
|
||||||
|
ExprKind::While(v) => v.interpret(env),
|
||||||
|
ExprKind::If(v) => v.interpret(env),
|
||||||
|
ExprKind::For(v) => v.interpret(env),
|
||||||
|
ExprKind::Break(v) => v.interpret(env),
|
||||||
|
ExprKind::Return(v) => v.interpret(env),
|
||||||
|
ExprKind::Continue => Err(Error::Continue),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_place_expr<'e>(
|
||||||
|
env: &'e mut Environment,
|
||||||
|
expr: &ExprKind,
|
||||||
|
) -> IResult<(&'e mut Option<ConValue>, Sym)> {
|
||||||
|
match expr {
|
||||||
|
ExprKind::Path(Path { parts, .. }) if parts.len() == 1 => {
|
||||||
|
match parts.last().expect("parts should not be empty") {
|
||||||
|
PathPart::SuperKw => Err(Error::NotAssignable),
|
||||||
|
PathPart::SelfKw => todo!("Assignment to `self`"),
|
||||||
|
PathPart::SelfTy => todo!("What does it mean to assign to capital-S Self?"),
|
||||||
|
PathPart::Ident(s) => env.get_mut(*s).map(|v| (v, *s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExprKind::Index(_) => todo!("Assignment to an index operation"),
|
||||||
|
ExprKind::Path(_) => todo!("Path expression resolution (IMPORTANT)"),
|
||||||
|
ExprKind::Empty | ExprKind::Group(_) | ExprKind::Tuple(_) => {
|
||||||
|
todo!("Pattern Destructuring?")
|
||||||
|
}
|
||||||
|
_ => Err(Error::NotAssignable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpret for Assign {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Assign { parts } = self;
|
||||||
|
let (head, tail) = parts.borrow();
|
||||||
|
let init = tail.interpret(env)?;
|
||||||
|
// Resolve the head pattern
|
||||||
|
let target = evaluate_place_expr(env, head)?;
|
||||||
|
use std::mem::discriminant as variant;
|
||||||
|
// runtime typecheck
|
||||||
|
match target.0 {
|
||||||
|
Some(value) if variant(value) == variant(&init) => {
|
||||||
|
*value = init;
|
||||||
|
}
|
||||||
|
value @ None => *value = Some(init),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
}
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Modify {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Modify { kind: op, parts } = self;
|
||||||
|
let (head, tail) = parts.borrow();
|
||||||
|
// Get the initializer and the tail
|
||||||
|
let init = tail.interpret(env)?;
|
||||||
|
// Resolve the head pattern
|
||||||
|
let target = evaluate_place_expr(env, head)?;
|
||||||
|
let (Some(target), _) = target else {
|
||||||
|
return Err(Error::NotInitialized(target.1));
|
||||||
|
};
|
||||||
|
|
||||||
|
match op {
|
||||||
|
ModifyKind::Add => target.add_assign(init),
|
||||||
|
ModifyKind::Sub => target.sub_assign(init),
|
||||||
|
ModifyKind::Mul => target.mul_assign(init),
|
||||||
|
ModifyKind::Div => target.div_assign(init),
|
||||||
|
ModifyKind::Rem => target.rem_assign(init),
|
||||||
|
ModifyKind::And => target.bitand_assign(init),
|
||||||
|
ModifyKind::Or => target.bitor_assign(init),
|
||||||
|
ModifyKind::Xor => target.bitxor_assign(init),
|
||||||
|
ModifyKind::Shl => target.shl_assign(init),
|
||||||
|
ModifyKind::Shr => target.shr_assign(init),
|
||||||
|
}?;
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Binary {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Binary { kind, parts } = self;
|
||||||
|
let (head, tail) = parts.borrow();
|
||||||
|
|
||||||
|
let head = head.interpret(env)?;
|
||||||
|
|
||||||
|
// Short-circuiting ops
|
||||||
|
match kind {
|
||||||
|
BinaryKind::LogAnd => {
|
||||||
|
return if head.truthy()? {
|
||||||
|
tail.interpret(env)
|
||||||
|
} else {
|
||||||
|
Ok(head)
|
||||||
|
}; // Short circuiting
|
||||||
|
}
|
||||||
|
BinaryKind::LogOr => {
|
||||||
|
return if !head.truthy()? {
|
||||||
|
tail.interpret(env)
|
||||||
|
} else {
|
||||||
|
Ok(head)
|
||||||
|
}; // Short circuiting
|
||||||
|
}
|
||||||
|
BinaryKind::LogXor => {
|
||||||
|
return Ok(ConValue::Bool(
|
||||||
|
head.truthy()? ^ tail.interpret(env)?.truthy()?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let tail = tail.interpret(env)?;
|
||||||
|
match kind {
|
||||||
|
BinaryKind::Lt => head.lt(&tail),
|
||||||
|
BinaryKind::LtEq => head.lt_eq(&tail),
|
||||||
|
BinaryKind::Equal => head.eq(&tail),
|
||||||
|
BinaryKind::NotEq => head.neq(&tail),
|
||||||
|
BinaryKind::GtEq => head.gt_eq(&tail),
|
||||||
|
BinaryKind::Gt => head.gt(&tail),
|
||||||
|
BinaryKind::RangeExc => head.range_exc(tail),
|
||||||
|
BinaryKind::RangeInc => head.range_inc(tail),
|
||||||
|
BinaryKind::BitAnd => head & tail,
|
||||||
|
BinaryKind::BitOr => head | tail,
|
||||||
|
BinaryKind::BitXor => head ^ tail,
|
||||||
|
BinaryKind::Shl => head << tail,
|
||||||
|
BinaryKind::Shr => head >> tail,
|
||||||
|
BinaryKind::Add => head + tail,
|
||||||
|
BinaryKind::Sub => head - tail,
|
||||||
|
BinaryKind::Mul => head * tail,
|
||||||
|
BinaryKind::Div => head / tail,
|
||||||
|
BinaryKind::Rem => head % tail,
|
||||||
|
BinaryKind::Call => match tail {
|
||||||
|
ConValue::Empty => head.call(env, &[]),
|
||||||
|
ConValue::Tuple(args) => head.call(env, &args),
|
||||||
|
_ => Err(Error::TypeError),
|
||||||
|
},
|
||||||
|
_ => Ok(head),
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Temporarily disabled, to avoid function dispatch overhead while I screw around
|
||||||
|
// // Not like it helped much in the first place!
|
||||||
|
// match kind {
|
||||||
|
// BinaryKind::Mul => env.call("mul", &[head, tail]),
|
||||||
|
// BinaryKind::Div => env.call("div", &[head, tail]),
|
||||||
|
// BinaryKind::Rem => env.call("rem", &[head, tail]),
|
||||||
|
// BinaryKind::Add => env.call("add", &[head, tail]),
|
||||||
|
// BinaryKind::Sub => env.call("sub", &[head, tail]),
|
||||||
|
// BinaryKind::Shl => env.call("shl", &[head, tail]),
|
||||||
|
// BinaryKind::Shr => env.call("shr", &[head, tail]),
|
||||||
|
// BinaryKind::BitAnd => env.call("and", &[head, tail]),
|
||||||
|
// BinaryKind::BitOr => env.call("or", &[head, tail]),
|
||||||
|
// BinaryKind::BitXor => env.call("xor", &[head, tail]),
|
||||||
|
// BinaryKind::RangeExc => env.call("range_exc", &[head, tail]),
|
||||||
|
// BinaryKind::RangeInc => env.call("range_inc", &[head, tail]),
|
||||||
|
// BinaryKind::Lt => env.call("lt", &[head, tail]),
|
||||||
|
// BinaryKind::LtEq => env.call("lt_eq", &[head, tail]),
|
||||||
|
// BinaryKind::Equal => env.call("eq", &[head, tail]),
|
||||||
|
// BinaryKind::NotEq => env.call("neq", &[head, tail]),
|
||||||
|
// BinaryKind::GtEq => env.call("gt_eq", &[head, tail]),
|
||||||
|
// BinaryKind::Gt => env.call("gt", &[head, tail]),
|
||||||
|
// BinaryKind::Dot => todo!("search within a type's namespace!"),
|
||||||
|
// BinaryKind::Call => match tail {
|
||||||
|
// ConValue::Empty => head.call(env, &[]),
|
||||||
|
// ConValue::Tuple(args) => head.call(env, &args),
|
||||||
|
// _ => Err(Error::TypeError),
|
||||||
|
// },
|
||||||
|
// _ => Ok(head),
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpret for Unary {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Unary { kind, tail } = self;
|
||||||
|
match kind {
|
||||||
|
UnaryKind::Loop => loop {
|
||||||
|
match tail.interpret(env) {
|
||||||
|
Err(Error::Break(value)) => return Ok(value),
|
||||||
|
Err(Error::Continue) => continue,
|
||||||
|
e => e?,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
UnaryKind::Deref => {
|
||||||
|
let operand = tail.interpret(env)?;
|
||||||
|
env.call("deref".into(), &[operand])
|
||||||
|
}
|
||||||
|
UnaryKind::Neg => {
|
||||||
|
let operand = tail.interpret(env)?;
|
||||||
|
env.call("neg".into(), &[operand])
|
||||||
|
}
|
||||||
|
UnaryKind::Not => {
|
||||||
|
let operand = tail.interpret(env)?;
|
||||||
|
env.call("not".into(), &[operand])
|
||||||
|
}
|
||||||
|
UnaryKind::At => {
|
||||||
|
let operand = tail.interpret(env)?;
|
||||||
|
println!("{operand}");
|
||||||
|
Ok(operand)
|
||||||
|
}
|
||||||
|
UnaryKind::Tilde => unimplemented!("Tilde operator"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: ConValue, ty: Sym) -> IResult<ConValue> {
|
||||||
|
let value = match value {
|
||||||
|
ConValue::Empty => 0,
|
||||||
|
ConValue::Int(i) => i as _,
|
||||||
|
ConValue::Bool(b) => b as _,
|
||||||
|
ConValue::Char(c) => c as _,
|
||||||
|
ConValue::Ref(v) => return cast((*v).clone(), ty),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
};
|
||||||
|
Ok(match &*ty {
|
||||||
|
"u8" => ConValue::Int(value as u8 as _),
|
||||||
|
"i8" => ConValue::Int(value as i8 as _),
|
||||||
|
"u16" => ConValue::Int(value as u16 as _),
|
||||||
|
"i16" => ConValue::Int(value as i16 as _),
|
||||||
|
"u32" => ConValue::Int(value as u32 as _),
|
||||||
|
"i32" => ConValue::Int(value as i32 as _),
|
||||||
|
"u64" => ConValue::Int(value),
|
||||||
|
"i64" => ConValue::Int(value),
|
||||||
|
"char" => ConValue::Char(char::from_u32(value as _).unwrap_or('\u{fffd}')),
|
||||||
|
"bool" => ConValue::Bool(value < 0),
|
||||||
|
_ => Err(Error::NotDefined(ty))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpret for Cast {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Cast { head, ty } = self;
|
||||||
|
let value = head.interpret(env)?;
|
||||||
|
if TyKind::Empty == ty.kind {
|
||||||
|
return Ok(ConValue::Empty);
|
||||||
|
};
|
||||||
|
let TyKind::Path(Path { absolute: false, parts }) = &ty.kind else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
match parts.as_slice() {
|
||||||
|
[PathPart::Ident(ty)] => cast(value, *ty),
|
||||||
|
_ => Err(Error::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpret for Member {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Member { head, kind } = self;
|
||||||
|
let head = head.interpret(env)?;
|
||||||
|
match (head, kind) {
|
||||||
|
(ConValue::Tuple(v), MemberKind::Tuple(Literal::Int(id))) => v
|
||||||
|
.get(*id as usize)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(Error::OobIndex(*id as usize, v.len())),
|
||||||
|
(head, MemberKind::Call(name, args)) => {
|
||||||
|
let mut values = vec![head];
|
||||||
|
for arg in &args.exprs {
|
||||||
|
values.push(arg.interpret(env)?);
|
||||||
|
}
|
||||||
|
env.call(*name, &values)
|
||||||
|
}
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Index {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { head, indices } = self;
|
||||||
|
let mut head = head.interpret(env)?;
|
||||||
|
for index in indices {
|
||||||
|
head = head.index(&index.interpret(env)?)?;
|
||||||
|
}
|
||||||
|
Ok(head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Structor {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("struct construction in {env}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Path {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { absolute: _, parts } = self;
|
||||||
|
|
||||||
|
if parts.len() == 1 {
|
||||||
|
match parts.last().expect("parts should not be empty") {
|
||||||
|
PathPart::SuperKw | PathPart::SelfKw => todo!("Path navigation"),
|
||||||
|
PathPart::SelfTy => todo!("Path navigation to Self"),
|
||||||
|
PathPart::Ident(name) => env.get(*name),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("Path navigation!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Literal {
|
||||||
|
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
Ok(match self {
|
||||||
|
Literal::String(value) => ConValue::from(value.as_str()),
|
||||||
|
Literal::Char(value) => ConValue::Char(*value),
|
||||||
|
Literal::Bool(value) => ConValue::Bool(*value),
|
||||||
|
// Literal::Float(value) => todo!("Float values in interpreter: {value:?}"),
|
||||||
|
Literal::Int(value) => ConValue::Int(*value as _),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Array {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { values } = self;
|
||||||
|
let mut out = vec![];
|
||||||
|
for expr in values {
|
||||||
|
out.push(expr.interpret(env)?)
|
||||||
|
}
|
||||||
|
Ok(ConValue::Array(out.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for ArrayRep {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { value, repeat } = self;
|
||||||
|
let repeat = match repeat.interpret(env)? {
|
||||||
|
ConValue::Int(v) => v,
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
};
|
||||||
|
let value = value.interpret(env)?;
|
||||||
|
Ok(ConValue::Array(vec![value; repeat as usize].into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for AddrOf {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { count: _, mutable: _, expr } = self;
|
||||||
|
match expr.as_ref() {
|
||||||
|
ExprKind::Index(_) => todo!("AddrOf array index"),
|
||||||
|
// ExprKind::Path(Path { absolute: false, parts }) => match parts.as_slice() {
|
||||||
|
// [PathPart::Ident(id)] => env.get_ref(id),
|
||||||
|
// _ => todo!("Path traversal in addrof"),
|
||||||
|
// },
|
||||||
|
ExprKind::Path(_) => todo!("Path traversal in addrof"),
|
||||||
|
_ => Ok(ConValue::Ref(Rc::new(expr.interpret(env)?))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Block {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { stmts } = self;
|
||||||
|
let mut env = env.frame("block");
|
||||||
|
let mut out = ConValue::Empty;
|
||||||
|
for stmt in stmts {
|
||||||
|
out = stmt.interpret(&mut env)?;
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Group {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { expr } = self;
|
||||||
|
expr.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Tuple {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { exprs } = self;
|
||||||
|
Ok(ConValue::Tuple(
|
||||||
|
exprs
|
||||||
|
.iter()
|
||||||
|
.try_fold(vec![], |mut out, element| {
|
||||||
|
out.push(element.interpret(env)?);
|
||||||
|
Ok(out)
|
||||||
|
})?
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for While {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { cond, pass, fail } = self;
|
||||||
|
loop {
|
||||||
|
if cond.interpret(env)?.truthy()? {
|
||||||
|
match pass.interpret(env) {
|
||||||
|
Err(Error::Break(value)) => return Ok(value),
|
||||||
|
Err(Error::Continue) => continue,
|
||||||
|
e => e?,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
break fail.interpret(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for If {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { cond, pass, fail } = self;
|
||||||
|
if cond.interpret(env)?.truthy()? {
|
||||||
|
pass.interpret(env)
|
||||||
|
} else {
|
||||||
|
fail.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for For {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { bind: name, cond, pass, fail } = self;
|
||||||
|
// TODO: A better iterator model
|
||||||
|
let mut bounds = match cond.interpret(env)? {
|
||||||
|
ConValue::RangeExc(a, b) => a..=b,
|
||||||
|
ConValue::RangeInc(a, b) => a..=b,
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
let mut env = env.frame("loop variable");
|
||||||
|
if let Some(loop_var) = bounds.next() {
|
||||||
|
env.insert(*name, Some(loop_var.into()));
|
||||||
|
match pass.interpret(&mut env) {
|
||||||
|
Err(Error::Break(value)) => return Ok(value),
|
||||||
|
Err(Error::Continue) => continue,
|
||||||
|
result => result?,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
break fail.interpret(&mut env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Else {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { body } = self;
|
||||||
|
match body {
|
||||||
|
Some(body) => body.interpret(env),
|
||||||
|
None => Ok(ConValue::Empty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Return {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { body } = self;
|
||||||
|
Err(Error::Return(
|
||||||
|
body.as_ref()
|
||||||
|
.map(|body| body.interpret(env))
|
||||||
|
.unwrap_or(Ok(ConValue::Empty))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Break {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { body } = self;
|
||||||
|
Err(Error::Break(
|
||||||
|
body.as_ref()
|
||||||
|
.map(|body| body.interpret(env))
|
||||||
|
.unwrap_or(Ok(ConValue::Empty))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
38
compiler/cl-interpret/src/lib.rs
Normal file
38
compiler/cl-interpret/src/lib.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//! Walks a Conlang AST, interpreting it as a program.
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
|
||||||
|
use cl_ast::Sym;
|
||||||
|
use convalue::ConValue;
|
||||||
|
use env::Environment;
|
||||||
|
use error::{Error, IResult};
|
||||||
|
use interpret::Interpret;
|
||||||
|
|
||||||
|
/// Callable types can be called from within a Conlang program
|
||||||
|
pub trait Callable: std::fmt::Debug {
|
||||||
|
/// Calls this [Callable] in the provided [Environment], with [ConValue] args \
|
||||||
|
/// The Callable is responsible for checking the argument count and validating types
|
||||||
|
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue>;
|
||||||
|
/// Returns the common name of this identifier.
|
||||||
|
fn name(&self) -> Sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [BuiltIn]s are [Callable]s with bespoke definitions
|
||||||
|
pub trait BuiltIn: std::fmt::Debug + Callable {
|
||||||
|
fn description(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod convalue;
|
||||||
|
|
||||||
|
pub mod interpret;
|
||||||
|
|
||||||
|
pub mod function;
|
||||||
|
|
||||||
|
pub mod builtin;
|
||||||
|
|
||||||
|
pub mod env;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
use crate::{
|
use crate::{env::Environment, convalue::ConValue, Interpret};
|
||||||
ast::*,
|
use cl_ast::*;
|
||||||
interpreter::{env::Environment, temp_type_impl::ConValue, Interpret},
|
use cl_lexer::Lexer;
|
||||||
lexer::Lexer,
|
use cl_parser::Parser;
|
||||||
parser::Parser,
|
|
||||||
};
|
|
||||||
pub use macros::*;
|
pub use macros::*;
|
||||||
|
|
||||||
mod macros {
|
mod macros {
|
||||||
@@ -49,7 +47,8 @@ mod macros {
|
|||||||
//! env_eq!(env.x, 10); // like assert_eq! for Environments
|
//! env_eq!(env.x, 10); // like assert_eq! for Environments
|
||||||
//! ```
|
//! ```
|
||||||
#![allow(unused_macros)]
|
#![allow(unused_macros)]
|
||||||
use crate::interpreter::IResult;
|
use crate::IResult;
|
||||||
|
use cl_parser::parser::Parse;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -65,14 +64,14 @@ mod macros {
|
|||||||
///
|
///
|
||||||
/// Returns a `Result<`[`File`]`, ParseError>`
|
/// Returns a `Result<`[`File`]`, ParseError>`
|
||||||
pub macro file($($t:tt)*) {
|
pub macro file($($t:tt)*) {
|
||||||
Parser::new(Lexer::new(stringify!( $($t)* ))).file()
|
File::parse(&mut Parser::new(Lexer::new(stringify!( $($t)* ))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stringifies, lexes, and parses everything you give to it
|
/// Stringifies, lexes, and parses everything you give to it
|
||||||
///
|
///
|
||||||
/// Returns a `Result<`[`Block`]`, ParseError>`
|
/// Returns a `Result<`[`Block`]`, ParseError>`
|
||||||
pub macro block($($t:tt)*) {
|
pub macro block($($t:tt)*) {
|
||||||
Parser::new(Lexer::new(stringify!({ $($t)* }))).block()
|
Block::parse(&mut Parser::new(Lexer::new(stringify!({ $($t)* }))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluates a block of code in the given environment
|
/// Evaluates a block of code in the given environment
|
||||||
@@ -129,7 +128,7 @@ mod macros {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub macro env_ne($env:ident.$var:ident, $expr:expr) {{
|
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));
|
.expect(stringify!($var should be defined and initialized));
|
||||||
if !conv_cmp!(neq, evaluated, $expr) {
|
if !conv_cmp!(neq, evaluated, $expr) {
|
||||||
panic!("assertion {} ({evaluated}) != {} failed.", stringify!($var), stringify!($expr))
|
panic!("assertion {} ({evaluated}) != {} failed.", stringify!($var), stringify!($expr))
|
||||||
@@ -137,7 +136,7 @@ mod macros {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
pub macro env_eq($env:ident.$var:ident, $expr:expr) {{
|
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));
|
.expect(stringify!($var should be defined and initialized));
|
||||||
if !conv_cmp!(eq, evaluated, $expr) {
|
if !conv_cmp!(eq, evaluated, $expr) {
|
||||||
panic!("assertion {} ({evaluated}) == {} failed.", stringify!($var), stringify!($expr))
|
panic!("assertion {} ({evaluated}) == {} failed.", stringify!($var), stringify!($expr))
|
||||||
@@ -189,10 +188,10 @@ mod fn_declarations {
|
|||||||
assert_eval!(env, fn empty_fn() {});
|
assert_eval!(env, fn empty_fn() {});
|
||||||
// TODO: true equality for functions
|
// TODO: true equality for functions
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"fn empty_fn",
|
"fn empty_fn () {\n \n}",
|
||||||
format!(
|
format!(
|
||||||
"{}",
|
"{}",
|
||||||
env.get("empty_fn")
|
env.get("empty_fn".into())
|
||||||
.expect(stringify!(empty_fn should be defined and initialized))
|
.expect(stringify!(empty_fn should be defined and initialized))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -212,7 +211,7 @@ mod fn_declarations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod operators {
|
mod operators {
|
||||||
use crate::ast::Tuple;
|
use cl_ast::Tuple;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
13
compiler/cl-lexer/Cargo.toml
Normal file
13
compiler/cl-lexer/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-lexer"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-token = { path = "../cl-token" }
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
|
unicode-ident = "1.0.12"
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
//! Converts a text file into tokens
|
//! Converts a text file into tokens
|
||||||
use crate::token::preamble::*;
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
use cl_structures::span::Loc;
|
||||||
|
use cl_token::{TokenKind as Kind, *};
|
||||||
use std::{
|
use std::{
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
};
|
};
|
||||||
use unicode_xid::UnicodeXID;
|
use unicode_ident::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub mod lexer_iter {
|
pub mod lexer_iter {
|
||||||
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
|
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
|
||||||
@@ -45,7 +51,8 @@ pub mod lexer_iter {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use conlang::lexer::Lexer;
|
/// # use cl_lexer::Lexer;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// // Read in your code from somewhere
|
/// // Read in your code from somewhere
|
||||||
/// let some_code = "
|
/// let some_code = "
|
||||||
/// fn main () {
|
/// fn main () {
|
||||||
@@ -55,16 +62,17 @@ pub mod lexer_iter {
|
|||||||
/// // Create a lexer over your code
|
/// // Create a lexer over your code
|
||||||
/// let mut lexer = Lexer::new(some_code);
|
/// let mut lexer = Lexer::new(some_code);
|
||||||
/// // Scan for a single token
|
/// // Scan for a single token
|
||||||
/// let first_token = lexer.scan().unwrap();
|
/// let first_token = lexer.scan()?;
|
||||||
/// println!("{first_token:?}");
|
/// println!("{first_token:?}");
|
||||||
/// // Loop over all the rest of the tokens
|
/// // Loop over all the rest of the tokens
|
||||||
/// for token in lexer {
|
/// for token in lexer {
|
||||||
/// # let token: Result<_,()> = Ok(token.unwrap());
|
/// # let token: Result<_,()> = Ok(token?);
|
||||||
/// match token {
|
/// match token {
|
||||||
/// Ok(token) => println!("{token:?}"),
|
/// Ok(token) => println!("{token:?}"),
|
||||||
/// Err(e) => eprintln!("{e:?}"),
|
/// Err(e) => eprintln!("{e:?}"),
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// # Ok(()) }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Lexer<'t> {
|
pub struct Lexer<'t> {
|
||||||
@@ -89,40 +97,40 @@ impl<'t> Lexer<'t> {
|
|||||||
/// Scans through the text, searching for the next [Token]
|
/// Scans through the text, searching for the next [Token]
|
||||||
pub fn scan(&mut self) -> LResult<Token> {
|
pub fn scan(&mut self) -> LResult<Token> {
|
||||||
match self.skip_whitespace().peek()? {
|
match self.skip_whitespace().peek()? {
|
||||||
'{' => self.consume()?.produce(Type::LCurly, ()),
|
'{' => self.consume()?.produce_op(Kind::LCurly),
|
||||||
'}' => self.consume()?.produce(Type::RCurly, ()),
|
'}' => self.consume()?.produce_op(Kind::RCurly),
|
||||||
'[' => self.consume()?.produce(Type::LBrack, ()),
|
'[' => self.consume()?.produce_op(Kind::LBrack),
|
||||||
']' => self.consume()?.produce(Type::RBrack, ()),
|
']' => self.consume()?.produce_op(Kind::RBrack),
|
||||||
'(' => self.consume()?.produce(Type::LParen, ()),
|
'(' => self.consume()?.produce_op(Kind::LParen),
|
||||||
')' => self.consume()?.produce(Type::RParen, ()),
|
')' => self.consume()?.produce_op(Kind::RParen),
|
||||||
'&' => self.consume()?.amp(),
|
'&' => self.consume()?.amp(),
|
||||||
'@' => self.consume()?.produce(Type::At, ()),
|
'@' => self.consume()?.produce_op(Kind::At),
|
||||||
'\\' => self.consume()?.produce(Type::Backslash, ()),
|
'\\' => self.consume()?.produce_op(Kind::Backslash),
|
||||||
'!' => self.consume()?.bang(),
|
'!' => self.consume()?.bang(),
|
||||||
'|' => self.consume()?.bar(),
|
'|' => self.consume()?.bar(),
|
||||||
':' => self.consume()?.colon(),
|
':' => self.consume()?.colon(),
|
||||||
',' => self.consume()?.produce(Type::Comma, ()),
|
',' => self.consume()?.produce_op(Kind::Comma),
|
||||||
'.' => self.consume()?.dot(),
|
'.' => self.consume()?.dot(),
|
||||||
'=' => self.consume()?.equal(),
|
'=' => self.consume()?.equal(),
|
||||||
'`' => self.consume()?.produce(Type::Grave, ()),
|
'`' => self.consume()?.produce_op(Kind::Grave),
|
||||||
'>' => self.consume()?.greater(),
|
'>' => self.consume()?.greater(),
|
||||||
'#' => self.consume()?.hash(),
|
'#' => self.consume()?.hash(),
|
||||||
'<' => self.consume()?.less(),
|
'<' => self.consume()?.less(),
|
||||||
'-' => self.consume()?.minus(),
|
'-' => self.consume()?.minus(),
|
||||||
'+' => self.consume()?.plus(),
|
'+' => self.consume()?.plus(),
|
||||||
'?' => self.consume()?.produce(Type::Question, ()),
|
'?' => self.consume()?.produce_op(Kind::Question),
|
||||||
'%' => self.consume()?.rem(),
|
'%' => self.consume()?.rem(),
|
||||||
';' => self.consume()?.produce(Type::Semi, ()),
|
';' => self.consume()?.produce_op(Kind::Semi),
|
||||||
'/' => self.consume()?.slash(),
|
'/' => self.consume()?.slash(),
|
||||||
'*' => self.consume()?.star(),
|
'*' => self.consume()?.star(),
|
||||||
'~' => self.consume()?.produce(Type::Tilde, ()),
|
'~' => self.consume()?.produce_op(Kind::Tilde),
|
||||||
'^' => self.consume()?.xor(),
|
'^' => self.consume()?.xor(),
|
||||||
'0' => self.consume()?.int_with_base(),
|
'0' => self.consume()?.int_with_base(),
|
||||||
'1'..='9' => self.digits::<10>(),
|
'1'..='9' => self.digits::<10>(),
|
||||||
'"' => self.consume()?.string(),
|
'"' => self.consume()?.string(),
|
||||||
'\'' => self.consume()?.character(),
|
'\'' => self.consume()?.character(),
|
||||||
'_' => self.identifier(),
|
'_' => self.identifier(),
|
||||||
i if i.is_xid_start() => self.identifier(),
|
i if is_xid_start(i) => self.identifier(),
|
||||||
e => {
|
e => {
|
||||||
let err = Err(Error::unexpected_char(e, self.line(), self.col()));
|
let err = Err(Error::unexpected_char(e, self.line(), self.col()));
|
||||||
let _ = self.consume();
|
let _ = self.consume();
|
||||||
@@ -149,11 +157,14 @@ impl<'t> Lexer<'t> {
|
|||||||
.copied()
|
.copied()
|
||||||
.ok_or(Error::end_of_file(self.line(), self.col()))
|
.ok_or(Error::end_of_file(self.line(), self.col()))
|
||||||
}
|
}
|
||||||
fn produce(&mut self, ty: Type, data: impl Into<Data>) -> LResult<Token> {
|
fn produce(&mut self, kind: Kind, data: impl Into<TokenData>) -> LResult<Token> {
|
||||||
let loc = self.start_loc;
|
let loc = self.start_loc;
|
||||||
self.start_loc = self.current_loc;
|
self.start_loc = self.current_loc;
|
||||||
self.start = self.current;
|
self.start = self.current;
|
||||||
Ok(Token::new(ty, data, loc.0, loc.1))
|
Ok(Token::new(kind, data, loc.0, loc.1))
|
||||||
|
}
|
||||||
|
fn produce_op(&mut self, kind: Kind) -> LResult<Token> {
|
||||||
|
self.produce(kind, ())
|
||||||
}
|
}
|
||||||
fn skip_whitespace(&mut self) -> &mut Self {
|
fn skip_whitespace(&mut self) -> &mut Self {
|
||||||
while let Ok(c) = self.peek() {
|
while let Ok(c) = self.peek() {
|
||||||
@@ -184,138 +195,147 @@ impl<'t> Lexer<'t> {
|
|||||||
impl<'t> Lexer<'t> {
|
impl<'t> Lexer<'t> {
|
||||||
fn amp(&mut self) -> LResult<Token> {
|
fn amp(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('&') => self.consume()?.produce(Type::AmpAmp, ()),
|
Ok('&') => self.consume()?.produce_op(Kind::AmpAmp),
|
||||||
Ok('=') => self.consume()?.produce(Type::AmpEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::AmpEq),
|
||||||
_ => self.produce(Type::Amp, ()),
|
_ => self.produce_op(Kind::Amp),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn bang(&mut self) -> LResult<Token> {
|
fn bang(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('!') => self.consume()?.produce(Type::BangBang, ()),
|
Ok('!') => self.consume()?.produce_op(Kind::BangBang),
|
||||||
Ok('=') => self.consume()?.produce(Type::BangEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::BangEq),
|
||||||
_ => self.produce(Type::Bang, ()),
|
_ => self.produce_op(Kind::Bang),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn bar(&mut self) -> LResult<Token> {
|
fn bar(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('|') => self.consume()?.produce(Type::BarBar, ()),
|
Ok('|') => self.consume()?.produce_op(Kind::BarBar),
|
||||||
Ok('=') => self.consume()?.produce(Type::BarEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::BarEq),
|
||||||
_ => self.produce(Type::Bar, ()),
|
_ => self.produce_op(Kind::Bar),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn colon(&mut self) -> LResult<Token> {
|
fn colon(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok(':') => self.consume()?.produce(Type::ColonColon, ()),
|
Ok(':') => self.consume()?.produce_op(Kind::ColonColon),
|
||||||
_ => self.produce(Type::Colon, ()),
|
_ => self.produce_op(Kind::Colon),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn dot(&mut self) -> LResult<Token> {
|
fn dot(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('.') => {
|
Ok('.') => {
|
||||||
if let Ok('=') = self.consume()?.peek() {
|
if let Ok('=') = self.consume()?.peek() {
|
||||||
self.consume()?.produce(Type::DotDotEq, ())
|
self.consume()?.produce_op(Kind::DotDotEq)
|
||||||
} else {
|
} else {
|
||||||
self.produce(Type::DotDot, ())
|
self.produce_op(Kind::DotDot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.produce(Type::Dot, ()),
|
_ => self.produce_op(Kind::Dot),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn equal(&mut self) -> LResult<Token> {
|
fn equal(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::EqEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::EqEq),
|
||||||
Ok('>') => self.consume()?.produce(Type::FatArrow, ()),
|
Ok('>') => self.consume()?.produce_op(Kind::FatArrow),
|
||||||
_ => self.produce(Type::Eq, ()),
|
_ => self.produce_op(Kind::Eq),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn greater(&mut self) -> LResult<Token> {
|
fn greater(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::GtEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::GtEq),
|
||||||
Ok('>') => {
|
Ok('>') => {
|
||||||
if let Ok('=') = self.consume()?.peek() {
|
if let Ok('=') = self.consume()?.peek() {
|
||||||
self.consume()?.produce(Type::GtGtEq, ())
|
self.consume()?.produce_op(Kind::GtGtEq)
|
||||||
} else {
|
} else {
|
||||||
self.produce(Type::GtGt, ())
|
self.produce_op(Kind::GtGt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.produce(Type::Gt, ()),
|
_ => self.produce_op(Kind::Gt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn hash(&mut self) -> LResult<Token> {
|
fn hash(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('!') => self.consume()?.produce(Type::HashBang, ()),
|
Ok('!') => self.consume()?.hashbang(),
|
||||||
_ => self.produce(Type::Hash, ()),
|
_ => self.produce_op(Kind::Hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn hashbang(&mut self) -> LResult<Token> {
|
||||||
|
match self.peek() {
|
||||||
|
Ok('/' | '\'') => self.line_comment(),
|
||||||
|
_ => self.produce_op(Kind::HashBang),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn less(&mut self) -> LResult<Token> {
|
fn less(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::LtEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::LtEq),
|
||||||
Ok('<') => {
|
Ok('<') => {
|
||||||
if let Ok('=') = self.consume()?.peek() {
|
if let Ok('=') = self.consume()?.peek() {
|
||||||
self.consume()?.produce(Type::LtLtEq, ())
|
self.consume()?.produce_op(Kind::LtLtEq)
|
||||||
} else {
|
} else {
|
||||||
self.produce(Type::LtLt, ())
|
self.produce_op(Kind::LtLt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.produce(Type::Lt, ()),
|
_ => self.produce_op(Kind::Lt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn minus(&mut self) -> LResult<Token> {
|
fn minus(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::MinusEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::MinusEq),
|
||||||
Ok('>') => self.consume()?.produce(Type::Arrow, ()),
|
Ok('>') => self.consume()?.produce_op(Kind::Arrow),
|
||||||
_ => self.produce(Type::Minus, ()),
|
_ => self.produce_op(Kind::Minus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn plus(&mut self) -> LResult<Token> {
|
fn plus(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::PlusEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::PlusEq),
|
||||||
_ => self.produce(Type::Plus, ()),
|
_ => self.produce_op(Kind::Plus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn rem(&mut self) -> LResult<Token> {
|
fn rem(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::RemEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::RemEq),
|
||||||
_ => self.produce(Type::Rem, ()),
|
_ => self.produce_op(Kind::Rem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn slash(&mut self) -> LResult<Token> {
|
fn slash(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::SlashEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::SlashEq),
|
||||||
Ok('/') => self.consume()?.line_comment(),
|
Ok('/') => self.consume()?.line_comment(),
|
||||||
Ok('*') => self.consume()?.block_comment(),
|
Ok('*') => self.consume()?.block_comment(),
|
||||||
_ => self.produce(Type::Slash, ()),
|
_ => self.produce_op(Kind::Slash),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn star(&mut self) -> LResult<Token> {
|
fn star(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::StarEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::StarEq),
|
||||||
_ => self.produce(Type::Star, ()),
|
_ => self.produce_op(Kind::Star),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn xor(&mut self) -> LResult<Token> {
|
fn xor(&mut self) -> LResult<Token> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Ok('=') => self.consume()?.produce(Type::XorEq, ()),
|
Ok('=') => self.consume()?.produce_op(Kind::XorEq),
|
||||||
Ok('^') => self.consume()?.produce(Type::XorXor, ()),
|
Ok('^') => self.consume()?.produce_op(Kind::XorXor),
|
||||||
_ => self.produce(Type::Xor, ()),
|
_ => self.produce_op(Kind::Xor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Comments
|
/// Comments
|
||||||
impl<'t> Lexer<'t> {
|
impl<'t> Lexer<'t> {
|
||||||
fn line_comment(&mut self) -> LResult<Token> {
|
fn line_comment(&mut self) -> LResult<Token> {
|
||||||
|
let mut comment = String::new();
|
||||||
while Ok('\n') != self.peek() {
|
while Ok('\n') != self.peek() {
|
||||||
self.consume()?;
|
comment.push(self.next()?);
|
||||||
}
|
}
|
||||||
self.produce(Type::Comment, ())
|
self.produce(Kind::Comment, comment)
|
||||||
}
|
}
|
||||||
fn block_comment(&mut self) -> LResult<Token> {
|
fn block_comment(&mut self) -> LResult<Token> {
|
||||||
|
let mut comment = String::new();
|
||||||
while let Ok(c) = self.next() {
|
while let Ok(c) = self.next() {
|
||||||
if '*' == c && Ok('/') == self.next() {
|
if '*' == c && Ok('/') == self.peek() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
comment.push(c);
|
||||||
}
|
}
|
||||||
self.produce(Type::Comment, ())
|
self.consume()?.produce(Kind::Comment, comment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Identifiers
|
/// Identifiers
|
||||||
@@ -325,15 +345,15 @@ impl<'t> Lexer<'t> {
|
|||||||
while let Ok(c) = self.xid_continue() {
|
while let Ok(c) = self.xid_continue() {
|
||||||
out.push(c)
|
out.push(c)
|
||||||
}
|
}
|
||||||
if let Ok(keyword) = Keyword::from_str(&out) {
|
if let Ok(keyword) = Kind::from_str(&out) {
|
||||||
self.produce(Type::Keyword(keyword), ())
|
self.produce(keyword, ())
|
||||||
} else {
|
} else {
|
||||||
self.produce(Type::Identifier, Data::Identifier(out.into()))
|
self.produce(Kind::Identifier, TokenData::String(out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn xid_start(&mut self) -> LResult<char> {
|
fn xid_start(&mut self) -> LResult<char> {
|
||||||
match self.peek()? {
|
match self.peek()? {
|
||||||
xid if xid == '_' || xid.is_xid_start() => {
|
xid if xid == '_' || is_xid_start(xid) => {
|
||||||
self.consume()?;
|
self.consume()?;
|
||||||
Ok(xid)
|
Ok(xid)
|
||||||
}
|
}
|
||||||
@@ -342,7 +362,7 @@ impl<'t> Lexer<'t> {
|
|||||||
}
|
}
|
||||||
fn xid_continue(&mut self) -> LResult<char> {
|
fn xid_continue(&mut self) -> LResult<char> {
|
||||||
match self.peek()? {
|
match self.peek()? {
|
||||||
xid if xid.is_xid_continue() => {
|
xid if is_xid_continue(xid) => {
|
||||||
self.consume()?;
|
self.consume()?;
|
||||||
Ok(xid)
|
Ok(xid)
|
||||||
}
|
}
|
||||||
@@ -359,7 +379,7 @@ impl<'t> Lexer<'t> {
|
|||||||
Ok('o') => self.consume()?.digits::<8>(),
|
Ok('o') => self.consume()?.digits::<8>(),
|
||||||
Ok('b') => self.consume()?.digits::<2>(),
|
Ok('b') => self.consume()?.digits::<2>(),
|
||||||
Ok('0'..='9') => self.digits::<10>(),
|
Ok('0'..='9') => self.digits::<10>(),
|
||||||
_ => self.produce(Type::Integer, 0),
|
_ => self.produce(Kind::Literal, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn digits<const B: u32>(&mut self) -> LResult<Token> {
|
fn digits<const B: u32>(&mut self) -> LResult<Token> {
|
||||||
@@ -367,7 +387,7 @@ impl<'t> Lexer<'t> {
|
|||||||
while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) {
|
while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) {
|
||||||
value = value * B as u128 + self.digit::<B>()? as u128;
|
value = value * B as u128 + self.digit::<B>()? as u128;
|
||||||
}
|
}
|
||||||
self.produce(Type::Integer, value)
|
self.produce(Kind::Literal, value)
|
||||||
}
|
}
|
||||||
fn digit<const B: u32>(&mut self) -> LResult<u32> {
|
fn digit<const B: u32>(&mut self) -> LResult<u32> {
|
||||||
let digit = self.peek()?;
|
let digit = self.peek()?;
|
||||||
@@ -388,12 +408,12 @@ impl<'t> Lexer<'t> {
|
|||||||
{
|
{
|
||||||
value.push(self.unescape()?)
|
value.push(self.unescape()?)
|
||||||
}
|
}
|
||||||
self.consume()?.produce(Type::String, value)
|
self.consume()?.produce(Kind::Literal, value)
|
||||||
}
|
}
|
||||||
fn character(&mut self) -> LResult<Token> {
|
fn character(&mut self) -> LResult<Token> {
|
||||||
let out = self.unescape()?;
|
let out = self.unescape()?;
|
||||||
match self.peek()? {
|
match self.peek()? {
|
||||||
'\'' => self.consume()?.produce(Type::Character, out),
|
'\'' => self.consume()?.produce(Kind::Literal, out),
|
||||||
_ => Err(Error::unmatched_delimiters('\'', self.line(), self.col())),
|
_ => Err(Error::unmatched_delimiters('\'', self.line(), self.col())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,6 +465,12 @@ impl<'t> Lexer<'t> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'t> From<&Lexer<'t>> for Loc {
|
||||||
|
fn from(value: &Lexer<'t>) -> Self {
|
||||||
|
Loc(value.line(), value.col())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use error::{Error, LResult, Reason};
|
use error::{Error, LResult, Reason};
|
||||||
pub mod error {
|
pub mod error {
|
||||||
//! [Error] type for the [Lexer](super::Lexer)
|
//! [Error] type for the [Lexer](super::Lexer)
|
||||||
@@ -463,7 +489,7 @@ pub mod error {
|
|||||||
pub enum Reason {
|
pub enum Reason {
|
||||||
/// Found an opening delimiter of type [char], but not the expected closing delimiter
|
/// Found an opening delimiter of type [char], but not the expected closing delimiter
|
||||||
UnmatchedDelimiters(char),
|
UnmatchedDelimiters(char),
|
||||||
/// Found a character that doesn't belong to any [Type](crate::token::token_type::Type)
|
/// Found a character that doesn't belong to any [TokenKind](cl_token::TokenKind)
|
||||||
UnexpectedChar(char),
|
UnexpectedChar(char),
|
||||||
/// Found a character that's not valid in identifiers while looking for an identifier
|
/// Found a character that's not valid in identifiers while looking for an identifier
|
||||||
NotIdentifier(char),
|
NotIdentifier(char),
|
||||||
171
compiler/cl-lexer/src/tests.rs
Normal file
171
compiler/cl-lexer/src/tests.rs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
use crate::Lexer;
|
||||||
|
use cl_token::*;
|
||||||
|
|
||||||
|
macro test_lexer_output_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
||||||
|
#[test]
|
||||||
|
fn $f() {$(
|
||||||
|
assert_eq!(
|
||||||
|
Lexer::new($test)
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.unwrap().ty())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
dbg!($expect)
|
||||||
|
);
|
||||||
|
)*}
|
||||||
|
)*}
|
||||||
|
|
||||||
|
macro test_lexer_data_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
||||||
|
#[test]
|
||||||
|
fn $f() {$(
|
||||||
|
assert_eq!(
|
||||||
|
Lexer::new($test)
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.unwrap().into_data())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
dbg!($expect)
|
||||||
|
);
|
||||||
|
)*}
|
||||||
|
)*}
|
||||||
|
|
||||||
|
/// Convert an `[ expr, ... ]` into a `[ *, ... ]`
|
||||||
|
macro td ($($id:expr),*) {
|
||||||
|
[$($id.into()),*]
|
||||||
|
}
|
||||||
|
|
||||||
|
mod ident {
|
||||||
|
use super::*;
|
||||||
|
macro ident ($($id:literal),*) {
|
||||||
|
[$(TokenData::String($id.into())),*]
|
||||||
|
}
|
||||||
|
test_lexer_data_type! {
|
||||||
|
underscore { "_ _" => ident!["_", "_"] }
|
||||||
|
unicode { "_ε ε_" => ident!["_ε", "ε_"] }
|
||||||
|
many_underscore { "____________________________________" =>
|
||||||
|
ident!["____________________________________"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod keyword {
|
||||||
|
use super::*;
|
||||||
|
macro kw($($k:ident),*) {
|
||||||
|
[ $(TokenKind::$k,)* ]
|
||||||
|
}
|
||||||
|
test_lexer_output_type! {
|
||||||
|
kw_break { "break break" => kw![Break, Break] }
|
||||||
|
kw_continue { "continue continue" => kw![Continue, Continue] }
|
||||||
|
kw_else { "else else" => kw![Else, Else] }
|
||||||
|
kw_false { "false false" => kw![False, False] }
|
||||||
|
kw_for { "for for" => kw![For, For] }
|
||||||
|
kw_fn { "fn fn" => kw![Fn, Fn] }
|
||||||
|
kw_if { "if if" => kw![If, If] }
|
||||||
|
kw_in { "in in" => kw![In, In] }
|
||||||
|
kw_let { "let let" => kw![Let, Let] }
|
||||||
|
kw_return { "return return" => kw![Return, Return] }
|
||||||
|
kw_true { "true true" => kw![True, True] }
|
||||||
|
kw_while { "while while" => kw![While, While] }
|
||||||
|
keywords { "break continue else false for fn if in let return true while" =>
|
||||||
|
kw![Break, Continue, Else, False, For, Fn, If, In, Let, Return, True, While] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod integer {
|
||||||
|
use super::*;
|
||||||
|
test_lexer_data_type! {
|
||||||
|
hex {
|
||||||
|
"0x0 0x1 0x15 0x2100 0x8000" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
dec {
|
||||||
|
"0d0 0d1 0d21 0d8448 0d32768" =>
|
||||||
|
td![0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
oct {
|
||||||
|
"0o0 0o1 0o25 0o20400 0o100000" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
bin {
|
||||||
|
"0b0 0b1 0b10101 0b10000100000000 0b1000000000000000" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
baseless {
|
||||||
|
"0 1 21 8448 32768" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod string {
|
||||||
|
use super::*;
|
||||||
|
test_lexer_data_type! {
|
||||||
|
empty_string {
|
||||||
|
"\"\"" =>
|
||||||
|
td![String::from("")]
|
||||||
|
}
|
||||||
|
unicode_string {
|
||||||
|
"\"I 💙 🦈!\"" =>
|
||||||
|
td![String::from("I 💙 🦈!")]
|
||||||
|
}
|
||||||
|
escape_string {
|
||||||
|
" \"This is a shark: \\u{1f988}\" " =>
|
||||||
|
td![String::from("This is a shark: 🦈")]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod punct {
|
||||||
|
macro op($op:ident) {
|
||||||
|
TokenKind::$op
|
||||||
|
}
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
test_lexer_output_type! {
|
||||||
|
l_curly { "{ {" => [ op!(LCurly), op!(LCurly) ] }
|
||||||
|
r_curly { "} }" => [ op!(RCurly), op!(RCurly) ] }
|
||||||
|
l_brack { "[ [" => [ op!(LBrack), op!(LBrack) ] }
|
||||||
|
r_brack { "] ]" => [ op!(RBrack), op!(RBrack) ] }
|
||||||
|
l_paren { "( (" => [ op!(LParen), op!(LParen) ] }
|
||||||
|
r_paren { ") )" => [ op!(RParen), op!(RParen) ] }
|
||||||
|
amp { "& &" => [ op!(Amp), op!(Amp) ] }
|
||||||
|
amp_amp { "&& &&" => [ op!(AmpAmp), op!(AmpAmp) ] }
|
||||||
|
amp_eq { "&= &=" => [ op!(AmpEq), op!(AmpEq) ] }
|
||||||
|
arrow { "-> ->" => [ op!(Arrow), op!(Arrow)] }
|
||||||
|
at { "@ @" => [ op!(At), op!(At)] }
|
||||||
|
backslash { "\\ \\" => [ op!(Backslash), op!(Backslash)] }
|
||||||
|
bang { "! !" => [ op!(Bang), op!(Bang)] }
|
||||||
|
bangbang { "!! !!" => [ op!(BangBang), op!(BangBang)] }
|
||||||
|
bangeq { "!= !=" => [ op!(BangEq), op!(BangEq)] }
|
||||||
|
bar { "| |" => [ op!(Bar), op!(Bar)] }
|
||||||
|
barbar { "|| ||" => [ op!(BarBar), op!(BarBar)] }
|
||||||
|
bareq { "|= |=" => [ op!(BarEq), op!(BarEq)] }
|
||||||
|
colon { ": :" => [ op!(Colon), op!(Colon)] }
|
||||||
|
comma { ", ," => [ op!(Comma), op!(Comma)] }
|
||||||
|
dot { ". ." => [ op!(Dot), op!(Dot)] }
|
||||||
|
dotdot { ".. .." => [ op!(DotDot), op!(DotDot)] }
|
||||||
|
dotdoteq { "..= ..=" => [ op!(DotDotEq), op!(DotDotEq)] }
|
||||||
|
eq { "= =" => [ op!(Eq), op!(Eq)] }
|
||||||
|
eqeq { "== ==" => [ op!(EqEq), op!(EqEq)] }
|
||||||
|
fatarrow { "=> =>" => [ op!(FatArrow), op!(FatArrow)] }
|
||||||
|
grave { "` `" => [ op!(Grave), op!(Grave)] }
|
||||||
|
gt { "> >" => [ op!(Gt), op!(Gt)] }
|
||||||
|
gteq { ">= >=" => [ op!(GtEq), op!(GtEq)] }
|
||||||
|
gtgt { ">> >>" => [ op!(GtGt), op!(GtGt)] }
|
||||||
|
gtgteq { ">>= >>=" => [ op!(GtGtEq), op!(GtGtEq)] }
|
||||||
|
hash { "# #" => [ op!(Hash), op!(Hash)] }
|
||||||
|
lt { "< <" => [ op!(Lt), op!(Lt)] }
|
||||||
|
lteq { "<= <=" => [ op!(LtEq), op!(LtEq)] }
|
||||||
|
ltlt { "<< <<" => [ op!(LtLt), op!(LtLt)] }
|
||||||
|
ltlteq { "<<= <<=" => [ op!(LtLtEq), op!(LtLtEq)] }
|
||||||
|
minus { "- -" => [ op!(Minus), op!(Minus)] }
|
||||||
|
minuseq { "-= -=" => [ op!(MinusEq), op!(MinusEq)] }
|
||||||
|
plus { "+ +" => [ op!(Plus), op!(Plus)] }
|
||||||
|
pluseq { "+= +=" => [ op!(PlusEq), op!(PlusEq)] }
|
||||||
|
question { "? ?" => [ op!(Question), op!(Question)] }
|
||||||
|
rem { "% %" => [ op!(Rem), op!(Rem)] }
|
||||||
|
remeq { "%= %=" => [ op!(RemEq), op!(RemEq)] }
|
||||||
|
semi { "; ;" => [ op!(Semi), op!(Semi)] }
|
||||||
|
slash { "/ /" => [ op!(Slash), op!(Slash)] }
|
||||||
|
slasheq { "/= /=" => [ op!(SlashEq), op!(SlashEq)] }
|
||||||
|
star { "* *" => [ op!(Star), op!(Star)] }
|
||||||
|
stareq { "*= *=" => [ op!(StarEq), op!(StarEq)] }
|
||||||
|
tilde { "~ ~" => [ op!(Tilde), op!(Tilde)] }
|
||||||
|
xor { "^ ^" => [ op!(Xor), op!(Xor)] }
|
||||||
|
xoreq { "^= ^=" => [ op!(XorEq), op!(XorEq)] }
|
||||||
|
xorxor { "^^ ^^" => [ op!(XorXor), op!(XorXor)] }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
compiler/cl-parser/Cargo.toml
Normal file
14
compiler/cl-parser/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-parser"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-ast = { path = "../cl-ast" }
|
||||||
|
cl-lexer = { path = "../cl-lexer" }
|
||||||
|
cl-token = { path = "../cl-token" }
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
231
compiler/cl-parser/src/error.rs
Normal file
231
compiler/cl-parser/src/error.rs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
use cl_lexer::error::{Error as LexError, Reason};
|
||||||
|
use std::fmt::Display;
|
||||||
|
pub type PResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
|
/// Contains information about [Parser] errors
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Error {
|
||||||
|
pub reason: ErrorKind,
|
||||||
|
pub while_parsing: Parsing,
|
||||||
|
pub loc: Loc,
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
/// Represents the reason for parse failure
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
Lexical(LexError),
|
||||||
|
EndOfInput,
|
||||||
|
UnmatchedParentheses,
|
||||||
|
UnmatchedCurlyBraces,
|
||||||
|
UnmatchedSquareBrackets,
|
||||||
|
Unexpected(TokenKind),
|
||||||
|
ExpectedToken {
|
||||||
|
want: TokenKind,
|
||||||
|
got: TokenKind,
|
||||||
|
},
|
||||||
|
ExpectedParsing {
|
||||||
|
want: Parsing,
|
||||||
|
},
|
||||||
|
/// Indicates unfinished code
|
||||||
|
Todo(&'static str),
|
||||||
|
}
|
||||||
|
impl From<LexError> for ErrorKind {
|
||||||
|
fn from(value: LexError) -> Self {
|
||||||
|
match value.reason() {
|
||||||
|
Reason::EndOfFile => Self::EndOfInput,
|
||||||
|
_ => Self::Lexical(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compactly represents the stage of parsing an [Error] originated in
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Parsing {
|
||||||
|
Mutability,
|
||||||
|
Visibility,
|
||||||
|
Identifier,
|
||||||
|
Literal,
|
||||||
|
|
||||||
|
File,
|
||||||
|
|
||||||
|
Attrs,
|
||||||
|
Meta,
|
||||||
|
MetaKind,
|
||||||
|
|
||||||
|
Item,
|
||||||
|
ItemKind,
|
||||||
|
Alias,
|
||||||
|
Const,
|
||||||
|
Static,
|
||||||
|
Module,
|
||||||
|
ModuleKind,
|
||||||
|
Function,
|
||||||
|
Param,
|
||||||
|
Struct,
|
||||||
|
StructKind,
|
||||||
|
StructMember,
|
||||||
|
Enum,
|
||||||
|
EnumKind,
|
||||||
|
Variant,
|
||||||
|
VariantKind,
|
||||||
|
Impl,
|
||||||
|
ImplKind,
|
||||||
|
Use,
|
||||||
|
UseTree,
|
||||||
|
|
||||||
|
Ty,
|
||||||
|
TyKind,
|
||||||
|
TySlice,
|
||||||
|
TyArray,
|
||||||
|
TyTuple,
|
||||||
|
TyRef,
|
||||||
|
TyFn,
|
||||||
|
|
||||||
|
Path,
|
||||||
|
PathPart,
|
||||||
|
|
||||||
|
Stmt,
|
||||||
|
StmtKind,
|
||||||
|
Let,
|
||||||
|
|
||||||
|
Expr,
|
||||||
|
ExprKind,
|
||||||
|
Assign,
|
||||||
|
AssignKind,
|
||||||
|
Binary,
|
||||||
|
BinaryKind,
|
||||||
|
Unary,
|
||||||
|
UnaryKind,
|
||||||
|
Cast,
|
||||||
|
Index,
|
||||||
|
Structor,
|
||||||
|
Fielder,
|
||||||
|
Call,
|
||||||
|
Member,
|
||||||
|
Array,
|
||||||
|
ArrayRep,
|
||||||
|
AddrOf,
|
||||||
|
Block,
|
||||||
|
Group,
|
||||||
|
Tuple,
|
||||||
|
Loop,
|
||||||
|
While,
|
||||||
|
If,
|
||||||
|
For,
|
||||||
|
Else,
|
||||||
|
Break,
|
||||||
|
Return,
|
||||||
|
Continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Self { reason, while_parsing, loc } = self;
|
||||||
|
match reason {
|
||||||
|
// TODO entries are debug-printed
|
||||||
|
ErrorKind::Todo(_) => write!(f, "{loc} {reason} {while_parsing:?}"),
|
||||||
|
// lexical errors print their own higher-resolution loc info
|
||||||
|
ErrorKind::Lexical(e) => write!(f, "{e} (while parsing {while_parsing})"),
|
||||||
|
_ => write!(f, "{loc} {reason} while parsing {while_parsing}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for ErrorKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorKind::Lexical(e) => e.fmt(f),
|
||||||
|
ErrorKind::EndOfInput => write!(f, "End of input"),
|
||||||
|
ErrorKind::UnmatchedParentheses => write!(f, "Unmatched parentheses"),
|
||||||
|
ErrorKind::UnmatchedCurlyBraces => write!(f, "Unmatched curly braces"),
|
||||||
|
ErrorKind::UnmatchedSquareBrackets => write!(f, "Unmatched square brackets"),
|
||||||
|
ErrorKind::Unexpected(t) => write!(f, "Encountered unexpected token `{t}`"),
|
||||||
|
ErrorKind::ExpectedToken { want: e, got: g } => write!(f, "Expected `{e}`, got `{g}`"),
|
||||||
|
ErrorKind::ExpectedParsing { want } => write!(f, "Expected {want}"),
|
||||||
|
ErrorKind::Todo(unfinished) => write!(f, "TODO: {unfinished}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for Parsing {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Parsing::Visibility => "a visibility qualifier",
|
||||||
|
Parsing::Mutability => "a mutability qualifier",
|
||||||
|
Parsing::Identifier => "an identifier",
|
||||||
|
Parsing::Literal => "a literal",
|
||||||
|
|
||||||
|
Parsing::File => "a file",
|
||||||
|
|
||||||
|
Parsing::Attrs => "an attribute-set",
|
||||||
|
Parsing::Meta => "an attribute",
|
||||||
|
Parsing::MetaKind => "an attribute's arguments",
|
||||||
|
Parsing::Item => "an item",
|
||||||
|
Parsing::ItemKind => "an item",
|
||||||
|
Parsing::Alias => "a type alias",
|
||||||
|
Parsing::Const => "a const item",
|
||||||
|
Parsing::Static => "a static variable",
|
||||||
|
Parsing::Module => "a module",
|
||||||
|
Parsing::ModuleKind => "a module",
|
||||||
|
Parsing::Function => "a function",
|
||||||
|
Parsing::Param => "a function parameter",
|
||||||
|
Parsing::Struct => "a struct",
|
||||||
|
Parsing::StructKind => "a struct",
|
||||||
|
Parsing::StructMember => "a struct member",
|
||||||
|
Parsing::Enum => "an enum",
|
||||||
|
Parsing::EnumKind => "an enum",
|
||||||
|
Parsing::Variant => "an enum variant",
|
||||||
|
Parsing::VariantKind => "an enum variant",
|
||||||
|
Parsing::Impl => "an impl block",
|
||||||
|
Parsing::ImplKind => "the target of an impl block",
|
||||||
|
Parsing::Use => "a use item",
|
||||||
|
Parsing::UseTree => "a use-tree",
|
||||||
|
|
||||||
|
Parsing::Ty => "a type",
|
||||||
|
Parsing::TyKind => "a type",
|
||||||
|
Parsing::TySlice => "a slice type",
|
||||||
|
Parsing::TyArray => "an array type",
|
||||||
|
Parsing::TyTuple => "a tuple of types",
|
||||||
|
Parsing::TyRef => "a reference type",
|
||||||
|
Parsing::TyFn => "a function pointer type",
|
||||||
|
|
||||||
|
Parsing::Path => "a path",
|
||||||
|
Parsing::PathPart => "a path component",
|
||||||
|
|
||||||
|
Parsing::Stmt => "a statement",
|
||||||
|
Parsing::StmtKind => "a statement",
|
||||||
|
Parsing::Let => "a local variable declaration",
|
||||||
|
|
||||||
|
Parsing::Expr => "an expression",
|
||||||
|
Parsing::ExprKind => "an expression",
|
||||||
|
Parsing::Assign => "an assignment",
|
||||||
|
Parsing::AssignKind => "an assignment operator",
|
||||||
|
Parsing::Binary => "a binary expression",
|
||||||
|
Parsing::BinaryKind => "a binary operator",
|
||||||
|
Parsing::Unary => "a unary expression",
|
||||||
|
Parsing::UnaryKind => "a unary operator",
|
||||||
|
Parsing::Cast => "an `as`-casting expression",
|
||||||
|
Parsing::Index => "an indexing expression",
|
||||||
|
Parsing::Structor => "a struct constructor expression",
|
||||||
|
Parsing::Fielder => "a struct field expression",
|
||||||
|
Parsing::Call => "a call expression",
|
||||||
|
Parsing::Member => "a member access expression",
|
||||||
|
Parsing::Array => "an array",
|
||||||
|
Parsing::ArrayRep => "an array of form [k;N]",
|
||||||
|
Parsing::AddrOf => "a borrow op",
|
||||||
|
Parsing::Block => "a block",
|
||||||
|
Parsing::Group => "a grouped expression",
|
||||||
|
Parsing::Tuple => "a tuple",
|
||||||
|
Parsing::Loop => "an unconditional loop expression",
|
||||||
|
Parsing::While => "a while expression",
|
||||||
|
Parsing::If => "an if expression",
|
||||||
|
Parsing::For => "a for expression",
|
||||||
|
Parsing::Else => "an else block",
|
||||||
|
Parsing::Break => "a break expression",
|
||||||
|
Parsing::Return => "a return expression",
|
||||||
|
Parsing::Continue => "a continue expression",
|
||||||
|
}
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
98
compiler/cl-parser/src/inliner.rs
Normal file
98
compiler/cl-parser/src/inliner.rs
Normal 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)).parse() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
compiler/cl-parser/src/lib.rs
Normal file
18
compiler/cl-parser/src/lib.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//! Parses [tokens](cl_token::token) into an [AST](cl_ast)
|
||||||
|
//!
|
||||||
|
//! For the full grammar, see [grammar.ebnf][1]
|
||||||
|
//!
|
||||||
|
//! [1]: https://git.soft.fish/j/Conlang/src/branch/main/grammar.ebnf
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
|
||||||
|
pub use parser::Parser;
|
||||||
|
|
||||||
|
use cl_structures::span::*;
|
||||||
|
use cl_token::*;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
pub mod parser;
|
||||||
|
|
||||||
|
pub mod inliner;
|
||||||
1064
compiler/cl-parser/src/parser.rs
Normal file
1064
compiler/cl-parser/src/parser.rs
Normal file
File diff suppressed because it is too large
Load Diff
385
compiler/cl-parser/src/parser/prec.rs
Normal file
385
compiler/cl-parser/src/parser/prec.rs
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
//! Parses an [ExprKind] using a modified pratt parser
|
||||||
|
//!
|
||||||
|
//! See also: [Expr::parse], [ExprKind::parse]
|
||||||
|
//!
|
||||||
|
//! Implementer's note: [ExprKind::parse] is the public API for parsing [ExprKind]s.
|
||||||
|
//! Do not call it from within this function.
|
||||||
|
|
||||||
|
use super::{Parse, *};
|
||||||
|
|
||||||
|
/// Parses an [ExprKind]
|
||||||
|
pub fn exprkind(p: &mut Parser, power: u8) -> PResult<ExprKind> {
|
||||||
|
let parsing = Parsing::ExprKind;
|
||||||
|
|
||||||
|
// Prefix expressions
|
||||||
|
let mut head = match p.peek_kind(Parsing::Unary)? {
|
||||||
|
literal_like!() => Literal::parse(p)?.into(),
|
||||||
|
path_like!() => exprkind_pathlike(p)?,
|
||||||
|
TokenKind::Amp | TokenKind::AmpAmp => AddrOf::parse(p)?.into(),
|
||||||
|
TokenKind::LCurly => Block::parse(p)?.into(),
|
||||||
|
TokenKind::LBrack => exprkind_arraylike(p)?,
|
||||||
|
TokenKind::LParen => exprkind_tuplelike(p)?,
|
||||||
|
TokenKind::Let => Let::parse(p)?.into(),
|
||||||
|
TokenKind::While => ExprKind::While(While::parse(p)?),
|
||||||
|
TokenKind::If => ExprKind::If(If::parse(p)?),
|
||||||
|
TokenKind::For => ExprKind::For(For::parse(p)?),
|
||||||
|
TokenKind::Break => ExprKind::Break(Break::parse(p)?),
|
||||||
|
TokenKind::Return => ExprKind::Return(Return::parse(p)?),
|
||||||
|
TokenKind::Continue => {
|
||||||
|
p.consume_peeked();
|
||||||
|
ExprKind::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
op => {
|
||||||
|
let (kind, prec) =
|
||||||
|
from_prefix(op).ok_or_else(|| p.error(Unexpected(op), parsing))?;
|
||||||
|
let ((), after) = prec.prefix().expect("should have a precedence");
|
||||||
|
p.consume_peeked();
|
||||||
|
Unary { kind, tail: exprkind(p, after)?.into() }.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn from_postfix(op: TokenKind) -> Option<Precedence> {
|
||||||
|
Some(match op {
|
||||||
|
TokenKind::LBrack => Precedence::Index,
|
||||||
|
TokenKind::LParen => Precedence::Call,
|
||||||
|
TokenKind::Dot => Precedence::Member,
|
||||||
|
_ => None?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Ok(op) = p.peek_kind(parsing) {
|
||||||
|
// Postfix expressions
|
||||||
|
if let Some((before, ())) = from_postfix(op).and_then(Precedence::postfix) {
|
||||||
|
if before < power {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.consume_peeked();
|
||||||
|
|
||||||
|
head = match op {
|
||||||
|
TokenKind::LBrack => {
|
||||||
|
let indices =
|
||||||
|
sep(Expr::parse, TokenKind::Comma, TokenKind::RBrack, parsing)(p)?;
|
||||||
|
p.match_type(TokenKind::RBrack, parsing)?;
|
||||||
|
ExprKind::Index(Index { head: head.into(), indices })
|
||||||
|
}
|
||||||
|
TokenKind::LParen => {
|
||||||
|
let exprs =
|
||||||
|
sep(Expr::parse, TokenKind::Comma, TokenKind::RParen, parsing)(p)?;
|
||||||
|
p.match_type(TokenKind::RParen, parsing)?;
|
||||||
|
Binary {
|
||||||
|
kind: BinaryKind::Call,
|
||||||
|
parts: (head, Tuple { exprs }.into()).into(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
TokenKind::Dot => {
|
||||||
|
let kind = MemberKind::parse(p)?;
|
||||||
|
Member { head: Box::new(head), kind }.into()
|
||||||
|
}
|
||||||
|
_ => Err(p.error(Unexpected(op), parsing))?,
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// infix expressions
|
||||||
|
if let Some((kind, prec)) = from_infix(op) {
|
||||||
|
let (before, after) = prec.infix().expect("should have a precedence");
|
||||||
|
if before < power {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.consume_peeked();
|
||||||
|
|
||||||
|
let tail = exprkind(p, after)?;
|
||||||
|
head = Binary { kind, parts: (head, tail).into() }.into();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((kind, prec)) = from_modify(op) {
|
||||||
|
let (before, after) = prec.infix().expect("should have a precedence");
|
||||||
|
if before < power {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.consume_peeked();
|
||||||
|
|
||||||
|
let tail = exprkind(p, after)?;
|
||||||
|
head = Modify { kind, parts: (head, tail).into() }.into();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let TokenKind::Eq = op {
|
||||||
|
let (before, after) = Precedence::Assign
|
||||||
|
.infix()
|
||||||
|
.expect("should have a precedence");
|
||||||
|
if before < power {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.consume_peeked();
|
||||||
|
|
||||||
|
let tail = exprkind(p, after)?;
|
||||||
|
head = Assign { parts: (head, tail).into() }.into();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let TokenKind::As = op {
|
||||||
|
let before = Precedence::Cast.level();
|
||||||
|
if before < power {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.consume_peeked();
|
||||||
|
|
||||||
|
let ty = Ty::parse(p)?;
|
||||||
|
head = Cast { head: head.into(), ty }.into();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Array] = '[' ([Expr] ',')* [Expr]? ']'
|
||||||
|
///
|
||||||
|
/// Array and ArrayRef are ambiguous until the second token,
|
||||||
|
/// so they can't be independent subexpressions
|
||||||
|
fn exprkind_arraylike(p: &mut Parser) -> PResult<ExprKind> {
|
||||||
|
const P: Parsing = Parsing::Array;
|
||||||
|
const START: TokenKind = TokenKind::LBrack;
|
||||||
|
const END: TokenKind = TokenKind::RBrack;
|
||||||
|
|
||||||
|
p.match_type(START, P)?;
|
||||||
|
let out = match p.peek_kind(P)? {
|
||||||
|
END => Array { values: vec![] }.into(),
|
||||||
|
_ => exprkind_array_rep(p)?,
|
||||||
|
};
|
||||||
|
p.match_type(END, P)?;
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [ArrayRep] = `[` [Expr] `;` [Expr] `]`
|
||||||
|
fn exprkind_array_rep(p: &mut Parser) -> PResult<ExprKind> {
|
||||||
|
const P: Parsing = Parsing::Array;
|
||||||
|
const END: TokenKind = TokenKind::RBrack;
|
||||||
|
|
||||||
|
let first = Expr::parse(p)?;
|
||||||
|
Ok(match p.peek_kind(P)? {
|
||||||
|
TokenKind::Semi => ArrayRep {
|
||||||
|
value: first.kind.into(),
|
||||||
|
repeat: {
|
||||||
|
p.consume_peeked();
|
||||||
|
Box::new(exprkind(p, 0)?)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
TokenKind::RBrack => Array { values: vec![first] }.into(),
|
||||||
|
TokenKind::Comma => Array {
|
||||||
|
values: {
|
||||||
|
p.consume_peeked();
|
||||||
|
let mut out = vec![first];
|
||||||
|
out.extend(sep(Expr::parse, TokenKind::Comma, END, P)(p)?);
|
||||||
|
out
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
ty => Err(p.error(Unexpected(ty), P))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Group] = `(`([Empty](ExprKind::Empty)|[Expr]|[Tuple])`)`
|
||||||
|
///
|
||||||
|
/// [ExprKind::Empty] and [Group] are special cases of [Tuple]
|
||||||
|
fn exprkind_tuplelike(p: &mut Parser) -> PResult<ExprKind> {
|
||||||
|
p.match_type(TokenKind::LParen, Parsing::Group)?;
|
||||||
|
let out = match p.peek_kind(Parsing::Group)? {
|
||||||
|
TokenKind::RParen => Ok(ExprKind::Empty),
|
||||||
|
_ => exprkind_group(p),
|
||||||
|
};
|
||||||
|
p.match_type(TokenKind::RParen, Parsing::Group)?;
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Group] = `(`([Empty](ExprKind::Empty)|[Expr]|[Tuple])`)`
|
||||||
|
fn exprkind_group(p: &mut Parser) -> PResult<ExprKind> {
|
||||||
|
let first = Expr::parse(p)?;
|
||||||
|
match p.peek_kind(Parsing::Group)? {
|
||||||
|
TokenKind::Comma => {
|
||||||
|
let mut exprs = vec![first];
|
||||||
|
p.consume_peeked();
|
||||||
|
while TokenKind::RParen != p.peek_kind(Parsing::Tuple)? {
|
||||||
|
exprs.push(Expr::parse(p)?);
|
||||||
|
match p.peek_kind(Parsing::Tuple)? {
|
||||||
|
TokenKind::Comma => p.consume_peeked(),
|
||||||
|
_ => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(Tuple { exprs }.into())
|
||||||
|
}
|
||||||
|
_ => Ok(Group { expr: first.kind.into() }.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses an expression beginning with a [Path] (i.e. [Path] or [Structor])
|
||||||
|
fn exprkind_pathlike(p: &mut Parser) -> PResult<ExprKind> {
|
||||||
|
let head = Path::parse(p)?;
|
||||||
|
Ok(match p.match_type(TokenKind::Colon, Parsing::Path) {
|
||||||
|
Ok(_) => ExprKind::Structor(structor_body(p, head)?),
|
||||||
|
Err(_) => ExprKind::Path(head),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Structor]Body = `{` ([Fielder] `,`)* [Fielder]? `}`
|
||||||
|
fn structor_body(p: &mut Parser, to: Path) -> PResult<Structor> {
|
||||||
|
let init = delim(
|
||||||
|
sep(
|
||||||
|
Fielder::parse,
|
||||||
|
TokenKind::Comma,
|
||||||
|
CURLIES.1,
|
||||||
|
Parsing::Structor,
|
||||||
|
),
|
||||||
|
CURLIES,
|
||||||
|
Parsing::Structor,
|
||||||
|
)(p)?;
|
||||||
|
|
||||||
|
Ok(Structor { to, init })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Precedence provides a total ordering among operators
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Precedence {
|
||||||
|
Assign,
|
||||||
|
Compare,
|
||||||
|
Range,
|
||||||
|
Logic,
|
||||||
|
Bitwise,
|
||||||
|
Shift,
|
||||||
|
Factor,
|
||||||
|
Term,
|
||||||
|
Unary,
|
||||||
|
Index,
|
||||||
|
Cast,
|
||||||
|
Member, // left-associative
|
||||||
|
Call,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Precedence {
|
||||||
|
#[inline]
|
||||||
|
pub const fn level(self) -> u8 {
|
||||||
|
(self as u8) << 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prefix(self) -> Option<((), u8)> {
|
||||||
|
match self {
|
||||||
|
Self::Assign => Some(((), self.level())),
|
||||||
|
Self::Unary => Some(((), self.level())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn infix(self) -> Option<(u8, u8)> {
|
||||||
|
let level = self.level();
|
||||||
|
match self {
|
||||||
|
Self::Unary => None,
|
||||||
|
Self::Assign => Some((level + 1, level)),
|
||||||
|
_ => Some((level, level + 1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn postfix(self) -> Option<(u8, ())> {
|
||||||
|
match self {
|
||||||
|
Self::Index | Self::Call | Self::Member => Some((self.level(), ())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModifyKind> for Precedence {
|
||||||
|
fn from(_value: ModifyKind) -> Self {
|
||||||
|
Precedence::Assign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BinaryKind> for Precedence {
|
||||||
|
fn from(value: BinaryKind) -> Self {
|
||||||
|
use BinaryKind as Op;
|
||||||
|
match value {
|
||||||
|
Op::Call => Precedence::Call,
|
||||||
|
Op::Mul | Op::Div | Op::Rem => Precedence::Term,
|
||||||
|
Op::Add | Op::Sub => Precedence::Factor,
|
||||||
|
Op::Shl | Op::Shr => Precedence::Shift,
|
||||||
|
Op::BitAnd | Op::BitOr | Op::BitXor => Precedence::Bitwise,
|
||||||
|
Op::LogAnd | Op::LogOr | Op::LogXor => Precedence::Logic,
|
||||||
|
Op::RangeExc | Op::RangeInc => Precedence::Range,
|
||||||
|
Op::Lt | Op::LtEq | Op::Equal | Op::NotEq | Op::GtEq | Op::Gt => {
|
||||||
|
Precedence::Compare
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UnaryKind> for Precedence {
|
||||||
|
fn from(value: UnaryKind) -> Self {
|
||||||
|
use UnaryKind as Op;
|
||||||
|
match value {
|
||||||
|
Op::Loop => Precedence::Assign,
|
||||||
|
Op::Deref | Op::Neg | Op::Not | Op::At | Op::Tilde => Precedence::Unary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates helper functions for turning TokenKinds into AST operators
|
||||||
|
macro operator($($name:ident ($takes:ident => $returns:ident) {$($t:ident => $p:ident),*$(,)?};)*) {$(
|
||||||
|
pub fn $name (value: $takes) -> Option<($returns, Precedence)> {
|
||||||
|
match value {
|
||||||
|
$($takes::$t => Some(($returns::$p, Precedence::from($returns::$p))),)*
|
||||||
|
_ => None?,
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
|
||||||
|
operator! {
|
||||||
|
from_prefix (TokenKind => UnaryKind) {
|
||||||
|
Loop => Loop,
|
||||||
|
Star => Deref,
|
||||||
|
Minus => Neg,
|
||||||
|
Bang => Not,
|
||||||
|
At => At,
|
||||||
|
Tilde => Tilde,
|
||||||
|
};
|
||||||
|
|
||||||
|
from_modify(TokenKind => ModifyKind) {
|
||||||
|
AmpEq => And,
|
||||||
|
BarEq => Or,
|
||||||
|
XorEq => Xor,
|
||||||
|
LtLtEq => Shl,
|
||||||
|
GtGtEq => Shr,
|
||||||
|
PlusEq => Add,
|
||||||
|
MinusEq => Sub,
|
||||||
|
StarEq => Mul,
|
||||||
|
SlashEq => Div,
|
||||||
|
RemEq => Rem,
|
||||||
|
};
|
||||||
|
|
||||||
|
from_infix (TokenKind => BinaryKind) {
|
||||||
|
Lt => Lt,
|
||||||
|
LtEq => LtEq,
|
||||||
|
EqEq => Equal,
|
||||||
|
BangEq => NotEq,
|
||||||
|
GtEq => GtEq,
|
||||||
|
Gt => Gt,
|
||||||
|
DotDot => RangeExc,
|
||||||
|
DotDotEq => RangeInc,
|
||||||
|
AmpAmp => LogAnd,
|
||||||
|
BarBar => LogOr,
|
||||||
|
XorXor => LogXor,
|
||||||
|
Amp => BitAnd,
|
||||||
|
Bar => BitOr,
|
||||||
|
Xor => BitXor,
|
||||||
|
LtLt => Shl,
|
||||||
|
GtGt => Shr,
|
||||||
|
Plus => Add,
|
||||||
|
Minus => Sub,
|
||||||
|
Star => Mul,
|
||||||
|
Slash => Div,
|
||||||
|
Rem => Rem,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,5 +10,10 @@ publish.workspace = true
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
conlang = { path = "../libconlang" }
|
cl-ast = { path = "../cl-ast" }
|
||||||
crossterm = "0.27.0"
|
cl-lexer = { path = "../cl-lexer" }
|
||||||
|
cl-token = { path = "../cl-token" }
|
||||||
|
cl-parser = { path = "../cl-parser" }
|
||||||
|
cl-interpret = { path = "../cl-interpret" }
|
||||||
|
repline = { path = "../../repline" }
|
||||||
|
argwerk = "0.20.4"
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
|
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
use conlang::lexer::Lexer;
|
use cl_lexer::Lexer;
|
||||||
|
use cl_token::Token;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{stdin, IsTerminal, Read},
|
io::{stdin, IsTerminal, Read},
|
||||||
@@ -57,7 +58,7 @@ fn lex_tokens(file: &str, path: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_token(t: conlang::token::Token) {
|
fn print_token(t: Token) {
|
||||||
println!(
|
println!(
|
||||||
"{:02}:{:02}: {:#19} │{}│",
|
"{:02}:{:02}: {:#19} │{}│",
|
||||||
t.line(),
|
t.line(),
|
||||||
723
compiler/cl-repl/examples/yaml.rs
Normal file
723
compiler/cl-repl/examples/yaml.rs
Normal file
@@ -0,0 +1,723 @@
|
|||||||
|
//! Pretty prints a conlang AST in yaml
|
||||||
|
|
||||||
|
use cl_ast::Stmt;
|
||||||
|
use cl_lexer::Lexer;
|
||||||
|
use cl_parser::Parser;
|
||||||
|
use repline::{error::Error as RlError, Repline};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
|
||||||
|
loop {
|
||||||
|
let line = match rl.read() {
|
||||||
|
Err(RlError::CtrlC(_)) => break,
|
||||||
|
Err(RlError::CtrlD(line)) => {
|
||||||
|
rl.deny();
|
||||||
|
line
|
||||||
|
}
|
||||||
|
Ok(line) => line,
|
||||||
|
Err(e) => Err(e)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut parser = Parser::new(Lexer::new(&line));
|
||||||
|
let code = match parser.parse::<Stmt>() {
|
||||||
|
Ok(code) => {
|
||||||
|
rl.accept();
|
||||||
|
code
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
print!("\x1b[40G\x1bJ\x1b[91m{e}\x1b[0m");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
print!("\x1b[G\x1b[J\x1b[A");
|
||||||
|
Yamler::new().yaml(&code);
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use yamler::Yamler;
|
||||||
|
pub mod yamler {
|
||||||
|
use crate::yamlify::Yamlify;
|
||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
io::Write,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Yamler {
|
||||||
|
depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Yamler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indent(&mut self) -> Section {
|
||||||
|
Section::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a [Yamlify] value
|
||||||
|
#[inline]
|
||||||
|
pub fn yaml<T: Yamlify>(&mut self, yaml: &T) -> &mut Self {
|
||||||
|
yaml.yaml(self);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increase(&mut self) {
|
||||||
|
self.depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrease(&mut self) {
|
||||||
|
self.depth -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_indentation(&self, writer: &mut impl Write) {
|
||||||
|
for _ in 0..self.depth {
|
||||||
|
let _ = write!(writer, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a section header and increases indentation
|
||||||
|
pub fn key(&mut self, name: impl Display) -> Section {
|
||||||
|
println!();
|
||||||
|
self.print_indentation(&mut std::io::stdout().lock());
|
||||||
|
print!("- {name}:");
|
||||||
|
self.indent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a yaml key value pair: `- name: "value"`
|
||||||
|
pub fn pair<D: Display, T: Yamlify>(&mut self, name: D, value: T) -> &mut Self {
|
||||||
|
self.key(name).yaml(&value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a yaml scalar value: `"name"``
|
||||||
|
pub fn value<D: Display>(&mut self, value: D) -> &mut Self {
|
||||||
|
print!(" {value}");
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list<D: Yamlify>(&mut self, list: &[D]) -> &mut Self {
|
||||||
|
for (idx, value) in list.iter().enumerate() {
|
||||||
|
self.pair(idx, value);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks the start and end of an indented block (a "section")
|
||||||
|
pub struct Section<'y> {
|
||||||
|
yamler: &'y mut Yamler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'y> Section<'y> {
|
||||||
|
pub fn new(yamler: &'y mut Yamler) -> Self {
|
||||||
|
yamler.increase();
|
||||||
|
Self { yamler }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'y> Deref for Section<'y> {
|
||||||
|
type Target = Yamler;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.yamler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'y> DerefMut for Section<'y> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.yamler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'y> Drop for Section<'y> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let Self { yamler } = self;
|
||||||
|
yamler.decrease();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod yamlify {
|
||||||
|
use super::yamler::Yamler;
|
||||||
|
use cl_ast::*;
|
||||||
|
|
||||||
|
pub trait Yamlify {
|
||||||
|
fn yaml(&self, y: &mut Yamler);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Yamlify for File {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let File { items } = self;
|
||||||
|
y.key("File").yaml(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Visibility {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
if let Visibility::Public = self {
|
||||||
|
y.pair("vis", "pub");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Mutability {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
if let Mutability::Mut = self {
|
||||||
|
y.pair("mut", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Yamlify for Attrs {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { meta } = self;
|
||||||
|
y.key("Attrs").yaml(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Meta {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, kind } = self;
|
||||||
|
y.key("Meta").pair("name", name).yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for MetaKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
MetaKind::Plain => y,
|
||||||
|
MetaKind::Equals(value) => y.pair("equals", value),
|
||||||
|
MetaKind::Func(args) => y.pair("args", args),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Yamlify for Item {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { extents: _, attrs, vis, kind } = self;
|
||||||
|
y.key("Item").yaml(attrs).yaml(vis).yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for ItemKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
ItemKind::Alias(f) => y.yaml(f),
|
||||||
|
ItemKind::Const(f) => y.yaml(f),
|
||||||
|
ItemKind::Static(f) => y.yaml(f),
|
||||||
|
ItemKind::Module(f) => y.yaml(f),
|
||||||
|
ItemKind::Function(f) => y.yaml(f),
|
||||||
|
ItemKind::Struct(f) => y.yaml(f),
|
||||||
|
ItemKind::Enum(f) => y.yaml(f),
|
||||||
|
ItemKind::Impl(f) => y.yaml(f),
|
||||||
|
ItemKind::Use(f) => y.yaml(f),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Alias {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { to, from } = self;
|
||||||
|
y.key("Alias").pair("to", to).pair("from", from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Const {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, ty, init } = self;
|
||||||
|
y.key("Const")
|
||||||
|
.pair("name", name)
|
||||||
|
.pair("ty", ty)
|
||||||
|
.pair("init", init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Static {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { mutable, name, ty, init } = self;
|
||||||
|
y.key(name).yaml(mutable).pair("ty", ty).pair("init", init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Module {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, kind } = self;
|
||||||
|
y.key("Module").pair("name", name).yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for ModuleKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
ModuleKind::Inline(f) => y.yaml(f),
|
||||||
|
ModuleKind::Outline => y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Function {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, sign, bind, body } = self;
|
||||||
|
y.key("Function")
|
||||||
|
.pair("name", name)
|
||||||
|
.pair("sign", sign)
|
||||||
|
.pair("bind", bind)
|
||||||
|
.pair("body", body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Struct {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, kind } = self;
|
||||||
|
y.key("Struct").pair("name", name).yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for StructKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
StructKind::Empty => y,
|
||||||
|
StructKind::Tuple(k) => y.yaml(k),
|
||||||
|
StructKind::Struct(k) => y.yaml(k),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for StructMember {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { vis, name, ty } = self;
|
||||||
|
y.key("StructMember").yaml(vis).pair("name", name).yaml(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Enum {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, kind } = self;
|
||||||
|
y.key("Enum").pair("name", name).yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for EnumKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
EnumKind::NoVariants => y,
|
||||||
|
EnumKind::Variants(v) => y.yaml(v),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Variant {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, kind } = self;
|
||||||
|
y.key("Variant").pair("name", name).yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for VariantKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
VariantKind::Plain => y,
|
||||||
|
VariantKind::CLike(v) => y.yaml(v),
|
||||||
|
VariantKind::Tuple(v) => y.yaml(v),
|
||||||
|
VariantKind::Struct(v) => y.yaml(v),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Impl {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { target, body } = self;
|
||||||
|
y.key("Impl").pair("target", target).pair("body", body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for ImplKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
ImplKind::Type(t) => y.value(t),
|
||||||
|
ImplKind::Trait { impl_trait, for_type } => {
|
||||||
|
y.pair("trait", impl_trait).pair("for_type", for_type)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Use {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { absolute, tree } = self;
|
||||||
|
y.key("Use").pair("absolute", absolute).yaml(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for UseTree {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
UseTree::Tree(trees) => y.pair("trees", trees),
|
||||||
|
UseTree::Path(path, tree) => y.pair("path", path).pair("tree", tree),
|
||||||
|
UseTree::Alias(from, to) => y.pair("from", from).pair("to", to),
|
||||||
|
UseTree::Name(name) => y.pair("name", name),
|
||||||
|
UseTree::Glob => y.value("Glob"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Block {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { stmts } = self;
|
||||||
|
y.key("Block").yaml(stmts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Stmt {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { extents: _, kind, semi } = self;
|
||||||
|
y.key("Stmt").yaml(kind).yaml(semi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Semi {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
if let Semi::Terminated = self {
|
||||||
|
y.pair("terminated", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for StmtKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
StmtKind::Empty => y,
|
||||||
|
StmtKind::Item(s) => y.yaml(s),
|
||||||
|
StmtKind::Expr(s) => y.yaml(s),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Let {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { mutable, name, ty, init } = self;
|
||||||
|
y.key("Let")
|
||||||
|
.pair("name", name)
|
||||||
|
.yaml(mutable)
|
||||||
|
.pair("ty", ty)
|
||||||
|
.pair("init", init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Expr {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { extents: _, kind } = self;
|
||||||
|
y.yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for ExprKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
ExprKind::Let(k) => k.yaml(y),
|
||||||
|
ExprKind::Assign(k) => k.yaml(y),
|
||||||
|
ExprKind::Modify(k) => k.yaml(y),
|
||||||
|
ExprKind::Binary(k) => k.yaml(y),
|
||||||
|
ExprKind::Unary(k) => k.yaml(y),
|
||||||
|
ExprKind::Cast(k) => k.yaml(y),
|
||||||
|
ExprKind::Member(k) => k.yaml(y),
|
||||||
|
ExprKind::Index(k) => k.yaml(y),
|
||||||
|
ExprKind::Structor(k) => k.yaml(y),
|
||||||
|
ExprKind::Path(k) => k.yaml(y),
|
||||||
|
ExprKind::Literal(k) => k.yaml(y),
|
||||||
|
ExprKind::Array(k) => k.yaml(y),
|
||||||
|
ExprKind::ArrayRep(k) => k.yaml(y),
|
||||||
|
ExprKind::AddrOf(k) => k.yaml(y),
|
||||||
|
ExprKind::Block(k) => k.yaml(y),
|
||||||
|
ExprKind::Empty => {}
|
||||||
|
ExprKind::Group(k) => k.yaml(y),
|
||||||
|
ExprKind::Tuple(k) => k.yaml(y),
|
||||||
|
ExprKind::While(k) => k.yaml(y),
|
||||||
|
ExprKind::If(k) => k.yaml(y),
|
||||||
|
ExprKind::For(k) => k.yaml(y),
|
||||||
|
ExprKind::Break(k) => k.yaml(y),
|
||||||
|
ExprKind::Return(k) => k.yaml(y),
|
||||||
|
ExprKind::Continue => {
|
||||||
|
y.key("Continue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Assign {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { parts } = self;
|
||||||
|
y.key("Assign")
|
||||||
|
.pair("head", &parts.0)
|
||||||
|
.pair("tail", &parts.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Modify {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { kind, parts } = self;
|
||||||
|
y.key("Modify")
|
||||||
|
.pair("kind", kind)
|
||||||
|
.pair("head", &parts.0)
|
||||||
|
.pair("tail", &parts.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for ModifyKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
y.value(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Binary {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { kind, parts } = self;
|
||||||
|
y.key("Binary")
|
||||||
|
.pair("kind", kind)
|
||||||
|
.pair("head", &parts.0)
|
||||||
|
.pair("tail", &parts.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for BinaryKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
y.value(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Unary {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { kind, tail } = self;
|
||||||
|
y.key("Unary").pair("kind", kind).pair("tail", tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for UnaryKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
y.value(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Cast {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { head, ty } = self;
|
||||||
|
y.key("Cast").pair("head", head).pair("ty", ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Member {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { head, kind } = self;
|
||||||
|
y.key("Member").pair("head", head).pair("kind", kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for MemberKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
MemberKind::Call(id, args) => y.pair("id", id).pair("args", args),
|
||||||
|
MemberKind::Struct(id) => y.pair("id", id),
|
||||||
|
MemberKind::Tuple(id) => y.pair("id", id),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Tuple {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { exprs } = self;
|
||||||
|
y.key("Tuple").list(exprs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Index {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { head, indices } = self;
|
||||||
|
y.key("Index").pair("head", head).list(indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Structor {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { to, init } = self;
|
||||||
|
y.key("Structor").pair("to", to).list(init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Fielder {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { name, init } = self;
|
||||||
|
y.key("Fielder").pair("name", name).pair("init", init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Array {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { values } = self;
|
||||||
|
y.key("Array").list(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for ArrayRep {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { value, repeat } = self;
|
||||||
|
y.key("ArrayRep")
|
||||||
|
.pair("value", value)
|
||||||
|
.pair("repeat", repeat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for AddrOf {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { count, mutable, expr } = self;
|
||||||
|
y.key("AddrOf")
|
||||||
|
.pair("count", count)
|
||||||
|
.yaml(mutable)
|
||||||
|
.pair("expr", expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Group {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { expr } = self;
|
||||||
|
y.key("Group").yaml(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for While {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { cond, pass, fail } = self;
|
||||||
|
y.key("While")
|
||||||
|
.pair("cond", cond)
|
||||||
|
.pair("pass", pass)
|
||||||
|
.yaml(fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Else {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { body } = self;
|
||||||
|
y.key("Else").yaml(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for If {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { cond, pass, fail } = self;
|
||||||
|
y.key("If").pair("cond", cond).pair("pass", pass).yaml(fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for For {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { bind, cond, pass, fail } = self;
|
||||||
|
y.key("For")
|
||||||
|
.pair("bind", bind)
|
||||||
|
.pair("cond", cond)
|
||||||
|
.pair("pass", pass)
|
||||||
|
.yaml(fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Break {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { body } = self;
|
||||||
|
y.key("Break").yaml(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Return {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { body } = self;
|
||||||
|
y.key("Return").yaml(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Literal {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
y.value(format_args!("\"{self}\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Sym {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
y.value(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Param {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { mutability, name } = self;
|
||||||
|
y.key("Param").yaml(mutability).pair("name", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Ty {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { extents: _, kind } = self;
|
||||||
|
y.key("Ty").yaml(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for TyKind {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
TyKind::Never => y.value("Never"),
|
||||||
|
TyKind::Empty => y.value("Empty"),
|
||||||
|
TyKind::Path(t) => y.yaml(t),
|
||||||
|
TyKind::Tuple(t) => y.yaml(t),
|
||||||
|
TyKind::Ref(t) => y.yaml(t),
|
||||||
|
TyKind::Fn(t) => y.yaml(t),
|
||||||
|
TyKind::Slice(_) => todo!(),
|
||||||
|
TyKind::Array(_) => todo!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for Path {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { absolute, parts } = self;
|
||||||
|
let mut y = y.key("Path");
|
||||||
|
if *absolute {
|
||||||
|
y.pair("absolute", absolute);
|
||||||
|
}
|
||||||
|
for part in parts {
|
||||||
|
y.pair("part", part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for PathPart {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
match self {
|
||||||
|
PathPart::SuperKw => y.value("super"),
|
||||||
|
PathPart::SelfKw => y.value("self"),
|
||||||
|
PathPart::SelfTy => y.value("Self"),
|
||||||
|
PathPart::Ident(i) => y.yaml(i),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for TyArray {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { ty, count } = self;
|
||||||
|
y.key("TyArray").pair("ty", ty).pair("count", count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for TySlice {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { ty } = self;
|
||||||
|
y.key("TyArray").pair("ty", ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for TyTuple {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { types } = self;
|
||||||
|
let mut y = y.key("TyTuple");
|
||||||
|
for ty in types {
|
||||||
|
y.yaml(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for TyRef {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { count, mutable, to } = self;
|
||||||
|
y.key("TyRef")
|
||||||
|
.pair("count", count)
|
||||||
|
.yaml(mutable)
|
||||||
|
.pair("to", to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for TyFn {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
let Self { args, rety } = self;
|
||||||
|
y.key("TyFn").pair("args", args).pair("rety", rety);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Yamlify> Yamlify for Option<T> {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
if let Some(v) = self {
|
||||||
|
y.yaml(v);
|
||||||
|
} else {
|
||||||
|
y.value("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Yamlify> Yamlify for Box<T> {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
y.yaml(&**self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Yamlify> Yamlify for Vec<T> {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
for thing in self {
|
||||||
|
y.yaml(thing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Yamlify for () {
|
||||||
|
fn yaml(&self, _y: &mut Yamler) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Yamlify> Yamlify for &T {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
(*self).yaml(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! scalar {
|
||||||
|
($($t:ty),*$(,)?) => {
|
||||||
|
$(impl Yamlify for $t {
|
||||||
|
fn yaml(&self, y: &mut Yamler) {
|
||||||
|
y.value(self);
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar! {
|
||||||
|
bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, &str, String
|
||||||
|
}
|
||||||
|
}
|
||||||
14
compiler/cl-repl/src/ansi.rs
Normal file
14
compiler/cl-repl/src/ansi.rs
Normal 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";
|
||||||
69
compiler/cl-repl/src/args.rs
Normal file
69
compiler/cl-repl/src/args.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//! Handles argument parsing (currently using the [argwerk] crate)
|
||||||
|
|
||||||
|
use std::{io::IsTerminal, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
argwerk::define! {
|
||||||
|
///
|
||||||
|
///The Conlang prototype debug interface
|
||||||
|
#[usage = "conlang [<file>] [-I <include...>] [-m <mode>] [-r <repl>]"]
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Args {
|
||||||
|
pub file: Option<PathBuf>,
|
||||||
|
pub include: Vec<PathBuf>,
|
||||||
|
pub mode: Mode,
|
||||||
|
pub repl: bool = is_terminal(),
|
||||||
|
}
|
||||||
|
|
||||||
|
///files to include
|
||||||
|
["-I" | "--include", path] => {
|
||||||
|
include.push(path.into());
|
||||||
|
}
|
||||||
|
///the CLI operating mode (`f`mt | `l`ex | `r`un)
|
||||||
|
["-m" | "--mode", flr] => {
|
||||||
|
mode = flr.parse()?;
|
||||||
|
}
|
||||||
|
///whether to start the repl (`true` or `false`)
|
||||||
|
["-r" | "--repl", bool] => {
|
||||||
|
repl = bool.parse()?;
|
||||||
|
}
|
||||||
|
///display usage information
|
||||||
|
["-h" | "--help"] => {
|
||||||
|
println!("{}", Args::help());
|
||||||
|
if true { std::process::exit(0); }
|
||||||
|
}
|
||||||
|
///the main source file
|
||||||
|
[#[option] path] if file.is_none() => {
|
||||||
|
file = path.map(Into::into);
|
||||||
|
}
|
||||||
|
|
||||||
|
[path] if file.is_some() => {
|
||||||
|
include.push(path.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets whether stdin AND stdout are a terminal, for pipelining
|
||||||
|
pub fn is_terminal() -> bool {
|
||||||
|
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The CLI's operating mode
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Mode {
|
||||||
|
#[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\"")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
5
compiler/cl-repl/src/bin/conlang.rs
Normal file
5
compiler/cl-repl/src/bin/conlang.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
use cl_repl::{args, cli::run};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
run(args::Args::args()?)
|
||||||
|
}
|
||||||
101
compiler/cl-repl/src/cli.rs
Normal file
101
compiler/cl-repl/src/cli.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
//! Implement's the command line interface
|
||||||
|
use crate::{
|
||||||
|
args::{Args, Mode},
|
||||||
|
ctx::Context,
|
||||||
|
menu,
|
||||||
|
tools::print_token,
|
||||||
|
};
|
||||||
|
use cl_ast::File;
|
||||||
|
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)).parse()?;
|
||||||
|
let code = match inliner.inline(code) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err((code, io_errs, parse_errs)) => {
|
||||||
|
for (file, err) in io_errs {
|
||||||
|
eprintln!("{}:{err}", file.display());
|
||||||
|
}
|
||||||
|
for (file, err) in parse_errs {
|
||||||
|
eprintln!("{}:{err}", file.display());
|
||||||
|
}
|
||||||
|
code
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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)).parse::<File>()?;
|
||||||
|
println!("{code}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_code(code: &str, env: &mut Environment) -> Result<(), Box<dyn Error>> {
|
||||||
|
let code = Parser::new(Lexer::new(code)).parse::<File>()?;
|
||||||
|
match code.interpret(env)? {
|
||||||
|
ConValue::Empty => {}
|
||||||
|
ret => println!("{ret}"),
|
||||||
|
}
|
||||||
|
if env.get("main".into()).is_ok() {
|
||||||
|
match env.call("main".into(), &[])? {
|
||||||
|
ConValue::Empty => {}
|
||||||
|
ret => println!("{ret}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
26
compiler/cl-repl/src/ctx.rs
Normal file
26
compiler/cl-repl/src/ctx.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
11
compiler/cl-repl/src/lib.rs
Normal file
11
compiler/cl-repl/src/lib.rs
Normal 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;
|
||||||
82
compiler/cl-repl/src/menu.rs
Normal file
82
compiler/cl-repl/src/menu.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use crate::{ansi, ctx};
|
||||||
|
use cl_ast::Stmt;
|
||||||
|
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)).parse::<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.parse::<Stmt>() {
|
||||||
|
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
|
||||||
|
Err(e) => Err(e)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Response::Accept)
|
||||||
|
})
|
||||||
|
}
|
||||||
11
compiler/cl-repl/src/tools.rs
Normal file
11
compiler/cl-repl/src/tools.rs
Normal 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
11
compiler/cl-structures/Cargo.toml
Normal file
11
compiler/cl-structures/Cargo.toml
Normal 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" }
|
||||||
217
compiler/cl-structures/src/index_map.rs
Normal file
217
compiler/cl-structures/src/index_map.rs
Normal 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> {}
|
||||||
|
}
|
||||||
308
compiler/cl-structures/src/intern.rs
Normal file
308
compiler/cl-structures/src/intern.rs
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
//! 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for StringInterner<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Ok(keys) = self.keys.read() else {
|
||||||
|
return write!(f, "Could not lock StringInterner key map.");
|
||||||
|
};
|
||||||
|
let mut keys: Vec<_> = keys.iter().collect();
|
||||||
|
keys.sort();
|
||||||
|
|
||||||
|
writeln!(f, "Keys:")?;
|
||||||
|
for (idx, key) in keys.iter().enumerate() {
|
||||||
|
writeln!(f, "{idx}:\t\"{key}\"")?
|
||||||
|
}
|
||||||
|
writeln!(f, "Count: {}", keys.len())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// # Safety:
|
||||||
|
// This is fine because StringInterner::get_or_insert(v) holds a RwLock
|
||||||
|
// for its entire duration, and doesn't touch the non-(Send+Sync) arena
|
||||||
|
// unless the lock is held by a write guard.
|
||||||
|
unsafe impl<'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> {}
|
||||||
|
}
|
||||||
24
compiler/cl-structures/src/lib.rs
Normal file
24
compiler/cl-structures/src/lib.rs
Normal 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;
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
//! # Universally useful structures
|
|
||||||
//! - [struct@Span]: Stores the start and end [struct@Loc] of a notable AST node
|
//! - [struct@Span]: Stores the start and end [struct@Loc] of a notable AST node
|
||||||
//! - [struct@Loc]: Stores the line/column of a notable AST node
|
//! - [struct@Loc]: Stores the line/column of a notable AST node
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use crate::lexer::Lexer;
|
|
||||||
|
|
||||||
/// Stores the start and end [locations](struct@Loc) within the token stream
|
/// Stores the start and end [locations](struct@Loc) within the token stream
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
@@ -10,24 +8,33 @@ pub struct Span {
|
|||||||
pub head: Loc,
|
pub head: Loc,
|
||||||
pub tail: Loc,
|
pub tail: Loc,
|
||||||
}
|
}
|
||||||
pub fn Span(head: Loc, tail: Loc) -> Span {
|
pub const fn Span(head: Loc, tail: Loc) -> Span {
|
||||||
Span { head, tail }
|
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
|
/// Stores a read-only (line, column) location in a token stream
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Loc {
|
pub struct Loc {
|
||||||
line: u32,
|
line: u32,
|
||||||
col: u32,
|
col: u32,
|
||||||
}
|
}
|
||||||
pub fn Loc(line: u32, col: u32) -> Loc {
|
pub const fn Loc(line: u32, col: u32) -> Loc {
|
||||||
Loc { line, col }
|
Loc { line, col }
|
||||||
}
|
}
|
||||||
impl Loc {
|
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
|
self.line
|
||||||
}
|
}
|
||||||
pub fn col(self) -> u32 {
|
pub const fn col(self) -> u32 {
|
||||||
self.col
|
self.col
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,9 +45,3 @@ impl std::fmt::Display for Loc {
|
|||||||
write!(f, "{line}:{col}:")
|
write!(f, "{line}:{col}:")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> From<&Lexer<'t>> for Loc {
|
|
||||||
fn from(value: &Lexer<'t>) -> Self {
|
|
||||||
Loc(value.line(), value.col())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
748
compiler/cl-structures/src/stack.rs
Normal file
748
compiler/cl-structures/src/stack.rs
Normal file
@@ -0,0 +1,748 @@
|
|||||||
|
//! A contiguous collection with constant capacity.
|
||||||
|
//!
|
||||||
|
//! Since the capacity of a [Stack] may be [*known at compile time*](Sized),
|
||||||
|
//! it may live on the call stack.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! Unlike a [Vec], the [Stack] doesn't grow when it reaches capacity.
|
||||||
|
//! ```should_panic
|
||||||
|
//! # use cl_structures::stack::*;
|
||||||
|
//! let mut v = stack![1];
|
||||||
|
//! v.push("This should work");
|
||||||
|
//! v.push("This will panic!");
|
||||||
|
//! ```
|
||||||
|
//! To get around this limitation, the methods [try_push](Stack::try_push) and
|
||||||
|
//! [try_insert](Stack::try_insert) are provided:
|
||||||
|
//! ```
|
||||||
|
//! # use cl_structures::stack::*;
|
||||||
|
//! let mut v = stack![1];
|
||||||
|
//! v.push("This should work");
|
||||||
|
//! v.try_push("This should produce an err").unwrap_err();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! As the name suggests, a [Stack] enforces a stack discipline:
|
||||||
|
//! ```
|
||||||
|
//! # use cl_structures::stack::*;
|
||||||
|
//! let mut v = stack![100];
|
||||||
|
//!
|
||||||
|
//! assert_eq!(100, v.capacity());
|
||||||
|
//! assert_eq!(0, v.len());
|
||||||
|
//!
|
||||||
|
//! // Elements are pushed one at a time onto the stack
|
||||||
|
//! v.push("foo");
|
||||||
|
//! v.push("bar");
|
||||||
|
//! assert_eq!(2, v.len());
|
||||||
|
//!
|
||||||
|
//! // The stack can be used anywhere a slice is expected
|
||||||
|
//! assert_eq!(Some(&"foo"), v.get(0));
|
||||||
|
//! assert_eq!(Some(&"bar"), v.last());
|
||||||
|
//!
|
||||||
|
//! // Elements are popped from the stack in reverse order
|
||||||
|
//! assert_eq!(Some("bar"), v.pop());
|
||||||
|
//! assert_eq!(Some("foo"), v.pop());
|
||||||
|
//! assert_eq!(None, v.pop());
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
// yar har! here there be unsafe code! Tread carefully.
|
||||||
|
|
||||||
|
use core::slice;
|
||||||
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
marker::PhantomData,
|
||||||
|
mem::{ManuallyDrop, MaybeUninit},
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a [`stack`] containing the arguments
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Creates a *full* [`Stack`] containing a list of elements
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::stack;
|
||||||
|
/// let mut v = stack![1, 2, 3];
|
||||||
|
///
|
||||||
|
/// assert_eq!(Some(3), v.pop());
|
||||||
|
/// assert_eq!(Some(2), v.pop());
|
||||||
|
/// assert_eq!(Some(1), v.pop());
|
||||||
|
/// assert_eq!(None, v.pop());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Creates a *full* [`Stack`] from a given element and size
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::stack;
|
||||||
|
/// let mut v = stack![1; 2];
|
||||||
|
///
|
||||||
|
/// assert_eq!(Some(1), v.pop());
|
||||||
|
/// assert_eq!(Some(1), v.pop());
|
||||||
|
/// assert_eq!(None, v.pop());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Creates an *empty* [`Stack`] from a given size
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::{Stack, stack};
|
||||||
|
/// let mut v = stack![10];
|
||||||
|
///
|
||||||
|
/// assert_eq!(0, v.len());
|
||||||
|
/// assert_eq!(10, v.capacity());
|
||||||
|
///
|
||||||
|
/// v.push(10);
|
||||||
|
/// assert_eq!(Some(&10), v.last());
|
||||||
|
/// ```
|
||||||
|
pub macro stack {
|
||||||
|
($count:literal) => {
|
||||||
|
Stack::<_, $count>::new()
|
||||||
|
},
|
||||||
|
($value:expr ; $count:literal) => {{
|
||||||
|
let mut stack: Stack<_, $count> = Stack::new();
|
||||||
|
for _ in 0..$count {
|
||||||
|
stack.push($value)
|
||||||
|
}
|
||||||
|
stack
|
||||||
|
}},
|
||||||
|
($($values:expr),* $(,)?) => {
|
||||||
|
Stack::from([$($values),*])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A contiguous collection with constant capacity
|
||||||
|
pub struct Stack<T, const N: usize> {
|
||||||
|
_data: PhantomData<T>,
|
||||||
|
buf: [MaybeUninit<T>; N],
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone, const N: usize> Clone for Stack<T, N> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let mut new = Self::new();
|
||||||
|
for value in self.iter() {
|
||||||
|
new.push(value.clone())
|
||||||
|
}
|
||||||
|
new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug, const N: usize> Debug for Stack<T, N> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_list().entries(self.iter()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Default for Stack<T, N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Deref for Stack<T, N> {
|
||||||
|
type Target = [T];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
// Safety:
|
||||||
|
// - We have ensured all elements from 0 to len have been initialized
|
||||||
|
// - self.elem[0] came from a reference, and so is aligned to T
|
||||||
|
// unsafe { &*(&self.buf[0..self.len] as *const [_] as *const [T]) }
|
||||||
|
unsafe { slice::from_raw_parts(self.buf.as_ptr().cast(), self.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> DerefMut for Stack<T, N> {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
// Safety:
|
||||||
|
// - See Deref
|
||||||
|
unsafe { slice::from_raw_parts_mut(self.buf.as_mut_ptr().cast(), self.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires dropck-eyepatch for elements with contravariant lifetimes
|
||||||
|
unsafe impl<#[may_dangle] T, const N: usize> Drop for Stack<T, N> {
|
||||||
|
#[inline]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Safety: We have ensured that all elements in the list are
|
||||||
|
if std::mem::needs_drop::<T>() {
|
||||||
|
unsafe { core::ptr::drop_in_place(self.as_mut_slice()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Extend<T> for Stack<T, N> {
|
||||||
|
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
|
||||||
|
for value in iter {
|
||||||
|
self.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> From<[T; N]> for Stack<T, N> {
|
||||||
|
fn from(value: [T; N]) -> Self {
|
||||||
|
let value = ManuallyDrop::new(value);
|
||||||
|
if std::mem::size_of::<[T; N]>() == 0 {
|
||||||
|
// Safety: since [T; N] is zero-sized, and there are no other fields,
|
||||||
|
// it should be okay to interpret N as Self
|
||||||
|
unsafe { ptr::read(&N as *const _ as *const _) }
|
||||||
|
} else {
|
||||||
|
// Safety:
|
||||||
|
// - `value` is ManuallyDrop, so its destructor won't run
|
||||||
|
// - All elements are assumed to be initialized (so len is N)
|
||||||
|
Self {
|
||||||
|
buf: unsafe { ptr::read(&value as *const _ as *const _) },
|
||||||
|
len: N,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Stack<T, N> {
|
||||||
|
/// Constructs a new, empty [Stack]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let mut v: Stack<_, 3> = Stack::new();
|
||||||
|
///
|
||||||
|
/// v.try_push(1).unwrap();
|
||||||
|
/// v.try_push(2).unwrap();
|
||||||
|
/// v.try_push(3).unwrap();
|
||||||
|
/// // Trying to push a 4th element will fail, and return the failed element
|
||||||
|
/// assert_eq!(4, v.try_push(4).unwrap_err());
|
||||||
|
///
|
||||||
|
/// assert_eq!(Some(3), v.pop());
|
||||||
|
/// ```
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { buf: [const { MaybeUninit::uninit() }; N], len: 0, _data: PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new [Stack] from an array of [`MaybeUninit<T>`] and an initialized length
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// - Elements from `0..len` must be initialized
|
||||||
|
/// - len must not exceed the length of the array
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// # use core::mem::MaybeUninit;
|
||||||
|
/// let mut v = unsafe { Stack::from_raw_parts([MaybeUninit::new(100)], 1) };
|
||||||
|
///
|
||||||
|
/// assert_eq!(1, v.len());
|
||||||
|
/// assert_eq!(1, v.capacity());
|
||||||
|
/// assert_eq!(Some(100), v.pop());
|
||||||
|
/// assert_eq!(None, v.pop());
|
||||||
|
/// ```
|
||||||
|
pub const unsafe fn from_raw_parts(buf: [MaybeUninit<T>; N], len: usize) -> Self {
|
||||||
|
Self { buf, len, _data: PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a [Stack] into an array of [`MaybeUninit<T>`] and the initialized length
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let mut v: Stack<_, 10> = Stack::new();
|
||||||
|
/// v.push(0);
|
||||||
|
/// v.push(1);
|
||||||
|
///
|
||||||
|
/// let (buf, len) = v.into_raw_parts();
|
||||||
|
///
|
||||||
|
/// assert_eq!(0, unsafe { buf[0].assume_init() });
|
||||||
|
/// assert_eq!(1, unsafe { buf[1].assume_init() });
|
||||||
|
/// assert_eq!(2, len);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn into_raw_parts(self) -> ([MaybeUninit<T>; N], usize) {
|
||||||
|
let this = ManuallyDrop::new(self);
|
||||||
|
// Safety: since
|
||||||
|
(unsafe { ptr::read(&this.buf) }, this.len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a raw pointer to the stack's buffer
|
||||||
|
pub const fn as_ptr(&self) -> *const T {
|
||||||
|
self.buf.as_ptr().cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an unsafe mutable pointer to the stack's buffer
|
||||||
|
pub fn as_mut_ptr(&mut self) -> *mut T {
|
||||||
|
self.buf.as_mut_ptr().cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a slice containing the entire vector
|
||||||
|
pub fn as_slice(&self) -> &[T] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a mutable slice containing the entire vector
|
||||||
|
pub fn as_mut_slice(&mut self) -> &mut [T] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total number of elements the stack can hold
|
||||||
|
pub const fn capacity(&self) -> usize {
|
||||||
|
N
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves an existing stack into an allocation of a (potentially) different size,
|
||||||
|
/// truncating if necessary.
|
||||||
|
///
|
||||||
|
/// This can be used to easily construct a half-empty stack
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// You can grow a stack to fit more elements
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let v = Stack::from([0, 1, 2, 3, 4]);
|
||||||
|
/// assert_eq!(5, v.capacity());
|
||||||
|
///
|
||||||
|
/// let mut v = v.resize::<10>();
|
||||||
|
/// assert_eq!(10, v.capacity());
|
||||||
|
///
|
||||||
|
/// v.push(5);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can truncate a stack, dropping elements off the end
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let v = Stack::from([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||||
|
/// assert_eq!(8, v.capacity());
|
||||||
|
///
|
||||||
|
/// let v = v.resize::<5>();
|
||||||
|
/// assert_eq!(5, v.capacity());
|
||||||
|
/// ```
|
||||||
|
pub fn resize<const M: usize>(mut self) -> Stack<T, M> {
|
||||||
|
// Drop elements until new length is reached
|
||||||
|
while self.len > M {
|
||||||
|
drop(self.pop());
|
||||||
|
}
|
||||||
|
let (old, len) = self.into_raw_parts();
|
||||||
|
let mut new: Stack<T, M> = Stack::new();
|
||||||
|
|
||||||
|
// Safety:
|
||||||
|
// - new and old are separate allocations
|
||||||
|
// - len <= M
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(old.as_ptr(), new.buf.as_mut_ptr(), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
new.len = len;
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new element onto the end of the stack
|
||||||
|
///
|
||||||
|
/// # May Panic
|
||||||
|
///
|
||||||
|
/// Panics if the new length exceeds capacity
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let mut v: Stack<_, 4> = Stack::new();
|
||||||
|
///
|
||||||
|
/// v.push(0);
|
||||||
|
/// v.push(1);
|
||||||
|
/// v.push(2);
|
||||||
|
/// v.push(3);
|
||||||
|
/// assert_eq!(&[0, 1, 2, 3], v.as_slice());
|
||||||
|
/// ```
|
||||||
|
pub fn push(&mut self, value: T) {
|
||||||
|
if self.len >= N {
|
||||||
|
panic!("Attempted to push into full stack")
|
||||||
|
}
|
||||||
|
// Safety: len is confirmed to be less than capacity
|
||||||
|
unsafe { self.push_unchecked(value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new element onto the end of the stack
|
||||||
|
///
|
||||||
|
/// Returns [`Err(value)`](Result::Err) if the new length would exceed capacity
|
||||||
|
pub fn try_push(&mut self, value: T) -> Result<(), T> {
|
||||||
|
if self.len >= N {
|
||||||
|
return Err(value);
|
||||||
|
}
|
||||||
|
// Safety: len is confirmed to be less than capacity
|
||||||
|
unsafe { self.push_unchecked(value) };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new element onto the end of the stack, without checking capacity
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// len after push must not exceed capacity N
|
||||||
|
#[inline]
|
||||||
|
unsafe fn push_unchecked(&mut self, value: T) {
|
||||||
|
unsafe { ptr::write(self.as_mut_ptr().add(self.len), value) }
|
||||||
|
self.len += 1; // post inc
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pops the last element off the end of the stack, and returns it
|
||||||
|
///
|
||||||
|
/// Returns None if the stack is empty
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let mut v = Stack::from([0, 1, 2, 3]);
|
||||||
|
///
|
||||||
|
/// assert_eq!(Some(3), v.pop());
|
||||||
|
/// assert_eq!(Some(2), v.pop());
|
||||||
|
/// assert_eq!(Some(1), v.pop());
|
||||||
|
/// assert_eq!(Some(0), v.pop());
|
||||||
|
/// assert_eq!(None, v.pop());
|
||||||
|
/// ```
|
||||||
|
pub fn pop(&mut self) -> Option<T> {
|
||||||
|
if self.len == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.len -= 1;
|
||||||
|
// Safety: MaybeUninit<T> implies ManuallyDrop<T>,
|
||||||
|
// therefore should not get dropped twice
|
||||||
|
Some(unsafe { ptr::read(self.as_ptr().add(self.len).cast()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and returns the element at the given index,
|
||||||
|
/// shifting other elements toward the start
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
|
||||||
|
///
|
||||||
|
/// assert_eq!(2, v.remove(2));
|
||||||
|
/// assert_eq!(&[0, 1, 3, 4], v.as_slice());
|
||||||
|
/// ```
|
||||||
|
pub fn remove(&mut self, index: usize) -> T {
|
||||||
|
if index >= self.len {
|
||||||
|
panic!("Index {index} exceeded length {}", self.len)
|
||||||
|
}
|
||||||
|
let len = self.len - 1;
|
||||||
|
let base = self.as_mut_ptr();
|
||||||
|
let out = unsafe { ptr::read(base.add(index)) };
|
||||||
|
|
||||||
|
unsafe { ptr::copy(base.add(index + 1), base.add(index), len - index) };
|
||||||
|
self.len = len;
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and returns the element at the given index,
|
||||||
|
/// swapping it with the last element.
|
||||||
|
///
|
||||||
|
/// # May Panic
|
||||||
|
///
|
||||||
|
/// Panics if `index >= len`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
|
||||||
|
///
|
||||||
|
/// assert_eq!(2, v.swap_remove(2));
|
||||||
|
///
|
||||||
|
/// assert_eq!(&[0, 1, 4, 3], v.as_slice());
|
||||||
|
/// ```
|
||||||
|
pub fn swap_remove(&mut self, index: usize) -> T {
|
||||||
|
if index >= self.len {
|
||||||
|
panic!("Index {index} exceeds length {}", self.len);
|
||||||
|
}
|
||||||
|
let len = self.len - 1;
|
||||||
|
let ptr = self.as_mut_ptr();
|
||||||
|
let out = unsafe { ptr::read(ptr.add(index)) };
|
||||||
|
|
||||||
|
unsafe { ptr::copy(ptr.add(len), ptr.add(index), 1) };
|
||||||
|
self.len = len;
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts an element at position `index` in the stack,
|
||||||
|
/// shifting all elements after it to the right.
|
||||||
|
///
|
||||||
|
/// # May Panic
|
||||||
|
///
|
||||||
|
/// Panics if `index > len` or [`self.is_full()`](Stack::is_full)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
/// let mut v = Stack::from([0, 1, 2, 3, 4]).resize::<6>();
|
||||||
|
///
|
||||||
|
/// v.insert(3, 0xbeef);
|
||||||
|
/// assert_eq!(&[0, 1, 2, 0xbeef, 3, 4], v.as_slice());
|
||||||
|
/// ```
|
||||||
|
pub fn insert(&mut self, index: usize, data: T) {
|
||||||
|
if index > self.len {
|
||||||
|
panic!("Index {index} exceeded length {}", self.len)
|
||||||
|
}
|
||||||
|
if self.is_full() {
|
||||||
|
panic!("Attempted to insert into full stack")
|
||||||
|
}
|
||||||
|
unsafe { self.insert_unchecked(index, data) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to insert an element at position `index` in the stack,
|
||||||
|
/// shifting all elements after it to the right.
|
||||||
|
///
|
||||||
|
/// If the stack is at capacity, returns the original element and an [InsertFailed] error.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use cl_structures::stack::Stack;
|
||||||
|
/// let mut v: Stack<_, 2> = Stack::new();
|
||||||
|
///
|
||||||
|
/// assert_eq!(Ok(()), v.try_insert(0, 0));
|
||||||
|
/// ```
|
||||||
|
pub fn try_insert(&mut self, index: usize, data: T) -> Result<(), (T, InsertFailed<N>)> {
|
||||||
|
if index > self.len {
|
||||||
|
return Err((data, InsertFailed::Bounds(index)));
|
||||||
|
}
|
||||||
|
if self.is_full() {
|
||||||
|
return Err((data, InsertFailed::Full));
|
||||||
|
}
|
||||||
|
// Safety: index < self.len && !self.is_full()
|
||||||
|
unsafe { self.insert_unchecked(index, data) };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety:
|
||||||
|
/// - index must be less than self.len
|
||||||
|
/// - length after insertion must be <= N
|
||||||
|
#[inline]
|
||||||
|
unsafe fn insert_unchecked(&mut self, index: usize, data: T) {
|
||||||
|
let base = self.as_mut_ptr();
|
||||||
|
|
||||||
|
unsafe { ptr::copy(base.add(index), base.add(index + 1), self.len - index) }
|
||||||
|
|
||||||
|
self.len += 1;
|
||||||
|
self.buf[index] = MaybeUninit::new(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the stack
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::Stack;
|
||||||
|
///
|
||||||
|
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
|
||||||
|
/// assert_eq!(v.as_slice(), &[0, 1, 2, 3, 4]);
|
||||||
|
///
|
||||||
|
/// v.clear();
|
||||||
|
/// assert_eq!(v.as_slice(), &[]);
|
||||||
|
/// ```
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
// Hopefully copy elision takes care of this lmao
|
||||||
|
drop(std::mem::take(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of elements in the stack
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::*;
|
||||||
|
/// let v = Stack::from([0, 1, 2, 3, 4]);
|
||||||
|
///
|
||||||
|
/// assert_eq!(5, v.len());
|
||||||
|
/// ```
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the stack is at (or over) capacity
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::*;
|
||||||
|
/// let v = Stack::from([(); 10]);
|
||||||
|
///
|
||||||
|
/// assert!(v.is_full());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_full(&self) -> bool {
|
||||||
|
self.len >= N
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the stack contains no elements
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use cl_structures::stack::*;
|
||||||
|
/// let v: Stack<(), 10> = Stack::new();
|
||||||
|
///
|
||||||
|
/// assert!(v.is_empty());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum InsertFailed<const N: usize> {
|
||||||
|
Bounds(usize),
|
||||||
|
Full,
|
||||||
|
}
|
||||||
|
impl<const N: usize> std::error::Error for InsertFailed<N> {}
|
||||||
|
impl<const N: usize> std::fmt::Display for InsertFailed<N> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
InsertFailed::Bounds(idx) => write!(f, "Index {idx} exceeded length {N}"),
|
||||||
|
InsertFailed::Full => {
|
||||||
|
write!(f, "Attempt to insert into full stack (length {N})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn zero_sized() {
|
||||||
|
let v: Stack<(), { usize::MAX }> = Stack::new();
|
||||||
|
assert_eq!(usize::MAX, v.capacity());
|
||||||
|
assert_eq!(std::mem::size_of::<usize>(), std::mem::size_of_val(&v))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn from_usize_max_zst_array() {
|
||||||
|
let mut v = Stack::from([(); usize::MAX]);
|
||||||
|
assert_eq!(v.len(), usize::MAX);
|
||||||
|
v.pop();
|
||||||
|
assert_eq!(v.len(), usize::MAX - 1);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
let v: Stack<(), 255> = Stack::new();
|
||||||
|
assert_eq!(0, v.len());
|
||||||
|
assert_eq!(255, v.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn push() {
|
||||||
|
let mut v: Stack<_, 64> = Stack::new();
|
||||||
|
v.push(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic = "Attempted to push into full stack"]
|
||||||
|
fn push_overflow() {
|
||||||
|
let mut v = Stack::from([]);
|
||||||
|
v.push(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pop() {
|
||||||
|
let mut v = Stack::from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
assert_eq!(Some(9), v.pop());
|
||||||
|
assert_eq!(Some(8), v.pop());
|
||||||
|
assert_eq!(Some(7), v.pop());
|
||||||
|
assert_eq!(Some(6), v.pop());
|
||||||
|
assert_eq!(Some(5), v.pop());
|
||||||
|
assert_eq!(Some(4), v.pop());
|
||||||
|
assert_eq!(Some(3), v.pop());
|
||||||
|
assert_eq!(Some(2), v.pop());
|
||||||
|
assert_eq!(Some(1), v.pop());
|
||||||
|
assert_eq!(None, v.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resize_smaller() {
|
||||||
|
let v = Stack::from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
let mut v = v.resize::<2>();
|
||||||
|
|
||||||
|
assert_eq!(2, v.capacity());
|
||||||
|
|
||||||
|
assert_eq!(Some(2), v.pop());
|
||||||
|
assert_eq!(Some(1), v.pop());
|
||||||
|
assert_eq!(None, v.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resize_bigger() {
|
||||||
|
let v = Stack::from([1, 2, 3, 4]);
|
||||||
|
let mut v: Stack<_, 10> = v.resize();
|
||||||
|
|
||||||
|
assert_eq!(Some(4), v.pop());
|
||||||
|
assert_eq!(Some(3), v.pop());
|
||||||
|
assert_eq!(Some(2), v.pop());
|
||||||
|
assert_eq!(Some(1), v.pop());
|
||||||
|
assert_eq!(None, v.pop());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn dangle() {
|
||||||
|
let mut v: Stack<_, 2> = Stack::new();
|
||||||
|
let a = 0;
|
||||||
|
let b = 1;
|
||||||
|
v.push(&a);
|
||||||
|
v.push(&b);
|
||||||
|
println!("{v:?}");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn remove() {
|
||||||
|
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
assert_eq!(3, v.remove(3));
|
||||||
|
assert_eq!(4, v.remove(3));
|
||||||
|
assert_eq!(5, v.remove(3));
|
||||||
|
assert_eq!(Some(2), v.pop());
|
||||||
|
assert_eq!(Some(1), v.pop());
|
||||||
|
assert_eq!(Some(0), v.pop());
|
||||||
|
assert_eq!(None, v.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap_remove() {
|
||||||
|
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
|
||||||
|
assert_eq!(3, v.swap_remove(3));
|
||||||
|
assert_eq!(&[0, 1, 2, 5, 4], v.as_slice());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn swap_remove_last() {
|
||||||
|
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
|
||||||
|
assert_eq!(5, v.swap_remove(5));
|
||||||
|
assert_eq!(&[0, 1, 2, 3, 4], v.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert() {
|
||||||
|
let mut v = Stack::from([0, 1, 2, 4, 5, 0x41414141]);
|
||||||
|
v.pop();
|
||||||
|
v.insert(3, 3);
|
||||||
|
|
||||||
|
assert_eq!(&[0, 1, 2, 3, 4, 5], v.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic = "Attempted to insert into full stack"]
|
||||||
|
fn insert_overflow() {
|
||||||
|
let mut v = Stack::from([0]);
|
||||||
|
v.insert(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop() {
|
||||||
|
let v = Stack::from([
|
||||||
|
Box::new(0),
|
||||||
|
Box::new(1),
|
||||||
|
Box::new(2),
|
||||||
|
Box::new(3),
|
||||||
|
Box::new(4),
|
||||||
|
]);
|
||||||
|
std::mem::drop(std::hint::black_box(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
221
compiler/cl-structures/src/tree.rs
Normal file
221
compiler/cl-structures/src/tree.rs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
//! An insert-only unordered tree, backed by a [Vec]
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//! ```
|
||||||
|
//! use cl_structures::tree::{Tree, Node};
|
||||||
|
//! // A tree can be created
|
||||||
|
//! let mut tree = Tree::new();
|
||||||
|
//! // Provided with a root node
|
||||||
|
//! let root = tree.root("This is the root node").unwrap();
|
||||||
|
//!
|
||||||
|
//! // Nodes can be accessed by indexing
|
||||||
|
//! assert_eq!(*tree[root].as_ref(), "This is the root node");
|
||||||
|
//! // Nodes' data can be accessed directly by calling `get`/`get_mut`
|
||||||
|
//! assert_eq!(tree.get(root).unwrap(), &"This is the root node")
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
// TODO: implement an Entry-style API for doing traversal algorithms
|
||||||
|
|
||||||
|
pub use self::tree_ref::Ref;
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
pub mod tree_ref;
|
||||||
|
|
||||||
|
/// An insert-only unordered tree, backed by a [Vec]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Tree<T> {
|
||||||
|
nodes: Vec<Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Tree<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { nodes: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getters
|
||||||
|
impl<T> Tree<T> {
|
||||||
|
pub fn get(&self, index: Ref<T>) -> Option<&T> {
|
||||||
|
self.get_node(index).map(|node| &node.value)
|
||||||
|
}
|
||||||
|
pub fn get_mut(&mut self, index: Ref<T>) -> Option<&mut T> {
|
||||||
|
self.get_node_mut(index).map(|node| &mut node.value)
|
||||||
|
}
|
||||||
|
pub fn get_node(&self, index: Ref<T>) -> Option<&Node<T>> {
|
||||||
|
self.nodes.get(usize::from(index))
|
||||||
|
}
|
||||||
|
pub fn get_node_mut(&mut self, index: Ref<T>) -> Option<&mut Node<T>> {
|
||||||
|
self.nodes.get_mut(usize::from(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tree operations
|
||||||
|
impl<T> Tree<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { nodes: Default::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new root for the tree.
|
||||||
|
///
|
||||||
|
/// If the tree already has a root, the value will be returned.
|
||||||
|
pub fn root(&mut self, value: T) -> Result<Ref<T>, T> {
|
||||||
|
if self.is_empty() {
|
||||||
|
// Create an index for the new node
|
||||||
|
let node = Ref::new_unchecked(self.nodes.len());
|
||||||
|
// add child to tree
|
||||||
|
self.nodes.push(Node::from(value));
|
||||||
|
Ok(node)
|
||||||
|
} else {
|
||||||
|
Err(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_root(&mut self) -> Option<Ref<T>> {
|
||||||
|
match self.nodes.is_empty() {
|
||||||
|
true => None,
|
||||||
|
false => Some(Ref::new_unchecked(0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a value into the tree as a child of the parent node
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// May panic if the node [Ref] is from a different tree
|
||||||
|
pub fn insert(&mut self, value: T, parent: Ref<T>) -> Ref<T> {
|
||||||
|
let child = Ref::new_unchecked(self.nodes.len());
|
||||||
|
// add child to tree before parent
|
||||||
|
self.nodes.push(Node::with_parent(value, parent));
|
||||||
|
// add child to parent
|
||||||
|
self[parent].children.push(child);
|
||||||
|
|
||||||
|
child
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the depth of a node
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// May panic if the node [Ref] is from a different tree
|
||||||
|
pub fn depth(&self, node: Ref<T>) -> usize {
|
||||||
|
match self[node].parent {
|
||||||
|
Some(node) => self.depth(node) + 1,
|
||||||
|
None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the number of branches in the tree
|
||||||
|
pub fn branches(&self) -> usize {
|
||||||
|
self.nodes.iter().fold(0, |edges, node| edges + node.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standard data structure functions
|
||||||
|
impl<T> Tree<T> {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.nodes.len()
|
||||||
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.nodes.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Index<Ref<T>> for Tree<T> {
|
||||||
|
type Output = Node<T>;
|
||||||
|
fn index(&self, index: Ref<T>) -> &Self::Output {
|
||||||
|
self.get_node(index).expect("Ref should be inside Tree")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> IndexMut<Ref<T>> for Tree<T> {
|
||||||
|
fn index_mut(&mut self, index: Ref<T>) -> &mut Self::Output {
|
||||||
|
self.get_node_mut(index).expect("Ref should be inside Tree")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A node in a [Tree]
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Node<T> {
|
||||||
|
value: T,
|
||||||
|
/// The parent
|
||||||
|
parent: Option<Ref<T>>,
|
||||||
|
/// The children
|
||||||
|
children: Vec<Ref<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Node<T> {
|
||||||
|
pub const fn new(value: T) -> Self {
|
||||||
|
Self { value, parent: None, children: vec![] }
|
||||||
|
}
|
||||||
|
pub const fn with_parent(value: T, parent: Ref<T>) -> Self {
|
||||||
|
Self { value, parent: Some(parent), children: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> &T {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
|
self.as_mut()
|
||||||
|
}
|
||||||
|
pub fn swap(&mut self, value: T) -> T {
|
||||||
|
std::mem::replace(&mut self.value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent(&self) -> Option<Ref<T>> {
|
||||||
|
self.parent
|
||||||
|
}
|
||||||
|
pub fn children(&self) -> &[Ref<T>] {
|
||||||
|
&self.children
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.children.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.children.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsRef<T> for Node<T> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> AsMut<T> for Node<T> {
|
||||||
|
fn as_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for Node<T> {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[allow(unused)]
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_children() {
|
||||||
|
let mut tree = Tree::new();
|
||||||
|
let root = tree.root(0).unwrap();
|
||||||
|
let one = tree.insert(1, root);
|
||||||
|
let two = tree.insert(2, root);
|
||||||
|
assert_eq!([one, two].as_slice(), tree[root].children());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn nest_children() {
|
||||||
|
let mut tree = Tree::new();
|
||||||
|
let root = tree.root(0).unwrap();
|
||||||
|
let one = tree.insert(1, root);
|
||||||
|
let two = tree.insert(2, one);
|
||||||
|
assert_eq!(&[one], tree[root].children());
|
||||||
|
assert_eq!(&[two], tree[one].children());
|
||||||
|
assert_eq!(tree[two].children(), &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compares_equal() {}
|
||||||
|
}
|
||||||
70
compiler/cl-structures/src/tree/tree_ref.rs
Normal file
70
compiler/cl-structures/src/tree/tree_ref.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//! An element in a [Tree](super::Tree)
|
||||||
|
///
|
||||||
|
/// Contains a niche, and as such, [`Option<TreeRef<T>>`] is free :D
|
||||||
|
use std::{marker::PhantomData, num::NonZeroUsize};
|
||||||
|
|
||||||
|
/// An element of in a [Tree](super::Tree).
|
||||||
|
//? The index of the node is stored as a [NonZeroUsize] for space savings
|
||||||
|
//? Making Refs T-specific helps the user keep track of which Refs belong to which trees.
|
||||||
|
//? This isn't bulletproof, of course, but it'll keep Ref<Foo> from being used on Tree<Bar>
|
||||||
|
pub struct Ref<T: ?Sized>(NonZeroUsize, PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T: ?Sized> Ref<T> {
|
||||||
|
/// Constructs a new [Ref] with the given index
|
||||||
|
pub fn new_unchecked(index: usize) -> Self {
|
||||||
|
// Safety: index cannot be zero because we use saturating addition on unsigned type.
|
||||||
|
Self(
|
||||||
|
unsafe { NonZeroUsize::new_unchecked(index.saturating_add(1)) },
|
||||||
|
PhantomData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> From<Ref<T>> for usize {
|
||||||
|
fn from(value: Ref<T>) -> Self {
|
||||||
|
usize::from(value.0) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- implementations of derivable traits, because we don't need bounds here --- */
|
||||||
|
|
||||||
|
impl<T: ?Sized> std::fmt::Debug for Ref<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("TreeRef").field(&self.0).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> std::hash::Hash for Ref<T> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.hash(state);
|
||||||
|
self.1.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> PartialEq for Ref<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0 == other.0 && self.1 == other.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Eq for Ref<T> {}
|
||||||
|
|
||||||
|
impl<T: ?Sized> PartialOrd for Ref<T> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Ord for Ref<T> {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.0.cmp(&other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Clone for Ref<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Copy for Ref<T> {}
|
||||||
10
compiler/cl-token/Cargo.toml
Normal file
10
compiler/cl-token/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-token"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
13
compiler/cl-token/src/lib.rs
Normal file
13
compiler/cl-token/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//! # Token
|
||||||
|
//!
|
||||||
|
//! Stores a component of a file as a [TokenKind], some [TokenData], and a line and column number
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
|
||||||
|
pub mod token;
|
||||||
|
pub mod token_data;
|
||||||
|
pub mod token_type;
|
||||||
|
|
||||||
|
pub use token::Token;
|
||||||
|
pub use token_data::TokenData;
|
||||||
|
pub use token_type::TokenKind;
|
||||||
42
compiler/cl-token/src/token.rs
Normal file
42
compiler/cl-token/src/token.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//! A [Token] contains a single unit of lexical information, and an optional bit of [TokenData]
|
||||||
|
use super::{TokenData, TokenKind};
|
||||||
|
|
||||||
|
/// Contains a single unit of lexical information,
|
||||||
|
/// and an optional bit of [TokenData]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Token {
|
||||||
|
pub ty: TokenKind,
|
||||||
|
pub data: TokenData,
|
||||||
|
pub line: u32,
|
||||||
|
pub col: u32,
|
||||||
|
}
|
||||||
|
impl Token {
|
||||||
|
/// Creates a new [Token] out of a [TokenKind], [TokenData], line, and column.
|
||||||
|
pub fn new(ty: TokenKind, data: impl Into<TokenData>, line: u32, col: u32) -> Self {
|
||||||
|
Self { ty, data: data.into(), line, col }
|
||||||
|
}
|
||||||
|
/// Casts this token to a new [TokenKind]
|
||||||
|
pub fn cast(self, ty: TokenKind) -> Self {
|
||||||
|
Self { ty, ..self }
|
||||||
|
}
|
||||||
|
/// Returns the [TokenKind] of this token
|
||||||
|
pub fn ty(&self) -> TokenKind {
|
||||||
|
self.ty
|
||||||
|
}
|
||||||
|
/// Returns a reference to this token's [TokenData]
|
||||||
|
pub fn data(&self) -> &TokenData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
/// Converts this token into its inner [TokenData]
|
||||||
|
pub fn into_data(self) -> TokenData {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
/// Returns the line where this token originated
|
||||||
|
pub fn line(&self) -> u32 {
|
||||||
|
self.line
|
||||||
|
}
|
||||||
|
/// Returns the column where this token originated
|
||||||
|
pub fn col(&self) -> u32 {
|
||||||
|
self.col
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
//! Additional data stored within a [Token](super::Token),
|
//! Additional data stored within a [Token](super::Token),
|
||||||
//! external to its [Type](super::token_type::Type)
|
//! external to its [TokenKind](super::token_type::TokenKind)
|
||||||
/// Additional data stored within a [Token](super::Token),
|
/// Additional data stored within a [Token](super::Token),
|
||||||
/// external to its [Type](super::token_type::Type)
|
/// external to its [TokenKind](super::token_type::TokenKind)
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Data {
|
pub enum TokenData {
|
||||||
/// [Token](super::Token) contains an [identifier](str)
|
|
||||||
Identifier(Box<str>),
|
|
||||||
/// [Token](super::Token) contains a [String]
|
/// [Token](super::Token) contains a [String]
|
||||||
String(String),
|
String(String),
|
||||||
/// [Token](super::Token) contains a [character](char)
|
/// [Token](super::Token) contains a [character](char)
|
||||||
@@ -18,7 +16,6 @@ pub enum Data {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
from! {
|
from! {
|
||||||
value: &str => Self::Identifier(value.into()),
|
|
||||||
value: String => Self::String(value),
|
value: String => Self::String(value),
|
||||||
value: u128 => Self::Integer(value),
|
value: u128 => Self::Integer(value),
|
||||||
value: f64 => Self::Float(value),
|
value: f64 => Self::Float(value),
|
||||||
@@ -27,19 +24,18 @@ from! {
|
|||||||
}
|
}
|
||||||
/// Implements [From] for an enum
|
/// Implements [From] for an enum
|
||||||
macro from($($value:ident: $src:ty => $dst:expr),*$(,)?) {
|
macro from($($value:ident: $src:ty => $dst:expr),*$(,)?) {
|
||||||
$(impl From<$src> for Data {
|
$(impl From<$src> for TokenData {
|
||||||
fn from($value: $src) -> Self { $dst }
|
fn from($value: $src) -> Self { $dst }
|
||||||
})*
|
})*
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for Data {
|
impl std::fmt::Display for TokenData {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Data::Identifier(v) => v.fmt(f),
|
TokenData::String(v) => write!(f, "\"{v}\""),
|
||||||
Data::String(v) => write!(f, "\"{v}\""),
|
TokenData::Character(v) => write!(f, "'{v}'"),
|
||||||
Data::Character(v) => write!(f, "'{v}'"),
|
TokenData::Integer(v) => v.fmt(f),
|
||||||
Data::Integer(v) => v.fmt(f),
|
TokenData::Float(v) => v.fmt(f),
|
||||||
Data::Float(v) => v.fmt(f),
|
TokenData::None => "None".fmt(f),
|
||||||
Data::None => "None".fmt(f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
232
compiler/cl-token/src/token_type.rs
Normal file
232
compiler/cl-token/src/token_type.rs
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
//! Stores a [Token's](super::Token) lexical information
|
||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
/// Stores a [Token's](super::Token) lexical information
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum TokenKind {
|
||||||
|
/// Invalid sequence
|
||||||
|
Invalid,
|
||||||
|
/// Any kind of comment
|
||||||
|
Comment,
|
||||||
|
/// Any tokenizable literal (See [TokenData](super::TokenData))
|
||||||
|
Literal,
|
||||||
|
/// A non-keyword identifier
|
||||||
|
Identifier,
|
||||||
|
// A keyword
|
||||||
|
As, // as
|
||||||
|
Break, // "break"
|
||||||
|
Cl, // "cl"
|
||||||
|
Const, // "const"
|
||||||
|
Continue, // "continue"
|
||||||
|
Else, // "else"
|
||||||
|
Enum, // "enum"
|
||||||
|
False, // "false"
|
||||||
|
Fn, // "fn"
|
||||||
|
For, // "for"
|
||||||
|
If, // "if"
|
||||||
|
Impl, // "impl"
|
||||||
|
In, // "in"
|
||||||
|
Let, // "let"
|
||||||
|
Loop, // "loop"
|
||||||
|
Mod, // "mod"
|
||||||
|
Mut, // "mut"
|
||||||
|
Pub, // "pub"
|
||||||
|
Return, // "return"
|
||||||
|
SelfKw, // "self"
|
||||||
|
SelfTy, // "Self"
|
||||||
|
Static, // "static"
|
||||||
|
Struct, // "struct"
|
||||||
|
Super, // "super"
|
||||||
|
True, // "true"
|
||||||
|
Type, // "type"
|
||||||
|
Use, // "use"
|
||||||
|
While, // "while"
|
||||||
|
// Delimiter or punctuation
|
||||||
|
LCurly, // {
|
||||||
|
RCurly, // }
|
||||||
|
LBrack, // [
|
||||||
|
RBrack, // ]
|
||||||
|
LParen, // (
|
||||||
|
RParen, // )
|
||||||
|
Amp, // &
|
||||||
|
AmpAmp, // &&
|
||||||
|
AmpEq, // &=
|
||||||
|
Arrow, // ->
|
||||||
|
At, // @
|
||||||
|
Backslash, // \
|
||||||
|
Bang, // !
|
||||||
|
BangBang, // !!
|
||||||
|
BangEq, // !=
|
||||||
|
Bar, // |
|
||||||
|
BarBar, // ||
|
||||||
|
BarEq, // |=
|
||||||
|
Colon, // :
|
||||||
|
ColonColon, // ::
|
||||||
|
Comma, // ,
|
||||||
|
Dot, // .
|
||||||
|
DotDot, // ..
|
||||||
|
DotDotEq, // ..=
|
||||||
|
Eq, // =
|
||||||
|
EqEq, // ==
|
||||||
|
FatArrow, // =>
|
||||||
|
Grave, // `
|
||||||
|
Gt, // >
|
||||||
|
GtEq, // >=
|
||||||
|
GtGt, // >>
|
||||||
|
GtGtEq, // >>=
|
||||||
|
Hash, // #
|
||||||
|
HashBang, // #!
|
||||||
|
Lt, // <
|
||||||
|
LtEq, // <=
|
||||||
|
LtLt, // <<
|
||||||
|
LtLtEq, // <<=
|
||||||
|
Minus, // -
|
||||||
|
MinusEq, // -=
|
||||||
|
Plus, // +
|
||||||
|
PlusEq, // +=
|
||||||
|
Question, // ?
|
||||||
|
Rem, // %
|
||||||
|
RemEq, // %=
|
||||||
|
Semi, // ;
|
||||||
|
Slash, // /
|
||||||
|
SlashEq, // /=
|
||||||
|
Star, // *
|
||||||
|
StarEq, // *=
|
||||||
|
Tilde, // ~
|
||||||
|
Xor, // ^
|
||||||
|
XorEq, // ^=
|
||||||
|
XorXor, // ^^
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TokenKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TokenKind::Invalid => "invalid".fmt(f),
|
||||||
|
TokenKind::Comment => "comment".fmt(f),
|
||||||
|
TokenKind::Literal => "literal".fmt(f),
|
||||||
|
TokenKind::Identifier => "identifier".fmt(f),
|
||||||
|
|
||||||
|
TokenKind::As => "as".fmt(f),
|
||||||
|
TokenKind::Break => "break".fmt(f),
|
||||||
|
TokenKind::Cl => "cl".fmt(f),
|
||||||
|
TokenKind::Const => "const".fmt(f),
|
||||||
|
TokenKind::Continue => "continue".fmt(f),
|
||||||
|
TokenKind::Else => "else".fmt(f),
|
||||||
|
TokenKind::Enum => "enum".fmt(f),
|
||||||
|
TokenKind::False => "false".fmt(f),
|
||||||
|
TokenKind::Fn => "fn".fmt(f),
|
||||||
|
TokenKind::For => "for".fmt(f),
|
||||||
|
TokenKind::If => "if".fmt(f),
|
||||||
|
TokenKind::Impl => "impl".fmt(f),
|
||||||
|
TokenKind::In => "in".fmt(f),
|
||||||
|
TokenKind::Let => "let".fmt(f),
|
||||||
|
TokenKind::Loop => "loop".fmt(f),
|
||||||
|
TokenKind::Mod => "mod".fmt(f),
|
||||||
|
TokenKind::Mut => "mut".fmt(f),
|
||||||
|
TokenKind::Pub => "pub".fmt(f),
|
||||||
|
TokenKind::Return => "return".fmt(f),
|
||||||
|
TokenKind::SelfKw => "self".fmt(f),
|
||||||
|
TokenKind::SelfTy => "Self".fmt(f),
|
||||||
|
TokenKind::Static => "static".fmt(f),
|
||||||
|
TokenKind::Struct => "struct".fmt(f),
|
||||||
|
TokenKind::Super => "super".fmt(f),
|
||||||
|
TokenKind::True => "true".fmt(f),
|
||||||
|
TokenKind::Type => "type".fmt(f),
|
||||||
|
TokenKind::Use => "use".fmt(f),
|
||||||
|
TokenKind::While => "while".fmt(f),
|
||||||
|
|
||||||
|
TokenKind::LCurly => "{".fmt(f),
|
||||||
|
TokenKind::RCurly => "}".fmt(f),
|
||||||
|
TokenKind::LBrack => "[".fmt(f),
|
||||||
|
TokenKind::RBrack => "]".fmt(f),
|
||||||
|
TokenKind::LParen => "(".fmt(f),
|
||||||
|
TokenKind::RParen => ")".fmt(f),
|
||||||
|
TokenKind::Amp => "&".fmt(f),
|
||||||
|
TokenKind::AmpAmp => "&&".fmt(f),
|
||||||
|
TokenKind::AmpEq => "&=".fmt(f),
|
||||||
|
TokenKind::Arrow => "->".fmt(f),
|
||||||
|
TokenKind::At => "@".fmt(f),
|
||||||
|
TokenKind::Backslash => "\\".fmt(f),
|
||||||
|
TokenKind::Bang => "!".fmt(f),
|
||||||
|
TokenKind::BangBang => "!!".fmt(f),
|
||||||
|
TokenKind::BangEq => "!=".fmt(f),
|
||||||
|
TokenKind::Bar => "|".fmt(f),
|
||||||
|
TokenKind::BarBar => "||".fmt(f),
|
||||||
|
TokenKind::BarEq => "|=".fmt(f),
|
||||||
|
TokenKind::Colon => ":".fmt(f),
|
||||||
|
TokenKind::ColonColon => "::".fmt(f),
|
||||||
|
TokenKind::Comma => ",".fmt(f),
|
||||||
|
TokenKind::Dot => ".".fmt(f),
|
||||||
|
TokenKind::DotDot => "..".fmt(f),
|
||||||
|
TokenKind::DotDotEq => "..=".fmt(f),
|
||||||
|
TokenKind::Eq => "=".fmt(f),
|
||||||
|
TokenKind::EqEq => "==".fmt(f),
|
||||||
|
TokenKind::FatArrow => "=>".fmt(f),
|
||||||
|
TokenKind::Grave => "`".fmt(f),
|
||||||
|
TokenKind::Gt => ">".fmt(f),
|
||||||
|
TokenKind::GtEq => ">=".fmt(f),
|
||||||
|
TokenKind::GtGt => ">>".fmt(f),
|
||||||
|
TokenKind::GtGtEq => ">>=".fmt(f),
|
||||||
|
TokenKind::Hash => "#".fmt(f),
|
||||||
|
TokenKind::HashBang => "#!".fmt(f),
|
||||||
|
TokenKind::Lt => "<".fmt(f),
|
||||||
|
TokenKind::LtEq => "<=".fmt(f),
|
||||||
|
TokenKind::LtLt => "<<".fmt(f),
|
||||||
|
TokenKind::LtLtEq => "<<=".fmt(f),
|
||||||
|
TokenKind::Minus => "-".fmt(f),
|
||||||
|
TokenKind::MinusEq => "-=".fmt(f),
|
||||||
|
TokenKind::Plus => "+".fmt(f),
|
||||||
|
TokenKind::PlusEq => "+=".fmt(f),
|
||||||
|
TokenKind::Question => "?".fmt(f),
|
||||||
|
TokenKind::Rem => "%".fmt(f),
|
||||||
|
TokenKind::RemEq => "%=".fmt(f),
|
||||||
|
TokenKind::Semi => ";".fmt(f),
|
||||||
|
TokenKind::Slash => "/".fmt(f),
|
||||||
|
TokenKind::SlashEq => "/=".fmt(f),
|
||||||
|
TokenKind::Star => "*".fmt(f),
|
||||||
|
TokenKind::StarEq => "*=".fmt(f),
|
||||||
|
TokenKind::Tilde => "~".fmt(f),
|
||||||
|
TokenKind::Xor => "^".fmt(f),
|
||||||
|
TokenKind::XorEq => "^=".fmt(f),
|
||||||
|
TokenKind::XorXor => "^^".fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromStr for TokenKind {
|
||||||
|
/// [FromStr] can only fail when an identifier isn't a keyword
|
||||||
|
type Err = ();
|
||||||
|
/// Parses a string s to return a Keyword
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"as" => Self::As,
|
||||||
|
"break" => Self::Break,
|
||||||
|
"cl" => Self::Cl,
|
||||||
|
"const" => Self::Const,
|
||||||
|
"continue" => Self::Continue,
|
||||||
|
"else" => Self::Else,
|
||||||
|
"enum" => Self::Enum,
|
||||||
|
"false" => Self::False,
|
||||||
|
"fn" => Self::Fn,
|
||||||
|
"for" => Self::For,
|
||||||
|
"if" => Self::If,
|
||||||
|
"impl" => Self::Impl,
|
||||||
|
"in" => Self::In,
|
||||||
|
"let" => Self::Let,
|
||||||
|
"loop" => Self::Loop,
|
||||||
|
"mod" => Self::Mod,
|
||||||
|
"mut" => Self::Mut,
|
||||||
|
"pub" => Self::Pub,
|
||||||
|
"return" => Self::Return,
|
||||||
|
"self" => Self::SelfKw,
|
||||||
|
"Self" => Self::SelfTy,
|
||||||
|
"static" => Self::Static,
|
||||||
|
"struct" => Self::Struct,
|
||||||
|
"super" => Self::Super,
|
||||||
|
"true" => Self::True,
|
||||||
|
"type" => Self::Type,
|
||||||
|
"use" => Self::Use,
|
||||||
|
"while" => Self::While,
|
||||||
|
_ => Err(())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
17
compiler/cl-typeck/Cargo.toml
Normal file
17
compiler/cl-typeck/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-typeck"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-ast = { path = "../cl-ast" }
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
repline = { path = "../../repline" }
|
||||||
|
cl-lexer = { path = "../cl-lexer" }
|
||||||
|
cl-parser = { path = "../cl-parser" }
|
||||||
339
compiler/cl-typeck/examples/typeck.rs
Normal file
339
compiler/cl-typeck/examples/typeck.rs
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
use cl_structures::intern::string_interner::StringInterner;
|
||||||
|
use cl_typeck::{entry::Entry, stage::*, table::Table, type_expression::TypeExpression};
|
||||||
|
|
||||||
|
use cl_ast::{
|
||||||
|
ast_visitor::{Fold, Visit},
|
||||||
|
desugar::*,
|
||||||
|
Stmt, Ty,
|
||||||
|
};
|
||||||
|
use cl_lexer::Lexer;
|
||||||
|
use cl_parser::{inliner::ModuleInliner, Parser};
|
||||||
|
use repline::{error::Error as RlError, prebaked::*};
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
path::{self, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Path to display in standard library errors
|
||||||
|
const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
|
||||||
|
// Statically included standard library
|
||||||
|
const PREAMBLE: &str = r"
|
||||||
|
pub mod std;
|
||||||
|
pub use std::preamble::*;
|
||||||
|
";
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const C_MAIN: &str = C_LISTING;
|
||||||
|
const C_RESV: &str = "\x1b[35m";
|
||||||
|
const C_CODE: &str = "\x1b[36m";
|
||||||
|
const C_BYID: &str = "\x1b[95m";
|
||||||
|
const C_ERROR: &str = "\x1b[31m";
|
||||||
|
const C_LISTING: &str = "\x1b[38;5;117m";
|
||||||
|
|
||||||
|
/// A home for immutable intermediate ASTs
|
||||||
|
///
|
||||||
|
/// TODO: remove this.
|
||||||
|
static mut TREES: TreeManager = TreeManager::new();
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut prj = Table::default();
|
||||||
|
|
||||||
|
let mut parser = Parser::new(Lexer::new(PREAMBLE));
|
||||||
|
let code = match parser.parse() {
|
||||||
|
Ok(code) => code,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{STDLIB_DISPLAY_PATH}:{e}");
|
||||||
|
Err(e)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// This code is special - it gets loaded from a hard-coded project directory (for now)
|
||||||
|
let code = inline_modules(code, concat!(env!("CARGO_MANIFEST_DIR"), "/../../stdlib"));
|
||||||
|
Populator::new(&mut prj).visit_file(unsafe { TREES.push(code) });
|
||||||
|
|
||||||
|
main_menu(&mut prj)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main_menu(prj: &mut Table) -> Result<(), RlError> {
|
||||||
|
banner();
|
||||||
|
read_and(C_MAIN, "mu>", "? >", |line| {
|
||||||
|
match line.trim() {
|
||||||
|
"c" | "code" => enter_code(prj)?,
|
||||||
|
"clear" => clear()?,
|
||||||
|
"d" | "desugar" => live_desugar()?,
|
||||||
|
"e" | "exit" => return Ok(Response::Break),
|
||||||
|
"f" | "file" => import_files(prj)?,
|
||||||
|
"i" | "id" => get_by_id(prj)?,
|
||||||
|
"l" | "list" => list_types(prj),
|
||||||
|
"q" | "query" => query_type_expression(prj)?,
|
||||||
|
"r" | "resolve" => resolve_all(prj)?,
|
||||||
|
"s" | "strings" => print_strings(),
|
||||||
|
"h" | "help" | "" => {
|
||||||
|
println!(
|
||||||
|
"Valid commands are:
|
||||||
|
clear : Clear the screen
|
||||||
|
code (c): Enter code to type-check
|
||||||
|
desugar (d): WIP: Test the experimental desugaring passes
|
||||||
|
file (f): Load files from disk
|
||||||
|
id (i): Get a type by its type ID
|
||||||
|
list (l): List all known types
|
||||||
|
query (q): Query the type system
|
||||||
|
resolve (r): Perform type resolution
|
||||||
|
help (h): Print this list
|
||||||
|
exit (e): Exit the program"
|
||||||
|
);
|
||||||
|
return Ok(Response::Deny);
|
||||||
|
}
|
||||||
|
_ => Err(r#"Invalid command. Type "help" to see the list of valid commands."#)?,
|
||||||
|
}
|
||||||
|
Ok(Response::Accept)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_code(prj: &mut Table) -> Result<(), RlError> {
|
||||||
|
read_and(C_CODE, "cl>", "? >", |line| {
|
||||||
|
if line.trim().is_empty() {
|
||||||
|
return Ok(Response::Break);
|
||||||
|
}
|
||||||
|
let code = Parser::new(Lexer::new(line)).parse()?;
|
||||||
|
let code = inline_modules(code, "");
|
||||||
|
let code = WhileElseDesugar.fold_file(code);
|
||||||
|
// Safety: this is totally unsafe
|
||||||
|
Populator::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)).parse::<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 print_strings() {
|
||||||
|
println!("{}", StringInterner::global());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_type_expression(prj: &mut Table) -> Result<(), RlError> {
|
||||||
|
read_and(C_RESV, "ty>", "? >", |line| {
|
||||||
|
if line.trim().is_empty() {
|
||||||
|
return Ok(Response::Break);
|
||||||
|
}
|
||||||
|
// parse it as a path, and convert the path into a borrowed path
|
||||||
|
let ty: Ty = Parser::new(Lexer::new(line)).parse()?;
|
||||||
|
let id = ty.evaluate(prj, prj.root())?;
|
||||||
|
pretty_handle(id.to_entry(prj))?;
|
||||||
|
Ok(Response::Accept)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_by_id(prj: &mut Table) -> Result<(), RlError> {
|
||||||
|
use cl_parser::parser::Parse;
|
||||||
|
use cl_structures::index_map::MapIndex;
|
||||||
|
use cl_typeck::handle::Handle;
|
||||||
|
read_and(C_BYID, "id>", "? >", |line| {
|
||||||
|
if line.trim().is_empty() {
|
||||||
|
return Ok(Response::Break);
|
||||||
|
}
|
||||||
|
let mut parser = Parser::new(Lexer::new(line));
|
||||||
|
let def_id = match Parse::parse(&mut parser)? {
|
||||||
|
cl_ast::Literal::Int(int) => int as _,
|
||||||
|
other => Err(format!("Expected integer, got {other}"))?,
|
||||||
|
};
|
||||||
|
let mut path = parser.parse::<cl_ast::Path>().unwrap_or_default();
|
||||||
|
path.absolute = false;
|
||||||
|
|
||||||
|
let handle = Handle::from_usize(def_id).to_entry(prj);
|
||||||
|
|
||||||
|
print!(" > {{{C_LISTING}{handle}\x1b[0m}}");
|
||||||
|
if !path.parts.is_empty() {
|
||||||
|
print!("::{path}")
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let Some(entry) = handle.nav(&path.parts) else {
|
||||||
|
Err("No results.")?
|
||||||
|
};
|
||||||
|
|
||||||
|
pretty_handle(entry)?;
|
||||||
|
|
||||||
|
Ok(Response::Accept)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_all(table: &mut Table) -> Result<(), Box<dyn Error>> {
|
||||||
|
for (id, error) in import(table) {
|
||||||
|
eprintln!("{error} in {} ({id})", id.to_entry(table))
|
||||||
|
}
|
||||||
|
for handle in table.handle_iter() {
|
||||||
|
if let Err(error) = handle.to_entry_mut(table).categorize() {
|
||||||
|
eprintln!("{error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for handle in implement(table) {
|
||||||
|
eprintln!("Unable to reparent {} ({handle})", handle.to_entry(table))
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("...Resolved!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_types(table: &mut Table) {
|
||||||
|
for handle in table.debug_entry_iter() {
|
||||||
|
let id = handle.id();
|
||||||
|
let kind = handle.kind().unwrap();
|
||||||
|
let name = handle.name().unwrap_or("".into());
|
||||||
|
println!("{id:3}: {name:16}| {kind}: {handle}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_files(table: &mut Table) -> Result<(), RlError> {
|
||||||
|
read_and(C_RESV, "fi>", "? >", |line| {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() {
|
||||||
|
return Ok(Response::Break);
|
||||||
|
}
|
||||||
|
let Ok(file) = std::fs::read_to_string(line) else {
|
||||||
|
for file in std::fs::read_dir(line)? {
|
||||||
|
println!("{}", file?.path().display())
|
||||||
|
}
|
||||||
|
return Ok(Response::Accept);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut parser = Parser::new(Lexer::new(&file));
|
||||||
|
let code = match parser.parse() {
|
||||||
|
Ok(code) => inline_modules(code, PathBuf::from(line).parent().unwrap_or("".as_ref())),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{C_ERROR}{line}:{e}\x1b[0m");
|
||||||
|
return Ok(Response::Deny);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Populator::new(table).visit_file(unsafe { TREES.push(code) });
|
||||||
|
|
||||||
|
println!("...Imported!");
|
||||||
|
Ok(Response::Accept)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pretty_handle(entry: Entry) -> Result<(), std::io::Error> {
|
||||||
|
use std::io::Write;
|
||||||
|
let mut out = std::io::stdout().lock();
|
||||||
|
let Some(kind) = entry.kind() else {
|
||||||
|
return writeln!(out, "{entry}");
|
||||||
|
};
|
||||||
|
write!(out, "{C_LISTING}{kind}")?;
|
||||||
|
|
||||||
|
if let Some(name) = entry.name() {
|
||||||
|
write!(out, " {name}")?;
|
||||||
|
}
|
||||||
|
writeln!(out, "\x1b[0m ({}): {entry}", entry.id())?;
|
||||||
|
|
||||||
|
if let Some(parent) = entry.parent() {
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
"- {C_LISTING}Parent\x1b[0m: {parent} ({})",
|
||||||
|
parent.id()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(span) = entry.span() {
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
"- {C_LISTING}Span:\x1b[0m ({}, {})",
|
||||||
|
span.head, span.tail
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match entry.meta() {
|
||||||
|
Some(meta) if !meta.is_empty() => {
|
||||||
|
writeln!(out, "- {C_LISTING}Meta:\x1b[0m")?;
|
||||||
|
for meta in meta {
|
||||||
|
writeln!(out, " - {meta}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(children) = entry.children() {
|
||||||
|
writeln!(out, "- {C_LISTING}Children:\x1b[0m")?;
|
||||||
|
for (name, child) in children {
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" - {C_LISTING}{name}\x1b[0m ({child}): {}",
|
||||||
|
entry.with_id(*child)
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(imports) = entry.imports() {
|
||||||
|
writeln!(out, "- {C_LISTING}Imports:\x1b[0m")?;
|
||||||
|
for (name, child) in imports {
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" - {C_LISTING}{name}\x1b[0m ({child}): {}",
|
||||||
|
entry.with_id(*child)
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inline_modules(code: cl_ast::File, path: impl AsRef<path::Path>) -> cl_ast::File {
|
||||||
|
match ModuleInliner::new(path).inline(code) {
|
||||||
|
Err((code, io, parse)) => {
|
||||||
|
for (file, error) in io {
|
||||||
|
eprintln!("{}:{error}", file.display());
|
||||||
|
}
|
||||||
|
for (file, error) in parse {
|
||||||
|
eprintln!("{}:{error}", file.display());
|
||||||
|
}
|
||||||
|
code
|
||||||
|
}
|
||||||
|
Ok(code) => code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn 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
|
||||||
|
}
|
||||||
|
}
|
||||||
184
compiler/cl-typeck/src/entry.rs
Normal file
184
compiler/cl-typeck/src/entry.rs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
//! An [Entry] is an accessor for [nodes](Handle) in a [Table].
|
||||||
|
//!
|
||||||
|
//! There are two kinds of entry:
|
||||||
|
//! - [Entry]: Provides getters for an entry's fields, and an implementation of
|
||||||
|
//! [Display](std::fmt::Display)
|
||||||
|
//! - [EntryMut]: Provides setters for an entry's fields, and an [`as_ref`](EntryMut::as_ref) method
|
||||||
|
//! to demote to an [Entry].
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use cl_ast::{Meta, PathPart, Sym};
|
||||||
|
use cl_structures::span::Span;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handle::Handle,
|
||||||
|
source::Source,
|
||||||
|
stage::categorize as cat,
|
||||||
|
table::{NodeKind, Table},
|
||||||
|
type_expression::{self as tex, TypeExpression},
|
||||||
|
type_kind::TypeKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod display;
|
||||||
|
|
||||||
|
impl Handle {
|
||||||
|
pub const fn to_entry<'t, 'a>(self, table: &'t Table<'a>) -> Entry<'t, 'a> {
|
||||||
|
Entry { id: self, table }
|
||||||
|
}
|
||||||
|
pub fn to_entry_mut<'t, 'a>(self, table: &'t mut Table<'a>) -> EntryMut<'t, 'a> {
|
||||||
|
EntryMut { id: self, table }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Entry<'t, 'a> {
|
||||||
|
table: &'t Table<'a>,
|
||||||
|
id: Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'t, 'a> Entry<'t, 'a> {
|
||||||
|
pub const fn new(table: &'t Table<'a>, id: Handle) -> Self {
|
||||||
|
Self { table, id }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn id(&self) -> Handle {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &Table<'a> {
|
||||||
|
self.table
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn with_id(&self, id: Handle) -> Entry<'_, 'a> {
|
||||||
|
Self { table: self.table, id }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nav(&self, path: &[PathPart]) -> Option<Entry<'_, 'a>> {
|
||||||
|
Some(Entry { id: self.table.nav(self.id, path)?, table: self.table })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn root(&self) -> Handle {
|
||||||
|
self.table.root()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> Option<&NodeKind> {
|
||||||
|
self.table.kind(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent(&self) -> Option<Entry<'_, 'a>> {
|
||||||
|
Some(Entry { id: *self.table.parent(self.id)?, ..*self })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(&self) -> Option<&HashMap<Sym, Handle>> {
|
||||||
|
self.table.children(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imports(&self) -> Option<&HashMap<Sym, Handle>> {
|
||||||
|
self.table.imports(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ty(&self) -> Option<&TypeKind> {
|
||||||
|
self.table.ty(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn span(&self) -> Option<&Span> {
|
||||||
|
self.table.span(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meta(&self) -> Option<&'a [Meta]> {
|
||||||
|
self.table.meta(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source(&self) -> Option<&Source<'a>> {
|
||||||
|
self.table.source(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn impl_target(&self) -> Option<Entry<'_, 'a>> {
|
||||||
|
Some(Entry { id: self.table.impl_target(self.id)?, ..*self })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selfty(&self) -> Option<Entry<'_, 'a>> {
|
||||||
|
Some(Entry { id: self.table.selfty(self.id)?, ..*self })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Option<Sym> {
|
||||||
|
self.table.name(self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EntryMut<'t, 'a> {
|
||||||
|
table: &'t mut Table<'a>,
|
||||||
|
id: Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'t, 'a> EntryMut<'t, 'a> {
|
||||||
|
pub fn new(table: &'t mut Table<'a>, id: Handle) -> Self {
|
||||||
|
Self { table, id }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_ref(&self) -> Entry<'_, 'a> {
|
||||||
|
Entry { table: self.table, id: self.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn id(&self) -> Handle {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates a [TypeExpression] in this entry's context
|
||||||
|
pub fn evaluate<Out>(&mut self, ty: &impl TypeExpression<Out>) -> Result<Out, tex::Error> {
|
||||||
|
let Self { table, id } = self;
|
||||||
|
ty.evaluate(table, *id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn categorize(&mut self) -> Result<(), cat::Error> {
|
||||||
|
cat::categorize(self.table, self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new Handle with the provided parent [Handle]
|
||||||
|
pub fn with_id(&mut self, parent: Handle) -> EntryMut<'_, 'a> {
|
||||||
|
EntryMut { table: self.table, id: parent }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nav(&mut self, path: &[PathPart]) -> Option<EntryMut<'_, 'a>> {
|
||||||
|
Some(EntryMut { id: self.table.nav(self.id, path)?, table: self.table })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_entry(&mut self, kind: NodeKind) -> EntryMut<'_, 'a> {
|
||||||
|
let id = self.table.new_entry(self.id, kind);
|
||||||
|
self.with_id(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_child(&mut self, name: Sym, child: Handle) -> Option<Handle> {
|
||||||
|
self.table.add_child(self.id, name, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_ty(&mut self, kind: TypeKind) -> Option<TypeKind> {
|
||||||
|
self.table.set_ty(self.id, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_span(&mut self, span: Span) -> Option<Span> {
|
||||||
|
self.table.set_span(self.id, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_meta(&mut self, meta: &'a [Meta]) -> Option<&'a [Meta]> {
|
||||||
|
self.table.set_meta(self.id, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_source(&mut self, source: Source<'a>) -> Option<Source<'a>> {
|
||||||
|
self.table.set_source(self.id, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_impl_target(&mut self, target: Handle) -> Option<Handle> {
|
||||||
|
self.table.set_impl_target(self.id, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_use_item(&mut self) {
|
||||||
|
self.table.mark_use_item(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_impl_item(&mut self) {
|
||||||
|
self.table.mark_impl_item(self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
100
compiler/cl-typeck/src/entry/display.rs
Normal file
100
compiler/cl-typeck/src/entry/display.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::{format_utils::*, type_kind::Adt};
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
|
/// Printing the name of a named type stops infinite recursion
|
||||||
|
fn write_name_or(h: Entry, f: &mut impl Write) -> fmt::Result {
|
||||||
|
match h.name() {
|
||||||
|
Some(name) => write!(f, "{name}"),
|
||||||
|
None => write!(f, "{h}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Entry<'_, '_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Some(&kind) = self.kind() else {
|
||||||
|
return write!(f, "<invalid type: {}>", self.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ty) = self.ty() {
|
||||||
|
match ty {
|
||||||
|
TypeKind::Instance(id) => write!(f, "{}", self.with_id(*id)),
|
||||||
|
TypeKind::Intrinsic(kind) => write!(f, "{kind}"),
|
||||||
|
TypeKind::Adt(adt) => write_adt(adt, self, f),
|
||||||
|
&TypeKind::Ref(id) => {
|
||||||
|
f.write_str("&")?;
|
||||||
|
let h_id = self.with_id(id);
|
||||||
|
write_name_or(h_id, f)
|
||||||
|
}
|
||||||
|
TypeKind::Slice(id) => {
|
||||||
|
write_name_or(self.with_id(*id), &mut f.delimit_with("[", "]"))
|
||||||
|
}
|
||||||
|
&TypeKind::Array(t, cnt) => {
|
||||||
|
let mut f = f.delimit_with("[", "]");
|
||||||
|
write_name_or(self.with_id(t), &mut f)?;
|
||||||
|
write!(f, "; {cnt}")
|
||||||
|
}
|
||||||
|
TypeKind::Tuple(ids) => {
|
||||||
|
let mut f = f.delimit_with("(", ")");
|
||||||
|
for (index, &id) in ids.iter().enumerate() {
|
||||||
|
if index > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
write_name_or(self.with_id(id), &mut f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
TypeKind::FnSig { args, rety } => {
|
||||||
|
write!(f, "fn {} -> ", self.with_id(*args))?;
|
||||||
|
write_name_or(self.with_id(*rety), f)
|
||||||
|
}
|
||||||
|
TypeKind::Empty => write!(f, "()"),
|
||||||
|
TypeKind::Never => write!(f, "!"),
|
||||||
|
TypeKind::Module => write!(f, "module?"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write!(f, "{kind}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_adt(adt: &Adt, h: &Entry, f: &mut impl Write) -> fmt::Result {
|
||||||
|
match adt {
|
||||||
|
Adt::Enum(variants) => {
|
||||||
|
let mut variants = variants.iter();
|
||||||
|
separate(", ", || {
|
||||||
|
variants.next().map(|(name, def)| {
|
||||||
|
move |f: &mut Delimit<_>| match def {
|
||||||
|
Some(def) => {
|
||||||
|
write!(f, "{name}: ")?;
|
||||||
|
write_name_or(h.with_id(*def), f)
|
||||||
|
}
|
||||||
|
None => write!(f, "{name}"),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})(f.delimit_with("enum {", "}"))
|
||||||
|
}
|
||||||
|
Adt::Struct(members) => {
|
||||||
|
let mut members = members.iter();
|
||||||
|
separate(", ", || {
|
||||||
|
let (name, vis, id) = members.next()?;
|
||||||
|
Some(move |f: &mut Delimit<_>| {
|
||||||
|
write!(f, "{vis}{name}: ")?;
|
||||||
|
write_name_or(h.with_id(*id), f)
|
||||||
|
})
|
||||||
|
})(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}")?;
|
||||||
|
write_name_or(h.with_id(*def), f)
|
||||||
|
})
|
||||||
|
})(f.delimit_with("struct (", ")"))
|
||||||
|
}
|
||||||
|
Adt::UnitStruct => write!(f, "struct"),
|
||||||
|
Adt::Union(_) => todo!("Display union types"),
|
||||||
|
}
|
||||||
|
}
|
||||||
23
compiler/cl-typeck/src/format_utils.rs
Normal file
23
compiler/cl-typeck/src/format_utils.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
15
compiler/cl-typeck/src/handle.rs
Normal file
15
compiler/cl-typeck/src/handle.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//! A [Handle] uniquely represents an entry in the [Table](crate::table::Table)
|
||||||
|
|
||||||
|
use cl_structures::index_map::*;
|
||||||
|
|
||||||
|
// define the index types
|
||||||
|
make_index! {
|
||||||
|
/// Uniquely represents an entry in the [Table](crate::table::Table)
|
||||||
|
Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Handle {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
compiler/cl-typeck/src/lib.rs
Normal file
74
compiler/cl-typeck/src/lib.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
//! # The Conlang Type Checker
|
||||||
|
//!
|
||||||
|
//! As a statically typed language, Conlang requires a robust type checker to enforce correctness.
|
||||||
|
//!
|
||||||
|
//! This crate is a major work-in-progress.
|
||||||
|
//!
|
||||||
|
//! # The [Table](table::Table)™
|
||||||
|
//! A directed graph of nodes and their dependencies.
|
||||||
|
//!
|
||||||
|
//! Contains [item definitions](handle) and [type expression](type_expression) information.
|
||||||
|
//!
|
||||||
|
//! *Every* item is itself a module, and can contain arbitrarily nested items
|
||||||
|
//! as part of the item graph
|
||||||
|
//!
|
||||||
|
//! The table, additionally, has some queues for use in external algorithms,
|
||||||
|
//! detailed in the [stage] module.
|
||||||
|
//!
|
||||||
|
//! # Namespaces
|
||||||
|
//! Each item in the graph is given its own namespace, which is further separated into
|
||||||
|
//! two distinct parts:
|
||||||
|
//! - Children of an item are direct descendents (i.e. their `parent` is a handle to the item)
|
||||||
|
//! - Imports of an item are indirect descendents created by `use` or `impl` directives. They are
|
||||||
|
//! shadowed by Children with the same name.
|
||||||
|
//!
|
||||||
|
//! # Order of operations:
|
||||||
|
//! For order-of-operations information, see the [stage] module.
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
|
||||||
|
pub(crate) mod format_utils;
|
||||||
|
|
||||||
|
pub mod table;
|
||||||
|
|
||||||
|
pub mod handle;
|
||||||
|
|
||||||
|
pub mod entry;
|
||||||
|
|
||||||
|
pub mod source;
|
||||||
|
|
||||||
|
pub mod type_kind;
|
||||||
|
|
||||||
|
pub mod type_expression;
|
||||||
|
|
||||||
|
pub mod stage {
|
||||||
|
//! Type collection, evaluation, checking, and inference passes.
|
||||||
|
//!
|
||||||
|
//! # Order of operations
|
||||||
|
//! 1. [mod@populate]: Populate the graph with nodes for every named item.
|
||||||
|
//! 2. [mod@import]: Import the `use` nodes discovered in [Stage 1](populate).
|
||||||
|
//! 3. [mod@categorize]: Categorize the nodes according to textual type information.
|
||||||
|
//! - Creates anonymous types (`fn(T) -> U`, `&T`, `[T]`, etc.) as necessary to fill in the
|
||||||
|
//! type graph
|
||||||
|
//! - Creates a new struct type for every enum struct-variant.
|
||||||
|
//! 4. [mod@implement]: Import members of implementation modules into types.
|
||||||
|
|
||||||
|
pub use populate::Populator;
|
||||||
|
/// Stage 1: Populate the graph with nodes.
|
||||||
|
pub mod populate;
|
||||||
|
|
||||||
|
pub use import::import;
|
||||||
|
/// Stage 2: Import the `use` nodes discovered in Stage 1.
|
||||||
|
pub mod import;
|
||||||
|
|
||||||
|
pub use categorize::categorize;
|
||||||
|
/// Stage 3: Categorize the nodes according to textual type information.
|
||||||
|
pub mod categorize;
|
||||||
|
|
||||||
|
pub use implement::implement;
|
||||||
|
/// Stage 4: Import members of `impl` blocks into their corresponding types.
|
||||||
|
pub mod implement;
|
||||||
|
|
||||||
|
// TODO: Make type inference stage 5
|
||||||
|
// TODO: Use the type information stored in the [table]
|
||||||
|
pub mod infer;
|
||||||
|
}
|
||||||
87
compiler/cl-typeck/src/source.rs
Normal file
87
compiler/cl-typeck/src/source.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
//! Holds the [Source] of a definition in the AST
|
||||||
|
|
||||||
|
use cl_ast::ast::*;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Source<'a> {
|
||||||
|
Root,
|
||||||
|
Module(&'a Module),
|
||||||
|
Alias(&'a Alias),
|
||||||
|
Enum(&'a Enum),
|
||||||
|
Variant(&'a Variant),
|
||||||
|
Struct(&'a Struct),
|
||||||
|
Const(&'a Const),
|
||||||
|
Static(&'a Static),
|
||||||
|
Function(&'a Function),
|
||||||
|
Local(&'a Let),
|
||||||
|
Impl(&'a Impl),
|
||||||
|
Use(&'a Use),
|
||||||
|
Ty(&'a TyKind),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Source<'a> {
|
||||||
|
pub fn name(&self) -> Option<Sym> {
|
||||||
|
match self {
|
||||||
|
Source::Root => None,
|
||||||
|
Source::Module(v) => Some(v.name),
|
||||||
|
Source::Alias(v) => Some(v.to),
|
||||||
|
Source::Enum(v) => Some(v.name),
|
||||||
|
Source::Variant(v) => Some(v.name),
|
||||||
|
Source::Struct(v) => Some(v.name),
|
||||||
|
Source::Const(v) => Some(v.name),
|
||||||
|
Source::Static(v) => Some(v.name),
|
||||||
|
Source::Function(v) => Some(v.name),
|
||||||
|
Source::Local(l) => Some(l.name),
|
||||||
|
Source::Impl(_) | Source::Use(_) | Source::Ty(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this [Source] defines a named value
|
||||||
|
pub fn is_named_value(&self) -> bool {
|
||||||
|
matches!(self, Self::Const(_) | Self::Static(_) | Self::Function(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this [Source] defines a named type
|
||||||
|
pub fn is_named_type(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Module(_) | Self::Alias(_) | Self::Enum(_) | Self::Struct(_)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this [Source] refers to a [Ty] with no name
|
||||||
|
pub fn is_anon_type(&self) -> bool {
|
||||||
|
matches!(self, Self::Ty(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this [Source] refers to an [Impl] block
|
||||||
|
pub fn is_impl(&self) -> bool {
|
||||||
|
matches!(self, Self::Impl(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this [Source] refers to a [Use] import
|
||||||
|
pub fn is_use_import(&self) -> bool {
|
||||||
|
matches!(self, Self::Use(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Source<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Root => "🌳 root 🌳".fmt(f),
|
||||||
|
Self::Module(arg0) => arg0.fmt(f),
|
||||||
|
Self::Alias(arg0) => arg0.fmt(f),
|
||||||
|
Self::Enum(arg0) => arg0.fmt(f),
|
||||||
|
Self::Variant(arg0) => arg0.fmt(f),
|
||||||
|
Self::Struct(arg0) => arg0.fmt(f),
|
||||||
|
Self::Const(arg0) => arg0.fmt(f),
|
||||||
|
Self::Static(arg0) => arg0.fmt(f),
|
||||||
|
Self::Function(arg0) => arg0.fmt(f),
|
||||||
|
Self::Impl(arg0) => arg0.fmt(f),
|
||||||
|
Self::Use(arg0) => arg0.fmt(f),
|
||||||
|
Self::Ty(arg0) => arg0.fmt(f),
|
||||||
|
Self::Local(arg0) => arg0.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
229
compiler/cl-typeck/src/stage/categorize.rs
Normal file
229
compiler/cl-typeck/src/stage/categorize.rs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
//! Categorizes an entry in a table according to its embedded type information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handle::Handle,
|
||||||
|
source::Source,
|
||||||
|
table::{NodeKind, Table},
|
||||||
|
type_expression::{Error as TypeEval, TypeExpression},
|
||||||
|
type_kind::{Adt, TypeKind},
|
||||||
|
};
|
||||||
|
use cl_ast::*;
|
||||||
|
|
||||||
|
/// Ensures a type entry exists for the provided handle in the table
|
||||||
|
pub fn categorize(table: &mut Table, node: Handle) -> CatResult<()> {
|
||||||
|
if let Some(meta) = table.meta(node) {
|
||||||
|
for meta @ Meta { name, kind } in meta {
|
||||||
|
if let ("intrinsic", MetaKind::Equals(Literal::String(s))) = (&**name, kind) {
|
||||||
|
let kind =
|
||||||
|
TypeKind::Intrinsic(s.parse().map_err(|_| Error::BadMeta(meta.clone()))?);
|
||||||
|
table.set_ty(node, kind);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(source) = table.source(node) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
match source {
|
||||||
|
Source::Root => Ok(()),
|
||||||
|
Source::Module(_) => Ok(()),
|
||||||
|
Source::Alias(a) => cat_alias(table, node, a),
|
||||||
|
Source::Enum(e) => cat_enum(table, node, e),
|
||||||
|
Source::Variant(_) => Ok(()),
|
||||||
|
Source::Struct(s) => cat_struct(table, node, s),
|
||||||
|
Source::Const(c) => cat_const(table, node, c),
|
||||||
|
Source::Static(s) => cat_static(table, node, s),
|
||||||
|
Source::Function(f) => cat_function(table, node, f),
|
||||||
|
Source::Local(l) => cat_local(table, node, l),
|
||||||
|
Source::Impl(i) => cat_impl(table, node, i),
|
||||||
|
Source::Use(_) => Ok(()),
|
||||||
|
Source::Ty(ty) => ty
|
||||||
|
.evaluate(table, node)
|
||||||
|
.map_err(|e| Error::TypeEval(e, " while categorizing a type"))
|
||||||
|
.map(drop),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parent(table: &Table, node: Handle) -> Handle {
|
||||||
|
table.parent(node).copied().unwrap_or(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_alias(table: &mut Table, node: Handle, a: &Alias) -> CatResult<()> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
let kind = match &a.from {
|
||||||
|
Some(ty) => TypeKind::Instance(
|
||||||
|
ty.evaluate(table, parent)
|
||||||
|
.map_err(|e| Error::TypeEval(e, " while categorizing an alias"))?,
|
||||||
|
),
|
||||||
|
None => TypeKind::Empty,
|
||||||
|
};
|
||||||
|
table.set_ty(node, kind);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_struct(table: &mut Table, node: Handle, s: &Struct) -> CatResult<()> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
let Struct { name: _, kind } = s;
|
||||||
|
let kind = match kind {
|
||||||
|
StructKind::Empty => TypeKind::Adt(Adt::UnitStruct),
|
||||||
|
StructKind::Tuple(types) => {
|
||||||
|
let mut out = vec![];
|
||||||
|
for ty in types {
|
||||||
|
out.push((Visibility::Public, ty.evaluate(table, parent)?))
|
||||||
|
}
|
||||||
|
TypeKind::Adt(Adt::TupleStruct(out))
|
||||||
|
}
|
||||||
|
StructKind::Struct(members) => {
|
||||||
|
let mut out = vec![];
|
||||||
|
for m in members {
|
||||||
|
out.push(cat_member(table, node, m)?)
|
||||||
|
}
|
||||||
|
TypeKind::Adt(Adt::Struct(out))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
table.set_ty(node, kind);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_member(
|
||||||
|
table: &mut Table,
|
||||||
|
node: Handle,
|
||||||
|
m: &StructMember,
|
||||||
|
) -> CatResult<(Sym, Visibility, Handle)> {
|
||||||
|
let StructMember { vis, name, ty } = m;
|
||||||
|
Ok((*name, *vis, ty.evaluate(table, node)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_enum<'a>(table: &mut Table<'a>, node: Handle, e: &'a Enum) -> CatResult<()> {
|
||||||
|
let Enum { name: _, kind } = e;
|
||||||
|
let kind = match kind {
|
||||||
|
EnumKind::NoVariants => TypeKind::Adt(Adt::Enum(vec![])),
|
||||||
|
EnumKind::Variants(variants) => {
|
||||||
|
let mut out_vars = vec![];
|
||||||
|
for v in variants {
|
||||||
|
out_vars.push(cat_variant(table, node, v)?)
|
||||||
|
}
|
||||||
|
TypeKind::Adt(Adt::Enum(out_vars))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
table.set_ty(node, kind);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_variant<'a>(
|
||||||
|
table: &mut Table<'a>,
|
||||||
|
node: Handle,
|
||||||
|
v: &'a Variant,
|
||||||
|
) -> CatResult<(Sym, Option<Handle>)> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
let Variant { name, kind } = v;
|
||||||
|
match kind {
|
||||||
|
VariantKind::Plain => Ok((*name, None)),
|
||||||
|
VariantKind::CLike(c) => todo!("enum-variant constant {c}"),
|
||||||
|
VariantKind::Tuple(ty) => {
|
||||||
|
let ty = ty
|
||||||
|
.evaluate(table, parent)
|
||||||
|
.map_err(|e| Error::TypeEval(e, " while categorizing a variant"))?;
|
||||||
|
Ok((*name, Some(ty)))
|
||||||
|
}
|
||||||
|
VariantKind::Struct(members) => {
|
||||||
|
let mut out = vec![];
|
||||||
|
for m in members {
|
||||||
|
out.push(cat_member(table, node, m)?)
|
||||||
|
}
|
||||||
|
let kind = TypeKind::Adt(Adt::Struct(out));
|
||||||
|
|
||||||
|
let mut h = node.to_entry_mut(table);
|
||||||
|
let mut variant = h.new_entry(NodeKind::Type);
|
||||||
|
variant.set_source(Source::Variant(v));
|
||||||
|
variant.set_ty(kind);
|
||||||
|
Ok((*name, Some(variant.id())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_const(table: &mut Table, node: Handle, c: &Const) -> CatResult<()> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
let kind = TypeKind::Instance(
|
||||||
|
c.ty.evaluate(table, parent)
|
||||||
|
.map_err(|e| Error::TypeEval(e, " while categorizing a const"))?,
|
||||||
|
);
|
||||||
|
table.set_ty(node, kind);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_static(table: &mut Table, node: Handle, s: &Static) -> CatResult<()> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
let kind = TypeKind::Instance(
|
||||||
|
s.ty.evaluate(table, parent)
|
||||||
|
.map_err(|e| Error::TypeEval(e, " while categorizing a static"))?,
|
||||||
|
);
|
||||||
|
table.set_ty(node, kind);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_function(table: &mut Table, node: Handle, f: &Function) -> CatResult<()> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
let kind = TypeKind::Instance(
|
||||||
|
f.sign
|
||||||
|
.evaluate(table, parent)
|
||||||
|
.map_err(|e| Error::TypeEval(e, " while categorizing a function"))?,
|
||||||
|
);
|
||||||
|
table.set_ty(node, kind);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_local(table: &mut Table, node: Handle, l: &Let) -> CatResult<()> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
if let Some(ty) = &l.ty {
|
||||||
|
let kind = ty
|
||||||
|
.evaluate(table, parent)
|
||||||
|
.map_err(|e| Error::TypeEval(e, " while categorizing a let binding"))?;
|
||||||
|
table.set_ty(node, TypeKind::Instance(kind));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cat_impl(table: &mut Table, node: Handle, i: &Impl) -> CatResult<()> {
|
||||||
|
let parent = parent(table, node);
|
||||||
|
let Impl { target, body: _ } = i;
|
||||||
|
let target = match target {
|
||||||
|
ImplKind::Type(t) => t.evaluate(table, parent),
|
||||||
|
ImplKind::Trait { impl_trait: _, for_type: t } => t.evaluate(table, parent),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
table.set_impl_target(node, target);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
type CatResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
BadMeta(Meta),
|
||||||
|
Recursive(Handle),
|
||||||
|
TypeEval(TypeEval, &'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TypeEval> for Error {
|
||||||
|
fn from(value: TypeEval) -> Self {
|
||||||
|
Error::TypeEval(value, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::BadMeta(meta) => write!(f, "Unknown meta attribute: #[{meta}]"),
|
||||||
|
Error::Recursive(id) => {
|
||||||
|
write!(f, "Encountered recursive type without indirection: {id}")
|
||||||
|
}
|
||||||
|
Error::TypeEval(e, during) => write!(f, "{e}{during}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
compiler/cl-typeck/src/stage/implement.rs
Normal file
23
compiler/cl-typeck/src/stage/implement.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use crate::{handle::Handle, table::Table};
|
||||||
|
|
||||||
|
pub fn implement(table: &mut Table) -> Vec<Handle> {
|
||||||
|
let pending = std::mem::take(&mut table.impls);
|
||||||
|
let mut errors = vec![];
|
||||||
|
for node in pending {
|
||||||
|
if let Err(e) = impl_one(table, node) {
|
||||||
|
errors.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn impl_one(table: &mut Table, node: Handle) -> Result<(), Handle> {
|
||||||
|
let Some(target) = table.impl_target(node) else {
|
||||||
|
Err(node)?
|
||||||
|
};
|
||||||
|
let Table { children, imports, .. } = table;
|
||||||
|
if let Some(children) = children.get(&node) {
|
||||||
|
imports.entry(target).or_default().extend(children);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
158
compiler/cl-typeck/src/stage/import.rs
Normal file
158
compiler/cl-typeck/src/stage/import.rs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
//! An algorithm for importing external nodes
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handle::Handle,
|
||||||
|
source::Source,
|
||||||
|
table::{NodeKind, Table},
|
||||||
|
};
|
||||||
|
use cl_ast::{PathPart, Sym, Use, UseTree};
|
||||||
|
use core::slice;
|
||||||
|
use std::{collections::HashSet, mem};
|
||||||
|
|
||||||
|
type Seen = HashSet<Handle>;
|
||||||
|
|
||||||
|
pub fn import<'a>(table: &mut Table<'a>) -> Vec<(Handle, Error<'a>)> {
|
||||||
|
let pending = mem::take(&mut table.uses);
|
||||||
|
|
||||||
|
let mut seen = Seen::new();
|
||||||
|
let mut failed = vec![];
|
||||||
|
for import in pending {
|
||||||
|
let Err(e) = import_one(table, import, &mut seen) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Error::NotFound(_, _) = e {
|
||||||
|
table.mark_use_item(import)
|
||||||
|
}
|
||||||
|
failed.push((import, e));
|
||||||
|
}
|
||||||
|
failed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_one<'a>(table: &mut Table<'a>, item: Handle, seen: &mut Seen) -> UseResult<'a, ()> {
|
||||||
|
if !seen.insert(item) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(NodeKind::Use) = table.kind(item) else {
|
||||||
|
Err(Error::ItsNoUse)?
|
||||||
|
};
|
||||||
|
let Some(&dst) = table.parent(item) else {
|
||||||
|
Err(Error::NoParents)?
|
||||||
|
};
|
||||||
|
let Some(code) = table.source(item) else {
|
||||||
|
Err(Error::NoSource)?
|
||||||
|
};
|
||||||
|
let &Source::Use(tree) = code else {
|
||||||
|
Err(Error::BadSource(*code))?
|
||||||
|
};
|
||||||
|
let Use { absolute, tree } = tree;
|
||||||
|
|
||||||
|
import_tree(
|
||||||
|
table,
|
||||||
|
if !absolute { dst } else { table.root() },
|
||||||
|
dst,
|
||||||
|
tree,
|
||||||
|
seen,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_tree<'a>(
|
||||||
|
table: &mut Table<'a>,
|
||||||
|
src: Handle,
|
||||||
|
dst: Handle,
|
||||||
|
tree: &UseTree,
|
||||||
|
seen: &mut Seen,
|
||||||
|
) -> UseResult<'a, ()> {
|
||||||
|
match tree {
|
||||||
|
UseTree::Tree(trees) => trees
|
||||||
|
.iter()
|
||||||
|
.try_for_each(|tree| import_tree(table, src, dst, tree, seen)),
|
||||||
|
UseTree::Path(part, rest) => {
|
||||||
|
let source = table
|
||||||
|
.nav(src, slice::from_ref(part))
|
||||||
|
.ok_or_else(|| Error::NotFound(src, part.clone()))?;
|
||||||
|
import_tree(table, source, dst, rest, seen)
|
||||||
|
}
|
||||||
|
UseTree::Alias(src_name, dst_name) => {
|
||||||
|
import_name(table, src, src_name, dst, dst_name, seen)
|
||||||
|
}
|
||||||
|
UseTree::Name(src_name) => import_name(table, src, src_name, dst, src_name, seen),
|
||||||
|
UseTree::Glob => import_glob(table, src, dst, seen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_glob<'a>(
|
||||||
|
table: &mut Table<'a>,
|
||||||
|
src: Handle,
|
||||||
|
dst: Handle,
|
||||||
|
seen: &mut Seen,
|
||||||
|
) -> UseResult<'a, ()> {
|
||||||
|
let Table { children, imports, .. } = table;
|
||||||
|
|
||||||
|
if let Some(c) = children.get(&src) {
|
||||||
|
imports.entry(dst).or_default().extend(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
import_deps(table, src, seen)?;
|
||||||
|
|
||||||
|
let Table { imports, .. } = table;
|
||||||
|
|
||||||
|
// Importing imports requires some extra work, since we can't `get_many_mut`
|
||||||
|
if let Some(i) = imports.get(&src) {
|
||||||
|
let uses: Vec<_> = i.iter().map(|(&k, &v)| (k, v)).collect();
|
||||||
|
imports.entry(dst).or_default().extend(uses);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_name<'a>(
|
||||||
|
table: &mut Table<'a>,
|
||||||
|
src: Handle,
|
||||||
|
src_name: &Sym,
|
||||||
|
dst: Handle,
|
||||||
|
dst_name: &Sym,
|
||||||
|
seen: &mut Seen,
|
||||||
|
) -> UseResult<'a, ()> {
|
||||||
|
import_deps(table, src, seen)?;
|
||||||
|
match table.get_by_sym(src, src_name) {
|
||||||
|
// TODO: check for new imports clobbering existing imports
|
||||||
|
Some(src_id) => table.add_import(dst, *dst_name, src_id),
|
||||||
|
None => Err(Error::NotFound(src, PathPart::Ident(*src_name)))?,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Imports the dependencies of this node
|
||||||
|
fn import_deps<'a>(table: &mut Table<'a>, node: Handle, seen: &mut Seen) -> UseResult<'a, ()> {
|
||||||
|
if let Some(items) = table.use_items.get(&node) {
|
||||||
|
let out = items.clone();
|
||||||
|
for item in out {
|
||||||
|
import_one(table, item, seen)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type UseResult<'a, T> = Result<T, Error<'a>>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error<'a> {
|
||||||
|
ItsNoUse,
|
||||||
|
NoParents,
|
||||||
|
NoSource,
|
||||||
|
BadSource(Source<'a>),
|
||||||
|
NotFound(Handle, PathPart),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::ItsNoUse => write!(f, "Entry is not use"),
|
||||||
|
Error::NoParents => write!(f, "Entry has no parents"),
|
||||||
|
Error::NoSource => write!(f, "Entry has no source"),
|
||||||
|
Error::BadSource(s) => write!(f, "Entry incorrectly marked as use item: {s}"),
|
||||||
|
Error::NotFound(id, part) => write!(f, "Could not traverse {id}::{part}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
248
compiler/cl-typeck/src/stage/infer.rs
Normal file
248
compiler/cl-typeck/src/stage/infer.rs
Normal 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::stage::infer::*;
|
||||||
|
/// 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::stage::infer::*;
|
||||||
|
/// 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!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
166
compiler/cl-typeck/src/stage/populate.rs
Normal file
166
compiler/cl-typeck/src/stage/populate.rs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
//! The [Populator] populates entries in the sym table, including span info
|
||||||
|
use crate::{
|
||||||
|
entry::EntryMut,
|
||||||
|
handle::Handle,
|
||||||
|
source::Source,
|
||||||
|
table::{NodeKind, Table},
|
||||||
|
};
|
||||||
|
use cl_ast::{ast_visitor::Visit, ItemKind, Sym};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Populator<'t, 'a> {
|
||||||
|
inner: EntryMut<'t, 'a>,
|
||||||
|
name: Option<Sym>, // this is a hack to get around the Visitor interface
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'t, 'a> Populator<'t, 'a> {
|
||||||
|
pub fn new(table: &'t mut Table<'a>) -> Self {
|
||||||
|
Self { inner: table.root_entry_mut(), name: None }
|
||||||
|
}
|
||||||
|
/// Constructs a new Populator with the provided parent Handle
|
||||||
|
pub fn with_id(&mut self, parent: Handle) -> Populator<'_, 'a> {
|
||||||
|
Populator { inner: self.inner.with_id(parent), name: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_entry(&mut self, kind: NodeKind) -> Populator<'_, 'a> {
|
||||||
|
Populator { inner: self.inner.new_entry(kind), name: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_name(&mut self, name: Sym) {
|
||||||
|
self.name = Some(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visit<'a> for Populator<'_, 'a> {
|
||||||
|
fn visit_item(&mut self, i: &'a cl_ast::Item) {
|
||||||
|
let cl_ast::Item { extents, attrs, vis, kind } = i;
|
||||||
|
// TODO: this, better, better.
|
||||||
|
let entry_kind = match kind {
|
||||||
|
ItemKind::Alias(_) => NodeKind::Type,
|
||||||
|
ItemKind::Enum(_) => NodeKind::Type,
|
||||||
|
ItemKind::Struct(_) => NodeKind::Type,
|
||||||
|
|
||||||
|
ItemKind::Const(_) => NodeKind::Const,
|
||||||
|
ItemKind::Static(_) => NodeKind::Static,
|
||||||
|
ItemKind::Function(_) => NodeKind::Function,
|
||||||
|
|
||||||
|
ItemKind::Module(_) => NodeKind::Module,
|
||||||
|
ItemKind::Impl(_) => NodeKind::Impl,
|
||||||
|
ItemKind::Use(_) => NodeKind::Use,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut entry = self.new_entry(entry_kind);
|
||||||
|
entry.inner.set_span(*extents);
|
||||||
|
entry.inner.set_meta(&attrs.meta);
|
||||||
|
|
||||||
|
entry.visit_span(extents);
|
||||||
|
entry.visit_attrs(attrs);
|
||||||
|
entry.visit_visibility(vis);
|
||||||
|
entry.visit_item_kind(kind);
|
||||||
|
|
||||||
|
if let (Some(name), child) = (entry.name, entry.inner.id()) {
|
||||||
|
self.inner.add_child(name, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_alias(&mut self, a: &'a cl_ast::Alias) {
|
||||||
|
let cl_ast::Alias { to, from } = a;
|
||||||
|
self.inner.set_source(Source::Alias(a));
|
||||||
|
self.set_name(*to);
|
||||||
|
|
||||||
|
if let Some(t) = from {
|
||||||
|
self.visit_ty(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_const(&mut self, c: &'a cl_ast::Const) {
|
||||||
|
let cl_ast::Const { name, ty, init } = c;
|
||||||
|
self.inner.set_source(Source::Const(c));
|
||||||
|
self.set_name(*name);
|
||||||
|
|
||||||
|
self.visit_ty(ty);
|
||||||
|
self.visit_expr(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_static(&mut self, s: &'a cl_ast::Static) {
|
||||||
|
let cl_ast::Static { mutable, name, ty, init } = s;
|
||||||
|
self.inner.set_source(Source::Static(s));
|
||||||
|
self.set_name(*name);
|
||||||
|
|
||||||
|
self.visit_mutability(mutable);
|
||||||
|
self.visit_ty(ty);
|
||||||
|
self.visit_expr(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_module(&mut self, m: &'a cl_ast::Module) {
|
||||||
|
let cl_ast::Module { name, kind } = m;
|
||||||
|
self.inner.set_source(Source::Module(m));
|
||||||
|
self.set_name(*name);
|
||||||
|
|
||||||
|
self.visit_module_kind(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_function(&mut self, f: &'a cl_ast::Function) {
|
||||||
|
let cl_ast::Function { name, sign, bind, body } = f;
|
||||||
|
self.inner.set_source(Source::Function(f));
|
||||||
|
self.set_name(*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_struct(&mut self, s: &'a cl_ast::Struct) {
|
||||||
|
let cl_ast::Struct { name, kind } = s;
|
||||||
|
self.inner.set_source(Source::Struct(s));
|
||||||
|
self.set_name(*name);
|
||||||
|
|
||||||
|
self.visit_struct_kind(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_enum(&mut self, e: &'a cl_ast::Enum) {
|
||||||
|
let cl_ast::Enum { name, kind } = e;
|
||||||
|
self.inner.set_source(Source::Enum(e));
|
||||||
|
self.set_name(*name);
|
||||||
|
|
||||||
|
self.visit_enum_kind(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_impl(&mut self, i: &'a cl_ast::Impl) {
|
||||||
|
let cl_ast::Impl { target, body } = i;
|
||||||
|
self.inner.set_source(Source::Impl(i));
|
||||||
|
self.inner.mark_impl_item();
|
||||||
|
|
||||||
|
self.visit_impl_kind(target);
|
||||||
|
self.visit_file(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_use(&mut self, u: &'a cl_ast::Use) {
|
||||||
|
let cl_ast::Use { absolute: _, tree } = u;
|
||||||
|
self.inner.set_source(Source::Use(u));
|
||||||
|
self.inner.mark_use_item();
|
||||||
|
|
||||||
|
self.visit_use_tree(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_let(&mut self, l: &'a cl_ast::Let) {
|
||||||
|
let cl_ast::Let { mutable, name, ty, init } = l;
|
||||||
|
let mut entry = self.new_entry(NodeKind::Local);
|
||||||
|
|
||||||
|
entry.inner.set_source(Source::Local(l));
|
||||||
|
entry.set_name(*name);
|
||||||
|
|
||||||
|
entry.visit_mutability(mutable);
|
||||||
|
if let Some(ty) = ty {
|
||||||
|
entry.visit_ty(ty);
|
||||||
|
}
|
||||||
|
if let Some(init) = init {
|
||||||
|
entry.visit_expr(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
let child = entry.inner.id();
|
||||||
|
self.inner.add_child(*name, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
308
compiler/cl-typeck/src/table.rs
Normal file
308
compiler/cl-typeck/src/table.rs
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
//! The [Table] is a monolithic data structure representing everything the type checker
|
||||||
|
//! knows about a program.
|
||||||
|
//!
|
||||||
|
//! Individual nodes in the table can be queried using the [Entry] API ([Table::entry])
|
||||||
|
//! or modified using the [EntryMut] API ([Table::entry_mut]).
|
||||||
|
//!
|
||||||
|
//! # Contents of a "node"
|
||||||
|
//! Always present:
|
||||||
|
//! - [NodeKind]: Determines how this node will be treated during the [stages](crate::stage) of
|
||||||
|
//! compilation
|
||||||
|
//! - [Parent node](Handle): Arranges this node in the hierarchical graph structure
|
||||||
|
//!
|
||||||
|
//! Populated as needed:
|
||||||
|
//! - Children: An associative array of [names](Sym) to child nodes in the graph. Child nodes are
|
||||||
|
//! arranged in a *strict* tree structure, with no back edges
|
||||||
|
//! - Imports: An associative array of [names](Sym) to other nodes in the graph. Not all import
|
||||||
|
//! nodes are back edges, but all back edges *must be* import nodes.
|
||||||
|
//! - [Types](TypeKind): Contains type information populated through type checking and inference.
|
||||||
|
//! Nodes with unpopulated types may be considered type variables in the future.
|
||||||
|
//! - [Spans][span]: Positional information from the source text. See [cl_structures::span].
|
||||||
|
//! - [Metas](Meta): Metadata decorators. These may have an effect throughout the compiler.
|
||||||
|
//! - [Sources](Source): Pointers back into the AST, for future analysis.
|
||||||
|
//! - Impl Targets: Sparse mapping of `impl` nodes to their corresponding targets.
|
||||||
|
//! - etc.
|
||||||
|
//!
|
||||||
|
//! [span]: struct@Span
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
entry::{Entry, EntryMut},
|
||||||
|
handle::Handle,
|
||||||
|
source::Source,
|
||||||
|
type_kind::TypeKind,
|
||||||
|
};
|
||||||
|
use cl_ast::{Meta, PathPart, Sym};
|
||||||
|
use cl_structures::{index_map::IndexMap, span::Span};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// TODO: Cycle detection external to this module
|
||||||
|
|
||||||
|
/// The table is a monolithic data structure representing everything the type checker
|
||||||
|
/// knows about a program.
|
||||||
|
///
|
||||||
|
/// See [module documentation](self).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Table<'a> {
|
||||||
|
root: Handle,
|
||||||
|
/// This is the source of truth for handles
|
||||||
|
kinds: IndexMap<Handle, NodeKind>,
|
||||||
|
parents: IndexMap<Handle, Handle>,
|
||||||
|
pub(crate) children: HashMap<Handle, HashMap<Sym, Handle>>,
|
||||||
|
pub(crate) imports: HashMap<Handle, HashMap<Sym, Handle>>,
|
||||||
|
pub(crate) use_items: HashMap<Handle, Vec<Handle>>,
|
||||||
|
types: HashMap<Handle, TypeKind>,
|
||||||
|
spans: HashMap<Handle, Span>,
|
||||||
|
metas: HashMap<Handle, &'a [Meta]>,
|
||||||
|
sources: HashMap<Handle, Source<'a>>,
|
||||||
|
// code: HashMap<Handle, BasicBlock>, // TODO: lower sources
|
||||||
|
impl_targets: HashMap<Handle, Handle>,
|
||||||
|
anon_types: HashMap<TypeKind, Handle>,
|
||||||
|
|
||||||
|
// --- Queues for algorithms ---
|
||||||
|
pub(crate) impls: Vec<Handle>,
|
||||||
|
pub(crate) uses: Vec<Handle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Table<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut kinds = IndexMap::new();
|
||||||
|
let mut parents = IndexMap::new();
|
||||||
|
let root = kinds.insert(NodeKind::Root);
|
||||||
|
assert_eq!(root, parents.insert(root));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
root,
|
||||||
|
kinds,
|
||||||
|
parents,
|
||||||
|
children: HashMap::new(),
|
||||||
|
imports: HashMap::new(),
|
||||||
|
use_items: HashMap::new(),
|
||||||
|
types: HashMap::new(),
|
||||||
|
spans: HashMap::new(),
|
||||||
|
metas: HashMap::new(),
|
||||||
|
sources: HashMap::new(),
|
||||||
|
impl_targets: HashMap::new(),
|
||||||
|
anon_types: HashMap::new(),
|
||||||
|
impls: Vec::new(),
|
||||||
|
uses: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry(&self, handle: Handle) -> Entry<'_, 'a> {
|
||||||
|
handle.to_entry(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry_mut(&mut self, handle: Handle) -> EntryMut<'_, 'a> {
|
||||||
|
handle.to_entry_mut(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_entry(&mut self, parent: Handle, kind: NodeKind) -> Handle {
|
||||||
|
let entry = self.kinds.insert(kind);
|
||||||
|
assert_eq!(entry, self.parents.insert(parent));
|
||||||
|
entry
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_child(&mut self, parent: Handle, name: Sym, child: Handle) -> Option<Handle> {
|
||||||
|
self.children.entry(parent).or_default().insert(name, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_import(&mut self, parent: Handle, name: Sym, import: Handle) -> Option<Handle> {
|
||||||
|
self.imports.entry(parent).or_default().insert(name, import)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_use_item(&mut self, item: Handle) {
|
||||||
|
let parent = self.parents[item];
|
||||||
|
self.use_items.entry(parent).or_default().push(item);
|
||||||
|
self.uses.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_impl_item(&mut self, item: Handle) {
|
||||||
|
self.impls.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_iter(&mut self) -> impl Iterator<Item = Handle> {
|
||||||
|
self.kinds.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns handles to all nodes sequentially by [Entry]
|
||||||
|
pub fn debug_entry_iter(&self) -> impl Iterator<Item = Entry<'_, 'a>> {
|
||||||
|
self.kinds.keys().map(|key| key.to_entry(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the [Handle] of an anonymous type with the provided [TypeKind].
|
||||||
|
/// If not already present, a new one is created.
|
||||||
|
pub(crate) fn anon_type(&mut self, kind: TypeKind) -> Handle {
|
||||||
|
if let Some(id) = self.anon_types.get(&kind) {
|
||||||
|
return *id;
|
||||||
|
}
|
||||||
|
let entry = self.new_entry(self.root, NodeKind::Type);
|
||||||
|
// Anonymous types require a bijective map (anon_types => Def => types)
|
||||||
|
self.types.insert(entry, kind.clone());
|
||||||
|
self.anon_types.insert(kind, entry);
|
||||||
|
entry
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn root_entry(&self) -> Entry<'_, 'a> {
|
||||||
|
self.root.to_entry(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_entry_mut(&mut self) -> crate::entry::EntryMut<'_, 'a> {
|
||||||
|
self.root.to_entry_mut(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- inherent properties ---
|
||||||
|
|
||||||
|
pub const fn root(&self) -> Handle {
|
||||||
|
self.root
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self, node: Handle) -> Option<&NodeKind> {
|
||||||
|
self.kinds.get(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent(&self, node: Handle) -> Option<&Handle> {
|
||||||
|
self.parents.get(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(&self, node: Handle) -> Option<&HashMap<Sym, Handle>> {
|
||||||
|
self.children.get(&node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imports(&self, node: Handle) -> Option<&HashMap<Sym, Handle>> {
|
||||||
|
self.imports.get(&node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ty(&self, node: Handle) -> Option<&TypeKind> {
|
||||||
|
self.types.get(&node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn span(&self, node: Handle) -> Option<&Span> {
|
||||||
|
self.spans.get(&node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meta(&self, node: Handle) -> Option<&'a [Meta]> {
|
||||||
|
self.metas.get(&node).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source(&self, node: Handle) -> Option<&Source<'a>> {
|
||||||
|
self.sources.get(&node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn impl_target(&self, node: Handle) -> Option<Handle> {
|
||||||
|
self.impl_targets.get(&node).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_ty(&mut self, node: Handle, kind: TypeKind) -> Option<TypeKind> {
|
||||||
|
self.types.insert(node, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_span(&mut self, node: Handle, span: Span) -> Option<Span> {
|
||||||
|
self.spans.insert(node, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_meta(&mut self, node: Handle, meta: &'a [Meta]) -> Option<&'a [Meta]> {
|
||||||
|
self.metas.insert(node, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_source(&mut self, node: Handle, source: Source<'a>) -> Option<Source<'a>> {
|
||||||
|
self.sources.insert(node, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_impl_target(&mut self, node: Handle, target: Handle) -> Option<Handle> {
|
||||||
|
self.impl_targets.insert(node, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- derived properties ---
|
||||||
|
|
||||||
|
/// Gets a handle to the local `Self` type, if one exists
|
||||||
|
pub fn selfty(&self, node: Handle) -> Option<Handle> {
|
||||||
|
match self.kinds.get(node)? {
|
||||||
|
NodeKind::Root | NodeKind::Use => None,
|
||||||
|
NodeKind::Type => Some(node),
|
||||||
|
NodeKind::Impl => self.impl_target(node),
|
||||||
|
_ => self.selfty(*self.parent(node)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self, node: Handle) -> Option<Sym> {
|
||||||
|
self.source(node).and_then(|s| s.name())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_transparent(&self, node: Handle) -> bool {
|
||||||
|
!matches!(
|
||||||
|
self.kind(node),
|
||||||
|
None | Some(NodeKind::Root | NodeKind::Module)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_child(&self, node: Handle, name: &Sym) -> Option<Handle> {
|
||||||
|
self.children.get(&node).and_then(|c| c.get(name)).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_import(&self, node: Handle, name: &Sym) -> Option<Handle> {
|
||||||
|
self.imports.get(&node).and_then(|i| i.get(name)).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_by_sym(&self, node: Handle, name: &Sym) -> Option<Handle> {
|
||||||
|
self.get_child(node, name)
|
||||||
|
.or_else(|| self.get_import(node, name))
|
||||||
|
.or_else(|| {
|
||||||
|
self.is_transparent(node)
|
||||||
|
.then(|| {
|
||||||
|
self.parent(node)
|
||||||
|
.and_then(|node| self.get_by_sym(*node, name))
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does path traversal relative to the provided `node`.
|
||||||
|
pub fn nav(&self, node: Handle, path: &[PathPart]) -> Option<Handle> {
|
||||||
|
match path {
|
||||||
|
[PathPart::SuperKw, rest @ ..] => self.nav(*self.parent(node)?, rest),
|
||||||
|
[PathPart::SelfKw, rest @ ..] => self.nav(node, rest),
|
||||||
|
[PathPart::SelfTy, rest @ ..] => self.nav(self.selfty(node)?, rest),
|
||||||
|
[PathPart::Ident(name), rest @ ..] => self.nav(self.get_by_sym(node, name)?, rest),
|
||||||
|
[] => Some(node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for Table<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum NodeKind {
|
||||||
|
Root,
|
||||||
|
Module,
|
||||||
|
Type,
|
||||||
|
Const,
|
||||||
|
Static,
|
||||||
|
Function,
|
||||||
|
Local,
|
||||||
|
Impl,
|
||||||
|
Use,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod display {
|
||||||
|
use super::*;
|
||||||
|
use std::fmt;
|
||||||
|
impl fmt::Display for NodeKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
NodeKind::Root => write!(f, "root"),
|
||||||
|
NodeKind::Module => write!(f, "mod"),
|
||||||
|
NodeKind::Type => write!(f, "type"),
|
||||||
|
NodeKind::Const => write!(f, "const"),
|
||||||
|
NodeKind::Static => write!(f, "static"),
|
||||||
|
NodeKind::Function => write!(f, "fn"),
|
||||||
|
NodeKind::Local => write!(f, "local"),
|
||||||
|
NodeKind::Use => write!(f, "use"),
|
||||||
|
NodeKind::Impl => write!(f, "impl"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
131
compiler/cl-typeck/src/type_expression.rs
Normal file
131
compiler/cl-typeck/src/type_expression.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
//! A [TypeExpression] is a [syntactic](cl_ast) representation of a [TypeKind], and is used to
|
||||||
|
//! construct type bindings in a [Table]'s typing context.
|
||||||
|
|
||||||
|
use crate::{handle::Handle, table::Table, type_kind::TypeKind};
|
||||||
|
use cl_ast::{PathPart, Ty, TyArray, TyFn, TyKind, TyRef, TySlice, TyTuple};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)] // TODO: impl Display and Error
|
||||||
|
pub enum Error {
|
||||||
|
BadPath { parent: Handle, path: Vec<PathPart> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::BadPath { parent, path } => {
|
||||||
|
write!(f, "No item at path {parent}")?;
|
||||||
|
for part in path {
|
||||||
|
write!(f, "::{part}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [TypeExpression] is a syntactic representation of a [TypeKind], and is used to construct
|
||||||
|
/// type bindings in a [Table]'s typing context.
|
||||||
|
pub trait TypeExpression<Out = Handle> {
|
||||||
|
/// Evaluates a type expression, recursively creating intermediate bindings.
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Out, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for Ty {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
self.kind.evaluate(table, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for TyKind {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
match self {
|
||||||
|
TyKind::Never => Ok(table.anon_type(TypeKind::Never)),
|
||||||
|
TyKind::Empty => Ok(table.anon_type(TypeKind::Empty)),
|
||||||
|
TyKind::Path(p) => p.evaluate(table, node),
|
||||||
|
TyKind::Array(a) => a.evaluate(table, node),
|
||||||
|
TyKind::Slice(s) => s.evaluate(table, node),
|
||||||
|
TyKind::Tuple(t) => t.evaluate(table, node),
|
||||||
|
TyKind::Ref(r) => r.evaluate(table, node),
|
||||||
|
TyKind::Fn(f) => f.evaluate(table, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for cl_ast::Path {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
let Self { absolute, parts } = self;
|
||||||
|
parts.evaluate(table, if *absolute { table.root() } else { node })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for [PathPart] {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
table
|
||||||
|
.nav(node, self)
|
||||||
|
.ok_or_else(|| Error::BadPath { parent: node, path: self.to_owned() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for TyArray {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
let Self { ty, count } = self;
|
||||||
|
let kind = TypeKind::Array(ty.evaluate(table, node)?, *count);
|
||||||
|
Ok(table.anon_type(kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for TySlice {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
let Self { ty } = self;
|
||||||
|
let kind = TypeKind::Slice(ty.evaluate(table, node)?);
|
||||||
|
Ok(table.anon_type(kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for TyTuple {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
let Self { types } = self;
|
||||||
|
let kind = match types.len() {
|
||||||
|
0 => TypeKind::Empty,
|
||||||
|
_ => TypeKind::Tuple(types.evaluate(table, node)?),
|
||||||
|
};
|
||||||
|
Ok(table.anon_type(kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for TyRef {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
let Self { mutable: _, count, to } = self;
|
||||||
|
let mut t = to.evaluate(table, node)?;
|
||||||
|
for _ in 0..*count {
|
||||||
|
let kind = TypeKind::Ref(t);
|
||||||
|
t = table.anon_type(kind)
|
||||||
|
}
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeExpression for TyFn {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||||
|
let Self { args, rety } = self;
|
||||||
|
let kind = TypeKind::FnSig {
|
||||||
|
args: args.evaluate(table, node)?,
|
||||||
|
rety: match rety {
|
||||||
|
Some(ty) => ty.evaluate(table, node)?,
|
||||||
|
None => TyKind::Empty.evaluate(table, node)?,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(table.anon_type(kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TypeExpression<U>, U> TypeExpression<Vec<U>> for [T] {
|
||||||
|
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Vec<U>, Error> {
|
||||||
|
let mut out = Vec::with_capacity(self.len());
|
||||||
|
for te in self {
|
||||||
|
out.push(te.evaluate(table, node)?) // try_collect is unstable
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
97
compiler/cl-typeck/src/type_kind.rs
Normal file
97
compiler/cl-typeck/src/type_kind.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
//! A [TypeKind] is a node in the [Table](crate::table::Table)'s type graph
|
||||||
|
|
||||||
|
use crate::handle::Handle;
|
||||||
|
use cl_ast::{Sym, Visibility};
|
||||||
|
use std::{fmt::Debug, str::FromStr};
|
||||||
|
|
||||||
|
mod display;
|
||||||
|
|
||||||
|
/// A [TypeKind] represents an item
|
||||||
|
/// (a component of a [Table](crate::table::Table))
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum TypeKind {
|
||||||
|
/// An alias for an already-defined type
|
||||||
|
Instance(Handle),
|
||||||
|
/// 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(Handle),
|
||||||
|
/// A contiguous view of dynamically sized memory
|
||||||
|
Slice(Handle),
|
||||||
|
/// A contiguous view of statically sized memory
|
||||||
|
Array(Handle, usize),
|
||||||
|
/// A tuple of existing types
|
||||||
|
Tuple(Vec<Handle>),
|
||||||
|
/// A function which accepts multiple inputs and produces an output
|
||||||
|
FnSig { args: Handle, rety: Handle },
|
||||||
|
/// 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<Handle>)>),
|
||||||
|
|
||||||
|
/// A structural product type with named members
|
||||||
|
Struct(Vec<(Sym, Visibility, Handle)>),
|
||||||
|
/// A structural product type with unnamed members
|
||||||
|
TupleStruct(Vec<(Visibility, Handle)>),
|
||||||
|
/// A structural product type of neither named nor unnamed members
|
||||||
|
UnitStruct,
|
||||||
|
|
||||||
|
/// A choose your own undefined behavior type
|
||||||
|
/// TODO: should unions be a language feature?
|
||||||
|
Union(Vec<(Sym, Handle)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The set of compiler-intrinsic types.
|
||||||
|
/// These primitive types have native implementations of the basic operations.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Intrinsic {
|
||||||
|
I8, I16, I32, I64, I128, Isize, // Signed integers
|
||||||
|
U8, U16, U32, U64, U128, Usize, // Unsigned integers
|
||||||
|
F8, F16, F32, F64, F128, Fsize, // Floating point numbers
|
||||||
|
Bool, // boolean value
|
||||||
|
Char, // Unicode codepoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author's note: the fsize type is a meme
|
||||||
|
|
||||||
|
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,
|
||||||
|
"i128" => Intrinsic::I128,
|
||||||
|
"isize" => Intrinsic::Isize,
|
||||||
|
"u8" => Intrinsic::U8,
|
||||||
|
"u16" => Intrinsic::U16,
|
||||||
|
"u32" => Intrinsic::U32,
|
||||||
|
"u64" => Intrinsic::U64,
|
||||||
|
"u128" => Intrinsic::U128,
|
||||||
|
"usize" => Intrinsic::Usize,
|
||||||
|
"f8" => Intrinsic::F8,
|
||||||
|
"f16" => Intrinsic::F16,
|
||||||
|
"f32" => Intrinsic::F32,
|
||||||
|
"f64" => Intrinsic::F64,
|
||||||
|
"f128" => Intrinsic::F128,
|
||||||
|
"fsize" => Intrinsic::Fsize,
|
||||||
|
"bool" => Intrinsic::Bool,
|
||||||
|
"char" => Intrinsic::Char,
|
||||||
|
_ => Err(())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
96
compiler/cl-typeck/src/type_kind/display.rs
Normal file
96
compiler/cl-typeck/src/type_kind/display.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
//! [Display] implementations for [TypeKind], [Adt], and [Intrinsic]
|
||||||
|
|
||||||
|
use super::{Adt, Intrinsic, TypeKind};
|
||||||
|
use crate::format_utils::*;
|
||||||
|
use cl_ast::format::FmtAdapter;
|
||||||
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
|
impl Display for TypeKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
TypeKind::Instance(def) => write!(f, "alias to #{def}"),
|
||||||
|
TypeKind::Intrinsic(i) => i.fmt(f),
|
||||||
|
TypeKind::Adt(a) => a.fmt(f),
|
||||||
|
TypeKind::Ref(def) => write!(f, "&{def}"),
|
||||||
|
TypeKind::Slice(def) => write!(f, "slice [#{def}]"),
|
||||||
|
TypeKind::Array(def, size) => write!(f, "array [#{def}; {size}]"),
|
||||||
|
TypeKind::Tuple(defs) => {
|
||||||
|
let mut defs = defs.iter();
|
||||||
|
separate(", ", || {
|
||||||
|
let def = defs.next()?;
|
||||||
|
Some(move |f: &mut Delimit<_>| write!(f, "#{def}"))
|
||||||
|
})(f.delimit_with("tuple (", ")"))
|
||||||
|
}
|
||||||
|
TypeKind::FnSig { args, rety } => write!(f, "fn (#{args}) -> #{rety}"),
|
||||||
|
TypeKind::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::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::I128 => f.write_str("i128"),
|
||||||
|
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::U128 => f.write_str("u128"),
|
||||||
|
Intrinsic::Usize => f.write_str("usize"),
|
||||||
|
Intrinsic::F8 => f.write_str("f8"),
|
||||||
|
Intrinsic::F16 => f.write_str("f16"),
|
||||||
|
Intrinsic::F32 => f.write_str("f32"),
|
||||||
|
Intrinsic::F64 => f.write_str("f64"),
|
||||||
|
Intrinsic::F128 => f.write_str("f128"),
|
||||||
|
Intrinsic::Fsize => f.write_str("fsize"),
|
||||||
|
Intrinsic::Bool => f.write_str("bool"),
|
||||||
|
Intrinsic::Char => f.write_str("char"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
grammar.ebnf
79
grammar.ebnf
@@ -1,38 +1,38 @@
|
|||||||
(* Conlang Expression Grammar *)
|
(* Conlang Expression Grammar *)
|
||||||
Start = File ;
|
Start = File EOI ;
|
||||||
|
|
||||||
Mutability = "mut"? ;
|
Mutability = "mut"? ;
|
||||||
Visibility = "pub"? ;
|
Visibility = "pub"? ;
|
||||||
|
|
||||||
|
|
||||||
File = Item* EOI ;
|
File = Item* ;
|
||||||
|
|
||||||
|
|
||||||
Attrs = ('#' '[' (Meta ',') Meta? ']')* ;
|
Attrs = ('#' '[' (Meta ',')* Meta? ']')* ;
|
||||||
Meta = Identifier ('=' Literal | '(' (Literal ',')* Literal? ')')? ;
|
Meta = Identifier ('=' Literal | '(' (Literal ',')* Literal? ')')? ;
|
||||||
|
|
||||||
|
|
||||||
Item = Attrs* Visibility ItemKind ;
|
Item = Attrs Visibility ItemKind ;
|
||||||
ItemKind = Const | Static | Module
|
ItemKind = Const | Static | Module
|
||||||
| Function | Struct | Enum
|
| Function | Struct | Enum
|
||||||
| Alias | Impl ;
|
| Alias | Impl | Use ;
|
||||||
|
|
||||||
|
|
||||||
(* item *)
|
(* item *)
|
||||||
Const = "const" Identifier ':' Type = Expr ';' ;
|
Const = "const" Identifier ':' Ty '=' Expr ';' ;
|
||||||
|
|
||||||
Static = "static" Mutability Identifier ':' Type = Expr ';' ;
|
Static = "static" Mutability Identifier ':' Ty '=' Expr ';' ;
|
||||||
|
|
||||||
Module = "mod" Identifier ModuleKind ;
|
Module = "mod" Identifier ModuleKind ;
|
||||||
ModuleKind = '{' Item* '}' | ';' ;
|
ModuleKind = '{' Item* '}' | ';' ;
|
||||||
|
|
||||||
Function = "fn" Identifier '(' (Param ',')* Param? ')' ('->' Type)? Block? ;
|
Function = "fn" Identifier '(' (Param ',')* Param? ')' ('->' Ty)? Block? ;
|
||||||
Param = Mutability Identifier ':' Type ;
|
Param = Mutability Identifier ':' Ty ;
|
||||||
|
|
||||||
Struct = "struct" Identifier (StructTuple | StructBody)?;
|
Struct = "struct" Identifier (StructTuple | StructBody)?;
|
||||||
StructBody = '{' (StructMember ',')* StructMember? '}' ;
|
StructBody = '{' (StructMember ',')* StructMember? '}' ;
|
||||||
StructTuple = TyTuple ;
|
StructTuple = TyTuple ;
|
||||||
StructMember = Visibility Identifier ':' Type ;
|
StructMember = Visibility Identifier ':' Ty ;
|
||||||
|
|
||||||
Enum = "enum" Identifier '{' (Variant ',')* Variant? '}' ;
|
Enum = "enum" Identifier '{' (Variant ',')* Variant? '}' ;
|
||||||
Variant = Identifier (VarStruct | VarTuple | VarCLike)? ;
|
Variant = Identifier (VarStruct | VarTuple | VarCLike)? ;
|
||||||
@@ -40,24 +40,31 @@ VarStruct = '{' (StructMember ',')* StructMember? '}' ;
|
|||||||
VarTuple = TyTuple ;
|
VarTuple = TyTuple ;
|
||||||
VarCLike = '=' INTEGER ;
|
VarCLike = '=' INTEGER ;
|
||||||
|
|
||||||
Alias = "type" Ty ('=' Ty)? ';' ;
|
Alias = "type" Identifier ('=' Ty)? ';' ;
|
||||||
|
|
||||||
Impl = "impl" Path '{' Item* '}' ;
|
Impl = "impl" Path '{' Item* '}' ;
|
||||||
(* TODO: Impl Trait for Target*)
|
(* TODO: Impl Trait for Target*)
|
||||||
|
|
||||||
|
Use = "use" '::'? UseTree ';' ;
|
||||||
|
UseTree = '*' | '{' (UseTree ',')* UseTree? '}'
|
||||||
|
| PathPart ('::' UseTree | "as" Identifier)? ;
|
||||||
|
|
||||||
(* type *)
|
(* type *)
|
||||||
Ty = Never | Empty | Path | TyTuple | TyRef | TyFn ;
|
Ty = Never | Empty | Path | TyArray | TySlice | TyTuple | TyRef | TyFn ;
|
||||||
Never = '!' ;
|
Never = '!' ;
|
||||||
Empty = '(' ')' ;
|
Empty = '(' ')' ;
|
||||||
TyTuple = '(' (Ty ',')* Ty? ')' ;
|
TyTuple = '(' (Ty ',')* Ty? ')' ;
|
||||||
TyRef = ('&' | '&&')* Path ;
|
TyArray = '[' Ty ';' INTEGER ']' ;
|
||||||
TyFn = "fn" TyTuple (-> Ty)? ;
|
TySlice = '[' Ty ']' ;
|
||||||
|
TyRef = Amps* Path ;
|
||||||
|
Amps = '&' | '&&' ;
|
||||||
|
TyFn = "fn" TyTuple ('->' Ty)? ;
|
||||||
|
|
||||||
|
|
||||||
(* path *)
|
(* path *)
|
||||||
Path = '::'? PathPart ('::' PathPart)* ;
|
Path = PathPart ('::' PathPart)*
|
||||||
PathPart = "super" | "self" | Identifier ;
|
| '::' (PathPart ('::' PathPart)*)? ;
|
||||||
|
PathPart = "super" | "self" | "Self" | Identifier ;
|
||||||
Identifier = IDENTIFIER ;
|
Identifier = IDENTIFIER ;
|
||||||
|
|
||||||
|
|
||||||
@@ -74,51 +81,53 @@ Bool = "true" | "false" ;
|
|||||||
|
|
||||||
|
|
||||||
(* expr *)
|
(* expr *)
|
||||||
ExprKind = Assign | Compare | Range | Logic | Bitwise | Shift
|
|
||||||
| Factor | Term | Unary | Member | Call | Index
|
|
||||||
| Path | Literal | Array | ArrayRep | AddrOf
|
|
||||||
| Block | Group
|
|
||||||
| While | If | For | Break | Return | Continue ;
|
|
||||||
|
|
||||||
Expr = Assign ;
|
Expr = Assign ;
|
||||||
|
|
||||||
Assign = Path (AssignKind Assign ) | Compare ;
|
Assign = Path (AssignKind Assign ) | Modify ;
|
||||||
|
Modify = Path (ModifyKind Assign ) | Compare ;
|
||||||
|
|
||||||
Binary = Compare | Range | Logic | Bitwise | Shift | Factor | Term ;
|
(* Binary = Compare | Range | Logic | Bitwise | Shift | Factor | Term ; *)
|
||||||
Compare = Range (CompareOp Range )* ;
|
Compare = Range (CompareOp Range )* ;
|
||||||
Range = Logic (RangeOp Logic )* ;
|
Range = Logic (RangeOp Logic )* ;
|
||||||
Logic = Bitwise (LogicOp Bitwise)* ;
|
Logic = Bitwise (LogicOp Bitwise)* ;
|
||||||
Bitwise = Shift (BitwiseOp Shift )* ;
|
Bitwise = Shift (BitwiseOp Shift )* ;
|
||||||
Shift = Factor (ShiftOp Factor )* ;
|
Shift = Factor (ShiftOp Factor )* ;
|
||||||
Factor = Term (FactorOp Term )* ;
|
Factor = Term (FactorOp Term )* ;
|
||||||
Term = Unary (FactorOp Unary )* ;
|
Term = Unary (TermOp Unary )* ;
|
||||||
|
|
||||||
Unary = (UnaryKind)* Member ;
|
Unary = (UnaryKind)* Cast ;
|
||||||
|
|
||||||
Member = Call ('.' Call)* ;
|
Cast = Member ("as" Ty)? ;
|
||||||
|
|
||||||
|
Member = Call (Access)* ;
|
||||||
|
Access = '.' (Identifier ('(' Tuple? ')')? | Literal) ;
|
||||||
|
|
||||||
Call = Index ('(' Tuple? ')')* ;
|
Call = Index ('(' Tuple? ')')* ;
|
||||||
|
|
||||||
Index = Primary ('[' Indices ']')* ;
|
Index = Primary ('[' Indices ']')* ;
|
||||||
Indices = (Expr ',')* Expr? ;
|
Indices = (Expr ',')* Expr? ;
|
||||||
|
|
||||||
Primary = Literal | Path | Array | ArrayRep | AddrOf
|
Primary = Literal | PathLike | Array | ArrayRep | AddrOf | Block | Group
|
||||||
| Block | Group
|
| Loop | If | While | For | Break | Return | Continue;
|
||||||
| If | While | For | Break | Return | Continue;
|
|
||||||
|
|
||||||
Literal = STRING | CHARACTER | FLOAT | INTEGER | Bool ;
|
Literal = STRING | CHARACTER | FLOAT | INTEGER | Bool ;
|
||||||
|
|
||||||
|
PathLike = Path | Structor ;
|
||||||
|
Structor = Path ':' '{' (Fielder ',')* Fielder? '}' ;
|
||||||
|
Fielder = Identifier ('=' Expr)? ;
|
||||||
|
|
||||||
Array = '[' (Expr ',')* Expr? ']' ;
|
Array = '[' (Expr ',')* Expr? ']' ;
|
||||||
ArrayRep = '[' Expr ';' Expr ']' ;
|
ArrayRep = '[' Expr ';' Expr ']' ;
|
||||||
|
|
||||||
AddrOf = ('&' | '&&')* Mutability? Expr ;
|
AddrOf = Amps Amps* Mutability Expr ;
|
||||||
|
|
||||||
Block = '{' Stmt* '}';
|
Block = '{' Stmt* '}';
|
||||||
|
|
||||||
Group = '(' (Empty | Expr | Tuple) ')' ;
|
Group = Empty | '(' (Expr | Tuple) ')' ;
|
||||||
Tuple = (Expr ',')* Expr? ;
|
Tuple = (Expr ',')* Expr? ;
|
||||||
Empty = ;
|
|
||||||
|
|
||||||
|
Loop = "loop" Block ;
|
||||||
While = "while" Expr Block Else ;
|
While = "while" Expr Block Else ;
|
||||||
If = "if" Expr Block Else ;
|
If = "if" Expr Block Else ;
|
||||||
For = "for" Identifier "in" Expr Block Else ;
|
For = "for" Identifier "in" Expr Block Else ;
|
||||||
@@ -127,11 +136,9 @@ Break = "break" Expr ;
|
|||||||
Return = "return" Expr ;
|
Return = "return" Expr ;
|
||||||
Continue = "continue" ;
|
Continue = "continue" ;
|
||||||
|
|
||||||
AssignKind = '=' | '+=' | '-=' | '*=' | '/=' |
|
AssignKind = '=' ;
|
||||||
'&=' | '|=' | '^=' |'<<=' |'>>=' ;
|
ModifyKind = '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' |'<<=' |'>>=' ;
|
||||||
|
|
||||||
BinaryKind = CompareOp | RangeOp | LogicOp | BitwiseOp
|
|
||||||
| ShiftOp | TermOp | FactorOp ;
|
|
||||||
CompareOp = '<' | '<=' | '==' | '!=' | '>=' | '>' ;
|
CompareOp = '<' | '<=' | '==' | '!=' | '>=' | '>' ;
|
||||||
RangeOp = '..' | '..=' ;
|
RangeOp = '..' | '..=' ;
|
||||||
LogicOp = '&&' | '||' | '^^' ;
|
LogicOp = '&&' | '||' | '^^' ;
|
||||||
|
|||||||
1
libconlang/.gitignore
vendored
1
libconlang/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/target
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "conlang"
|
|
||||||
description = "The Conlang Programming Language"
|
|
||||||
keywords = ["interpreter", "programming", "language"]
|
|
||||||
authors.workspace = true
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
publish.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
unicode-xid = "0.2.4"
|
|
||||||
@@ -1,515 +0,0 @@
|
|||||||
//! # The Abstract Syntax Tree
|
|
||||||
//! Contains definitions of AST Nodes, to be derived by a [parser](super::parser).
|
|
||||||
//!
|
|
||||||
//! # Notable nodes
|
|
||||||
//! - [Item] and [ItemKind]: Top-level constructs
|
|
||||||
//! - [Stmt] and [StmtKind]: Statements
|
|
||||||
//! - [Expr] and [ExprKind]: Expressions
|
|
||||||
//! - [Assign], [Binary], and [Unary] expressions
|
|
||||||
//! - [AssignKind], [BinaryKind], and [UnaryKind] operators
|
|
||||||
//! - [Ty] and [TyKind]: Type qualifiers
|
|
||||||
//! - [Path]: Path expressions
|
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
pub mod ast_impl;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub enum Mutability {
|
|
||||||
#[default]
|
|
||||||
Not,
|
|
||||||
Mut,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub enum Visibility {
|
|
||||||
#[default]
|
|
||||||
Private,
|
|
||||||
Public,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub struct File {
|
|
||||||
pub items: Vec<Item>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata decorators
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Attrs {
|
|
||||||
pub meta: Vec<Meta>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Meta {
|
|
||||||
pub name: Identifier,
|
|
||||||
pub kind: MetaKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum MetaKind {
|
|
||||||
Plain,
|
|
||||||
Equals(Literal),
|
|
||||||
Func(Vec<Literal>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Items
|
|
||||||
/// Stores an [ItemKind] and associated metadata
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Item {
|
|
||||||
pub extents: Span,
|
|
||||||
pub attrs: Attrs,
|
|
||||||
pub vis: Visibility,
|
|
||||||
pub kind: ItemKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a concrete Item
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ItemKind {
|
|
||||||
// TODO: Import declaration ("use") item
|
|
||||||
// TODO: Trait declaration ("trait") item?
|
|
||||||
/// A [type alias](Alias)
|
|
||||||
Alias(Alias),
|
|
||||||
/// A [constant](Const)
|
|
||||||
Const(Const),
|
|
||||||
/// A [static](Static) variable
|
|
||||||
Static(Static),
|
|
||||||
/// A [module](Module)
|
|
||||||
Module(Module),
|
|
||||||
/// A [function definition](Function)
|
|
||||||
Function(Function),
|
|
||||||
/// A [structure](Struct)
|
|
||||||
Struct(Struct),
|
|
||||||
/// An [enumerated type](Enum)
|
|
||||||
Enum(Enum),
|
|
||||||
/// An [implementation](Impl)
|
|
||||||
Impl(Impl),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Alias {
|
|
||||||
pub to: Box<Ty>,
|
|
||||||
pub from: Option<Box<Ty>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a `const` value
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Const {
|
|
||||||
pub name: Identifier,
|
|
||||||
pub ty: Box<Ty>,
|
|
||||||
pub init: Box<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a `static` variable
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Static {
|
|
||||||
pub mutable: Mutability,
|
|
||||||
pub name: Identifier,
|
|
||||||
pub ty: Box<Ty>,
|
|
||||||
pub init: Box<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a collection of [Items](Item)
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Module {
|
|
||||||
pub name: Identifier,
|
|
||||||
pub kind: ModuleKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ModuleKind {
|
|
||||||
Inline(File),
|
|
||||||
Outline,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains code, and the interface to that code
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Function {
|
|
||||||
pub name: Identifier,
|
|
||||||
pub args: Vec<Param>,
|
|
||||||
pub body: Option<Block>,
|
|
||||||
pub rety: Option<Box<Ty>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Param {
|
|
||||||
pub mutability: Mutability,
|
|
||||||
pub name: Identifier,
|
|
||||||
pub ty: Box<Ty>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Struct {
|
|
||||||
pub name: Identifier,
|
|
||||||
pub kind: StructKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum StructKind {
|
|
||||||
Empty,
|
|
||||||
Tuple(Vec<Ty>),
|
|
||||||
Struct(Vec<StructMember>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct StructMember {
|
|
||||||
pub vis: Visibility,
|
|
||||||
pub name: Identifier,
|
|
||||||
pub ty: Ty,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Enum {
|
|
||||||
pub name: Identifier,
|
|
||||||
pub kind: EnumKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum EnumKind {
|
|
||||||
/// Represents an enum with no variants
|
|
||||||
NoVariants,
|
|
||||||
Variants(Vec<Variant>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Variant {
|
|
||||||
pub name: Identifier,
|
|
||||||
pub kind: VariantKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum VariantKind {
|
|
||||||
Plain,
|
|
||||||
CLike(u128),
|
|
||||||
Tuple(Vec<Ty>),
|
|
||||||
Struct(Vec<StructMember>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Impl {
|
|
||||||
pub target: Ty,
|
|
||||||
pub body: Vec<Item>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: `impl` Trait for <Target> { }
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ImplKind {
|
|
||||||
Type(Box<Ty>),
|
|
||||||
Trait { impl_trait: Path, for_type: Box<Ty> },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Static Type Information
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Ty {
|
|
||||||
pub extents: Span,
|
|
||||||
pub kind: TyKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum TyKind {
|
|
||||||
Never,
|
|
||||||
Empty,
|
|
||||||
SelfTy,
|
|
||||||
Path(Path),
|
|
||||||
Tuple(TyTuple),
|
|
||||||
Ref(TyRef),
|
|
||||||
Fn(TyFn),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct TyTuple {
|
|
||||||
pub types: Vec<Ty>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct TyRef {
|
|
||||||
pub count: u16,
|
|
||||||
pub to: Path,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct TyFn {
|
|
||||||
pub args: TyTuple,
|
|
||||||
pub rety: Option<Box<Ty>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Path {
|
|
||||||
pub absolute: bool,
|
|
||||||
pub parts: Vec<PathPart>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum PathPart {
|
|
||||||
SuperKw,
|
|
||||||
SelfKw,
|
|
||||||
Ident(Identifier),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Capture token?
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Identifier(pub String);
|
|
||||||
|
|
||||||
/// Stores an abstract statement, and associated metadata
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Stmt {
|
|
||||||
pub extents: Span,
|
|
||||||
pub kind: StmtKind,
|
|
||||||
pub semi: Semi,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Semi {
|
|
||||||
Terminated,
|
|
||||||
Unterminated,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum StmtKind {
|
|
||||||
Empty,
|
|
||||||
Local(Let),
|
|
||||||
Item(Box<Item>),
|
|
||||||
Expr(Box<Expr>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Let {
|
|
||||||
pub mutable: Mutability,
|
|
||||||
pub name: Identifier,
|
|
||||||
pub ty: Option<Box<Ty>>,
|
|
||||||
pub init: Option<Box<Expr>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores an abstract expression, and associated metadata
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Expr {
|
|
||||||
pub extents: Span,
|
|
||||||
pub kind: ExprKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ExprKind {
|
|
||||||
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
|
|
||||||
Assign(Box<Assign>),
|
|
||||||
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
|
|
||||||
Binary(Binary),
|
|
||||||
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
|
|
||||||
Unary(Unary),
|
|
||||||
/// A [Member] access expression: [`Expr`] (`.` [`Expr`])+
|
|
||||||
Member(Member),
|
|
||||||
/// A [Call] expression, with arguments: a(foo, bar)
|
|
||||||
Call(Call),
|
|
||||||
/// An Array [Index] expression: a[10, 20, 30]
|
|
||||||
Index(Index),
|
|
||||||
/// A [path expression](Path): `::`? [PathPart] (`::` [PathPart])*
|
|
||||||
Path(Path),
|
|
||||||
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
|
|
||||||
Literal(Literal),
|
|
||||||
/// An [Array] literal: `[` [`Expr`] (`,` [`Expr`])\* `]`
|
|
||||||
Array(Array),
|
|
||||||
/// An Array literal constructed with [repeat syntax](ArrayRep)
|
|
||||||
/// `[` [Expr] `;` [Literal] `]`
|
|
||||||
ArrayRep(ArrayRep),
|
|
||||||
/// An address-of expression: `&` `mut`? [`Expr`]
|
|
||||||
AddrOf(AddrOf),
|
|
||||||
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
|
|
||||||
Block(Block),
|
|
||||||
/// An empty expression: `(` `)`
|
|
||||||
Empty,
|
|
||||||
/// A [Grouping](Group) expression `(` [`Expr`] `)`
|
|
||||||
Group(Group),
|
|
||||||
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
|
|
||||||
Tuple(Tuple),
|
|
||||||
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
|
|
||||||
While(While),
|
|
||||||
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
|
|
||||||
If(If),
|
|
||||||
/// A [For] expression: `for` Pattern `in` [`Expr`] [`Block`] [`Else`]?
|
|
||||||
For(For),
|
|
||||||
/// A [Break] expression: `break` [`Expr`]?
|
|
||||||
Break(Break),
|
|
||||||
/// A [Return] expression `return` [`Expr`]?
|
|
||||||
Return(Return),
|
|
||||||
/// A continue expression: `continue`
|
|
||||||
Continue(Continue),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Assign {
|
|
||||||
pub head: Expr,
|
|
||||||
pub op: AssignKind,
|
|
||||||
pub tail: Box<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum AssignKind {
|
|
||||||
/// Standard Assignment with no read-back
|
|
||||||
Plain,
|
|
||||||
And,
|
|
||||||
Or,
|
|
||||||
Xor,
|
|
||||||
Shl,
|
|
||||||
Shr,
|
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Mul,
|
|
||||||
Div,
|
|
||||||
Rem,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Binary {
|
|
||||||
pub head: Box<Expr>,
|
|
||||||
pub tail: Vec<(BinaryKind, Expr)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum BinaryKind {
|
|
||||||
Lt,
|
|
||||||
LtEq,
|
|
||||||
Equal,
|
|
||||||
NotEq,
|
|
||||||
GtEq,
|
|
||||||
Gt,
|
|
||||||
RangeExc,
|
|
||||||
RangeInc,
|
|
||||||
LogAnd,
|
|
||||||
LogOr,
|
|
||||||
LogXor,
|
|
||||||
BitAnd,
|
|
||||||
BitOr,
|
|
||||||
BitXor,
|
|
||||||
Shl,
|
|
||||||
Shr,
|
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Mul,
|
|
||||||
Div,
|
|
||||||
Rem,
|
|
||||||
Dot,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Unary {
|
|
||||||
pub ops: Vec<UnaryKind>,
|
|
||||||
pub tail: Box<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum UnaryKind {
|
|
||||||
Deref,
|
|
||||||
Neg,
|
|
||||||
Not,
|
|
||||||
/// Unused
|
|
||||||
At,
|
|
||||||
/// Unused
|
|
||||||
Tilde,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Member {
|
|
||||||
pub head: Box<Expr>,
|
|
||||||
pub tail: Vec<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum MemberKind {
|
|
||||||
Dot,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Call {
|
|
||||||
pub callee: Box<Expr>,
|
|
||||||
pub args: Vec<Tuple>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Index operator: Member (`[` Expr `]`)*
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Index {
|
|
||||||
pub head: Box<Expr>,
|
|
||||||
pub indices: Vec<Indices>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Indices {
|
|
||||||
pub exprs: Vec<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Literal {
|
|
||||||
Bool(bool),
|
|
||||||
Char(char),
|
|
||||||
Int(u128),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Array {
|
|
||||||
pub values: Vec<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ArrayRep {
|
|
||||||
pub value: Box<Expr>,
|
|
||||||
pub repeat: Box<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct AddrOf {
|
|
||||||
pub count: usize,
|
|
||||||
pub mutable: Mutability,
|
|
||||||
pub expr: Box<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Block {
|
|
||||||
pub stmts: Vec<Stmt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Group {
|
|
||||||
pub expr: Box<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Tuple {
|
|
||||||
pub exprs: Vec<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct While {
|
|
||||||
pub cond: Box<Expr>,
|
|
||||||
pub pass: Box<Block>,
|
|
||||||
pub fail: Else,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct If {
|
|
||||||
pub cond: Box<Expr>,
|
|
||||||
pub pass: Box<Block>,
|
|
||||||
pub fail: Else,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct For {
|
|
||||||
pub bind: Identifier, // TODO: Patterns?
|
|
||||||
pub cond: Box<Expr>,
|
|
||||||
pub pass: Box<Block>,
|
|
||||||
pub fail: Else,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Else {
|
|
||||||
pub body: Option<Box<Expr>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Break {
|
|
||||||
pub body: Option<Box<Expr>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Return {
|
|
||||||
pub body: Option<Box<Expr>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub struct Continue;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
|||||||
//! Conlang is an expression-based programming language with similarities to Rust and Python
|
|
||||||
#![warn(clippy::all)]
|
|
||||||
#![feature(decl_macro)]
|
|
||||||
|
|
||||||
pub mod common;
|
|
||||||
|
|
||||||
pub mod token;
|
|
||||||
|
|
||||||
pub mod ast;
|
|
||||||
|
|
||||||
pub mod lexer;
|
|
||||||
|
|
||||||
pub mod parser;
|
|
||||||
|
|
||||||
pub mod resolver;
|
|
||||||
|
|
||||||
pub mod interpreter;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,916 +0,0 @@
|
|||||||
//! Extremely early WIP of a static type-checker/resolver
|
|
||||||
//!
|
|
||||||
//! This will hopefully become a fully fledged static resolution pass in the future
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use scopeguard::Scoped;
|
|
||||||
pub mod scopeguard {
|
|
||||||
//! Implements a generic RAII scope-guard
|
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
pub trait Scoped: Sized {
|
|
||||||
fn frame(&mut self) -> Guard<Self> {
|
|
||||||
Guard::new(self)
|
|
||||||
}
|
|
||||||
///
|
|
||||||
fn enter_scope(&mut self);
|
|
||||||
fn exit_scope(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Guard<'scope, T: Scoped> {
|
|
||||||
inner: &'scope mut T,
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> Guard<'scope, T> {
|
|
||||||
pub fn new(inner: &'scope mut T) -> Self {
|
|
||||||
inner.enter_scope();
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> Deref for Guard<'scope, T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> DerefMut for Guard<'scope, T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> Drop for Guard<'scope, T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.inner.exit_scope()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prints like [println] if `debug_assertions` are enabled
|
|
||||||
macro debugln($($t:tt)*) {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
println!($($t)*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro debug($($t:tt)*) {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
print!($($t)*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use ty::Type;
|
|
||||||
pub mod ty {
|
|
||||||
//! Describes the type of a [Variable](super::Variable)
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
/// Describes the type of a [Variable](super::Variable)
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Type {
|
|
||||||
#[default]
|
|
||||||
Empty,
|
|
||||||
Int,
|
|
||||||
Bool,
|
|
||||||
Char,
|
|
||||||
String,
|
|
||||||
Float,
|
|
||||||
Fn {
|
|
||||||
args: Vec<Type>,
|
|
||||||
ret: Box<Type>,
|
|
||||||
},
|
|
||||||
Range(Box<Type>),
|
|
||||||
Tuple(Vec<Type>),
|
|
||||||
Never,
|
|
||||||
/// [Inferred](Type::Inferred) is for error messages. DO NOT CONSTRUCT
|
|
||||||
Inferred,
|
|
||||||
Generic(String),
|
|
||||||
/// A function with a single parameter of [Type::ManyInferred]
|
|
||||||
/// is assumed to always be correct.
|
|
||||||
ManyInferred,
|
|
||||||
}
|
|
||||||
impl Type {
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self == &Type::Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for Type {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Type::Empty => "Empty".fmt(f),
|
|
||||||
Type::Int => "integer".fmt(f),
|
|
||||||
Type::Bool => "bool".fmt(f),
|
|
||||||
Type::Char => "char".fmt(f),
|
|
||||||
Type::String => "String".fmt(f),
|
|
||||||
Type::Float => "float".fmt(f),
|
|
||||||
// TODO: clean this up
|
|
||||||
Type::Fn { args, ret } => {
|
|
||||||
"fn (".fmt(f)?;
|
|
||||||
let mut args = args.iter();
|
|
||||||
if let Some(arg) = args.next() {
|
|
||||||
arg.fmt(f)?;
|
|
||||||
}
|
|
||||||
for arg in args {
|
|
||||||
write!(f, ", {arg}")?
|
|
||||||
}
|
|
||||||
")".fmt(f)?;
|
|
||||||
if !ret.is_empty() {
|
|
||||||
write!(f, " -> {ret}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Type::Range(t) => write!(f, "{t}..{t}"),
|
|
||||||
Type::Tuple(t) => {
|
|
||||||
"(".fmt(f)?;
|
|
||||||
for (idx, ty) in t.iter().enumerate() {
|
|
||||||
if idx > 0 {
|
|
||||||
", ".fmt(f)?;
|
|
||||||
}
|
|
||||||
ty.fmt(f)?;
|
|
||||||
}
|
|
||||||
")".fmt(f)
|
|
||||||
}
|
|
||||||
Type::Never => "!".fmt(f),
|
|
||||||
Type::Inferred => "_".fmt(f),
|
|
||||||
Type::Generic(name) => write!(f, "<{name}>"),
|
|
||||||
Type::ManyInferred => "..".fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the life-cycle of a [Variable]: Whether it's bound, typed, or initialized
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Status {
|
|
||||||
#[default]
|
|
||||||
Bound,
|
|
||||||
Uninitialized(Type),
|
|
||||||
Initialized(Type),
|
|
||||||
}
|
|
||||||
impl Status {
|
|
||||||
/// Performs type-checking for a [Variable] assignment
|
|
||||||
pub fn assign(&mut self, ty: &Type) -> TyResult<()> {
|
|
||||||
match self {
|
|
||||||
// Variable uninitialized: initialize it
|
|
||||||
Status::Bound => {
|
|
||||||
*self = Status::Initialized(ty.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Typecheck ok! Reuse the allocation for t
|
|
||||||
Status::Uninitialized(t) if t == ty => {
|
|
||||||
*self = Status::Initialized(std::mem::take(t));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Status::Initialized(t) if t == ty => Ok(()),
|
|
||||||
// Typecheck not ok.
|
|
||||||
Status::Uninitialized(e) | Status::Initialized(e) => {
|
|
||||||
Err(Error::TypeMismatch { want: ty.clone(), got: e.clone() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct Variable {
|
|
||||||
/// The unique, global index of this variable
|
|
||||||
pub index: usize,
|
|
||||||
/// The [Status] of this variable
|
|
||||||
pub status: Status,
|
|
||||||
/// The mutability qualifier of this variable
|
|
||||||
pub mutable: bool,
|
|
||||||
}
|
|
||||||
impl Variable {
|
|
||||||
/// Constructs a new variable with the provided index and mutability
|
|
||||||
pub fn new(index: usize, mutable: bool) -> Self {
|
|
||||||
Self { index, mutable, ..Default::default() }
|
|
||||||
}
|
|
||||||
/// Performs a type-checked assignment on self
|
|
||||||
pub fn assign(&mut self, name: &str, ty: &Type) -> TyResult<()> {
|
|
||||||
let Variable { index, status, mutable } = self;
|
|
||||||
debug!("Typecheck for {name} #{index}: ");
|
|
||||||
let out = match (status, mutable) {
|
|
||||||
// Variable uninitialized: initialize it
|
|
||||||
(Status::Bound, _) => {
|
|
||||||
self.status = Status::Initialized(ty.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Typecheck ok! Reuse the allocation for t
|
|
||||||
(Status::Uninitialized(t), _) if t == ty => {
|
|
||||||
self.status = Status::Initialized(std::mem::take(t));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Reassignment of mutable variable is ok
|
|
||||||
(Status::Initialized(t), true) if t == ty => Ok(()),
|
|
||||||
// Reassignment of immutable variable is not ok
|
|
||||||
(Status::Initialized(_), false) => Err(Error::ImmutableAssign(name.into(), *index)),
|
|
||||||
// Typecheck not ok.
|
|
||||||
(Status::Uninitialized(e) | Status::Initialized(e), _) => {
|
|
||||||
Err(Error::TypeMismatch { want: ty.clone(), got: e.clone() })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match &out {
|
|
||||||
Ok(_) => debugln!("Ok! ({ty})"),
|
|
||||||
Err(e) => debugln!("Error: {e:?}"),
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
/// Performs the type-checking for a modifying assignment
|
|
||||||
pub fn modify_assign(&self, name: &str, ty: &Type) -> TyResult<()> {
|
|
||||||
let Variable { index, status, mutable } = &self;
|
|
||||||
match (status, mutable) {
|
|
||||||
(Status::Initialized(t), true) if t == ty => Ok(()),
|
|
||||||
(Status::Initialized(t), true) => {
|
|
||||||
Err(Error::TypeMismatch { want: t.clone(), got: ty.clone() })
|
|
||||||
}
|
|
||||||
(Status::Initialized(_), false) => Err(Error::ImmutableAssign(name.into(), *index)),
|
|
||||||
(..) => Err(Error::Uninitialized(name.into(), *index)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
THE BIG IDEA:
|
|
||||||
- Each `let` statement binds a *different* variable.
|
|
||||||
- Shadowing is a FEATURE
|
|
||||||
- Traversing the tree before program launch allows the Resolver to assign
|
|
||||||
an index to each variable usage in the scope-tree
|
|
||||||
- These indices allow constant-time variable lookup in the interpreter!!!
|
|
||||||
- The added type-checking means fewer type errors!
|
|
||||||
|
|
||||||
REQUIREMENTS FOR FULL TYPE-CHECKING:
|
|
||||||
- Meaningful type expressions in function declarations
|
|
||||||
|
|
||||||
NECESSARY CONSIDERATIONS:
|
|
||||||
- Variable binding happens AFTER the initialization expression is run.
|
|
||||||
- If a variable is *entirely* unbound before being referenced,
|
|
||||||
it'll still error.
|
|
||||||
- This is *intentional*, and ALLOWS shadowing previous variables.
|
|
||||||
- In my experience, this is almost NEVER an error :P
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Scope {
|
|
||||||
/// A monotonically increasing counter of variable declarations
|
|
||||||
count: usize,
|
|
||||||
/// A dictionary keeping track of type and lifetime information
|
|
||||||
vars: HashMap<String, Variable>,
|
|
||||||
}
|
|
||||||
impl Scope {
|
|
||||||
/// Bind a [Variable] in Scope
|
|
||||||
pub fn insert(&mut self, name: &str, index: usize, mutable: bool) {
|
|
||||||
self.count += 1;
|
|
||||||
self.vars
|
|
||||||
.insert(name.to_string(), Variable::new(index, mutable));
|
|
||||||
}
|
|
||||||
/// Returns a reference to a [Variable], if `name` is bound
|
|
||||||
pub fn get(&self, name: &str) -> Option<&Variable> {
|
|
||||||
self.vars.get(name)
|
|
||||||
}
|
|
||||||
/// Returns a mutable reference to a [Variable], if `name` is bound
|
|
||||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut Variable> {
|
|
||||||
self.vars.get_mut(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Implements a dynamically scoped namespace
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Module {
|
|
||||||
modules: HashMap<String, Module>,
|
|
||||||
vars: HashMap<String, Variable>,
|
|
||||||
}
|
|
||||||
impl Module {
|
|
||||||
pub fn insert_var(&mut self, name: &str, index: usize, mutable: bool) -> TyResult<()> {
|
|
||||||
if self
|
|
||||||
.vars
|
|
||||||
.insert(name.into(), Variable::new(index, mutable))
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
Err(Error::NonUniqueInModule(name.into()))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn insert_module(&mut self, name: String, module: Module) -> TyResult<()> {
|
|
||||||
if self.modules.insert(name.clone(), module).is_some() {
|
|
||||||
Err(Error::NonUniqueInModule(name + "(module)"))?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Returns a reference to a [Variable] in this Module, if `name` is bound
|
|
||||||
pub fn get(&self, name: &str) -> Option<&Variable> {
|
|
||||||
self.vars.get(name)
|
|
||||||
}
|
|
||||||
/// Returns a mutable reference to a [Variable] in this Module, if `name` is bound
|
|
||||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut Variable> {
|
|
||||||
self.vars.get_mut(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_get(&self, name: &str, path: &[String]) -> Option<&Variable> {
|
|
||||||
if path.is_empty() {
|
|
||||||
return self.get(name);
|
|
||||||
}
|
|
||||||
let module = self.modules.get(&path[0])?;
|
|
||||||
module
|
|
||||||
.resolve_get(name, &path[1..])
|
|
||||||
.or_else(|| self.get(name))
|
|
||||||
}
|
|
||||||
// Returns a reference to the module at a specified path
|
|
||||||
pub fn resolve(&self, path: &[String]) -> TyResult<&Module> {
|
|
||||||
if path.is_empty() {
|
|
||||||
return Ok(self);
|
|
||||||
}
|
|
||||||
let module = self
|
|
||||||
.modules
|
|
||||||
.get(&path[0])
|
|
||||||
.ok_or_else(|| Error::Unbound(path[0].clone()))?;
|
|
||||||
module.resolve(&path[1..])
|
|
||||||
}
|
|
||||||
/// Returns a mutable reference to a Module if one is bound
|
|
||||||
pub fn resolve_mut(&mut self, path: &[String]) -> TyResult<&mut Module> {
|
|
||||||
if path.is_empty() {
|
|
||||||
return Ok(self);
|
|
||||||
}
|
|
||||||
let module = self
|
|
||||||
.modules
|
|
||||||
.get_mut(&path[0])
|
|
||||||
.ok_or_else(|| Error::Unbound(path[0].clone()))?;
|
|
||||||
module.resolve_mut(&path[1..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Resolver {
|
|
||||||
/// A monotonically increasing counter of variable declarations
|
|
||||||
count: usize,
|
|
||||||
/// A stack of nested scopes *inside* a function
|
|
||||||
scopes: Vec<Scope>,
|
|
||||||
/// A stack of nested scopes *outside* a function
|
|
||||||
// TODO: Record the name of the module, and keep a stack of the current module
|
|
||||||
// for name resolution
|
|
||||||
modules: Module,
|
|
||||||
/// Describes the current path
|
|
||||||
module: Vec<String>,
|
|
||||||
/// A stack of types
|
|
||||||
types: Vec<Type>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scoped for Resolver {
|
|
||||||
fn enter_scope(&mut self) {
|
|
||||||
self.enter_scope();
|
|
||||||
}
|
|
||||||
fn exit_scope(&mut self) {
|
|
||||||
self.exit_scope();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Resolver {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut new = Self {
|
|
||||||
count: Default::default(),
|
|
||||||
scopes: vec![Default::default()],
|
|
||||||
modules: Default::default(),
|
|
||||||
module: Default::default(),
|
|
||||||
types: Default::default(),
|
|
||||||
};
|
|
||||||
new.register_builtin("print", &[], &[Type::ManyInferred], Type::Empty)
|
|
||||||
.expect("print should not be bound in new Resolver");
|
|
||||||
new
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolver {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
/// Register a built-in function into the top-level module
|
|
||||||
pub fn register_builtin(
|
|
||||||
&mut self,
|
|
||||||
name: &str,
|
|
||||||
path: &[String],
|
|
||||||
args: &[Type],
|
|
||||||
ret: Type,
|
|
||||||
) -> TyResult<()> {
|
|
||||||
let module = self.modules.resolve_mut(path)?;
|
|
||||||
module.vars.insert(
|
|
||||||
name.into(),
|
|
||||||
Variable {
|
|
||||||
index: 0,
|
|
||||||
status: Status::Initialized(Type::Fn { args: args.into(), ret: ret.into() }),
|
|
||||||
mutable: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Enters a Module Scope
|
|
||||||
pub fn enter_module(&mut self, name: &str) -> TyResult<()> {
|
|
||||||
let module = self.modules.resolve_mut(&self.module)?;
|
|
||||||
module.insert_module(name.into(), Default::default())?;
|
|
||||||
self.module.push(name.into());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Exits a Module Scope
|
|
||||||
pub fn exit_module(&mut self) -> Option<String> {
|
|
||||||
// Modules stay registered
|
|
||||||
self.module.pop()
|
|
||||||
}
|
|
||||||
/// Enters a Block Scope
|
|
||||||
pub fn enter_scope(&mut self) {
|
|
||||||
self.scopes.push(Default::default());
|
|
||||||
}
|
|
||||||
/// Exits a Block Scope, returning the value
|
|
||||||
pub fn exit_scope(&mut self) -> Option<usize> {
|
|
||||||
self.scopes.pop().map(|scope| scope.count)
|
|
||||||
}
|
|
||||||
/// Resolves a name to a [Variable]
|
|
||||||
pub fn get(&self, name: &str) -> TyResult<&Variable> {
|
|
||||||
if let Some(var) = self.scopes.iter().rev().find_map(|s| s.get(name)) {
|
|
||||||
return Ok(var);
|
|
||||||
}
|
|
||||||
self.modules
|
|
||||||
.resolve_get(name, &self.module)
|
|
||||||
.ok_or_else(|| Error::Unbound(name.into()))
|
|
||||||
}
|
|
||||||
/// Mutably resolves a name to a [Variable]
|
|
||||||
pub fn get_mut(&mut self, name: &str) -> TyResult<&mut Variable> {
|
|
||||||
if let Some(var) = self.scopes.iter_mut().rev().find_map(|s| s.get_mut(name)) {
|
|
||||||
return Ok(var);
|
|
||||||
}
|
|
||||||
self.modules
|
|
||||||
.resolve_mut(&self.module)?
|
|
||||||
.get_mut(name)
|
|
||||||
.ok_or_else(|| Error::Unbound(name.into()))
|
|
||||||
}
|
|
||||||
/// Binds a name in the current lexical scope
|
|
||||||
pub fn insert_scope(&mut self, name: &str, mutable: bool) -> TyResult<usize> {
|
|
||||||
self.count += 1;
|
|
||||||
self.scopes
|
|
||||||
.last_mut()
|
|
||||||
.ok_or_else(|| panic!("Stack underflow in resolver?"))?
|
|
||||||
.insert(name, self.count, mutable);
|
|
||||||
Ok(self.count)
|
|
||||||
}
|
|
||||||
/// Binds a name in the current module
|
|
||||||
pub fn insert_module(&mut self, name: &str, mutable: bool) -> TyResult<usize> {
|
|
||||||
self.count += 1;
|
|
||||||
self.modules
|
|
||||||
.resolve_mut(&self.module)?
|
|
||||||
.insert_var(name, self.count, mutable)?;
|
|
||||||
Ok(self.count)
|
|
||||||
}
|
|
||||||
/// Performs scoped type-checking of variables
|
|
||||||
pub fn assign(&mut self, name: &str, ty: &Type) -> TyResult<()> {
|
|
||||||
self.get_mut(name)?.assign(name, ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
/// Manages a module scope
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// macro module(self, name: &str, inner: {...}) -> Result<_, Error>
|
|
||||||
/// ```
|
|
||||||
macro module($self:ident, $name:tt, $inner:tt) {{
|
|
||||||
$self.enter_module($name)?;
|
|
||||||
#[allow(clippy::redundant_closure_call)]
|
|
||||||
let scope = (|| $inner)(); // This is pretty gross, but hey, try {} syntax is unstable too
|
|
||||||
$self.exit_module();
|
|
||||||
scope
|
|
||||||
}}
|
|
||||||
|
|
||||||
impl Resolver {
|
|
||||||
pub fn visit_empty(&mut self) -> TyResult<()> {
|
|
||||||
debugln!("Got Empty");
|
|
||||||
self.types.push(Type::Empty);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Resolve {
|
|
||||||
/// Performs variable resolution on self, and returns the type of self.
|
|
||||||
///
|
|
||||||
/// For expressions, this is the type of the expression.
|
|
||||||
///
|
|
||||||
/// For declarations, this is Empty.
|
|
||||||
fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
Ok(Type::Empty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod ast1 {
|
|
||||||
// #![allow(deprecated)]
|
|
||||||
// use super::*;
|
|
||||||
// use crate::ast::preamble::*;
|
|
||||||
// impl Resolve for Start {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Self(program) = self;
|
|
||||||
// program.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Program {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Self(module) = self;
|
|
||||||
// for decl in module {
|
|
||||||
// decl.resolve(resolver)?;
|
|
||||||
// }
|
|
||||||
// // TODO: record the number of module-level assignments into the AST
|
|
||||||
// Ok(Type::Empty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Stmt {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Stmt::Let(value) => value.resolve(resolver),
|
|
||||||
// Stmt::Fn(value) => value.resolve(resolver),
|
|
||||||
// Stmt::Expr(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Let {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Let { name: Name { symbol: Identifier { name, index }, mutable, ty: _ }, init } =
|
|
||||||
// self;
|
|
||||||
// debugln!("ty> let {name} ...");
|
|
||||||
// if let Some(init) = init {
|
|
||||||
// let ty = init.resolve(resolver)?;
|
|
||||||
// *index = Some(resolver.insert_scope(name, *mutable)?);
|
|
||||||
// resolver.get_mut(name)?.assign(name, &ty)?;
|
|
||||||
// } else {
|
|
||||||
// resolver.insert_scope(name, *mutable)?;
|
|
||||||
// }
|
|
||||||
// Ok(Type::Empty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for FnDecl {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let FnDecl { name: Name { symbol: Identifier { name, index }, .. }, args, body } =
|
|
||||||
// self; debugln!("ty> fn {name} ...");
|
|
||||||
// // register the name at module scope
|
|
||||||
// *index = Some(resolver.insert_module(name, false)?);
|
|
||||||
// // create a new lexical scope
|
|
||||||
// let scopes = std::mem::take(&mut resolver.scopes);
|
|
||||||
// // type-check the function body
|
|
||||||
// let out = {
|
|
||||||
// let mut resolver = resolver.frame();
|
|
||||||
// let mut evaluated_args = vec![];
|
|
||||||
// for arg in args {
|
|
||||||
// evaluated_args.push(arg.resolve(&mut resolver)?)
|
|
||||||
// }
|
|
||||||
// let fn_decl = Type::Fn { args: evaluated_args.clone(), ret: Box::new(Type::Empty)
|
|
||||||
// }; resolver.get_mut(name)?.assign(name, &fn_decl)?;
|
|
||||||
// module!(resolver, name, { body.resolve(&mut resolver) })
|
|
||||||
// };
|
|
||||||
// let _ = std::mem::replace(&mut resolver.scopes, scopes);
|
|
||||||
// out
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Name {
|
|
||||||
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// Ok(Type::Empty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Block {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Block { let_count: _, statements, expr } = self;
|
|
||||||
// let mut resolver = resolver.frame();
|
|
||||||
// for stmt in statements {
|
|
||||||
// stmt.resolve(&mut resolver)?;
|
|
||||||
// }
|
|
||||||
// expr.resolve(&mut resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Expr {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Expr(expr) = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Operation {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Operation::Assign(value) => value.resolve(resolver),
|
|
||||||
// Operation::Binary(value) => value.resolve(resolver),
|
|
||||||
// Operation::Unary(value) => value.resolve(resolver),
|
|
||||||
// Operation::Call(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Assign {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Assign { target, operator, init } = self;
|
|
||||||
// // Evaluate the initializer expression
|
|
||||||
// let ty = init.resolve(resolver)?;
|
|
||||||
// // Resolve the variable
|
|
||||||
// match (operator, resolver.get_mut(&target.name)?) {
|
|
||||||
// (
|
|
||||||
// operator::Assign::Assign,
|
|
||||||
// Variable { status: Status::Initialized(_), mutable: false, index },
|
|
||||||
// ) => Err(Error::ImmutableAssign(target.name.clone(), *index)),
|
|
||||||
// // TODO: make typing more expressive for modifying assignment
|
|
||||||
// (_, variable) => variable
|
|
||||||
// .modify_assign(&target.name, &ty)
|
|
||||||
// .map(|_| Type::Empty),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Binary {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Binary { first, other } = self;
|
|
||||||
|
|
||||||
// let mut first = first.resolve(resolver)?;
|
|
||||||
// for (op, other) in other {
|
|
||||||
// let other = other.resolve(resolver)?;
|
|
||||||
// first = resolver.resolve_binary_operator(first, other, op)?;
|
|
||||||
// }
|
|
||||||
// Ok(first)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Unary {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Unary { operators, operand } = self;
|
|
||||||
// let mut operand = operand.resolve(resolver)?;
|
|
||||||
// for op in operators {
|
|
||||||
// operand = resolver.resolve_unary_operator(operand, op)?;
|
|
||||||
// }
|
|
||||||
// Ok(operand)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// /// Resolve [operator]s
|
|
||||||
// impl Resolver {
|
|
||||||
// fn resolve_binary_operator(
|
|
||||||
// &mut self,
|
|
||||||
// first: Type,
|
|
||||||
// other: Type,
|
|
||||||
// op: &operator::Binary,
|
|
||||||
// ) -> TyResult<Type> {
|
|
||||||
// // TODO: check type compatibility for binary ops
|
|
||||||
// // TODO: desugar binary ops into function calls, when member functions are a thing
|
|
||||||
// eprintln!("Resolve binary operators {first} {op:?} {other}");
|
|
||||||
// if first != other {
|
|
||||||
// Err(Error::TypeMismatch { want: first, got: other })
|
|
||||||
// } else {
|
|
||||||
// Ok(first)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// fn resolve_unary_operator(
|
|
||||||
// &mut self,
|
|
||||||
// operand: Type,
|
|
||||||
// op: &operator::Unary,
|
|
||||||
// ) -> TyResult<Type> {
|
|
||||||
// // TODO: Allow more expressive unary operator type conversions
|
|
||||||
// todo!("Resolve unary operators {op:?} {operand}")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Call {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Call::FnCall(value) => value.resolve(resolver),
|
|
||||||
// Call::Primary(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for FnCall {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let FnCall { callee, args } = self;
|
|
||||||
// let mut callee = callee.resolve(resolver)?;
|
|
||||||
// for argset in args {
|
|
||||||
// // arguments should always be a tuple here
|
|
||||||
// let arguments = argset.resolve(resolver)?;
|
|
||||||
// let Type::Tuple(arguments) = arguments else {
|
|
||||||
// Err(Error::TypeMismatch {
|
|
||||||
// want: Type::Tuple(vec![Type::ManyInferred]),
|
|
||||||
// got: arguments,
|
|
||||||
// })?
|
|
||||||
// };
|
|
||||||
// // Verify that the callee is a function, and the arguments match.
|
|
||||||
// // We need the arguments
|
|
||||||
// let Type::Fn { args, ret } = callee else {
|
|
||||||
// return Err(Error::TypeMismatch {
|
|
||||||
// want: Type::Fn { args: arguments, ret: Type::Inferred.into() },
|
|
||||||
// got: callee,
|
|
||||||
// })?;
|
|
||||||
// };
|
|
||||||
// for (want, got) in args.iter().zip(&arguments) {
|
|
||||||
// // TODO: verify generics
|
|
||||||
// if let Type::Generic(_) = want {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// if want != got {
|
|
||||||
// return Err(Error::TypeMismatch {
|
|
||||||
// want: Type::Fn { args: arguments, ret: Type::Inferred.into() },
|
|
||||||
// got: Type::Fn { args, ret },
|
|
||||||
// })?;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// callee = *ret;
|
|
||||||
// }
|
|
||||||
// Ok(callee)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Primary {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Primary::Identifier(value) => value.resolve(resolver),
|
|
||||||
// Primary::Literal(value) => value.resolve(resolver),
|
|
||||||
// Primary::Block(value) => value.resolve(resolver),
|
|
||||||
// Primary::Group(value) => value.resolve(resolver),
|
|
||||||
// Primary::Branch(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Group {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Group::Tuple(tuple) => tuple.resolve(resolver),
|
|
||||||
// Group::Single(expr) => expr.resolve(resolver),
|
|
||||||
// Group::Empty => Ok(Type::Empty),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Tuple {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Tuple { elements } = self;
|
|
||||||
// let mut types = vec![];
|
|
||||||
// for expr in elements.iter_mut() {
|
|
||||||
// types.push(expr.resolve(resolver)?);
|
|
||||||
// }
|
|
||||||
// Ok(Type::Tuple(types))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Identifier {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Identifier { name, index: id_index } = self;
|
|
||||||
// let Variable { index, status, .. } = resolver.get(name)?;
|
|
||||||
// *id_index = Some(*index);
|
|
||||||
// let ty = match status {
|
|
||||||
// Status::Initialized(t) => t,
|
|
||||||
// _ => Err(Error::Uninitialized(name.to_owned(), *index))?,
|
|
||||||
// };
|
|
||||||
// debugln!("ty> Resolved {} #{index}: {ty}", name);
|
|
||||||
// Ok(ty.to_owned())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Literal {
|
|
||||||
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// Ok(match self {
|
|
||||||
// Literal::String(_) => Type::String,
|
|
||||||
// Literal::Char(_) => Type::Char,
|
|
||||||
// Literal::Bool(_) => Type::Bool,
|
|
||||||
// Literal::Float(_) => Type::Float,
|
|
||||||
// Literal::Int(_) => Type::Int,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Flow {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish this
|
|
||||||
// match self {
|
|
||||||
// Flow::While(value) => value.resolve(resolver),
|
|
||||||
// Flow::If(value) => value.resolve(resolver),
|
|
||||||
// Flow::For(value) => value.resolve(resolver),
|
|
||||||
// Flow::Continue(value) => value.resolve(resolver),
|
|
||||||
// Flow::Return(value) => value.resolve(resolver),
|
|
||||||
// Flow::Break(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for While {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish this
|
|
||||||
// // Visit else first, save that to a break-pattern stack in the Resolver,
|
|
||||||
// // and check it inside Break::resolve()
|
|
||||||
// let While { cond, body, else_ } = self;
|
|
||||||
// cond.resolve(resolver)?; // must be Type::Bool
|
|
||||||
// body.resolve(resolver)?; // discard
|
|
||||||
// else_.resolve(resolver) // compare with returns inside body
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for If {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let If { cond, body, else_ } = self;
|
|
||||||
// let cond = cond.resolve(resolver)?;
|
|
||||||
// if Type::Bool != cond {
|
|
||||||
// return Err(Error::TypeMismatch { want: Type::Bool, got: cond });
|
|
||||||
// }
|
|
||||||
// let body_ty = body.resolve(resolver)?;
|
|
||||||
// let else_ty = else_.resolve(resolver)?;
|
|
||||||
// if body_ty == else_ty {
|
|
||||||
// Ok(body_ty)
|
|
||||||
// } else {
|
|
||||||
// Err(Error::TypeMismatch { want: body_ty, got: else_ty })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for For {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let For { var: Identifier { name, index }, iter, body, else_ } = self;
|
|
||||||
// debugln!("> for {name} in ...");
|
|
||||||
// // Visit the iter expression and get its type
|
|
||||||
// let range = iter.resolve(resolver)?;
|
|
||||||
// let ty = match range {
|
|
||||||
// Type::Range(t) => t,
|
|
||||||
// got => Err(Error::TypeMismatch { want: Type::Range(Type::Inferred.into()), got
|
|
||||||
// })?, };
|
|
||||||
// let body_ty = {
|
|
||||||
// let mut resolver = resolver.frame();
|
|
||||||
// // bind the variable in the loop scope
|
|
||||||
// *index = Some(resolver.insert_scope(name, false)?);
|
|
||||||
// resolver.get_mut(name)?.assign(name, &ty)?;
|
|
||||||
// body.resolve(&mut resolver)
|
|
||||||
// }?;
|
|
||||||
// // visit the else block
|
|
||||||
// let else_ty = else_.resolve(resolver)?;
|
|
||||||
// if body_ty != else_ty {
|
|
||||||
// Err(Error::TypeMismatch { want: body_ty, got: else_ty })
|
|
||||||
// } else {
|
|
||||||
// Ok(body_ty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Else {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Else { expr } = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Continue {
|
|
||||||
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish control flow
|
|
||||||
// Ok(Type::Never)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Break {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish control flow
|
|
||||||
// let Break { expr } = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Return {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish control flow
|
|
||||||
// let Return { expr } = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
mod ast {
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
use crate::ast::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
// heakc yea man, generics
|
|
||||||
impl<T: Resolve> Resolve for Option<T> {
|
|
||||||
fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
match self {
|
|
||||||
Some(t) => t.resolve(resolver),
|
|
||||||
None => Ok(Type::Empty),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Resolve> Resolve for Box<T> {
|
|
||||||
fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
self.as_mut().resolve(resolver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use error::{Error, TyResult};
|
|
||||||
pub mod error {
|
|
||||||
use super::Type;
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
pub type TyResult<T> = Result<T, Error>;
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
StackUnderflow,
|
|
||||||
// types
|
|
||||||
TypeMismatch { want: Type, got: Type },
|
|
||||||
// modules
|
|
||||||
NonUniqueInModule(String),
|
|
||||||
// lifetimes
|
|
||||||
Uninitialized(String, usize),
|
|
||||||
ImmutableAssign(String, usize),
|
|
||||||
Unbound(String),
|
|
||||||
}
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::StackUnderflow => "Stack underflow in Resolver".fmt(f),
|
|
||||||
Error::TypeMismatch { want, got } => {
|
|
||||||
write!(f, "Type error: {want} != {got}")
|
|
||||||
}
|
|
||||||
Error::ImmutableAssign(name, index) => {
|
|
||||||
write!(f, "Cannot mutate immutable variable {name}(#{index})")
|
|
||||||
}
|
|
||||||
Error::Uninitialized(name, index) => {
|
|
||||||
write!(f, "{name}(#{index}) was accessed before initialization")
|
|
||||||
}
|
|
||||||
Error::Unbound(name) => write!(f, "{name} not bound before use."),
|
|
||||||
Error::NonUniqueInModule(name) => {
|
|
||||||
write!(f, "Name {name} not unique at module scope!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
mod token {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
mod ast {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
mod lexer {
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use crate::{lexer::Lexer, token::preamble::*};
|
|
||||||
|
|
||||||
macro test_lexer_output_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
|
||||||
#[test]
|
|
||||||
fn $f() {$(
|
|
||||||
assert_eq!(
|
|
||||||
Lexer::new($test)
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.unwrap().ty())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
dbg!($expect)
|
|
||||||
);
|
|
||||||
)*}
|
|
||||||
)*}
|
|
||||||
|
|
||||||
macro test_lexer_data_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
|
||||||
#[test]
|
|
||||||
fn $f() {$(
|
|
||||||
assert_eq!(
|
|
||||||
Lexer::new($test)
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.unwrap().into_data())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
dbg!($expect)
|
|
||||||
);
|
|
||||||
)*}
|
|
||||||
)*}
|
|
||||||
|
|
||||||
/// Convert an `[ expr, ... ]` into a `[ *, ... ]`
|
|
||||||
macro td ($($id:expr),*) {
|
|
||||||
[$($id.into()),*]
|
|
||||||
}
|
|
||||||
|
|
||||||
mod ident {
|
|
||||||
use super::*;
|
|
||||||
macro ident ($($id:literal),*) {
|
|
||||||
[$(Data::Identifier($id.into())),*]
|
|
||||||
}
|
|
||||||
test_lexer_data_type! {
|
|
||||||
underscore { "_ _" => ident!["_", "_"] }
|
|
||||||
unicode { "_ε ε_" => ident!["_ε", "ε_"] }
|
|
||||||
many_underscore { "____________________________________" =>
|
|
||||||
ident!["____________________________________"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod keyword {
|
|
||||||
use super::*;
|
|
||||||
macro kw($($k:ident),*) {
|
|
||||||
[ $(Type::Keyword(Keyword::$k),)* ]
|
|
||||||
}
|
|
||||||
test_lexer_output_type! {
|
|
||||||
kw_break { "break break" => kw![Break, Break] }
|
|
||||||
kw_continue { "continue continue" => kw![Continue, Continue] }
|
|
||||||
kw_else { "else else" => kw![Else, Else] }
|
|
||||||
kw_false { "false false" => kw![False, False] }
|
|
||||||
kw_for { "for for" => kw![For, For] }
|
|
||||||
kw_fn { "fn fn" => kw![Fn, Fn] }
|
|
||||||
kw_if { "if if" => kw![If, If] }
|
|
||||||
kw_in { "in in" => kw![In, In] }
|
|
||||||
kw_let { "let let" => kw![Let, Let] }
|
|
||||||
kw_return { "return return" => kw![Return, Return] }
|
|
||||||
kw_true { "true true" => kw![True, True] }
|
|
||||||
kw_while { "while while" => kw![While, While] }
|
|
||||||
keywords { "break continue else false for fn if in let return true while" =>
|
|
||||||
kw![Break, Continue, Else, False, For, Fn, If, In, Let, Return, True, While] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod integer {
|
|
||||||
use super::*;
|
|
||||||
test_lexer_data_type! {
|
|
||||||
hex {
|
|
||||||
"0x0 0x1 0x15 0x2100 0x8000" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
dec {
|
|
||||||
"0d0 0d1 0d21 0d8448 0d32768" =>
|
|
||||||
td![0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
oct {
|
|
||||||
"0o0 0o1 0o25 0o20400 0o100000" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
bin {
|
|
||||||
"0b0 0b1 0b10101 0b10000100000000 0b1000000000000000" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
baseless {
|
|
||||||
"0 1 21 8448 32768" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod string {
|
|
||||||
use super::*;
|
|
||||||
test_lexer_data_type! {
|
|
||||||
empty_string {
|
|
||||||
"\"\"" =>
|
|
||||||
td![String::from("")]
|
|
||||||
}
|
|
||||||
unicode_string {
|
|
||||||
"\"I 💙 🦈!\"" =>
|
|
||||||
td![String::from("I 💙 🦈!")]
|
|
||||||
}
|
|
||||||
escape_string {
|
|
||||||
" \"This is a shark: \\u{1f988}\" " =>
|
|
||||||
td![String::from("This is a shark: 🦈")]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod punct {
|
|
||||||
use super::*;
|
|
||||||
test_lexer_output_type! {
|
|
||||||
l_curly { "{ {" => [ Type::LCurly, Type::LCurly ] }
|
|
||||||
r_curly { "} }" => [ Type::RCurly, Type::RCurly ] }
|
|
||||||
l_brack { "[ [" => [ Type::LBrack, Type::LBrack ] }
|
|
||||||
r_brack { "] ]" => [ Type::RBrack, Type::RBrack ] }
|
|
||||||
l_paren { "( (" => [ Type::LParen, Type::LParen ] }
|
|
||||||
r_paren { ") )" => [ Type::RParen, Type::RParen ] }
|
|
||||||
amp { "& &" => [ Type::Amp, Type::Amp ] }
|
|
||||||
amp_amp { "&& &&" => [ Type::AmpAmp, Type::AmpAmp ] }
|
|
||||||
amp_eq { "&= &=" => [ Type::AmpEq, Type::AmpEq ] }
|
|
||||||
arrow { "-> ->" => [ Type::Arrow, Type::Arrow] }
|
|
||||||
at { "@ @" => [ Type::At, Type::At] }
|
|
||||||
backslash { "\\ \\" => [ Type::Backslash, Type::Backslash] }
|
|
||||||
bang { "! !" => [ Type::Bang, Type::Bang] }
|
|
||||||
bangbang { "!! !!" => [ Type::BangBang, Type::BangBang] }
|
|
||||||
bangeq { "!= !=" => [ Type::BangEq, Type::BangEq] }
|
|
||||||
bar { "| |" => [ Type::Bar, Type::Bar] }
|
|
||||||
barbar { "|| ||" => [ Type::BarBar, Type::BarBar] }
|
|
||||||
bareq { "|= |=" => [ Type::BarEq, Type::BarEq] }
|
|
||||||
colon { ": :" => [ Type::Colon, Type::Colon] }
|
|
||||||
comma { ", ," => [ Type::Comma, Type::Comma] }
|
|
||||||
dot { ". ." => [ Type::Dot, Type::Dot] }
|
|
||||||
dotdot { ".. .." => [ Type::DotDot, Type::DotDot] }
|
|
||||||
dotdoteq { "..= ..=" => [ Type::DotDotEq, Type::DotDotEq] }
|
|
||||||
eq { "= =" => [ Type::Eq, Type::Eq] }
|
|
||||||
eqeq { "== ==" => [ Type::EqEq, Type::EqEq] }
|
|
||||||
fatarrow { "=> =>" => [ Type::FatArrow, Type::FatArrow] }
|
|
||||||
grave { "` `" => [ Type::Grave, Type::Grave] }
|
|
||||||
gt { "> >" => [ Type::Gt, Type::Gt] }
|
|
||||||
gteq { ">= >=" => [ Type::GtEq, Type::GtEq] }
|
|
||||||
gtgt { ">> >>" => [ Type::GtGt, Type::GtGt] }
|
|
||||||
gtgteq { ">>= >>=" => [ Type::GtGtEq, Type::GtGtEq] }
|
|
||||||
hash { "# #" => [ Type::Hash, Type::Hash] }
|
|
||||||
lt { "< <" => [ Type::Lt, Type::Lt] }
|
|
||||||
lteq { "<= <=" => [ Type::LtEq, Type::LtEq] }
|
|
||||||
ltlt { "<< <<" => [ Type::LtLt, Type::LtLt] }
|
|
||||||
ltlteq { "<<= <<=" => [ Type::LtLtEq, Type::LtLtEq] }
|
|
||||||
minus { "- -" => [ Type::Minus, Type::Minus] }
|
|
||||||
minuseq { "-= -=" => [ Type::MinusEq, Type::MinusEq] }
|
|
||||||
plus { "+ +" => [ Type::Plus, Type::Plus] }
|
|
||||||
pluseq { "+= +=" => [ Type::PlusEq, Type::PlusEq] }
|
|
||||||
question { "? ?" => [ Type::Question, Type::Question] }
|
|
||||||
rem { "% %" => [ Type::Rem, Type::Rem] }
|
|
||||||
remeq { "%= %=" => [ Type::RemEq, Type::RemEq] }
|
|
||||||
semi { "; ;" => [ Type::Semi, Type::Semi] }
|
|
||||||
slash { "/ /" => [ Type::Slash, Type::Slash] }
|
|
||||||
slasheq { "/= /=" => [ Type::SlashEq, Type::SlashEq] }
|
|
||||||
star { "* *" => [ Type::Star, Type::Star] }
|
|
||||||
stareq { "*= *=" => [ Type::StarEq, Type::StarEq] }
|
|
||||||
tilde { "~ ~" => [ Type::Tilde, Type::Tilde] }
|
|
||||||
xor { "^ ^" => [ Type::Xor, Type::Xor] }
|
|
||||||
xoreq { "^= ^=" => [ Type::XorEq, Type::XorEq] }
|
|
||||||
xorxor { "^^ ^^" => [ Type::XorXor, Type::XorXor] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod parser {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
mod interpreter {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
//! # Token
|
|
||||||
//!
|
|
||||||
//! Stores a component of a file as a [Type], some [Data], and a line and column number
|
|
||||||
|
|
||||||
pub mod token_data;
|
|
||||||
pub mod token_type;
|
|
||||||
pub mod preamble {
|
|
||||||
//! Common imports for working with [tokens](super)
|
|
||||||
pub use super::{
|
|
||||||
token_data::Data,
|
|
||||||
token_type::{Keyword, Type},
|
|
||||||
Token,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
use token_data::Data;
|
|
||||||
use token_type::Type;
|
|
||||||
|
|
||||||
/// Contains a single unit of lexical information,
|
|
||||||
/// and an optional bit of [Data]
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Token {
|
|
||||||
ty: Type,
|
|
||||||
data: Data,
|
|
||||||
line: u32,
|
|
||||||
col: u32,
|
|
||||||
}
|
|
||||||
impl Token {
|
|
||||||
/// Creates a new [Token] out of a [Type], [Data], line, and column.
|
|
||||||
pub fn new(ty: Type, data: impl Into<Data>, line: u32, col: u32) -> Self {
|
|
||||||
Self { ty, data: data.into(), line, col }
|
|
||||||
}
|
|
||||||
/// Casts this token to a new [Type]
|
|
||||||
pub fn cast(self, ty: Type) -> Self {
|
|
||||||
Self { ty, ..self }
|
|
||||||
}
|
|
||||||
/// Returns the [Type] of this token
|
|
||||||
pub fn ty(&self) -> Type {
|
|
||||||
self.ty
|
|
||||||
}
|
|
||||||
/// Returns a reference to this token's [Data]
|
|
||||||
pub fn data(&self) -> &Data {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
/// Converts this token into its inner [Data]
|
|
||||||
pub fn into_data(self) -> Data {
|
|
||||||
self.data
|
|
||||||
}
|
|
||||||
/// Returns the line where this token originated
|
|
||||||
pub fn line(&self) -> u32 {
|
|
||||||
self.line
|
|
||||||
}
|
|
||||||
/// Returns the column where this token originated
|
|
||||||
pub fn col(&self) -> u32 {
|
|
||||||
self.col
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
//! Stores a [Token's](super::Token) lexical information
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
|
||||||
|
|
||||||
/// Stores a [Token's](super::Token) lexical information
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Type {
|
|
||||||
// Invalid syntax
|
|
||||||
Invalid,
|
|
||||||
// Any kind of comment
|
|
||||||
Comment,
|
|
||||||
// Any identifier
|
|
||||||
Identifier,
|
|
||||||
Keyword(Keyword),
|
|
||||||
// Literals
|
|
||||||
Integer,
|
|
||||||
Float,
|
|
||||||
String,
|
|
||||||
Character,
|
|
||||||
// Delimiters and punctuation
|
|
||||||
LCurly, // {
|
|
||||||
RCurly, // }
|
|
||||||
LBrack, // [
|
|
||||||
RBrack, // ]
|
|
||||||
LParen, // (
|
|
||||||
RParen, // )
|
|
||||||
Amp, // &
|
|
||||||
AmpAmp, // &&
|
|
||||||
AmpEq, // &=
|
|
||||||
Arrow, // ->
|
|
||||||
At, // @
|
|
||||||
Backslash, // \
|
|
||||||
Bang, // !
|
|
||||||
BangBang, // !!
|
|
||||||
BangEq, // !=
|
|
||||||
Bar, // |
|
|
||||||
BarBar, // ||
|
|
||||||
BarEq, // |=
|
|
||||||
Colon, // :
|
|
||||||
ColonColon, // ::
|
|
||||||
Comma, // ,
|
|
||||||
Dot, // .
|
|
||||||
DotDot, // ..
|
|
||||||
DotDotEq, // ..=
|
|
||||||
Eq, // =
|
|
||||||
EqEq, // ==
|
|
||||||
FatArrow, // =>
|
|
||||||
Grave, // `
|
|
||||||
Gt, // >
|
|
||||||
GtEq, // >=
|
|
||||||
GtGt, // >>
|
|
||||||
GtGtEq, // >>=
|
|
||||||
Hash, // #
|
|
||||||
HashBang, // #!
|
|
||||||
Lt, // <
|
|
||||||
LtEq, // <=
|
|
||||||
LtLt, // <<
|
|
||||||
LtLtEq, // <<=
|
|
||||||
Minus, // -
|
|
||||||
MinusEq, // -=
|
|
||||||
Plus, // +
|
|
||||||
PlusEq, // +=
|
|
||||||
Question, // ?
|
|
||||||
Rem, // %
|
|
||||||
RemEq, // %=
|
|
||||||
Semi, // ;
|
|
||||||
Slash, // /
|
|
||||||
SlashEq, // /=
|
|
||||||
Star, // *
|
|
||||||
StarEq, // *=
|
|
||||||
Tilde, // ~
|
|
||||||
Xor, // ^
|
|
||||||
XorEq, // ^=
|
|
||||||
XorXor, // ^^
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a reserved word.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Keyword {
|
|
||||||
Break,
|
|
||||||
Cl,
|
|
||||||
Const,
|
|
||||||
Continue,
|
|
||||||
Else,
|
|
||||||
Enum,
|
|
||||||
False,
|
|
||||||
For,
|
|
||||||
Fn,
|
|
||||||
If,
|
|
||||||
Impl,
|
|
||||||
In,
|
|
||||||
Let,
|
|
||||||
Mod,
|
|
||||||
Mut,
|
|
||||||
Pub,
|
|
||||||
Return,
|
|
||||||
SelfKw,
|
|
||||||
SelfTy,
|
|
||||||
Static,
|
|
||||||
Struct,
|
|
||||||
Super,
|
|
||||||
True,
|
|
||||||
Type,
|
|
||||||
While,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Type {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Type::Invalid => "invalid".fmt(f),
|
|
||||||
Type::Comment => "comment".fmt(f),
|
|
||||||
Type::Identifier => "identifier".fmt(f),
|
|
||||||
Type::Keyword(k) => k.fmt(f),
|
|
||||||
Type::Integer => "integer literal".fmt(f),
|
|
||||||
Type::Float => "float literal".fmt(f),
|
|
||||||
Type::String => "string literal".fmt(f),
|
|
||||||
Type::Character => "char literal".fmt(f),
|
|
||||||
Type::LCurly => "left curly".fmt(f),
|
|
||||||
Type::RCurly => "right curly".fmt(f),
|
|
||||||
Type::LBrack => "left brack".fmt(f),
|
|
||||||
Type::RBrack => "right brack".fmt(f),
|
|
||||||
Type::LParen => "left paren".fmt(f),
|
|
||||||
Type::RParen => "right paren".fmt(f),
|
|
||||||
Type::Amp => "and".fmt(f),
|
|
||||||
Type::AmpAmp => "and-and".fmt(f),
|
|
||||||
Type::AmpEq => "and-assign".fmt(f),
|
|
||||||
Type::Arrow => "arrow".fmt(f),
|
|
||||||
Type::At => "at".fmt(f),
|
|
||||||
Type::Backslash => "backslash".fmt(f),
|
|
||||||
Type::Bang => "bang".fmt(f),
|
|
||||||
Type::BangBang => "not-not".fmt(f),
|
|
||||||
Type::BangEq => "not equal to".fmt(f),
|
|
||||||
Type::Bar => "or".fmt(f),
|
|
||||||
Type::BarBar => "or-or".fmt(f),
|
|
||||||
Type::BarEq => "or-assign".fmt(f),
|
|
||||||
Type::Colon => "colon".fmt(f),
|
|
||||||
Type::ColonColon => "path separator".fmt(f),
|
|
||||||
Type::Comma => "comma".fmt(f),
|
|
||||||
Type::Dot => "dot".fmt(f),
|
|
||||||
Type::DotDot => "exclusive range".fmt(f),
|
|
||||||
Type::DotDotEq => "inclusive range".fmt(f),
|
|
||||||
Type::Eq => "assign".fmt(f),
|
|
||||||
Type::EqEq => "equal to".fmt(f),
|
|
||||||
Type::FatArrow => "fat arrow".fmt(f),
|
|
||||||
Type::Grave => "grave".fmt(f),
|
|
||||||
Type::Gt => "greater than".fmt(f),
|
|
||||||
Type::GtEq => "greater than or equal to".fmt(f),
|
|
||||||
Type::GtGt => "shift right".fmt(f),
|
|
||||||
Type::GtGtEq => "shift right-assign".fmt(f),
|
|
||||||
Type::Hash => "hash".fmt(f),
|
|
||||||
Type::HashBang => "shebang".fmt(f),
|
|
||||||
Type::Lt => "less than".fmt(f),
|
|
||||||
Type::LtEq => "less than or equal to".fmt(f),
|
|
||||||
Type::LtLt => "shift left".fmt(f),
|
|
||||||
Type::LtLtEq => "shift left-assign".fmt(f),
|
|
||||||
Type::Minus => "sub".fmt(f),
|
|
||||||
Type::MinusEq => "sub-assign".fmt(f),
|
|
||||||
Type::Plus => "add".fmt(f),
|
|
||||||
Type::PlusEq => "add-assign".fmt(f),
|
|
||||||
Type::Question => "huh?".fmt(f),
|
|
||||||
Type::Rem => "rem".fmt(f),
|
|
||||||
Type::RemEq => "rem-assign".fmt(f),
|
|
||||||
Type::Semi => "ignore".fmt(f),
|
|
||||||
Type::Slash => "div".fmt(f),
|
|
||||||
Type::SlashEq => "div-assign".fmt(f),
|
|
||||||
Type::Star => "star".fmt(f),
|
|
||||||
Type::StarEq => "star-assign".fmt(f),
|
|
||||||
Type::Tilde => "tilde".fmt(f),
|
|
||||||
Type::Xor => "xor".fmt(f),
|
|
||||||
Type::XorEq => "xor-assign".fmt(f),
|
|
||||||
Type::XorXor => "cat-ears".fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Keyword {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Break => "break".fmt(f),
|
|
||||||
Self::Cl => "cl".fmt(f),
|
|
||||||
Self::Const => "const".fmt(f),
|
|
||||||
Self::Continue => "continue".fmt(f),
|
|
||||||
Self::Else => "else".fmt(f),
|
|
||||||
Self::Enum => "enum".fmt(f),
|
|
||||||
Self::False => "false".fmt(f),
|
|
||||||
Self::For => "for".fmt(f),
|
|
||||||
Self::Fn => "fn".fmt(f),
|
|
||||||
Self::If => "if".fmt(f),
|
|
||||||
Self::Impl => "impl".fmt(f),
|
|
||||||
Self::In => "in".fmt(f),
|
|
||||||
Self::Let => "let".fmt(f),
|
|
||||||
Self::Mod => "mod".fmt(f),
|
|
||||||
Self::Mut => "mut".fmt(f),
|
|
||||||
Self::Pub => "pub".fmt(f),
|
|
||||||
Self::Return => "return".fmt(f),
|
|
||||||
Self::SelfKw => "self".fmt(f),
|
|
||||||
Self::SelfTy => "Self".fmt(f),
|
|
||||||
Self::Static => "static".fmt(f),
|
|
||||||
Self::Struct => "struct".fmt(f),
|
|
||||||
Self::Super => "super".fmt(f),
|
|
||||||
Self::True => "true".fmt(f),
|
|
||||||
Self::Type => "type".fmt(f),
|
|
||||||
Self::While => "while".fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for Keyword {
|
|
||||||
/// [FromStr] can only fail when an identifier isn't a keyword
|
|
||||||
type Err = ();
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(match s {
|
|
||||||
"break" => Self::Break,
|
|
||||||
"cl" => Self::Cl,
|
|
||||||
"const" => Self::Const,
|
|
||||||
"continue" => Self::Continue,
|
|
||||||
"else" => Self::Else,
|
|
||||||
"enum" => Self::Enum,
|
|
||||||
"false" => Self::False,
|
|
||||||
"for" => Self::For,
|
|
||||||
"fn" => Self::Fn,
|
|
||||||
"if" => Self::If,
|
|
||||||
"impl" => Self::Impl,
|
|
||||||
"in" => Self::In,
|
|
||||||
"let" => Self::Let,
|
|
||||||
"mod" => Self::Mod,
|
|
||||||
"mut" => Self::Mut,
|
|
||||||
"pub" => Self::Pub,
|
|
||||||
"return" => Self::Return,
|
|
||||||
"self" => Self::SelfKw,
|
|
||||||
"Self" => Self::SelfTy,
|
|
||||||
"static" => Self::Static,
|
|
||||||
"struct" => Self::Struct,
|
|
||||||
"super" => Self::Super,
|
|
||||||
"true" => Self::True,
|
|
||||||
"type" => Self::Type,
|
|
||||||
"while" => Self::While,
|
|
||||||
_ => Err(())?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# Conlang: Expression-Oriented Programming Language
|
# Conlang: Expression-Oriented Programming Language
|
||||||
This project began out of a desire to merge Rust-style control flow expressions
|
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
|
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
|
to conlang for the forseeable future.
|
||||||
Friday each month.
|
|
||||||
|
|
||||||
## Immediate Goals:
|
## Immediate Goals:
|
||||||
- [x] Decide on a minimal set of keywords and operators to support
|
- [x] Decide on a minimal set of keywords and operators to support
|
||||||
@@ -20,10 +19,11 @@ Friday each month.
|
|||||||
## Short Goals:
|
## Short Goals:
|
||||||
- [x] `for` loops and `while` loops can be used on the trailing side of an assignment
|
- [x] `for` loops and `while` loops can be used on the trailing side of an assignment
|
||||||
- [x] Tree-walk interpreter for prototyping and debugging
|
- [x] Tree-walk interpreter for prototyping and debugging
|
||||||
- [ ] Data structures and sum-type enums
|
- [x] Data structures and sum-type enums
|
||||||
- [ ] Expression type-checker
|
- [ ] Expression type-checker
|
||||||
- [ ] Trait/Interface system
|
- [ ] Pattern destructuring, to take advantage of sum-type enums
|
||||||
- [ ] Three-address bytecode VM for standard library development
|
- [ ] Three-address bytecode VM for standard library development
|
||||||
|
- [ ] Trait/Interface system
|
||||||
|
|
||||||
## Long Goals:
|
## Long Goals:
|
||||||
- [ ] Minimize the number of kinds of statements
|
- [ ] Minimize the number of kinds of statements
|
||||||
|
|||||||
11
repline/Cargo.toml
Normal file
11
repline/Cargo.toml
Normal 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
324
repline/src/editor.rs
Normal 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
42
repline/src/error.rs
Normal 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
68
repline/src/iter.rs
Normal 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()?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user