diff --git a/Cargo.toml b/Cargo.toml index fd93385..1db2e31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [workspace] -members = ["libconlang"] +members = ["libconlang", "cl-frontend"] resolver = "2" [workspace.package] repository = "https://git.soft.fish/j/Conlang" -version = "0.0.1" +version = "0.0.2" authors = ["John Breaux "] edition = "2021" license = "MIT" diff --git a/cl-frontend/Cargo.toml b/cl-frontend/Cargo.toml new file mode 100644 index 0000000..caa01aa --- /dev/null +++ b/cl-frontend/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cl-frontend" +repository.workspace = true +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +publish.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +conlang = { path = "../libconlang" } diff --git a/libconlang/examples/identify_tokens.rs b/cl-frontend/examples/identify_tokens.rs similarity index 100% rename from libconlang/examples/identify_tokens.rs rename to cl-frontend/examples/identify_tokens.rs diff --git a/cl-frontend/src/lib.rs b/cl-frontend/src/lib.rs new file mode 100644 index 0000000..99e01fc --- /dev/null +++ b/cl-frontend/src/lib.rs @@ -0,0 +1,483 @@ +//! Utilities for cl-frontend + +pub mod args { + use crate::cli::Mode; + use std::{ + io::{stdin, IsTerminal}, + ops::Deref, + path::{Path, PathBuf}, + }; + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct Args { + pub path: Option, // defaults None + pub repl: bool, // defaults true if stdin is terminal + pub mode: Mode, // defaults Interpret + } + const HELP: &str = "[( --repl | --no-repl )] [( -f | --file ) ]"; + + impl Args { + pub fn new() -> Self { + Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret } + } + pub fn parse(mut self) -> Option { + let mut args = std::env::args(); + let name = args.next().unwrap_or_default(); + let mut unknown = false; + while let Some(arg) = args.next() { + match arg.deref() { + "--repl" => self.repl = true, + "--no-repl" => self.repl = false, + "-f" | "--file" => self.path = args.next().map(PathBuf::from), + "-m" | "--mode" => { + self.mode = args.next().unwrap_or_default().parse().unwrap_or_default() + } + arg => { + eprintln!("Unknown argument: {arg}"); + unknown = true; + } + } + } + if unknown { + println!("Usage: {name} {HELP}"); + None? + } + Some(self) + } + /// Returns the path to a file, if one was specified + pub fn path(&self) -> Option<&Path> { + self.path.as_deref() + } + /// Returns whether to start a REPL session or not + pub fn repl(&self) -> bool { + self.repl + } + /// Returns the repl Mode + pub fn mode(&self) -> Mode { + self.mode + } + } + impl Default for Args { + fn default() -> Self { + Self::new() + } + } +} + +pub mod program { + use std::io::{Result as IOResult, Write}; + + use conlang::{ + ast::preamble::{expression::Expr, *}, + interpreter::{error::IResult, Interpreter}, + lexer::Lexer, + parser::{error::PResult, Parser}, + pretty_printer::{PrettyPrintable, Printer}, + resolver::{error::TyResult, Resolve, Resolver}, + token::Token, + }; + + pub struct Tokenized { + tokens: Vec, + } + + pub enum Parsed { + Program(Start), + Expr(Expr), + } + + impl TryFrom for Parsed { + type Error = conlang::parser::error::Error; + fn try_from(value: Tokenized) -> Result { + let mut parser = Parser::new(value.tokens); + let ast = parser.parse()?; + //if let Ok(ast) = ast { + //return + Ok(Self::Program(ast)) + //} + + //Ok(Self::Expr(parser.reset().parse_expr()?)) + } + } + + pub struct Program { + data: Variant, + } + + impl Program { + pub fn new(input: &str) -> Result> { + let mut tokens = vec![]; + let mut errors = vec![]; + for token in Lexer::new(input) { + match token { + Ok(token) => tokens.push(token), + Err(error) => errors.push(error), + } + } + if errors.is_empty() { + Ok(Self { data: Tokenized { tokens } }) + } else { + Err(errors) + } + } + pub fn tokens(&self) -> &[Token] { + &self.data.tokens + } + pub fn parse(self) -> PResult> { + Ok(Program { data: Parsed::try_from(self.data)? }) + } + } + + impl Program { + pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> { + match &mut self.data { + Parsed::Program(start) => start.resolve(resolver), + Parsed::Expr(expr) => expr.resolve(resolver), + } + .map(|ty| println!("{ty}")) + } + /// Runs the [Program] in the specified [Interpreter] + pub fn run(&self, interpreter: &mut Interpreter) -> IResult<()> { + match &self.data { + Parsed::Program(start) => interpreter.interpret(start), + Parsed::Expr(expr) => { + for value in interpreter.eval(expr)? { + println!("{value}") + } + Ok(()) + } + } + } + } + + impl PrettyPrintable for Program { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match &self.data { + Parsed::Program(value) => value.visit(p), + Parsed::Expr(value) => value.visit(p), + } + } + } +} + +pub mod cli { + use conlang::{ + interpreter::Interpreter, pretty_printer::PrettyPrintable, resolver::Resolver, token::Token, + }; + + use crate::{ + args::Args, + program::{Parsed, Program, Tokenized}, + }; + use std::{ + convert::Infallible, + error::Error, + io::{stdin, stdout, Write}, + path::{Path, PathBuf}, + str::FromStr, + }; + + // ANSI color escape sequences + const ANSI_RED: &str = "\x1b[31m"; + const ANSI_GREEN: &str = "\x1b[32m"; + const ANSI_CYAN: &str = "\x1b[36m"; + const ANSI_BRIGHT_BLUE: &str = "\x1b[94m"; + const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m"; + // const ANSI_BRIGHT_CYAN: &str = "\x1b[96m"; + const ANSI_RESET: &str = "\x1b[0m"; + const ANSI_OUTPUT: &str = "\x1b[38;5;117m"; + + #[derive(Clone, Debug)] + pub enum CLI { + Repl(Repl), + File { mode: Mode, path: PathBuf }, + Stdin { mode: Mode }, + } + impl From for CLI { + fn from(value: Args) -> Self { + let Args { path, repl, mode } = value; + match (repl, path) { + (_, Some(path)) => Self::File { mode, path }, + (true, None) => Self::Repl(Repl { mode, ..Default::default() }), + (false, None) => Self::Stdin { mode }, + } + } + } + impl CLI { + pub fn run(&mut self) -> Result<(), Box> { + use std::{fs, io}; + match self { + CLI::Repl(repl) => repl.repl(), + CLI::File { mode, ref path } => { + // read file + Self::no_repl(*mode, Some(path), &fs::read_to_string(path)?) + } + CLI::Stdin { mode } => { + Self::no_repl(*mode, None, &io::read_to_string(io::stdin())?) + } + } + Ok(()) + } + fn no_repl(mode: Mode, path: Option<&Path>, code: &str) { + let program = Program::new(code); + match (mode, program) { + (Mode::Tokenize, Ok(program)) => { + for token in program.tokens() { + if let Some(path) = path { + print!("{}:", path.display()); + } + print_token(token) + } + } + (Mode::Beautify, Ok(program)) => Self::beautify(program), + (Mode::Resolve, Ok(program)) => Self::resolve(program, Default::default()), + (Mode::Interpret, Ok(program)) => Self::interpret(program, Default::default()), + (_, Err(errors)) => { + for error in errors { + if let Some(path) = path { + eprint!("{}:", path.display()); + } + eprintln!("{error}") + } + } + } + } + fn beautify(program: Program) { + match program.parse() { + Ok(program) => program.print(), + Err(e) => eprintln!("{e}"), + }; + } + fn resolve(program: Program, mut resolver: Resolver) { + let mut program = match program.parse() { + Ok(program) => program, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + if let Err(e) = program.resolve(&mut resolver) { + eprintln!("{e}"); + } + } + fn interpret(program: Program, mut interpreter: Interpreter) { + let program = match program.parse() { + Ok(program) => program, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + if let Err(e) = program.run(&mut interpreter) { + eprintln!("{e}"); + return; + } + if let Err(e) = interpreter.call("main", &[]) { + eprintln!("{e}"); + } + } + } + + /// The CLI's operating mode + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub enum Mode { + Tokenize, + Beautify, + Resolve, + #[default] + Interpret, + } + impl Mode { + pub fn ansi_color(self) -> &'static str { + match self { + Mode::Tokenize => ANSI_BRIGHT_BLUE, + Mode::Beautify => ANSI_BRIGHT_MAGENTA, + Mode::Resolve => ANSI_GREEN, + Mode::Interpret => ANSI_CYAN, + } + } + } + impl FromStr for Mode { + type Err = Infallible; + fn from_str(s: &str) -> Result { + Ok(match s { + "i" | "interpret" => Mode::Interpret, + "b" | "beautify" | "p" | "pretty" => Mode::Beautify, + "r" | "resolve" | "typecheck" => Mode::Resolve, + "t" | "tokenize" => Mode::Tokenize, + _ => Mode::Interpret, + }) + } + } + + /// Implements the interactive interpreter + #[derive(Clone, Debug)] + pub struct Repl { + prompt_again: &'static str, // " ?>" + prompt_begin: &'static str, // "cl>" + prompt_error: &'static str, // "! >" + interpreter: Interpreter, + resolver: Resolver, + mode: Mode, + } + + impl Default for Repl { + fn default() -> Self { + Self { + prompt_begin: "cl>", + prompt_again: " ?>", + prompt_error: "! >", + interpreter: Default::default(), + resolver: Default::default(), + mode: Default::default(), + } + } + } + + /// Prompt functions + impl Repl { + pub fn prompt_begin(&self) { + print!( + "{}{} {ANSI_RESET}", + self.mode.ansi_color(), + self.prompt_begin + ); + let _ = stdout().flush(); + } + pub fn prompt_again(&self) { + print!( + "{}{} {ANSI_RESET}", + self.mode.ansi_color(), + self.prompt_again + ); + let _ = stdout().flush(); + } + pub fn prompt_error(&self, err: &impl Error) { + println!("{ANSI_RED}{} {err}{ANSI_RESET}", self.prompt_error) + } + pub fn begin_output(&self) { + print!("{ANSI_OUTPUT}") + } + fn reprompt(&self, buf: &mut String) { + self.prompt_begin(); + buf.clear(); + } + } + /// The actual REPL + impl Repl { + /// Constructs a new [Repl] with the provided [Mode] + pub fn new(mode: Mode) -> Self { + Self { mode, ..Default::default() } + } + /// Runs the main REPL loop + pub fn repl(&mut self) { + let mut buf = String::new(); + self.prompt_begin(); + while let Ok(len) = stdin().read_line(&mut buf) { + // Exit the loop + if len == 0 { + println!(); + break; + } + self.begin_output(); + // Process mode-change commands + if self.command(&buf) { + self.reprompt(&mut buf); + continue; + } + // Lex the buffer, or reset and output the error + let code = match Program::new(&buf) { + Ok(code) => code, + Err(e) => { + for error in e { + eprintln!("{error}"); + } + self.reprompt(&mut buf); + continue; + } + }; + // Tokenize mode doesn't require valid parse, so it gets processed first + if self.mode == Mode::Tokenize { + self.tokenize(&code); + self.reprompt(&mut buf); + continue; + } + // Parse code and dispatch to the proper function + match (len, code.parse()) { + // If the code is OK, run it and print any errors + (_, Ok(mut code)) => { + self.dispatch(&mut code); + buf.clear(); + } + // If the user types two newlines, print syntax errors + (1, Err(e)) => { + self.prompt_error(&e); + buf.clear(); + } + // Otherwise, ask for more input + _ => { + self.prompt_again(); + continue; + } + } + self.prompt_begin() + } + } + + fn help(&self) { + println!( + "Commands:\n- $tokens\n Tokenize Mode:\n Outputs information derived by the Lexer\n- $pretty\n Beautify Mode:\n Pretty-prints the input\n- $type\n Resolve Mode:\n Attempts variable resolution and type-checking on the input\n- $run\n Interpret Mode:\n Interprets the input using Conlang\'s work-in-progress interpreter\n- $mode\n Prints the current mode\n- $help\n Prints this help message" + ); + } + fn command(&mut self, line: &str) -> bool { + match line.trim() { + "$pretty" => self.mode = Mode::Beautify, + "$tokens" => self.mode = Mode::Tokenize, + "$type" => self.mode = Mode::Resolve, + "$run" => self.mode = Mode::Interpret, + "$mode" => print!("{:?} Mode", self.mode), + "$help" => self.help(), + _ => return false, + } + println!(); + true + } + /// Dispatches calls to repl functions based on the program + fn dispatch(&mut self, code: &mut Program) { + match self.mode { + Mode::Tokenize => (), + Mode::Beautify => self.beautify(code), + Mode::Resolve => self.typecheck(code), + Mode::Interpret => self.interpret(code), + } + } + fn tokenize(&mut self, code: &Program) { + for token in code.tokens() { + print_token(token); + } + } + fn interpret(&mut self, code: &Program) { + if let Err(e) = code.run(&mut self.interpreter) { + self.prompt_error(&e) + } + } + fn typecheck(&mut self, code: &mut Program) { + if let Err(e) = code.resolve(&mut self.resolver) { + self.prompt_error(&e) + } + } + fn beautify(&mut self, code: &Program) { + code.print() + } + } + + fn print_token(t: &Token) { + println!( + "{:02}:{:02}: {:#19} │{}│", + t.line(), + t.col(), + t.ty(), + t.data(), + ) + } +} diff --git a/cl-frontend/src/main.rs b/cl-frontend/src/main.rs new file mode 100644 index 0000000..4f0b7e0 --- /dev/null +++ b/cl-frontend/src/main.rs @@ -0,0 +1,9 @@ +use cl_frontend::{args::Args, cli::CLI}; +use std::error::Error; + +fn main() -> Result<(), Box> { + // parse args + let args = Args::new().parse().unwrap_or_default(); + let mut cli = CLI::from(args); + cli.run() +} diff --git a/grammar.ebnf b/grammar.ebnf index 942dc6d..de51666 100644 --- a/grammar.ebnf +++ b/grammar.ebnf @@ -1,7 +1,14 @@ (* Conlang Expression Grammar *) Start = Program ; Program = Stmt* EOI ; - +(* TODO: +- Replace Program with Module + Module = Decl* EOI ; +- Move Fn and Let into "Decl": + Decl = Fn | Let ; +- allow Decl | ExprStmt in Stmt: + Stmt = Decl | Expr ';' ; +*) (* literal *) Literal = STRING | CHARACTER | FLOAT | INTEGER | Bool ; Bool = "true" | "false" ; @@ -10,11 +17,22 @@ Identifier = IDENTIFIER ; (* # Statements *) (* statement *) Stmt = Fn | Let | Expr ';' ; -Let = "let" "mut"? Identifier (':' Type)? ('=' Expr)? ';' ; +Let = "let" Name ('=' Expr)? ';' ; Fn = "fn" Identifier '(' Params? ')' Block ; (* TODO: Type system *) -Params = (Param ',')* Param? ; -Param = Identifier (*':' Type *) ; +Params = (Name ',')* Name? ; +Name = "mut"? Identifier (':' Type )? ; + +(* # Type Expressions *) +(* types *) +TypeExpr = Never | Empty | TypeTuple | PathExpr ; +Never = '!' ; +Empty = '(' ')' ; +TypeTuple = '(' (TypeExpr ',')* TypeExpr? ')' ; + +PathExpr = '::'? PathPart ; +PathPart = "super" | Identifier ; + (* # Expressions *) (* expression *) diff --git a/libconlang/examples/format.rs b/libconlang/examples/format.rs deleted file mode 100644 index 6d51d81..0000000 --- a/libconlang/examples/format.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! This example grabs input from stdin, lexes it, parses it, and prints the AST -#![allow(unused_imports)] -use conlang::{lexer::Lexer, parser::Parser, pretty_printer::PrettyPrintable, token::Token}; -use std::{ - error::Error, - io::{stdin, stdout, IsTerminal, Read, Write}, - path::{Path, PathBuf}, -}; - -fn main() -> Result<(), Box> { - let conf = Config::new(); - if conf.paths.is_empty() { - take_stdin()?; - } else { - for path in conf.paths.iter().map(PathBuf::as_path) { - parse(&std::fs::read_to_string(path)?, Some(path)); - } - } - Ok(()) -} - -struct Config { - paths: Vec, -} - -impl Config { - fn new() -> Self { - Config { paths: std::env::args().skip(1).map(PathBuf::from).collect() } - } -} - -fn take_stdin() -> Result<(), Box> { - const PROMPT: &str = "> "; - if stdin().is_terminal() { - print!("{PROMPT}"); - stdout().flush()?; - for line in stdin().lines() { - let line = line?; - if !line.is_empty() { - parse(&line, None); - println!(); - } - print!("{PROMPT}"); - stdout().flush()?; - } - } else { - parse(&std::io::read_to_string(stdin())?, None) - } - Ok(()) -} - -fn parse(file: &str, path: Option<&Path>) { - use conlang::parser::error::Error; - match Parser::from(Lexer::new(file)).parse() { - Ok(ast) => ast.print(), - Err(e) if e.start().is_some() => print!("{:?}:{}", path.unwrap_or(Path::new("-")), e), - Err(e) => print!("{e}"), - } - println!(); -} diff --git a/libconlang/examples/interpret.rs b/libconlang/examples/interpret.rs deleted file mode 100644 index d592976..0000000 --- a/libconlang/examples/interpret.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! This example grabs input from stdin or a file, lexes it, parses it, and interprets it -use conlang::{ - ast::{expression::Expr, Start}, - interpreter::Interpreter, - lexer::Lexer, - parser::Parser, -}; -use std::{ - error::Error, - io::{stdin, stdout, IsTerminal, Write}, - path::{Path, PathBuf}, -}; - -fn main() -> Result<(), Box> { - let conf = Config::new(); - if conf.paths.is_empty() { - take_stdin()?; - } else { - for path in conf.paths.iter().map(PathBuf::as_path) { - run_file(&std::fs::read_to_string(path)?, Some(path))?; - } - } - Ok(()) -} - -struct Config { - paths: Vec, -} - -impl Config { - fn new() -> Self { - Config { paths: std::env::args().skip(1).map(PathBuf::from).collect() } - } -} - -macro_rules! prompt { - ($($t:tt)*) => {{ - let mut out = stdout().lock(); - out.write_all(b"\x1b[36m")?; - write!(out, $($t)*)?; - out.write_all(b"\x1b[0m")?; - out.flush() - }}; -} - -fn take_stdin() -> Result<(), Box> { - const CONLANG: &str = "cl>"; - const MOREPLS: &str = " ?>"; - if stdin().is_terminal() { - let mut interpreter = Interpreter::new(); - let mut buf = String::new(); - prompt!("{CONLANG} ")?; - while let Ok(len) = stdin().read_line(&mut buf) { - let code = Program::parse(&buf); - match (len, code) { - // Exit the loop - (0, _) => { - println!(); - break; - } - // If the code is OK, run it and print any errors - (_, Ok(code)) => { - let _ = code.run(&mut interpreter).map_err(|e| eprintln!("{e}")); - buf.clear(); - } - // If the user types two newlines, print syntax errors - (1, Err(e)) => { - eprintln!("{e}"); - buf.clear(); - } - // Otherwise, ask for more input - _ => { - prompt!("{MOREPLS} ")?; - continue; - } - } - prompt!("{CONLANG} ")?; - } - } else { - run_file(&std::io::read_to_string(stdin())?, None)? - } - Ok(()) -} - -fn run_file(file: &str, path: Option<&Path>) -> Result<(), Box> { - let mut interpreter = Interpreter::new(); - match Parser::from(Lexer::new(file)).parse() { - Ok(ast) => { - interpreter.interpret(&ast)?; - println!("{}", interpreter.call("main", &[])?) - }, - Err(e) if e.start().is_some() => print!("{:?}:{}", path.unwrap_or(Path::new("-")), e), - Err(e) => print!("{e}"), - } - println!(); - Ok(()) -} - -enum Program { - Program(Start), - Expr(Expr), -} - -impl Program { - fn parse(input: &str) -> conlang::parser::error::PResult { - let ast = Parser::from(Lexer::new(input)).parse(); - if let Ok(ast) = ast { - return Ok(Self::Program(ast)); - } - Ok(Self::Expr(Parser::from(Lexer::new(input)).parse_expr()?)) - } - fn run(&self, interpreter: &mut Interpreter) -> conlang::interpreter::error::IResult<()> { - match self { - Program::Program(start) => interpreter.interpret(start), - Program::Expr(expr) => { - for value in interpreter.eval(expr)? { - println!("{value}") - } - Ok(()) - } - } - } -} diff --git a/libconlang/src/ast.rs b/libconlang/src/ast.rs index f848aa6..dbda59e 100644 --- a/libconlang/src/ast.rs +++ b/libconlang/src/ast.rs @@ -11,19 +11,16 @@ //! See [statement], [literal], and [expression] for more information. pub mod preamble { + #![allow(deprecated)] //! Common imports for working with the [ast](super) pub use super::{ - expression::{ - self, - call::*, - control, - math::{self, operator}, - tuple::*, - }, - literal, + expression::{call::*, control::*, math::*, tuple::*, *}, + literal::*, + path::*, statement::*, + types::*, visitor::Visitor, - Identifier, Program, Start, + *, }; } @@ -43,47 +40,9 @@ pub struct Program(pub Vec); /// # Syntax /// [`Identifier`]` := `[`IDENTIFIER`](crate::token::token_type::Type::Identifier) #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Identifier(pub String); - -pub mod todo { - //! temporary storage for pending expression work. \ - //! when an item is in progress, remove it from todo. - //! - //! # General TODOs: - //! - [x] Implement support for storing items in the AST - //! - [ ] Keep track of the source location of each node - //! - [ ] Implement paths - //! - [x] Implement functions - //! - [ ] Implement structs - //! - [ ] Implement enums - //! - [ ] Implement implementation - //! - [ ] Store token spans in AST - pub mod path { - //! Path support - //! - [ ] Add namespace syntax (i.e. `::crate::foo::bar` | `foo::bar::Baz` | `foo::bar::*`) - //! - //! Path resolution will be vital to the implementation of structs, enums, impl blocks, - //! traits, modules, etc. - } - - pub mod structure { - //! Struct support - //! - [ ] Add struct declaration expression (returns a struct declaration) - //! - [ ] Add struct value expression (returns a struct value) - //! - [ ] Add struct update syntax (yippee!!) - } - - pub mod enumeration { - //! Enum support - //! - [ ] Add enum declaration expression (returns an enum declaration) - //! - [ ] Add enum value expression (returns an enum value) - } - - pub mod implementation { - //! Impl block support - //! - [ ] Add impl block expression? Statement? - //! - [ ] Add member function call expression - } +pub struct Identifier { + pub name: String, + pub index: Option, } pub mod literal { @@ -150,6 +109,7 @@ pub mod statement { use super::{ expression::{Block, Expr}, + types::TypeExpr, Identifier, }; @@ -164,7 +124,7 @@ pub mod statement { Let(Let), /// Contains a function declaration /// # Syntax - /// [`Fn`](Stmt::Fn) := `"fn"` [`Identifier`] `'('` [`Tuple`] `')'` [`Block`] + /// [`Fn`](Stmt::Fn) := `"fn"` [`Identifier`] `'('` `Args...` `')'` [`Block`] Fn(FnDecl), /// Contains an expression statement /// # Syntax @@ -177,26 +137,104 @@ pub mod statement { /// [`Let`] := `let` [`Identifier`] (`:`) `Type`)? (`=` [`Expr`])? `;` #[derive(Clone, Debug)] pub struct Let { - pub name: Identifier, - pub mutable: bool, - pub ty: Option, + pub name: Name, pub init: Option, } /// Contains a function declaration /// # Syntax - /// [`FnDecl`] := `"fn"` [`Identifier`] `'('` [`Tuple`] `')'` + /// [`FnDecl`] := `"fn"` [`Identifier`] `'('` `Args...` `')'` #[derive(Clone, Debug)] pub struct FnDecl { - pub name: Identifier, - pub args: Vec, + pub name: Name, + pub args: Vec, pub body: Block, // TODO: Store type information } + /// Contains the name, mutability, and type information for a [Let] or [FnDecl] + /// # Syntax + #[derive(Clone, Debug)] + pub struct Name { + pub name: Identifier, + /// The mutability of the [Name]. Functions are never mutable. + pub mutable: bool, + /// The [type](TypeExpr) + pub ty: Option, + } + // TODO: Create closure, transmute fndecl into a name and closure } +pub mod path { + //! Paths + //! + //! A Path Expression refers to an item, either local or module-scoped. + + use super::Identifier; + + /// A path to an item in a module + /// # Syntax + /// [`Path`]` := "::"? `[`PathPart`]` ("::" `[`PathPart`]`)*` + #[derive(Clone, Debug)] + pub struct Path { + pub absolute: bool, + pub parts: Vec, + } + + /// A component of a [`TypePath`] + /// # Syntax + /// [`PathPart`]` := "super" | `[`Identifier`] + #[derive(Clone, Debug)] + pub enum PathPart { + PathSuper, + PathSelf, + PathIdent(Identifier), + } +} + +pub mod types { + //! # Types + //! + //! The [Type Expresson](TypeExpr) powers Conlang's type checker. + //! + //! # Syntax + //! [`TypeExpr`]` := `[`TupleType`]` | `[`TypePath`]` | `[`Never`] + + pub use super::path::Path as TypePath; + + /// Contains a [Type Expression](self) + /// + /// # Syntax + /// [`TypeExpr`]` := `[`TupleType`]` | `[`TypePath`]` | `[`Empty`]` | `[`Never`] + #[derive(Clone, Debug)] + pub enum TypeExpr { + TupleType(TupleType), + TypePath(TypePath), + Empty(Empty), + Never(Never), + } + + /// A [TupleType] represents the [TypeExpr] of a Tuple value + #[derive(Clone, Debug)] + pub struct TupleType { + pub types: Vec, + } + + /// The empty type. You get nothing! You lose! + /// # Syntax + /// [`Empty`]` := '(' ')'` + #[derive(Clone, Copy, Debug, Default)] + pub struct Empty; + + /// The never type. This type can never be constructed, and can only appear if a block of code + /// doesn't terminate + /// # Syntax + /// [`Never`]` := '!'` + #[derive(Clone, Copy, Debug, Default)] + pub struct Never; +} + pub mod expression { //! # Expressions //! @@ -219,17 +257,19 @@ pub mod expression { //! | 9 | [`control::Flow`] | Branch expressions (`if`, `while`, `for`, `return`, `break`, `continue`) //! | 9 | [`Group`] | Group expressions `(` [Expr]? `)` /* Can evaluate to Empty! */ //! | 9 | [`Block`] | Block expressions `{` [Expr] `}` - //! | 9 | [`Primary`] | Contains an [Identifier], [Literal](literal::Literal), [Block], [Group], or [Flow](control::Flow) + //! | 9 | [`Primary`] | Contains an [Identifier], [Literal], [Block], [Group], or [Flow] //! //! ## Syntax //! [`Expr`]` := `[`math::Operation`] \ //! [`Block`]` := '{' `[`Expr`]` '}'` \ //! [`Group`]` := '(' `[`Expr`]`? ')'` \ - //! [`Primary`]` := `[`Identifier`]` | `[`Literal`](literal::Literal)` | `[`Block`]` | - //! `[`Group`]` | `[`control::Flow`] + //! [`Primary`]` := `[`Identifier`]` | `[`Literal`]` | `[`Block`]` | + //! `[`Group`]` | `[`Flow`] //! //! See [control] and [math] for their respective production rules. - use super::{statement::Stmt, *}; + use super::{literal::Literal, statement::Stmt, *}; + use control::Flow; + use tuple::Group; /// Contains an expression /// @@ -241,18 +281,18 @@ pub mod expression { /// A [Primary] Expression is the expression with the highest precedence (i.e. the deepest /// derivation) /// # Syntax - /// [`Primary`]` := `[`IDENTIFIER`](Identifier)` - /// | `[`Literal`](literal::Literal)` - /// | `[`Block`]` - /// | `[`Group`](tuple::Group)` - /// | `[`Branch`](control::Flow) + /// [`Primary`]` := `[`Identifier`]` + /// | `[`Literal`]` + /// | `[`Block`]` + /// | `[`Group`]` + /// | `[`Branch`](Flow) #[derive(Clone, Debug)] pub enum Primary { Identifier(Identifier), - Literal(literal::Literal), + Literal(Literal), Block(Block), - Group(tuple::Group), - Branch(control::Flow), + Group(Group), + Branch(Flow), } /// Contains a Block Expression @@ -260,6 +300,7 @@ pub mod expression { /// [`Block`] := `'{'` [`Expr`] `'}'` #[derive(Clone, Debug)] pub struct Block { + pub let_count: Option, pub statements: Vec, pub expr: Option>, } @@ -352,7 +393,7 @@ pub mod expression { //! [`Shift`][2]` := `[`Term`][2]` (`[`ShiftOp`][5]` `[`Term`][2]` )*` \ //! [`Term`][2]` := `[`Factor`][2]` (`[`TermOp`][5]` `[`Factor`][2]` )*` \ //! [`Factor`][2]` := `[`Unary`][1]` (`[`FactorOp`][5]` `[`Unary`][1]` )*` \ - //! [`Unary`][1]` := (`[`UnaryOp`][4]`)* `[`FnCall`][7] + //! [`Unary`][1]` := (`[`UnaryOp`][4]`)* `[`Call`] //! //! [1]: Operation::Unary //! [2]: Operation::Binary @@ -704,10 +745,10 @@ pub mod expression { pub mod visitor { //! A [`Visitor`] visits every kind of node in the [Abstract Syntax Tree](super) - //! + //! //! This trait is mostly here to ensure that every branch of the tree is accounted for. - //! - //! Default implementations are provided for + //! + //! Default implementations are provided for use super::{ expression::{call::*, control::*, math::*, tuple::*, Block, *}, literal::*, @@ -716,6 +757,7 @@ pub mod visitor { }; /// A Visitor traverses every kind of node in the [Abstract Syntax Tree](super) + #[deprecated] pub trait Visitor { /// Visit the start of an AST fn visit(&mut self, start: &Start) -> R { @@ -733,9 +775,9 @@ pub mod visitor { } } /// Visit a [Let statement](Let) - fn visit_let(&mut self, stmt: &Let) -> R; + fn visit_let(&mut self, decl: &Let) -> R; /// Visit a [Fn declaration](FnDecl) - fn visit_fn_decl(&mut self, function: &FnDecl) -> R; + fn visit_fn_decl(&mut self, decl: &FnDecl) -> R; /// Visit an [Expression](Expr) fn visit_expr(&mut self, expr: &Expr) -> R { @@ -781,10 +823,11 @@ pub mod visitor { /// Visit a [Unary] Operation fn visit_unary(&mut self, unary: &Unary) -> R; // Math operators + /// Visit an [Assignment](Assign) [operator](operator::Assign) fn visit_assign_op(&mut self, op: &operator::Assign) -> R; - /// Visit a [Binary](Operation::Binary) [operator](operator::Binary) + /// Visit a [Binary] [operator](operator::Binary) fn visit_binary_op(&mut self, op: &operator::Binary) -> R; - /// Visit a [Unary](Operation::Unary) [operator](operator::Unary) + /// Visit a [Unary] [operator](operator::Unary) fn visit_unary_op(&mut self, op: &operator::Unary) -> R; /// Visit a [Primary] expression @@ -796,7 +839,7 @@ pub mod visitor { Primary::Literal(v) => self.visit_literal(v), Primary::Block(v) => self.visit_block(v), Primary::Group(v) => self.visit_group(v), - Primary::Branch(v) => self.visit_branch_expr(v), + Primary::Branch(v) => self.visit_branch(v), } } @@ -804,8 +847,8 @@ pub mod visitor { /// /// [`Flow`]` := `[`While`]` | `[`If`]` | `[`For`]` /// | `[`Continue`]` | `[`Return`]` | `[`Break`] - fn visit_branch_expr(&mut self, expr: &Flow) -> R { - match expr { + fn visit_branch(&mut self, flow: &Flow) -> R { + match flow { Flow::While(e) => self.visit_while(e), Flow::If(e) => self.visit_if(e), Flow::For(e) => self.visit_for(e), @@ -859,3 +902,46 @@ pub mod visitor { fn visit_empty(&mut self) -> R; } } + +pub mod todo { + //! temporary storage for pending expression work. \ + //! when an item is in progress, remove it from todo. + //! + //! # General TODOs: + //! - [ ] REMOVE VISITOR TRAIT + //! - [ ] + //! - [x] Implement support for storing items in the AST + //! - [ ] Keep track of the source location of each node + //! - [ ] Implement paths + //! - [x] Implement functions + //! - [ ] Implement structs + //! - [ ] Implement enums + //! - [ ] Implement implementation + //! - [ ] Store token spans in AST + pub mod path { + //! Path support + //! - [ ] Add namespace syntax (i.e. `::crate::foo::bar` | `foo::bar::Baz` | `foo::bar::*`) + //! + //! Path resolution will be vital to the implementation of structs, enums, impl blocks, + //! traits, modules, etc. + } + + pub mod structure { + //! Struct support + //! - [ ] Add struct declaration expression (returns a struct declaration) + //! - [ ] Add struct value expression (returns a struct value) + //! - [ ] Add struct update syntax (yippee!!) + } + + pub mod enumeration { + //! Enum support + //! - [ ] Add enum declaration expression (returns an enum declaration) + //! - [ ] Add enum value expression (returns an enum value) + } + + pub mod implementation { + //! Impl block support + //! - [ ] Add impl block expression? Statement? + //! - [ ] Add member function call expression + } +} diff --git a/libconlang/src/interpreter.rs b/libconlang/src/interpreter.rs index 559489e..17a9df6 100644 --- a/libconlang/src/interpreter.rs +++ b/libconlang/src/interpreter.rs @@ -1,4 +1,5 @@ //! Interprets an AST as a program +#![allow(deprecated)] // TODO: REMOVE use crate::ast::preamble::*; use error::{Error, IResult}; @@ -9,7 +10,7 @@ use temp_type_impl::ConValue; pub trait Callable: std::fmt::Debug { /// Calls this [Callable] in the provided [Interpreter], with [ConValue] args \ /// The Callable is responsible for checking the argument count and validating types - fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult<()>; + fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult; /// Returns the common name of this identifier. fn name(&self) -> &str; } @@ -105,7 +106,7 @@ pub mod temp_type_impl { _ => "", } } - fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { + fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult { match self { Self::Function(func) => func.call(interpreter, args), Self::BuiltIn(func) => func.call(interpreter, args), @@ -347,7 +348,7 @@ impl Visitor> for Interpreter { } fn visit_let(&mut self, stmt: &Let) -> IResult<()> { - let Let { name: Identifier(name), init, .. } = stmt; + let Let { name: Name { name: Identifier { name, .. }, .. }, init, .. } = stmt; if let Some(init) = init { self.visit_expr(init)?; let init = self.pop()?; @@ -394,7 +395,8 @@ impl Visitor> for Interpreter { let (ConValue::Tuple(args), callee) = self.pop_two()? else { Err(Error::TypeError)? }; - callee.call(self, &args)?; + let return_value = callee.call(self, &args)?; + self.push(return_value); } Ok(()) } @@ -404,7 +406,8 @@ impl Visitor> for Interpreter { let math::Assign { target, operator, init } = assign; self.visit_operation(init)?; let init = self.pop()?; - let resolved = self.scope.get_mut(&target.0)?; + let resolved = self.scope.get_mut(&target.name)?; + if let Assign::Assign = operator { use std::mem::discriminant as variant; // runtime typecheck @@ -418,9 +421,11 @@ impl Visitor> for Interpreter { self.push(ConValue::Empty); return Ok(()); } + let Some(target) = resolved.as_mut() else { - Err(Error::NotInitialized(target.0.to_owned()))? + Err(Error::NotInitialized(target.name.to_owned()))? }; + match operator { Assign::AddAssign => target.add_assign(init)?, Assign::SubAssign => target.sub_assign(init)?, @@ -434,12 +439,13 @@ impl Visitor> for Interpreter { Assign::ShrAssign => target.shr_assign(init)?, _ => (), } + self.push(ConValue::Empty); + Ok(()) } fn visit_binary(&mut self, bin: &math::Binary) -> IResult<()> { - use math::Binary; let Binary { first, other } = bin; self.visit_operation(first)?; @@ -469,15 +475,19 @@ impl Visitor> for Interpreter { } } } + Ok(()) } fn visit_unary(&mut self, unary: &math::Unary) -> IResult<()> { - let math::Unary { operand, operators } = unary; + let Unary { operand, operators } = unary; + self.visit_operation(operand)?; + for op in operators.iter().rev() { self.visit_unary_op(op)?; } + Ok(()) } @@ -488,6 +498,7 @@ impl Visitor> for Interpreter { fn visit_binary_op(&mut self, op: &operator::Binary) -> IResult<()> { use operator::Binary; let (second, first) = self.pop_two()?; + let out = match op { Binary::Mul => first * second, Binary::Div => first / second, @@ -511,12 +522,14 @@ impl Visitor> for Interpreter { Binary::GreaterEq => first.gt_eq(&second), Binary::Greater => first.gt(&second), }?; + self.push(out); Ok(()) } fn visit_unary_op(&mut self, op: &operator::Unary) -> IResult<()> { let operand = self.pop()?; + self.push(match op { operator::Unary::RefRef => todo!(), operator::Unary::Ref => todo!(), @@ -530,10 +543,11 @@ impl Visitor> for Interpreter { } operator::Unary::Tilde => todo!(), }); + Ok(()) } - fn visit_if(&mut self, expr: &control::If) -> IResult<()> { + fn visit_if(&mut self, expr: &If) -> IResult<()> { self.visit_expr(&expr.cond)?; if self.pop()?.truthy()? { self.visit_block(&expr.body)?; @@ -545,7 +559,7 @@ impl Visitor> for Interpreter { Ok(()) } - fn visit_while(&mut self, expr: &control::While) -> IResult<()> { + fn visit_while(&mut self, expr: &While) -> IResult<()> { while { self.visit_expr(&expr.cond)?; self.pop()?.truthy()? @@ -581,7 +595,7 @@ impl Visitor> for Interpreter { _ => Err(Error::NotIterable)?, }; for loop_var in bounds.0..=bounds.1 { - self.scope.insert(&expr.var.0, Some(loop_var.into())); + self.scope.insert(&expr.var.name, Some(loop_var.into())); let Err(out) = self.visit_block(&expr.body) else { self.pop()?; continue; @@ -627,7 +641,7 @@ impl Visitor> for Interpreter { } fn visit_identifier(&mut self, ident: &Identifier) -> IResult<()> { - let value = self.resolve(&ident.0)?; + let value = self.resolve(&ident.name)?; self.push(value); Ok(()) } @@ -670,8 +684,17 @@ impl Default for Interpreter { pub mod function { //! Represents a block of code which lives inside the Interpreter - use super::{Callable, ConValue, Error, FnDecl, IResult, Identifier, Interpreter}; - use crate::ast::visitor::Visitor; + use super::{ + // scope::Environment, + Callable, + ConValue, + Error, + FnDecl, + IResult, + Identifier, + Interpreter, + }; + use crate::ast::{preamble::Name, visitor::Visitor}; /// Represents a block of code which persists inside the Interpreter #[derive(Clone, Debug)] pub struct Function { @@ -679,19 +702,23 @@ pub mod function { declaration: Box, // /// Stores the enclosing scope of the function // TODO: Capture variables + //environment: Box, } impl Function { pub fn new(declaration: &FnDecl) -> Self { - Self { declaration: declaration.clone().into() } + Self { + declaration: declaration.clone().into(), + //environment: Box::new(Environment::new()), + } } } impl Callable for Function { fn name(&self) -> &str { - &self.declaration.name.0 + &self.declaration.name.name.name } - fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { + fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult { // Check arg mapping if args.len() != self.declaration.args.len() { return Err(Error::ArgNumber { @@ -701,17 +728,19 @@ pub mod function { } // TODO: Isolate cross-function scopes! interpreter.scope.enter(); - for (Identifier(arg), value) in self.declaration.args.iter().zip(args) { - interpreter.scope.insert(arg, Some(value.clone())); + for (Name { name: Identifier { name, .. }, .. }, value) in + self.declaration.args.iter().zip(args) + { + interpreter.scope.insert(name, Some(value.clone())); } match interpreter.visit_block(&self.declaration.body) { Err(Error::Return(value)) => interpreter.push(value), Err(Error::Break(value)) => Err(Error::BadBreak(value))?, Err(e) => Err(e)?, - Ok(_) => (), + Ok(()) => (), } interpreter.scope.exit()?; - Ok(()) + interpreter.pop() } } } @@ -719,14 +748,12 @@ pub mod function { pub mod builtin { mod builtin_imports { pub use crate::interpreter::{ - error::{Error, IResult}, - temp_type_impl::ConValue, - BuiltIn, Callable, Interpreter, + error::IResult, temp_type_impl::ConValue, BuiltIn, Callable, Interpreter, }; } use super::BuiltIn; /// Builtins to load when a new interpreter is created - pub const DEFAULT_BUILTINS: &[&dyn BuiltIn] = &[&print::Print, &dbg::Dbg]; + pub const DEFAULT_BUILTINS: &[&dyn BuiltIn] = &[&print::Print, &dbg::Dbg, &dump::Dump]; mod print { //! Implements the unstable `print(...)` builtin @@ -738,11 +765,12 @@ pub mod builtin { #[rustfmt::skip] impl Callable for Print { fn name(&self) -> &'static str { "print" } - fn call(&self, inter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { - for arg in args { print!("{arg}") } + fn call(&self, _inter: &mut Interpreter, args: &[ConValue]) -> IResult { + for arg in args { + print!("{arg}") + } println!(); - inter.push(ConValue::Empty); - Ok(()) + Ok(ConValue::Empty) } } } @@ -755,10 +783,27 @@ pub mod builtin { #[rustfmt::skip] impl Callable for Dbg { fn name(&self) -> &str { "dbg" } - fn call(&self, inter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { + fn call(&self, _inter: &mut Interpreter, args: &[ConValue]) -> IResult { println!("{args:?}"); - inter.push(args); - Ok(()) + Ok(args.into()) + } + } + } + + mod dump { + use super::builtin_imports::*; + #[derive(Clone, Debug)] + pub struct Dump; + impl BuiltIn for Dump {} + impl Callable for Dump { + fn call(&self, interpreter: &mut Interpreter, _args: &[ConValue]) -> IResult { + println!("Scope:\n{}", interpreter.scope); + println!("Stack:{:#?}", interpreter.stack); + Ok(ConValue::Empty) + } + + fn name(&self) -> &str { + "dump" } } } @@ -774,7 +819,7 @@ pub mod scope { temp_type_impl::ConValue, FnDecl, }; - use std::collections::HashMap; + use std::{collections::HashMap, fmt::Display}; /// Implements a nested lexical scope #[derive(Clone, Debug, Default)] @@ -783,6 +828,19 @@ pub mod scope { vars: HashMap>, } + impl Display for Environment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (var, val) in &self.vars { + writeln!(f, "{var}: {}", if val.is_some() { "..." } else { "None" })?; + } + "--- Frame ---\n".fmt(f)?; + if let Some(outer) = &self.outer { + outer.fmt(f)?; + } + Ok(()) + } + } + impl Environment { pub fn new() -> Self { let mut out = Self::default(); @@ -835,8 +893,10 @@ pub mod scope { } /// A convenience function for registering a [FnDecl] as a [Function] pub fn insert_fn(&mut self, decl: &FnDecl) { - self.vars - .insert(decl.name.0.clone(), Some(Function::new(decl).into())); + self.vars.insert( + decl.name.name.name.clone(), + Some(Function::new(decl).into()), + ); } } } diff --git a/libconlang/src/parser.rs b/libconlang/src/parser.rs index 91a8b25..382b072 100644 --- a/libconlang/src/parser.rs +++ b/libconlang/src/parser.rs @@ -1,18 +1,37 @@ //! Parses [tokens](super::token) into an [AST](super::ast) use super::{ast::preamble::*, lexer::Lexer, token::preamble::*}; -use error::{Error, Reason::*, *}; +use error::{Error, *}; pub mod error { use super::{Token, Type}; use std::fmt::Display; + pub trait WrapError { + /// Wraps this error in a parent [Error] + fn wrap(self, parent: Error) -> Self; + } + impl WrapError for Error { + fn wrap(self, parent: Error) -> Self { + Self { child: Some(self.into()), ..parent } + } + } + impl WrapError for Result { + fn wrap(self, parent: Error) -> Self { + self.map_err(|e| e.wrap(parent)) + } + } + /// The reason for the [Error] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Reason { Expected(Type), Unexpected(Type), + NotPathSegment(Type), NotIdentifier, + NotStatement, + NotLet, + NotFnDecl, NotOperator, NotLiteral, NotString, @@ -37,7 +56,11 @@ pub mod error { match self { Self::Expected(t) => write!(f, "Expected {t}"), Self::Unexpected(t) => write!(f, "Unexpected {t} in bagging area"), + Self::NotPathSegment(t) => write!(f, "{t} not a path segment"), Self::NotIdentifier => "Not an identifier".fmt(f), + Self::NotStatement => "Not a statement".fmt(f), + Self::NotLet => "Not a let statement".fmt(f), + Self::NotFnDecl => "Not a valid function declaration".fmt(f), Self::NotOperator => "Not an operator".fmt(f), Self::NotLiteral => "Not a literal".fmt(f), Self::NotString => "Not a string".fmt(f), @@ -67,11 +90,15 @@ pub mod error { #[derive(Clone, Debug, Default, PartialEq)] pub struct Error { reason: Reason, + child: Option>, start: Option, } impl std::error::Error for Error {} impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(child) = &self.child { + write!(f, "{child}: ")?; + } if let Some(token) = &self.start { write!(f, "{}:{}: ", token.line(), token.col())?; } @@ -84,7 +111,7 @@ pub mod error { #[doc = concat!("[`", stringify!($reason), "`]")] #[allow(dead_code)] pub(crate) fn $fn($($($p : $t),*)?) -> Self { - Self { reason: $reason$(($($p)*))?, start: None } + Self { reason: $reason$(($($p)*))?, child: None, start: None } } )*} impl Error { @@ -104,14 +131,14 @@ pub mod error { pub fn reason(&self) -> Reason { self.reason } - /// Modifies the [Reason] of this error - pub fn with_reason(self, reason: Reason) -> Self { - Self { reason, ..self } - } error_impl! { expected(e: Type): Expected, unexpected(e: Type): Unexpected, + not_path_segment(e: Type): NotPathSegment, not_identifier: NotIdentifier, + not_statement: NotStatement, + not_let: NotLet, + not_fn_decl: NotFnDecl, not_operator: NotOperator, not_literal: NotLiteral, not_string: NotString, @@ -160,20 +187,25 @@ impl Parser { pub fn new(tokens: Vec) -> Self { Self { tokens, panic_stack: vec![], errors: vec![], cursor: 0 } } + /// Resets the parser, so it can be reused + pub fn reset(&mut self) -> &mut Self { + *self = Self::new(std::mem::take(&mut self.tokens)); + self + } /// Parses the [start of an AST](Start) pub fn parse(&mut self) -> PResult { self.consume_comments(); Ok(Start(self.program()?)) } /// Parses only one expression - pub fn parse_expr(&mut self) -> PResult { + pub fn parse_expr(&mut self) -> PResult { self.expr() } /// Peeks at the current token pub fn peek(&self) -> PResult<&Token> { self.tokens .get(self.cursor) - .ok_or(Error::end_of_file().maybe_token(self.tokens.last().cloned())) + .ok_or_else(|| Error::end_of_file().maybe_token(self.tokens.last().cloned())) } /// Consumes any number of consecutive comments fn consume_comments(&mut self) -> &mut Self { @@ -212,9 +244,7 @@ impl Parser { /// Advances forward until a token with type [`t`](Type) is encountered fn advance_until(&mut self, t: Type) -> PResult<&mut Self> { while self.matches(t).is_err() { - self.check_eof() - .map_err(|e| e.with_reason(Expected(t)))? - .consume(); + self.check_eof().wrap(Error::expected(t))?.consume(); } Ok(self) } @@ -244,9 +274,10 @@ impl Parser { fn matches(&mut self, t: Type) -> PResult<&Token> { let token = self.check_eof()?.peek().expect("self should not be eof"); if token.ty() != t { - Err(Error::expected(t).token(token.clone()))? + Err(Error::expected(t).token(token.clone())) + } else { + Ok(token) } - Ok(token) } /// Consumes, without returning, a token with the given [Keyword], or returns an error. /// @@ -282,7 +313,7 @@ impl Parser { /// Parses an [Identifier] fn identifier(&mut self) -> PResult { let out = match self.matches(Type::Identifier)?.data() { - Data::Identifier(id) => Identifier(id.to_string()), + Data::Identifier(id) => Identifier { name: id.to_string(), index: None }, _ => Err(Error::not_identifier())?, }; self.consume(); @@ -363,30 +394,25 @@ impl Parser { fn stmt(&mut self) -> PResult { let token = self.peek()?; match token.ty() { - Type::Keyword(Keyword::Let) => self.let_stmt().map(Stmt::Let), - Type::Keyword(Keyword::Fn) => self.fn_decl().map(Stmt::Fn), + Type::Keyword(Keyword::Let) => self.let_stmt().map(Stmt::Let).wrap(Error::not_let()), + Type::Keyword(Keyword::Fn) => self.fn_decl().map(Stmt::Fn).wrap(Error::not_fn_decl()), _ => { let out = Stmt::Expr(self.expr()?); self.consume_type(Type::Semi)?; Ok(out) } } + .wrap(Error::not_statement()) } /// Parses a [Let] statement fn let_stmt(&mut self) -> PResult { - let out = Let { - mutable: self.consume().keyword(Keyword::Mut).is_ok(), - name: self.identifier()?, - ty: self - .consume_type(Type::Colon) - .and_then(Self::identifier) - .ok(), - init: self.consume_type(Type::Eq).and_then(Self::expr).ok(), - }; + self.keyword(Keyword::Let)?; + let out = + Let { name: self.name()?, init: self.consume_type(Type::Eq).and_then(Self::expr).ok() }; self.consume_type(Type::Semi)?; Ok(out) } - /// Parses a [Function] statement + /// Parses a [function declaration](FnDecl) statement fn fn_decl(&mut self) -> PResult { self.keyword(Keyword::Fn)?; let name = self.identifier()?; @@ -394,37 +420,98 @@ impl Parser { let args = self.params()?; self.consume_type(Type::RParen)?; // TODO: Parse type-expressions and store return types in the AST - if self.consume_type(Type::Arrow).is_ok() { - self.expr()?; - } - Ok(FnDecl { name, args, body: self.block()? }) + let ty = if self.consume_type(Type::Arrow).is_ok() { + Some(self.type_expr()?) + } else { + None + }; + Ok(FnDecl { name: Name { name, mutable: false, ty }, args, body: self.block()? }) } - - fn params(&mut self) -> PResult> { + /// Parses a [parameter](Name) list for [FnDecl] + fn params(&mut self) -> PResult> { let mut args = vec![]; - while let Ok(ident) = self.identifier() { - args.push(ident); - if self.consume_type(Type::Colon).is_ok() { - // TODO: Parse type-expressions and make this mandatory - self.expr()?; - } + while let Ok(name) = self.name() { + args.push(name); if self.consume_type(Type::Comma).is_err() { break; } } Ok(args) } + /// Parses a [Name]; the object of a let statement, or a single function parameter. + fn name(&mut self) -> PResult { + Ok(Name { + mutable: self.keyword(Keyword::Mut).is_ok(), + name: self.identifier()?, + ty: self + .consume_type(Type::Colon) + .and_then(|this| this.type_expr()) + .ok(), + }) + } } +/// Path Expressions +impl Parser { + fn path(&mut self) -> PResult { + let absolute = self.consume_type(Type::ColonColon).is_ok(); + let mut parts = vec![]; + while let Ok(id) = self.path_part() { + parts.push(id); + if self.consume_type(Type::ColonColon).is_err() { + break; + } + } + Ok(Path { absolute, parts }) + } + + fn path_part(&mut self) -> PResult { + match self.peek()?.ty() { + Type::Identifier => self.identifier().map(PathPart::PathIdent), + Type::Keyword(Keyword::Super) => { + self.keyword(Keyword::Super).map(|_| PathPart::PathSuper) + } + Type::Keyword(Keyword::SelfKw) => { + self.keyword(Keyword::SelfKw).map(|_| PathPart::PathSelf) + } + e => Err(Error::not_path_segment(e)) + } + } +} +/// Type Expressions +impl Parser { + /// Parses a [Type Expression](TypeExpr) + fn type_expr(&mut self) -> PResult { + match self.peek()?.ty() { + Type::LParen => self.type_tuple().map(TypeExpr::TupleType), + Type::Bang => self.type_never().map(TypeExpr::Never), + _ => self.path().map(TypeExpr::TypePath), + } + } + fn type_tuple(&mut self) -> PResult { + self.consume_type(Type::LParen)?; + let mut types = vec![]; + while let Ok(ty) = self.type_expr() { + types.push(ty); + if self.consume_type(Type::Comma).is_err() { + break; + } + } + self.consume_type(Type::RParen)?; + Ok(TupleType { types }) + } + fn type_never(&mut self) -> PResult { + self.consume_type(Type::Bang).map(|_| Never) + } +} + /// Expressions impl Parser { - /// Parses an [expression](expression::Expr) - fn expr(&mut self) -> PResult { - use expression::Expr; + /// Parses an [expression](Expr) + fn expr(&mut self) -> PResult { Ok(Expr(self.assign()?)) } - /// Parses a [block expression](expression::Block) - fn block(&mut self) -> PResult { - use expression::{Block, Expr}; + /// Parses a [block expression](Block) + fn block(&mut self) -> PResult { let mut statements = vec![]; let mut expr: Option> = None; self.consume_type(Type::LCurly)?; @@ -440,11 +527,10 @@ impl Parser { Err(_) => statements.push(self.stmt()?), } } - Ok(Block { statements, expr }) + Ok(Block { statements, expr, let_count: None }) } - /// Parses a [primary expression](expression::Primary) - fn primary(&mut self) -> PResult { - use expression::Primary; + /// Parses a [primary expression](Primary) + fn primary(&mut self) -> PResult { let token = self.peek()?; match token.ty() { Type::Identifier => self.identifier().map(Primary::Identifier), @@ -465,7 +551,7 @@ impl Parser { /// Parses a [call expression](Call) fn call(&mut self) -> PResult { let callee = self.primary()?; - let Ok(Type::LParen) = self.peek().map(Token::ty) else { + if self.matches(Type::LParen).is_err() { return Ok(Call::Primary(callee)); }; let mut args = vec![]; @@ -526,12 +612,11 @@ impl Parser { /// ``` /// becomes /// ```rust,ignore -/// fn function_name(&mut self) -> PResult { ... } +/// fn function_name(&mut self) -> PResult { ... } /// ``` macro binary ($($f:ident = $a:ident, $b:ident);*$(;)?) {$( - #[doc = concat!("Parses a(n) [", stringify!($f), " operation](math::Operation::Binary) expression")] - fn $f (&mut self) -> PResult { - use math::{Operation, Binary}; + #[doc = concat!("Parses a(n) [", stringify!($f), " operation](Operation::Binary) expression")] + fn $f (&mut self) -> PResult { let (first, mut other) = (self.$a()?, vec![]); while let Ok(op) = self.$b() { other.push((op, self.$a()?)); @@ -543,9 +628,7 @@ macro binary ($($f:ident = $a:ident, $b:ident);*$(;)?) {$( )*} /// # [Arithmetic and Logical Subexpressions](math) impl Parser { - fn assign(&mut self) -> PResult { - use expression::Primary; - use math::{Assign, Operation}; + fn assign(&mut self) -> PResult { let next = self.compare()?; let Ok(operator) = self.assign_op() else { return Ok(next); @@ -569,9 +652,8 @@ impl Parser { term = factor, term_op; factor = unary, factor_op; } - /// Parses a [unary operation](math::Operation::Unary) expression - fn unary(&mut self) -> PResult { - use math::{Operation, Unary}; + /// Parses a [unary operation](Operation::Unary) expression + fn unary(&mut self) -> PResult { let mut operators = vec![]; while let Ok(op) = self.unary_op() { operators.push(op) @@ -584,15 +666,15 @@ impl Parser { operand: self.primary_operation()?.into(), })) } - /// Parses a [primary operation](math::Operation::Primary) expression - fn primary_operation(&mut self) -> PResult { - Ok(math::Operation::Call(self.call()?)) + /// Parses a [primary operation](Operation::Primary) expression + fn primary_operation(&mut self) -> PResult { + Ok(Operation::Call(self.call()?)) } } macro operator_impl ($($(#[$m:meta])* $f:ident : {$($type:pat => $op:ident),*$(,)?})*) { $($(#[$m])* fn $f(&mut self) -> PResult { use operator::Binary; - let token = self.peek()?; + let token = self.peek().wrap(Error::not_operator())?; let out = Ok(match token.ty() { $($type => Binary::$op,)* _ => Err(Error::not_operator().token(token.clone()))?, @@ -689,9 +771,8 @@ impl Parser { } /// # [Control Flow](control) impl Parser { - /// Parses a [control flow](control::Flow) expression - fn flow(&mut self) -> PResult { - use control::Flow; + /// Parses a [control flow](Flow) expression + fn flow(&mut self) -> PResult { use Keyword::{Break, Continue, For, If, Return, While}; let token = self.peek()?; match token.ty() { @@ -703,55 +784,47 @@ impl Parser { Type::Keyword(Continue) => self.parse_continue().map(Flow::Continue), e => Err(Error::unexpected(e).token(token.clone()))?, } - .map_err(|e| e.with_reason(IncompleteBranch)) + .wrap(Error::not_branch()) } - /// Parses an [if](control::If) expression - fn parse_if(&mut self) -> PResult { + /// Parses an [if](If) expression + fn parse_if(&mut self) -> PResult { self.keyword(Keyword::If)?; - Ok(control::If { - cond: self.expr()?.into(), - body: self.block()?, - else_: self.parse_else()?, - }) + Ok(If { cond: self.expr()?.into(), body: self.block()?, else_: self.parse_else()? }) } - /// Parses a [while](control::While) expression - fn parse_while(&mut self) -> PResult { + /// Parses a [while](While) expression + fn parse_while(&mut self) -> PResult { self.keyword(Keyword::While)?; - Ok(control::While { - cond: self.expr()?.into(), - body: self.block()?, - else_: self.parse_else()?, - }) + Ok(While { cond: self.expr()?.into(), body: self.block()?, else_: self.parse_else()? }) } - /// Parses a [for](control::For) expression - fn parse_for(&mut self) -> PResult { + /// Parses a [for](For) expression + fn parse_for(&mut self) -> PResult { self.keyword(Keyword::For)?; - Ok(control::For { + Ok(For { var: self.identifier()?, iter: { self.keyword(Keyword::In)?.expr()?.into() }, body: self.block()?, else_: self.parse_else()?, }) } - /// Parses an [else](control::Else) sub-expression - fn parse_else(&mut self) -> PResult> { + /// Parses an [else](Else) sub-expression + fn parse_else(&mut self) -> PResult> { // it's fine for `else` to be missing entirely self.keyword(Keyword::Else) .ok() - .map(|p| Ok(control::Else { expr: p.expr()?.into() })) + .map(|p| Ok(Else { expr: p.expr()?.into() })) .transpose() } - /// Parses a [break](control::Break) expression - fn parse_break(&mut self) -> PResult { - Ok(control::Break { expr: self.keyword(Keyword::Break)?.expr()?.into() }) + /// Parses a [break](Break) expression + fn parse_break(&mut self) -> PResult { + Ok(Break { expr: self.keyword(Keyword::Break)?.expr()?.into() }) } - /// Parses a [return](control::Return) expression - fn parse_return(&mut self) -> PResult { - Ok(control::Return { expr: self.keyword(Keyword::Return)?.expr()?.into() }) + /// Parses a [return](Return) expression + fn parse_return(&mut self) -> PResult { + Ok(Return { expr: self.keyword(Keyword::Return)?.expr()?.into() }) } - /// Parses a [continue](control::Continue) expression - fn parse_continue(&mut self) -> PResult { + /// Parses a [continue](Continue) expression + fn parse_continue(&mut self) -> PResult { self.keyword(Keyword::Continue)?; - Ok(control::Continue) + Ok(Continue) } } diff --git a/libconlang/src/pretty_printer.rs b/libconlang/src/pretty_printer.rs index 762b09e..39f0742 100644 --- a/libconlang/src/pretty_printer.rs +++ b/libconlang/src/pretty_printer.rs @@ -1,4 +1,5 @@ //! A [Printer] pretty-prints a Conlang [syntax tree](crate::ast) + use super::ast::preamble::*; use std::{ fmt::Display, @@ -7,17 +8,14 @@ use std::{ /// Prettily prints this node pub trait PrettyPrintable { /// Prettily prints this node - fn print(&self); - /// Prettily writes this node into the given [Writer](Write) - fn write(&self, into: impl Write) -> IOResult<()>; -} -impl PrettyPrintable for Start { fn print(&self) { - let _ = Printer::default().visit(self); + let _ = self.visit(&mut Printer::default()); } + /// Prettily writes this node into the given [Writer](Write) fn write(&self, into: impl Write) -> IOResult<()> { - Printer::from(into).visit(self) + self.visit(&mut Printer::from(into)) } + fn visit(&self, p: &mut Printer) -> IOResult<()>; } /// Prints a Conlang [syntax tree](crate::ast) into a [Writer](Write) @@ -26,6 +24,18 @@ pub struct Printer { level: u32, writer: W, } + +impl Write for Printer { + #[inline] + fn write(&mut self, buf: &[u8]) -> IOResult { + self.writer.write(buf) + } + #[inline] + fn flush(&mut self) -> IOResult<()> { + self.writer.flush() + } +} + impl<'t> Default for Printer> { fn default() -> Self { Self { level: 0, writer: stdout().lock() } @@ -67,82 +77,154 @@ impl Printer { macro visit_operator($self:ident.$op:expr) { $self.space()?.put($op)?.space().map(drop) } -impl Visitor> for Printer { - fn visit_program(&mut self, prog: &Program) -> IOResult<()> { - // delegate to the walker - for stmt in &prog.0 { - self.visit_statement(stmt)?; + +impl PrettyPrintable for Start { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Self(program) = self; + program.visit(p) + } +} +impl PrettyPrintable for Program { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Self(module) = self; + for decl in module { + decl.visit(p)?; + p.newline()?; } Ok(()) } - fn visit_statement(&mut self, stmt: &Stmt) -> IOResult<()> { - match stmt { - Stmt::Let(stmt) => self.visit_let(stmt)?, - Stmt::Fn(function) => self.visit_fn_decl(function)?, - Stmt::Expr(e) => { - self.visit_expr(e)?; - self.put(';').map(drop)? +} +impl PrettyPrintable for Stmt { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + Stmt::Let(value) => value.visit(p), + Stmt::Fn(value) => value.visit(p), + Stmt::Expr(value) => { + value.visit(p)?; + p.put(';')?.newline().map(drop) } } - self.newline().map(drop) } - fn visit_let(&mut self, stmt: &Let) -> IOResult<()> { - let Let { name, mutable, ty, init } = stmt; - self.put("let")?.space()?; +} +impl PrettyPrintable for Let { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Let { name: Name { name, mutable, ty }, init } = self; + p.put("let")?.space()?; if *mutable { - self.put("mut")?.space()?; + p.put("mut")?.space()?; } - self.visit_identifier(name)?; + name.visit(p)?; if let Some(ty) = ty { - self.put(':')?.space()?.visit_identifier(ty)?; + ty.visit(p.put(':')?.space()?)? } if let Some(init) = init { - self.space()?.put('=')?.space()?.visit_expr(init)?; + init.visit(p.space()?.put('=')?.space()?)?; } - self.put(';').map(drop) + p.put(';').map(drop) } - - fn visit_fn_decl(&mut self, function: &FnDecl) -> IOResult<()> { - let FnDecl { name, args, body } = function; - self.put("fn")?.space()?; - self.visit_identifier(name)?; - self.space()?.put('(')?; +} +impl PrettyPrintable for FnDecl { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let FnDecl { name, args, body } = self; + p.put("fn")?.space()?; + name.visit(p)?; + p.space()?.put('(')?; for (idx, arg) in args.iter().enumerate() { if idx > 0 { - self.put(',')?.space()?; + p.put(',')?.space()?; } - self.visit_identifier(arg)?; + arg.visit(p)?; } - self.put(')')?.space()?; - self.visit_block(body) + p.put(')')?.space()?; + body.visit(p) } +} +impl PrettyPrintable for Name { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + if self.mutable { + p.put("mut")?.space()?; + } + self.name.visit(p)?; + if let Some(ty) = &self.ty { + ty.visit(p.put(':')?.space()?)?; + } + Ok(()) + } +} +impl PrettyPrintable for TypeExpr { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + TypeExpr::TupleType(_tt) => todo!(), + TypeExpr::TypePath(t) => t.visit(p), + TypeExpr::Empty(_) => p.put("()").map(drop), + TypeExpr::Never(_) => p.put('!').map(drop), + } + } +} +impl PrettyPrintable for Path { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Path { absolute, parts } = self; + if *absolute { + p.put("::")?; + } + for (idx, part) in parts.iter().enumerate() { + if idx != 0 { p.put("::")?;} + part.visit(p)?; + } + Ok(()) + } +} +impl PrettyPrintable for PathPart { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + PathPart::PathSuper => p.put("super").map(drop), + PathPart::PathSelf => p.put("self").map(drop), + PathPart::PathIdent(id) =>id.visit(p), + } + } +} +impl PrettyPrintable for Block { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Block { let_count: _, statements, expr } = self; + p.put('{')?.indent().newline()?; + for stmt in statements { + stmt.visit(p)?; + } + for expr in expr { + expr.visit(p)?; + } + p.dedent().newline()?.put('}').map(drop) + } +} +impl PrettyPrintable for Expr { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Expr(expr) = self; + expr.visit(p) + } +} - fn visit_assign(&mut self, assign: &math::Assign) -> IOResult<()> { - let math::Assign { target, operator, init } = assign; - self.visit_identifier(target)?; - self.visit_assign_op(operator)?; - self.visit_operation(init) - } - fn visit_binary(&mut self, binary: &math::Binary) -> IOResult<()> { - let math::Binary { first, other } = binary; - self.put('(')?.visit_operation(first)?; - for (op, other) in other { - self.visit_binary_op(op)?; - self.visit_operation(other)?; +impl PrettyPrintable for Operation { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + Operation::Assign(value) => value.visit(p), + Operation::Binary(value) => value.visit(p), + Operation::Unary(value) => value.visit(p), + Operation::Call(value) => value.visit(p), } - self.put(')').map(drop) } - fn visit_unary(&mut self, unary: &math::Unary) -> IOResult<()> { - let math::Unary { operators, operand } = unary; - for op in operators { - self.visit_unary_op(op)?; - } - self.visit_operation(operand) +} +impl PrettyPrintable for Assign { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Assign { target, operator, init } = self; + target.visit(p)?; + operator.visit(p)?; + init.visit(p) } - - fn visit_assign_op(&mut self, op: &operator::Assign) -> IOResult<()> { +} +impl PrettyPrintable for operator::Assign { + fn visit(&self, p: &mut Printer) -> IOResult<()> { use operator::Assign; - visit_operator!(self.match op { + visit_operator!(p.match self { Assign::Assign => "=", Assign::AddAssign => "+=", Assign::SubAssign => "-=", @@ -156,9 +238,23 @@ impl Visitor> for Printer { Assign::ShrAssign => ">>=", }) } - fn visit_binary_op(&mut self, op: &operator::Binary) -> IOResult<()> { +} +impl PrettyPrintable for Binary { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Binary { first, other } = self; + p.put('(')?; + first.visit(p)?; + for (op, other) in other { + op.visit(p)?; + other.visit(p)? + } + p.put(')').map(drop) + } +} +impl PrettyPrintable for operator::Binary { + fn visit(&self, p: &mut Printer) -> IOResult<()> { use operator::Binary; - visit_operator!(self.match op { + visit_operator!(p.match self { Binary::Mul => "*", Binary::Div => "/", Binary::Rem => "%", @@ -182,9 +278,20 @@ impl Visitor> for Printer { Binary::Greater => ">", }) } - fn visit_unary_op(&mut self, op: &operator::Unary) -> IOResult<()> { +} +impl PrettyPrintable for Unary { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Unary { operators, operand } = self; + for op in operators { + op.visit(p)?; + } + operand.visit(p) + } +} +impl PrettyPrintable for operator::Unary { + fn visit(&self, p: &mut Printer) -> IOResult<()> { use operator::Unary; - self.put(match op { + p.put(match self { Unary::RefRef => "&&", Unary::Deref => "*", Unary::Ref => "&", @@ -196,108 +303,155 @@ impl Visitor> for Printer { }) .map(drop) } - fn visit_if(&mut self, expr: &control::If) -> IOResult<()> { - self.put("while")?.space()?.visit_expr(&expr.cond)?; - self.space()?.visit_block(&expr.body)?; - match &expr.else_ { - Some(e) => self.visit_else(e), - None => Ok(()), - } - } - fn visit_while(&mut self, expr: &control::While) -> IOResult<()> { - self.put("while")?.space()?.visit_expr(&expr.cond)?; - self.space()?.visit_block(&expr.body)?; - match &expr.else_ { - Some(e) => self.visit_else(e), - None => Ok(()), - } - } - fn visit_for(&mut self, expr: &control::For) -> IOResult<()> { - self.put("for")?.space()?.visit_identifier(&expr.var)?; - self.space()?.put("in")?.space()?.visit_expr(&expr.iter)?; - self.space()?.visit_block(&expr.body)?; - match &expr.else_ { - Some(e) => self.visit_else(e), - None => Ok(()), - } - } - fn visit_else(&mut self, else_: &control::Else) -> IOResult<()> { - self.space()?.put("else")?.space()?.visit_expr(&else_.expr) - } - fn visit_continue(&mut self, _: &control::Continue) -> IOResult<()> { - self.put("continue").map(drop) - } - fn visit_break(&mut self, brk: &control::Break) -> IOResult<()> { - self.put("break")?.space()?.visit_expr(&brk.expr) - } - fn visit_return(&mut self, ret: &control::Return) -> IOResult<()> { - self.put("return")?.space()?.visit_expr(&ret.expr) - } +} - fn visit_identifier(&mut self, ident: &Identifier) -> IOResult<()> { - self.put(&ident.0).map(drop) - } - fn visit_string_literal(&mut self, string: &str) -> IOResult<()> { - self.put("\"")?.put(string)?.put("\"").map(drop) - } - fn visit_char_literal(&mut self, char: &char) -> IOResult<()> { - self.put("'")?.put(char)?.put("'").map(drop) - } - fn visit_bool_literal(&mut self, bool: &bool) -> IOResult<()> { - self.put(bool).map(drop) - } - fn visit_float_literal(&mut self, float: &literal::Float) -> IOResult<()> { - self.put(float.sign)? - .put(float.exponent)? - .put(float.mantissa) - .map(drop) - } - fn visit_int_literal(&mut self, int: &u128) -> IOResult<()> { - self.put(int).map(drop) - } - fn visit_empty(&mut self) -> IOResult<()> { - self.put("()").map(drop) - } - - fn visit_block(&mut self, block: &expression::Block) -> IOResult<()> { - self.put('{')?.indent().newline()?; - for stmt in &block.statements { - self.visit_statement(stmt)?; +impl PrettyPrintable for Call { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + Call::FnCall(value) => value.visit(p), + Call::Primary(value) => value.visit(p), } - for expr in &block.expr { - self.visit_expr(expr)?; - } - self.dedent().newline()?.put('}').map(drop) } - - fn visit_tuple(&mut self, tuple: &Tuple) -> IOResult<()> { - for (idx, expr) in tuple.elements.iter().enumerate() { - if idx > 0 { - self.put(',')?.space()?; - } - self.visit_expr(expr)?; - } - Ok(()) - } - - fn visit_group(&mut self, expr: &Group) -> IOResult<()> { - self.put('(')?; - match expr { - Group::Tuple(tuple) => self.space()?.visit_tuple(tuple), - Group::Single(expr) => self.space()?.visit_expr(expr), - Group::Empty => self.visit_empty(), - }?; - self.space()?.put(')').map(drop) - } - - fn visit_fn_call(&mut self, call: &FnCall) -> IOResult<()> { - let FnCall { callee, args } = call; - self.visit_primary(callee)?; +} +impl PrettyPrintable for FnCall { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let FnCall { callee, args } = self; + callee.visit(p)?; for arg_list in args { - self.put('(')?; - self.visit_tuple(arg_list)?; - self.put(')')?; + p.put('(')?; + arg_list.visit(p)?; + p.put(')')?; } Ok(()) } } +impl PrettyPrintable for Primary { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + Primary::Identifier(value) => value.visit(p), + Primary::Literal(value) => value.visit(p), + Primary::Block(value) => value.visit(p), + Primary::Group(value) => value.visit(p), + Primary::Branch(value) => value.visit(p), + } + } +} +impl PrettyPrintable for Group { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + p.put('(')?; + match self { + Group::Tuple(tuple) => tuple.visit(p.space()?)?, + Group::Single(expr) => expr.visit(p.space()?)?, + Group::Empty => (), + }; + p.space()?.put(')').map(drop) + } +} +impl PrettyPrintable for Tuple { + /// Writes a *non-delimited* [Tuple] to the [Printer] + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Tuple { elements } = self; + for (idx, expr) in elements.iter().enumerate() { + if idx > 0 { + p.put(',')?.space()?; + } + expr.visit(p)?; + } + Ok(()) + } +} + +impl PrettyPrintable for Identifier { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Identifier { name, index: _ } = self; // TODO: Pretty-print variable number as well + p.put(name).map(drop) + } +} +impl PrettyPrintable for Literal { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + Literal::String(value) => write!(p, "\"{value}\""), + Literal::Char(value) => write!(p, "'{value}'"), + Literal::Bool(value) => write!(p, "{value}"), + Literal::Float(value) => value.visit(p), + Literal::Int(value) => write!(p, "{value}"), + } + } +} +impl PrettyPrintable for Float { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Float { sign, exponent, mantissa } = self; + p.put(sign)?.put(exponent)?.put(mantissa).map(drop) + } +} + +impl PrettyPrintable for Flow { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + match self { + Flow::While(value) => value.visit(p), + Flow::If(value) => value.visit(p), + Flow::For(value) => value.visit(p), + Flow::Continue(value) => value.visit(p), + Flow::Return(value) => value.visit(p), + Flow::Break(value) => value.visit(p), + } + } +} +impl PrettyPrintable for While { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let While { cond, body, else_ } = self; + cond.visit(p.put("while")?.space()?)?; + body.visit(p.space()?)?; + match else_ { + Some(e) => e.visit(p), + None => Ok(()), + } + } +} +impl PrettyPrintable for If { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let If { cond, body, else_ } = self; + cond.visit(p.put("if")?.space()?)?; + body.visit(p.space()?)?; + match else_ { + Some(e) => e.visit(p), + None => Ok(()), + } + } +} +impl PrettyPrintable for For { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let For { var, iter, body, else_ } = self; + var.visit(p.put("for")?.space()?)?; + iter.visit(p.space()?.put("in")?.space()?)?; + body.visit(p.space()?)?; + match else_ { + Some(e) => e.visit(p), + None => Ok(()), + } + } +} +impl PrettyPrintable for Else { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Else { expr } = self; + expr.visit(p.space()?.put("else")?.space()?) + } +} +impl PrettyPrintable for Continue { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let control::Continue = self; // using pattern destructuring, rather than assigning to "Continue" + p.put("continue").map(drop) + } +} +impl PrettyPrintable for Break { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Break { expr } = self; + expr.visit(p.put("break")?.space()?) + } +} +impl PrettyPrintable for Return { + fn visit(&self, p: &mut Printer) -> IOResult<()> { + let Return { expr } = self; + expr.visit(p.put("return")?.space()?) + } +} diff --git a/libconlang/src/token/token_type.rs b/libconlang/src/token/token_type.rs index 2821de6..86a9d5d 100644 --- a/libconlang/src/token/token_type.rs +++ b/libconlang/src/token/token_type.rs @@ -87,6 +87,9 @@ pub enum Keyword { Let, Mut, Return, + SelfKw, + SelfTy, + Super, True, While, } @@ -174,6 +177,9 @@ impl Display for Keyword { Self::Let => "let".fmt(f), Self::Mut => "mut".fmt(f), Self::Return => "return".fmt(f), + Self::SelfKw => "self".fmt(f), + Self::SelfTy => "Self".fmt(f), + Self::Super => "super".fmt(f), Self::True => "true".fmt(f), Self::While => "while".fmt(f), } @@ -195,6 +201,9 @@ impl FromStr for Keyword { "let" => Self::Let, "mut" => Self::Mut, "return" => Self::Return, + "self" => Self::SelfKw, + "Self" => Self::SelfTy, + "super" => Self::Super, "true" => Self::True, "while" => Self::While, _ => Err(())?, diff --git a/sample-code/fibonacci.cl b/sample-code/fibonacci.cl index ef36508..e87b3f5 100644 --- a/sample-code/fibonacci.cl +++ b/sample-code/fibonacci.cl @@ -1,8 +1,7 @@ // Calculate Fibonacci numbers fn main() -> i128 { - print("fib(10):"); - fib(10) + print("fib(10): ", fib(10)); } /// Implements the classic recursive definition of fib()