mirror of
https://github.com/azur1s/bobbylisp.git
synced 2024-10-16 02:37:40 -05:00
Compare commits
2 commits
fefffdd79e
...
74adfe1f0f
Author | SHA1 | Date | |
---|---|---|---|
Natapat Samutpong | 74adfe1f0f | ||
Natapat Samutpong | 8021a0e31d |
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -131,6 +131,7 @@ dependencies = [
|
||||||
"chumsky",
|
"chumsky",
|
||||||
"hir",
|
"hir",
|
||||||
"lexer",
|
"lexer",
|
||||||
|
"typecheck",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -163,6 +164,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"toml",
|
"toml",
|
||||||
|
"typecheck",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -184,7 +186,6 @@ dependencies = [
|
||||||
name = "hir"
|
name = "hir"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"levenshtein",
|
|
||||||
"parser",
|
"parser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -204,12 +205,6 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "levenshtein"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lexer"
|
name = "lexer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -361,6 +356,13 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typecheck"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"hir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
|
@ -5,5 +5,6 @@ members = [
|
||||||
"crates/parser",
|
"crates/parser",
|
||||||
"crates/diagnostic",
|
"crates/diagnostic",
|
||||||
"crates/hir",
|
"crates/hir",
|
||||||
|
"crates/typecheck",
|
||||||
"crates/codegen",
|
"crates/codegen",
|
||||||
]
|
]
|
||||||
|
|
|
@ -31,7 +31,7 @@ impl Codegen {
|
||||||
macro_rules! semicolon { () => { if should_gen_semicolon { ";" } else { "" } }; }
|
macro_rules! semicolon { () => { if should_gen_semicolon { ";" } else { "" } }; }
|
||||||
|
|
||||||
match ir {
|
match ir {
|
||||||
IRKind::Define { public, name, type_hint, value, mutable } => {
|
IRKind::Define { public, name, type_hint, value, mutable, .. } => {
|
||||||
format!(
|
format!(
|
||||||
"{} {} v_{}: {} = {}{}\n",
|
"{} {} v_{}: {} = {}{}\n",
|
||||||
if *public { "export" } else { "" },
|
if *public { "export" } else { "" },
|
||||||
|
@ -43,7 +43,7 @@ impl Codegen {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Call { name, args } => {
|
IRKind::Call { name, args, .. } => {
|
||||||
format!(
|
format!(
|
||||||
"f_{}({}){}",
|
"f_{}({}){}",
|
||||||
name,
|
name,
|
||||||
|
@ -57,7 +57,7 @@ impl Codegen {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Intrinsic { name, args } => {
|
IRKind::Intrinsic { name, args, .. } => {
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
"write" => { format!("write({}){}\n" , self.gen_ir(&args[0], false), semicolon!()) },
|
"write" => { format!("write({}){}\n" , self.gen_ir(&args[0], false), semicolon!()) },
|
||||||
"write_file" => { format!("writeFile({}, {}){}\n", self.gen_ir(&args[0], false), self.gen_ir(&args[1], false), semicolon!()) },
|
"write_file" => { format!("writeFile({}, {}){}\n", self.gen_ir(&args[0], false), self.gen_ir(&args[1], false), semicolon!()) },
|
||||||
|
@ -71,7 +71,7 @@ impl Codegen {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Fun { public, name, return_type_hint, args, body } => {
|
IRKind::Fun { public, name, return_type_hint, args, body, .. } => {
|
||||||
let args = args
|
let args = args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| format!("v_{}: {}", arg.0, arg.1))
|
.map(|arg| format!("v_{}: {}", arg.0, arg.1))
|
||||||
|
@ -87,14 +87,14 @@ impl Codegen {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Return { value } => {
|
IRKind::Return { value, .. } => {
|
||||||
format!(
|
format!(
|
||||||
"return {};\n",
|
"return {};\n",
|
||||||
self.gen_ir(value, false)
|
self.gen_ir(value, false)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Do { body } => {
|
IRKind::Do { body, .. } => {
|
||||||
let mut out = "{\n".to_string();
|
let mut out = "{\n".to_string();
|
||||||
for expr in body {
|
for expr in body {
|
||||||
out.push_str(&self.gen_ir(&expr, true));
|
out.push_str(&self.gen_ir(&expr, true));
|
||||||
|
@ -103,7 +103,7 @@ impl Codegen {
|
||||||
out
|
out
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::If { cond, body, else_body } => {
|
IRKind::If { cond, body, else_body, .. } => {
|
||||||
format!(
|
format!(
|
||||||
"if ({}) {{\n{}}} else {{\n{}}}\n",
|
"if ({}) {{\n{}}} else {{\n{}}}\n",
|
||||||
self.gen_ir(cond, true),
|
self.gen_ir(cond, true),
|
||||||
|
@ -112,7 +112,7 @@ impl Codegen {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Case { cond, cases, default } => {
|
IRKind::Case { cond, cases, default, .. } => {
|
||||||
format!(
|
format!(
|
||||||
"switch ({}) {{\n{}{}\n}}\n",
|
"switch ({}) {{\n{}{}\n}}\n",
|
||||||
self.gen_ir(cond, true),
|
self.gen_ir(cond, true),
|
||||||
|
@ -131,11 +131,11 @@ impl Codegen {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Unary { op, right } => {
|
IRKind::Unary { op, right, .. } => {
|
||||||
format!("{}{}", op, self.gen_ir(right, false))
|
format!("{}{}", op, self.gen_ir(right, false))
|
||||||
},
|
},
|
||||||
|
|
||||||
IRKind::Binary { left, op, right } => {
|
IRKind::Binary { left, op, right, .. } => {
|
||||||
format!("{} {} {}", self.gen_ir(left, false), op, self.gen_ir(right, false))
|
format!("{} {} {}", self.gen_ir(left, false), op, self.gen_ir(right, false))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,5 @@ edition = "2021"
|
||||||
chumsky = "0.8.0"
|
chumsky = "0.8.0"
|
||||||
ariadne = "0.1.5"
|
ariadne = "0.1.5"
|
||||||
lexer = { path = "../lexer" }
|
lexer = { path = "../lexer" }
|
||||||
hir = { path = "../hir" }
|
hir = { path = "../hir" }
|
||||||
|
typecheck = { path = "../typecheck" }
|
|
@ -12,6 +12,7 @@ pub enum Kind {
|
||||||
LexError(Simple<char>),
|
LexError(Simple<char>),
|
||||||
ParseError(Simple<Token>),
|
ParseError(Simple<Token>),
|
||||||
LoweringError(hir::LoweringError),
|
LoweringError(hir::LoweringError),
|
||||||
|
TypecheckError(typecheck::TypecheckError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostics {
|
impl Diagnostics {
|
||||||
|
@ -37,6 +38,10 @@ impl Diagnostics {
|
||||||
self.errors.push(Kind::LoweringError(error));
|
self.errors.push(Kind::LoweringError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_typecheck_error(&mut self, error: typecheck::TypecheckError) {
|
||||||
|
self.errors.push(Kind::TypecheckError(error));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn display(&self, src: String) {
|
pub fn display(&self, src: String) {
|
||||||
let lex_error = self.errors.iter().filter_map(|kind| match kind {
|
let lex_error = self.errors.iter().filter_map(|kind| match kind {
|
||||||
Kind::LexError(error) => Some(error.clone()), // Using clone() to remove reference
|
Kind::LexError(error) => Some(error.clone()), // Using clone() to remove reference
|
||||||
|
@ -120,6 +125,11 @@ impl Diagnostics {
|
||||||
Kind::LoweringError(error) => Some(error.clone()),
|
Kind::LoweringError(error) => Some(error.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
let typecheck_error = self.errors.iter().filter_map(|kind| match kind {
|
||||||
|
Kind::TypecheckError(error) => Some(error.clone()),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
// TODO: so many .iter(), maybe collapse them into one?
|
||||||
|
|
||||||
lower_error.into_iter()
|
lower_error.into_iter()
|
||||||
.for_each(|e| {
|
.for_each(|e| {
|
||||||
|
@ -138,13 +148,27 @@ impl Diagnostics {
|
||||||
.with_color(Color::Red)
|
.with_color(Color::Red)
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(note) = &e.note {
|
report.finish().print(Source::from(&src)).unwrap();
|
||||||
report
|
|
||||||
.with_note(note)
|
|
||||||
.finish().print(Source::from(&src)).unwrap();
|
|
||||||
} else {
|
|
||||||
report.finish().print(Source::from(&src)).unwrap();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
typecheck_error.into_iter()
|
||||||
|
.for_each(|e| {
|
||||||
|
let span = &e.span;
|
||||||
|
let message = &e.kind;
|
||||||
|
|
||||||
|
let report = Report::build(ReportKind::Error, (), span.start)
|
||||||
|
.with_message(
|
||||||
|
format!("{}", message)
|
||||||
|
)
|
||||||
|
.with_label(
|
||||||
|
Label::new(span.clone())
|
||||||
|
.with_message(
|
||||||
|
format!("{}", message)
|
||||||
|
)
|
||||||
|
.with_color(Color::Red)
|
||||||
|
);
|
||||||
|
|
||||||
|
report.finish().print(Source::from(&src)).unwrap();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,4 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parser = { path = "../parser" }
|
parser = { path = "../parser" }
|
||||||
levenshtein = "1.0.5" # Used for error reporting
|
|
|
@ -29,31 +29,62 @@ impl std::fmt::Display for Value {
|
||||||
pub enum IRKind {
|
pub enum IRKind {
|
||||||
Value { value: Value },
|
Value { value: Value },
|
||||||
Vector { values: Vec<Self> },
|
Vector { values: Vec<Self> },
|
||||||
Unary { op: String, right: Box<Self> },
|
|
||||||
Binary { op: String, left: Box<Self>, right: Box<Self> },
|
Unary {
|
||||||
Call { name: String, args: Vec<Self> },
|
op: String,
|
||||||
Intrinsic { name: String, args: Vec<Self> },
|
right: Box<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
Binary {
|
||||||
|
op: String,
|
||||||
|
left: Box<Self>,
|
||||||
|
right: Box<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
Call {
|
||||||
|
name: String,
|
||||||
|
args: Vec<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
Intrinsic {
|
||||||
|
name: String,
|
||||||
|
args: Vec<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
|
||||||
Define {
|
Define {
|
||||||
public: bool,
|
public: bool,
|
||||||
name: String,
|
name: String,
|
||||||
type_hint: String,
|
type_hint: String,
|
||||||
value: Box<Self>,
|
value: Box<Self>,
|
||||||
mutable: bool
|
mutable: bool,
|
||||||
|
span: Range<usize>,
|
||||||
},
|
},
|
||||||
Fun {
|
Fun {
|
||||||
public: bool,
|
public: bool,
|
||||||
name: String,
|
name: String,
|
||||||
return_type_hint: String,
|
return_type_hint: String,
|
||||||
args: Vec<(String, String)>,
|
args: Vec<(String, String)>,
|
||||||
body: Box<Self>
|
body: Box<Self>,
|
||||||
|
span: Range<usize>,
|
||||||
},
|
},
|
||||||
|
|
||||||
If { cond: Box<Self>, body: Box<Self>, else_body: Box<Self> },
|
If {
|
||||||
Case { cond: Box<Self>, cases: Vec<(Box<Self>, Box<Self>)>, default: Box<Self> },
|
cond: Box<Self>,
|
||||||
Do { body: Vec<Self> },
|
body: Box<Self>,
|
||||||
|
else_body: Box<Self>,
|
||||||
|
span: Range<usize>,
|
||||||
|
},
|
||||||
|
Case {
|
||||||
|
cond: Box<Self>,
|
||||||
|
cases: Vec<(Box<Self>, Box<Self>)>,
|
||||||
|
default: Box<Self>,
|
||||||
|
span: Range<usize>,
|
||||||
|
},
|
||||||
|
Do { body: Vec<Self>, span: Range<usize> },
|
||||||
|
|
||||||
Return { value: Box<Self> },
|
Return { value: Box<Self>, span: Range<usize> },
|
||||||
|
// Error { message: String, note: Option<String>, span: Range<usize> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -62,70 +93,10 @@ pub struct IR {
|
||||||
pub span: Range<usize>
|
pub span: Range<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
// S-Expression displaying for IR
|
|
||||||
// Used for debugging (run `compile` with `-a`)
|
|
||||||
impl std::fmt::Display for IRKind {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
IRKind::Define { ref public, ref name, ref type_hint, ref value, ref mutable } => {
|
|
||||||
write!(f, "(let {} {} {} {} {})",
|
|
||||||
if *public { "export" } else { "" },
|
|
||||||
name,
|
|
||||||
type_hint,
|
|
||||||
value,
|
|
||||||
if *mutable { "mut" } else { "" },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
IRKind::Fun { ref public, ref name, ref return_type_hint, ref args, ref body } => {
|
|
||||||
write!(f, "(fun{} {} :{} [{}] {})",
|
|
||||||
if *public { " export" } else { "" },
|
|
||||||
name,
|
|
||||||
return_type_hint,
|
|
||||||
args.iter().map(|(name, type_hint)| format!(":{} {}", name, type_hint)).collect::<Vec<_>>().join(" "),
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
IRKind::Call { ref name, ref args } => {
|
|
||||||
write!(f, "({} {})", name, args.iter().map(|arg| arg.to_string()).collect::<Vec<_>>().join(" "))
|
|
||||||
},
|
|
||||||
IRKind::Intrinsic { ref name, ref args } => {
|
|
||||||
write!(f, "(@{} {})", name, args.iter().map(|arg| arg.to_string()).collect::<Vec<_>>().join(" "))
|
|
||||||
},
|
|
||||||
IRKind::Do { ref body } => {
|
|
||||||
write!(f, "(do {})", body.iter().map(|expr| expr.to_string()).collect::<Vec<_>>().join(" "))
|
|
||||||
},
|
|
||||||
IRKind::If { ref cond, ref body, ref else_body } => {
|
|
||||||
write!(f, "(if {} {} {})", cond, body, else_body)
|
|
||||||
},
|
|
||||||
IRKind::Case { ref cond, ref cases, ref default } => {
|
|
||||||
write!(f, "(case {} {} {})", cond, cases.iter().map(|(cond, body)| format!("({} {})", cond, body)).collect::<Vec<_>>().join(" "), default)
|
|
||||||
},
|
|
||||||
IRKind::Unary { ref op, ref right } => {
|
|
||||||
write!(f, "({} {})", op, right)
|
|
||||||
},
|
|
||||||
IRKind::Binary { ref op, ref left, ref right } => {
|
|
||||||
write!(f, "({} {} {})", op, left, right)
|
|
||||||
},
|
|
||||||
IRKind::Value { ref value } => {
|
|
||||||
write!(f, "{}", value)
|
|
||||||
},
|
|
||||||
IRKind::Vector { ref values } => {
|
|
||||||
write!(f, "[{}]", values.iter().map(|expr| expr.to_string()).collect::<Vec<_>>().join(" "))
|
|
||||||
},
|
|
||||||
IRKind::Return { ref value } => {
|
|
||||||
write!(f, "(return {})", value)
|
|
||||||
}
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => { dbg!(self); unreachable!() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LoweringError {
|
pub struct LoweringError {
|
||||||
pub span: Range<usize>,
|
pub span: Range<usize>,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub note: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ast_to_ir(ast: Vec<(Expr, Range<usize>)>) -> (Vec<IR>, Vec<LoweringError>) {
|
pub fn ast_to_ir(ast: Vec<(Expr, Range<usize>)>) -> (Vec<IR>, Vec<LoweringError>) {
|
||||||
|
@ -153,13 +124,31 @@ macro_rules! if_err_return {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! return_ok {
|
||||||
|
($value:expr) => {
|
||||||
|
return (Some($value), None)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! return_err {
|
||||||
|
($value:expr) => {
|
||||||
|
return (None, Some($value))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Unary { op, rhs } => {
|
Expr::Unary { op, rhs } => {
|
||||||
let rhs_ir = expr_to_ir(&rhs.0);
|
let rhs_ir = expr_to_ir(&rhs.0);
|
||||||
if_err_return!(rhs_ir.1);
|
if_err_return!(rhs_ir.1);
|
||||||
|
|
||||||
return (Some(IRKind::Unary { op: op.to_string(), right: Box::new(rhs_ir.0.unwrap()) }), None);
|
return_ok!(IRKind::Unary {
|
||||||
|
op: op.to_string(),
|
||||||
|
right: Box::new(rhs_ir.0.unwrap()),
|
||||||
|
span: rhs.1.clone()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Binary { lhs, op, rhs } => {
|
Expr::Binary { lhs, op, rhs } => {
|
||||||
|
@ -169,14 +158,19 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
let rhs_ir = expr_to_ir(&rhs.0);
|
let rhs_ir = expr_to_ir(&rhs.0);
|
||||||
if_err_return!(rhs_ir.1);
|
if_err_return!(rhs_ir.1);
|
||||||
|
|
||||||
return (Some(IRKind::Binary { op: op.to_string(), left: Box::new(lhs_ir.0.unwrap()), right: Box::new(rhs_ir.0.unwrap()) }), None)
|
return_ok!(IRKind::Binary {
|
||||||
|
op: op.to_string(),
|
||||||
|
left: Box::new(lhs_ir.0.unwrap()),
|
||||||
|
right: Box::new(rhs_ir.0.unwrap()),
|
||||||
|
span: lhs.1.start..rhs.1.end
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Call { name, args } => {
|
Expr::Call { name, args } => {
|
||||||
let name = match &name.0 {
|
let lname = match &name.0 {
|
||||||
Expr::Identifier(s) => s.clone(),
|
Expr::Identifier(s) => s.clone(),
|
||||||
// Should never happen because the parser should have caught this
|
// Should never happen because the parser should have caught this
|
||||||
_ => return (None, Some(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string(), note: None }))
|
_ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()})
|
||||||
};
|
};
|
||||||
let mut largs = Vec::new(); // `largs` stand for lowered args
|
let mut largs = Vec::new(); // `largs` stand for lowered args
|
||||||
// Iterate over args
|
// Iterate over args
|
||||||
|
@ -186,8 +180,12 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
if_err_return!(arg.1);
|
if_err_return!(arg.1);
|
||||||
largs.push(arg.0.unwrap());
|
largs.push(arg.0.unwrap());
|
||||||
}
|
}
|
||||||
let ir_kind = IRKind::Call { name, args: largs };
|
|
||||||
return (Some(ir_kind), None);
|
return_ok!(IRKind::Call {
|
||||||
|
name: lname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Pipeline { lhs, rhs } => {
|
Expr::Pipeline { lhs, rhs } => {
|
||||||
|
@ -200,7 +198,7 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
let cname = match &name.0 {
|
let cname = match &name.0 {
|
||||||
Expr::Identifier(s) => s.clone(),
|
Expr::Identifier(s) => s.clone(),
|
||||||
// Should never happen because the parser should have caught this
|
// Should never happen because the parser should have caught this
|
||||||
_ => return (None, Some(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string(), note: None }))
|
_ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all the `Hole` indexes
|
// Get all the `Hole` indexes
|
||||||
|
@ -213,11 +211,10 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
|
|
||||||
// If there is no `Hole` in the args then return early
|
// If there is no `Hole` in the args then return early
|
||||||
if indexes.is_empty() {
|
if indexes.is_empty() {
|
||||||
return (None, Some(LoweringError {
|
return_err!(LoweringError {
|
||||||
span: rhs.1.clone(),
|
span: rhs.1.clone(),
|
||||||
message: "Expected hole in piping".to_string(),
|
message: "Expected hole in piping".to_string(),
|
||||||
note: None
|
});
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the `Hole` from the args
|
// Remove the `Hole` from the args
|
||||||
|
@ -259,50 +256,54 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
|
|
||||||
// Match the call to the right IRKind
|
// Match the call to the right IRKind
|
||||||
let ir_kind = match new_call.0.unwrap() {
|
let ir_kind = match new_call.0.unwrap() {
|
||||||
IRKind::Call { .. } => IRKind::Call { name: cname, args: largs },
|
IRKind::Call { .. } => IRKind::Call {
|
||||||
IRKind::Intrinsic { .. } => IRKind::Intrinsic { name: cname, args: largs },
|
name: cname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
},
|
||||||
|
IRKind::Intrinsic { .. } => IRKind::Intrinsic {
|
||||||
|
name: cname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
},
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
return (Some(ir_kind), None);
|
return_ok!(ir_kind);
|
||||||
},
|
},
|
||||||
_ => return (None, Some(LoweringError {
|
_ => return_err!(LoweringError {
|
||||||
span: rhs.1.clone(),
|
span: rhs.1.clone(),
|
||||||
message: "Expected call".to_string(),
|
message: "Expected call".to_string()
|
||||||
note: None
|
}),
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Let { public, name, type_hint, value, mutable } => {
|
Expr::Let { public, name, type_hint, value, mutable } => {
|
||||||
let value = expr_to_ir(&value.0);
|
let lvalue = expr_to_ir(&value.0);
|
||||||
if_err_return!(value.1);
|
if_err_return!(lvalue.1);
|
||||||
|
|
||||||
let value = value.0.unwrap();
|
return_ok!(IRKind::Define {
|
||||||
let ir_kind = IRKind::Define {
|
|
||||||
public: *public,
|
public: *public,
|
||||||
name: name.clone(),
|
name: name.0.clone(),
|
||||||
type_hint: gen_type_hint(type_hint),
|
type_hint: gen_type_hint(&type_hint.0),
|
||||||
value: Box::new(value),
|
value: Box::new(lvalue.0.unwrap()),
|
||||||
mutable: *mutable
|
mutable: *mutable,
|
||||||
};
|
span: value.1.clone()
|
||||||
|
});
|
||||||
return (Some(ir_kind), None);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Intrinsic { name, args } => {
|
Expr::Intrinsic { name, args } => {
|
||||||
let name = match &name.0 {
|
let lname = match &name.0 {
|
||||||
Expr::Identifier(s) => {
|
Expr::Identifier(s) => {
|
||||||
if INTRINSICS.contains(&s.as_str()) { s.clone() }
|
if INTRINSICS.contains(&s.as_str()) { s.clone() }
|
||||||
else {
|
else {
|
||||||
return (None, Some(LoweringError {
|
return_err!(LoweringError {
|
||||||
span: name.1.clone(),
|
span: name.1.clone(),
|
||||||
message: format!("Unknown intrinsic: `{}`", s),
|
message: format!("Unknown intrinsic: `{}`", s),
|
||||||
note: Some(format!("Did you mean: {}?", closet_intrinsic(s.to_string())))
|
});
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return (None, Some(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string(), note: None }))
|
_ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()})
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut largs = Vec::new();
|
let mut largs = Vec::new();
|
||||||
|
@ -314,73 +315,80 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
if let IRKind::Value{ .. } = larg.0.clone().unwrap() {
|
if let IRKind::Value{ .. } = larg.0.clone().unwrap() {
|
||||||
largs.push(larg.0.clone().unwrap());
|
largs.push(larg.0.clone().unwrap());
|
||||||
} else {
|
} else {
|
||||||
return (None, Some(LoweringError { span: arg.1.clone(), message: "Expected string".to_string(), note: None }))
|
return_err!(LoweringError { span: arg.1.clone(), message: "Expected string".to_string()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ir_kind = IRKind::Intrinsic { name, args: largs };
|
return_ok!(IRKind::Intrinsic {
|
||||||
return (Some(ir_kind), None);
|
name: lname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Fun { public, name, type_hint, args, body } => {
|
Expr::Fun { public, name, type_hint, args, body } => {
|
||||||
// Iterate each argument and give it a type hint
|
// Iterate each argument and give it a type hint
|
||||||
let args = args.0.iter().map(|arg| (arg.0.0.clone(), gen_type_hint(&arg.1.0))).collect::<Vec<_>>();
|
let largs = args.0.iter().map(|arg| (arg.0.0.clone(), gen_type_hint(&arg.1.0))).collect::<Vec<_>>();
|
||||||
|
|
||||||
let body = expr_to_ir(&body.0);
|
let lbody = expr_to_ir(&body.0);
|
||||||
if_err_return!(body.1);
|
if_err_return!(lbody.1);
|
||||||
|
|
||||||
let body = body.0.unwrap();
|
return_ok!(IRKind::Fun {
|
||||||
let ir_kind = IRKind::Fun {
|
|
||||||
public: *public,
|
public: *public,
|
||||||
name: name.clone(),
|
name: name.0.clone(),
|
||||||
return_type_hint: gen_type_hint(type_hint),
|
return_type_hint: gen_type_hint(&type_hint.0),
|
||||||
args,
|
args: largs,
|
||||||
body: Box::new(body)
|
body: Box::new(lbody.0.unwrap()),
|
||||||
};
|
span: name.1.start..body.1.end
|
||||||
return (Some(ir_kind), None);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Return { expr } => {
|
Expr::Return { expr } => {
|
||||||
let expr = expr_to_ir(&expr.0);
|
let lexpr = expr_to_ir(&expr.0);
|
||||||
if_err_return!(expr.1);
|
if_err_return!(lexpr.1);
|
||||||
|
|
||||||
let expr = expr.0.unwrap();
|
return_ok!(IRKind::Return {
|
||||||
let ir_kind = IRKind::Return { value: Box::new(expr) };
|
value: Box::new(lexpr.0.unwrap()),
|
||||||
return (Some(ir_kind), None);
|
span: expr.1.clone()
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Do { body } => {
|
Expr::Do { body } => {
|
||||||
let mut lbody = Vec::new();
|
let mut lbody = Vec::new();
|
||||||
for expr in body {
|
|
||||||
|
for expr in &body.0 {
|
||||||
let expr = expr_to_ir(&expr.0);
|
let expr = expr_to_ir(&expr.0);
|
||||||
if_err_return!(expr.1);
|
if_err_return!(expr.1);
|
||||||
lbody.push(expr.0.unwrap());
|
lbody.push(expr.0.unwrap());
|
||||||
}
|
};
|
||||||
let ir_kind = IRKind::Do { body: lbody };
|
|
||||||
return (Some(ir_kind), None);
|
return_ok!(IRKind::Do {
|
||||||
|
body: lbody,
|
||||||
|
span: body.1.clone()
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::If { cond, body, else_body } => {
|
Expr::If { cond, body, else_body } => {
|
||||||
let cond = expr_to_ir(&cond.0);
|
let lcond = expr_to_ir(&cond.0);
|
||||||
if_err_return!(cond.1);
|
if_err_return!(lcond.1);
|
||||||
|
|
||||||
let body = expr_to_ir(&body.0);
|
let lbody = expr_to_ir(&body.0);
|
||||||
if_err_return!(body.1);
|
if_err_return!(lbody.1);
|
||||||
|
|
||||||
let else_body = expr_to_ir(&else_body.0);
|
let lelse_body = expr_to_ir(&else_body.0);
|
||||||
if_err_return!(else_body.1);
|
if_err_return!(lelse_body.1);
|
||||||
|
|
||||||
let ir_kind = IRKind::If {
|
return_ok!(IRKind::If {
|
||||||
cond: Box::new(cond.0.unwrap()),
|
cond: Box::new(lcond.0.unwrap()),
|
||||||
body: Box::new(body.0.unwrap()),
|
body: Box::new(lbody.0.unwrap()),
|
||||||
else_body: Box::new(else_body.0.unwrap())
|
else_body: Box::new(lelse_body.0.unwrap()),
|
||||||
};
|
span: cond.1.start..else_body.1.end
|
||||||
return (Some(ir_kind), None);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
Expr::Case { expr, cases, default } => {
|
Expr::Case { expr, cases, default } => {
|
||||||
let expr = expr_to_ir(&expr.0);
|
let lexpr = expr_to_ir(&expr.0);
|
||||||
if_err_return!(expr.1);
|
if_err_return!(lexpr.1);
|
||||||
|
|
||||||
let mut lcases = Vec::new();
|
let mut lcases = Vec::new();
|
||||||
for case in &cases.0 {
|
for case in &cases.0 {
|
||||||
|
@ -395,23 +403,23 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let default = expr_to_ir(&default.0);
|
let ldefault = expr_to_ir(&default.0);
|
||||||
if_err_return!(default.1);
|
if_err_return!(ldefault.1);
|
||||||
|
|
||||||
let ir_kind = IRKind::Case {
|
return_ok!(IRKind::Case {
|
||||||
cond: Box::new(expr.0.unwrap()),
|
cond: Box::new(lexpr.0.unwrap()),
|
||||||
cases: lcases,
|
cases: lcases,
|
||||||
default: Box::new(default.0.unwrap())
|
default: Box::new(ldefault.0.unwrap()),
|
||||||
};
|
span: expr.1.start..default.1.end
|
||||||
return (Some(ir_kind), None);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Handle primitive types error (e.g. overflow)
|
// TODO: Handle primitive types error (e.g. overflow)
|
||||||
// For now it just leaves the value as is and let the target compiler handle it
|
// For now it just leaves the value as is and let the target compiler handle it
|
||||||
Expr::Int(value) => (Some(IRKind::Value { value: Value::Int(*value) }), None),
|
Expr::Int(value) => return_ok!(IRKind::Value { value: Value::Int(*value) }),
|
||||||
Expr::Boolean(value) => (Some(IRKind::Value { value: Value::Boolean(*value) }), None),
|
Expr::Boolean(value) => return_ok!(IRKind::Value { value: Value::Boolean(*value) }),
|
||||||
Expr::String(value) => (Some(IRKind::Value { value: Value::String(value.clone()) }), None),
|
Expr::String(value) => return_ok!(IRKind::Value { value: Value::String(value.clone()) }),
|
||||||
Expr::Identifier(value) => (Some(IRKind::Value { value: Value::Ident(value.clone()) }), None),
|
Expr::Identifier(value) => return_ok!(IRKind::Value { value: Value::Ident(value.clone()) }),
|
||||||
|
|
||||||
Expr::Vector(values) => {
|
Expr::Vector(values) => {
|
||||||
let mut lvalues = Vec::new();
|
let mut lvalues = Vec::new();
|
||||||
|
@ -427,8 +435,7 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
// Probably will never happen because it is catched in parser
|
// Probably will never happen because it is catched in parser
|
||||||
Expr::Hole(start, end) => (None, Some(LoweringError {
|
Expr::Hole(start, end) => (None, Some(LoweringError {
|
||||||
span: *start..*end,
|
span: *start..*end,
|
||||||
message: "Hole can only be used in piping, it is not allowed here.".to_string(),
|
message: "Hole can only be used in piping, it is not allowed here.".to_string()
|
||||||
note: None
|
|
||||||
})),
|
})),
|
||||||
_ => { dbg!(expr); todo!() }
|
_ => { dbg!(expr); todo!() }
|
||||||
}
|
}
|
||||||
|
@ -446,18 +453,4 @@ fn gen_type_hint(type_hint: &str) -> String {
|
||||||
"vec_str" => "string[]".to_string(),
|
"vec_str" => "string[]".to_string(),
|
||||||
_ => { dbg!(type_hint); todo!() }
|
_ => { dbg!(type_hint); todo!() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the closet intrinsic name to the given name
|
|
||||||
fn closet_intrinsic(got: String) -> String {
|
|
||||||
let mut closest = String::new();
|
|
||||||
let mut closest_dist = std::usize::MAX;
|
|
||||||
for intrinsic in INTRINSICS.iter() {
|
|
||||||
let dist = levenshtein::levenshtein(got.as_str(), intrinsic);
|
|
||||||
if dist < closest_dist {
|
|
||||||
closest = intrinsic.to_string();
|
|
||||||
closest_dist = dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closest
|
|
||||||
}
|
|
|
@ -18,6 +18,7 @@ lexer = { path = "../lexer" }
|
||||||
parser = { path = "../parser" }
|
parser = { path = "../parser" }
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
typecheck = { path = "../typecheck" }
|
||||||
diagnostic = { path = "../diagnostic" }
|
diagnostic = { path = "../diagnostic" }
|
||||||
|
|
||||||
# Codegen
|
# Codegen
|
||||||
|
|
|
@ -6,6 +6,7 @@ use lexer::lex;
|
||||||
use parser::parse;
|
use parser::parse;
|
||||||
use diagnostic::Diagnostics;
|
use diagnostic::Diagnostics;
|
||||||
use hir::ast_to_ir;
|
use hir::ast_to_ir;
|
||||||
|
use typecheck::check;
|
||||||
use codegen::ts;
|
use codegen::ts;
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
|
@ -52,16 +53,24 @@ fn main() {
|
||||||
logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis()));
|
logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: S-Expr syntax for AST
|
|
||||||
// if print_ast { log(0, format!("{:#?}", ast)); }
|
|
||||||
|
|
||||||
match ast {
|
match ast {
|
||||||
Some(ast) => {
|
Some(ast) => {
|
||||||
// Convert the AST to HIR
|
// Convert the AST to HIR
|
||||||
let (ir, lowering_error) = ast_to_ir(ast);
|
let (ir, lowering_error) = ast_to_ir(ast);
|
||||||
for err in lowering_error { diagnostics.add_lowering_error(err); }
|
for err in lowering_error { diagnostics.add_lowering_error(err); }
|
||||||
|
|
||||||
if print_ast { log(0, format!("IR\n{}", ir.iter().map(|x| format!("{}", x.kind)).collect::<Vec<String>>().join("\n\n"))); }
|
if print_ast { log(0, format!("IR\n{:#?}", ir)); }
|
||||||
|
|
||||||
|
// Typecheck the HIR
|
||||||
|
match check(&ir) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => {
|
||||||
|
diagnostics.add_typecheck_error(err);
|
||||||
|
diagnostics.display(src);
|
||||||
|
logif!(0, "Typechecking failed");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Report lowering errors if any
|
// Report lowering errors if any
|
||||||
if diagnostics.has_error() {
|
if diagnostics.has_error() {
|
||||||
|
|
|
@ -18,15 +18,15 @@ pub enum Expr {
|
||||||
|
|
||||||
Let {
|
Let {
|
||||||
public: bool,
|
public: bool,
|
||||||
name: String,
|
name: Spanned<String>,
|
||||||
type_hint: String,
|
type_hint: Spanned<String>,
|
||||||
value: Box<Spanned<Self>>,
|
value: Box<Spanned<Self>>,
|
||||||
mutable: bool,
|
mutable: bool,
|
||||||
},
|
},
|
||||||
Fun {
|
Fun {
|
||||||
public: bool,
|
public: bool,
|
||||||
name: String,
|
name: Spanned<String>,
|
||||||
type_hint: String,
|
type_hint: Spanned<String>,
|
||||||
args: Spanned<Vec<(Spanned<String>, Spanned<String>)>>,
|
args: Spanned<Vec<(Spanned<String>, Spanned<String>)>>,
|
||||||
body: Box<Spanned<Self>>
|
body: Box<Spanned<Self>>
|
||||||
},
|
},
|
||||||
|
@ -43,7 +43,7 @@ pub enum Expr {
|
||||||
default: Box<Spanned<Self>>
|
default: Box<Spanned<Self>>
|
||||||
},
|
},
|
||||||
Do {
|
Do {
|
||||||
body: Vec<Spanned<Self>>
|
body: Spanned<Vec<Spanned<Self>>>
|
||||||
},
|
},
|
||||||
|
|
||||||
// Hole for positional argument(s) in piping
|
// Hole for positional argument(s) in piping
|
||||||
|
@ -226,8 +226,8 @@ fn expr_parser() -> impl Parser<Token, Vec<Spanned<Expr>>, Error = Simple<Token>
|
||||||
(
|
(
|
||||||
Expr::Let {
|
Expr::Let {
|
||||||
public: public.is_some(),
|
public: public.is_some(),
|
||||||
name: name.0.clone(),
|
name: name.clone(),
|
||||||
type_hint: type_hint.0,
|
type_hint,
|
||||||
value: Box::new(value.clone()),
|
value: Box::new(value.clone()),
|
||||||
mutable: mutable.is_some(),
|
mutable: mutable.is_some(),
|
||||||
},
|
},
|
||||||
|
@ -256,8 +256,8 @@ fn expr_parser() -> impl Parser<Token, Vec<Spanned<Expr>>, Error = Simple<Token>
|
||||||
(
|
(
|
||||||
Expr::Fun {
|
Expr::Fun {
|
||||||
public: public.is_some(),
|
public: public.is_some(),
|
||||||
name: name.0.clone(),
|
name: name.clone(),
|
||||||
type_hint: type_hint.0,
|
type_hint,
|
||||||
args: (args, name.1.clone()),
|
args: (args, name.1.clone()),
|
||||||
body: Box::new(body.clone()),
|
body: Box::new(body.clone()),
|
||||||
},
|
},
|
||||||
|
@ -286,7 +286,7 @@ fn expr_parser() -> impl Parser<Token, Vec<Spanned<Expr>>, Error = Simple<Token>
|
||||||
.map_with_span(|body, span| {
|
.map_with_span(|body, span| {
|
||||||
(
|
(
|
||||||
Expr::Do {
|
Expr::Do {
|
||||||
body: body.clone(),
|
body: (body.clone(), span.clone()),
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
|
|
10
crates/typecheck/Cargo.toml
Normal file
10
crates/typecheck/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "typecheck"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hir = { path = "../hir" }
|
34
crates/typecheck/src/lib.rs
Normal file
34
crates/typecheck/src/lib.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use hir::{IR, IRKind};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TypecheckErrorKind {
|
||||||
|
DefinitionTypeMismatch {
|
||||||
|
type_specified: String,
|
||||||
|
type_found: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TypecheckErrorKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TypecheckErrorKind::DefinitionTypeMismatch {
|
||||||
|
type_specified,
|
||||||
|
type_found,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"expected type `{}`, found type `{}`",
|
||||||
|
type_specified, type_found
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TypecheckError {
|
||||||
|
pub kind: TypecheckErrorKind,
|
||||||
|
pub span: std::ops::Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(ir: &Vec<IR>) -> Result<(), TypecheckError> {
|
||||||
|
todo!();
|
||||||
|
}
|
Loading…
Reference in a new issue