47 Commits

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

Currently implemented as jankily as possible in the interpreter, but it works alright™️
2024-07-26 05:26:08 -05:00
a8b8a91c79 sample-code: print->println to match interpreter behavior 2024-07-26 05:13:52 -05:00
695c812bf5 cl-repl: increase jank: first positional arg is main file, remainder are imports 2024-07-26 05:13:06 -05:00
524c84be9e cl_typeck: Add new primitive types (including joking-point numbers) 2024-07-26 03:24:34 -05:00
4096442f75 cl-typeck: Turn ref into a linked list.
This should be fine, since the only thing you can do with a ref is dereference it.
2024-07-26 02:14:41 -05:00
03a4e76292 cl-typeck: rustfmt implement.rs 2024-07-26 00:15:00 -05:00
46a1639990 sample-code: Have fun with random number generators 2024-07-25 23:59:41 -05:00
5ea8039a8a typeck.rs: Update for new stdlib layout; don't hardcode the root stdlib module. 2024-07-25 07:09:12 -05:00
479efbad73 typeck.rs: Add file-loading mode 2024-07-25 07:08:07 -05:00
a462dd2be3 stdlib: Use Conlang module layout 2024-07-25 07:05:57 -05:00
4d6b94b570 conlang: Bump version to v0.0.6
- Major milestone: cl-typeck doesn't suck as much!
2024-07-25 05:56:05 -05:00
fe2b816f27 cl-typeck: Crate-spanning refactor part 2
- Removed all unreferenced files
- Reimplemented missing/nonfunctional behavior
- Added module documentation for most things
  - TODO: item-level docs on Entry(Mut)
- Reparented the stages of Table population into the `stage` module.
  - TODO: rewrite type inference to use only the tools provided by Table.
2024-07-25 05:55:11 -05:00
e19127facc cl-typeck: Crate-spanning refactor
TODO: remove all unreferenced files
TODO: Finish resolving statically known types of values
TODO: Type inference
2024-07-24 18:22:42 -05:00
b7ad285a11 cl-typeck: give Handle accessors for useful attributes 2024-07-24 14:29:27 -05:00
63 changed files with 4212 additions and 4080 deletions

View File

@@ -15,7 +15,7 @@ resolver = "2"
[workspace.package]
repository = "https://git.soft.fish/j/Conlang"
version = "0.0.5"
version = "0.0.7"
authors = ["John Breaux <j@soft.fish>"]
edition = "2021"
license = "MIT"

View File

@@ -258,7 +258,6 @@ pub enum TyKind {
Tuple(TyTuple),
Ref(TyRef),
Fn(TyFn),
// TODO: slice, array types
}
/// An array of [`T`](Ty)
@@ -323,7 +322,6 @@ pub struct Stmt {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum StmtKind {
Empty,
Local(Let),
Item(Box<Item>),
Expr(Box<Expr>),
}
@@ -335,15 +333,6 @@ pub enum Semi {
Unterminated,
}
/// A local variable declaration [Stmt]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Let {
pub mutable: Mutability,
pub name: Sym,
pub ty: Option<Box<Ty>>,
pub init: Option<Box<Expr>>,
}
/// An expression, the beating heart of the language
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Expr {
@@ -357,6 +346,8 @@ 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`])\+
@@ -365,6 +356,8 @@ pub enum ExprKind {
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]
@@ -388,8 +381,6 @@ pub enum ExprKind {
Group(Group),
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
Tuple(Tuple),
/// A [Loop] expression: `loop` [`Block`]
Loop(Loop),
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
While(While),
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
@@ -401,7 +392,16 @@ pub enum ExprKind {
/// A [Return] expression `return` [`Expr`]?
Return(Return),
/// A continue expression: `continue`
Continue(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`])\+
@@ -478,12 +478,20 @@ 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 {
@@ -560,12 +568,6 @@ pub struct Tuple {
pub exprs: Vec<Expr>,
}
/// A [Loop] expression: `loop` [`Block`]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Loop {
pub body: Box<Expr>,
}
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct While {
@@ -608,7 +610,3 @@ pub struct Break {
pub struct Return {
pub body: Option<Box<Expr>>,
}
/// A continue expression: `continue`
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Continue;

View File

@@ -48,9 +48,9 @@ mod display {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Literal::Bool(v) => v.fmt(f),
Literal::Char(v) => write!(f, "'{v}'"),
Literal::Char(v) => write!(f, "'{}'", v.escape_debug()),
Literal::Int(v) => v.fmt(f),
Literal::String(v) => write!(f, "\"{v}\""),
Literal::String(v) => write!(f, "\"{}\"", v.escape_debug()),
}
}
}
@@ -389,7 +389,6 @@ mod display {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StmtKind::Empty => Ok(()),
StmtKind::Local(v) => v.fmt(f),
StmtKind::Item(v) => v.fmt(f),
StmtKind::Expr(v) => v.fmt(f),
}
@@ -405,20 +404,6 @@ mod display {
}
}
impl Display for Let {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { mutable, name, ty, init } = self;
write!(f, "let {mutable}{name}")?;
if let Some(value) = ty {
write!(f, ": {value}")?;
}
if let Some(value) = init {
write!(f, " = {value}")?;
}
Ok(())
}
}
impl Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.kind.fmt(f)
@@ -429,10 +414,12 @@ mod display {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExprKind::Empty => "()".fmt(f),
ExprKind::Let(v) => v.fmt(f),
ExprKind::Assign(v) => v.fmt(f),
ExprKind::Modify(v) => v.fmt(f),
ExprKind::Binary(v) => v.fmt(f),
ExprKind::Unary(v) => v.fmt(f),
ExprKind::Cast(v) => v.fmt(f),
ExprKind::Member(v) => v.fmt(f),
ExprKind::Index(v) => v.fmt(f),
ExprKind::Structor(v) => v.fmt(f),
@@ -444,17 +431,30 @@ mod display {
ExprKind::Block(v) => v.fmt(f),
ExprKind::Group(v) => v.fmt(f),
ExprKind::Tuple(v) => v.fmt(f),
ExprKind::Loop(v) => v.fmt(f),
ExprKind::While(v) => v.fmt(f),
ExprKind::If(v) => v.fmt(f),
ExprKind::For(v) => v.fmt(f),
ExprKind::Break(v) => v.fmt(f),
ExprKind::Return(v) => v.fmt(f),
ExprKind::Continue(_) => "continue".fmt(f),
ExprKind::Continue => "continue".fmt(f),
}
}
}
impl Display for Let {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { mutable, name, ty, init } = self;
write!(f, "let {mutable}{name}")?;
if let Some(value) = ty {
write!(f, ": {value}")?;
}
if let Some(value) = init {
write!(f, " = {value}")?;
}
Ok(())
}
}
impl Display for Assign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { parts } = self;
@@ -538,6 +538,7 @@ mod display {
impl Display for UnaryKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnaryKind::Loop => "loop ",
UnaryKind::Deref => "*",
UnaryKind::Neg => "-",
UnaryKind::Not => "!",
@@ -548,6 +549,13 @@ mod display {
}
}
impl Display for Cast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head, ty } = self;
write!(f, "{head} as {ty}")
}
}
impl Display for Member {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { head, kind } = self;
@@ -617,7 +625,12 @@ mod display {
impl Display for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
separate(&self.stmts, "\n")(f.delimit(BRACES))
let Self { stmts } = self;
match stmts.as_slice() {
[] => "{}".fmt(f),
stmts => separate(stmts, "\n")(f.delimit(BRACES)),
}
}
}
@@ -629,14 +642,13 @@ mod display {
impl Display for Tuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
separate(&self.exprs, ", ")(f.delimit(INLINE_PARENS))
}
}
let Self { exprs } = self;
impl Display for Loop {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { body } = self;
write!(f, "loop {body}")
match exprs.as_slice() {
[] => write!(f, "()"),
[expr] => write!(f, "({expr},)"),
exprs => separate(exprs, ", ")(f.delimit(INLINE_PARENS)),
}
}
}
@@ -689,12 +701,6 @@ mod display {
}
}
}
impl Display for Continue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"continue".fmt(f)
}
}
}
mod convert {
@@ -755,15 +761,16 @@ mod convert {
TyFn => TyKind::Fn,
}
impl From for StmtKind {
Let => StmtKind::Local,
Item => StmtKind::Item,
Expr => StmtKind::Expr,
}
impl From for ExprKind {
Let => ExprKind::Let,
Assign => ExprKind::Assign,
Modify => ExprKind::Modify,
Binary => ExprKind::Binary,
Unary => ExprKind::Unary,
Cast => ExprKind::Cast,
Member => ExprKind::Member,
Index => ExprKind::Index,
Path => ExprKind::Path,
@@ -774,13 +781,11 @@ mod convert {
Block => ExprKind::Block,
Group => ExprKind::Group,
Tuple => ExprKind::Tuple,
Loop => ExprKind::Loop,
While => ExprKind::While,
If => ExprKind::If,
For => ExprKind::For,
Break => ExprKind::Break,
Return => ExprKind::Return,
Continue => ExprKind::Continue,
}
impl From for Literal {
bool => Literal::Bool,

View File

@@ -276,6 +276,10 @@ pub trait Fold {
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) }
@@ -334,10 +338,6 @@ pub trait Fold {
let Tuple { exprs } = t;
Tuple { exprs: exprs.into_iter().map(|e| self.fold_expr(e)).collect() }
}
fn fold_loop(&mut self, l: Loop) -> Loop {
let Loop { body } = l;
Loop { body: Box::new(self.fold_expr(*body)) }
}
fn fold_while(&mut self, w: While) -> While {
let While { cond, pass, fail } = w;
While {
@@ -375,10 +375,6 @@ pub trait Fold {
let Return { body } = r;
Return { body: body.map(|e| Box::new(self.fold_expr(*e))) }
}
fn fold_continue(&mut self, c: Continue) -> Continue {
let Continue = c;
Continue
}
}
#[inline]
@@ -521,7 +517,6 @@ pub fn or_fold_ty_kind<F: Fold + ?Sized>(folder: &mut F, kind: TyKind) -> TyKind
pub fn or_fold_stmt_kind<F: Fold + ?Sized>(folder: &mut F, kind: StmtKind) -> StmtKind {
match kind {
StmtKind::Empty => StmtKind::Empty,
StmtKind::Local(l) => StmtKind::Local(folder.fold_let(l)),
StmtKind::Item(i) => StmtKind::Item(Box::new(folder.fold_item(*i))),
StmtKind::Expr(e) => StmtKind::Expr(Box::new(folder.fold_expr(*e))),
}
@@ -531,10 +526,12 @@ pub fn or_fold_stmt_kind<F: Fold + ?Sized>(folder: &mut F, kind: StmtKind) -> St
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)),
@@ -546,13 +543,12 @@ pub fn or_fold_expr_kind<F: Fold + ?Sized>(folder: &mut F, kind: ExprKind) -> Ex
ExprKind::Block(b) => ExprKind::Block(folder.fold_block(b)),
ExprKind::Group(g) => ExprKind::Group(folder.fold_group(g)),
ExprKind::Tuple(t) => ExprKind::Tuple(folder.fold_tuple(t)),
ExprKind::Loop(l) => ExprKind::Loop(folder.fold_loop(l)),
ExprKind::While(w) => ExprKind::While(folder.fold_while(w)),
ExprKind::If(i) => ExprKind::If(folder.fold_if(i)),
ExprKind::For(f) => ExprKind::For(folder.fold_for(f)),
ExprKind::Break(b) => ExprKind::Break(folder.fold_break(b)),
ExprKind::Return(r) => ExprKind::Return(folder.fold_return(r)),
ExprKind::Continue(c) => ExprKind::Continue(folder.fold_continue(c)),
ExprKind::Continue => ExprKind::Continue,
}
}
pub fn or_fold_member_kind<F: Fold + ?Sized>(folder: &mut F, kind: MemberKind) -> MemberKind {

View File

@@ -238,6 +238,11 @@ pub trait Visit<'a>: Sized {
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);
@@ -289,10 +294,6 @@ pub trait Visit<'a>: Sized {
let Tuple { exprs } = t;
exprs.iter().for_each(|e| self.visit_expr(e))
}
fn visit_loop(&mut self, l: &'a Loop) {
let Loop { body } = l;
self.visit_expr(body)
}
fn visit_while(&mut self, w: &'a While) {
let While { cond, pass, fail } = w;
self.visit_expr(cond);
@@ -330,9 +331,7 @@ pub trait Visit<'a>: Sized {
self.visit_expr(body)
}
}
fn visit_continue(&mut self, c: &'a Continue) {
let Continue = c;
}
fn visit_continue(&mut self) {}
}
pub fn or_visit_literal<'a, V: Visit<'a>>(visitor: &mut V, l: &'a Literal) {
@@ -443,7 +442,6 @@ pub fn or_visit_ty_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a TyKind) {
pub fn or_visit_stmt_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a StmtKind) {
match kind {
StmtKind::Empty => {}
StmtKind::Local(l) => visitor.visit_let(l),
StmtKind::Item(i) => visitor.visit_item(i),
StmtKind::Expr(e) => visitor.visit_expr(e),
}
@@ -452,10 +450,12 @@ pub fn or_visit_stmt_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a StmtKind)
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),
@@ -467,13 +467,12 @@ pub fn or_visit_expr_kind<'a, V: Visit<'a>>(visitor: &mut V, e: &'a ExprKind) {
ExprKind::Block(b) => visitor.visit_block(b),
ExprKind::Group(g) => visitor.visit_group(g),
ExprKind::Tuple(t) => visitor.visit_tuple(t),
ExprKind::Loop(l) => visitor.visit_loop(l),
ExprKind::While(w) => visitor.visit_while(w),
ExprKind::If(i) => visitor.visit_if(i),
ExprKind::For(f) => visitor.visit_for(f),
ExprKind::Break(b) => visitor.visit_break(b),
ExprKind::Return(r) => visitor.visit_return(r),
ExprKind::Continue(c) => visitor.visit_continue(c),
ExprKind::Continue => visitor.visit_continue(),
}
}
pub fn or_visit_member_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a MemberKind) {

View File

@@ -26,8 +26,8 @@ fn desugar_while(extents: Span, kind: ExprKind) -> ExprKind {
let break_expr = Expr { extents: fail_span, kind: ExprKind::Break(Break { body }) };
let loop_body = If { cond, pass, fail: Else { body: Some(Box::new(break_expr)) } };
let loop_body = Expr { extents, kind: ExprKind::If(loop_body) };
ExprKind::Loop(Loop { body: Box::new(loop_body) })
let loop_body = ExprKind::If(loop_body);
ExprKind::Unary(Unary { kind: UnaryKind::Loop, tail: Box::new(loop_body) })
}
_ => kind,
}

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

View File

@@ -10,6 +10,7 @@ use cl_ast::Sym;
use std::{
io::{stdout, Write},
rc::Rc,
slice,
};
builtins! {
@@ -57,6 +58,19 @@ builtins! {
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;

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

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

View 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.")
}
}
}
}

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

View File

@@ -105,7 +105,6 @@ impl Interpret for Stmt {
let Self { extents: _, kind, semi } = self;
let out = match kind {
StmtKind::Empty => ConValue::Empty,
StmtKind::Local(stmt) => stmt.interpret(env)?,
StmtKind::Item(stmt) => stmt.interpret(env)?,
StmtKind::Expr(stmt) => stmt.interpret(env)?,
};
@@ -134,10 +133,12 @@ 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),
@@ -149,13 +150,12 @@ impl Interpret for ExprKind {
ExprKind::Block(v) => v.interpret(env),
ExprKind::Group(v) => v.interpret(env),
ExprKind::Tuple(v) => v.interpret(env),
ExprKind::Loop(v) => v.interpret(env),
ExprKind::While(v) => v.interpret(env),
ExprKind::If(v) => v.interpret(env),
ExprKind::For(v) => v.interpret(env),
ExprKind::Break(v) => v.interpret(env),
ExprKind::Return(v) => v.interpret(env),
ExprKind::Continue(v) => v.interpret(env),
ExprKind::Continue => Err(Error::Continue),
}
}
}
@@ -321,12 +321,28 @@ impl Interpret for Binary {
impl Interpret for Unary {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Unary { kind, tail } = self;
let operand = tail.interpret(env)?;
match kind {
UnaryKind::Deref => env.call("deref".into(), &[operand]),
UnaryKind::Neg => env.call("neg".into(), &[operand]),
UnaryKind::Not => env.call("not".into(), &[operand]),
UnaryKind::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)
}
@@ -334,6 +350,48 @@ impl Interpret for Unary {
}
}
}
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;
@@ -461,18 +519,6 @@ impl Interpret for Tuple {
))
}
}
impl Interpret for Loop {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { body } = self;
loop {
match body.interpret(env) {
Err(Error::Break(value)) => return Ok(value),
Err(Error::Continue) => continue,
e => e?,
};
}
}
}
impl Interpret for While {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { cond, pass, fail } = self;
@@ -532,11 +578,6 @@ impl Interpret for Else {
}
}
}
impl Interpret for Continue {
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
Err(Error::Continue)
}
}
impl Interpret for Return {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { body } = self;

View File

@@ -22,611 +22,17 @@ pub trait BuiltIn: std::fmt::Debug + Callable {
fn description(&self) -> &str;
}
pub mod convalue {
//! Values in the dynamically typed AST interpreter.
//!
//! The most permanent fix is a temporary one.
use cl_ast::Sym;
use super::{
error::{Error, IResult},
function::Function,
BuiltIn, Callable, Environment,
};
use std::{ops::*, rc::Rc};
type Integer = isize;
/// A Conlang value stores data in the interpreter
#[derive(Clone, Debug, Default)]
pub enum ConValue {
/// The empty/unit `()` type
#[default]
Empty,
/// An integer
Int(Integer),
/// A boolean
Bool(bool),
/// A unicode character
Char(char),
/// A string
String(Sym),
/// A reference
Ref(Rc<ConValue>),
/// An Array
Array(Rc<[ConValue]>),
/// A tuple
Tuple(Rc<[ConValue]>),
/// An exclusive range
RangeExc(Integer, Integer),
/// An inclusive range
RangeInc(Integer, Integer),
/// A callable thing
Function(Function),
/// A built-in function
BuiltIn(&'static dyn BuiltIn),
}
impl ConValue {
/// Gets whether the current value is true or false
pub fn truthy(&self) -> IResult<bool> {
match self {
ConValue::Bool(v) => Ok(*v),
_ => Err(Error::TypeError)?,
}
}
pub fn range_exc(self, other: Self) -> IResult<Self> {
let (Self::Int(a), Self::Int(b)) = (self, other) else {
Err(Error::TypeError)?
};
Ok(Self::RangeExc(a, b.saturating_sub(1)))
}
pub fn range_inc(self, other: Self) -> IResult<Self> {
let (Self::Int(a), Self::Int(b)) = (self, other) else {
Err(Error::TypeError)?
};
Ok(Self::RangeInc(a, b))
}
pub fn index(&self, index: &Self) -> IResult<ConValue> {
let Self::Int(index) = index else {
Err(Error::TypeError)?
};
let Self::Array(arr) = self else {
Err(Error::TypeError)?
};
arr.get(*index as usize)
.cloned()
.ok_or(Error::OobIndex(*index as usize, arr.len()))
}
cmp! {
lt: false, <;
lt_eq: true, <=;
eq: true, ==;
neq: false, !=;
gt_eq: true, >=;
gt: false, >;
}
assign! {
add_assign: +;
bitand_assign: &;
bitor_assign: |;
bitxor_assign: ^;
div_assign: /;
mul_assign: *;
rem_assign: %;
shl_assign: <<;
shr_assign: >>;
sub_assign: -;
}
}
impl Callable for ConValue {
fn name(&self) -> Sym {
match self {
ConValue::Function(func) => func.name(),
ConValue::BuiltIn(func) => func.name(),
_ => "".into(),
}
}
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
match self {
Self::Function(func) => func.call(interpreter, args),
Self::BuiltIn(func) => func.call(interpreter, args),
_ => Err(Error::NotCallable(self.clone())),
}
}
}
/// Templates comparison functions for [ConValue]
macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$(
/// TODO: Remove when functions are implemented:
/// Desugar into function calls
pub fn $fn(&self, other: &Self) -> IResult<Self> {
match (self, other) {
(Self::Empty, Self::Empty) => Ok(Self::Bool($empty)),
(Self::Int(a), Self::Int(b)) => Ok(Self::Bool(a $op b)),
(Self::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)),
(Self::Char(a), Self::Char(b)) => Ok(Self::Bool(a $op b)),
(Self::String(a), Self::String(b)) => Ok(Self::Bool(&**a $op &**b)),
_ => Err(Error::TypeError)
}
}
)*}
macro assign($( $fn: ident: $op: tt );*$(;)?) {$(
pub fn $fn(&mut self, other: Self) -> IResult<()> {
*self = (std::mem::take(self) $op other)?;
Ok(())
}
)*}
/// Implements [From] for an enum with 1-tuple variants
macro from ($($T:ty => $v:expr),*$(,)?) {
$(impl From<$T> for ConValue {
fn from(value: $T) -> Self { $v(value.into()) }
})*
}
impl From<&Sym> for ConValue {
fn from(value: &Sym) -> Self {
ConValue::String(*value)
}
}
from! {
Integer => ConValue::Int,
bool => ConValue::Bool,
char => ConValue::Char,
Sym => ConValue::String,
&str => ConValue::String,
String => ConValue::String,
Rc<str> => ConValue::String,
Function => ConValue::Function,
Vec<ConValue> => ConValue::Tuple,
&'static dyn BuiltIn => ConValue::BuiltIn,
}
impl From<()> for ConValue {
fn from(_: ()) -> Self {
Self::Empty
}
}
impl From<&[ConValue]> for ConValue {
fn from(value: &[ConValue]) -> Self {
match value.len() {
0 => Self::Empty,
1 => value[0].clone(),
_ => Self::Tuple(value.into()),
}
}
}
/// Implements binary [std::ops] traits for [ConValue]
///
/// TODO: Desugar operators into function calls
macro ops($($trait:ty: $fn:ident = [$($match:tt)*])*) {
$(impl $trait for ConValue {
type Output = IResult<Self>;
/// TODO: Desugar operators into function calls
fn $fn(self, rhs: Self) -> Self::Output {Ok(match (self, rhs) {$($match)*})}
})*
}
ops! {
Add: add = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_add(b)),
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).into(),
(ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() }
(ConValue::Char(a), ConValue::Char(b)) => {
ConValue::String([a, b].into_iter().collect::<String>().into())
}
_ => Err(Error::TypeError)?
]
BitAnd: bitand = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b),
_ => Err(Error::TypeError)?
]
BitOr: bitor = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b),
_ => Err(Error::TypeError)?
]
BitXor: bitxor = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b),
_ => Err(Error::TypeError)?
]
Div: div = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_div(b).unwrap_or_else(|| {
eprintln!("Warning: Divide by zero in {a} / {b}"); a
})),
_ => Err(Error::TypeError)?
]
Mul: mul = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(b)),
_ => Err(Error::TypeError)?
]
Rem: rem = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_rem(b).unwrap_or_else(|| {
eprintln!("Warning: Divide by zero in {a} % {b}"); a
})),
_ => Err(Error::TypeError)?
]
Shl: shl = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shl(b as _)),
_ => Err(Error::TypeError)?
]
Shr: shr = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shr(b as _)),
_ => Err(Error::TypeError)?
]
Sub: sub = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(b)),
_ => Err(Error::TypeError)?
]
}
impl std::fmt::Display for ConValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConValue::Empty => "Empty".fmt(f),
ConValue::Int(v) => v.fmt(f),
ConValue::Bool(v) => v.fmt(f),
ConValue::Char(v) => v.fmt(f),
ConValue::String(v) => v.fmt(f),
ConValue::Ref(v) => write!(f, "&{v}"),
ConValue::Array(array) => {
'['.fmt(f)?;
for (idx, element) in array.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?
}
element.fmt(f)?
}
']'.fmt(f)
}
ConValue::RangeExc(a, b) => write!(f, "{a}..{}", b + 1),
ConValue::RangeInc(a, b) => write!(f, "{a}..={b}"),
ConValue::Tuple(tuple) => {
'('.fmt(f)?;
for (idx, element) in tuple.iter().enumerate() {
if idx > 0 {
", ".fmt(f)?
}
element.fmt(f)?
}
')'.fmt(f)
}
ConValue::Function(func) => {
write!(f, "{}", func.decl())
}
ConValue::BuiltIn(func) => {
write!(f, "{}", func.description())
}
}
}
}
}
pub mod convalue;
pub mod interpret;
pub mod function {
//! Represents a block of code which lives inside the Interpreter
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
use cl_ast::{Function as FnDecl, Param, Sym};
use std::rc::Rc;
/// Represents a block of code which persists inside the Interpreter
#[derive(Clone, Debug)]
pub struct Function {
/// Stores the contents of the function declaration
decl: Rc<FnDecl>,
// /// Stores the enclosing scope of the function
// env: Box<Environment>,
}
impl Function {
pub fn new(decl: &FnDecl) -> Self {
Self { decl: decl.clone().into() }
}
pub fn decl(&self) -> &FnDecl {
&self.decl
}
}
impl Callable for Function {
fn name(&self) -> Sym {
let FnDecl { name, .. } = *self.decl;
name
}
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
let FnDecl { name, bind, body, sign: _ } = &*self.decl;
// Check arg mapping
if args.len() != bind.len() {
return Err(Error::ArgNumber { want: bind.len(), got: args.len() });
}
let Some(body) = body else {
return Err(Error::NotDefined(*name));
};
// TODO: completely refactor data storage
let mut frame = env.frame("fn args");
for (Param { mutability: _, name }, value) in bind.iter().zip(args) {
frame.insert(*name, Some(value.clone()));
}
match body.interpret(&mut frame) {
Err(Error::Return(value)) => Ok(value),
Err(Error::Break(value)) => Err(Error::BadBreak(value)),
result => result,
}
}
}
}
pub mod function;
pub mod builtin;
pub mod env {
//! Lexical and non-lexical scoping for variables
use super::{
builtin::{BINARY, MISC, RANGE, UNARY},
convalue::ConValue,
error::{Error, IResult},
function::Function,
BuiltIn, Callable, Interpret,
};
use cl_ast::{Function as FnDecl, Sym};
use std::{
collections::HashMap,
fmt::Display,
ops::{Deref, DerefMut},
};
pub mod env;
type StackFrame = HashMap<Sym, Option<ConValue>>;
/// Implements a nested lexical scope
#[derive(Clone, Debug)]
pub struct Environment {
frames: Vec<(StackFrame, &'static str)>,
}
impl Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (frame, name) in self.frames.iter().rev() {
writeln!(f, "--- {name} ---")?;
for (var, val) in frame {
write!(f, "{var}: ")?;
match val {
Some(value) => writeln!(f, "\t{value}"),
None => writeln!(f, "<undefined>"),
}?
}
}
Ok(())
}
}
impl Default for Environment {
fn default() -> Self {
Self {
frames: vec![
(to_hashmap(RANGE), "range ops"),
(to_hashmap(UNARY), "unary ops"),
(to_hashmap(BINARY), "binary ops"),
(to_hashmap(MISC), "builtins"),
(HashMap::new(), "globals"),
],
}
}
}
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<Sym, Option<ConValue>> {
from.iter().map(|&v| (v.name(), Some(v.into()))).collect()
}
impl Environment {
pub fn new() -> Self {
Self::default()
}
/// Creates an [Environment] with no [builtins](super::builtin)
pub fn no_builtins(name: &'static str) -> Self {
Self { frames: vec![(Default::default(), name)] }
}
pub fn eval(&mut self, node: &impl Interpret) -> IResult<ConValue> {
node.interpret(self)
}
/// Calls a function inside the interpreter's scope,
/// and returns the result
pub fn call(&mut self, name: Sym, args: &[ConValue]) -> IResult<ConValue> {
// FIXME: Clone to satisfy the borrow checker
let function = self.get(name)?.clone();
function.call(self, args)
}
/// Enters a nested scope, returning a [`Frame`] stack-guard.
///
/// [`Frame`] implements Deref/DerefMut for [`Environment`].
pub fn frame(&mut self, name: &'static str) -> Frame {
Frame::new(self, name)
}
/// Resolves a variable mutably.
///
/// Returns a mutable reference to the variable's record, if it exists.
pub fn get_mut(&mut self, id: Sym) -> IResult<&mut Option<ConValue>> {
for (frame, _) in self.frames.iter_mut().rev() {
if let Some(var) = frame.get_mut(&id) {
return Ok(var);
}
}
Err(Error::NotDefined(id))
}
/// Resolves a variable immutably.
///
/// Returns a reference to the variable's contents, if it is defined and initialized.
pub fn get(&self, id: Sym) -> IResult<ConValue> {
for (frame, _) in self.frames.iter().rev() {
match frame.get(&id) {
Some(Some(var)) => return Ok(var.clone()),
Some(None) => return Err(Error::NotInitialized(id)),
_ => (),
}
}
Err(Error::NotDefined(id))
}
/// Inserts a new [ConValue] into this [Environment]
pub fn insert(&mut self, id: Sym, value: Option<ConValue>) {
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(id, value);
}
}
/// A convenience function for registering a [FnDecl] as a [Function]
pub fn insert_fn(&mut self, decl: &FnDecl) {
let FnDecl { name, .. } = decl;
let (name, function) = (name, Some(Function::new(decl).into()));
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(*name, function);
}
}
}
/// Functions which aid in the implementation of [`Frame`]
impl Environment {
/// Enters a scope, creating a new namespace for variables
fn enter(&mut self, name: &'static str) -> &mut Self {
self.frames.push((Default::default(), name));
self
}
/// Exits the scope, destroying all local variables and
/// returning the outer scope, if there is one
fn exit(&mut self) -> &mut Self {
if self.frames.len() > 2 {
self.frames.pop();
}
self
}
}
/// Represents a stack frame
#[derive(Debug)]
pub struct Frame<'scope> {
scope: &'scope mut Environment,
}
impl<'scope> Frame<'scope> {
fn new(scope: &'scope mut Environment, name: &'static str) -> Self {
Self { scope: scope.enter(name) }
}
}
impl<'scope> Deref for Frame<'scope> {
type Target = Environment;
fn deref(&self) -> &Self::Target {
self.scope
}
}
impl<'scope> DerefMut for Frame<'scope> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.scope
}
}
impl<'scope> Drop for Frame<'scope> {
fn drop(&mut self) {
self.scope.exit();
}
}
}
pub mod error {
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
use cl_ast::Sym;
use super::convalue::ConValue;
pub type IResult<T> = Result<T, Error>;
/// Represents any error thrown by the [Environment](super::Environment)
#[derive(Clone, Debug)]
pub enum Error {
/// Propagate a Return value
Return(ConValue),
/// Propagate a Break value
Break(ConValue),
/// Break propagated across function bounds
BadBreak(ConValue),
/// Continue to the next iteration of a loop
Continue,
/// Underflowed the stack
StackUnderflow,
/// Exited the last scope
ScopeExit,
/// Type incompatibility
// TODO: store the type information in this error
TypeError,
/// In clause of For loop didn't yield a Range
NotIterable,
/// A value could not be indexed
NotIndexable,
/// An array index went out of bounds
OobIndex(usize, usize),
/// An expression is not assignable
NotAssignable,
/// A name was not defined in scope before being used
NotDefined(Sym),
/// A name was defined but not initialized
NotInitialized(Sym),
/// A value was called, but is not callable
NotCallable(ConValue),
/// A function was called with the wrong number of arguments
ArgNumber {
want: usize,
got: usize,
},
Outlined(Sym),
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Return(value) => write!(f, "return {value}"),
Error::Break(value) => write!(f, "break {value}"),
Error::BadBreak(value) => write!(f, "rogue break: {value}"),
Error::Continue => "continue".fmt(f),
Error::StackUnderflow => "Stack underflow".fmt(f),
Error::ScopeExit => "Exited the last scope. This is a logic bug.".fmt(f),
Error::TypeError => "Incompatible types".fmt(f),
Error::NotIterable => "`in` clause of `for` loop did not yield an iterable".fmt(f),
Error::NotIndexable => {
write!(f, "expression cannot be indexed")
}
Error::OobIndex(idx, len) => {
write!(f, "Index out of bounds: index was {idx}. but len is {len}")
}
Error::NotAssignable => {
write!(f, "expression is not assignable")
}
Error::NotDefined(value) => {
write!(f, "{value} not bound. Did you mean `let {value};`?")
}
Error::NotInitialized(value) => {
write!(f, "{value} bound, but not initialized")
}
Error::NotCallable(value) => {
write!(f, "{value} is not callable.")
}
Error::ArgNumber { want, got } => {
write!(
f,
"Expected {want} argument{}, got {got}",
if *want == 1 { "" } else { "s" }
)
}
Error::Outlined(name) => {
write!(f, "Module {name} specified, but not imported.")
}
}
}
}
}
pub mod error;
#[cfg(test)]
mod tests;

View File

@@ -48,6 +48,7 @@ mod macros {
//! ```
#![allow(unused_macros)]
use crate::IResult;
use cl_parser::parser::Parse;
use super::*;
@@ -63,14 +64,14 @@ mod macros {
///
/// Returns a `Result<`[`File`]`, ParseError>`
pub macro file($($t:tt)*) {
Parser::new(Lexer::new(stringify!( $($t)* ))).file()
File::parse(&mut Parser::new(Lexer::new(stringify!( $($t)* ))))
}
/// Stringifies, lexes, and parses everything you give to it
///
/// Returns a `Result<`[`Block`]`, ParseError>`
pub macro block($($t:tt)*) {
Parser::new(Lexer::new(stringify!({ $($t)* }))).block()
Block::parse(&mut Parser::new(Lexer::new(stringify!({ $($t)* }))))
}
/// Evaluates a block of code in the given environment

View File

@@ -97,33 +97,33 @@ impl<'t> Lexer<'t> {
/// Scans through the text, searching for the next [Token]
pub fn scan(&mut self) -> LResult<Token> {
match self.skip_whitespace().peek()? {
'{' => self.consume()?.produce_op(Punct::LCurly),
'}' => self.consume()?.produce_op(Punct::RCurly),
'[' => self.consume()?.produce_op(Punct::LBrack),
']' => self.consume()?.produce_op(Punct::RBrack),
'(' => self.consume()?.produce_op(Punct::LParen),
')' => self.consume()?.produce_op(Punct::RParen),
'{' => self.consume()?.produce_op(Kind::LCurly),
'}' => self.consume()?.produce_op(Kind::RCurly),
'[' => self.consume()?.produce_op(Kind::LBrack),
']' => self.consume()?.produce_op(Kind::RBrack),
'(' => self.consume()?.produce_op(Kind::LParen),
')' => self.consume()?.produce_op(Kind::RParen),
'&' => self.consume()?.amp(),
'@' => self.consume()?.produce_op(Punct::At),
'\\' => self.consume()?.produce_op(Punct::Backslash),
'@' => self.consume()?.produce_op(Kind::At),
'\\' => self.consume()?.produce_op(Kind::Backslash),
'!' => self.consume()?.bang(),
'|' => self.consume()?.bar(),
':' => self.consume()?.colon(),
',' => self.consume()?.produce_op(Punct::Comma),
',' => self.consume()?.produce_op(Kind::Comma),
'.' => self.consume()?.dot(),
'=' => self.consume()?.equal(),
'`' => self.consume()?.produce_op(Punct::Grave),
'`' => self.consume()?.produce_op(Kind::Grave),
'>' => self.consume()?.greater(),
'#' => self.consume()?.hash(),
'<' => self.consume()?.less(),
'-' => self.consume()?.minus(),
'+' => self.consume()?.plus(),
'?' => self.consume()?.produce_op(Punct::Question),
'?' => self.consume()?.produce_op(Kind::Question),
'%' => self.consume()?.rem(),
';' => self.consume()?.produce_op(Punct::Semi),
';' => self.consume()?.produce_op(Kind::Semi),
'/' => self.consume()?.slash(),
'*' => self.consume()?.star(),
'~' => self.consume()?.produce_op(Punct::Tilde),
'~' => self.consume()?.produce_op(Kind::Tilde),
'^' => self.consume()?.xor(),
'0' => self.consume()?.int_with_base(),
'1'..='9' => self.digits::<10>(),
@@ -157,14 +157,14 @@ impl<'t> Lexer<'t> {
.copied()
.ok_or(Error::end_of_file(self.line(), self.col()))
}
fn produce(&mut self, kind: TokenKind, data: impl Into<TokenData>) -> LResult<Token> {
fn produce(&mut self, kind: Kind, data: impl Into<TokenData>) -> LResult<Token> {
let loc = self.start_loc;
self.start_loc = self.current_loc;
self.start = self.current;
Ok(Token::new(kind, data, loc.0, loc.1))
}
fn produce_op(&mut self, kind: Punct) -> LResult<Token> {
self.produce(TokenKind::Punct(kind), ())
fn produce_op(&mut self, kind: Kind) -> LResult<Token> {
self.produce(kind, ())
}
fn skip_whitespace(&mut self) -> &mut Self {
while let Ok(c) = self.peek() {
@@ -195,138 +195,147 @@ impl<'t> Lexer<'t> {
impl<'t> Lexer<'t> {
fn amp(&mut self) -> LResult<Token> {
match self.peek() {
Ok('&') => self.consume()?.produce_op(Punct::AmpAmp),
Ok('=') => self.consume()?.produce_op(Punct::AmpEq),
_ => self.produce_op(Punct::Amp),
Ok('&') => self.consume()?.produce_op(Kind::AmpAmp),
Ok('=') => self.consume()?.produce_op(Kind::AmpEq),
_ => self.produce_op(Kind::Amp),
}
}
fn bang(&mut self) -> LResult<Token> {
match self.peek() {
Ok('!') => self.consume()?.produce_op(Punct::BangBang),
Ok('=') => self.consume()?.produce_op(Punct::BangEq),
_ => self.produce_op(Punct::Bang),
Ok('!') => self.consume()?.produce_op(Kind::BangBang),
Ok('=') => self.consume()?.produce_op(Kind::BangEq),
_ => self.produce_op(Kind::Bang),
}
}
fn bar(&mut self) -> LResult<Token> {
match self.peek() {
Ok('|') => self.consume()?.produce_op(Punct::BarBar),
Ok('=') => self.consume()?.produce_op(Punct::BarEq),
_ => self.produce_op(Punct::Bar),
Ok('|') => self.consume()?.produce_op(Kind::BarBar),
Ok('=') => self.consume()?.produce_op(Kind::BarEq),
_ => self.produce_op(Kind::Bar),
}
}
fn colon(&mut self) -> LResult<Token> {
match self.peek() {
Ok(':') => self.consume()?.produce_op(Punct::ColonColon),
_ => self.produce_op(Punct::Colon),
Ok(':') => self.consume()?.produce_op(Kind::ColonColon),
_ => self.produce_op(Kind::Colon),
}
}
fn dot(&mut self) -> LResult<Token> {
match self.peek() {
Ok('.') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce_op(Punct::DotDotEq)
self.consume()?.produce_op(Kind::DotDotEq)
} else {
self.produce_op(Punct::DotDot)
self.produce_op(Kind::DotDot)
}
}
_ => self.produce_op(Punct::Dot),
_ => self.produce_op(Kind::Dot),
}
}
fn equal(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::EqEq),
Ok('>') => self.consume()?.produce_op(Punct::FatArrow),
_ => self.produce_op(Punct::Eq),
Ok('=') => self.consume()?.produce_op(Kind::EqEq),
Ok('>') => self.consume()?.produce_op(Kind::FatArrow),
_ => self.produce_op(Kind::Eq),
}
}
fn greater(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::GtEq),
Ok('=') => self.consume()?.produce_op(Kind::GtEq),
Ok('>') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce_op(Punct::GtGtEq)
self.consume()?.produce_op(Kind::GtGtEq)
} else {
self.produce_op(Punct::GtGt)
self.produce_op(Kind::GtGt)
}
}
_ => self.produce_op(Punct::Gt),
_ => self.produce_op(Kind::Gt),
}
}
fn hash(&mut self) -> LResult<Token> {
match self.peek() {
Ok('!') => self.consume()?.produce_op(Punct::HashBang),
_ => self.produce_op(Punct::Hash),
Ok('!') => self.consume()?.hashbang(),
_ => 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> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::LtEq),
Ok('=') => self.consume()?.produce_op(Kind::LtEq),
Ok('<') => {
if let Ok('=') = self.consume()?.peek() {
self.consume()?.produce_op(Punct::LtLtEq)
self.consume()?.produce_op(Kind::LtLtEq)
} else {
self.produce_op(Punct::LtLt)
self.produce_op(Kind::LtLt)
}
}
_ => self.produce_op(Punct::Lt),
_ => self.produce_op(Kind::Lt),
}
}
fn minus(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::MinusEq),
Ok('>') => self.consume()?.produce_op(Punct::Arrow),
_ => self.produce_op(Punct::Minus),
Ok('=') => self.consume()?.produce_op(Kind::MinusEq),
Ok('>') => self.consume()?.produce_op(Kind::Arrow),
_ => self.produce_op(Kind::Minus),
}
}
fn plus(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::PlusEq),
_ => self.produce_op(Punct::Plus),
Ok('=') => self.consume()?.produce_op(Kind::PlusEq),
_ => self.produce_op(Kind::Plus),
}
}
fn rem(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::RemEq),
_ => self.produce_op(Punct::Rem),
Ok('=') => self.consume()?.produce_op(Kind::RemEq),
_ => self.produce_op(Kind::Rem),
}
}
fn slash(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::SlashEq),
Ok('=') => self.consume()?.produce_op(Kind::SlashEq),
Ok('/') => self.consume()?.line_comment(),
Ok('*') => self.consume()?.block_comment(),
_ => self.produce_op(Punct::Slash),
_ => self.produce_op(Kind::Slash),
}
}
fn star(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::StarEq),
_ => self.produce_op(Punct::Star),
Ok('=') => self.consume()?.produce_op(Kind::StarEq),
_ => self.produce_op(Kind::Star),
}
}
fn xor(&mut self) -> LResult<Token> {
match self.peek() {
Ok('=') => self.consume()?.produce_op(Punct::XorEq),
Ok('^') => self.consume()?.produce_op(Punct::XorXor),
_ => self.produce_op(Punct::Xor),
Ok('=') => self.consume()?.produce_op(Kind::XorEq),
Ok('^') => self.consume()?.produce_op(Kind::XorXor),
_ => self.produce_op(Kind::Xor),
}
}
}
/// Comments
impl<'t> Lexer<'t> {
fn line_comment(&mut self) -> LResult<Token> {
let mut comment = String::new();
while Ok('\n') != self.peek() {
self.consume()?;
comment.push(self.next()?);
}
self.produce(Kind::Comment, ())
self.produce(Kind::Comment, comment)
}
fn block_comment(&mut self) -> LResult<Token> {
let mut comment = String::new();
while let Ok(c) = self.next() {
if '*' == c && Ok('/') == self.next() {
if '*' == c && Ok('/') == self.peek() {
break;
}
comment.push(c);
}
self.produce(Kind::Comment, ())
self.consume()?.produce(Kind::Comment, comment)
}
}
/// Identifiers

View File

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

View File

@@ -99,6 +99,7 @@ pub enum Parsing {
BinaryKind,
Unary,
UnaryKind,
Cast,
Index,
Structor,
Fielder,
@@ -204,6 +205,7 @@ impl Display for Parsing {
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",

View File

@@ -85,7 +85,7 @@ impl Fold for ModuleInliner {
Ok(file) => file,
};
let kind = match Parser::new(Lexer::new(&file)).file() {
let kind = match Parser::new(Lexer::new(&file)).parse() {
Err(e) => return self.handle_parse_error(e),
Ok(file) => ModuleKind::Inline(file),
};

File diff suppressed because it is too large Load Diff

View 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,
};
}

View File

@@ -1,5 +1,6 @@
//! 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};
@@ -19,7 +20,7 @@ fn main() -> Result<(), Box<dyn Error>> {
};
let mut parser = Parser::new(Lexer::new(&line));
let code = match parser.stmt() {
let code = match parser.parse::<Stmt>() {
Ok(code) => {
rl.accept();
code
@@ -363,7 +364,6 @@ pub mod yamlify {
fn yaml(&self, y: &mut Yamler) {
match self {
StmtKind::Empty => y,
StmtKind::Local(s) => y.yaml(s),
StmtKind::Item(s) => y.yaml(s),
StmtKind::Expr(s) => y.yaml(s),
};
@@ -388,10 +388,12 @@ pub mod yamlify {
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),
@@ -404,13 +406,14 @@ pub mod yamlify {
ExprKind::Empty => {}
ExprKind::Group(k) => k.yaml(y),
ExprKind::Tuple(k) => k.yaml(y),
ExprKind::Loop(k) => k.yaml(y),
ExprKind::While(k) => k.yaml(y),
ExprKind::If(k) => k.yaml(y),
ExprKind::For(k) => k.yaml(y),
ExprKind::Break(k) => k.yaml(y),
ExprKind::Return(k) => k.yaml(y),
ExprKind::Continue(k) => k.yaml(y),
ExprKind::Continue => {
y.key("Continue");
}
}
}
}
@@ -461,6 +464,12 @@ pub mod yamlify {
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;
@@ -529,12 +538,6 @@ pub mod yamlify {
y.key("Group").yaml(expr);
}
}
impl Yamlify for Loop {
fn yaml(&self, y: &mut Yamler) {
let Self { body } = self;
y.key("Loop").yaml(body);
}
}
impl Yamlify for While {
fn yaml(&self, y: &mut Yamler) {
let Self { cond, pass, fail } = self;
@@ -578,11 +581,6 @@ pub mod yamlify {
y.key("Return").yaml(body);
}
}
impl Yamlify for Continue {
fn yaml(&self, y: &mut Yamler) {
y.key("Continue");
}
}
impl Yamlify for Literal {
fn yaml(&self, y: &mut Yamler) {
y.value(format_args!("\"{self}\""));

View File

@@ -35,6 +35,10 @@ argwerk::define! {
[#[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

View File

@@ -5,6 +5,7 @@ use crate::{
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;
@@ -49,7 +50,7 @@ fn load_file(env: &mut Environment, path: impl AsRef<Path>) -> Result<ConValue,
let inliner =
cl_parser::inliner::ModuleInliner::new(path.as_ref().parent().unwrap_or(Path::new("")));
let file = std::fs::read_to_string(path)?;
let code = Parser::new(Lexer::new(&file)).file()?;
let code = Parser::new(Lexer::new(&file)).parse()?;
let code = match inliner.inline(code) {
Ok(a) => a,
Err((code, io_errs, parse_errs)) => {
@@ -79,13 +80,13 @@ fn lex_code(code: &str, path: Option<impl AsRef<Path>>) -> Result<(), Box<dyn Er
}
fn fmt_code(code: &str) -> Result<(), Box<dyn Error>> {
let code = Parser::new(Lexer::new(code)).file()?;
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)).file()?;
let code = Parser::new(Lexer::new(code)).parse::<File>()?;
match code.interpret(env)? {
ConValue::Empty => {}
ret => println!("{ret}"),

View File

@@ -1,4 +1,5 @@
use crate::{ansi, ctx};
use cl_ast::Stmt;
use cl_lexer::Lexer;
use cl_parser::Parser;
use repline::{error::ReplResult, prebaked::*};
@@ -42,7 +43,7 @@ pub fn run(ctx: &mut ctx::Context) -> ReplResult<()> {
use cl_parser::inliner::ModuleInliner;
read_and(ansi::CYAN, "cl>", " ?>", |line| {
let code = Parser::new(Lexer::new(line)).stmt()?;
let code = Parser::new(Lexer::new(line)).parse::<Stmt>()?;
let code = ModuleInliner::new(".").fold_stmt(code);
print!("{}", ansi::OUTPUT);
@@ -71,7 +72,7 @@ pub fn fmt(_ctx: &mut ctx::Context) -> ReplResult<()> {
read_and(ansi::BRIGHT_MAGENTA, "cl>", " ?>", |line| {
let mut p = Parser::new(Lexer::new(line));
match p.stmt() {
match p.parse::<Stmt>() {
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
Err(e) => Err(e)?,
}

View File

@@ -185,6 +185,24 @@ pub mod string_interner {
}
}
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

View File

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

View File

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

View File

@@ -1,30 +1,33 @@
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 cl_typeck::{
definition::Def,
handle::Handle,
name_collector::NameCollector,
node::{Node, NodeSource},
project::Project,
type_resolver::resolve,
};
use repline::{error::Error as RlError, prebaked::*};
use std::{error::Error, path};
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 STDLIB: &str = include_str!("../../../stdlib/lib.cl");
const PREAMBLE: &str = r"
pub mod std;
pub use std::preamble::*;
";
// Colors
const C_MAIN: &str = "";
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
@@ -33,44 +36,49 @@ const C_LISTING: &str = "\x1b[38;5;117m";
static mut TREES: TreeManager = TreeManager::new();
fn main() -> Result<(), Box<dyn Error>> {
let mut prj = Project::default();
let mut prj = Table::default();
let mut parser = Parser::new(Lexer::new(STDLIB));
let code = match parser.file() {
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)?
}
};
let code = inline_modules(code, concat!(env!("PWD"), "/stdlib"));
NameCollector::new(&mut prj).visit_file(unsafe { TREES.push(code) });
// 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 Project) -> Result<(), RlError> {
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)?,
"i" | "id" => get_by_id(prj)?,
"r" | "resolve" => resolve_all(prj)?,
"d" | "desugar" => live_desugar()?,
"h" | "help" => {
"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
id (i): Get a type by its type ID
resolve (r): Perform type resolution
desugar (d): WIP: Test the experimental desugaring passes
help (h): Print this list
exit (e): Exit the program"
);
@@ -82,24 +90,23 @@ fn main_menu(prj: &mut Project) -> Result<(), RlError> {
})
}
fn enter_code(prj: &mut Project) -> Result<(), RlError> {
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)).file()?;
let code = Parser::new(Lexer::new(line)).parse()?;
let code = inline_modules(code, "");
let code = WhileElseDesugar.fold_file(code);
// Safety: this is totally unsafe
NameCollector::new(prj).visit_file(unsafe { TREES.push(code) });
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)).stmt()?;
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);
@@ -115,37 +122,40 @@ fn live_desugar() -> Result<(), RlError> {
})
}
fn query_type_expression(prj: &mut Project) -> Result<(), RlError> {
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 = Parser::new(Lexer::new(line)).ty()?.kind;
let id = prj.evaluate(&ty, prj.root)?.handle_unchecked(prj);
pretty_handle(id)?;
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 Project) -> Result<(), RlError> {
fn get_by_id(prj: &mut Table) -> Result<(), RlError> {
use cl_parser::parser::Parse;
use cl_structures::index_map::MapIndex;
use cl_typeck::handle::DefID;
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 parser.literal()? {
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.path().unwrap_or_default();
let mut path = parser.parse::<cl_ast::Path>().unwrap_or_default();
path.absolute = false;
let Some(handle) = DefID::from_usize(def_id).handle(prj) else {
return Ok(Response::Deny);
};
let handle = Handle::from_usize(def_id).to_entry(prj);
print!(" > {{{C_LISTING}{handle}\x1b[0m}}");
if !path.parts.is_empty() {
@@ -153,71 +163,134 @@ fn get_by_id(prj: &mut Project) -> Result<(), RlError> {
}
println!();
let (ty, value) = handle.navigate((&path).into());
if let (None, None) = (ty, value) {
let Some(entry) = handle.nav(&path.parts) else {
Err("No results.")?
}
if let Some(t) = ty {
println!("Result in type namespace: {}", t.id());
pretty_handle(t)?;
}
if let Some(v) = value {
println!("Result in value namespace: {}", v.id());
pretty_handle(v)?;
}
};
pretty_handle(entry)?;
Ok(Response::Accept)
})
}
fn resolve_all(prj: &mut Project) -> Result<(), Box<dyn Error>> {
prj.resolve_imports()?;
for id in prj.pool.keys() {
resolve(prj, id)?;
fn resolve_all(table: &mut Table) -> Result<(), Box<dyn Error>> {
for (id, error) in import(table) {
eprintln!("{error} in {} ({id})", id.to_entry(table))
}
println!("Types resolved successfully!");
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(prj: &mut Project) {
println!(" name\x1b[30G type");
for (idx, key) in prj.pool.keys().enumerate() {
let Def { node: Node { vis, kind: source, .. }, .. } = &prj[key];
let name = match source.as_ref().map(NodeSource::name) {
Some(Some(name)) => name,
_ => "".into(),
};
print!(
"{idx:3}: {vis}{name}\x1b[30G = {}",
key.handle_unchecked(prj)
);
println!();
fn 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 pretty_handle(handle: Handle) -> Result<(), std::io::Error> {
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 stdout = std::io::stdout().lock();
let Some(Def { module, node: Node { vis, .. }, .. }) = handle.get() else {
return writeln!(stdout, "Invalid handle: {handle}");
let mut out = std::io::stdout().lock();
let Some(kind) = entry.kind() else {
return writeln!(out, "{entry}");
};
writeln!(stdout, "{C_LISTING}{vis}{handle}\x1b[0m: {}", handle.id())?;
if let Some(parent) = module.parent {
writeln!(stdout, "{C_LISTING}Parent\x1b[0m: {}", handle.with(parent))?;
write!(out, "{C_LISTING}{kind}")?;
if let Some(name) = entry.name() {
write!(out, " {name}")?;
}
if !module.types.is_empty() {
writeln!(stdout, "{C_LISTING}Types:\x1b[0m")?;
for (name, def) in &module.types {
writeln!(stdout, "- {C_LISTING}{name}\x1b[0m: {}", handle.with(*def))?
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 !module.values.is_empty() {
writeln!(stdout, "{C_LISTING}Values:\x1b[0m")?;
for (name, def) in &module.values {
writeln!(stdout, "- {C_LISTING}{name}\x1b[0m: {}", handle.with(*def))?
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)
)?
}
}
write!(stdout, "\x1b[0m")
Ok(())
}
fn inline_modules(code: cl_ast::File, path: impl AsRef<path::Path>) -> cl_ast::File {

View File

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

View File

@@ -0,0 +1,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)
}
}

View File

@@ -0,0 +1,100 @@
use super::*;
use crate::{format_utils::*, type_kind::Adt};
use std::fmt::{self, Write};
/// Printing the name of a named type stops infinite recursion
fn write_name_or(h: Entry, f: &mut impl Write) -> fmt::Result {
match h.name() {
Some(name) => write!(f, "{name}"),
None => write!(f, "{h}"),
}
}
impl fmt::Display for Entry<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(&kind) = self.kind() else {
return write!(f, "<invalid type: {}>", self.id);
};
if let Some(ty) = self.ty() {
match ty {
TypeKind::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"),
}
}

View File

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

View File

@@ -4,154 +4,71 @@
//!
//! This crate is a major work-in-progress.
//!
//! # The [Project](project::Project)™
//! Contains [item definitions](definition) and type expression information.
//! # The [Table](table::Table)™
//! A directed graph of nodes and their dependencies.
//!
//! *Every* definition is itself a module, and can contain arbitrarily nested items
//! as part of the [Module](module::Module) tree.
//! Contains [item definitions](handle) and [type expression](type_expression) information.
//!
//! The Project keeps track of a *global intern pool* of definitions, which are
//! trivially comparable by [DefID](key::DefID). Note that, for item definitions,
//! identical types in different modules DO NOT COMPARE EQUAL under this constraint.
//! However, so-called "anonymous" types *do not* follow this rule, as their
//! definitions are constructed dynamically and ensured to be unique.
// Note: it's a class invariant that named types are not added
// to the anon-types list. Please keep it that way. ♥ Thanks!
//! *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
//! Within a Project, [definitions](definition::Def) are classified into two namespaces:
//!
//! ## Type Namespace:
//! - Modules
//! - Structs
//! - Enums
//! - Type aliases
//!
//! ## Value Namespace:
//! - Functions
//! - Constants
//! - Static variables
//!
//! There is a *key* distinction between the two namespaces when it comes to
//! [Path](path::Path) traversal: Only items in the Type Namespace will be considered
//! as an intermediate path target. This means items in the Value namespace are
//! entirely *opaque*, and form a one-way barrier. Items outside a Value can be
//! referred to within the Value, but items inside a Value *cannot* be referred to
//! outside the Value.
//! 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:
//! Currently, the process of type resolution goes as follows:
//!
//! 1. Traverse the AST, [collecting all Items into item definitions](name_collector)
//! 2. Eagerly [resolve `use` imports](use_importer)
//! 3. Traverse all [definitions](definition::Def), and [resolve the types for every
//! item](type_resolver)
//! 4. TODO: Construct a typed AST for expressions, and type-check them
//! For order-of-operations information, see the [stage] module.
#![warn(clippy::all)]
/*
How do I flesh out modules in an incremental way?
1. Visit all *modules* and create nodes in a module tree for them
- This can be done by holding mutable references to submodules!
Module:
values: Map(name -> Value),
types: Map(name -> Type),
Value: Either
function: { signature: Type, body: FunctionBody }
static: { Mutability, ty: Type, init: ConstEvaluationResult }
const: { ty: Type, init: ConstEvaluationResult }
*/
pub mod handle;
pub mod node;
pub mod definition;
pub mod module;
pub mod path;
pub mod project;
pub mod name_collector;
pub mod use_importer;
pub mod type_resolver;
pub mod inference;
pub(crate) mod format_utils;
/*
pub mod table;
LET THERE BE NOTES:
pub mod handle;
/// What is an inference rule?
/// An inference rule is a specification with a set of predicates and a judgement
pub mod entry;
/// Let's give every type an ID
struct TypeID(usize);
pub mod source;
/// Let's give every type some data:
pub mod type_kind;
struct TypeDef<'def> {
name: String,
definition: &'def Item,
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;
}
and store them in a big vector of type descriptions:
struct TypeMap<'def> {
types: Vec<TypeDef<'def>>,
}
// todo: insertion of a type should yield a TypeID
// todo: impl index with TypeID
Let's store type information as either a concrete type or a generic type:
/// The Type struct represents all valid types, and can be trivially equality-compared
pub struct Type {
/// You can only have a pointer chain 65535 pointers long.
ref_depth: u16,
kind: TKind,
}
pub enum TKind {
Concrete(TypeID),
Generic(usize),
}
And assume I can specify a rule based on its inputs and outputs:
Rule {
operation: If,
/// The inputs field is populated by
inputs: [Concrete(BOOL), Generic(0), Generic(0)],
outputs: Generic(0),
/// This rule is compiler-intrinsic!
through: None,
}
Rule {
operation: Add,
inputs: [Concrete(I32), Concrete(I32)],
outputs: Concrete(I32),
/// This rule is not compiler-intrinsic (it is overloaded!)
through: Some(&ImplAddForI32::Add),
}
These rules can be stored in some kind of rule database:
let rules: Hashmap<Operation, Vec<Rule>> {
}
*/

View File

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

View File

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

View File

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

View File

@@ -1,60 +0,0 @@
//! A [Path] is a borrowed view of an [AST Path](AstPath)
use cl_ast::{Path as AstPath, PathPart};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Path<'p> {
pub absolute: bool,
pub parts: &'p [PathPart],
}
impl<'p> Path<'p> {
pub fn new(path: &'p AstPath) -> Self {
let AstPath { absolute, parts } = path;
Self { absolute: *absolute, parts }
}
pub fn relative(self) -> Self {
Self { absolute: false, ..self }
}
pub fn pop_front(self) -> Option<Self> {
let Self { absolute, parts } = self;
Some(Self { absolute, parts: parts.get(1..)? })
}
pub fn front(self) -> Option<Self> {
let Self { absolute, parts } = self;
Some(Self { absolute, parts: parts.get(..1)? })
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn len(&self) -> usize {
self.parts.len()
}
pub fn first(&self) -> Option<&PathPart> {
self.parts.first()
}
}
impl<'p> From<&'p AstPath> for Path<'p> {
fn from(value: &'p AstPath) -> Self {
Self::new(value)
}
}
impl AsRef<[PathPart]> for Path<'_> {
fn as_ref(&self) -> &[PathPart] {
self.parts
}
}
impl std::fmt::Display for Path<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const SEPARATOR: &str = "::";
let Self { absolute, parts } = self;
if *absolute {
write!(f, "{SEPARATOR}")?
}
for (idx, part) in parts.iter().enumerate() {
write!(f, "{}{part}", if idx > 0 { SEPARATOR } else { "" })?;
}
Ok(())
}
}

View File

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

View File

@@ -0,0 +1,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),
}
}
}

View 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}"),
}
}
}

View File

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

View File

@@ -0,0 +1,158 @@
//! An algorithm for importing external nodes
use crate::{
handle::Handle,
source::Source,
table::{NodeKind, Table},
};
use cl_ast::{PathPart, Sym, Use, UseTree};
use core::slice;
use std::{collections::HashSet, mem};
type Seen = HashSet<Handle>;
pub fn import<'a>(table: &mut Table<'a>) -> Vec<(Handle, Error<'a>)> {
let pending = mem::take(&mut table.uses);
let mut seen = Seen::new();
let mut failed = vec![];
for import in pending {
let Err(e) = import_one(table, import, &mut seen) else {
continue;
};
if let Error::NotFound(_, _) = e {
table.mark_use_item(import)
}
failed.push((import, e));
}
failed
}
fn import_one<'a>(table: &mut Table<'a>, item: Handle, seen: &mut Seen) -> UseResult<'a, ()> {
if !seen.insert(item) {
return Ok(());
}
let Some(NodeKind::Use) = table.kind(item) else {
Err(Error::ItsNoUse)?
};
let Some(&dst) = table.parent(item) else {
Err(Error::NoParents)?
};
let Some(code) = table.source(item) else {
Err(Error::NoSource)?
};
let &Source::Use(tree) = code else {
Err(Error::BadSource(*code))?
};
let Use { absolute, tree } = tree;
import_tree(
table,
if !absolute { dst } else { table.root() },
dst,
tree,
seen,
)
}
fn import_tree<'a>(
table: &mut Table<'a>,
src: Handle,
dst: Handle,
tree: &UseTree,
seen: &mut Seen,
) -> UseResult<'a, ()> {
match tree {
UseTree::Tree(trees) => trees
.iter()
.try_for_each(|tree| import_tree(table, src, dst, tree, seen)),
UseTree::Path(part, rest) => {
let source = table
.nav(src, slice::from_ref(part))
.ok_or_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}"),
}
}
}

View File

@@ -96,7 +96,7 @@ impl Type {
}
/// Checks whether there are any unbound type variables in this type.
/// ```rust
/// # use cl_typeck::inference::*;
/// # use cl_typeck::stage::infer::*;
/// let bool = Type::new_op("bool".into(), &[]);
/// let true_v = Type::new_inst(&bool);
/// let unbound = Type::new_var();
@@ -149,7 +149,7 @@ impl Type {
/// Panics if this type variable's instance field is already borrowed.
/// # Examples
/// ```rust
/// # use cl_typeck::inference::*;
/// # 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();

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

View 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"),
}
}
}
}

View 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)
}
}

View 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(())?,
})
}
}

View File

@@ -1,68 +1,17 @@
//! [Display] implementations for [TypeKind], [Adt], and [Intrinsic]
use super::{Adt, Def, DefKind, Intrinsic, TypeKind, ValueKind};
use crate::{format_utils::*, node::Node};
use super::{Adt, Intrinsic, TypeKind};
use crate::format_utils::*;
use cl_ast::format::FmtAdapter;
use std::fmt::{self, Display, Write};
impl Display for Def<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { module, node: Node { in_path: _, span: _, meta, vis, kind: source }, kind } =
self;
if !meta.is_empty() {
writeln!(f, "#{meta:?}")?;
}
if let Some(source) = source {
if let Some(name) = source.name() {
writeln!(f, "{vis}{name}:")?;
}
writeln!(f.indent(), "source:\n{source}")?;
} else {
writeln!(f, "{vis}: ")?;
}
writeln!(f, "kind: {kind}")?;
write!(f, "module: {module}")
}
}
impl Display for DefKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DefKind::Undecided => write!(f, "undecided"),
DefKind::Impl(id) => write!(f, "impl {id}"),
DefKind::Use(id) => write!(f, "use (inside {id})"),
DefKind::Type(kind) => write!(f, "{kind}"),
DefKind::Value(kind) => write!(f, "{kind}"),
}
}
}
impl std::fmt::Display for ValueKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValueKind::Const(id) => write!(f, "const ({id})"),
ValueKind::Static(id) => write!(f, "static ({id})"),
ValueKind::Local(id) => write!(f, "let ({id})"),
ValueKind::Fn(id) => write!(f, "fn def ({id})"),
}
}
}
impl Display for TypeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeKind::Alias(def) => match def {
Some(def) => write!(f, "alias to #{def}"),
None => f.write_str("type"),
},
TypeKind::Instance(def) => write!(f, "alias to #{def}"),
TypeKind::Intrinsic(i) => i.fmt(f),
TypeKind::Adt(a) => a.fmt(f),
TypeKind::Ref(cnt, def) => {
for _ in 0..*cnt {
f.write_str("&")?;
}
def.fmt(f)
}
TypeKind::Ref(def) => write!(f, "&{def}"),
TypeKind::Slice(def) => write!(f, "slice [#{def}]"),
TypeKind::Array(def, size) => write!(f, "array [#{def}; {size}]"),
TypeKind::Tuple(defs) => {
@@ -93,14 +42,6 @@ impl Display for Adt {
})
})(f.delimit_with("enum {", "}"))
}
Adt::CLikeEnum(variants) => {
let mut variants = variants.iter();
separate(", ", || {
let (name, descrim) = variants.next()?;
Some(move |f: &mut Delimit<_>| write!(f, "{name} = {descrim}"))
})(f.delimit_with("enum {", "}"))
}
Adt::FieldlessEnum => write!(f, "enum"),
Adt::Struct(members) => {
let mut members = members.iter();
separate(", ", || {
@@ -134,12 +75,20 @@ impl Display for Intrinsic {
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"),
}

View File

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

View File

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

View File

@@ -96,7 +96,9 @@ Shift = Factor (ShiftOp Factor )* ;
Factor = Term (FactorOp Term )* ;
Term = Unary (TermOp Unary )* ;
Unary = (UnaryKind)* Member ;
Unary = (UnaryKind)* Cast ;
Cast = Member ("as" Ty)? ;
Member = Call (Access)* ;
Access = '.' (Identifier ('(' Tuple? ')')? | Literal) ;

48
sample-code/ascii.cl Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env -S conlang -r false
//! Prints out the characters in the ASCII printable range
//! and the Latin-1 supplement in the format of a hex-dump
fn main () {
ascii()
}
fn n_digit(n: u32) -> char {
(if n > 9 {
('a' as u32) + n - 10
} else {
('0' as u32) + n
}) as char
}
fn in_range(num: u32, start: u32, end: u32) -> bool {
(start <= num) && (num <= end )
}
fn ascii() {
for row in 0..16 {
for col in 0..16 {
if col == 8 {
print(' ')
}
print(n_digit(row), n_digit(col), ' ')
}
print(" │")
for col in 0..16 {
print(ascii_picture(row << 4 | col))
}
println("│")
}
}
// Start of the C0 control pictures region
const CO_CONTROL_PICTURES: u32 = '\u{2400}' as u32;
fn ascii_picture(c: u32) -> char {
if c < ' ' as u32 { // C0
(CO_CONTROL_PICTURES + c) as char
} else if c == 127 { // C0:DEL
'' // SYMBOL_FOR_DELETE
} else if c.in_range(0x7f, 0xa0) { // C1
' '
} else c as char
}

7
sample-code/fib.cl Normal file → Executable file
View File

@@ -1,11 +1,12 @@
#!/usr/bin/env -S conlang -r false
// Calculate Fibonacci numbers
fn main() {
for num in 0..=30 {
print("fib(", num, ") = ", fib_iterative(num))
println("fib(", num, ") = ", fibit(num))
}
for num in 0..=30 {
print("fib(", num, ") = ", fib(num))
println("fib(", num, ") = ", fib(num))
}
}
@@ -17,7 +18,7 @@ fn fib(a: i64) -> i64 {
}
/// The classic iterative algorithm for fib()
fn fib_iterative(n: i64) -> i64 {
fn fibit(n: i64) -> i64 {
let mut a = 0;
let mut b = 1;
let mut c = 1;

5
sample-code/fizzbuzz.cl Normal file → Executable file
View File

@@ -1,13 +1,14 @@
#!/usr/bin/env -S conlang -r false
// FizzBuzz, using the unstable variadic-`print` builtin
fn main() {
fizzbuzz(10, 20)
fizzbuzz(0, 30)
}
// Outputs FizzBuzz for numbers between `start` and `end`, inclusive
fn fizzbuzz(start: i128, end: i128) {
for x in start..=end {
print(if x % 15 == 0 {
println(if x % 15 == 0 {
"FizzBuzz"
} else if 0 == x % 3 {
"Fizz"

5
sample-code/hello.cl Normal file → Executable file
View File

@@ -1,3 +1,6 @@
#!/usr/bin/env -S conlang -r false
//! Prints "Hello, world!"
fn main() {
print("Hello, world!")
println("Hello, world!")
}

View File

@@ -1,23 +1,39 @@
//! Formats numbers in hexadecimal, octal, or binary
mod math;
// TODO: casting and/or conversion
const HEX_LUT: Array = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
fn as_digit(n: u32) -> char {
(if n > 9 {
n - 10 + ('a' as u32)
} else {
n + ('0' as u32)
}) as char
}
pub fn radix(n: i64, radix: i64) {
fn r_str_radix(n: i64, radix: i64) {
if n != 0 {
r_str_radix(n / radix, radix) + as_digit(n % radix)
} else ""
}
if n == 0 {
"0"
} else if n < 0 {
// TODO: breaks at i64::MIN
"-" + r_str_radix(-n, radix)
} else r_str_radix(n, radix)
}
pub fn hex(n: u64) {
let out = "0x";
for xd in min(count_leading_zeroes(n) / 4, 15)..16 {
out += HEX_LUT[(n >> (15 - xd) * 4) & 0xf]
out += as_digit((n >> (15 - xd) * 4) & 0xf)
}
out
}
pub fn oct(n: u64) {
let out = "0o";
for xd in min((count_leading_zeroes(n) + 2) / 3, 21)..22 {
out += HEX_LUT[(n >> max(63 - (3 * xd), 0)) & 7]
out += as_digit((n >> max(63 - (3 * xd), 0)) & 7)
}
out
}
@@ -25,7 +41,7 @@ pub fn oct(n: u64) {
pub fn bin(n: u64) {
let out = "0b";
for xd in min(count_leading_zeroes(n), 63)..64 {
out += HEX_LUT[(n >> 63 - xd) & 1]
out += as_digit((n >> 63 - xd) & 1)
}
out
}

View File

@@ -1,9 +1,15 @@
//! Useful math functions
// FIXME:
// These two functions shouldn't actually be polymorphic, but
// the AST interpreter doesn't know about type annotations
// or operator overloading.
#[generic("T")]
pub fn max(a: T, b: T) -> T {
(if a < b { b } else { a })
}
#[generic("T")]
pub fn min(a: T, b: T) -> T {
(if a > b { b } else { a })
}

37
sample-code/rand.cl Normal file
View File

@@ -0,0 +1,37 @@
//! Pseudo-random number generation using a LFSR algorithm
static state: u64 = 0xdeadbeefdeadbeef;
pub fn seed(seed: u64) {
state = seed;
}
pub fn lfsr_next() {
state ^= state >> 7;
state ^= state << 9;
state ^= state >> 13;
}
/// Returns a pseudorandom byte
pub fn rand() -> u8 {
for _ in 0..8 {
lfsr_next()
}
state & 0xff
}
// Prints a maze out of diagonal box drawing characters, ['', '']
fn mazel(width: u64, height: u64) {
let walls = ['\u{2571}', '\u{2572}'];
rand_rect(width, height, walls)
}
// Prints a rectangle with the provided walls
fn rand_rect(width: u64, height: u64, walls: [char; 2]) {
for _ in 0..height {
for _ in 0..width {
print(walls[rand() % 2])
}
println()
}
}

View File

@@ -1,10 +1,12 @@
//! # The Conlang Standard Library
pub mod preamble {
pub use super::num::*;
pub use super::{num::*, str::str};
}
pub mod num;
pub mod str;
#[cfg("test")]
mod test;

View File

@@ -18,6 +18,9 @@ pub type i32;
#[intrinsic = "i64"]
pub type i64;
#[intrinsic = "i128"]
pub type i128;
#[intrinsic = "isize"]
pub type isize;
@@ -33,9 +36,18 @@ pub type u32;
#[intrinsic = "u64"]
pub type u64;
#[intrinsic = "u128"]
pub type u128;
#[intrinsic = "usize"]
pub type usize;
#[intrinsic = "f32"]
pub type f32;
#[intrinsic = "f64"]
pub type f64;
// Contains implementations for (TODO) overloaded operators on num types
pub mod ops {
use super::*;
@@ -226,6 +238,45 @@ pub mod ops {
}
}
impl u128 {
pub const MIN: Self = 0;
pub const MAX: Self = !0;
pub const BIT_WIDTH: u32 = 8;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl usize {
pub const MIN: Self = __march_ptr_width_unsigned_min();
pub const MAX: Self = __march_ptr_width_unsigned_max();
@@ -421,6 +472,45 @@ pub mod ops {
}
}
impl i128 {
pub const MIN: Self = !(1 << 128);
pub const MAX: Self = 1 << 128;
pub const BIT_WIDTH: u32 = 8;
pub fn default() -> Self {
0
}
pub fn mul(a: Self, b: Self) -> Self {
a * b
}
pub fn div(a: Self, b: Self) -> Self {
a / b
}
pub fn rem(a: Self, b: Self) -> Self {
a % b
}
pub fn add(a: Self, b: Self) -> Self {
a + b
}
pub fn sub(a: Self, b: Self) -> Self {
a - b
}
pub fn shl(a: Self, b: u32) -> Self {
a << b
}
pub fn shr(a: Self, b: u32) -> Self {
a >> b
}
pub fn and(a: Self, b: Self) -> Self {
a & b
}
pub fn or(a: Self, b: Self) -> Self {
a | b
}
pub fn xor(a: Self, b: Self) -> Self {
a ^ b
}
}
impl isize {
pub const MIN: Self = __march_ptr_width_signed_min();
pub const MAX: Self = __march_ptr_width_signed_max();

4
stdlib/std/str.cl Normal file
View File

@@ -0,0 +1,4 @@
//! TODO: give conland a string type
use super::num::u8;
type str = [u8];

View File

@@ -62,3 +62,18 @@ fn if_else() -> i32 {
}
// block 4
}
mod horrible_imports {
mod foo {
use super::{bar::*, baz::*};
struct Foo(&Foo, &Bar)
}
mod bar {
use super::{foo::*, baz::*};
struct Bar(&Foo, &Baz)
}
mod baz {
use super::{foo::*, bar::*};
struct Baz(&Foo, &Bar)
}
}