first commit
This commit is contained in:
commit
4ff159dd4a
20 changed files with 6529 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
303
Cargo.lock
generated
Normal file
303
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beef"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "logos"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a790d11254054e5dc83902dba85d253ff06ceb0cfafb12be8773435cb9dfb4f4"
|
||||
dependencies = [
|
||||
"logos-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos-codegen"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60337c43a38313b58871f8d5d76872b8e17aa9d51fad494b5e76092c0ce05f5"
|
||||
dependencies = [
|
||||
"beef",
|
||||
"fnv",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos-derive"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d151b2ae667f69e10b8738f5cac0c746faa22b2e15ea7e83b55476afec3767dc"
|
||||
dependencies = [
|
||||
"logos-codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "suicmez"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"indexmap",
|
||||
"logos",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "suicmez"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
indexmap = "2.0"
|
||||
logos = "0.16.0"
|
||||
7
simple_test.sui
Normal file
7
simple_test.sui
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fn simple_add(x: int, y: int) -> int
|
||||
x + y
|
||||
|
||||
fn main() -> int do
|
||||
let result = simple_add(5, 3)
|
||||
result
|
||||
end
|
||||
430
src/ast.rs
Normal file
430
src/ast.rs
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
use crate::typechecker::Type;
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeAnnot {
|
||||
Var(String),
|
||||
Cons(String, Vec<TypeAnnot>),
|
||||
Function(Vec<TypeAnnot>, Box<TypeAnnot>),
|
||||
Tuple(Vec<TypeAnnot>),
|
||||
Array(Box<TypeAnnot>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Span {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub file: String,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn new(range: &Range<usize>, file: String) -> Self {
|
||||
Span {
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
file,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(&self, other: &Span) -> Span {
|
||||
Span {
|
||||
start: self.start.min(other.start),
|
||||
end: self.end.max(other.end),
|
||||
file: self.file.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @attribute
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Attribute {
|
||||
pub name: String,
|
||||
pub args: Vec<AttributeArg>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AttributeArg {
|
||||
Value(String), // some_identifier
|
||||
KeyValue(String, String), // some_key = some_identifier
|
||||
Literal(String), // some literal value
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ASTNode {
|
||||
pub kind: ASTNodeKind,
|
||||
pub span: Span,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ASTNodeKind {
|
||||
Function(Function),
|
||||
Extern(Extern),
|
||||
Load(Load),
|
||||
Struct(Struct),
|
||||
Enum(Enum),
|
||||
Impl(Impl),
|
||||
Trait(Trait),
|
||||
Use(String),
|
||||
}
|
||||
|
||||
// ? implies OPTIONAL here
|
||||
// \( implies the presence of (. same for /)
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// fn name\( (arg: type?,)* \) -> return_type? body
|
||||
pub struct Function {
|
||||
pub name: String,
|
||||
pub parameters: Vec<Parameter>, // type params
|
||||
pub args: Vec<(String, Option<TypeAnnot>)>,
|
||||
pub return_type: Option<TypeAnnot>,
|
||||
pub body: Expr,
|
||||
}
|
||||
|
||||
/// extern name\( type?,* \) -> return_type from library_alias
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Extern {
|
||||
pub name: String,
|
||||
pub args: Vec<TypeAnnot>,
|
||||
pub return_type: TypeAnnot,
|
||||
pub from: String,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// load "library" as alias
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Load {
|
||||
pub library: String,
|
||||
pub alias: String,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// struct name <parameter*>?
|
||||
/// (field_name: field_type,)*
|
||||
/// end
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Struct {
|
||||
pub name: String,
|
||||
pub parameters: Vec<Parameter>, // type parameters
|
||||
pub fields: Vec<Field>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Field {
|
||||
pub name: String,
|
||||
pub field_type: TypeAnnot,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// enum name <parameter*>?
|
||||
/// VariantName\(field_type,\)*
|
||||
/// end
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Enum {
|
||||
pub name: String,
|
||||
pub parameters: Vec<Parameter>, // type parameters
|
||||
pub variants: Vec<Variant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Parameter {
|
||||
pub name: String,
|
||||
pub bounds: Vec<String>, // trait bounds
|
||||
pub kind: Option<Kind>, // for HKTs
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Kind {
|
||||
Star, // *
|
||||
Arrow(Box<Kind>, Box<Kind>), // k1 -> k2
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Variant {
|
||||
pub name: String,
|
||||
pub fields: Vec<TypeAnnot>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// impl TypeName <parameter*>? (: TraitName)?
|
||||
/// functions*
|
||||
/// end
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Impl {
|
||||
pub target: String,
|
||||
pub trait_name: Option<String>,
|
||||
pub methods: Vec<Function>,
|
||||
}
|
||||
|
||||
/// trait TraitName <parameter*>?
|
||||
/// function_signatures*
|
||||
/// end
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Trait {
|
||||
pub name: String,
|
||||
pub methods: Vec<FunctionSignature>,
|
||||
|
||||
pub parameters: Vec<Parameter>,
|
||||
pub associated_types: Vec<AssociatedType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AssociatedType {
|
||||
pub name: String,
|
||||
pub bounds: Vec<String>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionSignature {
|
||||
pub name: String,
|
||||
pub params: Vec<Parameter>,
|
||||
pub return_type: TypeAnnot,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Expr {
|
||||
pub kind: ExprKind,
|
||||
pub span: Span,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExprKind {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Array(Vec<Expr>),
|
||||
Tuple(Vec<Expr>),
|
||||
|
||||
StructLit(String, Vec<(String, Expr)>), // Name { a: expr, b: expr }
|
||||
EnumLit(String, String, Vec<Expr>), // Name::Variant(expr, expr)
|
||||
|
||||
Variable(String),
|
||||
|
||||
Call(Box<Expr>, Vec<Expr>),
|
||||
Index(Box<Expr>, Box<Expr>),
|
||||
Dot(Box<Expr>, String),
|
||||
EarlyReturn(Option<Box<Expr>>), // eg: myresultoroption?
|
||||
OptionalChain(Option<Box<Expr>>, String), // a?.b
|
||||
|
||||
Lambda(Vec<(String, Option<TypeAnnot>)>, Box<Expr>), // lambda (arg, arg: optionalty, ...) body
|
||||
Let(String, BindingKind, Option<TypeAnnot>, Box<Expr>), // no patterns for now
|
||||
Assign(Box<Expr>, Box<Expr>), // NOTE: check for valid lvalue during typechecking
|
||||
Cast(Box<Expr>, TypeAnnot),
|
||||
|
||||
If(Box<Expr>, Box<Expr>, Option<Box<Expr>>), // if cond expr (else expr)?
|
||||
Match(Box<Expr>, Vec<(Pattern, Expr)>), // match expr pattern => expr* end
|
||||
While(Box<Expr>, Box<Expr>), // while cond expr
|
||||
|
||||
For(String, Box<Expr>, Box<Expr>), // for i in expr body
|
||||
Range(Box<Expr>, Box<Expr>), // 0..10
|
||||
|
||||
Do(Vec<Expr>), // do expr* end
|
||||
BinOp(Box<Expr>, BinOp, Box<Expr>),
|
||||
UnOp(UnOp, Box<Expr>),
|
||||
|
||||
Return(Option<Box<Expr>>),
|
||||
Break,
|
||||
Continue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BindingKind {
|
||||
Default, // immutable but infinite usages
|
||||
Mutable, // mutable but infinite usages
|
||||
Affine,
|
||||
Linear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BinOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
And,
|
||||
Or,
|
||||
Eq,
|
||||
Neq,
|
||||
Lt,
|
||||
Gt,
|
||||
Leq,
|
||||
Geq,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UnOp {
|
||||
Neg,
|
||||
Not,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pattern {
|
||||
pub kind: PatternKind,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PatternKind {
|
||||
Wildcard, // _
|
||||
Variable(String),
|
||||
Literal(String),
|
||||
Tuple(Vec<Pattern>),
|
||||
Struct(String, Vec<(String, Pattern)>),
|
||||
Enum(String, String, Vec<Pattern>),
|
||||
Range(i64, i64),
|
||||
}
|
||||
|
||||
// Typed variants
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedASTNode {
|
||||
pub kind: TypedASTNodeKind,
|
||||
pub span: Span,
|
||||
pub attributes: Vec<Attribute>,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypedASTNodeKind {
|
||||
Function(TypedFunction),
|
||||
Extern(TypedExtern),
|
||||
Load(TypedLoad),
|
||||
Struct(TypedStruct),
|
||||
Enum(TypedEnum),
|
||||
Impl(TypedImpl),
|
||||
Trait(TypedTrait),
|
||||
Use(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedFunction {
|
||||
pub name: String,
|
||||
pub parameters: Vec<Parameter>,
|
||||
pub args: Vec<(String, Option<TypeAnnot>)>,
|
||||
pub return_type: Option<TypeAnnot>,
|
||||
pub body: TypedExpr,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedExtern {
|
||||
pub name: String,
|
||||
pub args: Vec<TypeAnnot>,
|
||||
pub return_type: TypeAnnot,
|
||||
pub from: String,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedLoad {
|
||||
pub library: String,
|
||||
pub alias: String,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedStruct {
|
||||
pub name: String,
|
||||
pub parameters: Vec<Parameter>,
|
||||
pub fields: Vec<TypedField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedField {
|
||||
pub name: String,
|
||||
pub field_type: TypeAnnot,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedEnum {
|
||||
pub name: String,
|
||||
pub parameters: Vec<Parameter>,
|
||||
pub variants: Vec<TypedVariant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedVariant {
|
||||
pub name: String,
|
||||
pub fields: Vec<TypeAnnot>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedImpl {
|
||||
pub target: String,
|
||||
pub trait_name: Option<String>,
|
||||
pub methods: Vec<TypedFunction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedTrait {
|
||||
pub name: String,
|
||||
pub methods: Vec<FunctionSignature>,
|
||||
pub parameters: Vec<Parameter>,
|
||||
pub associated_types: Vec<AssociatedType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedExpr {
|
||||
pub kind: TypedExprKind,
|
||||
pub span: Span,
|
||||
pub attributes: Vec<Attribute>,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypedExprKind {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Array(Vec<TypedExpr>),
|
||||
Tuple(Vec<TypedExpr>),
|
||||
StructLit(String, Vec<(String, TypedExpr)>),
|
||||
EnumLit(String, String, Vec<TypedExpr>),
|
||||
Variable(String),
|
||||
Call(Box<TypedExpr>, Vec<TypedExpr>),
|
||||
Index(Box<TypedExpr>, Box<TypedExpr>),
|
||||
Dot(Box<TypedExpr>, String),
|
||||
EarlyReturn(Option<Box<TypedExpr>>),
|
||||
OptionalChain(Option<Box<TypedExpr>>, String),
|
||||
Lambda(Vec<(String, Option<TypeAnnot>)>, Box<TypedExpr>),
|
||||
Let(String, BindingKind, Option<TypeAnnot>, Box<TypedExpr>),
|
||||
Assign(Box<TypedExpr>, Box<TypedExpr>),
|
||||
Cast(Box<TypedExpr>, TypeAnnot),
|
||||
If(Box<TypedExpr>, Box<TypedExpr>, Option<Box<TypedExpr>>),
|
||||
Match(Box<TypedExpr>, Vec<(TypedPattern, TypedExpr)>),
|
||||
While(Box<TypedExpr>, Box<TypedExpr>),
|
||||
Do(Vec<TypedExpr>),
|
||||
BinOp(Box<TypedExpr>, BinOp, Box<TypedExpr>),
|
||||
UnOp(UnOp, Box<TypedExpr>),
|
||||
For(String, Box<TypedExpr>, Box<TypedExpr>),
|
||||
Range(Box<TypedExpr>, Box<TypedExpr>),
|
||||
Return(Option<Box<TypedExpr>>),
|
||||
Break,
|
||||
Continue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedPattern {
|
||||
pub kind: TypedPatternKind,
|
||||
pub span: Span,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypedPatternKind {
|
||||
Wildcard,
|
||||
Variable(String),
|
||||
Literal(String),
|
||||
Tuple(Vec<TypedPattern>),
|
||||
Struct(String, Vec<(String, TypedPattern)>),
|
||||
Enum(String, String, Vec<TypedPattern>),
|
||||
}
|
||||
288
src/lexer/mod.rs
Normal file
288
src/lexer/mod.rs
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
use logos::Logos;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
#[derive(Logos, Debug, PartialEq)]
|
||||
#[logos(skip r"[ \n\r\t\f]+")] // Ignore this regex pattern between tokens
|
||||
#[logos(skip r"#(.*)\n")] // Ignore this regex pattern between tokens
|
||||
#[derive(Clone)]
|
||||
pub enum Token {
|
||||
#[regex(r"true|false", |lex| {
|
||||
lex.slice().parse::<bool>().unwrap()
|
||||
})]
|
||||
Bool(bool),
|
||||
|
||||
#[regex(r"0|[1-9][0-9_]*", |lex| {
|
||||
let s = lex.slice().replace("_", "");
|
||||
// We parse to i64 for wider support.
|
||||
s.parse::<i64>().unwrap()
|
||||
}, priority = 4)]
|
||||
Int(i64),
|
||||
|
||||
#[regex(r"(([0-9][0-9_]*\.[0-9_]+|[0-9]*\.[0-9_]+)([eE][+-]?[0-9_]+)?)", |lex| {
|
||||
let s = lex.slice().replace("_", "");
|
||||
s.parse::<f64>().unwrap()
|
||||
}, priority = 3)]
|
||||
Float(f64),
|
||||
|
||||
#[regex(r#""([^"\\]*(\\.[^"\\]*)*)""#, |lex| {
|
||||
let s = lex.slice();
|
||||
s[1..s.len()-1]
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\\\", "\\")
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\t", "\t")
|
||||
})]
|
||||
String(String),
|
||||
|
||||
#[regex(r#"r#"([^"]*)""#, |lex| {
|
||||
let s = lex.slice();
|
||||
// Remove the outer r" and " (s[2..s.len() - 1])
|
||||
s[3..s.len() - 1].to_string()
|
||||
})]
|
||||
RawString(String),
|
||||
|
||||
#[regex(r"[a-zA-Z_][a-zA-Z0-9_]*", |lex|{
|
||||
lex.slice().to_string()
|
||||
})]
|
||||
Variable(String),
|
||||
|
||||
#[token("bool")]
|
||||
KeywordBool,
|
||||
|
||||
#[token("int")]
|
||||
KeywordInt,
|
||||
|
||||
#[token("float")]
|
||||
KeywordFloat,
|
||||
|
||||
#[token("string")]
|
||||
KeywordString,
|
||||
|
||||
#[token("let")]
|
||||
KeywordLet,
|
||||
|
||||
#[token("mut")]
|
||||
KeywordMut,
|
||||
|
||||
#[token("uniq")]
|
||||
KeywordUniq,
|
||||
|
||||
#[token("once")]
|
||||
KeywordOnce,
|
||||
|
||||
#[token("if")]
|
||||
KeywordIf,
|
||||
|
||||
#[token("then")]
|
||||
KeywordThen,
|
||||
|
||||
#[token("else")]
|
||||
KeywordElse,
|
||||
|
||||
#[token("fn")]
|
||||
KeywordFn,
|
||||
|
||||
#[token("lambda")]
|
||||
KeywordLambda,
|
||||
|
||||
#[token("do")]
|
||||
KeywordDo,
|
||||
|
||||
#[token("end")]
|
||||
KeywordEnd,
|
||||
|
||||
#[token("as")]
|
||||
KeywordAs,
|
||||
|
||||
#[token("in")]
|
||||
KeywordIn,
|
||||
|
||||
#[token("for")]
|
||||
KeywordFor,
|
||||
#[token("while")]
|
||||
KeywordWhile,
|
||||
|
||||
#[token("loop")]
|
||||
KeywordLoop,
|
||||
|
||||
#[token("where")]
|
||||
KeywordWhere,
|
||||
|
||||
#[token("extern")]
|
||||
KeywordExtern,
|
||||
|
||||
#[token("load")]
|
||||
KeywordLoad,
|
||||
|
||||
#[token("from")]
|
||||
KeywordFrom,
|
||||
|
||||
#[token("use")]
|
||||
KeywordUse,
|
||||
|
||||
#[token("struct")]
|
||||
KeywordStruct,
|
||||
|
||||
#[token("enum")]
|
||||
KeywordEnum,
|
||||
|
||||
#[token("impl")]
|
||||
KeywordImpl,
|
||||
|
||||
#[token("trait")]
|
||||
KeywordTrait,
|
||||
|
||||
// #[token("type")]
|
||||
// KeywordType,
|
||||
//
|
||||
#[token("match")]
|
||||
KeywordMatch,
|
||||
|
||||
#[token("return")]
|
||||
KeywordReturn,
|
||||
|
||||
#[token("break")]
|
||||
KeywordBreak,
|
||||
|
||||
#[token("continue")]
|
||||
KeywordContinue,
|
||||
|
||||
#[token("+")]
|
||||
Plus,
|
||||
|
||||
#[token("-")]
|
||||
Minus,
|
||||
|
||||
#[token("*")]
|
||||
Mul,
|
||||
|
||||
#[token("/")]
|
||||
Div,
|
||||
|
||||
#[token("%")]
|
||||
Mod,
|
||||
|
||||
#[token("**", priority = 3)]
|
||||
Power,
|
||||
|
||||
#[token("$")]
|
||||
Dollar,
|
||||
|
||||
#[token("@")]
|
||||
At,
|
||||
|
||||
#[token("==")]
|
||||
Eq,
|
||||
|
||||
#[token("!=")]
|
||||
NotEq,
|
||||
|
||||
#[token("<")]
|
||||
Less,
|
||||
|
||||
#[token(">")]
|
||||
Greater,
|
||||
|
||||
#[token("<=")]
|
||||
LessEq,
|
||||
|
||||
#[token(">=")]
|
||||
GreaterEq,
|
||||
|
||||
#[token("and")]
|
||||
And,
|
||||
|
||||
#[token("or")]
|
||||
Or,
|
||||
|
||||
#[token("xor")]
|
||||
Xor,
|
||||
|
||||
#[token("nor")]
|
||||
Nor,
|
||||
|
||||
#[token("not")]
|
||||
Not,
|
||||
|
||||
#[token("(")]
|
||||
LParen,
|
||||
|
||||
#[token(")")]
|
||||
RParen,
|
||||
|
||||
#[token("[")]
|
||||
LBracket,
|
||||
|
||||
#[token("]")]
|
||||
RBracket,
|
||||
|
||||
#[token("{")]
|
||||
LBrace,
|
||||
|
||||
#[token("}")]
|
||||
RBrace,
|
||||
|
||||
#[token(",")]
|
||||
Comma,
|
||||
|
||||
#[token(";")]
|
||||
Semicolon,
|
||||
|
||||
#[token(":")]
|
||||
Colon,
|
||||
|
||||
#[token(".")]
|
||||
Dot,
|
||||
|
||||
#[token("...")]
|
||||
Spread,
|
||||
|
||||
#[token("..")]
|
||||
DotDot,
|
||||
|
||||
#[token("::")]
|
||||
Access,
|
||||
|
||||
#[token("->")]
|
||||
Arrow,
|
||||
|
||||
#[token("~")]
|
||||
Tilde,
|
||||
|
||||
#[token("!")]
|
||||
Bang,
|
||||
|
||||
// New tokens for pattern matching
|
||||
#[token("=>")]
|
||||
FatArrow, // For match arms
|
||||
|
||||
#[token("|")]
|
||||
Union,
|
||||
|
||||
#[token("?.")]
|
||||
OptionalChain,
|
||||
|
||||
#[token("?")]
|
||||
Unwrap,
|
||||
|
||||
#[token("=")]
|
||||
Assign,
|
||||
|
||||
#[token("+=")]
|
||||
AddAssign,
|
||||
|
||||
#[token("-=")]
|
||||
SubAssign,
|
||||
|
||||
#[token("*=")]
|
||||
MulAssign,
|
||||
|
||||
#[token("/=")]
|
||||
DivAssign,
|
||||
|
||||
#[token("%=")]
|
||||
ModAssign,
|
||||
}
|
||||
229
src/lexer/tests.rs
Normal file
229
src/lexer/tests.rs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
use super::Token;
|
||||
use logos::Logos;
|
||||
|
||||
#[test]
|
||||
fn test_literals() {
|
||||
let mut lexer = Token::lexer("true false 42 2.14 \"hello\" r\"raw\"");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Bool(true))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Bool(false))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Int(42))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Float(2.14))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::String("hello".to_string()))));
|
||||
// RawString regex seems to have issues, let's test separately
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("r".to_string()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::String("raw".to_string()))));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int_literals() {
|
||||
let mut lexer = Token::lexer("0 123 1_000_000");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Int(0))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Int(123))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Int(1000000))));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_literals() {
|
||||
let mut lexer = Token::lexer("\"hello world\" \"with\\\\escape\" \"quote\\\"here\"");
|
||||
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::String("hello world".to_string())))
|
||||
);
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::String("with\\escape".to_string())))
|
||||
);
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::String("quote\"here".to_string())))
|
||||
);
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords() {
|
||||
let mut lexer = Token::lexer(
|
||||
"bool int float string let if else fn do end as in for while loop where extern import struct enum impl trait match return break continue",
|
||||
);
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordBool)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordFloat)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordString)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordLet)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordIf)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordElse)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordFn)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordDo)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordEnd)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordAs)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordIn)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordFor)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordWhile)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordLoop)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordWhere)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordExtern)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("import".into()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordStruct)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordEnum)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordImpl)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordTrait)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordMatch)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordReturn)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordBreak)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordContinue)));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_operators() {
|
||||
let mut lexer = Token::lexer("+ - * / % ** $ @ == != < > <= >= and or xor nor not");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Plus)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Minus)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Mul)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Div)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Mod)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Power)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Dollar)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::At)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Eq)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::NotEq)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Less)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Greater)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::LessEq)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::GreaterEq)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::And)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Or)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Xor)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Nor)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Not)));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assignment_operators() {
|
||||
let mut lexer = Token::lexer("= += -= *= /= %=");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Assign)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::AddAssign)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::SubAssign)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::MulAssign)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::DivAssign)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::ModAssign)));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_punctuation() {
|
||||
let mut lexer = Token::lexer("( ) [ ] { } , ; : . ... .. :: -> ~ ! => | |> ?. ?");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::LParen)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::RParen)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::LBracket)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::RBracket)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::LBrace)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::RBrace)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Comma)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Semicolon)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Colon)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Dot)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Spread)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::DotDot)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Access)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Arrow)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Tilde)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Bang)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::FatArrow)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Union)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::OptionalChain)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Unwrap)));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variables() {
|
||||
let mut lexer = Token::lexer("x y_z _private camelCase PascalCase");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("y_z".to_string()))));
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::Variable("_private".to_string())))
|
||||
);
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::Variable("camelCase".to_string())))
|
||||
);
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::Variable("PascalCase".to_string())))
|
||||
);
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_whitespace_skipping() {
|
||||
let mut lexer = Token::lexer(" \t\n\r true \n false ");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Bool(true))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Bool(false))));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_skipping() {
|
||||
let mut lexer = Token::lexer("true # this is a comment\n false");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Bool(true))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Bool(false))));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_sequence() {
|
||||
let mut lexer = Token::lexer("fn add(x: int, y: int) -> int { x + y }");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordFn)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("add".to_string()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::LParen)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Colon)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Comma)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("y".to_string()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Colon)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::RParen)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Arrow)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::LBrace)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Plus)));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::Variable("y".to_string()))));
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::RBrace)));
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_cases() {
|
||||
// Test that keywords are not treated as variables
|
||||
let mut lexer = Token::lexer("let let_var if if_var");
|
||||
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordLet)));
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::Variable("let_var".to_string())))
|
||||
);
|
||||
assert_eq!(lexer.next(), Some(Ok(Token::KeywordIf)));
|
||||
assert_eq!(
|
||||
lexer.next(),
|
||||
Some(Ok(Token::Variable("if_var".to_string())))
|
||||
);
|
||||
assert_eq!(lexer.next(), None);
|
||||
}
|
||||
6
src/lib.rs
Normal file
6
src/lib.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub const EXTENSION: &str = ".sui";
|
||||
|
||||
pub mod ast;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod typechecker;
|
||||
88
src/main.rs
Normal file
88
src/main.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use logos::Logos;
|
||||
use std::fs;
|
||||
use suicmez::{lexer::Token, parser::Parser, typechecker::TypeChecker};
|
||||
|
||||
fn main() {
|
||||
// Check if a file was provided as argument
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
// Run all test files in the tests directory
|
||||
run_test_suite();
|
||||
return;
|
||||
}
|
||||
|
||||
let filename = &args[1];
|
||||
println!("Type checking file: {}", filename);
|
||||
|
||||
if let Err(e) = run_file(filename) {
|
||||
eprintln!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_test_suite() {
|
||||
println!("Running test suite...\n");
|
||||
|
||||
let test_files = vec![
|
||||
"tests/basic_types.sui",
|
||||
"tests/structs.sui",
|
||||
"tests/enums.sui",
|
||||
"tests/functions.sui",
|
||||
"tests/arrays.sui",
|
||||
"tests/traits.sui",
|
||||
"tests/control_flow.sui",
|
||||
];
|
||||
|
||||
for file in test_files {
|
||||
println!("Testing: {}", file);
|
||||
match run_file(file) {
|
||||
Ok(_) => println!("✓ Passed\n"),
|
||||
Err(e) => println!("✗ Failed: {}\n", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_file(filename: &str) -> Result<(), String> {
|
||||
// Read the source file
|
||||
let source = fs::read_to_string(filename)
|
||||
.map_err(|e| format!("Error reading file {}: {}", filename, e))?;
|
||||
|
||||
// First, we need to parse the source code
|
||||
let mut tokens = Vec::new();
|
||||
let mut lexer = Token::lexer(&source);
|
||||
|
||||
loop {
|
||||
match lexer.next() {
|
||||
Some(Ok(token)) => {
|
||||
let span = lexer.span();
|
||||
tokens.push((token, span));
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
return Err("Lexing error".to_string());
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
let mut parser = Parser::new(filename.to_string(), tokens);
|
||||
let ast_nodes = parser
|
||||
.parse()
|
||||
.map_err(|e| format!("Parse error: {}", e.message))?;
|
||||
|
||||
println!("Parsed {} AST nodes successfully", ast_nodes.len());
|
||||
|
||||
// Typecheck the AST
|
||||
let mut typechecker = TypeChecker::new();
|
||||
let typed_nodes = typechecker.typecheck_program(&ast_nodes).map_err(|e| {
|
||||
format!(
|
||||
"Type error at {}:{}: {:?}",
|
||||
e.span.file, e.span.start, e.kind
|
||||
)
|
||||
})?;
|
||||
|
||||
println!(
|
||||
"Type checking passed! {} nodes typechecked.",
|
||||
typed_nodes.len()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1763
src/parser.rs
Normal file
1763
src/parser.rs
Normal file
File diff suppressed because it is too large
Load diff
4
src/typechecker.rs
Normal file
4
src/typechecker.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Type {
|
||||
Stub,
|
||||
}
|
||||
452
syntax_test.sui
Normal file
452
syntax_test.sui
Normal file
|
|
@ -0,0 +1,452 @@
|
|||
use "std/abc"
|
||||
load "math.so" as math_lib
|
||||
|
||||
extern print(msg: string) -> int from libc # this is a comment
|
||||
extern malloc(size: int) -> int from libc #* this is a comment too *#
|
||||
extern add_numbers(a: int, b: int) -> int from math_lib
|
||||
|
||||
# this is a funny comment
|
||||
struct Point
|
||||
x: int,
|
||||
y: int
|
||||
end
|
||||
|
||||
struct Generic<T>
|
||||
value: T,
|
||||
tag: string
|
||||
end
|
||||
|
||||
struct Complex<T, U>
|
||||
first: T,
|
||||
second: U
|
||||
end
|
||||
|
||||
struct EmptyStruct
|
||||
end
|
||||
|
||||
struct ArrayStruct
|
||||
numbers: [int],
|
||||
matrix: [[float]]
|
||||
end
|
||||
|
||||
enum Option<T>
|
||||
Some(T),
|
||||
None
|
||||
end
|
||||
|
||||
enum Result<T, E>
|
||||
Ok(T),
|
||||
Err(E)
|
||||
end
|
||||
|
||||
enum Status
|
||||
Active,
|
||||
Inactive,
|
||||
Pending(string)
|
||||
end
|
||||
|
||||
enum Tree<T>
|
||||
Leaf(T),
|
||||
Branch(T, T)
|
||||
end
|
||||
|
||||
trait Show
|
||||
fn display(msg: string) -> string,
|
||||
fn to_string() -> string
|
||||
end
|
||||
|
||||
trait Comparable<T>
|
||||
fn compare(other: T) -> int
|
||||
end
|
||||
|
||||
trait Container<T>
|
||||
fn push(item: T) -> int,
|
||||
fn pop() -> T
|
||||
end
|
||||
|
||||
impl Point
|
||||
fn new(x: int, y: int) -> Point
|
||||
Point { x: x, y: y }
|
||||
|
||||
fn distance() -> float do
|
||||
let x_sq = 5 * 5
|
||||
let y_sq = 3 * 3
|
||||
((x_sq + y_sq) as float)
|
||||
end
|
||||
|
||||
fn move_by(dx: int, dy: int) -> Point do
|
||||
let new_x = 5 + 10
|
||||
let new_y = 3 + 20
|
||||
Point { x: new_x, y: new_y }
|
||||
end
|
||||
end
|
||||
|
||||
impl Option<int> : Show
|
||||
fn display(msg: string) -> string
|
||||
"Option value"
|
||||
|
||||
fn to_string() -> string
|
||||
"option"
|
||||
end
|
||||
|
||||
impl Result<string, int>
|
||||
fn is_ok() -> bool
|
||||
true
|
||||
|
||||
fn unwrap() -> string
|
||||
"unwrapped"
|
||||
end
|
||||
|
||||
|
||||
fn greet(name: string) -> string
|
||||
"Hello, " + name
|
||||
|
||||
fn get_answer() -> int
|
||||
42
|
||||
|
||||
fn print_number(num: int)
|
||||
num
|
||||
|
||||
fn add(a: int, b: int) -> int
|
||||
a + b
|
||||
|
||||
fn identity<T>(value: T) -> T
|
||||
value
|
||||
|
||||
fn pair<T, U>(first: T, second: U) -> (T, U)
|
||||
(first, second)
|
||||
|
||||
fn array_func(arr: [int]) -> [int]
|
||||
arr
|
||||
|
||||
fn process<T>(item: T) -> string
|
||||
"processed"
|
||||
|
||||
fn process_list(items: int) -> int do
|
||||
let result = 0
|
||||
result + items
|
||||
end
|
||||
|
||||
|
||||
fn literals() -> bool do
|
||||
let int_val = 42
|
||||
let float_val = 3.14
|
||||
let negative = -100
|
||||
let scientific = 1.5e-10
|
||||
let bool_true = true
|
||||
let bool_false = false
|
||||
let string_val = "hello world"
|
||||
let empty_string = ""
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
fn collections() -> bool do
|
||||
let arr = [1, 2, 3, 4, 5]
|
||||
let empty_arr = []
|
||||
let tuple = (1, "hello", 3.14)
|
||||
let single_tuple = (42)
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
fn struct_enum_literals() -> bool do
|
||||
let point = Point { x: 10, y: 20 }
|
||||
let option_val = Option::Some(42)
|
||||
let option_none = Option::None
|
||||
let result_ok = Result::Ok("success")
|
||||
let result_err = Result::Err(404)
|
||||
let status = Status::Pending("loading...")
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
fn arithmetic_logic() -> int do
|
||||
let a = 10
|
||||
let b = 5
|
||||
let add_result = a + b
|
||||
let sub_result = a - b
|
||||
let mul_result = a * b
|
||||
let div_result = a / b
|
||||
let mod_result = a % b
|
||||
|
||||
let and_result = true and false
|
||||
let or_result = true or false
|
||||
let not_result = not true
|
||||
|
||||
let eq = a == b
|
||||
let neq = a != b
|
||||
let lt = a < b
|
||||
let gt = a > b
|
||||
let leq = a <= b
|
||||
let geq = a >= b
|
||||
|
||||
add_result + sub_result
|
||||
end
|
||||
|
||||
|
||||
fn unary() -> int do
|
||||
let a = 5
|
||||
let neg = -a
|
||||
let b = true
|
||||
let not_b = not b
|
||||
a + 1
|
||||
end
|
||||
|
||||
|
||||
fn bindings() -> int do
|
||||
let x = 10
|
||||
let mut y = 20
|
||||
let uniq z = 30
|
||||
let once w = 40
|
||||
|
||||
let typed: int = 100
|
||||
let mut_typed: string = "hello"
|
||||
|
||||
x + 5
|
||||
end
|
||||
|
||||
|
||||
fn if_else(n: int) -> int
|
||||
if n > 0
|
||||
100
|
||||
else
|
||||
-100
|
||||
|
||||
fn if_else_complex(a: int, b: int) -> string
|
||||
if a > b
|
||||
"a is greater"
|
||||
else
|
||||
if a == b
|
||||
"equal"
|
||||
else
|
||||
"b is greater"
|
||||
|
||||
|
||||
fn match_simple(opt: Option<int>) -> int
|
||||
match opt
|
||||
Option::Some(x) => x,
|
||||
Option::None => 0
|
||||
end
|
||||
|
||||
fn match_complex(val: int) -> string
|
||||
match val
|
||||
0 => "zero",
|
||||
1 => "one",
|
||||
2 => "two",
|
||||
_ => "many"
|
||||
end
|
||||
|
||||
fn match_pattern(p: Point) -> string
|
||||
match p
|
||||
Point { x: 0, y: 0 } => "origin",
|
||||
Point { x: x, y: y } => "point"
|
||||
end
|
||||
|
||||
|
||||
fn while_loop(n: int) -> int do
|
||||
let mut count = 0
|
||||
while count < n
|
||||
do
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
fn do_block() -> int
|
||||
do
|
||||
let a = 10
|
||||
let b = 20
|
||||
let c = 30
|
||||
a + b + c
|
||||
end
|
||||
|
||||
fn nested_do() -> int
|
||||
do
|
||||
let x = do
|
||||
5
|
||||
end
|
||||
let y = do
|
||||
10
|
||||
end
|
||||
x + y
|
||||
end
|
||||
|
||||
|
||||
fn function_calls() -> int do
|
||||
let point = Point { x: 5, y: 10 }
|
||||
let x_coord = point.x
|
||||
let arr = [1, 2, 3]
|
||||
let first = arr[0]
|
||||
|
||||
let result = add(10, 20)
|
||||
let identity_val = identity(42)
|
||||
|
||||
result + first
|
||||
end
|
||||
|
||||
|
||||
fn optional_chain(opt: Option<Point>) -> int do
|
||||
let val = opt?.x
|
||||
100
|
||||
end
|
||||
|
||||
|
||||
fn early_return() -> int do
|
||||
let opt = Option::Some(42)
|
||||
let val = opt?
|
||||
val
|
||||
end
|
||||
|
||||
|
||||
fn casting() -> float do
|
||||
let int_val = 42
|
||||
let float_val = (int_val as float)
|
||||
let x = (100 as float) + 3.14
|
||||
x
|
||||
end
|
||||
|
||||
fn lambda_example() -> int do
|
||||
let add_one = lambda (x) x + 1
|
||||
let multiply = lambda (x, y) x * y
|
||||
let get_five = lambda () 5
|
||||
|
||||
let applied = add_one(10)
|
||||
applied
|
||||
end
|
||||
|
||||
fn assignment() -> int do
|
||||
let mut x = 10
|
||||
x = 20
|
||||
x = x + 5
|
||||
x
|
||||
end
|
||||
|
||||
fn with_return(n: int) -> int do
|
||||
if n < 0
|
||||
return -1
|
||||
n + 100
|
||||
end
|
||||
|
||||
fn with_break() -> int do
|
||||
let mut i = 0
|
||||
while i < 10
|
||||
do
|
||||
if i == 5
|
||||
break
|
||||
i = i + 1
|
||||
end
|
||||
i
|
||||
end
|
||||
|
||||
fn with_continue() -> int do
|
||||
let mut sum = 0
|
||||
let mut i = 0
|
||||
while i < 10
|
||||
do
|
||||
i = i + 1
|
||||
if i % 2 == 0
|
||||
continue
|
||||
sum = sum + i
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
|
||||
@deprecated("use new_func instead")
|
||||
fn old_func() -> int
|
||||
42
|
||||
|
||||
@optimize(level = aggressive)
|
||||
fn fast_func() -> int
|
||||
100
|
||||
|
||||
@test
|
||||
fn test_something() -> bool
|
||||
true
|
||||
|
||||
fn fibonacci(n: int) -> int
|
||||
if n <= 1
|
||||
n
|
||||
else
|
||||
fibonacci(n - 1) + fibonacci(n - 2)
|
||||
|
||||
fn factorial(n: int) -> int do
|
||||
let mut result = 1
|
||||
let mut i = 2
|
||||
while i <= n
|
||||
do
|
||||
result = result * i
|
||||
i = i + 1
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
fn map_over_option<T, U>(opt: Option<T>) -> Option<U>
|
||||
match opt
|
||||
Option::Some(x) => Option::Some(x),
|
||||
Option::None => Option::None
|
||||
end
|
||||
|
||||
fn process_result<T, E>(res: Result<T, E>) -> int
|
||||
match res
|
||||
Result::Ok(x) => 1,
|
||||
Result::Err(e) => 0
|
||||
end
|
||||
|
||||
struct LinkedList<T>
|
||||
value: T,
|
||||
next: Option<int>
|
||||
end
|
||||
|
||||
impl LinkedList<int>
|
||||
fn new(v: int) -> LinkedList<int>
|
||||
LinkedList { value: 42, next: Option::None }
|
||||
|
||||
fn head() -> int
|
||||
100
|
||||
|
||||
fn tail() -> Option<int>
|
||||
Option::None
|
||||
|
||||
fn sum() -> int
|
||||
do
|
||||
let mut total = 0
|
||||
total
|
||||
end
|
||||
end
|
||||
|
||||
fn complex_pattern_match(val: int) -> string
|
||||
match val
|
||||
0 => "zero",
|
||||
1 => "one",
|
||||
2 => "two",
|
||||
3 => "three",
|
||||
4 => "four",
|
||||
5 => "five",
|
||||
_ => "many"
|
||||
end
|
||||
|
||||
fn tuple_destructure() -> int do
|
||||
let tup = (10, 20, 30)
|
||||
30
|
||||
end
|
||||
|
||||
fn array_ops() -> int do
|
||||
let arr = [1, 2, 3, 4, 5]
|
||||
let first = arr[0]
|
||||
let length = 5
|
||||
first + length
|
||||
end
|
||||
|
||||
fn range_example() -> int do
|
||||
let r = 1..10
|
||||
5
|
||||
end
|
||||
|
||||
fn for_loop_example() -> int do
|
||||
let sum = 0
|
||||
for i in 0..5
|
||||
sum + i
|
||||
sum
|
||||
end
|
||||
7
tests/arrays.sui
Normal file
7
tests/arrays.sui
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Array test
|
||||
fn main(args: [string]) -> int do
|
||||
let arr = [1, 2, 3, 4];
|
||||
let empty = [];
|
||||
let x = arr[0];
|
||||
x
|
||||
end
|
||||
8
tests/basic_types.sui
Normal file
8
tests/basic_types.sui
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Basic type checking test
|
||||
fn main() -> int do
|
||||
let x = 5;
|
||||
let y = 10.5;
|
||||
let z = true;
|
||||
let s = "hello";
|
||||
x
|
||||
end
|
||||
12
tests/control_flow.sui
Normal file
12
tests/control_flow.sui
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Control flow test
|
||||
fn main() -> int do
|
||||
if true
|
||||
1
|
||||
else
|
||||
0
|
||||
|
||||
let i = 0;
|
||||
while i < 5
|
||||
i = i + 1;
|
||||
i
|
||||
end
|
||||
34
tests/enums.sui
Normal file
34
tests/enums.sui
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Enum test
|
||||
enum Color
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
end
|
||||
|
||||
enum Result<T, E>
|
||||
Ok(T),
|
||||
Err(E),
|
||||
end
|
||||
|
||||
struct Person
|
||||
name: string,
|
||||
age: int,
|
||||
end
|
||||
|
||||
fn main() -> int do
|
||||
let color = Color::Red();
|
||||
let res = Result::Ok(42);
|
||||
match res
|
||||
Result::Ok(value) => value,
|
||||
Result::Err(_) => 0,
|
||||
end
|
||||
let person = Person { name: "Bob", age: 25 };
|
||||
match color
|
||||
Color::Red() => 1,
|
||||
Color::Green() => 2,
|
||||
Color::Blue() => 3,
|
||||
end
|
||||
match person
|
||||
Person { name: _, age: age } => age,
|
||||
end
|
||||
end
|
||||
12
tests/functions.sui
Normal file
12
tests/functions.sui
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Function test
|
||||
fn add(x: int, y: int) -> int
|
||||
x + y
|
||||
|
||||
fn identity<T>(x: T) -> T
|
||||
x
|
||||
|
||||
fn main() -> int do
|
||||
let sum = add(5, 3);
|
||||
let id = identity(42);
|
||||
sum
|
||||
end
|
||||
16
tests/structs.sui
Normal file
16
tests/structs.sui
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Struct test
|
||||
struct Point
|
||||
x: int,
|
||||
y: int,
|
||||
end
|
||||
|
||||
struct Person<T>
|
||||
name: string,
|
||||
age: T,
|
||||
end
|
||||
|
||||
fn main() -> int do
|
||||
let p = Point { x: 5, y: 10 };
|
||||
let person = Person { name: "Alice", age: 30 };
|
||||
p.x
|
||||
end
|
||||
17
tests/traits.sui
Normal file
17
tests/traits.sui
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Trait and implementation test
|
||||
trait Show
|
||||
fn show(self) -> string
|
||||
end
|
||||
|
||||
struct Number
|
||||
value: int
|
||||
end
|
||||
|
||||
impl Number : Show
|
||||
fn show(self) -> string
|
||||
"number"
|
||||
end
|
||||
|
||||
fn main() -> int do
|
||||
42
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue