cl-parser: Implement a module inlining pass

This commit is contained in:
John 2024-04-21 22:31:01 -05:00
parent 02323ae6f2
commit 9c3c2e8674
6 changed files with 235 additions and 103 deletions

View File

@ -0,0 +1,102 @@
//! The [ModuleInliner] reads files described in the module structure of the
use crate::Parser;
use cl_ast::{ast_visitor::Fold, *};
use cl_lexer::Lexer;
use std::{
ops::{Deref, DerefMut},
path::{Path, PathBuf},
};
type IoErrs = Vec<(PathBuf, std::io::Error)>;
type ParseErrs = Vec<(PathBuf, crate::error::Error)>;
pub struct ModuleInliner {
path: PathBuf,
io_errs: IoErrs,
parse_errs: ParseErrs,
}
impl ModuleInliner {
/// Creates a new [ModuleInliner]
pub fn new(root: impl AsRef<Path>) -> Self {
Self {
path: root.as_ref().to_path_buf(),
io_errs: Default::default(),
parse_errs: Default::default(),
}
}
pub fn inline(mut self, file: File) -> Result<File, (File, IoErrs, ParseErrs)> {
let file = self.fold_file(file);
if self.io_errs.is_empty() && self.parse_errs.is_empty() {
Ok(file)
} else {
Err((file, self.io_errs, self.parse_errs))
}
}
/// Records an [I/O error](std::io::Error), and rebuilds the outline [Module]
fn io_oops(&mut self, name: Identifier, error: std::io::Error) -> Module {
self.io_errs.push((self.path.clone(), error));
Module { name, kind: ModuleKind::Outline }
}
/// Records a [parse error](crate::error::Error), and rebuilds the outline [Module]
fn parse_oops(&mut self, name: Identifier, error: crate::error::Error) -> Module {
self.parse_errs.push((self.path.clone(), error));
Module { name, kind: ModuleKind::Outline }
}
}
impl Fold for ModuleInliner {
fn fold_module(&mut self, m: Module) -> cl_ast::Module {
let Module { name, kind } = m;
let mut inliner = PathMiser::new(self, &name.0);
let kind = inliner.fold_module_kind(kind);
let ModuleKind::Outline = kind else {
return Module { name, kind };
};
inliner.path.set_extension("cl");
let file = match std::fs::read_to_string(&inliner.path) {
Err(e) => return inliner.io_oops(name, e),
Ok(s) => s,
};
let kind = match Parser::new(Lexer::new(&file)).file() {
Err(e) => return inliner.parse_oops(name, e),
Ok(file) => ModuleKind::Inline(file),
};
inliner.path.set_extension("");
Module { name, kind: inliner.fold_module_kind(kind) }
}
}
/// I am the [Path] miser. Whatever I touch turns to [Path] in my clutch.
pub struct PathMiser<'mi> {
mi: &'mi mut ModuleInliner,
}
impl<'mi> PathMiser<'mi> {
pub fn new(mi: &'mi mut ModuleInliner, name: &str) -> Self {
mi.path.push(name);
Self { mi }
}
}
impl<'mi> Deref for PathMiser<'mi> {
type Target = ModuleInliner;
fn deref(&self) -> &Self::Target {
self.mi
}
}
impl<'mi> DerefMut for PathMiser<'mi> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.mi
}
}
impl<'mi> Drop for PathMiser<'mi> {
fn drop(&mut self) {
self.mi.path.pop();
}
}

View File

@ -14,3 +14,5 @@ use cl_token::*;
pub mod error; pub mod error;
pub mod parser; pub mod parser;
pub mod inliner;

View File

@ -3,12 +3,12 @@ use cl_ast::{
desugar::{squash_groups::SquashGroups, while_else::WhileElseDesugar}, desugar::{squash_groups::SquashGroups, while_else::WhileElseDesugar},
}; };
use cl_lexer::Lexer; use cl_lexer::Lexer;
use cl_parser::Parser; use cl_parser::{inliner::ModuleInliner, Parser};
use cl_typeck::{ use cl_typeck::{
definition::Def, name_collector::NameCollectable, project::Project, type_resolver::resolve, definition::Def, name_collector::NameCollectable, project::Project, type_resolver::resolve,
}; };
use repline::{error::Error as RlError, prebaked::*}; use repline::{error::Error as RlError, prebaked::*};
use std::error::Error; use std::{error::Error, path};
// Path to display in standard library errors // Path to display in standard library errors
const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl"; const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
@ -37,6 +37,7 @@ fn main() -> Result<(), Box<dyn Error>> {
Err(e)? Err(e)?
} }
}; };
let code = inline_modules(code, concat!(env!("PWD"), "/stdlib"));
unsafe { TREES.push(code) }.collect_in_root(&mut prj)?; unsafe { TREES.push(code) }.collect_in_root(&mut prj)?;
@ -80,6 +81,7 @@ fn enter_code(prj: &mut Project) -> Result<(), RlError> {
return Ok(Response::Break); return Ok(Response::Break);
} }
let code = Parser::new(Lexer::new(line)).file()?; let code = Parser::new(Lexer::new(line)).file()?;
let code = inline_modules(code, "");
let code = WhileElseDesugar.fold_file(code); let code = WhileElseDesugar.fold_file(code);
// Safety: this is totally unsafe // Safety: this is totally unsafe
unsafe { TREES.push(code) }.collect_in_root(prj)?; unsafe { TREES.push(code) }.collect_in_root(prj)?;
@ -117,6 +119,7 @@ fn query_type_expression(prj: &mut Project) -> Result<(), RlError> {
} }
fn resolve_all(prj: &mut Project) -> Result<(), Box<dyn Error>> { fn resolve_all(prj: &mut Project) -> Result<(), Box<dyn Error>> {
prj.resolve_imports()?;
for id in prj.pool.key_iter() { for id in prj.pool.key_iter() {
resolve(prj, id)?; resolve(prj, id)?;
} }
@ -148,6 +151,21 @@ fn pretty_def(def: &Def, id: impl Into<usize>) {
println!("\x1b[90m{module}\x1b[0m"); println!("\x1b[90m{module}\x1b[0m");
} }
fn inline_modules(code: cl_ast::File, path: impl AsRef<path::Path>) -> cl_ast::File {
match ModuleInliner::new(path).inline(code) {
Err((code, io, parse)) => {
for (file, error) in io {
eprintln!("{}:{error}", file.display());
}
for (file, error) in parse {
eprintln!("{}:{error}", file.display());
}
code
}
Ok(code) => code,
}
}
fn clear() -> Result<(), Box<dyn Error>> { fn clear() -> Result<(), Box<dyn Error>> {
println!("\x1b[H\x1b[2J"); println!("\x1b[H\x1b[2J");
banner(); banner();

View File

@ -1,107 +1,10 @@
//! # The Conlang Standard Library //! # The Conlang Standard Library
#[intrinsic = "bool"] pub mod preamble {
pub type bool; pub use super::num::*;
#[intrinsic = "char"]
pub type char;
#[intrinsic = "i8"]
pub type i8;
#[intrinsic = "i16"]
pub type i16;
#[intrinsic = "i32"]
pub type i32;
#[intrinsic = "i64"]
pub type i64;
#[intrinsic = "u8"]
pub type u8;
#[intrinsic = "u16"]
pub type u16;
#[intrinsic = "u32"]
pub type u32;
#[intrinsic = "u64"]
pub type u64;
impl bool {
const MIN: Self = false;
const MAX: Self = {
fn ret_true() -> Self {
true
}
ret_true()
};
} }
fn if_else() -> i32 { pub mod num;
// block 1
let x = 10;
let y = x + 2;
if x > 10
// compare x and 10
// end block 1, goto block 2 else goto block 3
{
// block 2
4
// end block 2, goto block 4
} else {
// block 3
5
// end block 3, goto block 4
}
// block 4
}
#[cfg("test")] #[cfg("test")]
mod test { mod test;
//! Tests for funky behavior
struct UnitLike;
struct TupleLike(super::i32, super::self::test::super::char)
struct StructLike {
pub member1: UnitLike,
member2: TupleLike,
}
enum NeverLike;
enum EmptyLike {
Empty,
}
enum Hodgepodge {
Empty,
UnitLike (),
Tuple (TupleLike),
StructEmpty {},
StructLike { member1: UnitLike, member2: TupleLike },
}
fn noop () -> bool {
loop if false {
} else break loop if false {
} else break loop if false {
} else break true;
}
fn while_else() -> i32 {
while conditional {
pass
} else {
fail
}
}
}

41
stdlib/num.cl Normal file
View File

@ -0,0 +1,41 @@
//! The primitive numeric types
#[intrinsic = "bool"]
pub type bool;
#[intrinsic = "char"]
pub type char;
#[intrinsic = "i8"]
pub type i8;
#[intrinsic = "i16"]
pub type i16;
#[intrinsic = "i32"]
pub type i32;
#[intrinsic = "i64"]
pub type i64;
#[intrinsic = "u8"]
pub type u8;
#[intrinsic = "u16"]
pub type u16;
#[intrinsic = "u32"]
pub type u32;
#[intrinsic = "u64"]
pub type u64;
impl bool {
const MIN: Self = false;
const MAX: Self = {
fn ret_true() -> Self {
true
}
ret_true()
};
}

66
stdlib/test.cl Normal file
View File

@ -0,0 +1,66 @@
//! Tests for funky behavior
use super::preamble::*;
struct UnitLike;
struct TupleLike(super::i32, super::self::test::char)
struct StructLike {
pub member1: UnitLike,
member2: TupleLike,
}
enum NeverLike;
enum EmptyLike {
Empty,
}
enum Hodgepodge {
Empty,
UnitLike (),
Tuple (TupleLike),
StructEmpty {},
StructLike { member1: UnitLike, member2: TupleLike },
}
fn noop () -> bool {
loop if false {
} else break loop if false {
} else break loop if false {
} else break true;
}
fn while_else() -> i32 {
while conditional {
pass
} else {
fail
}
}
fn if_else() -> i32 {
// block 1
let x = 10;
let y = x + 2;
if x > 10
// compare x and 10
// end block 1, goto block 2 else goto block 3
{
// block 2
4
// end block 2, goto block 4
} else {
// block 3
5
// end block 3, goto block 4
}
// block 4
}
let x = 0;