From efd442bbfa2fbcffe612b8b9d9a9a7d9ae30b50f Mon Sep 17 00:00:00 2001 From: John Date: Sat, 20 Apr 2024 14:51:54 -0500 Subject: [PATCH] conlang: Import items into scope with `use`! grammar: - Improve specification of `Path` - Add `Use` and `UseTree` rules - Add `Use` as a variant of ItemKind cl-token: - Add new keywords `use` and `as` cl-ast: - Add nodes for Use and UseTree - Add new ItemKind for Use - Implement traversal in Visit and Fold cl-interpret: - Mark ItemKind::Use with a todo cl-parser: - Update to match grammar cl-typeck: - Update to match changes in AST - Mark UseTrees as NameCollectable and TypeResolvable, but leave as todos --- compiler/cl-ast/src/ast.rs | 17 +++++++++ compiler/cl-ast/src/ast_impl.rs | 23 ++++++++++++ compiler/cl-ast/src/ast_visitor/fold.rs | 25 ++++++++++++ compiler/cl-ast/src/ast_visitor/visit.rs | 25 +++++++++++- compiler/cl-interpret/src/interpret.rs | 1 + compiler/cl-parser/src/error.rs | 10 +++-- compiler/cl-parser/src/parser.rs | 48 +++++++++++++++++++++--- compiler/cl-repl/examples/yaml.rs | 17 +++++++++ compiler/cl-token/src/token_type.rs | 6 +++ compiler/cl-typeck/src/lib.rs | 22 +++++++++++ grammar.ebnf | 9 ++++- 11 files changed, 192 insertions(+), 11 deletions(-) diff --git a/compiler/cl-ast/src/ast.rs b/compiler/cl-ast/src/ast.rs index 3249fd9..dd5d3f2 100644 --- a/compiler/cl-ast/src/ast.rs +++ b/compiler/cl-ast/src/ast.rs @@ -99,6 +99,8 @@ pub enum ItemKind { Function(Function), /// An [implementation](Impl) Impl(Impl), + /// An [import](Use) + Use(Use), } /// An alias to another [Ty] @@ -223,6 +225,21 @@ pub enum ImplKind { Trait { impl_trait: Path, for_type: Box }, } +/// An import of nonlocal [Item]s +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Use { + pub tree: UseTree, +} + +/// A tree of [Item] imports +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum UseTree { + Tree(Path, Vec), + Alias(Path, Identifier), + Path(Path), + Glob, +} + /// A type expression #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Ty { diff --git a/compiler/cl-ast/src/ast_impl.rs b/compiler/cl-ast/src/ast_impl.rs index 10562db..740d350 100644 --- a/compiler/cl-ast/src/ast_impl.rs +++ b/compiler/cl-ast/src/ast_impl.rs @@ -116,6 +116,7 @@ mod display { ItemKind::Struct(v) => v.fmt(f), ItemKind::Enum(v) => v.fmt(f), ItemKind::Impl(v) => v.fmt(f), + ItemKind::Use(v) => v.fmt(f), } } } @@ -280,6 +281,27 @@ mod display { } } + impl Display for Use { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { tree } = self; + write!(f, "use {tree}") + } + } + + impl Display for UseTree { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UseTree::Tree(path, tree) => { + write!(f, "{path}")?; + separate(tree, ", ")(f.delimit(INLINE_BRACES)) + } + UseTree::Alias(path, name) => write!(f, "{path} as {name}"), + UseTree::Path(path) => write!(f, "{path}"), + UseTree::Glob => write!(f, "*"), + } + } + } + impl Display for Ty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.kind.fmt(f) @@ -665,6 +687,7 @@ mod convert { Struct => ItemKind::Struct, Enum => ItemKind::Enum, Impl => ItemKind::Impl, + Use => ItemKind::Use, } impl From for StructKind { Vec => StructKind::Tuple, diff --git a/compiler/cl-ast/src/ast_visitor/fold.rs b/compiler/cl-ast/src/ast_visitor/fold.rs index 8ca6bd2..36481d8 100644 --- a/compiler/cl-ast/src/ast_visitor/fold.rs +++ b/compiler/cl-ast/src/ast_visitor/fold.rs @@ -158,6 +158,13 @@ pub trait Fold { fn fold_impl_kind(&mut self, kind: ImplKind) -> ImplKind { or_fold_impl_kind(self, kind) } + fn fold_use(&mut self, u: Use) -> Use { + let Use { tree } = u; + Use { tree: self.fold_use_tree(tree) } + } + fn fold_use_tree(&mut self, tree: UseTree) -> UseTree { + or_fold_use_tree(self, tree) + } fn fold_ty(&mut self, t: Ty) -> Ty { let Ty { extents, kind } = t; Ty { extents: self.fold_span(extents), kind: self.fold_ty_kind(kind) } @@ -375,6 +382,7 @@ pub fn or_fold_item_kind(folder: &mut F, kind: ItemKind) -> It ItemKind::Static(s) => ItemKind::Static(folder.fold_static(s)), ItemKind::Function(f) => ItemKind::Function(folder.fold_function(f)), ItemKind::Impl(i) => ItemKind::Impl(folder.fold_impl(i)), + ItemKind::Use(u) => ItemKind::Use(folder.fold_use(u)), } } @@ -441,6 +449,23 @@ pub fn or_fold_impl_kind(folder: &mut F, kind: ImplKind) -> Im } } +#[inline] +pub fn or_fold_use_tree(folder: &mut F, tree: UseTree) -> UseTree { + match tree { + UseTree::Tree(path, tree) => UseTree::Tree( + folder.fold_path(path), + tree.into_iter() + .map(|tree| folder.fold_use_tree(tree)) + .collect(), + ), + UseTree::Alias(path, name) => { + UseTree::Alias(folder.fold_path(path), folder.fold_identifier(name)) + } + UseTree::Path(path) => UseTree::Path(folder.fold_path(path)), + UseTree::Glob => UseTree::Glob, + } +} + #[inline] /// Folds a [TyKind] in the default way pub fn or_fold_ty_kind(folder: &mut F, kind: TyKind) -> TyKind { diff --git a/compiler/cl-ast/src/ast_visitor/visit.rs b/compiler/cl-ast/src/ast_visitor/visit.rs index b5ee6be..dd7992c 100644 --- a/compiler/cl-ast/src/ast_visitor/visit.rs +++ b/compiler/cl-ast/src/ast_visitor/visit.rs @@ -125,11 +125,18 @@ pub trait Visit<'a>: Sized { fn visit_impl(&mut self, i: &'a Impl) { let Impl { target, body } = i; self.visit_impl_kind(target); - self.visit_file(body) + self.visit_file(body); } fn visit_impl_kind(&mut self, target: &'a ImplKind) { or_visit_impl_kind(self, target) } + fn visit_use(&mut self, u: &'a Use) { + let Use { tree } = u; + self.visit_use_tree(tree); + } + fn visit_use_tree(&mut self, tree: &'a UseTree) { + or_visit_use_tree(self, tree) + } fn visit_ty(&mut self, t: &'a Ty) { let Ty { extents, kind } = t; self.visit_span(extents); @@ -320,6 +327,7 @@ pub fn or_visit_item_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a ItemKind) ItemKind::Static(s) => visitor.visit_static(s), ItemKind::Function(f) => visitor.visit_function(f), ItemKind::Impl(i) => visitor.visit_impl(i), + ItemKind::Use(u) => visitor.visit_use(u), } } @@ -364,6 +372,21 @@ pub fn or_visit_impl_kind<'a, V: Visit<'a>>(visitor: &mut V, target: &'a ImplKin } } +pub fn or_visit_use_tree<'a, V: Visit<'a>>(visitor: &mut V, tree: &'a UseTree) { + match tree { + UseTree::Tree(path, tree) => { + visitor.visit_path(path); + tree.iter().for_each(|tree| visitor.visit_use_tree(tree)); + } + UseTree::Alias(path, name) => { + visitor.visit_path(path); + visitor.visit_identifier(name); + } + UseTree::Path(path) => visitor.visit_path(path), + UseTree::Glob => {} + } +} + pub fn or_visit_ty_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a TyKind) { match kind { TyKind::Never => {} diff --git a/compiler/cl-interpret/src/interpret.rs b/compiler/cl-interpret/src/interpret.rs index 96da7c7..5e74275 100644 --- a/compiler/cl-interpret/src/interpret.rs +++ b/compiler/cl-interpret/src/interpret.rs @@ -36,6 +36,7 @@ impl Interpret for Item { ItemKind::Struct(item) => item.interpret(env), ItemKind::Enum(item) => item.interpret(env), ItemKind::Impl(item) => item.interpret(env), + ItemKind::Use(_) => todo!("namespaces and imports in the interpreter"), } } } diff --git a/compiler/cl-parser/src/error.rs b/compiler/cl-parser/src/error.rs index 6fcd1ac..c31c4ac 100644 --- a/compiler/cl-parser/src/error.rs +++ b/compiler/cl-parser/src/error.rs @@ -30,7 +30,7 @@ pub enum ErrorKind { want: Parsing, }, /// Indicates unfinished code - Todo, + Todo(&'static str), } impl From for ErrorKind { fn from(value: LexError) -> Self { @@ -69,6 +69,8 @@ pub enum Parsing { VariantKind, Impl, ImplKind, + Use, + UseTree, Ty, TyKind, @@ -116,7 +118,7 @@ impl Display for Error { let Self { reason, while_parsing, loc } = self; match reason { // TODO entries are debug-printed - ErrorKind::Todo => write!(f, "{loc} {reason} {while_parsing:?}"), + ErrorKind::Todo(_) => write!(f, "{loc} {reason} {while_parsing:?}"), // lexical errors print their own higher-resolution loc info ErrorKind::Lexical(e) => write!(f, "{e} (while parsing {while_parsing})"), _ => write!(f, "{loc} {reason} while parsing {while_parsing}"), @@ -134,7 +136,7 @@ impl Display for ErrorKind { ErrorKind::Unexpected(t) => write!(f, "Encountered unexpected token `{t}`"), ErrorKind::ExpectedToken { want: e, got: g } => write!(f, "Expected `{e}`, got `{g}`"), ErrorKind::ExpectedParsing { want } => write!(f, "Expected {want}"), - ErrorKind::Todo => write!(f, "TODO:"), + ErrorKind::Todo(unfinished) => write!(f, "TODO: {unfinished}"), } } } @@ -166,6 +168,8 @@ impl Display for Parsing { Parsing::VariantKind => "an enum variant", Parsing::Impl => "an impl block", Parsing::ImplKind => "the target of an impl block", + Parsing::Use => "a use item", + Parsing::UseTree => "a use-tree", Parsing::Ty => "a type", Parsing::TyKind => "a type", diff --git a/compiler/cl-parser/src/parser.rs b/compiler/cl-parser/src/parser.rs index 518cb99..cf39986 100644 --- a/compiler/cl-parser/src/parser.rs +++ b/compiler/cl-parser/src/parser.rs @@ -162,6 +162,7 @@ macro item_like() { | TokenKind::Struct | TokenKind::Enum | TokenKind::Impl + | TokenKind::Use } /// Top level parsing @@ -204,17 +205,24 @@ impl<'t> Parser<'t> { /// /// See also: [Parser::path_part], [Parser::identifier] /// - /// [Path] = `::`? ([PathPart] `::`)* [PathPart]? + /// [Path] = `::` *RelativePath*? | *RelativePath* \ + /// *RelativePath* = [PathPart] (`::` [PathPart])* pub fn path(&mut self) -> PResult { const PARSING: Parsing = Parsing::PathExpr; let absolute = self.match_op(Punct::ColonColon, PARSING).is_ok(); let mut parts = vec![]; - while let Ok(path_part) = self.path_part() { - parts.push(path_part); - if self.match_op(Punct::ColonColon, PARSING).is_err() { - break; + if absolute { + match self.path_part() { + Ok(part) => parts.push(part), + Err(_) => return Ok(Path { absolute, parts }), } + } else { + parts.push(self.path_part()?) + }; + + while self.match_op(Punct::ColonColon, Parsing::PathExpr).is_ok() { + parts.push(self.path_part()?) } Ok(Path { absolute, parts }) @@ -295,6 +303,7 @@ impl<'t> Parser<'t> { TokenKind::Struct => self.parse_struct()?.into(), TokenKind::Enum => self.parse_enum()?.into(), TokenKind::Impl => self.parse_impl()?.into(), + TokenKind::Use => self.parse_use()?.into(), t => Err(self.error(Unexpected(t), Parsing::Item))?, }) } @@ -596,6 +605,35 @@ impl<'t> Parser<'t> { } } + pub fn parse_use(&mut self) -> PResult { + self.consume_peeked(); + Ok(Use { tree: self.parse_use_tree()? }) + } + + pub fn parse_use_tree(&mut self) -> PResult { + const PARSING: Parsing = Parsing::UseTree; + // glob import + if self.match_op(Punct::Star, PARSING).is_ok() { + return Ok(UseTree::Glob); + } + let path = self.path()?; + Ok(match self.peek_kind(PARSING) { + Ok(TokenKind::As) => { + self.consume_peeked(); + UseTree::Alias(path, self.identifier()?) + } + Ok(TokenKind::Punct(Punct::LCurly)) => UseTree::Tree( + path, + delim( + sep(Self::parse_use_tree, Punct::Comma, CURLIES.1, PARSING), + CURLIES, + PARSING, + )(self)?, + ), + _ => UseTree::Path(path), + }) + } + /// [Visibility] = `pub`? pub fn visibility(&mut self) -> Visibility { match self.match_type(TokenKind::Pub, Parsing::Visibility) { diff --git a/compiler/cl-repl/examples/yaml.rs b/compiler/cl-repl/examples/yaml.rs index 14984b4..ddb34eb 100644 --- a/compiler/cl-repl/examples/yaml.rs +++ b/compiler/cl-repl/examples/yaml.rs @@ -207,6 +207,7 @@ pub mod yamlify { ItemKind::Struct(f) => y.yaml(f), ItemKind::Enum(f) => y.yaml(f), ItemKind::Impl(f) => y.yaml(f), + ItemKind::Use(f) => y.yaml(f), }; } } @@ -322,6 +323,22 @@ pub mod yamlify { }; } } + impl Yamlify for Use { + fn yaml(&self, y: &mut Yamler) { + let Self { tree } = self; + y.key("Use").yaml(tree); + } + } + impl Yamlify for UseTree { + fn yaml(&self, y: &mut Yamler) { + match self { + UseTree::Tree(path, tree) => y.pair("path", path).pair("tree", tree), + UseTree::Alias(path, name) => y.pair("path", path).pair("name", name), + UseTree::Path(path) => y.pair("path", path), + UseTree::Glob => y.value("Glob"), + }; + } + } impl Yamlify for Block { fn yaml(&self, y: &mut Yamler) { let Self { stmts } = self; diff --git a/compiler/cl-token/src/token_type.rs b/compiler/cl-token/src/token_type.rs index 12e5f2f..b24ec62 100644 --- a/compiler/cl-token/src/token_type.rs +++ b/compiler/cl-token/src/token_type.rs @@ -13,6 +13,7 @@ pub enum TokenKind { /// A non-keyword identifier Identifier, // A keyword + As, Break, Cl, Const, @@ -38,6 +39,7 @@ pub enum TokenKind { Super, True, Type, + Use, While, /// Delimiter or punctuation Punct(Punct), @@ -110,6 +112,7 @@ impl Display for TokenKind { TokenKind::Literal => "literal".fmt(f), TokenKind::Identifier => "identifier".fmt(f), + TokenKind::As => "as".fmt(f), TokenKind::Break => "break".fmt(f), TokenKind::Cl => "cl".fmt(f), TokenKind::Const => "const".fmt(f), @@ -135,6 +138,7 @@ impl Display for TokenKind { TokenKind::Super => "super".fmt(f), TokenKind::True => "true".fmt(f), TokenKind::Type => "type".fmt(f), + TokenKind::Use => "use".fmt(f), TokenKind::While => "while".fmt(f), TokenKind::Punct(op) => op.fmt(f), @@ -147,6 +151,7 @@ impl FromStr for TokenKind { /// Parses a string s to return a Keyword fn from_str(s: &str) -> Result { Ok(match s { + "as" => Self::As, "break" => Self::Break, "cl" => Self::Cl, "const" => Self::Const, @@ -172,6 +177,7 @@ impl FromStr for TokenKind { "super" => Self::Super, "true" => Self::True, "type" => Self::Type, + "use" => Self::Use, "while" => Self::While, _ => Err(())?, }) diff --git a/compiler/cl-typeck/src/lib.rs b/compiler/cl-typeck/src/lib.rs index c1cfa82..ad3ed62 100644 --- a/compiler/cl-typeck/src/lib.rs +++ b/compiler/cl-typeck/src/lib.rs @@ -697,6 +697,7 @@ pub mod name_collector { ItemKind::Const(i) => i.collect(c, parent), ItemKind::Static(i) => i.collect(c, parent), ItemKind::Function(i) => i.collect(c, parent), + ItemKind::Use(i) => i.collect(c, parent), }?; c[id].set_meta(meta).set_vis(*vis).set_source(self); @@ -808,6 +809,17 @@ pub mod name_collector { Ok(id) } } + impl<'a> NameCollectable<'a> for Use { + fn collect(&'a self, _c: &mut Prj<'a>, parent: DefID) -> Result { + let Self { tree } = self; + todo!("Use {tree} in {parent}") + } + } + impl<'a> NameCollectable<'a> for UseTree { + fn collect(&'a self, _c: &mut Prj<'a>, parent: DefID) -> Result { + todo!("Use {self} in {parent}") + } + } impl<'a> NameCollectable<'a> for Block { fn collect(&'a self, c: &mut Prj<'a>, parent: DefID) -> Result { self.stmts.as_slice().collect(c, parent) @@ -917,6 +929,7 @@ pub mod type_resolver { ItemKind::Static(_) => "static", ItemKind::Function(_) => "fn", ItemKind::Impl(_) => "impl", + ItemKind::Use(_) => "use", }; eprintln!( "Resolver: \x1b[32mEvaluating\x1b[0m \"\x1b[36m{kind} {}\x1b[0m\" (`{id:?}`)", @@ -984,6 +997,7 @@ pub mod type_resolver { ItemKind::Const(i) => i.resolve_type(prj, id), ItemKind::Static(i) => i.resolve_type(prj, id), ItemKind::Function(i) => i.resolve_type(prj, id), + ItemKind::Use(i) => i.resolve_type(prj, id), } } } @@ -1014,6 +1028,14 @@ pub mod type_resolver { } } + impl<'a> TypeResolvable<'a> for Use { + type Out = DefKind<'a>; + + fn resolve_type(&'a self, prj: &mut Prj<'a>, id: DefID) -> Result { + todo!("Resolve types for {self} with ID {id} in {prj:?}") + } + } + impl<'a> TypeResolvable<'a> for Alias { type Out = DefKind<'a>; diff --git a/grammar.ebnf b/grammar.ebnf index 110c694..85e7da1 100644 --- a/grammar.ebnf +++ b/grammar.ebnf @@ -15,7 +15,7 @@ Meta = Identifier ('=' Literal | '(' (Literal ',')* Literal? ')')? ; Item = Attrs Visibility ItemKind ; ItemKind = Const | Static | Module | Function | Struct | Enum - | Alias | Impl ; + | Alias | Impl | Use ; (* item *) @@ -45,6 +45,10 @@ Alias = "type" Identifier ('=' Ty)? ';' ; Impl = "impl" Path '{' Item* '}' ; (* TODO: Impl Trait for Target*) +Use = "use" UseTree ; +UseTree = Path '{' (UseTree ',')* UseTree? '}' + | Path "as" Identifier + | Path | '*' ; (* type *) Ty = Never | Empty | Path | TyTuple | TyRef | TyFn ; @@ -57,7 +61,8 @@ TyFn = "fn" TyTuple ('->' Ty)? ; (* path *) -Path = '::'? PathPart ('::' PathPart)* ; +Path = PathPart ('::' PathPart)* + | '::' (PathPart ('::' PathPart)*)? ; PathPart = "super" | "self" | Identifier ; Identifier = IDENTIFIER ;