refactor: split project into multiple crates
All checks were successful
buildbot/nix-eval Build done.
buildbot/nix-build Build done.
buildbot/nix-effects Build done.

This commit is contained in:
2025-11-05 20:23:17 +01:00
parent 486af67fc2
commit 857f747524
27 changed files with 308 additions and 222 deletions

View File

@@ -1,34 +1,13 @@
[package]
name = "lilac"
[workspace]
resolver = "3"
members = [
"lila",
"lila-ast",
"lila-checking",
"lila-cli",
"lila-jit",
"lila-parsing",
]
[workspace.package]
version = "0.0.1"
edition = "2021"
[features]
default = ["pest"]
pest = ["dep:pest", "dep:pest_derive"]
tree-sitter = ["dep:tree-sitter", "dep:tree-sitter-lila"]
[dependencies]
clap = { version = "4.5.7", features = ["derive"] }
cranelift = "0.109.0"
cranelift-jit = "0.109.0"
cranelift-module = "0.109.0"
cranelift-native = "0.109.0"
lazy_static = "1.4.0"
pest = { version = "2.7.4", optional = true }
pest_derive = { version = "2.7.4", optional = true }
tree-sitter = { version = "0.22", optional = true }
ariadne = "0.4.1"
anyhow = "1.0.86"
[dependencies.tree-sitter-lila]
version = "0.0.1"
optional = true
git = "https://git.sr.ht/~rpqt/tree-sitter-lila"
branch = "main"
[dev-dependencies]
pretty_assertions = "1.4.0"
[build-dependencies]
cc = "*"

View File

@@ -26,10 +26,15 @@
let
craneLib = crane.mkLib pkgs;
pestFilter = path: _type: (builtins.match ".*\.pest$" path) != null;
sourceFilter = path: type: (craneLib.filterCargoSources path type) || (pestFilter path type);
sourceFilter =
path: type:
builtins.any (suffix: lib.hasSuffix suffix path) [
".pest"
]
|| (craneLib.filterCargoSources path type);
lilac-crate = craneLib.buildPackage ({
pname = "lilac";
src = lib.cleanSourceWith {
src = ./.;
filter = sourceFilter;

10
lila-ast/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "lila-ast"
version = "0.0.1"
edition = "2021"
[features]
ariadne = [ "dep:ariadne" ]
[dependencies]
ariadne = { version = "0.4.1", optional = true }

View File

@@ -1,5 +1,5 @@
use crate::ast::*;
use crate::typing::Type;
use crate::*;
#[derive(Debug, PartialEq)]
pub struct SExpr {

View File

@@ -1,10 +1,10 @@
pub mod expr;
pub mod typing;
pub use expr::{BinaryExpression, Expr, SExpr};
use crate::typing::Type;
use ariadne;
use std::{fmt::Display, path::Path};
#[derive(Debug, PartialEq, Clone)]
@@ -54,6 +54,7 @@ pub struct Span {
pub end: usize,
}
#[cfg(feature = "ariadne")]
impl ariadne::Span for Span {
type SourceId = SourceId;

View File

@@ -0,0 +1,56 @@
use std::fmt::Debug;
use crate::{BinaryOperator, Identifier, ModulePath, Span, Type, UnaryOperator};
#[derive(Debug, PartialEq)]
pub struct TypeError {
pub file: Option<std::path::PathBuf>,
pub module: ModulePath,
pub function: Option<String>,
pub kind: TypeErrorKind,
}
#[derive(PartialEq, Debug)]
pub struct TypeAndSpan {
pub ty: Type,
pub span: Span,
}
#[derive(PartialEq, Debug)]
pub struct BinOpAndSpan {
pub op: BinaryOperator,
pub span: Span,
}
#[derive(Debug, PartialEq)]
pub enum TypeErrorKind {
InvalidBinaryOperator {
operator: BinOpAndSpan,
lhs: TypeAndSpan,
rhs: TypeAndSpan,
},
BlockTypeDoesNotMatchFunctionType {
block_type: Type,
},
ReturnTypeDoesNotMatchFunctionType {
return_expr: Option<TypeAndSpan>,
return_stmt: TypeAndSpan,
},
UnknownIdentifier {
identifier: String,
},
AssignmentMismatch {
lht: Type,
rht: Type,
},
AssignUndeclared,
VariableRedeclaration,
UnknownFunctionCalled(Identifier),
WrongFunctionArguments,
ConditionIsNotBool,
IfElseMismatch,
InvalidUnaryOperator {
operator: UnaryOperator,
inner: Type,
},
}

View File

@@ -0,0 +1,73 @@
pub mod error;
use crate::{FunctionDefinition, Identifier};
use std::fmt::Display;
#[derive(Debug, PartialEq, Clone)]
pub enum Type {
/// Not a real type, used for parsing pass
Undefined,
Bool,
Int,
Float,
Unit,
Str,
Function {
params: Vec<Type>,
returns: Box<Type>,
},
Custom(Identifier),
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Undefined => f.write_str("UNDEFINED"),
Type::Bool => f.write_str("Bool"),
Type::Int => f.write_str("Int"),
Type::Float => f.write_str("Float"),
Type::Unit => f.write_str("Unit"),
Type::Str => f.write_str("Str"),
Type::Custom(identifier) => f.write_str(identifier),
Type::Function { params, returns } => {
f.write_str("Fn(")?;
for param in params {
f.write_fmt(format_args!("{param}, "))?;
}
f.write_str(") -> ")?;
f.write_fmt(format_args!("{returns}"))
}
}
}
}
impl From<&str> for Type {
fn from(value: &str) -> Self {
match value {
"int" => Type::Int,
"float" => Type::Float,
"bool" => Type::Bool,
_ => Type::Custom(Identifier::from(value)),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Signature(pub Vec<Type>, pub Type);
impl From<Signature> for Type {
fn from(val: Signature) -> Self {
Type::Function {
params: val.0,
returns: Box::new(val.1),
}
}
}
impl FunctionDefinition {
pub fn signature(&self) -> Signature {
let return_type = self.return_type.clone().unwrap_or(Type::Unit);
let params_types = self.parameters.iter().map(|p| p.typ.clone()).collect();
Signature(params_types, return_type)
}
}

11
lila-checking/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "lila-checking"
version = "0.0.1"
edition = "2021"
[dependencies]
lila-ast = { path = "../lila-ast" }
[dev-dependencies]
lila-parsing = { path = "../lila-parsing" }
pretty_assertions = "1.4.0"

View File

@@ -1,87 +1,21 @@
mod error;
use crate::ast::*;
use crate::typing::error::{TypeAndSpan, TypeError, TypeErrorKind};
use std::collections::HashMap;
use std::fmt::Display;
use lila_ast::typing::error::{BinOpAndSpan, TypeAndSpan, TypeError, TypeErrorKind};
use lila_ast::typing::{Signature, Type};
use lila_ast::*;
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Clone)]
pub enum Type {
/// Not a real type, used for parsing pass
Undefined,
Bool,
Int,
Float,
Unit,
Str,
Function {
params: Vec<Type>,
returns: Box<Type>,
},
Custom(Identifier),
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Undefined => f.write_str("UNDEFINED"),
Type::Bool => f.write_str("Bool"),
Type::Int => f.write_str("Int"),
Type::Float => f.write_str("Float"),
Type::Unit => f.write_str("Unit"),
Type::Str => f.write_str("Str"),
Type::Custom(identifier) => f.write_str(identifier),
Type::Function { params, returns } => {
f.write_str("Fn(")?;
for param in params {
f.write_fmt(format_args!("{}, ", param))?;
}
f.write_str(") -> ")?;
f.write_fmt(format_args!("{}", returns))
}
}
}
}
impl From<&str> for Type {
fn from(value: &str) -> Self {
match value {
"int" => Type::Int,
"float" => Type::Float,
"bool" => Type::Bool,
_ => Type::Custom(Identifier::from(value)),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Signature(Vec<Type>, Type);
impl From<Signature> for Type {
fn from(val: Signature) -> Self {
Type::Function {
params: val.0,
returns: Box::new(val.1),
}
}
}
impl FunctionDefinition {
fn signature(&self) -> Signature {
let return_type = self.return_type.clone().unwrap_or(Type::Unit);
let params_types = self.parameters.iter().map(|p| p.typ.clone()).collect();
Signature(params_types, return_type)
}
}
#[derive(Debug, PartialEq)]
pub struct CheckedModule(pub Module);
impl Module {
pub fn type_check(&mut self) -> Result<(), Vec<TypeError>> {
pub trait TypeCheckModule {
fn type_check(&mut self) -> Result<(), Vec<TypeError>>;
}
impl TypeCheckModule for Module {
fn type_check(&mut self) -> Result<(), Vec<TypeError>> {
let mut ctx = TypingContext::new(self.path.clone());
ctx.file.clone_from(&self.file);
@@ -325,7 +259,7 @@ impl TypeCheck for Expr {
typ,
op_span,
}) => {
let operator = error::BinOpAndSpan {
let operator = BinOpAndSpan {
op: op.clone(),
span: *op_span,
};

View File

@@ -1,11 +1,7 @@
use crate::{
ast::ModulePath,
parsing::{DefaultParser, Parser},
typing::error::*,
typing::*,
};
#[cfg(test)]
use crate::TypeCheckModule;
use lila_ast::typing::{error::*, Type};
use lila_ast::ModulePath;
use lila_parsing::{DefaultParser, Parser};
use pretty_assertions::assert_eq;
#[test]

22
lila-cli/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "lila-cli"
version = "0.0.1"
edition = "2021"
[dependencies]
anyhow = "1.0.86"
ariadne = "0.4.1"
clap = { version = "4.5.7", features = ["derive"] }
lila = { path = "../lila" }
lila-ast = { path = "../lila-ast", features = ["ariadne"] }
lila-checking = { path = "../lila-checking" }
lila-jit = { path = "../lila-jit" }
lila-parsing = { path = "../lila-parsing" }
[dev-dependencies]
pretty_assertions = "1.4.0"
[[bin]]
name = "lilac"
path = "src/main.rs"

View File

@@ -1,18 +1,15 @@
pub mod ast;
pub mod jit;
pub mod parsing;
pub mod source;
pub mod typing;
use std::default::Default;
use std::io::Write as _;
use std::path::PathBuf;
use clap::{Parser as ClapParser, Subcommand};
use crate::ast::Module;
use crate::parsing::Parser;
use crate::source::SourceCache;
use lila::report::ToReport;
use lila::source::SourceCache;
use lila_ast::Module;
use lila_checking::TypeCheckModule;
use lila_jit as jit;
use lila_parsing::{DefaultParser as LilaParser, Parser as _};
/// Experimental compiler for lila
#[derive(ClapParser, Debug)]
@@ -59,13 +56,13 @@ enum Commands {
}
fn parse(files: &[String]) -> Vec<Module> {
let mut parser = parsing::DefaultParser::default();
let mut parser = LilaParser::default();
let paths = files.iter().map(std::path::Path::new);
paths
.enumerate()
.map(|(i, path)| match parser.parse_file(path, i as u32) {
Ok(module) => module,
Err(e) => panic!("Parsing error: {:#?}", e),
Err(e) => panic!("Parsing error: {e:#?}"),
})
.collect()
}

15
lila-jit/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "lila-jit"
version = "0.0.1"
edition = "2021"
[dependencies]
ariadne = "0.4.1" # TODO: use ariadne only in CLI
cranelift = "0.109.0"
cranelift-jit = "0.109.0"
cranelift-module = "0.109.0"
cranelift-native = "0.109.0"
lila = { path = "../lila" }
lila-ast = { path = "../lila-ast", features = ["ariadne"] } # TODO: don't include ariadne feature
lila-checking = { path = "../lila-checking" }
lila-parsing = { path = "../lila-parsing" }

View File

@@ -1,16 +1,17 @@
use crate::{
ast::{
self, expr::BinaryExpression, BinaryOperator, Expr, FunctionDefinition, ModulePath,
ReturnStatement, SourceId, Statement, UnaryOperator,
},
parsing::{DefaultParser, Parser},
typing::Type,
SourceCache,
};
use ariadne::Cache as _;
use cranelift::{codegen::ir::UserFuncName, prelude::*};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{DataDescription, FuncId, FuncOrDataId, Linkage, Module};
use lila::report::ToReport as _;
use lila::source::SourceCache;
use lila_ast as ast;
use lila_ast::typing::{self, Type};
use lila_ast::{
expr::BinaryExpression, BinaryOperator, Expr, FunctionDefinition, ModulePath, ReturnStatement,
SourceId, Statement, UnaryOperator,
};
use lila_checking::TypeCheckModule as _;
use lila_parsing::{DefaultParser, Parser};
use std::collections::HashMap;
/// The basic JIT class.
@@ -44,7 +45,7 @@ impl Default for JIT {
flag_builder.set("is_pic", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
panic!("host machine is not supported: {msg}");
});
let isa = isa_builder
@@ -137,12 +138,12 @@ impl JIT {
for param in &func.parameters {
assert_ne!(param.typ, Type::Unit);
sig.params.append(&mut Vec::from(&param.typ));
sig.params.append(&mut to_abi_params(&param.typ));
}
if let Some(return_type) = &func.return_type {
if *return_type != Type::Unit {
sig.returns = return_type.into();
sig.returns = to_abi_params(return_type);
}
};
@@ -235,14 +236,12 @@ impl JIT {
}
}
impl From<&Type> for Vec<AbiParam> {
fn from(value: &Type) -> Self {
match value {
Type::Bool => vec![AbiParam::new(types::I8)],
Type::Int => vec![AbiParam::new(types::I32)],
Type::Float => vec![AbiParam::new(types::F32)],
_ => unimplemented!(),
}
fn to_abi_params(value: &Type) -> Vec<AbiParam> {
match value {
Type::Bool => vec![AbiParam::new(types::I8)],
Type::Int => vec![AbiParam::new(types::I32)],
Type::Float => vec![AbiParam::new(types::F32)],
_ => unimplemented!(),
}
}

29
lila-parsing/Cargo.toml Normal file
View File

@@ -0,0 +1,29 @@
[package]
name = "lila-parsing"
version = "0.0.1"
edition = "2021"
[features]
default = ["pest"]
pest = ["dep:pest", "dep:pest_derive", "dep:lazy_static"]
tree-sitter = ["dep:tree-sitter", "dep:tree-sitter-lila"]
[dependencies]
anyhow = "1.0.86"
lazy_static = { version = "1.4.0", optional = true }
lila-ast = { path = "../lila-ast" }
pest = { version = "2.7.4", optional = true }
pest_derive = { version = "2.7.4", optional = true }
tree-sitter = { version = "0.22", optional = true }
[dependencies.tree-sitter-lila]
version = "0.0.1"
optional = true
git = "https://git.sr.ht/~rpqt/tree-sitter-lila"
branch = "main"
[build-dependencies]
cc = "*"
[dev-dependencies]
pretty_assertions = "1.4.0"

View File

@@ -1,14 +1,12 @@
use expr::BinaryExpression;
use lila_ast::typing::Type;
use pest::iterators::Pair;
use pest::pratt_parser::PrattParser;
use pest::Parser as PestParser;
use ReturnStatement;
use crate::ast::*;
use crate::typing::Type;
use lila_ast::*;
#[derive(pest_derive::Parser)]
#[grammar = "parsing/backend/pest/grammar.pest"]
#[grammar = "src/backend/pest/grammar.pest"]
struct LilaParser;
use lazy_static;
@@ -34,7 +32,7 @@ pub struct Parser {
source: SourceId,
}
impl crate::parsing::Parser for Parser {
impl crate::Parser for Parser {
fn parse_as_module(
&mut self,
source: &str,

View File

@@ -1,7 +1,7 @@
mod backend;
mod tests;
use crate::ast::{Module, ModulePath, SourceId};
use lila_ast::{Module, ModulePath, SourceId};
pub trait Parser: Default {
fn parse_file(&mut self, path: &std::path::Path, id: SourceId) -> anyhow::Result<Module> {

View File

@@ -3,9 +3,9 @@ use pretty_assertions::assert_eq;
#[test]
fn test_addition_function() {
use crate::ast::*;
use crate::parsing::*;
use crate::typing::*;
use crate::*;
use lila_ast::typing::*;
use lila_ast::*;
let source = "fn add(a: int, b: int) int { a + b }";
let path = ModulePath::from("test");

8
lila/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "lila"
version = "0.0.1"
edition = "2021"
[dependencies]
ariadne = "0.4.1" # TODO: only CLI should use ariadne
lila-ast = { path = "../lila-ast" }

2
lila/src/lib.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod report;
pub mod source;

View File

@@ -1,64 +1,14 @@
use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Span as _};
use std::fmt::Debug;
use lila_ast::typing::error::{TypeError, TypeErrorKind};
use lila_ast::typing::Type;
use lila_ast::{Module, Span};
use super::{Span, UnaryOperator};
use crate::typing::{BinaryOperator, Identifier, ModulePath, Type};
#[derive(Debug, PartialEq)]
pub struct TypeError {
pub file: Option<std::path::PathBuf>,
pub module: ModulePath,
pub function: Option<String>,
pub kind: TypeErrorKind,
pub trait ToReport {
fn to_report(&self, ast: &Module) -> Report<Span>;
}
#[derive(PartialEq, Debug)]
pub struct TypeAndSpan {
pub ty: Type,
pub span: Span,
}
#[derive(PartialEq, Debug)]
pub struct BinOpAndSpan {
pub op: BinaryOperator,
pub span: Span,
}
#[derive(Debug, PartialEq)]
pub enum TypeErrorKind {
InvalidBinaryOperator {
operator: BinOpAndSpan,
lhs: TypeAndSpan,
rhs: TypeAndSpan,
},
BlockTypeDoesNotMatchFunctionType {
block_type: Type,
},
ReturnTypeDoesNotMatchFunctionType {
return_expr: Option<TypeAndSpan>,
return_stmt: TypeAndSpan,
},
UnknownIdentifier {
identifier: String,
},
AssignmentMismatch {
lht: Type,
rht: Type,
},
AssignUndeclared,
VariableRedeclaration,
UnknownFunctionCalled(Identifier),
WrongFunctionArguments,
ConditionIsNotBool,
IfElseMismatch,
InvalidUnaryOperator {
operator: UnaryOperator,
inner: Type,
},
}
impl TypeError {
pub fn to_report(&self, ast: &crate::ast::Module) -> Report<Span> {
impl ToReport for TypeError {
fn to_report(&self, ast: &Module) -> Report<Span> {
let mut colors = ColorGenerator::new();
let c0 = colors.next();
let c1 = colors.next();
@@ -85,6 +35,7 @@ impl TypeError {
.with_color(c2)
.with_order(1),
])
// TODO: add hint for conversion
.finish()
}
@@ -129,7 +80,7 @@ impl TypeError {
if let Some(span) = function.return_type_span {
report.add_label(
Label::new(span)
.with_message(format!("The signature shows {}", func_return_type_text))
.with_message(format!("The signature shows {func_return_type_text}"))
.with_color(signature_color),
);
}

View File

@@ -1,5 +1,5 @@
use crate::ast::SourceId;
use ariadne::FileCache;
use lila_ast::SourceId;
pub struct SourceCache {
pub paths: Vec<std::path::PathBuf>,