diff --git a/src/bin/doughlang.rs b/src/bin/doughlang.rs index f17700e..40503bb 100644 --- a/src/bin/doughlang.rs +++ b/src/bin/doughlang.rs @@ -1,13 +1,15 @@ //! Tests the lexer\ use doughlang::{ ast::{ - Anno, Annotation, Bind, Expr, Pat, Use, + Anno, Annotation, Bind, DefaultTypes, Expr, Pat, Use, macro_matcher::{Match, Subst}, + visit::Walk, }, lexer::{EOF, LexError, Lexer}, parser::{Parse, ParseError, Parser}, span::Span, token::{TKind, Token}, + typeck::Collector, }; use repline::prebaked::*; use std::{ @@ -25,6 +27,7 @@ enum Verbosity { #[default] Pretty, Debug, + Frob, Quiet, } impl From<&str> for Verbosity { @@ -32,6 +35,7 @@ impl From<&str> for Verbosity { match value { "quiet" | "false" | "0" | "no" => Verbosity::Quiet, "debug" | "d" => Verbosity::Debug, + "frob" => Verbosity::Frob, "pretty" => Verbosity::Pretty, _ => Default::default(), } @@ -99,7 +103,7 @@ fn main() -> Result<(), Box> { println!("Parse mode set to '{parsing:?}'"); Ok(Response::Accept) } - line @ ("quiet" | "debug" | "pretty") => { + line @ ("quiet" | "debug" | "frob" | "pretty") => { verbose = Verbosity::from(line); println!("Verbosity set to '{verbose:?}'"); Ok(Response::Accept) @@ -194,7 +198,10 @@ fn tokens<'t, T: Parse<'t> + ?Sized>(document: &'t str, verbose: Verbosity) { } } -fn parse<'t, T: Parse<'t> + Annotation>(document: &'t str, verbose: Verbosity) { +fn parse<'t, T: Parse<'t> + Annotation + for<'a> Walk<'a, DefaultTypes>>( + document: &'t str, + verbose: Verbosity, +) { let mut parser = Parser::new(Lexer::new(document)); for idx in 0.. { match (parser.parse::>(T::Prec::default()), verbose) { @@ -223,6 +230,10 @@ fn parse<'t, T: Parse<'t> + Annotation>(document: &'t str, verbose: Verbosity) { (Ok(Anno(expr, span)), Verbosity::Pretty) => { println!("\x1b[{}m{span}:\n{expr}", (idx + 5) % 6 + 31); } + (Ok(Anno(expr, span)), Verbosity::Frob) => { + println!("\x1b[{}m{span}:\n", (idx + 5) % 6 + 31); + let _ = expr.visit_in(&mut Collector::new()); + } (Ok(Anno(expr, span)), Verbosity::Debug) => { println!("\x1b[{}m{span}:\n{expr:#?}", (idx + 5) % 6 + 31); } diff --git a/src/lib.rs b/src/lib.rs index f24beb9..385f23d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,8 +18,6 @@ pub mod parser; pub mod typed_ast { //! The Typed AST defines an interface between the type checker and code generator - - use crate::span::Span; use std::collections::HashMap; pub struct Table { @@ -29,16 +27,278 @@ pub mod typed_ast { pub local: HashMap, } - /// `DefID` annotation - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct Defn { - pub span: Span, - /// The index of this name in the associated Table - pub defid: usize, + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub enum Vis { + #[default] + Private, + Public, + } + + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub enum Mut { + #[default] + Not, + Mut, + } + + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub enum Binding { + #[default] + Const, + Static, + Local, } } -pub mod typeck {} +pub mod typeck { + use crate::{ + ast::{ + types::Symbol, + visit::{Visit, Walk}, + *, + }, + typed_ast::{Binding, Mut, Vis}, + }; + + /* + TODO: + - Make Expr polymorphic over the Path type + - Construct disjoint set forest of names + - Replace names with their resolved module identifiers + + + Pattern matching + + A ::= Decision trees + Leaf(k) success (k is an action, an integer) + Fail failure + Switch(L) multi-way test (o is an occurrence) + Swap(A) stack swap (i is an integer) + + + + What information do I need to collect before type inference can be run? + 1. Where all the items are + 2. Where all the imports link to + + How do I represent that information? + + - Directed graph? + + How do I represent a *lazy* back edge? + */ + + #[derive(Clone, Copy, Debug, Default)] + pub enum List<'parent, T> { + Cons(&'parent List<'parent, T>, T), + #[default] + Nil, + } + + impl<'parent, T> List<'parent, T> { + pub fn new(value: T) -> List<'static, T> { + List::Cons(&List::Nil, value) + } + + pub fn enter<'a>(&'a self, value: T) -> List<'a, T> + where T: 'a { + List::Cons(self, value) + } + + pub fn parent(&self) -> Option<&List<'parent, T>> { + match self { + Self::Cons(parent, _) => Some(parent), + Self::Nil => None, + } + } + } + + type Path<'parent> = List<'parent, Symbol>; + + impl From> for crate::ast::types::Path { + fn from(value: Path<'_>) -> Self { + fn inner(path: &Path<'_>, vec: &mut Vec) { + match path { + List::Nil => {} + &List::Cons(nested, name) => { + inner(nested, vec); + vec.push(name); + } + } + } + + let mut parts = vec![]; + inner(&value, &mut parts); + Self { parts } + } + } + + impl std::fmt::Display for Path<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Cons(Self::Nil, name) => name.fmt(f), + Self::Cons(parent, name) => write!(f, "{parent}::{name}"), + Self::Nil => Ok(()), + } + } + } + + #[derive(Debug)] + pub struct Collector<'parent, 'source, A: AstTypes> { + pub path: List<'parent, &'parent A::Symbol>, + pub meta: List<'parent, &'source Expr>, + pub visible: Vis, + pub mutable: Mut, + pub binding: Binding, + } + + impl<'parent, 'source, A: AstTypes> Default for Collector<'parent, 'source, A> { + fn default() -> Self { + Self { + path: Default::default(), + meta: Default::default(), + visible: Default::default(), + mutable: Default::default(), + binding: Default::default(), + } + } + } + + impl<'a, 'source, A: AstTypes> Collector<'a, 'source, A> { + pub fn new() -> Collector<'static, 'static, A> { + Collector::default() + } + + pub fn scope<'b>(&'b self, name: &'b A::Symbol) -> Collector<'b, 'source, A> { + Collector { path: self.path.enter(name), ..*self } + } + + pub fn module<'b>(&'b self, part: &'b A::Symbol) -> Collector<'b, 'source, A> { + let Collector { path, .. } = self; + Collector { path: path.enter(part), binding: Binding::Const, ..Default::default() } + } + + pub fn function<'b>(&'b self, part: &'b A::Symbol) -> Collector<'b, 'source, A> { + let Collector { path, .. } = self; + Collector { path: path.enter(part), binding: Binding::Local, ..Default::default() } + } + + pub fn meta<'e>(&self, expr: &'e Expr) -> Collector<'_, 'e, A> + where 'source: 'e { + let &Self { path, ref meta, .. } = self; + Collector { path, meta: meta.enter(expr), ..*self } + } + + pub fn visible(&self, visible: Vis) -> Self { + Self { visible, ..*self } + } + pub fn mutable(&self, mutable: Mut) -> Self { + Self { mutable, ..*self } + } + pub fn bindmode(&self, binding: Binding) -> Self { + Self { binding, ..*self } + } + } + + impl std::fmt::Display for Collector<'_, '_, A> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { path, meta, visible, mutable, binding } = self; + + fn walk_meta( + f: &mut std::fmt::Formatter<'_>, + meta: &List<'_, &Expr>, + ) -> std::fmt::Result { + match meta { + List::Cons(nested, expr) => { + walk_meta(f, nested)?; + writeln!(f, "{expr}") + } + List::Nil => Ok(()), + } + } + fn walk_path( + f: &mut std::fmt::Formatter<'_>, + meta: &List<'_, &D>, + ) -> std::fmt::Result { + match meta { + List::Cons(nested, expr) => { + walk_path(f, nested)?; + write!(f, "::{expr}") + } + List::Nil => Ok(()), + } + } + + walk_meta(f, meta)?; + match binding { + Binding::Const => "const ", + Binding::Static => "static ", + Binding::Local => "local ", + } + .fmt(f)?; + + match visible { + Vis::Private => "", + Vis::Public => "pub ", + } + .fmt(f)?; + + match mutable { + Mut::Not => "", + Mut::Mut => "mut ", + } + .fmt(f)?; + + walk_path(f, path) + } + } + + impl<'b> Visit<'b, DefaultTypes> for Collector<'_, '_, DefaultTypes> { + type Error = (); + + fn visit_expr(&mut self, expr: &'b Expr) -> Result<(), Self::Error> { + match expr { + Expr::Op(Op::Pub, exprs) => self.visible(Vis::Public).visit(exprs), + Expr::Op(Op::Const, exprs) => self.bindmode(Binding::Const).visit(exprs), + Expr::Op(Op::Static, exprs) => self.bindmode(Binding::Static).visit(exprs), + Expr::Op(Op::Meta, exprs) => match exprs.as_slice() { + [Anno(meta, _), exprs @ ..] => self.meta(meta).visit(exprs), + [] => Ok(()), + }, + _ => expr.children(self), + } + } + + fn visit_bind(&mut self, item: &'b Bind) -> Result<(), Self::Error> { + if let Bind(BindOp::Match | BindOp::Impl, _, pat, ..) = item { + // TODO: convert patterns into concrete type annotations + return item.children(&mut self.scope(&pat.to_string().as_str().into())); + } + println!("{self}::{}", item.2); + + match item { + Bind(BindOp::Fn, _, Pat::Op(PatOp::TypePrefixed, pats), exprs) => { + match pats.as_slice() { + [Pat::Name(name), ..] => self.function(name).visit(exprs), + [other, ..] => { + println!("Warning: unhandled fn pattern: {other}"); + Ok(()) + } + [] => Ok(()), + } + } + Bind(BindOp::Mod, _, Pat::Name(name), exprs) => self.module(name).visit(exprs), + _ => item.children( + &mut self + .visible(Vis::Private) + .scope(&Symbol::from(format!("{:?}", item.0).as_str())), + ), + }?; + + Ok(()) + } + } +} pub mod ir { //! The IR defines an interface between the code generator and interpreter(?)