first commit

This commit is contained in:
Masashi 2025-12-15 13:53:57 +05:30
commit 4ff159dd4a
20 changed files with 6529 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

303
Cargo.lock generated Normal file
View 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
View 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"

2843
output.md Normal file

File diff suppressed because it is too large Load diff

7
simple_test.sui Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

4
src/typechecker.rs Normal file
View file

@ -0,0 +1,4 @@
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Stub,
}

452
syntax_test.sui Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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