cl-typeck: Computer! Define "types!"
WARNING: The type checker is still a MAJOR work in progress. You'll be unable to ignore the stringly-typed error handling, effort duplication, and general poor code quality all around. However, this makes leaps and bounds toward a functional, if primitive, type checker. Definitions now borrow their data from the AST, reducing needless copies. - This unfortunately means the REPL has to keep around old ASTs, but... Eh. Probably have to keep those around anyway. The "char" primitive type has been added. Semantics TBD. Modules definition, type_kind, and value_kind have been consolidated into one. Project now keeps track of the set of unnamed (anonymous) types (i.e. tuples, function pointers, references) and will yield them freely. - Project can now also evaluate arbitrary type expressions via the EvaluableTypeExpression trait. The NameCollector has been replaced with trait NameCollectable. - This pass visits each node in the AST which can have an item declaration inside it, and constructs an unevaluated module tree. The TypeResolver has been replaced with trait TypeResolvable and the function resolve() - This pass visits each *Def* in the project, and attempts to derive all subtypes. - It's important to note that for Items, unlike EvaluableTypeExpression, the ID passed into resolve_type is the ID of `self`, not of the parent! typeck.rs: - The Conlang "standard library" is included in the binary - There's a main menu now! Type "help" for options. - Queries have been upgraded from paths to full type expressions! - Querying doesn't currently trigger resolution, but it could!
This commit is contained in:
parent
83694988c3
commit
9e90eea7b6
@ -1,37 +1,37 @@
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use cl_repl::repline::{error::Error as RlError, Repline};
|
||||
use cl_typeck::{name_collector::NameCollector, project::Project};
|
||||
use cl_typeck::{
|
||||
definition::Def, name_collector::NameCollectable, project::Project, type_resolver::resolve,
|
||||
};
|
||||
use std::error::Error;
|
||||
|
||||
const STDLIB_PATH: &str = "stdlib/lib.cl";
|
||||
const STDLIB: &str = include_str!("../../stdlib/lib.cl");
|
||||
|
||||
const C_MAIN: &str = "\x1b[30m";
|
||||
const C_RESV: &str = "\x1b[35m";
|
||||
const C_CODE: &str = "\x1b[36m";
|
||||
|
||||
/// A home for immutable intermediate ASTs
|
||||
///
|
||||
/// TODO: remove this.
|
||||
static mut TREES: TreeManager = TreeManager::new();
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut prj = Project::default();
|
||||
let mut tcol = NameCollector::new(&mut prj);
|
||||
|
||||
println!(
|
||||
"--- {} v{} 💪🦈 ---",
|
||||
env!("CARGO_BIN_NAME"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
);
|
||||
let mut parser = Parser::new(Lexer::new(STDLIB));
|
||||
let code = match parser.file() {
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
eprintln!("{STDLIB_PATH}:{e}");
|
||||
Err(e)?
|
||||
}
|
||||
};
|
||||
unsafe { TREES.push(code) }.collect_in_root(&mut prj)?;
|
||||
|
||||
read_and(
|
||||
"\x1b[33m",
|
||||
"cl>",
|
||||
"? >",
|
||||
|line| -> Result<_, Box<dyn Error>> {
|
||||
if line.trim_start().is_empty() {
|
||||
query(&tcol)?;
|
||||
return Ok(Response::Deny);
|
||||
}
|
||||
let mut parser = Parser::new(Lexer::new(line));
|
||||
let code = match parser.file() {
|
||||
Ok(code) => code,
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
tcol.file(&code)?;
|
||||
Ok(Response::Accept)
|
||||
},
|
||||
)
|
||||
main_menu(&mut prj)
|
||||
}
|
||||
|
||||
pub enum Response {
|
||||
@ -43,10 +43,9 @@ pub enum Response {
|
||||
fn read_and(
|
||||
color: &str,
|
||||
begin: &str,
|
||||
again: &str,
|
||||
mut f: impl FnMut(&str) -> Result<Response, Box<dyn Error>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut rl = Repline::new(color, begin, again);
|
||||
let mut rl = Repline::new(color, begin, "? >");
|
||||
loop {
|
||||
let line = match rl.read() {
|
||||
Err(RlError::CtrlC(_)) => break,
|
||||
@ -68,42 +67,117 @@ fn read_and(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn query(prj: &Project) -> Result<(), Box<dyn Error>> {
|
||||
use cl_typeck::{
|
||||
definition::{Def, DefKind},
|
||||
type_kind::TypeKind,
|
||||
};
|
||||
read_and("\x1b[35m", "qy>", "? >", |line| {
|
||||
if line.trim_start().is_empty() {
|
||||
fn main_menu(prj: &mut Project) -> Result<(), Box<dyn Error>> {
|
||||
banner();
|
||||
read_and(C_MAIN, "mu>", |line| {
|
||||
match line.trim() {
|
||||
"c" | "code" => enter_code(prj),
|
||||
"clear" => clear(),
|
||||
"e" | "exit" => return Ok(Response::Break),
|
||||
"l" | "list" => list_types(prj),
|
||||
"q" | "query" => query_type_expression(prj),
|
||||
"r" | "resolve" => resolve_all(prj),
|
||||
"h" | "help" => {
|
||||
println!(
|
||||
"Valid commands are:
|
||||
code (c): Enter code to type-check
|
||||
list (l): List all known types
|
||||
query (q): Query the type system
|
||||
resolve (r): Perform type resolution
|
||||
help (h): Print this list
|
||||
exit (e): Exit the program"
|
||||
);
|
||||
return Ok(Response::Deny);
|
||||
}
|
||||
_ => Err(r#"Invalid command. Type "help" to see the list of valid commands."#)?,
|
||||
}
|
||||
.map(|_| Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn enter_code(prj: &mut Project) -> Result<(), Box<dyn Error>> {
|
||||
read_and(C_CODE, "cl>", |line| {
|
||||
if line.trim().is_empty() {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
match line {
|
||||
"$all\n" => println!("{prj:#?}"),
|
||||
_ => {
|
||||
// parse it as a path, and convert the path into a borrowed path
|
||||
let path = Parser::new(Lexer::new(line)).path()?;
|
||||
let code = Parser::new(Lexer::new(line)).file()?;
|
||||
|
||||
// Safety: this is totally unsafe
|
||||
unsafe { TREES.push(code) }.collect_in_root(prj)?;
|
||||
|
||||
let Some((type_id, path)) = prj.get_type((&path).into(), prj.module_root) else {
|
||||
return Ok(Response::Deny);
|
||||
};
|
||||
let Def { name, vis, meta: _, kind, source: _, module } = &prj[type_id];
|
||||
match (kind, prj.get_value(path, type_id)) {
|
||||
(_, Some((val, path))) => {
|
||||
println!("value {}; {path}\n{:#?}", usize::from(val), prj[val])
|
||||
}
|
||||
(DefKind::Type(TypeKind::Module), None) => println!(
|
||||
"{vis}mod \"{name}\" (#{}); {path}\n{:#?}",
|
||||
usize::from(type_id),
|
||||
module
|
||||
),
|
||||
(_, None) => println!(
|
||||
"type {name}(#{}); {path}\n{:#?}",
|
||||
usize::from(type_id),
|
||||
prj.pool[type_id]
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn query_type_expression(prj: &mut Project) -> Result<(), Box<dyn Error>> {
|
||||
read_and(C_RESV, "ty>", |line| {
|
||||
if line.trim().is_empty() {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
// parse it as a path, and convert the path into a borrowed path
|
||||
let ty = Parser::new(Lexer::new(line)).ty()?.kind;
|
||||
let id = prj.evaluate(&ty, prj.root)?;
|
||||
pretty_def(&prj[id], id);
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_all(prj: &mut Project) -> Result<(), Box<dyn Error>> {
|
||||
for id in prj.pool.key_iter() {
|
||||
resolve(prj, id)?;
|
||||
}
|
||||
println!("Types resolved successfully!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_types(prj: &mut Project) -> Result<(), Box<dyn Error>> {
|
||||
println!(" name\x1b[30G type");
|
||||
for (idx, Def { name, vis, kind, .. }) in prj.pool.iter().enumerate() {
|
||||
print!("{idx:3}: {vis}");
|
||||
if name.is_empty() {
|
||||
print!("\x1b[30m_\x1b[0m")
|
||||
}
|
||||
println!("{name}\x1b[30G| {kind}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pretty_def(def: &Def, id: impl Into<usize>) {
|
||||
let id = id.into();
|
||||
let Def { vis, name, kind, module, meta, source: _ } = def;
|
||||
for meta in *meta {
|
||||
println!("#[{meta}]")
|
||||
}
|
||||
println!("{vis}{name} [id: {id}] = {kind}");
|
||||
println!("Module:\n\x1b[97m{module}\x1b[0m");
|
||||
}
|
||||
|
||||
fn clear() -> Result<(), Box<dyn Error>> {
|
||||
println!("\x1b[H\x1b[2J");
|
||||
banner();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn banner() {
|
||||
println!(
|
||||
"--- {} v{} 💪🦈 ---",
|
||||
env!("CARGO_BIN_NAME"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
);
|
||||
}
|
||||
|
||||
/// Keeps leaked references to past ASTs, for posterity:tm:
|
||||
struct TreeManager {
|
||||
trees: Vec<&'static cl_ast::File>,
|
||||
}
|
||||
|
||||
impl TreeManager {
|
||||
const fn new() -> Self {
|
||||
Self { trees: vec![] }
|
||||
}
|
||||
fn push(&mut self, tree: cl_ast::File) -> &'static cl_ast::File {
|
||||
let ptr = Box::leak(Box::new(tree));
|
||||
self.trees.push(ptr);
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
162
cl-typeck/src/definition/display.rs
Normal file
162
cl-typeck/src/definition/display.rs
Normal file
@ -0,0 +1,162 @@
|
||||
//! [Display] implementations for [TypeKind], [Adt], and [Intrinsic]
|
||||
|
||||
use super::{Adt, Def, DefKind, Intrinsic, TypeKind, ValueKind};
|
||||
use cl_ast::format::FmtPretty;
|
||||
use std::{
|
||||
fmt::{self, Display, Write},
|
||||
iter,
|
||||
};
|
||||
|
||||
fn sep<'f, 's, Item, F>(
|
||||
before: &'s str,
|
||||
after: &'s str,
|
||||
t: F,
|
||||
) -> impl FnOnce(&mut fmt::Formatter<'f>) -> fmt::Result + 's
|
||||
where
|
||||
Item: FnMut(&mut fmt::Formatter<'f>) -> fmt::Result,
|
||||
F: FnMut() -> Option<Item> + 's,
|
||||
{
|
||||
move |f| {
|
||||
f.write_str(before)?;
|
||||
for (idx, mut disp) in iter::from_fn(t).enumerate() {
|
||||
if idx > 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
disp(f)?;
|
||||
}
|
||||
f.write_str(after)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Def<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, vis, meta, kind, source, module } = self;
|
||||
writeln!(f, "{vis}{name}: ")?;
|
||||
writeln!(f, "kind: {kind}")?;
|
||||
if !meta.is_empty() {
|
||||
writeln!(f, "meta: {meta:?}")?;
|
||||
}
|
||||
if let Some(source) = source {
|
||||
writeln!(f.pretty(), "source: {{\n{source}\n}}")?;
|
||||
}
|
||||
write!(f, "module: {module}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DefKind<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DefKind::Undecided => write!(f, "undecided"),
|
||||
DefKind::Impl(id) => write!(f, "impl {id}"),
|
||||
DefKind::Type(kind) => write!(f, "{kind}"),
|
||||
DefKind::Value(kind) => write!(f, "{kind}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ValueKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ValueKind::Const(id) => write!(f, "const ({id})"),
|
||||
ValueKind::Static(id) => write!(f, "static ({id})"),
|
||||
ValueKind::Fn(id) => write!(f, "fn def ({id})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TypeKind<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TypeKind::Alias(def) => match def {
|
||||
Some(def) => write!(f, "alias to #{def}"),
|
||||
None => f.write_str("type"),
|
||||
},
|
||||
TypeKind::Intrinsic(i) => i.fmt(f),
|
||||
TypeKind::Adt(a) => a.fmt(f),
|
||||
TypeKind::Ref(cnt, def) => {
|
||||
for _ in 0..*cnt {
|
||||
f.write_str("&")?;
|
||||
}
|
||||
def.fmt(f)
|
||||
}
|
||||
TypeKind::Slice(def) => write!(f, "slice [#{def}]"),
|
||||
TypeKind::Array(def, size) => write!(f, "array [#{def}; {size}]"),
|
||||
TypeKind::Tuple(defs) => {
|
||||
let mut defs = defs.iter();
|
||||
sep("tuple (", ")", || {
|
||||
let def = defs.next()?;
|
||||
Some(move |f: &mut fmt::Formatter| write!(f, "#{def}"))
|
||||
})(f)
|
||||
}
|
||||
TypeKind::FnSig { args, rety } => write!(f, "fn (#{args}) -> #{rety}"),
|
||||
TypeKind::Empty => f.write_str("()"),
|
||||
TypeKind::Never => f.write_str("!"),
|
||||
TypeKind::SelfTy => f.write_str("Self"),
|
||||
TypeKind::Module => f.write_str("mod"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Adt<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Adt::Enum(variants) => {
|
||||
let mut variants = variants.iter();
|
||||
sep("enum {", "}", || {
|
||||
let (name, def) = variants.next()?;
|
||||
Some(move |f: &mut fmt::Formatter| match def {
|
||||
Some(def) => write!(f, "{name}: #{def}"),
|
||||
None => write!(f, "{name}"),
|
||||
})
|
||||
})(f)
|
||||
}
|
||||
Adt::CLikeEnum(variants) => {
|
||||
let mut variants = variants.iter();
|
||||
sep("enum {", "}", || {
|
||||
let (name, descrim) = variants.next()?;
|
||||
Some(move |f: &mut fmt::Formatter| write!(f, "{name} = {descrim}"))
|
||||
})(f)
|
||||
}
|
||||
Adt::FieldlessEnum => write!(f, "enum"),
|
||||
Adt::Struct(members) => {
|
||||
let mut members = members.iter();
|
||||
sep("struct {", "}", || {
|
||||
let (name, vis, def) = members.next()?;
|
||||
Some(move |f: &mut fmt::Formatter| write!(f, "{vis}{name}: #{def}"))
|
||||
})(f)
|
||||
}
|
||||
Adt::TupleStruct(members) => {
|
||||
let mut members = members.iter();
|
||||
sep("struct (", ")", || {
|
||||
let (vis, def) = members.next()?;
|
||||
Some(move |f: &mut fmt::Formatter| write!(f, "{vis}#{def}"))
|
||||
})(f)
|
||||
}
|
||||
Adt::UnitStruct => write!(f, "struct ()"),
|
||||
Adt::Union(variants) => {
|
||||
let mut variants = variants.iter();
|
||||
sep("union {", "}", || {
|
||||
let (name, def) = variants.next()?;
|
||||
Some(move |f: &mut fmt::Formatter| write!(f, "{name}: #{def}"))
|
||||
})(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Intrinsic {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Intrinsic::I8 => f.write_str("i8"),
|
||||
Intrinsic::I16 => f.write_str("i16"),
|
||||
Intrinsic::I32 => f.write_str("i32"),
|
||||
Intrinsic::I64 => f.write_str("i64"),
|
||||
Intrinsic::U8 => f.write_str("u8"),
|
||||
Intrinsic::U16 => f.write_str("u16"),
|
||||
Intrinsic::U32 => f.write_str("u32"),
|
||||
Intrinsic::U64 => f.write_str("u64"),
|
||||
Intrinsic::Bool => f.write_str("bool"),
|
||||
Intrinsic::Char => f.write_str("char"),
|
||||
}
|
||||
}
|
||||
}
|
1334
cl-typeck/src/lib.rs
1334
cl-typeck/src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,99 @@
|
||||
//! # The Conlang Standard Library
|
||||
|
||||
/// 32-bit signed integer type
|
||||
#[intrinsic = "bool"]
|
||||
pub type bool;
|
||||
|
||||
#[intrinsic = "char"]
|
||||
pub type char;
|
||||
|
||||
#[intrinsic = "i8"]
|
||||
pub type i8;
|
||||
|
||||
#[intrinsic = "i16"]
|
||||
pub type i16;
|
||||
|
||||
#[intrinsic = "i32"]
|
||||
type i32;
|
||||
pub type i32;
|
||||
|
||||
#[intrinsic = "i64"]
|
||||
pub type i64;
|
||||
|
||||
#[intrinsic = "u8"]
|
||||
pub type u8;
|
||||
|
||||
#[intrinsic = "u16"]
|
||||
pub type u16;
|
||||
|
||||
/// 32-bit unsigned integer type
|
||||
#[intrinsic = "u32"]
|
||||
type 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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
fn while_else() -> i32 {
|
||||
loop {
|
||||
if conditional {
|
||||
body;
|
||||
} else {
|
||||
break { else_body };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg("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 },
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user