added structs
This commit is contained in:
parent
0edbc46aa0
commit
47c6ccfa6b
97
Cargo.lock
generated
97
Cargo.lock
generated
|
@ -2,6 +2,103 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lang"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wat",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.219.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.219.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "219.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"leb128",
|
||||
"memchr",
|
||||
"unicode-width",
|
||||
"wasm-encoder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wat"
|
||||
version = "1.219.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d"
|
||||
dependencies = [
|
||||
"wast",
|
||||
]
|
||||
|
|
|
@ -4,3 +4,4 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
wat = "1.219"
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
i := 0;
|
||||
|
||||
while i < 10 {
|
||||
i = i + 1;
|
||||
print;
|
||||
}
|
||||
Point :: struct {x: integer};
|
||||
a : integer : 10;
|
||||
p :: Point{x: a};
|
||||
|
|
23
index.html
Normal file
23
index.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
|
||||
<script>
|
||||
const importObject = {
|
||||
console: {
|
||||
log(arg) {
|
||||
console.log(arg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebAssembly.instantiateStreaming(fetch("/test.wasm"), importObject)
|
||||
.then(obj => {
|
||||
obj.instance.exports.main();
|
||||
});
|
||||
</script>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
16
src/err.rs
16
src/err.rs
|
@ -3,7 +3,7 @@ use crate::Span;
|
|||
pub type Result<T> = std::result::Result<T, Diagnostic>;
|
||||
|
||||
pub fn error<T>() -> Result<T> {
|
||||
Err(Diagnostic::new(""))
|
||||
Err(Diagnostic::new("", None))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -14,10 +14,10 @@ pub struct Diagnostic {
|
|||
}
|
||||
|
||||
impl Diagnostic {
|
||||
pub fn new(reason: impl Into<String>) -> Self {
|
||||
pub fn new(reason: impl Into<String>, span: Option<Span>) -> Self {
|
||||
Self {
|
||||
reason: reason.into(),
|
||||
span: None,
|
||||
span,
|
||||
backtrace: vec![],
|
||||
}
|
||||
}
|
||||
|
@ -58,17 +58,19 @@ impl From<std::num::ParseIntError> for Diagnostic {
|
|||
use std::num::IntErrorKind::*;
|
||||
match value.kind() {
|
||||
PosOverflow | NegOverflow => {
|
||||
Diagnostic::new("Integer value is too large to represent")
|
||||
Diagnostic::new("Integer value is too large to represent", None)
|
||||
},
|
||||
InvalidDigit => Diagnostic::new("Integer value containts invalid digits"),
|
||||
_ => Diagnostic::new("Integer value could not be parsed"),
|
||||
InvalidDigit => {
|
||||
Diagnostic::new("Integer value containts invalid digits", None)
|
||||
},
|
||||
_ => Diagnostic::new("Integer value could not be parsed", None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::ParseFloatError> for Diagnostic {
|
||||
fn from(_value: std::num::ParseFloatError) -> Self {
|
||||
Diagnostic::new("Float value could not be parsed")
|
||||
Diagnostic::new("Float value could not be parsed", None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::{
|
||||
err::*, treewalk::Interpreter, Parser, Statement, StatementKind, Token,
|
||||
Tokenizer,
|
||||
};
|
||||
use crate::{err::*, semantic::typecheck, Parser, Statement, Tokenizer};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Module {
|
||||
file_name: String,
|
||||
source: String,
|
||||
ast: Vec<Statement>,
|
||||
pub program: Vec<Statement>,
|
||||
errors: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
|
@ -28,19 +25,13 @@ impl Module {
|
|||
|
||||
pub fn from_string(file_name: String, source: String) -> Self {
|
||||
let tokens = Tokenizer::new(source.chars()).filter(|t| t.0.is_meaningful());
|
||||
let mut ast = vec![];
|
||||
let statements = Parser::new(tokens);
|
||||
let program = typecheck(statements.collect());
|
||||
let mut errors = vec![];
|
||||
for statement in Parser::new(tokens) {
|
||||
use StatementKind as s;
|
||||
match statement.kind {
|
||||
s::Error(e) => errors.push(e),
|
||||
_ => ast.push(statement),
|
||||
}
|
||||
}
|
||||
Self {
|
||||
file_name,
|
||||
source: source.into(),
|
||||
ast,
|
||||
program,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
@ -52,16 +43,4 @@ impl Module {
|
|||
pub fn ok(&self) -> bool {
|
||||
self.errors.len() == 0
|
||||
}
|
||||
|
||||
pub fn execute(&self) {
|
||||
if !self.ok() {
|
||||
for e in self.errors() {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
Interpreter::new(self.ast.clone().into_iter())
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ mod err;
|
|||
mod frontend;
|
||||
mod lookahead;
|
||||
mod parse;
|
||||
mod semantic;
|
||||
mod token;
|
||||
mod treewalk;
|
||||
mod types;
|
||||
use std::ops::Add;
|
||||
|
||||
use err::*;
|
||||
|
@ -63,6 +63,8 @@ fn test_expression(expr: &str) {
|
|||
|
||||
fn main() -> Result<()> {
|
||||
let module = frontend::Module::from_file("./demo.lang")?;
|
||||
module.execute();
|
||||
for s in &module.program {
|
||||
println!("{s:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,39 +1,53 @@
|
|||
use crate::semantic::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Parameter {
|
||||
name: String,
|
||||
type_: String,
|
||||
pub name: String,
|
||||
pub type_str: String,
|
||||
pub type_actual: Type,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Parameter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {}", self.name, self.type_)
|
||||
write!(f, "{}: {:?}", self.name, self.type_actual)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ExpressionKind {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Immediate {
|
||||
Integer(i64),
|
||||
Real(f64),
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ExpressionKind {
|
||||
Immediate(Immediate),
|
||||
Identifier(String),
|
||||
Binary {
|
||||
token: TokenKind,
|
||||
op: BinaryOp,
|
||||
left: Box<Expression>,
|
||||
right: Box<Expression>,
|
||||
},
|
||||
Unary {
|
||||
token: TokenKind,
|
||||
op: UnaryOp,
|
||||
child: Box<Expression>,
|
||||
},
|
||||
Parenthesis(Box<Expression>),
|
||||
Function {
|
||||
params: Vec<Parameter>,
|
||||
returns: Option<String>,
|
||||
returns_str: Option<String>,
|
||||
returns_actual: Type,
|
||||
body: Vec<Statement>,
|
||||
},
|
||||
Struct(Vec<Parameter>),
|
||||
StructLiteral {
|
||||
name: String,
|
||||
args: Vec<(String, Expression)>,
|
||||
},
|
||||
Call {
|
||||
callee: Box<Expression>,
|
||||
args: Vec<Expression>,
|
||||
|
@ -48,39 +62,55 @@ pub enum ExpressionKind {
|
|||
pub struct Expression {
|
||||
pub kind: ExpressionKind,
|
||||
pub span: Span,
|
||||
pub type_: Type,
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn new(kind: ExpressionKind, span: Span) -> Self {
|
||||
Self { kind, span }
|
||||
Self {
|
||||
kind,
|
||||
span,
|
||||
type_: Type::Ambiguous,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ExpressionKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use ExpressionKind as e;
|
||||
use Immediate as im;
|
||||
match self {
|
||||
e::Integer(i) => write!(f, "{i}"),
|
||||
e::Binary { token, left, right } => {
|
||||
e::Immediate(i) => match i {
|
||||
im::Integer(i) => write!(f, "{i}"),
|
||||
im::Real(fp) => write!(f, "{fp}"),
|
||||
im::String(s) => write!(f, r#""{s}""#),
|
||||
im::Boolean(b) => write!(f, "{b}"),
|
||||
},
|
||||
e::Binary {
|
||||
op: token,
|
||||
left,
|
||||
right,
|
||||
} => {
|
||||
write!(f, "({left:?} {token:?} {right:?})")
|
||||
},
|
||||
e::Parenthesis(inner) => write!(f, "{inner:?}"),
|
||||
e::Unary { token, child } => {
|
||||
e::Unary { op: token, child } => {
|
||||
write!(f, "({token:?} {child:?})")
|
||||
},
|
||||
e::Real(fp) => write!(f, "{fp}"),
|
||||
e::String(s) => write!(f, r#""{s}""#),
|
||||
e::Identifier(i) => write!(f, "{i}"),
|
||||
e::Boolean(b) => write!(f, "{b}"),
|
||||
e::Call { callee, args } => write!(f, "({callee:?} call {args:?})"),
|
||||
e::Field { namespace, field } => {
|
||||
write!(f, "({namespace:?} . {field:?})")
|
||||
},
|
||||
e::Function {
|
||||
params, returns, ..
|
||||
params,
|
||||
returns_actual,
|
||||
..
|
||||
} => {
|
||||
write!(f, "(fn({params:?}) -> {returns:?})")
|
||||
write!(f, "(fn({params:?}) -> {returns_actual:?})")
|
||||
},
|
||||
e::Struct(params) => write!(f, "struct {{ {params:?} }}"),
|
||||
e::StructLiteral { name, args } => write!(f, "{name} {{ {args:?} }}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,16 +127,22 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
use TokenKind as t;
|
||||
let next = self.peek(0)?;
|
||||
// Unary prefix expression
|
||||
let mut current = if let Ok(p) = unary_prefix_prec(&next.0) {
|
||||
let operator = self.next_tok().expect("unreachable");
|
||||
let mut current = if let Ok(operator) = UnaryOp::try_from(&next.0) {
|
||||
self.skip(1);
|
||||
let span = next.1;
|
||||
if operator.assoc() == RIGHT_ASSOC {
|
||||
return error()
|
||||
.reason(format!("The {} operator must come after a value", operator))
|
||||
.span(&span);
|
||||
}
|
||||
let child = self
|
||||
.expression(p)
|
||||
.trace(format!("while parsing unary {}", operator.0))
|
||||
.span(&operator.1)?;
|
||||
let span = child.span + operator.1;
|
||||
.expression(operator.precedence())
|
||||
.trace(format!("while parsing unary {}", operator))
|
||||
.span(&span)?;
|
||||
let span = span + child.span;
|
||||
Expression::new(
|
||||
e::Unary {
|
||||
token: operator.0,
|
||||
op: operator,
|
||||
child: child.into(),
|
||||
},
|
||||
span,
|
||||
|
@ -120,21 +156,23 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
// Precedence climbing loop
|
||||
while let Ok(next) = self.peek(0) {
|
||||
// Binary infix
|
||||
if let Ok((new_precedence, left_assoc)) = binary_prec(&next.0) {
|
||||
if (!left_assoc && new_precedence <= precedence)
|
||||
if let Ok(operator) = BinaryOp::try_from(&next.0) {
|
||||
let new_precedence = operator.precedence();
|
||||
if ((operator.assoc() == LEFT_ASSOC) && new_precedence <= precedence)
|
||||
|| (new_precedence < precedence)
|
||||
{
|
||||
return Ok(current);
|
||||
}
|
||||
let operator = self.next_tok().expect("unreachable");
|
||||
self.skip(1);
|
||||
let span = next.1;
|
||||
let rhs = self
|
||||
.expression(new_precedence)
|
||||
.trace(format!("while parsing binary {}", operator.0))
|
||||
.span(&operator.1)?;
|
||||
.trace(format!("while parsing binary {}", operator))
|
||||
.span(&span)?;
|
||||
let span = next.1 + rhs.span;
|
||||
current = Expression::new(
|
||||
e::Binary {
|
||||
token: operator.0,
|
||||
op: operator,
|
||||
left: current.into(),
|
||||
right: rhs.into(),
|
||||
},
|
||||
|
@ -187,12 +225,20 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
);
|
||||
}
|
||||
// Unary postfix
|
||||
else if let Ok(_) = unary_postfix_prec(&next.0) {
|
||||
let operator = self.next_tok().expect("unreachable");
|
||||
let span = next.1 + operator.1;
|
||||
else if let Ok(operator) = UnaryOp::try_from(&next.0) {
|
||||
self.skip(1);
|
||||
let span = next.1;
|
||||
if operator.assoc() == RIGHT_ASSOC {
|
||||
return error()
|
||||
.reason(format!(
|
||||
"The {} operator must come before a value",
|
||||
operator
|
||||
))
|
||||
.span(&span);
|
||||
}
|
||||
current = Expression::new(
|
||||
e::Unary {
|
||||
token: operator.0,
|
||||
op: operator,
|
||||
child: current.into(),
|
||||
},
|
||||
span,
|
||||
|
@ -206,16 +252,32 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
|
||||
fn primary(&mut self) -> Result<Expression> {
|
||||
use ExpressionKind as e;
|
||||
use Immediate as im;
|
||||
use TokenKind as t;
|
||||
let next = self.peek(0)?;
|
||||
let mut span = next.1;
|
||||
let kind = match next.0 {
|
||||
t::IntegerLiteral(i) => e::Integer(i),
|
||||
t::FloatLiteral(f) => e::Real(f),
|
||||
t::StringLiteral(s) => e::String(s),
|
||||
t::True => e::Boolean(true),
|
||||
t::Identifier(i) => e::Identifier(i),
|
||||
// function
|
||||
t::IntegerLiteral(i) => {
|
||||
self.skip(1);
|
||||
e::Immediate(im::Integer(i))
|
||||
},
|
||||
t::FloatLiteral(f) => {
|
||||
self.skip(1);
|
||||
e::Immediate(im::Real(f))
|
||||
},
|
||||
t::StringLiteral(s) => {
|
||||
self.skip(1);
|
||||
e::Immediate(im::String(s))
|
||||
},
|
||||
t::True => {
|
||||
self.skip(1);
|
||||
e::Immediate(im::Boolean(true))
|
||||
},
|
||||
t::False => {
|
||||
self.skip(1);
|
||||
e::Immediate(im::Boolean(true))
|
||||
},
|
||||
// Function definition
|
||||
t::LeftParen
|
||||
if (self.look(1, t::Identifier("".into())).is_ok()
|
||||
&& self.look(2, t::Colon).is_ok())
|
||||
|
@ -236,7 +298,11 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
.identifier()
|
||||
.trace_span(span, "while parsing function parameter type")?;
|
||||
span = span + span2;
|
||||
params.push(Parameter { name, type_ });
|
||||
params.push(Parameter {
|
||||
name,
|
||||
type_str: type_,
|
||||
type_actual: Type::Ambiguous,
|
||||
});
|
||||
if !self.eat(t::Comma).is_ok() {
|
||||
break;
|
||||
}
|
||||
|
@ -245,7 +311,7 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
.eat(t::RightParen)
|
||||
.span(&span)
|
||||
.trace_span(span, "while parsing function definition")?;
|
||||
let returns = if let Ok(Token(_, span2)) = self.eat(t::Arrow) {
|
||||
let returns_str = if let Ok(Token(_, span2)) = self.eat(t::Arrow) {
|
||||
span = span + span2;
|
||||
let (identifier, span2) = self
|
||||
.identifier()
|
||||
|
@ -262,18 +328,78 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
span = span + span2;
|
||||
e::Function {
|
||||
params,
|
||||
returns,
|
||||
returns_str,
|
||||
returns_actual: Type::Ambiguous,
|
||||
body,
|
||||
}
|
||||
},
|
||||
// parenthetical
|
||||
// Struct definition
|
||||
t::Struct => {
|
||||
self.skip(1);
|
||||
self.eat(t::LeftBrace)?;
|
||||
let mut params = vec![];
|
||||
loop {
|
||||
let (name, span2) = match self.identifier() {
|
||||
Ok((name, span)) => (name, span),
|
||||
Err(_) => break,
|
||||
};
|
||||
span = span + span2;
|
||||
self
|
||||
.eat(t::Colon)
|
||||
.trace_span(span, "while parsing struct parameter type")?;
|
||||
let (type_, span2) = self
|
||||
.identifier()
|
||||
.trace_span(span, "while parsing struct parameter type")?;
|
||||
span = span + span2;
|
||||
params.push(Parameter {
|
||||
name,
|
||||
type_str: type_,
|
||||
type_actual: Type::Ambiguous,
|
||||
});
|
||||
if !self.eat(t::Comma).is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.eat(t::RightBrace)?;
|
||||
e::Struct(params)
|
||||
},
|
||||
// Struct literal
|
||||
t::Identifier(name) if self.look(1, t::LeftBrace).is_ok() => {
|
||||
self.skip(2);
|
||||
let mut args = vec![];
|
||||
loop {
|
||||
let (name, span2) = match self.identifier() {
|
||||
Ok((name, span)) => (name, span),
|
||||
Err(_) => break,
|
||||
};
|
||||
span = span + span2;
|
||||
self
|
||||
.eat(t::Colon)
|
||||
.trace_span(span, "while parsing struct parameter type")?;
|
||||
let expr = self
|
||||
.expression(0)
|
||||
.trace_span(span, "while parsing struct parameter type")?;
|
||||
span = span + expr.span;
|
||||
args.push((name, expr));
|
||||
if !self.eat(t::Comma).is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.eat(t::RightBrace)?;
|
||||
e::StructLiteral { name, args }
|
||||
},
|
||||
t::Identifier(i) => {
|
||||
self.skip(1);
|
||||
e::Identifier(i)
|
||||
},
|
||||
// Parenthetical
|
||||
t::LeftParen => {
|
||||
self.skip(1);
|
||||
let expr = self
|
||||
.expression(0)
|
||||
.trace("while parsing parenthesized expression")?;
|
||||
self
|
||||
.look(0, t::RightParen)
|
||||
.eat(t::RightParen)
|
||||
.reason("Unclosed '('")
|
||||
.span(&expr.span)?;
|
||||
e::Parenthesis(expr.into())
|
||||
|
@ -284,8 +410,7 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
.reason(format!("Expected expression, found {}", next.0));
|
||||
},
|
||||
};
|
||||
self.skip(1);
|
||||
Ok(Expression { kind, span })
|
||||
Ok(Expression::new(kind, span))
|
||||
}
|
||||
|
||||
fn identifier(&mut self) -> Result<(String, Span)> {
|
||||
|
|
118
src/parse/mod.rs
118
src/parse/mod.rs
|
@ -8,51 +8,82 @@ use crate::{Span, Token, TokenKind};
|
|||
|
||||
pub type Precedence = usize;
|
||||
|
||||
macro_rules! op {
|
||||
($name:ident; $($op:ident, $prec:expr, $assoc:expr);*;) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum $name {
|
||||
$($op,)*
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub fn precedence(&self) -> Precedence {
|
||||
match self {
|
||||
$(Self::$op => $prec),*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assoc(&self) -> bool {
|
||||
match self {
|
||||
$(Self::$op => $assoc),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&TokenKind> for $name {
|
||||
type Error = Diagnostic;
|
||||
fn try_from(value: &TokenKind) -> Result<Self> {
|
||||
match value {
|
||||
$(TokenKind::$op => Ok(Self::$op),)*
|
||||
_ => error().reason(format!("Invalid operator {value}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const RIGHT_ASSOC: bool = true;
|
||||
const LEFT_ASSOC: bool = false;
|
||||
|
||||
// Name, precedence, associativity;
|
||||
op! {
|
||||
BinaryOp;
|
||||
Star, 10, LEFT_ASSOC;
|
||||
Slash, 10, LEFT_ASSOC;
|
||||
Percent, 10, LEFT_ASSOC;
|
||||
Plus, 9, LEFT_ASSOC;
|
||||
Minus, 9, LEFT_ASSOC;
|
||||
And, 8, LEFT_ASSOC;
|
||||
Nand, 8, LEFT_ASSOC;
|
||||
Xor, 7, LEFT_ASSOC;
|
||||
Xnor, 7, LEFT_ASSOC;
|
||||
Or, 6, LEFT_ASSOC;
|
||||
Nor, 6, LEFT_ASSOC;
|
||||
DoubleEqual, 5, LEFT_ASSOC;
|
||||
BangEqual, 5, LEFT_ASSOC;
|
||||
Less, 5, LEFT_ASSOC;
|
||||
LessEqual, 5, LEFT_ASSOC;
|
||||
Greater, 5, LEFT_ASSOC;
|
||||
GreaterEqual, 5, LEFT_ASSOC;
|
||||
}
|
||||
|
||||
op! {
|
||||
UnaryOp;
|
||||
Bang, 12, RIGHT_ASSOC;
|
||||
Question, 12, RIGHT_ASSOC;
|
||||
Minus, 11, LEFT_ASSOC;
|
||||
Plus, 11, LEFT_ASSOC;
|
||||
Not, 11, LEFT_ASSOC;
|
||||
}
|
||||
|
||||
const FIELD_PREC: Precedence = 13;
|
||||
const CALL_PREC: Precedence = 12;
|
||||
|
||||
fn binary_prec(tok: &TokenKind) -> Result<(Precedence, bool)> {
|
||||
use TokenKind::*;
|
||||
Ok(match tok {
|
||||
Star | Slash | Percent => (10, false),
|
||||
Plus | Minus => (9, false),
|
||||
And | Nand => (8, false),
|
||||
Xor | Xnor => (7, false),
|
||||
Or | Nor => (6, false),
|
||||
DoubleEqual | BangEqual | Less | LessEqual | Greater | GreaterEqual => {
|
||||
(5, false)
|
||||
},
|
||||
//Colon => Some((5, false)),
|
||||
_ => {
|
||||
return error().reason(format!("{} is not a valid binary operator", tok));
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn unary_prefix_prec(tok: &TokenKind) -> Result<Precedence> {
|
||||
use TokenKind::*;
|
||||
Ok(match tok {
|
||||
Minus | Not => 11,
|
||||
Break => 3,
|
||||
_ => {
|
||||
return error()
|
||||
.reason(format!("{tok} is not a valid prefix unary operator"));
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn unary_postfix_prec(tok: &TokenKind) -> Result<Precedence> {
|
||||
use TokenKind::*;
|
||||
Ok(match tok {
|
||||
Question => 12,
|
||||
Bang => 12,
|
||||
_ => {
|
||||
return error()
|
||||
.reason(format!("{tok} is not a valid postfix unary operator"));
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const PARSER_LOOKAHEAD: usize = 3;
|
||||
|
||||
type TokenIter<I> = crate::Window<PARSER_LOOKAHEAD, Token, I>;
|
||||
|
@ -146,8 +177,7 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
let mut span = self.eat(t::LeftBrace).reason("Expected block")?.1;
|
||||
let mut statements = vec![];
|
||||
loop {
|
||||
let next = self.peek(0)?;
|
||||
span = span + next.1;
|
||||
span = span + self.peek(0)?.1;
|
||||
match self.eat(t::RightBrace) {
|
||||
Ok(t) => {
|
||||
span = span + t.1;
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use crate::semantic::Type;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StatementKind {
|
||||
Mutable {
|
||||
Declaration {
|
||||
name: String,
|
||||
type_: Option<String>,
|
||||
value: Option<Expression>,
|
||||
},
|
||||
Immutable {
|
||||
name: String,
|
||||
type_: Option<String>,
|
||||
type_str: Option<String>,
|
||||
type_actual: Type,
|
||||
value: Expression,
|
||||
mutable: bool,
|
||||
},
|
||||
Assignment {
|
||||
name: String,
|
||||
|
@ -49,44 +48,47 @@ impl<I: Iterator<Item = Token>> Parser<I> {
|
|||
(Token(t::Identifier(name), span2), Ok(Token(t::Colon, span3))) => {
|
||||
self.skip(2);
|
||||
span = span + span2 + span3;
|
||||
// type
|
||||
let type_ = match self.eat(t::Identifier("".into())) {
|
||||
let type_str = match self.eat(t::Identifier("".into())) {
|
||||
Ok(Token(t::Identifier(s), span2)) => {
|
||||
span = span + span2;
|
||||
Some(s)
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
// value
|
||||
match self.eat(t::Equal).or_else(|_| self.eat(t::Colon)) {
|
||||
Ok(Token(t::Colon, span2)) => {
|
||||
span = span + span2;
|
||||
let mutable = if self.eat(t::Equal).is_ok() {
|
||||
true
|
||||
} else if self.eat(t::Colon).is_ok() {
|
||||
false
|
||||
} else {
|
||||
return error()
|
||||
.reason(format!("Declaration of '{}' must be initialized", name))
|
||||
.span(&span);
|
||||
};
|
||||
let value = self
|
||||
.expression(0)
|
||||
.trace_span(span, "while parsing mutable declaration")?;
|
||||
.trace_span(span, "while parsing declaration")?;
|
||||
span = span + value.span;
|
||||
Statement {
|
||||
kind: s::Immutable { name, type_, value },
|
||||
span,
|
||||
}
|
||||
},
|
||||
Ok(Token(t::Equal, span2)) => {
|
||||
span = span + span2;
|
||||
let value = self
|
||||
.expression(0)
|
||||
.trace_span(span, "while parsing mutable declaration")?;
|
||||
span = span + value.span;
|
||||
Statement {
|
||||
kind: s::Mutable {
|
||||
let no_semicolon = if let ExpressionKind::Function { .. } = value.kind {
|
||||
true
|
||||
} else if let ExpressionKind::Struct(_) = value.kind {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let s = Statement {
|
||||
kind: s::Declaration {
|
||||
name,
|
||||
type_,
|
||||
value: Some(value),
|
||||
type_str,
|
||||
type_actual: Type::Ambiguous,
|
||||
value,
|
||||
mutable,
|
||||
},
|
||||
span,
|
||||
};
|
||||
if no_semicolon {
|
||||
return Ok(s);
|
||||
}
|
||||
},
|
||||
_ => return error().reason("Expected expression").span(&span),
|
||||
}
|
||||
s
|
||||
},
|
||||
// Assignment
|
||||
(Token(t::Identifier(name), span2), Ok(Token(t::Equal, span3))) => {
|
||||
|
|
5
src/semantic/mod.rs
Normal file
5
src/semantic/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod primitives;
|
||||
mod types;
|
||||
|
||||
pub use primitives::*;
|
||||
pub use types::*;
|
172
src/semantic/primitives.rs
Normal file
172
src/semantic/primitives.rs
Normal file
|
@ -0,0 +1,172 @@
|
|||
use crate::{BinaryOp, UnaryOp};
|
||||
|
||||
use crate::err::*;
|
||||
|
||||
macro_rules! primitives {
|
||||
( $($i:ident),* ) => {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types, dead_code)]
|
||||
pub enum Primitive {
|
||||
integer_ambiguous,
|
||||
real_ambiguous,
|
||||
$($i,)*
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn from_string(string: &str) -> Option<Self> {
|
||||
match string {
|
||||
$(stringify!{$i} => Some(Self::$i),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Primitive {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Primitive::integer_ambiguous => write!(f, "<ambiguous integer>"),
|
||||
Primitive::real_ambiguous => write!(f, "<ambiguous real>"),
|
||||
$(Primitive::$i => write!(f, stringify!{$i}),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
primitives! {
|
||||
w8, w16, w32, w64, whole,
|
||||
i8, i16, i32, i64, integer,
|
||||
r32, r64, real,
|
||||
boolean,
|
||||
string, glyph
|
||||
}
|
||||
|
||||
macro_rules! selfsame_basic {
|
||||
( $lhs:ident, $op:ident, $rhs:ident, $binop:ident, $i:ident ) => {
|
||||
if ($i == $rhs && $rhs == $lhs) && $op == BinaryOp::$binop {
|
||||
return Ok($i);
|
||||
}
|
||||
};
|
||||
|
||||
( $lhs:ident, $op:ident, $rhs:ident; $($i:ident),* ) => {
|
||||
$(
|
||||
selfsame_basic!($lhs, $op, $rhs, Plus, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Minus, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Star, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Slash, $i);
|
||||
)*
|
||||
logical!($lhs, $op, $rhs; $($i),*);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! logical {
|
||||
( $lhs:ident, $op:ident, $rhs:ident; $($i:ident),* ) => {
|
||||
$(
|
||||
selfsame_basic!($lhs, $op, $rhs, And, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Nand, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Xor, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Xnor, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Or, $i);
|
||||
selfsame_basic!($lhs, $op, $rhs, Nor, $i);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! comparison {
|
||||
( $lhs:ident, $op:ident, $rhs:ident, $binop:ident, $i:ident ) => {
|
||||
if ($i == $rhs && $rhs == $lhs) && $op == BinaryOp::$binop {
|
||||
return Ok(boolean);
|
||||
}
|
||||
};
|
||||
|
||||
( $lhs:ident, $op:ident, $rhs:ident; $($i:ident),* ) => {
|
||||
$(
|
||||
comparison!($lhs, $op, $rhs, DoubleEqual, $i);
|
||||
comparison!($lhs, $op, $rhs, BangEqual, $i);
|
||||
comparison!($lhs, $op, $rhs, Less, $i);
|
||||
comparison!($lhs, $op, $rhs, LessEqual, $i);
|
||||
comparison!($lhs, $op, $rhs, Greater, $i);
|
||||
comparison!($lhs, $op, $rhs, GreaterEqual, $i);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn coerce_ambiguous(
|
||||
lhs: Primitive,
|
||||
rhs: Primitive,
|
||||
) -> (Primitive, Primitive) {
|
||||
use Primitive::*;
|
||||
let is_int = |i| {
|
||||
if let i8 | i16 | i32 | i64 | integer | integer_ambiguous = i {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
let is_real = |r| {
|
||||
if let r32 | r64 | real | real_ambiguous = r {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
// Ambiguous integer coercion
|
||||
if lhs == integer_ambiguous && is_int(rhs) {
|
||||
return (rhs, rhs);
|
||||
} else if rhs == integer_ambiguous && is_int(lhs) {
|
||||
return (lhs, lhs);
|
||||
}
|
||||
// Ambiguous real coercion
|
||||
if lhs == real_ambiguous && is_real(rhs) {
|
||||
return (rhs, rhs);
|
||||
} else if rhs == real_ambiguous && is_real(lhs) {
|
||||
return (lhs, lhs);
|
||||
}
|
||||
return (lhs, rhs);
|
||||
}
|
||||
|
||||
pub fn binary_op(
|
||||
lhs: Primitive,
|
||||
op: BinaryOp,
|
||||
rhs: Primitive,
|
||||
) -> Result<Primitive> {
|
||||
use Primitive::*;
|
||||
let (lhs, rhs) = Primitive::coerce_ambiguous(lhs, rhs);
|
||||
selfsame_basic! {
|
||||
lhs, op, rhs; w8, w16, w32, w64, i8, i16, i32, i64,
|
||||
integer, integer_ambiguous, real, real_ambiguous
|
||||
}
|
||||
logical! { lhs, op, rhs; boolean }
|
||||
comparison! {
|
||||
lhs, op, rhs; w8, w16, w32, w64, i8, i16, i32, i64,
|
||||
integer, integer_ambiguous, real, real_ambiguous,
|
||||
boolean, string
|
||||
}
|
||||
error().reason(format!(
|
||||
"Binary {} is not defined for {} and {}",
|
||||
op, lhs, rhs
|
||||
))
|
||||
}
|
||||
|
||||
pub fn unary_op(op: UnaryOp, child: Primitive) -> Result<Primitive> {
|
||||
use Primitive::*;
|
||||
use UnaryOp::*;
|
||||
let e =
|
||||
error().reason(format!("Unary {} is not defined for {}", op, child));
|
||||
match op {
|
||||
Minus => match child {
|
||||
boolean | string | glyph | w8 | w16 | w32 | w64 => e,
|
||||
_ => Ok(child),
|
||||
},
|
||||
Plus => match child {
|
||||
boolean | string | glyph => e,
|
||||
_ => Ok(child),
|
||||
},
|
||||
Not => match child {
|
||||
string | glyph => e,
|
||||
_ => Ok(child),
|
||||
},
|
||||
_ => error().reason(format!("Unary {} is not implemented (yet)", op)),
|
||||
}
|
||||
}
|
||||
}
|
522
src/semantic/types.rs
Normal file
522
src/semantic/types.rs
Normal file
|
@ -0,0 +1,522 @@
|
|||
use crate::{
|
||||
BinaryOp, Expression, ExpressionKind, Immediate, Parameter, Statement,
|
||||
StatementKind, UnaryOp,
|
||||
};
|
||||
|
||||
use super::primitives::*;
|
||||
use crate::err::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Type {
|
||||
Ambiguous,
|
||||
Nothing,
|
||||
Prim(Primitive),
|
||||
Struct(Vec<Parameter>),
|
||||
Function {
|
||||
params: Vec<Type>,
|
||||
returns: Box<Type>,
|
||||
},
|
||||
}
|
||||
|
||||
impl PartialEq for Type {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use Type::*;
|
||||
match (self, other) {
|
||||
(Ambiguous, Ambiguous) => true,
|
||||
(Prim(p1), Prim(p2)) if p1 == p2 => true,
|
||||
(Struct(p1), Struct(p2)) => p1
|
||||
.iter()
|
||||
.map(|p| p.type_actual.clone())
|
||||
.eq(p2.iter().map(|p| p.type_actual.clone())),
|
||||
(
|
||||
Function {
|
||||
params: p1,
|
||||
returns: r1,
|
||||
},
|
||||
Function {
|
||||
params: p2,
|
||||
returns: r2,
|
||||
},
|
||||
) => p1.iter().eq(p2.iter()) && r1 == r2,
|
||||
(Nothing, Nothing) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn from_string(value: &str) -> Self {
|
||||
if let Some(p) = Primitive::from_string(value) {
|
||||
Type::Prim(p)
|
||||
} else {
|
||||
Type::Ambiguous
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binary_op(lhs: &Type, op: BinaryOp, rhs: &Type) -> Result<Type> {
|
||||
use Type as t;
|
||||
let e = error().reason(format!(
|
||||
"Binary {op} is not defined for {lhs:?} and {rhs:?}",
|
||||
));
|
||||
match (lhs, rhs) {
|
||||
(t::Prim(a), t::Prim(b)) => {
|
||||
let p = Primitive::binary_op(*a, op, *b)?;
|
||||
Ok(t::Prim(p))
|
||||
},
|
||||
_ => e,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unary_op(op: UnaryOp, child: &Type) -> Result<Type> {
|
||||
use Type as t;
|
||||
if let t::Prim(p) = child {
|
||||
let p = Primitive::unary_op(op, *p)?;
|
||||
Ok(t::Prim(p))
|
||||
} else {
|
||||
error().reason(format!("Unary {op} is not defined for {child:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce(expect: &Type, actual: &Type) -> Option<Type> {
|
||||
use Primitive as p;
|
||||
use Type::*;
|
||||
match (expect, actual) {
|
||||
(Ambiguous, Ambiguous) => None,
|
||||
(Ambiguous, Prim(p::integer_ambiguous)) => Some(Prim(p::integer)),
|
||||
(Ambiguous, Prim(p::real_ambiguous)) => Some(Prim(p::real)),
|
||||
(Ambiguous, t) => Some(t.clone()),
|
||||
(Prim(p1), Prim(p2)) => {
|
||||
let (p1, p2) = Primitive::coerce_ambiguous(*p1, *p2);
|
||||
if p1 != p2 { None } else { Some(Type::Prim(p1)) }
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Symbol {
|
||||
Var(String, Type, bool),
|
||||
Type(String, Type),
|
||||
BlockStart,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SymbolTable {
|
||||
syms: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
fn define_var(&mut self, name: String, type_: Type, mutable: bool) {
|
||||
self.syms.push(Symbol::Var(name, type_, mutable));
|
||||
}
|
||||
|
||||
fn define_type(&mut self, name: String, type_: Type) {
|
||||
self.syms.push(Symbol::Type(name, type_));
|
||||
}
|
||||
|
||||
fn start_block(&mut self) {
|
||||
self.syms.push(Symbol::BlockStart);
|
||||
}
|
||||
|
||||
fn end_block(&mut self) {
|
||||
while !self.syms.is_empty() {
|
||||
if let Some(Symbol::BlockStart) = self.syms.pop() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
unreachable!("Tried to exit global scope in symbol table")
|
||||
}
|
||||
|
||||
fn get_var(&self, name: &str) -> Result<(Type, bool)> {
|
||||
for s in self.syms.iter().rev() {
|
||||
if let Symbol::Var(name2, type_, mutable) = s {
|
||||
if name == name2 {
|
||||
return Ok((type_.clone(), *mutable));
|
||||
}
|
||||
}
|
||||
}
|
||||
error().reason(format!("Identifier {name} is not defined"))
|
||||
}
|
||||
|
||||
fn get_type(&self, name: &str) -> Result<Type> {
|
||||
for s in self.syms.iter().rev() {
|
||||
if let Symbol::Type(name2, t) = s {
|
||||
if name == name2 {
|
||||
return Ok(t.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(p) = Primitive::from_string(name) {
|
||||
return Ok(Type::Prim(p));
|
||||
}
|
||||
error().reason(format!("Type {name} is not defined"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn typecheck(program: Vec<Statement>) -> Vec<Statement> {
|
||||
use StatementKind as s;
|
||||
let mut table = SymbolTable { syms: vec![] };
|
||||
let mut ret = vec![];
|
||||
for s in program {
|
||||
let span = s.span;
|
||||
ret.push(match statement(s.into(), &mut table) {
|
||||
Ok(s) => *s,
|
||||
Err(e) => Statement {
|
||||
kind: s::Error(e),
|
||||
span,
|
||||
},
|
||||
})
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn statement(
|
||||
mut stmt: Box<Statement>,
|
||||
table: &mut SymbolTable,
|
||||
) -> Result<Box<Statement>> {
|
||||
use Primitive as p;
|
||||
use StatementKind as s;
|
||||
match stmt.kind {
|
||||
s::Declaration {
|
||||
name,
|
||||
type_str,
|
||||
value,
|
||||
mutable,
|
||||
..
|
||||
} => {
|
||||
let type_expect = match type_str {
|
||||
Some(ref s) => table.get_type(s).span(&stmt.span)?,
|
||||
None => Type::Ambiguous,
|
||||
};
|
||||
let value = expression(value.into(), table)?;
|
||||
let type_actual =
|
||||
Type::coerce(&type_expect, &value.type_).reason(format!(
|
||||
"Expected type '{:?}', found type '{:?}'",
|
||||
type_expect, value.type_
|
||||
))?;
|
||||
// Check that structs are const
|
||||
if let Type::Struct(_) = type_actual {
|
||||
if mutable {
|
||||
return error()
|
||||
.reason("Struct declarations must be immutable")
|
||||
.span(&stmt.span);
|
||||
}
|
||||
table.define_type(name.clone(), type_actual.clone());
|
||||
}
|
||||
// Check that functions are const
|
||||
else if let Type::Function { .. } = type_actual {
|
||||
if mutable {
|
||||
return error()
|
||||
.reason("Function declarations must be immutable")
|
||||
.span(&stmt.span);
|
||||
}
|
||||
table.define_var(name.clone(), type_actual.clone(), false);
|
||||
} else {
|
||||
table.define_var(name.clone(), type_actual.clone(), mutable);
|
||||
}
|
||||
stmt.kind = s::Declaration {
|
||||
name,
|
||||
type_str,
|
||||
type_actual,
|
||||
value: *value,
|
||||
mutable,
|
||||
};
|
||||
},
|
||||
s::Assignment { name, value } => {
|
||||
let (type_, mutable) = table.get_var(&name).span(&stmt.span)?;
|
||||
// Check that it is mutable
|
||||
if !mutable {
|
||||
return error()
|
||||
.reason(format!("Cannot assign to immutable '{}'", name))
|
||||
.span(&stmt.span);
|
||||
}
|
||||
let value = *expression(value.into(), table)?;
|
||||
// Check for correct type
|
||||
if type_ != value.type_ {
|
||||
return error().reason(format!(
|
||||
"Attempted to assign '{:?}' to '{type_:?}'",
|
||||
value.type_
|
||||
));
|
||||
}
|
||||
stmt.kind = s::Assignment { name, value };
|
||||
},
|
||||
s::If {
|
||||
predicate,
|
||||
block,
|
||||
else_,
|
||||
} => {
|
||||
table.start_block();
|
||||
let predicate = *expression(predicate.into(), table)?;
|
||||
if predicate.type_ != Type::Prim(p::boolean) {
|
||||
return error()
|
||||
.reason(format!(
|
||||
"Predicate of if statement must be a boolean, found {:?}",
|
||||
predicate.type_
|
||||
))
|
||||
.span(&predicate.span);
|
||||
}
|
||||
let mut new_block = vec![];
|
||||
for stmt in block {
|
||||
new_block.push(*statement(stmt.into(), table)?);
|
||||
}
|
||||
let else_ = if let Some(else_) = else_ {
|
||||
Some(statement(else_, table)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
stmt.kind = s::If {
|
||||
predicate,
|
||||
block: new_block,
|
||||
else_,
|
||||
};
|
||||
table.end_block();
|
||||
},
|
||||
s::While { predicate, block } => {
|
||||
table.start_block();
|
||||
let predicate = *expression(predicate.into(), table)?;
|
||||
if predicate.type_ != Type::Prim(p::boolean) {
|
||||
return error()
|
||||
.reason(format!(
|
||||
"Predicate of while statement must be a boolean, found {:?}",
|
||||
predicate.type_
|
||||
))
|
||||
.span(&predicate.span);
|
||||
}
|
||||
let mut new_block = vec![];
|
||||
for stmt in block {
|
||||
new_block.push(*statement(stmt.into(), table)?);
|
||||
}
|
||||
stmt.kind = s::While {
|
||||
predicate,
|
||||
block: new_block,
|
||||
};
|
||||
table.end_block();
|
||||
},
|
||||
s::Print(e) => {
|
||||
stmt.kind = s::Print(*expression(e.into(), table)?);
|
||||
},
|
||||
s::Expression(mut e) => {
|
||||
use ExpressionKind as e;
|
||||
let is_func = if let e::Function { params, .. } = &mut e.kind {
|
||||
// TODO: start/end function instead
|
||||
table.start_block();
|
||||
for p in params {
|
||||
p.type_actual = table.get_type(&p.type_str)?;
|
||||
table.define_var(p.name.clone(), p.type_actual.clone(), false);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
stmt.kind = s::Expression(*expression(e.into(), table)?);
|
||||
if is_func {
|
||||
table.end_block();
|
||||
}
|
||||
},
|
||||
s::Block(block) => {
|
||||
table.start_block();
|
||||
let mut new_block = vec![];
|
||||
for stmt in block {
|
||||
new_block.push(*statement(stmt.into(), table)?);
|
||||
}
|
||||
stmt.kind = s::Block(new_block);
|
||||
table.end_block();
|
||||
},
|
||||
s::Error(e) => return Err(e),
|
||||
}
|
||||
Ok(stmt)
|
||||
}
|
||||
|
||||
fn expression(
|
||||
mut expr: Box<Expression>,
|
||||
table: &SymbolTable,
|
||||
) -> Result<Box<Expression>> {
|
||||
use ExpressionKind as e;
|
||||
use Immediate as i;
|
||||
use Primitive as p;
|
||||
let type_ = match expr.kind {
|
||||
e::Immediate(ref i) => Type::Prim(match i {
|
||||
i::Integer(_) => p::integer_ambiguous,
|
||||
i::Real(_) => p::real_ambiguous,
|
||||
i::String(_) => p::string,
|
||||
i::Boolean(_) => p::boolean,
|
||||
}),
|
||||
e::Identifier(ref i) => table.get_var(i)?.0,
|
||||
e::Binary { op, left, right } => {
|
||||
let left = expression(left, table)?;
|
||||
let right = expression(right, table)?;
|
||||
let type_ = Type::binary_op(&left.type_, op, &right.type_)?;
|
||||
expr.kind = e::Binary { left, right, op };
|
||||
type_
|
||||
},
|
||||
e::Unary { op, child } => {
|
||||
let child = expression(child, table)?;
|
||||
let type_ = Type::unary_op(op, &child.type_)?;
|
||||
expr.kind = e::Unary { child, op };
|
||||
type_
|
||||
},
|
||||
e::Parenthesis(inner) => {
|
||||
let inner = expression(inner, table)?;
|
||||
let type_ = inner.type_.clone();
|
||||
expr.kind = e::Parenthesis(inner);
|
||||
type_
|
||||
},
|
||||
e::Function {
|
||||
mut params,
|
||||
returns_str,
|
||||
mut returns_actual,
|
||||
body,
|
||||
} => {
|
||||
for p in &mut params {
|
||||
p.type_actual = table.get_type(&p.type_str).span(&expr.span)?;
|
||||
}
|
||||
returns_actual = match &returns_str {
|
||||
Some(s) => table.get_type(s).span(&expr.span)?,
|
||||
None => Type::Nothing,
|
||||
};
|
||||
expr.kind = e::Function {
|
||||
params: params.clone(),
|
||||
returns_str,
|
||||
returns_actual: returns_actual.clone(),
|
||||
body,
|
||||
};
|
||||
Type::Function {
|
||||
params: params.into_iter().map(|p| p.type_actual).collect(),
|
||||
returns: returns_actual.into(),
|
||||
}
|
||||
},
|
||||
e::Struct(mut params) => {
|
||||
for p in &mut params {
|
||||
p.type_actual = table.get_type(&p.type_str).span(&expr.span)?;
|
||||
}
|
||||
expr.kind = e::Struct(params.clone());
|
||||
Type::Struct(params)
|
||||
},
|
||||
e::StructLiteral { name, mut args } => {
|
||||
let type_ = table.get_type(&name).span(&expr.span)?;
|
||||
let Type::Struct(params) = type_ else {
|
||||
return error().reason(format!(
|
||||
"Cannot construct type {:?} as struct literal",
|
||||
type_
|
||||
));
|
||||
};
|
||||
if args.len() != params.len() {
|
||||
return error().reason(format!(
|
||||
"Incorrect number of parameters for struct '{}', expected {} and \
|
||||
found {}",
|
||||
name,
|
||||
params.len(),
|
||||
args.len()
|
||||
));
|
||||
}
|
||||
// TODO out of order params
|
||||
let mut new_args = vec![];
|
||||
for (
|
||||
(argname, argexpr),
|
||||
Parameter {
|
||||
name: pname,
|
||||
type_actual: ptype,
|
||||
..
|
||||
},
|
||||
) in args.iter().zip(params.iter())
|
||||
{
|
||||
if argname != pname {
|
||||
return error()
|
||||
.reason(format!(
|
||||
"In struct literal, expected parameter '{pname}', found \
|
||||
'{argname}'"
|
||||
))
|
||||
.span(&argexpr.span);
|
||||
}
|
||||
let argspan = argexpr.span;
|
||||
let arg = *expression(argexpr.clone().into(), table)
|
||||
.trace_span(expr.span, "while parsing struct literal")?;
|
||||
if &arg.type_ != ptype {
|
||||
return error()
|
||||
.reason(format!(
|
||||
"In struct literal, expected type '{ptype:?}', found '{:?}",
|
||||
arg.type_,
|
||||
))
|
||||
.span(&argspan);
|
||||
}
|
||||
new_args.push((argname.clone(), arg));
|
||||
}
|
||||
expr.kind = e::StructLiteral {
|
||||
name,
|
||||
args: new_args,
|
||||
};
|
||||
Type::Struct(params)
|
||||
},
|
||||
e::Call { callee, args } => {
|
||||
let callee = expression(callee, table)?;
|
||||
// Check that this is actually a function
|
||||
let Type::Function {
|
||||
ref params,
|
||||
ref returns,
|
||||
} = callee.type_
|
||||
else {
|
||||
return error()
|
||||
.reason(format!("Cannot call type {:?}", callee.type_))
|
||||
.span(&callee.span);
|
||||
};
|
||||
// Check for correct number of args
|
||||
if params.len() != args.len() {
|
||||
return error()
|
||||
.reason(format!(
|
||||
"Wrong number of arguments, function expects {}",
|
||||
params.len()
|
||||
))
|
||||
.span(&callee.span);
|
||||
}
|
||||
// Check for correct arg types
|
||||
for (expect, actual) in params.iter().zip(args.iter()) {
|
||||
if *expect != actual.type_ {
|
||||
return error()
|
||||
.reason(format!(
|
||||
"Expected type {expect:?}, found {:?}",
|
||||
actual.type_
|
||||
))
|
||||
.span(&actual.span);
|
||||
}
|
||||
}
|
||||
let returns = *returns.clone();
|
||||
expr.kind = e::Call { callee, args };
|
||||
returns
|
||||
},
|
||||
e::Field { namespace, field } => {
|
||||
let namespace = expression(namespace, table)?;
|
||||
// Check that namespace is struct
|
||||
// TODO: fields in other types
|
||||
let Type::Struct(ref params) = namespace.type_ else {
|
||||
return error()
|
||||
.reason(format!("Type {:?} does not have fields", namespace.type_))
|
||||
.span(&namespace.span);
|
||||
};
|
||||
// Check that field is identifier
|
||||
// TODO: tuple fields?
|
||||
let e::Identifier(ref name) = field.kind else {
|
||||
return error()
|
||||
.reason("Field must be an identifier")
|
||||
.span(&field.span);
|
||||
};
|
||||
let mut type_ = None;
|
||||
for p in params {
|
||||
if &p.name == name {
|
||||
type_ = Some(p.type_actual.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
let type_ = type_
|
||||
.reason(format!(
|
||||
"Type {:?} does not contain field {}",
|
||||
namespace, name
|
||||
))
|
||||
.span(&field.span)?;
|
||||
expr.kind = e::Field { namespace, field };
|
||||
type_
|
||||
},
|
||||
};
|
||||
expr.type_ = type_;
|
||||
Ok(expr)
|
||||
}
|
|
@ -577,7 +577,9 @@ fn bake_string(s: &str) -> Result<String> {
|
|||
};
|
||||
a().reason("Found invalid Unicode (\\uXXXX) escape sequence")?
|
||||
},
|
||||
_ => return Err(Diagnostic::new("Found invalid escape sequence")),
|
||||
_ => {
|
||||
return Err(Diagnostic::new("Found invalid escape sequence", None));
|
||||
},
|
||||
}),
|
||||
// Unremarkable character
|
||||
Some(c) => baked.push(c),
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use crate::err::*;
|
||||
/*
|
||||
use crate::{err::*, BinaryOp, UnaryOp};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
Expression, ExpressionKind, Statement, StatementKind, Token, TokenKind,
|
||||
};
|
||||
use crate::{Expression, ExpressionKind, Statement, StatementKind};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Value {
|
||||
|
@ -106,31 +105,31 @@ impl<I: Iterator<Item = Statement>> Interpreter<I> {
|
|||
|
||||
fn evaluate_unary(
|
||||
&mut self,
|
||||
token: TokenKind,
|
||||
token: UnaryOp,
|
||||
child: Expression,
|
||||
) -> Result<Value> {
|
||||
use TokenKind as t;
|
||||
use UnaryOp as u;
|
||||
use Value as v;
|
||||
let val = self.evaluate(child)?;
|
||||
Ok(match val {
|
||||
v::Integer(i) => v::Integer(match token {
|
||||
t::Plus => i,
|
||||
t::Minus => -i,
|
||||
u::Plus => i,
|
||||
u::Minus => -i,
|
||||
_ => {
|
||||
return error()
|
||||
.reason(format!("Unary {token:?} is undefined for integers"));
|
||||
},
|
||||
}),
|
||||
v::Real(r) => v::Real(match token {
|
||||
t::Plus => r,
|
||||
t::Minus => -r,
|
||||
u::Plus => r,
|
||||
u::Minus => -r,
|
||||
_ => {
|
||||
return error()
|
||||
.reason(format!("Unary {token:?} is undefined for reals"));
|
||||
},
|
||||
}),
|
||||
v::Boolean(b) => v::Boolean(match token {
|
||||
t::Not => !b,
|
||||
u::Not => !b,
|
||||
_ => {
|
||||
return error()
|
||||
.reason(format!("Unary {token:?} is undefined for booleans"));
|
||||
|
@ -145,36 +144,36 @@ impl<I: Iterator<Item = Statement>> Interpreter<I> {
|
|||
|
||||
fn evaluate_binary(
|
||||
&mut self,
|
||||
token: TokenKind,
|
||||
token: BinaryOp,
|
||||
left: Expression,
|
||||
right: Expression,
|
||||
) -> Result<Value> {
|
||||
use TokenKind as t;
|
||||
use BinaryOp as b;
|
||||
use Value::*;
|
||||
let left = self.evaluate(left)?;
|
||||
let right = self.evaluate(right)?;
|
||||
Ok(match (left.clone(), right.clone()) {
|
||||
(Integer(l), Integer(r)) => match token {
|
||||
t::Plus => Integer(l + r),
|
||||
t::Minus => Integer(l - r),
|
||||
t::Star => Integer(l * r),
|
||||
t::Slash => Integer(l / r),
|
||||
t::Percent => Integer(l % r),
|
||||
t::DoubleEqual => Boolean(l == r),
|
||||
t::Less => Boolean(l < r),
|
||||
t::Greater => Boolean(l > r),
|
||||
t::LessEqual => Boolean(l <= r),
|
||||
t::GreaterEqual => Boolean(l >= r),
|
||||
b::Plus => Integer(l + r),
|
||||
b::Minus => Integer(l - r),
|
||||
b::Star => Integer(l * r),
|
||||
b::Slash => Integer(l / r),
|
||||
b::Percent => Integer(l % r),
|
||||
b::DoubleEqual => Boolean(l == r),
|
||||
b::Less => Boolean(l < r),
|
||||
b::Greater => Boolean(l > r),
|
||||
b::LessEqual => Boolean(l <= r),
|
||||
b::GreaterEqual => Boolean(l >= r),
|
||||
t => {
|
||||
return error()
|
||||
.reason(format!("Binary {t:?} is undefined for integers"));
|
||||
},
|
||||
},
|
||||
(Real(l), Real(r)) => Real(match token {
|
||||
t::Plus => l + r,
|
||||
t::Minus => l - r,
|
||||
t::Star => l * r,
|
||||
t::Slash => l / r,
|
||||
b::Plus => l + r,
|
||||
b::Minus => l - r,
|
||||
b::Star => l * r,
|
||||
b::Slash => l / r,
|
||||
t => {
|
||||
return error()
|
||||
.reason(format!("Binary {t:?} is undefined for reals"));
|
||||
|
@ -190,17 +189,16 @@ impl<I: Iterator<Item = Statement>> Interpreter<I> {
|
|||
}
|
||||
|
||||
fn evaluate(&mut self, expr: Expression) -> Result<Value> {
|
||||
use crate::Immediate as im;
|
||||
use ExpressionKind as e;
|
||||
match expr.kind {
|
||||
e::Integer(i) => Ok(Value::Integer(i)),
|
||||
e::Real(r) => Ok(Value::Real(r)),
|
||||
e::String(s) => Ok(Value::String(s)),
|
||||
e::Boolean(b) => Ok(Value::Boolean(b)),
|
||||
e::Immediate(im::Integer(i)) => Ok(Value::Integer(i)),
|
||||
e::Immediate(im::Real(r)) => Ok(Value::Real(r)),
|
||||
e::Immediate(im::String(s)) => Ok(Value::String(s)),
|
||||
e::Immediate(im::Boolean(b)) => Ok(Value::Boolean(b)),
|
||||
e::Identifier(i) => self.scope.access(i),
|
||||
e::Binary { token, left, right } => {
|
||||
self.evaluate_binary(token, *left, *right)
|
||||
},
|
||||
e::Unary { token, child } => self.evaluate_unary(token, *child),
|
||||
e::Binary { op, left, right } => self.evaluate_binary(op, *left, *right),
|
||||
e::Unary { op, child } => self.evaluate_unary(op, *child),
|
||||
e::Parenthesis(e) => self.evaluate(*e),
|
||||
e::Call { callee, args } => todo!(),
|
||||
e::Field { namespace, field } => todo!(),
|
||||
|
@ -209,6 +207,7 @@ impl<I: Iterator<Item = Statement>> Interpreter<I> {
|
|||
returns,
|
||||
body,
|
||||
} => todo!(),
|
||||
e::Struct(_) => todo!(),
|
||||
}
|
||||
.span(&expr.span)
|
||||
}
|
||||
|
@ -216,14 +215,13 @@ impl<I: Iterator<Item = Statement>> Interpreter<I> {
|
|||
pub fn execute(&mut self, statement: Statement) -> Result<()> {
|
||||
use StatementKind as s;
|
||||
match statement.kind {
|
||||
s::Mutable { name, value, .. } => {
|
||||
self.scope.declare(name.clone())?;
|
||||
if let Some(value) = value {
|
||||
let value = self.evaluate(value)?;
|
||||
self.scope.assign(name, value)?;
|
||||
}
|
||||
},
|
||||
s::Immutable { name, value, .. } => {
|
||||
s::Declaration {
|
||||
name,
|
||||
type_str,
|
||||
type_actual,
|
||||
value,
|
||||
mutable,
|
||||
} => {
|
||||
self.scope.declare(name.clone())?;
|
||||
let value = self.evaluate(value)?;
|
||||
self.scope.assign(name, value)?;
|
||||
|
@ -304,3 +302,4 @@ impl<I: Iterator<Item = Statement>> Interpreter<I> {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
136
src/types.rs
136
src/types.rs
|
@ -1,136 +0,0 @@
|
|||
use crate::Expression;
|
||||
|
||||
use super::err::*;
|
||||
|
||||
macro_rules! primitives {
|
||||
( $($i:ident),* ) => {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types, dead_code)]
|
||||
pub enum Primitive {
|
||||
whole_ambiguous,
|
||||
integer_ambiguous,
|
||||
real_ambiguous,
|
||||
$($i,)*
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn from_string(string: &'static str) -> Option<Self> {
|
||||
match string {
|
||||
$(stringify!{$i} => Some(Self::$i),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Primitive {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Primitive::whole_ambiguous => write!(f, "<ambiguous whole>"),
|
||||
Primitive::integer_ambiguous => write!(f, "<ambiguous integer>"),
|
||||
Primitive::real_ambiguous => write!(f, "<ambiguous real>"),
|
||||
$(Primitive::$i => write!(f, stringify!{$i}),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
primitives! {
|
||||
w8, w16, w32, w64, whole,
|
||||
i8, i16, i32, i64, integer,
|
||||
r32, r64, real,
|
||||
boolean,
|
||||
string, glyph
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn coerce(a: Self, b: Self) -> Result<Self> {
|
||||
use Primitive::*;
|
||||
match (a, b) {
|
||||
// Whole? coerces to any whole or integer
|
||||
(whole_ambiguous, w @ (w8 | w16 | w32 | w64 | i8 | i16 | i32 | i64))
|
||||
| (w @ (w8 | w16 | w32 | w64 | i8 | i16 | i32 | i64), whole_ambiguous) => Ok(w),
|
||||
// Integer? coerces to any integer
|
||||
(integer_ambiguous, i @ (i8 | i16 | i32 | i64))
|
||||
| (i @ (i8 | i16 | i32 | i64), integer_ambiguous) => Ok(i),
|
||||
_ => error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use Primitive as p;
|
||||
|
||||
// Implement math operations for regular types
|
||||
macro_rules! selfsame_op {
|
||||
($trait:ident, $fn:ident, $($i:ident),* ) => {
|
||||
impl std::ops::$trait for Primitive {
|
||||
type Output = Result<Primitive>;
|
||||
fn $fn(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
$((p::$i, p::$i) => Ok(p::$i),)*
|
||||
_ => error()
|
||||
.reason(format!("Operation not defined for primitives {} and {}", self, rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Implement all regular math
|
||||
macro_rules! all_selfsame {
|
||||
($($i:ident),*) => {
|
||||
selfsame_op!(Add, add, $($i),*);
|
||||
selfsame_op!(Sub, sub, $($i),*);
|
||||
selfsame_op!(Mul, mul, $($i),*);
|
||||
selfsame_op!(Div, div, $($i),*);
|
||||
};
|
||||
}
|
||||
|
||||
all_selfsame!(w8, w16, w32, w64, whole, i8, i16, i32, i64, integer, r32, r64, real, string);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Type {
|
||||
Ambiguous,
|
||||
Empty,
|
||||
Primitive(Primitive),
|
||||
Struct(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Symbol {
|
||||
Mutable {
|
||||
name: String,
|
||||
value: Option<Expression>,
|
||||
type_: Type,
|
||||
},
|
||||
Immutable {
|
||||
name: String,
|
||||
value: Expression,
|
||||
type_: Type,
|
||||
},
|
||||
Struct {
|
||||
name: String,
|
||||
},
|
||||
BlockStart,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SymbolTable {
|
||||
stack: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
pub fn define(&mut self, sym: Symbol) {
|
||||
self.stack.push(sym);
|
||||
}
|
||||
pub fn start_block(&mut self) {
|
||||
self.stack.push(Symbol::BlockStart);
|
||||
}
|
||||
|
||||
pub fn end_block(&mut self) {
|
||||
while !self.stack.is_empty() {
|
||||
if let Some(Symbol::BlockStart) = self.stack.pop() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue