├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── build.rs ├── crates ├── ast │ ├── Cargo.toml │ └── src │ │ ├── ast.rs │ │ ├── format.rs │ │ ├── lib.rs │ │ └── token.rs ├── cli │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── codegen_llvm │ ├── Cargo.toml │ └── src │ │ ├── build.rs │ │ ├── diag.rs │ │ ├── enums.rs │ │ ├── exprs.rs │ │ ├── funcs.rs │ │ ├── lib.rs │ │ ├── modules.rs │ │ ├── opts.rs │ │ ├── runtime.rs │ │ ├── scope.rs │ │ ├── stmts.rs │ │ ├── strings.rs │ │ ├── structs.rs │ │ ├── tests.rs │ │ ├── types.rs │ │ └── values.rs ├── diag │ ├── Cargo.toml │ └── src │ │ ├── errors.rs │ │ ├── lib.rs │ │ └── mod.rs ├── layout │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── lexer │ ├── Cargo.toml │ └── src │ │ ├── cli.rs │ │ ├── diag.rs │ │ ├── format.rs │ │ ├── lib.rs │ │ ├── mod.rs │ │ └── tests.rs ├── parser │ ├── Cargo.toml │ └── src │ │ ├── cli.rs │ │ ├── common.rs │ │ ├── diag.rs │ │ ├── exprs.rs │ │ ├── lib.rs │ │ ├── mod.rs │ │ ├── prec.rs │ │ ├── stmts.rs │ │ └── tests.rs └── utils │ ├── Cargo.toml │ └── src │ ├── fs.rs │ ├── generate_random_hex.rs │ ├── lib.rs │ ├── purify_string.rs │ └── tui.rs ├── examples └── main.cyr ├── flake.lock ├── flake.nix ├── rustfmt.toml ├── scripts ├── install.ps1 └── install.sh └── stdlib ├── io.cyr ├── math.cyr └── mem.cyr /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build & Upload Cyrus Lang 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | build-linux: 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Install dependencies 18 | run: | 19 | sudo apt-get update 20 | sudo apt install -y software-properties-common llvm-18 llvm-18-dev clang zlib1g zlib1g-dev libpolly-18-dev 21 | 22 | - name: Build 23 | run: cargo build --verbose --release 24 | 25 | - name: Install Rust 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: stable 29 | override: true 30 | 31 | - name: Run tests 32 | run: cargo test --all --verbose 33 | 34 | - name: Collect files in single directory 35 | run: | 36 | mkdir cyrus 37 | cp ./target/release/cyrus ./cyrus 38 | cp ./scripts/install.sh ./cyrus 39 | cp -r ./stdlib ./cyrus 40 | 41 | - name: Upload as artifact 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: cyrus-linux 45 | path: ./cyrus 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **target** 2 | *.ll 3 | tmp/** 4 | .vscode 5 | result 6 | **.o 7 | build/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cyrus-lang" 3 | version = "0.0.1" 4 | edition = "2024" 5 | resolver = "2" 6 | build = "./build.rs" 7 | 8 | [workspace] 9 | members = [ 10 | "crates/codegen_llvm", 11 | "crates/lexer", 12 | "crates/parser", 13 | "crates/ast", 14 | "crates/utils", 15 | "crates/cli", 16 | "crates/layout", 17 | "crates/diag", 18 | ] 19 | 20 | [[bin]] 21 | name = "cyrus" 22 | path = "./crates/cli/src/main.rs" 23 | 24 | [dependencies] 25 | codegen_llvm = { path = "./crates/codegen_llvm", version = "*" } 26 | layout = { path = "./crates/layout", version = "*" } 27 | parser = { path = "./crates/parser", version = "*" } 28 | clap = { version = "4.5.34", features = ["derive"] } 29 | lexer = { path = "./crates/lexer", version = "*" } 30 | utils = { path = "./crates/utils", version = "*" } 31 | ast = { path = "./crates/ast", version = "*" } 32 | colorized = "1.0.0" 33 | toml = "0.8.20" 34 | 35 | [profile.dev] 36 | codegen-units = 256 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.84 2 | 3 | RUN apt install -y build-essentials software-properties-common llvm-19 llvm-19-dev clang zlib1g zlib1g-dev 4 | 5 | WORKDIR /build 6 | 7 | COPY . /build 8 | 9 | RUN cargo build --release && \ 10 | mkdir cyrus && \ 11 | cp ./target/release/cyrus ./cyrus && \ 12 | cp -r ./stdlib ./cyrus 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: run 2 | 3 | emit-llvm: 4 | cargo run --jobs 16 -- emit-llvm ./examples/main.cyr -o ./tmp/main.ll 5 | 6 | run: 7 | cargo run --jobs 16 -- run ./examples/main.cyr 8 | 9 | test: 10 | cargo test --jobs 16 --all -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cyrus Lang 2 | 3 | Cyrus is a general-purpose, statically-typed, manually memory-managed programming language designed for performance-critical applications. It leverages **LLVM** as its compiler backend, providing efficient code generation and optimization capabilities. The language syntax is heavily influenced by C, offering familiarity to developers experienced with C-like languages. 4 | 5 | ## Target Audience 6 | 7 | Cyrus is primarily aimed at experienced programmers who value performance, control, and low-level system interactions. Developers seeking to build high-performance applications that require precise memory management will find this language well-suited to their needs. 8 | 9 | ## Testing, Documentation, and API References 10 | 11 | All information about testing `Cyrus` in a development environment, along with comprehensive documentation and API references, is available on our website at this [link](https://cyrus-lang-v2.netlify.app). 12 | 13 | ### Contributing to Documentation 14 | 15 | If you notice any misspellings, inaccuracies, or areas for improvement in the documentation, we welcome your contributions! Feel free to open an issue or submit a pull request in the [Official Website Repository](https://github.com/cyrus-lang/Official-Website). Your feedback helps us improve the quality of our resources for everyone. 16 | 17 | ## Open to Contribution 18 | 19 | We highly encourage contributions! If you're interested in helping to shape the future of Cyrus Lang, feel free to fork the repository, propose improvements, or report any issues you encounter. Together, we can make Cyrus even better! 20 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /crates/ast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ast" 3 | version = "0.1.0" 4 | edition = "2024" 5 | -------------------------------------------------------------------------------- /crates/ast/src/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::token::*; 2 | 3 | #[derive(Debug)] 4 | pub enum Node { 5 | ProgramTree(ProgramTree), 6 | Statement(Statement), 7 | Expression(Expression), 8 | } 9 | 10 | #[derive(Debug)] 11 | pub struct ProgramTree { 12 | pub body: Vec, 13 | pub span: Span, 14 | } 15 | 16 | impl Default for ProgramTree { 17 | fn default() -> Self { 18 | Self::new() 19 | } 20 | } 21 | 22 | impl ProgramTree { 23 | pub fn new() -> Self { 24 | Self { 25 | body: vec![], 26 | span: Span::default(), 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug, Clone, PartialEq)] 32 | pub enum Expression { 33 | Cast(Cast), 34 | Identifier(Identifier), 35 | TypeSpecifier(TypeSpecifier), 36 | ModuleImport(ModuleImport), 37 | Assignment(Box), 38 | Literal(Literal), 39 | Prefix(UnaryExpression), 40 | Infix(BinaryExpression), 41 | UnaryOperator(UnaryOperator), 42 | Array(Array), 43 | ArrayIndex(ArrayIndex), 44 | AddressOf(Box), 45 | Dereference(Box), 46 | StructInit(StructInit), 47 | FuncCall(FuncCall), 48 | FieldAccess(FieldAccess), 49 | MethodCall(MethodCall), 50 | } 51 | 52 | #[derive(Debug, Clone, PartialEq)] 53 | pub struct Enum { 54 | pub name: Identifier, 55 | pub variants: Vec, 56 | pub storage_class: StorageClass, 57 | pub loc: Location, 58 | } 59 | 60 | #[derive(Debug, Clone, PartialEq)] 61 | pub enum EnumField { 62 | OnlyIdentifier(Identifier), 63 | Variant(Identifier, Vec), 64 | Valued(Identifier, Box), 65 | } 66 | 67 | #[derive(Debug, Clone, PartialEq)] 68 | pub struct EnumValuedField { 69 | pub name: Identifier, 70 | pub field_type: TypeSpecifier, 71 | } 72 | 73 | #[derive(Debug, Clone, PartialEq)] 74 | pub struct Cast { 75 | pub expr: Box, 76 | pub target_type: TypeSpecifier, 77 | pub span: Span, 78 | pub loc: Location, 79 | } 80 | 81 | #[derive(Debug, Clone, PartialEq)] 82 | pub enum UnaryOperatorType { 83 | PreIncrement, 84 | PreDecrement, 85 | PostIncrement, 86 | PostDecrement, 87 | } 88 | 89 | #[derive(Debug, Clone, PartialEq)] 90 | pub struct UnaryOperator { 91 | pub module_import: ModuleImport, 92 | pub ty: UnaryOperatorType, 93 | pub span: Span, 94 | pub loc: Location, 95 | } 96 | 97 | #[derive(Debug, Clone, PartialEq)] 98 | pub struct FuncCall { 99 | pub operand: Box, 100 | pub arguments: Vec, 101 | pub span: Span, 102 | pub loc: Location, 103 | } 104 | 105 | #[derive(Debug, Clone, PartialEq)] 106 | pub struct FieldAccess { 107 | pub operand: Box, 108 | pub field_name: Identifier, 109 | pub span: Span, 110 | pub loc: Location, 111 | } 112 | 113 | #[derive(Debug, Clone, PartialEq)] 114 | pub struct MethodCall { 115 | pub operand: Box, 116 | pub method_name: Identifier, 117 | pub arguments: Vec, 118 | pub span: Span, 119 | pub loc: Location, 120 | } 121 | 122 | #[derive(Debug, Clone, PartialEq)] 123 | pub struct Identifier { 124 | pub name: String, 125 | pub span: Span, 126 | pub loc: Location, 127 | } 128 | 129 | #[derive(Debug, Clone, PartialEq)] 130 | pub struct ModuleImport { 131 | pub segments: Vec, 132 | pub span: Span, 133 | pub loc: Location, 134 | } 135 | 136 | #[derive(Debug, PartialEq, Clone)] 137 | pub enum Literal { 138 | Integer(i64), 139 | Float(f64), 140 | Bool(bool), 141 | String(String), 142 | Char(char), 143 | Null, 144 | } 145 | 146 | #[derive(Debug, Clone, PartialEq)] 147 | pub enum TypeSpecifier { 148 | Identifier(Identifier), 149 | ModuleImport(ModuleImport), 150 | TypeToken(Token), 151 | Const(Box), 152 | AddressOf(Box), 153 | Dereference(Box), 154 | Array(ArrayTypeSpecifier), 155 | } 156 | 157 | #[derive(Debug, Clone, PartialEq)] 158 | pub struct ArrayTypeSpecifier { 159 | pub size: ArrayCapacity, 160 | pub element_type: Box, 161 | } 162 | 163 | #[derive(Debug, Clone, PartialEq)] 164 | pub enum ArrayCapacity { 165 | Fixed(TokenKind), 166 | Dynamic, 167 | } 168 | 169 | #[derive(Debug, Clone, PartialEq)] 170 | pub struct UnaryExpression { 171 | pub operator: Token, 172 | pub operand: Box, 173 | pub span: Span, 174 | pub loc: Location, 175 | } 176 | 177 | #[derive(Debug, Clone, PartialEq)] 178 | pub struct BinaryExpression { 179 | pub operator: Token, 180 | pub left: Box, 181 | pub right: Box, 182 | pub span: Span, 183 | pub loc: Location, 184 | } 185 | 186 | #[derive(Debug, Clone, PartialEq)] 187 | pub struct Array { 188 | pub data_type: TypeSpecifier, 189 | pub elements: Vec, 190 | pub span: Span, 191 | pub loc: Location, 192 | } 193 | 194 | #[derive(Debug, Clone, PartialEq)] 195 | pub struct ArrayIndex { 196 | pub expr: Box, 197 | pub index: Box, 198 | pub span: Span, 199 | pub loc: Location, 200 | } 201 | 202 | #[derive(Debug, Clone, PartialEq)] 203 | pub struct Hash { 204 | pub pairs: Vec<(Expression, Expression)>, 205 | pub span: Span, 206 | pub loc: Location, 207 | } 208 | 209 | #[derive(Debug, Clone, PartialEq)] 210 | pub enum Statement { 211 | Variable(Variable), 212 | Expression(Expression), 213 | If(If), 214 | Return(Return), 215 | FuncDef(FuncDef), 216 | FuncDecl(FuncDecl), 217 | For(For), 218 | Switch(Switch), 219 | Struct(Struct), 220 | Import(Import), 221 | BlockStatement(BlockStatement), 222 | Break(Location), 223 | Continue(Location), 224 | Enum(Enum), 225 | } 226 | 227 | pub fn format_expressions(exprs: &Vec) -> String { 228 | exprs.iter().map(|expr| expr.to_string()).collect() 229 | } 230 | 231 | pub fn format_statements(stmts: &Vec) -> String { 232 | stmts.iter().map(|stmt| stmt.to_string()).collect() 233 | } 234 | 235 | #[derive(Debug, Clone, PartialEq)] 236 | pub struct Return { 237 | pub argument: Expression, 238 | pub span: Span, 239 | pub loc: Location, 240 | } 241 | 242 | #[derive(Debug, Clone, PartialEq)] 243 | pub enum ModuleSegment { 244 | SubModule(Identifier), 245 | } 246 | 247 | #[derive(Debug, Clone, PartialEq)] 248 | pub struct ModulePath { 249 | pub alias: Option, 250 | pub segments: Vec, 251 | } 252 | 253 | #[derive(Debug, Clone, PartialEq)] 254 | pub struct Import { 255 | pub paths: Vec, 256 | pub span: Span, 257 | pub loc: Location, 258 | } 259 | 260 | #[derive(Debug, Clone, PartialEq)] 261 | pub struct Struct { 262 | pub name: String, 263 | pub inherits: Vec, 264 | pub fields: Vec, 265 | pub methods: Vec, 266 | pub storage_class: StorageClass, 267 | pub loc: Location, 268 | pub span: Span, 269 | } 270 | 271 | #[derive(Debug, Clone, PartialEq)] 272 | pub struct StructInit { 273 | pub struct_name: ModuleImport, 274 | pub field_inits: Vec, 275 | pub loc: Location, 276 | pub span: Span, 277 | } 278 | 279 | #[derive(Debug, Clone, PartialEq)] 280 | pub struct Field { 281 | pub name: String, 282 | pub ty: TypeSpecifier, 283 | pub loc: Location, 284 | pub span: Span, 285 | } 286 | 287 | #[derive(Debug, Clone, PartialEq)] 288 | pub struct FieldInit { 289 | pub name: String, 290 | pub value: Expression, 291 | pub loc: Location, 292 | } 293 | 294 | #[derive(Debug, Clone, PartialEq)] 295 | pub struct For { 296 | pub initializer: Option, 297 | pub condition: Option, 298 | pub increment: Option, 299 | pub body: Box, 300 | pub span: Span, 301 | pub loc: Location, 302 | } 303 | 304 | #[derive(Debug, Clone, PartialEq)] 305 | pub struct Switch { 306 | pub value: Expression, 307 | pub sections: Option>, 308 | pub default: BlockStatement, 309 | pub body: Box, 310 | pub span: Span, 311 | pub loc: Location, 312 | } 313 | 314 | #[derive(Debug, Clone, PartialEq)] 315 | pub struct MatchPattern { 316 | pub raw: Expression, 317 | pub span: Span, 318 | pub loc: Location, 319 | } 320 | 321 | #[derive(Debug, Clone, PartialEq)] 322 | pub struct FuncDef { 323 | pub name: String, 324 | pub params: FuncParams, 325 | pub body: Box, 326 | pub return_type: Option, 327 | pub storage_class: StorageClass, 328 | pub span: Span, 329 | pub loc: Location, 330 | } 331 | 332 | #[derive(Debug, Clone, PartialEq)] 333 | pub struct FuncDecl { 334 | pub name: String, 335 | pub params: FuncParams, 336 | pub return_type: Option, 337 | pub storage_class: StorageClass, 338 | pub renamed_as: Option, 339 | pub span: Span, 340 | pub loc: Location, 341 | } 342 | 343 | #[derive(Debug, Clone, PartialEq)] 344 | pub enum StorageClass { 345 | Extern, 346 | Public, 347 | Internal, 348 | Inline, 349 | PublicInline, 350 | PublicExtern, 351 | } 352 | 353 | #[derive(Debug, Clone, PartialEq)] 354 | pub struct BlockStatement { 355 | pub exprs: Vec, 356 | pub span: Span, 357 | pub loc: Location, 358 | } 359 | 360 | #[derive(Debug, Clone, PartialEq)] 361 | pub struct Variable { 362 | pub name: String, 363 | pub ty: Option, 364 | pub expr: Option, 365 | pub span: Span, 366 | pub loc: Location, 367 | } 368 | 369 | #[derive(Debug, Clone, PartialEq)] 370 | pub struct Assignment { 371 | pub assign_to: Expression, 372 | pub expr: Expression, 373 | pub span: Span, 374 | pub loc: Location, 375 | } 376 | 377 | #[derive(Debug, Clone, PartialEq)] 378 | pub struct FuncParam { 379 | pub identifier: Identifier, 380 | pub ty: Option, 381 | pub default_value: Option, 382 | pub span: Span, 383 | pub loc: Location, 384 | } 385 | 386 | #[derive(Debug, Clone, PartialEq)] 387 | pub struct FuncParams { 388 | pub list: Vec, 389 | pub variadic: Option, 390 | } 391 | 392 | #[derive(Debug, Clone, PartialEq)] 393 | pub enum FuncVariadicParams { 394 | UntypedCStyle, 395 | Typed(Identifier, TypeSpecifier), 396 | } 397 | 398 | #[derive(Debug, Clone, PartialEq)] 399 | pub struct If { 400 | pub condition: Expression, 401 | pub consequent: Box, 402 | pub branches: Vec, 403 | pub alternate: Option>, 404 | pub span: Span, 405 | pub loc: Location, 406 | } 407 | -------------------------------------------------------------------------------- /crates/ast/src/format.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | use core::fmt; 3 | 4 | impl fmt::Display for BlockStatement { 5 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 6 | write!(f, "{}", format_statements(&self.exprs)) 7 | } 8 | } 9 | 10 | impl fmt::Display for Identifier { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!(f, "{}", self.name) 13 | } 14 | } 15 | 16 | impl fmt::Display for Literal { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | match self { 19 | Literal::Integer(integer) => write!(f, "{}", integer), 20 | Literal::Bool(bool) => write!(f, "{}", bool), 21 | Literal::String(string_type) => write!(f, "\"{}\"", string_type), 22 | Literal::Float(float) => write!(f, "{}", float), 23 | Literal::Char(ch) => write!(f, "{}", ch), 24 | Literal::Null => write!(f, "null"), 25 | } 26 | } 27 | } 28 | 29 | impl fmt::Display for UnaryOperatorType { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | match self { 32 | UnaryOperatorType::PreIncrement => write!(f, "++"), 33 | UnaryOperatorType::PreDecrement => write!(f, "--"), 34 | UnaryOperatorType::PostIncrement => write!(f, "++"), 35 | UnaryOperatorType::PostDecrement => write!(f, "--"), 36 | } 37 | } 38 | } 39 | 40 | impl fmt::Display for Cast { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | write!(f, "{} as {}", self.expr, self.target_type) 43 | } 44 | } 45 | 46 | impl fmt::Display for FuncCall { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | write!( 49 | f, 50 | "{}({})", 51 | self.operand, 52 | expression_series_to_string(self.arguments.clone()) 53 | ) 54 | } 55 | } 56 | 57 | impl fmt::Display for ModuleSegment { 58 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 59 | match self { 60 | ModuleSegment::SubModule(identifier) => write!(f, "{}", identifier.name), 61 | } 62 | } 63 | } 64 | 65 | impl fmt::Display for TypeSpecifier { 66 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 67 | match self { 68 | TypeSpecifier::TypeToken(token) => write!(f, "{}", token.kind), 69 | TypeSpecifier::Identifier(identifier) => write!(f, "{}", identifier), 70 | TypeSpecifier::ModuleImport(module_import) => write!(f, "{}", module_import), 71 | TypeSpecifier::Const(type_specifier) => write!(f, "const {}", type_specifier), 72 | TypeSpecifier::AddressOf(type_specifier) => write!(f, "{}&", type_specifier), 73 | TypeSpecifier::Dereference(type_specifier) => write!(f, "{}*", type_specifier), 74 | TypeSpecifier::Array(array_type_specifier) => { 75 | write!( 76 | f, 77 | "{}[{}]", 78 | array_type_specifier.element_type, 79 | match &array_type_specifier.size { 80 | ArrayCapacity::Fixed(size) => size.to_string(), 81 | ArrayCapacity::Dynamic => "".to_string(), 82 | } 83 | ) 84 | } 85 | } 86 | } 87 | } 88 | 89 | impl fmt::Display for Expression { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | match self { 92 | Expression::UnaryOperator(unary_operator) => write!( 93 | f, 94 | "{}{}", 95 | Expression::ModuleImport(unary_operator.module_import.clone()).to_string(), 96 | unary_operator.ty 97 | ), 98 | Expression::Identifier(identifier) => write!(f, "{}", identifier.name), 99 | Expression::Literal(literal) => write!(f, "{}", literal.to_string()), 100 | Expression::Prefix(UnaryExpression { operand, operator, .. }) => { 101 | write!(f, "({}{})", operator.kind, operand) 102 | } 103 | Expression::Infix(BinaryExpression { 104 | operator, left, right, .. 105 | }) => { 106 | write!(f, "({} {} {})", left, operator.kind, right) 107 | } 108 | Expression::FuncCall(func_call) => { 109 | write!(f, "{}", func_call) 110 | } 111 | Expression::FieldAccess(field_access) => { 112 | write!(f, "{}.{}", field_access.operand, field_access.field_name) 113 | } 114 | Expression::MethodCall(method_call) => { 115 | write!( 116 | f, 117 | "{}.{}({})", 118 | method_call.operand, 119 | method_call.method_name, 120 | expression_series_to_string(method_call.arguments.clone()) 121 | ) 122 | } 123 | Expression::Array(array) => { 124 | write!(f, "[{}]", expression_series_to_string(array.elements.clone())) 125 | } 126 | Expression::ArrayIndex(array_index) => { 127 | write!(f, "{}[{}]", array_index.expr.to_string(), array_index.index) 128 | } 129 | Expression::Assignment(assignment) => { 130 | write!(f, "{} = {}", assignment.assign_to.to_string(), assignment.expr) 131 | } 132 | Expression::AddressOf(expression) => write!(f, "&({})", expression), 133 | Expression::Dereference(expression) => write!(f, "(*{})", expression), 134 | Expression::StructInit(struct_init) => { 135 | write!( 136 | f, 137 | "{} {{", 138 | Expression::ModuleImport(struct_init.struct_name.clone()).to_string() 139 | )?; 140 | for field in &struct_init.field_inits { 141 | write!(f, "{}: {};", field.name, field.value)?; 142 | } 143 | write!(f, "}}") 144 | } 145 | Expression::Cast(cast_as) => { 146 | write!(f, "{}", cast_as) 147 | } 148 | Expression::ModuleImport(module_import) => { 149 | write!(f, "{}", module_import.to_string()) 150 | } 151 | Expression::TypeSpecifier(type_specifier) => write!(f, "{}", type_specifier), 152 | } 153 | } 154 | } 155 | 156 | fn expression_series_to_string(exprs: Vec) -> String { 157 | let str = exprs.iter().map(|c| c.to_string()).collect::>().join(", "); 158 | str 159 | } 160 | 161 | impl fmt::Display for Statement { 162 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 163 | write!(f, "{:?}", self) 164 | } 165 | } 166 | 167 | impl fmt::Display for Node { 168 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 169 | match self { 170 | Node::ProgramTree(program) => write!(f, "{}", program), 171 | Node::Statement(stmt) => write!(f, "{}", stmt), 172 | Node::Expression(expr) => write!(f, "{}", expr), 173 | } 174 | } 175 | } 176 | 177 | impl fmt::Display for ProgramTree { 178 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 179 | write!(f, "{}", format_statements(&self.body)) 180 | } 181 | } 182 | 183 | impl fmt::Display for ModuleImport { 184 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 185 | write!(f, "{}", module_segments_as_string(self.segments.clone())) 186 | } 187 | } 188 | 189 | pub fn module_segments_as_string(segments: Vec) -> String { 190 | segments 191 | .iter() 192 | .map(|p| p.to_string()) 193 | .collect::>() 194 | .join("::") 195 | } 196 | -------------------------------------------------------------------------------- /crates/ast/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub mod format; 3 | pub mod token; 4 | -------------------------------------------------------------------------------- /crates/ast/src/token.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::Literal; 2 | use std::fmt; 3 | 4 | #[derive(Debug, PartialEq, Clone)] 5 | pub struct Token { 6 | pub kind: TokenKind, 7 | pub span: Span, 8 | } 9 | 10 | #[derive(Debug, PartialEq, Clone)] 11 | pub enum TokenKind { 12 | Illegal, 13 | EOF, 14 | Identifier { name: String }, 15 | Literal(Literal), 16 | // Operators 17 | Plus, 18 | Minus, 19 | Slash, 20 | Asterisk, 21 | Percent, 22 | Increment, 23 | Decrement, 24 | // Symbols 25 | BackTick, 26 | Assign, 27 | Equal, 28 | NotEqual, 29 | Bang, 30 | LeftParen, 31 | RightParen, 32 | LeftBrace, 33 | RightBrace, 34 | LeftBracket, 35 | RightBracket, 36 | Comma, 37 | Hashtag, 38 | Dot, 39 | DoubleDot, 40 | TripleDot, 41 | DoubleQuote, 42 | SingleQuote, 43 | Pipe, 44 | Ampersand, 45 | Semicolon, 46 | Colon, 47 | DoubleColon, 48 | LessThan, 49 | GreaterThan, 50 | LessEqual, 51 | GreaterEqual, 52 | And, 53 | Or, 54 | // Keywords 55 | Function, 56 | Match, 57 | If, 58 | Else, 59 | Return, 60 | For, 61 | Break, 62 | Continue, 63 | Struct, 64 | Import, 65 | // Types 66 | Int, 67 | Int8, 68 | Int16, 69 | Int32, 70 | Int64, 71 | Int128, 72 | UInt, 73 | UInt8, 74 | UInt16, 75 | UInt32, 76 | UInt64, 77 | UInt128, 78 | Float16, 79 | Float32, 80 | Float64, 81 | Float128, 82 | Char, 83 | Void, 84 | String, 85 | Bool, 86 | Macro, 87 | In, 88 | Enum, 89 | True, 90 | False, 91 | Null, 92 | As, 93 | Const, 94 | 95 | // Object Visibility Keywords 96 | Extern, 97 | Public, 98 | Inline, 99 | } 100 | 101 | pub const PRIMITIVE_TYPES: &[TokenKind] = &[ 102 | TokenKind::Int, 103 | TokenKind::Int8, 104 | TokenKind::Int16, 105 | TokenKind::Int32, 106 | TokenKind::Int64, 107 | TokenKind::Int128, 108 | TokenKind::UInt, 109 | TokenKind::UInt8, 110 | TokenKind::UInt16, 111 | TokenKind::UInt32, 112 | TokenKind::UInt64, 113 | TokenKind::UInt128, 114 | TokenKind::Float16, 115 | TokenKind::Float32, 116 | TokenKind::Float64, 117 | TokenKind::Float128, 118 | TokenKind::Char, 119 | TokenKind::Bool, 120 | TokenKind::Void, 121 | TokenKind::String, 122 | ]; 123 | 124 | impl fmt::Display for TokenKind { 125 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 126 | match self { 127 | Self::Identifier { name } => write!(f, "{}", name), 128 | Self::Plus => write!(f, "+"), 129 | Self::Minus => write!(f, "-"), 130 | Self::Asterisk => write!(f, "*"), 131 | Self::Slash => write!(f, "/"), 132 | Self::Percent => write!(f, "%"), 133 | Self::Assign => write!(f, "="), 134 | Self::Equal => write!(f, "=="), 135 | Self::LeftParen => write!(f, "("), 136 | Self::RightParen => write!(f, ")"), 137 | Self::LeftBrace => write!(f, "{{"), 138 | Self::RightBrace => write!(f, "}}"), 139 | Self::LeftBracket => write!(f, "[["), 140 | Self::RightBracket => write!(f, "]]"), 141 | Self::Comma => write!(f, ","), 142 | Self::Hashtag => write!(f, "#"), 143 | Self::DoubleDot => write!(f, ".."), 144 | Self::TripleDot => write!(f, "..."), 145 | Self::Dot => write!(f, "."), 146 | Self::DoubleQuote => write!(f, "\""), 147 | Self::SingleQuote => write!(f, "'"), 148 | Self::Pipe => write!(f, "|"), 149 | Self::Ampersand => write!(f, "&"), 150 | Self::LessThan => write!(f, "<"), 151 | Self::GreaterThan => write!(f, ">"), 152 | Self::LessEqual => write!(f, "<="), 153 | Self::GreaterEqual => write!(f, ">="), 154 | Self::And => write!(f, "&&"), 155 | Self::Or => write!(f, "||"), 156 | Self::Semicolon => write!(f, ";"), 157 | Self::Colon => write!(f, ":"), 158 | // Keywords 159 | Self::Function => write!(f, "fn"), 160 | Self::Match => write!(f, "match"), 161 | Self::Struct => write!(f, "struct"), 162 | Self::Import => write!(f, "import"), 163 | Self::If => write!(f, "if"), 164 | Self::Else => write!(f, "else"), 165 | Self::Return => write!(f, "return"), 166 | Self::For => write!(f, "for"), 167 | Self::Break => write!(f, "break"), 168 | Self::Continue => write!(f, "continue"), 169 | Self::True => write!(f, "true"), 170 | Self::False => write!(f, "false"), 171 | Self::Null => write!(f, "null"), 172 | Self::Int => write!(f, "int"), 173 | Self::Int8 => write!(f, "int8"), 174 | Self::Int16 => write!(f, "int16"), 175 | Self::Int32 => write!(f, "int32"), 176 | Self::Int64 => write!(f, "int64"), 177 | Self::Int128 => write!(f, "int128"), 178 | Self::UInt => write!(f, "uint"), 179 | Self::UInt16 => write!(f, "uint16"), 180 | Self::UInt32 => write!(f, "uint32"), 181 | Self::UInt64 => write!(f, "uint64"), 182 | Self::UInt128 => write!(f, "uint128"), 183 | Self::Float16 => write!(f, "float16"), 184 | Self::Float32 => write!(f, "float32"), 185 | Self::Float64 => write!(f, "float64"), 186 | Self::Float128 => write!(f, "float128"), 187 | Self::Void => write!(f, "void"), 188 | Self::Enum => write!(f, "enum"), 189 | Self::Macro => write!(f, "macro"), 190 | Self::In => write!(f, "in"), 191 | Self::As => write!(f, "as"), 192 | Self::Extern => write!(f, "extern"), 193 | Self::Inline => write!(f, "inline"), 194 | Self::Public => write!(f, "public"), 195 | Self::Const => write!(f, "const"), 196 | Self::Literal(literal) => match literal { 197 | Literal::Integer(v) => write!(f, "{}", v), 198 | Literal::Float(v) => write!(f, "{}", v), 199 | Literal::String(v) => write!(f, "{}", v), 200 | Literal::Char(v) => write!(f, "{}", v), 201 | Literal::Bool(v) => write!(f, "{}", v), 202 | Literal::Null => write!(f, "null"), 203 | }, 204 | Self::String => write!(f, "string"), 205 | // ETC 206 | Self::Illegal => write!(f, "ILLEGAL"), 207 | Self::EOF => write!(f, "EOF"), 208 | _ => write!(f, "INVALID_TOKEN"), 209 | } 210 | } 211 | } 212 | 213 | #[derive(Debug, PartialEq, Eq, Clone)] 214 | pub struct Span { 215 | pub start: usize, 216 | pub end: usize, 217 | } 218 | 219 | impl Span { 220 | pub fn new(start: usize, end: usize) -> Self { 221 | Self { start, end } 222 | } 223 | } 224 | 225 | impl Default for Span { 226 | fn default() -> Self { 227 | Self { 228 | start: Default::default(), 229 | end: Default::default(), 230 | } 231 | } 232 | } 233 | 234 | #[derive(Debug, PartialEq, Eq, Clone)] 235 | pub struct Location { 236 | pub line: usize, 237 | pub column: usize, 238 | } 239 | 240 | impl Location { 241 | pub fn new(line: usize, column: usize) -> Self { 242 | Self { line, column } 243 | } 244 | } 245 | 246 | impl Default for Location { 247 | fn default() -> Self { 248 | Self { 249 | line: Default::default(), 250 | column: Default::default(), 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /crates/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | codegen_llvm = { path = "../codegen_llvm", version = "*" } 8 | layout = { path = "../layout", version = "*" } 9 | parser = { path = "../parser", version = "*" } 10 | clap = { version = "4.5.34", features = ["derive"] } 11 | lexer = { path = "../lexer", version = "*" } 12 | utils = { path = "../utils", version = "*" } 13 | ast = { path = "../ast", version = "*" } 14 | colorized = "1.0.0" 15 | toml = "0.8.20" 16 | -------------------------------------------------------------------------------- /crates/cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use codegen_llvm::opts::BuildDir; 2 | use ::parser::parse_program; 3 | use clap::*; 4 | use codegen_llvm::CodeGenLLVM; 5 | use codegen_llvm::build::OutputKind; 6 | use codegen_llvm::diag::*; 7 | use utils::fs::get_directory_of_file; 8 | 9 | const PROJECT_FILE_PATH: &str = "Project.toml"; 10 | 11 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 12 | enum CpuValue { 13 | None, 14 | // TODO 15 | } 16 | 17 | impl std::fmt::Display for CpuValue { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | match self { 20 | CpuValue::None => write!(f, "none"), 21 | } 22 | } 23 | } 24 | 25 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 26 | enum OptimizeLevel { 27 | None, 28 | O1, 29 | O2, 30 | O3, 31 | } 32 | 33 | impl OptimizeLevel { 34 | pub fn as_integer(&self) -> i32 { 35 | match self { 36 | OptimizeLevel::None => 0, 37 | OptimizeLevel::O1 => 1, 38 | OptimizeLevel::O2 => 2, 39 | OptimizeLevel::O3 => 3, 40 | } 41 | } 42 | } 43 | 44 | impl std::fmt::Display for OptimizeLevel { 45 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 46 | match self { 47 | OptimizeLevel::None => write!(f, "none"), 48 | OptimizeLevel::O1 => write!(f, "o1"), 49 | OptimizeLevel::O2 => write!(f, "o2"), 50 | OptimizeLevel::O3 => write!(f, "o3"), 51 | } 52 | } 53 | } 54 | 55 | #[derive(Parser, Debug, Clone)] 56 | struct CompilerOptions { 57 | #[clap(long, value_enum, default_value_t = CpuValue::None, help = "Set CPU name")] 58 | cpu: CpuValue, 59 | 60 | #[clap(long, value_enum, default_value_t = OptimizeLevel::None, help = "Set optimization level")] 61 | optimize: OptimizeLevel, 62 | 63 | #[clap(long, value_name = "LIBRARY_PATH", help = "Add a library search path")] 64 | library_path: Vec, 65 | 66 | #[clap(long = "library", value_name = "LIB", help = "Link a library")] 67 | libraries: Vec, 68 | 69 | #[clap(long = "sources", value_name = "SOURCES", help = "Source files")] 70 | sources_dir: Vec, 71 | 72 | #[clap( 73 | long = "build-dir", 74 | value_name = "PATH", 75 | help = "Specifies the directory where build artifacts will be stored. 76 | This includes compiled binaries, intermediate files, and other outputs 77 | generated during the build process. If not provided, a tmp directory will be used." 78 | )] 79 | build_dir: Option, 80 | } 81 | 82 | impl CompilerOptions { 83 | pub fn to_compiler_options(&self) -> codegen_llvm::opts::Options { 84 | codegen_llvm::opts::Options { 85 | opt_level: self.optimize.as_integer(), 86 | cpu: self.cpu.to_string(), 87 | library_path: self.library_path.clone(), 88 | libraries: self.libraries.clone(), 89 | sources_dir: self.sources_dir.clone(), 90 | project_name: None, 91 | project_version: None, 92 | cyrus_version: None, 93 | authors: None, 94 | project_type: None, 95 | build_dir: { 96 | match self.build_dir.clone() { 97 | Some(path) => BuildDir::Provided(path), 98 | None => BuildDir::Default, 99 | } 100 | }, 101 | } 102 | } 103 | } 104 | 105 | #[derive(clap::Parser, Clone)] 106 | #[command()] 107 | struct Args { 108 | #[command(subcommand)] 109 | cmd: Commands, 110 | } 111 | 112 | #[derive(clap::Subcommand, Debug, Clone)] 113 | enum Commands { 114 | #[clap(about = "Create a new project", display_order = 0)] 115 | New { 116 | project_name: String, 117 | #[clap(long, default_value_t = false)] 118 | lib: bool, 119 | }, 120 | 121 | #[clap(about = "Execute a compiled program", display_order = 1)] 122 | Run { 123 | file_path: Option, 124 | #[clap(flatten)] 125 | compiler_options: CompilerOptions, 126 | }, 127 | 128 | #[clap(about = "Fetches a library into vendor directory.", display_order = 2)] 129 | Fetch { libraries: String }, 130 | 131 | #[clap(about = "Compile source code into an executable", display_order = 3)] 132 | Build { 133 | file_path: Option, 134 | #[clap(long, short)] 135 | output_path: Option, 136 | #[clap(flatten)] 137 | compiler_options: CompilerOptions, 138 | }, 139 | 140 | #[clap(about = "Generate an object file", display_order = 4)] 141 | Object { 142 | file_path: Option, 143 | #[clap(long, short)] 144 | output_path: Option, 145 | #[clap(flatten)] 146 | compiler_options: CompilerOptions, 147 | }, 148 | 149 | #[clap(about = "Generate a dynamic library (shared object)", display_order = 5)] 150 | Dylib { 151 | file_path: Option, 152 | #[clap(long, short)] 153 | output_path: Option, 154 | #[clap(flatten)] 155 | compiler_options: CompilerOptions, 156 | }, 157 | 158 | #[clap(about = "Emit LLVM IR as a .ll file per module.", display_order = 6)] 159 | EmitLLVM { 160 | file_path: Option, 161 | #[clap(long, short)] 162 | output_path: Option, 163 | #[clap(flatten)] 164 | compiler_options: CompilerOptions, 165 | }, 166 | 167 | #[clap(about = "Emit asm as a .s file per module.", display_order = 7)] 168 | EmitASM { 169 | file_path: Option, 170 | #[clap(long, short)] 171 | output_path: Option, 172 | #[clap(flatten)] 173 | compiler_options: CompilerOptions, 174 | }, 175 | 176 | #[clap(about = "Print version information", display_order = 8)] 177 | Version, 178 | } 179 | 180 | fn project_file_required() { 181 | if !std::path::Path::new(PROJECT_FILE_PATH).exists() { 182 | display_single_diag(Diag { 183 | level: DiagLevel::Error, 184 | kind: DiagKind::Custom(format!("'{}' not found in current directory.", PROJECT_FILE_PATH)), 185 | location: None, 186 | }); 187 | std::process::exit(1); 188 | } 189 | } 190 | 191 | macro_rules! init_compiler { 192 | ($context:expr, $file_path:expr, $opts:expr, $output_kind:expr) => {{ 193 | if let Some(file_path) = $file_path { 194 | if !file_path.ends_with(".cyr") { 195 | display_single_diag(Diag { 196 | level: DiagLevel::Error, 197 | kind: DiagKind::Custom("Invalid file extension.".to_string()), 198 | location: None, 199 | }); 200 | std::process::exit(1); 201 | } 202 | 203 | let mut opts = $opts.clone(); 204 | 205 | opts.sources_dir = { 206 | if $opts.sources_dir.len() > 0 { 207 | $opts.sources_dir.clone() 208 | } else { 209 | match get_directory_of_file(file_path.clone()) { 210 | Some(source_dir) => [source_dir].to_vec(), 211 | None => { 212 | display_single_diag(Diag { 213 | level: DiagLevel::Error, 214 | kind: DiagKind::Custom("Could not get directory path of the input file.".to_string()), 215 | location: None, 216 | }); 217 | std::process::exit(1); 218 | } 219 | } 220 | } 221 | }; 222 | 223 | let (program, file_name) = parse_program(file_path.clone()); 224 | let codegen_llvm = match CodeGenLLVM::new( 225 | $context, 226 | file_path, 227 | file_name.clone(), 228 | program, 229 | opts, 230 | true, 231 | $output_kind, 232 | ) { 233 | Ok(instance) => instance, 234 | Err(err) => { 235 | display_single_diag(Diag { 236 | level: DiagLevel::Error, 237 | kind: DiagKind::Custom(format!("Creating CodeGenLLVM instance failed:{}", err.to_string())), 238 | location: None, 239 | }); 240 | std::process::exit(1); 241 | } 242 | }; 243 | codegen_llvm 244 | } else { 245 | project_file_required(); 246 | 247 | match codegen_llvm::opts::Options::read_toml(PROJECT_FILE_PATH.to_string()) { 248 | Ok(mut options) => { 249 | if !std::path::Path::new("src/main.cyr").exists() { 250 | display_single_diag(Diag { 251 | level: DiagLevel::Error, 252 | kind: DiagKind::Custom("'src/main.cyr' file not found.".to_string()), 253 | location: None, 254 | }); 255 | std::process::exit(1); 256 | } 257 | 258 | let main_file_path = std::path::Path::new("src/main.cyr") 259 | .canonicalize() 260 | .unwrap_or_else(|_| { 261 | display_single_diag(Diag { 262 | level: DiagLevel::Error, 263 | kind: DiagKind::Custom("Failed to get absolute path for 'src/main.cyr'.".to_string()), 264 | location: None, 265 | }); 266 | std::process::exit(1); 267 | }) 268 | .to_str() 269 | .unwrap() 270 | .to_string(); 271 | 272 | options.sources_dir = { 273 | if $opts.sources_dir.len() > 0 { 274 | $opts.sources_dir.clone() 275 | } else { 276 | match get_directory_of_file(main_file_path.clone()) { 277 | Some(source_dir) => [source_dir].to_vec(), 278 | None => { 279 | display_single_diag(Diag { 280 | level: DiagLevel::Error, 281 | kind: DiagKind::Custom( 282 | "Could not get directory path of the input file.".to_string(), 283 | ), 284 | location: None, 285 | }); 286 | std::process::exit(1); 287 | } 288 | } 289 | } 290 | }; 291 | 292 | let (program, file_name) = parse_program(main_file_path.clone()); 293 | let codegen_llvm = match CodeGenLLVM::new( 294 | $context, 295 | main_file_path, 296 | file_name.clone(), 297 | program, 298 | options, 299 | false, 300 | $output_kind, 301 | ) { 302 | Ok(instance) => instance, 303 | Err(err) => { 304 | display_single_diag(Diag { 305 | level: DiagLevel::Error, 306 | kind: DiagKind::Custom(format!( 307 | "Creating CodeGenLLVM instance failed:{}", 308 | err.to_string() 309 | )), 310 | location: None, 311 | }); 312 | std::process::exit(1); 313 | } 314 | }; 315 | 316 | codegen_llvm 317 | } 318 | Err(err) => { 319 | display_single_diag(Diag { 320 | level: DiagLevel::Error, 321 | kind: DiagKind::Custom(err.to_string()), 322 | location: None, 323 | }); 324 | std::process::exit(1); 325 | } 326 | } 327 | } 328 | }}; 329 | } 330 | 331 | pub fn main() { 332 | let context = CodeGenLLVM::new_context(); 333 | let version = env!("CARGO_PKG_VERSION"); 334 | let args = Args::parse(); 335 | 336 | match args.cmd { 337 | Commands::Fetch { .. } => { 338 | todo!(); 339 | } 340 | Commands::New { project_name, lib } => { 341 | if lib { 342 | if let Err(err) = layout::create_library_project(project_name) { 343 | display_single_diag(Diag { 344 | level: DiagLevel::Error, 345 | kind: DiagKind::Custom(err), 346 | location: None, 347 | }); 348 | std::process::exit(1); 349 | } 350 | } else { 351 | if let Err(err) = layout::create_project(project_name) { 352 | display_single_diag(Diag { 353 | level: DiagLevel::Error, 354 | kind: DiagKind::Custom(err), 355 | location: None, 356 | }); 357 | std::process::exit(1); 358 | } 359 | } 360 | } 361 | Commands::Run { 362 | file_path, 363 | compiler_options, 364 | } => { 365 | if file_path.is_none() { 366 | project_file_required(); 367 | } 368 | 369 | let mut codegen_llvm = init_compiler!( 370 | &context, 371 | file_path.clone(), 372 | compiler_options.to_compiler_options(), 373 | OutputKind::None 374 | ); 375 | codegen_llvm.compile(); 376 | codegen_llvm.compilation_process_finished(); 377 | codegen_llvm.execute(); 378 | } 379 | Commands::EmitLLVM { 380 | file_path, 381 | output_path, 382 | compiler_options, 383 | } => { 384 | if file_path.is_none() && output_path.is_none() { 385 | project_file_required(); 386 | } 387 | 388 | let mut codegen_llvm = init_compiler!( 389 | &context, 390 | file_path.clone(), 391 | compiler_options.to_compiler_options(), 392 | OutputKind::LlvmIr(output_path) 393 | ); 394 | codegen_llvm.compile(); 395 | codegen_llvm.compilation_process_finished(); 396 | } 397 | Commands::EmitASM { 398 | file_path, 399 | output_path, 400 | compiler_options, 401 | } => { 402 | if file_path.is_none() && output_path.is_none() { 403 | project_file_required(); 404 | } 405 | 406 | let mut codegen_llvm = init_compiler!( 407 | &context, 408 | file_path.clone(), 409 | compiler_options.to_compiler_options(), 410 | OutputKind::Asm(output_path) 411 | ); 412 | codegen_llvm.compile(); 413 | codegen_llvm.compilation_process_finished(); 414 | } 415 | Commands::Build { 416 | file_path, 417 | output_path, 418 | compiler_options, 419 | } => { 420 | if file_path.is_none() && output_path.is_none() { 421 | project_file_required(); 422 | } 423 | 424 | let mut codegen_llvm = init_compiler!( 425 | &context, 426 | file_path.clone(), 427 | compiler_options.to_compiler_options(), 428 | OutputKind::None 429 | ); 430 | codegen_llvm.compile(); 431 | codegen_llvm.generate_executable_file(output_path); 432 | codegen_llvm.compilation_process_finished(); 433 | } 434 | Commands::Object { 435 | file_path, 436 | output_path, 437 | compiler_options, 438 | } => { 439 | if file_path.is_none() && output_path.is_none() { 440 | project_file_required(); 441 | } 442 | 443 | let mut codegen_llvm = init_compiler!( 444 | &context, 445 | file_path.clone(), 446 | compiler_options.to_compiler_options(), 447 | OutputKind::ObjectFile(output_path) 448 | ); 449 | codegen_llvm.compile(); 450 | codegen_llvm.compilation_process_finished(); 451 | } 452 | Commands::Dylib { 453 | file_path, 454 | output_path, 455 | compiler_options, 456 | } => { 457 | if file_path.is_none() && output_path.is_none() { 458 | project_file_required(); 459 | } 460 | 461 | let mut codegen_llvm = init_compiler!( 462 | &context, 463 | file_path.clone(), 464 | compiler_options.to_compiler_options(), 465 | OutputKind::Dylib(output_path) 466 | ); 467 | codegen_llvm.compile(); 468 | codegen_llvm.compilation_process_finished(); 469 | } 470 | Commands::Version => { 471 | println!("Cyrus {}", version) 472 | } 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /crates/codegen_llvm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen_llvm" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | inkwell = { version = "0.5.0", features = ["llvm18-0"] } 8 | clap = { version = "4.5.23", features = ["derive"] } 9 | ast = { path = "../ast", version = "*" } 10 | utils = { path = "../utils", version = "*" } 11 | parser = { path = "../parser", version = "*" } 12 | colorized = "1.0.0" 13 | serde = { version = "1.0.219", features = ["derive"] } 14 | toml = "0.8.20" 15 | crc32fast = "1.4.2" 16 | serde_json = "1.0.140" 17 | rand = "0.9.0" 18 | console = "0.15.11" 19 | 20 | [build-dependencies] 21 | cc = "1.2.19" 22 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/build.rs: -------------------------------------------------------------------------------- 1 | use crate::CodeGenLLVM; 2 | use crate::diag::*; 3 | use crate::opts::BuildDir; 4 | use ast::ast::StorageClass; 5 | use inkwell::llvm_sys::core::LLVMFunctionType; 6 | use inkwell::llvm_sys::prelude::LLVMTypeRef; 7 | use inkwell::module::Linkage; 8 | use inkwell::passes::PassManager; 9 | use inkwell::targets::FileType; 10 | use inkwell::types::AsTypeRef; 11 | use inkwell::types::FunctionType; 12 | use rand::Rng; 13 | use rand::distr::Alphanumeric; 14 | use serde::Deserialize; 15 | use serde::Serialize; 16 | use std::collections::HashMap; 17 | use std::env; 18 | use std::fs; 19 | use std::fs::File; 20 | use std::io::Read; 21 | use std::io::Write; 22 | use std::ops::DerefMut; 23 | use std::path::Path; 24 | use std::process::Stdio; 25 | use std::process::exit; 26 | use utils::fs::absolute_to_relative; 27 | use utils::fs::dylib_extension; 28 | use utils::fs::ensure_output_dir; 29 | use utils::fs::executable_extension; 30 | use utils::generate_random_hex::generate_random_hex; 31 | 32 | const SOURCES_DIR_PATH: &str = "build/sources"; 33 | const OBJECTS_FILENAME: &str = "build/obj"; 34 | const MANIFEST_FILENAME: &str = "manifest.json"; 35 | const OUTPUT_FILENAME: &str = "build/output"; 36 | 37 | #[derive(Debug, Clone)] 38 | pub enum OutputKind { 39 | None, 40 | LlvmIr(Option), 41 | Asm(Option), 42 | ObjectFile(Option), 43 | Dylib(Option), 44 | } 45 | 46 | #[derive(Debug, Clone, Serialize, Deserialize)] 47 | pub struct BuildManifest { 48 | pub sources: HashMap, 49 | pub objects: HashMap, 50 | } 51 | 52 | impl Default for BuildManifest { 53 | fn default() -> Self { 54 | Self { 55 | sources: HashMap::new(), 56 | objects: HashMap::new(), 57 | } 58 | } 59 | } 60 | 61 | impl BuildManifest { 62 | fn save_file(&self, build_dir: String) { 63 | let manifest_filepath = format!("{}/{}", build_dir, MANIFEST_FILENAME); 64 | 65 | fs::remove_file(manifest_filepath.clone()).unwrap(); 66 | let mut file = File::create(manifest_filepath).unwrap(); 67 | file.write(serde_json::to_string(&self).unwrap().as_bytes()).unwrap(); 68 | } 69 | 70 | fn read_file(&self, build_dir: String) -> Self { 71 | let manifest_filepath = format!("{}/{}", build_dir, MANIFEST_FILENAME); 72 | 73 | match serde_json::from_str::(&utils::fs::read_file(manifest_filepath.to_string()).0) { 74 | Ok(manifest) => manifest, 75 | Err(err) => { 76 | display_single_diag(Diag { 77 | level: DiagLevel::Error, 78 | kind: DiagKind::Custom(format!("Failed to parse '{}': {}", manifest_filepath, err.to_string())), 79 | location: None, 80 | }); 81 | exit(1); 82 | } 83 | } 84 | } 85 | } 86 | 87 | impl<'ctx> CodeGenLLVM<'ctx> { 88 | pub(crate) fn execute_linker(&self, output_path: String, object_files: Vec, extra_args: Vec) { 89 | // TODO Consider to make linker dynamic through Project.toml and CLI Program. 90 | let linker = "cc"; 91 | 92 | let mut linker_command = std::process::Command::new(linker); 93 | linker_command.arg("-o").arg(output_path); 94 | 95 | for path in object_files { 96 | linker_command.arg(path); 97 | } 98 | 99 | for path in extra_args { 100 | linker_command.arg(path); 101 | } 102 | 103 | match linker_command.output() { 104 | Ok(output) => { 105 | if !output.status.success() { 106 | eprintln!("Linker error: {}", String::from_utf8_lossy(&output.stderr)); 107 | exit(1); 108 | } 109 | } 110 | Err(err) => { 111 | display_single_diag(Diag { 112 | level: DiagLevel::Error, 113 | kind: DiagKind::Custom(format!("Failed execute linker ({}):\n{}", linker, err.to_string())), 114 | location: None, 115 | }); 116 | exit(1); 117 | } 118 | } 119 | 120 | if let BuildDir::Default = self.opts.build_dir { 121 | fs::remove_dir_all(format!("{}/{}", self.final_build_dir.clone(), "build")).unwrap(); 122 | fs::remove_file(format!("{}/{}", self.final_build_dir.clone(), MANIFEST_FILENAME)).unwrap(); 123 | } 124 | } 125 | 126 | pub(crate) fn generate_output_file_name(&self) -> String { 127 | let mut file_name = Path::new(&self.file_path.clone()) 128 | .with_extension("") 129 | .to_str() 130 | .unwrap() 131 | .to_string(); 132 | let wd = env::current_dir().unwrap(); 133 | file_name = file_name.replace(wd.to_str().unwrap(), ""); 134 | file_name = file_name 135 | .trim_start_matches('/') // remove leading slash 136 | .replace('/', "_") // replace slashes with underscores 137 | .to_string(); 138 | file_name 139 | } 140 | 141 | pub(crate) fn generate_output(&mut self) { 142 | match &self.output_kind { 143 | OutputKind::LlvmIr(output_path) => self.emit_llvm_ir(output_path.clone()), 144 | OutputKind::Asm(output_path) => self.emit_asm(output_path.clone()), 145 | OutputKind::ObjectFile(output_path) => self.generate_object_file(output_path.clone()), 146 | OutputKind::Dylib(output_path) => self.generate_dynamic_library(output_path.clone()), 147 | OutputKind::None => {} 148 | } 149 | } 150 | 151 | fn emit_llvm_ir(&mut self, output_path: Option) { 152 | if let Some(output_path) = output_path { 153 | ensure_output_dir(Path::new(&output_path.clone())); 154 | let file_path = format!("{}/{}.ll", output_path, self.generate_output_file_name()); 155 | 156 | if let Err(err) = self.module.borrow_mut().deref_mut().print_to_file(file_path) { 157 | display_single_diag(Diag { 158 | level: DiagLevel::Error, 159 | kind: DiagKind::Custom(format!("Failed to print llvm-ir into file:\n{}", err.to_string())), 160 | location: None, 161 | }); 162 | exit(1); 163 | } 164 | } else { 165 | display_single_diag(Diag { 166 | level: DiagLevel::Error, 167 | kind: DiagKind::Custom("Output directory must be specified to generate llvm-ir.".to_string()), 168 | location: None, 169 | }); 170 | exit(1); 171 | } 172 | } 173 | 174 | fn emit_asm(&mut self, output_path: Option) { 175 | if let Some(output_path) = output_path { 176 | ensure_output_dir(Path::new(&output_path.clone())); 177 | let file_path = format!("{}/{}.asm", output_path, self.generate_output_file_name()); 178 | 179 | if let Err(err) = self.target_machine.write_to_file( 180 | &self.module.borrow_mut().deref_mut(), 181 | FileType::Assembly, 182 | Path::new(&file_path), 183 | ) { 184 | display_single_diag(Diag { 185 | level: DiagLevel::Error, 186 | kind: DiagKind::Custom(format!("Failed to write assembly into file:\n{}", err.to_string())), 187 | location: None, 188 | }); 189 | exit(1); 190 | } 191 | } else { 192 | display_single_diag(Diag { 193 | level: DiagLevel::Error, 194 | kind: DiagKind::Custom("Output directory must be specified to generate assembly.".to_string()), 195 | location: None, 196 | }); 197 | exit(1); 198 | } 199 | } 200 | 201 | pub fn generate_executable_file(&self, output_path: Option) { 202 | let object_files: Vec = self.build_manifest.objects.values().cloned().collect(); 203 | 204 | let output_path = { 205 | if let Some(path) = output_path { 206 | path 207 | } else { 208 | if self.compiler_invoked_single { 209 | display_single_diag(Diag { 210 | level: DiagLevel::Error, 211 | kind: DiagKind::Custom( 212 | "Output file path must be specified to generate executable.".to_string(), 213 | ), 214 | location: None, 215 | }); 216 | exit(1); 217 | } 218 | 219 | ensure_output_dir(Path::new(OUTPUT_FILENAME)); 220 | format!("{}/{}", OUTPUT_FILENAME, { 221 | if let Some(file_name) = &self.opts.project_name { 222 | file_name.clone() 223 | } else { 224 | Path::new(&self.file_path) 225 | .file_stem() 226 | .unwrap() 227 | .to_str() 228 | .unwrap() 229 | .replace(".", "_") 230 | .replace("/", "") 231 | .to_string() 232 | } 233 | }) 234 | } 235 | }; 236 | 237 | self.execute_linker(output_path, object_files, Vec::new()); 238 | } 239 | 240 | fn generate_dynamic_library(&self, output_path: Option) { 241 | let object_files: Vec = self.build_manifest.objects.values().cloned().collect(); 242 | 243 | let output_path = { 244 | if let Some(path) = output_path { 245 | path 246 | } else { 247 | display_single_diag(Diag { 248 | level: DiagLevel::Error, 249 | kind: DiagKind::Custom( 250 | "Output directory must be specified to generate dynamic library.".to_string(), 251 | ), 252 | location: None, 253 | }); 254 | exit(1); 255 | } 256 | }; 257 | 258 | ensure_output_dir(Path::new(&output_path.clone())); 259 | let output_path = format!( 260 | "{}/{}.{}", 261 | output_path, 262 | self.generate_output_file_name(), 263 | dylib_extension() 264 | ); 265 | 266 | self.execute_linker(output_path, object_files, vec!["-fPIC".to_string()]); 267 | } 268 | 269 | fn generate_object_file(&self, output_path: Option) { 270 | if let Some(output_path) = output_path { 271 | ensure_output_dir(Path::new(&output_path.clone())); 272 | let file_path = format!("{}/{}.o", output_path, self.generate_output_file_name()); 273 | self.generate_object_file_internal(file_path); 274 | } else { 275 | display_single_diag(Diag { 276 | level: DiagLevel::Error, 277 | kind: DiagKind::Custom("Output directory path must be specified to generate object files.".to_string()), 278 | location: None, 279 | }); 280 | exit(1); 281 | } 282 | } 283 | 284 | pub(crate) fn optimize(&self) { 285 | let fpm = PassManager::create(self.module.borrow_mut().deref_mut()); 286 | self.target_machine.add_analysis_passes(&fpm); 287 | fpm.initialize(); 288 | fpm.finalize(); 289 | } 290 | 291 | pub(crate) fn build_entry_point(&mut self) { 292 | if let Some(mut main_func) = self.entry_point.clone() { 293 | main_func.name = format!("main_{}", generate_random_hex()); 294 | if main_func.storage_class != StorageClass::Internal { 295 | display_single_diag(Diag { 296 | level: DiagLevel::Error, 297 | kind: DiagKind::NonInternalEntryPoint, 298 | location: Some(DiagLoc { 299 | file: self.file_path.clone(), 300 | line: main_func.loc.line, 301 | column: main_func.loc.column, 302 | length: main_func.span.end, 303 | }), 304 | }); 305 | exit(1); 306 | } 307 | 308 | // wrap actual main_func as entry point 309 | let return_type = self.context.i32_type(); 310 | let mut param_types: Vec = Vec::new(); 311 | let fn_type = unsafe { 312 | FunctionType::new(LLVMFunctionType( 313 | return_type.as_type_ref(), 314 | param_types.as_mut_ptr(), 315 | param_types.len() as u32, 316 | 0, 317 | )) 318 | }; 319 | 320 | let main_func_ptr = self.build_func_def(main_func.clone()); 321 | 322 | let entry_point = 323 | self.module 324 | .borrow_mut() 325 | .deref_mut() 326 | .add_function("main", fn_type, Some(Linkage::External)); 327 | 328 | let entry_block = self.context.append_basic_block(entry_point, "entry"); 329 | self.builder.position_at_end(entry_block); 330 | 331 | self.builder.build_call(main_func_ptr, &[], "call_main").unwrap(); 332 | 333 | self.builder 334 | .build_return(Some(&return_type.const_int(0, false))) 335 | .unwrap(); 336 | } else if !self.is_entry_point { 337 | } else { 338 | display_single_diag(Diag { 339 | level: DiagLevel::Error, 340 | kind: DiagKind::NoEntryPointDetected, 341 | location: None, 342 | }); 343 | exit(1); 344 | } 345 | } 346 | 347 | pub fn execute(&mut self) { 348 | let temp_file_path = format!( 349 | "{}/{}{}", 350 | env::temp_dir().to_str().unwrap(), 351 | generate_random_hex(), 352 | executable_extension() 353 | ); 354 | self.generate_executable_file(Some(temp_file_path.clone())); 355 | std::process::Command::new(temp_file_path.clone()) 356 | .stdout(Stdio::inherit()) 357 | .stderr(Stdio::inherit()) 358 | .status() 359 | .unwrap_or_else(|e| { 360 | display_single_diag(Diag { 361 | level: DiagLevel::Error, 362 | kind: DiagKind::Custom(format!("Failed to execute process: {}", e.to_string())), 363 | location: None, 364 | }); 365 | exit(1); 366 | }); 367 | } 368 | 369 | pub(crate) fn generate_object_file_internal(&self, output_path: String) { 370 | if let Err(err) = self.target_machine.write_to_file( 371 | &self.module.borrow_mut().deref_mut(), 372 | FileType::Object, 373 | Path::new(&output_path), 374 | ) { 375 | display_single_diag(Diag { 376 | level: DiagLevel::Error, 377 | kind: DiagKind::Custom(format!("Failed to generate object file: {}", err.to_string())), 378 | location: None, 379 | }); 380 | exit(1); 381 | } 382 | } 383 | 384 | pub(crate) fn ensure_build_manifest(&mut self, build_dir: String) { 385 | if !fs::exists(format!("{}/{}", build_dir, MANIFEST_FILENAME)).unwrap() { 386 | match File::create(format!("{}/{}", build_dir, MANIFEST_FILENAME)) { 387 | Ok(mut file) => { 388 | file.write(serde_json::to_string(&BuildManifest::default()).unwrap().as_bytes()) 389 | .unwrap(); 390 | } 391 | Err(err) => { 392 | display_single_diag(Diag { 393 | level: DiagLevel::Error, 394 | kind: DiagKind::Custom(format!( 395 | "Failed to create '{}': {}", 396 | format!("{}/{}", build_dir, MANIFEST_FILENAME), 397 | err.to_string() 398 | )), 399 | location: None, 400 | }); 401 | exit(1); 402 | } 403 | } 404 | } else { 405 | self.build_manifest = self.build_manifest.read_file(build_dir); 406 | } 407 | } 408 | 409 | pub(crate) fn ensure_build_directory(&self, build_dir: String) { 410 | ensure_output_dir(Path::new(&build_dir.clone())); 411 | ensure_output_dir(Path::new(&format!("{}/{}", build_dir, OBJECTS_FILENAME))); 412 | ensure_output_dir(Path::new(&format!("{}/{}", build_dir, SOURCES_DIR_PATH))); 413 | } 414 | 415 | pub(crate) fn hash_source_code(&mut self) -> String { 416 | let source_code = utils::fs::read_file(self.file_path.clone()).0; 417 | crc32fast::hash(source_code.as_bytes()).to_string() 418 | } 419 | 420 | pub(crate) fn object_file_exists(&mut self) -> bool { 421 | let wd = std::env::current_dir().unwrap().to_str().unwrap().to_string(); 422 | let file_path = absolute_to_relative(self.file_path.clone(), wd).unwrap(); 423 | self.build_manifest.objects.get(&file_path.clone()).is_some() 424 | } 425 | 426 | pub(crate) fn source_code_changed(&mut self, build_dir: String) -> bool { 427 | let output_dir = SOURCES_DIR_PATH.to_string(); 428 | let current_hash = self.hash_source_code(); 429 | let wd = std::env::current_dir().unwrap().to_str().unwrap().to_string(); 430 | let file_path = absolute_to_relative(self.file_path.clone(), wd).unwrap(); 431 | if let Some(hash_file_path) = self 432 | .build_manifest 433 | .read_file(build_dir.clone()) 434 | .sources 435 | .get(&file_path.clone()) 436 | { 437 | match File::open(hash_file_path.clone()) { 438 | Ok(mut file) => { 439 | let mut saved_hash = String::new(); 440 | file.read_to_string(&mut saved_hash).unwrap(); 441 | !(current_hash == *saved_hash) 442 | } 443 | Err(_) => { 444 | // saved hash does not exist in sources directory 445 | // let's remove it from BuildManifest and create a new one 446 | self.build_manifest.sources.remove(&file_path.clone()); 447 | let output_file = self.save_source_hash(output_dir, current_hash); 448 | self.build_manifest.sources.insert(file_path.clone(), output_file); 449 | self.build_manifest.save_file(build_dir); 450 | true 451 | } 452 | } 453 | } else { 454 | let output_file = self.save_source_hash(output_dir, current_hash); 455 | self.build_manifest.sources.insert(file_path.clone(), output_file); 456 | self.build_manifest.save_file(build_dir); 457 | true 458 | } 459 | } 460 | 461 | pub(crate) fn save_source_hash(&self, output_dir: String, hash_str: String) -> String { 462 | let rng = rand::rng(); 463 | let random_hex: String = rng.sample_iter(&Alphanumeric).take(30).map(char::from).collect(); 464 | let output_file = format!("{}/{}", output_dir, random_hex); 465 | File::create_new(output_file.clone()) 466 | .unwrap() 467 | .write(hash_str.as_bytes()) 468 | .unwrap(); 469 | output_file 470 | } 471 | 472 | pub(crate) fn save_object_file(&mut self, build_dir: String) { 473 | let rng = rand::rng(); 474 | let random_hex: String = rng.sample_iter(&Alphanumeric).take(30).map(char::from).collect(); 475 | let output_file = format!("{}/{}/{}.o", build_dir, OBJECTS_FILENAME, random_hex); 476 | 477 | let wd = std::env::current_dir().unwrap().to_str().unwrap().to_string(); 478 | let file_path = absolute_to_relative(self.file_path.clone(), wd).unwrap(); 479 | 480 | // remove previous object_file 481 | if let Some(obj_file) = self.build_manifest.objects.get(&file_path.clone()) { 482 | if fs::exists(obj_file).unwrap() { 483 | let _ = fs::remove_file(obj_file).unwrap(); 484 | } 485 | } 486 | 487 | // generate new object_file 488 | self.generate_object_file_internal(output_file.clone()); 489 | self.build_manifest.objects.insert(file_path.clone(), output_file); 490 | self.build_manifest.save_file(build_dir); 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/diag.rs: -------------------------------------------------------------------------------- 1 | use colorized::{Color, Colors}; 2 | use console::user_attended; 3 | use core::fmt; 4 | use std::fs; 5 | use utils::purify_string::{escape_string, saturating_sub, spaces, unescape_string}; 6 | 7 | const PANEL_LENGTH: usize = 4; 8 | 9 | #[derive(Debug, Clone, PartialEq, Eq)] 10 | pub enum DiagKind { 11 | NoEntryPointDetected, 12 | InvalidTypeToken, 13 | DerefNonPointerType, 14 | InfixNonBasic, 15 | NonInternalEntryPoint, 16 | UnimplementedFeature, 17 | InvalidTokenAsArrayCapacity, 18 | IdentifierNotDefined(String), 19 | TypeAnnotationRequired, 20 | UndefinedDataType(String), 21 | FuncNotFound(String), 22 | InvalidWildcard, 23 | ModuleNotFound(String), 24 | FuncCallArgumentCountMismatch(String, i32, i32), 25 | TypeAnnotationRequiredForParam(String, String), 26 | LenCalledWithInvalidInput, 27 | Custom(String), 28 | } 29 | 30 | #[derive(Debug, Clone, PartialEq, Eq)] 31 | pub enum DiagLevel { 32 | Error, 33 | Warning, 34 | } 35 | 36 | #[derive(Debug, Clone, PartialEq, Eq)] 37 | pub struct DiagLoc { 38 | pub file: String, 39 | pub line: usize, 40 | pub column: usize, 41 | pub length: usize, 42 | } 43 | 44 | #[derive(Debug, Clone, PartialEq, Eq)] 45 | pub struct Diag { 46 | pub level: DiagLevel, 47 | pub kind: DiagKind, 48 | pub location: Option, 49 | } 50 | 51 | impl fmt::Display for DiagKind { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | let msg = match self { 54 | DiagKind::Custom(str) => str, 55 | DiagKind::InvalidTypeToken => "Invalid type token.", 56 | DiagKind::UnimplementedFeature => "Unimplemented.", 57 | DiagKind::DerefNonPointerType => "Cannot dereference a non-pointer type.", 58 | DiagKind::NoEntryPointDetected => "No entry point detected.", 59 | DiagKind::NonInternalEntryPoint => "Entry pont must be defined internally.", 60 | DiagKind::TypeAnnotationRequiredForParam(param, func) => &format!( 61 | "Type annotation required for parameter '{}' in function '{}'.", 62 | param, func 63 | ), 64 | DiagKind::TypeAnnotationRequired => &format!("Type annotation required.",), 65 | DiagKind::InfixNonBasic => "Cannot build infix expression for non-basic value.", 66 | DiagKind::InvalidTokenAsArrayCapacity => "Invalid token given as array capacity.", 67 | DiagKind::IdentifierNotDefined(value) => &format!("The '{}' not found anywhere.", value), 68 | DiagKind::FuncNotFound(func_name) => &format!("Function '{}' not found in this module.", func_name), 69 | DiagKind::InvalidWildcard => "Wildcard cannot be used in the begging or middle of a module path.", 70 | DiagKind::ModuleNotFound(name) => &format!( 71 | "The module '{}' could not be found in any of the specified source directories.", 72 | name 73 | ), 74 | DiagKind::FuncCallArgumentCountMismatch(func_name, current, expected) => &format!( 75 | "Expected {} arguments for function '{}', but got {}.", 76 | expected, func_name, current 77 | ), 78 | DiagKind::LenCalledWithInvalidInput => "Cannot get length of non-string or non-array value.", 79 | DiagKind::UndefinedDataType(type_name) => { 80 | &format!("The data type '{}' is not defined in this module.", type_name) 81 | } 82 | }; 83 | write!(f, "{}", msg) 84 | } 85 | } 86 | 87 | #[derive(Debug, Clone)] 88 | pub struct DiagReporter { 89 | diags: Vec, 90 | } 91 | 92 | pub fn display_single_diag(diag: Diag) { 93 | let mut reporter = DiagReporter::new(); 94 | reporter.report(diag); 95 | reporter.display_diags(); 96 | } 97 | 98 | impl DiagReporter { 99 | pub fn new() -> Self { 100 | DiagReporter { diags: Vec::new() } 101 | } 102 | 103 | pub fn report(&mut self, diag: Diag) -> &mut Self { 104 | self.diags.push(diag); 105 | self 106 | } 107 | 108 | pub fn display_diags(&self) { 109 | for diag in &self.diags { 110 | match diag.level { 111 | DiagLevel::Error => eprintln!("{}", self.format_panel(diag)), 112 | DiagLevel::Warning => println!("{}", self.format_panel(diag)), 113 | } 114 | } 115 | } 116 | 117 | pub fn has_errors(&self) -> bool { 118 | self.diags.len() > 0 119 | } 120 | 121 | fn format_panel(&self, diag: &Diag) -> String { 122 | let mut formatted = String::new(); 123 | 124 | let diag_level_text = match diag.level { 125 | DiagLevel::Error => "error".color(Colors::RedFg), 126 | DiagLevel::Warning => "warning".color(Colors::YellowFg), 127 | }; 128 | 129 | formatted.push_str(&format!("{}: {}\n", diag_level_text, diag.kind.to_string())); 130 | 131 | if let Some(loc) = &diag.location { 132 | formatted.push_str(&format!( 133 | " --> {}:{}:{}\n\n", 134 | loc.file.clone(), 135 | loc.line, 136 | loc.column 137 | )); 138 | 139 | let mut starting_line = saturating_sub(loc.line, PANEL_LENGTH); 140 | let source_content = fs::read_to_string(loc.file.clone()).unwrap(); 141 | let sources_lines: Vec<&str> = source_content.split("\n").collect(); 142 | 143 | while starting_line < loc.line + PANEL_LENGTH { 144 | if let Some(line_str) = sources_lines.get(starting_line) { 145 | if starting_line + 1 == loc.line && user_attended() { 146 | formatted.push_str( 147 | &format!("{}{} | {}", spaces(2), starting_line + 1, line_str).color(Colors::RedFg), 148 | ); 149 | } else { 150 | formatted.push_str(&format!("{}{} | {}", spaces(2), starting_line + 1, line_str)); 151 | } 152 | } else { 153 | break; 154 | } 155 | 156 | starting_line += 1; 157 | formatted.push_str("\n"); 158 | } 159 | } 160 | 161 | // formatted.push_str(&format!("{}", diag.kind.to_string())); 162 | 163 | formatted 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/enums.rs: -------------------------------------------------------------------------------- 1 | use ast::ast::Enum; 2 | 3 | use crate::CodeGenLLVM; 4 | 5 | impl<'ctx> CodeGenLLVM<'ctx> { 6 | pub(crate) fn build_enum(&self, enum_statement: Enum) { 7 | dbg!(enum_statement.clone()); 8 | todo!(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ast::ast::*; 2 | use ast::token::Location; 3 | use build::{BuildManifest, OutputKind}; 4 | use diag::*; 5 | use funcs::FuncTable; 6 | use inkwell::basic_block::BasicBlock; 7 | use inkwell::builder::Builder; 8 | use inkwell::context::Context; 9 | use inkwell::module::Module; 10 | use inkwell::support::LLVMString; 11 | use inkwell::targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine}; 12 | use inkwell::values::{FunctionValue, PointerValue}; 13 | use inkwell::{AddressSpace, OptimizationLevel}; 14 | use modules::ModuleMetadata; 15 | use opts::Options; 16 | use scope::{Scope, ScopeRef}; 17 | use std::cell::RefCell; 18 | use std::collections::HashMap; 19 | use std::env; 20 | use std::process::exit; 21 | use std::rc::Rc; 22 | use structs::StructTable; 23 | use types::{InternalType, StringType}; 24 | use utils::fs::file_stem; 25 | use utils::tui::{tui_compile_finished, tui_compiled}; 26 | use values::{InternalValue, StringValue}; 27 | 28 | pub mod build; 29 | pub mod diag; 30 | mod enums; 31 | mod exprs; 32 | mod funcs; 33 | mod modules; 34 | pub mod opts; 35 | mod runtime; 36 | mod scope; 37 | mod stmts; 38 | mod strings; 39 | mod structs; 40 | mod tests; 41 | mod types; 42 | mod values; 43 | 44 | pub struct CodeGenLLVM<'ctx> { 45 | #[allow(dead_code)] 46 | opts: Options, 47 | context: &'ctx Context, 48 | module: Rc>>, 49 | module_name: String, 50 | builder: Builder<'ctx>, 51 | target_machine: TargetMachine, 52 | build_manifest: BuildManifest, 53 | program: ProgramTree, 54 | file_path: String, 55 | reporter: DiagReporter, 56 | entry_point: Option, 57 | is_entry_point: bool, 58 | entry_point_path: String, 59 | func_table: FuncTable<'ctx>, 60 | struct_table: StructTable<'ctx>, 61 | compiler_invoked_single: bool, 62 | current_func_ref: Option>, 63 | current_block_ref: Option>, 64 | terminated_blocks: Vec>, 65 | string_type: StringType<'ctx>, 66 | loaded_modules: Vec>, 67 | dependent_modules: HashMap>, 68 | output_kind: OutputKind, 69 | final_build_dir: String, 70 | } 71 | 72 | impl<'ctx> CodeGenLLVM<'ctx> { 73 | pub fn new( 74 | context: &'ctx Context, 75 | file_path: String, 76 | file_name: String, 77 | program: ProgramTree, 78 | opts: Options, 79 | compiler_invoked_single: bool, 80 | output_kind: OutputKind, 81 | ) -> Result { 82 | let reporter = DiagReporter::new(); 83 | let module_name = file_stem(&file_name).unwrap_or(&file_name).to_string(); 84 | let module = Rc::new(RefCell::new(context.create_module(&module_name.clone()))); 85 | let builder = context.create_builder(); 86 | let target_machine = CodeGenLLVM::target_machine(Rc::clone(&module)); 87 | 88 | let final_build_dir = { 89 | match opts.build_dir.clone() { 90 | opts::BuildDir::Default => { 91 | // specify a tmp directory to be used as build_dir 92 | env::temp_dir().to_str().unwrap().to_string() 93 | } 94 | opts::BuildDir::Provided(path) => path, 95 | } 96 | }; 97 | 98 | let codegen_llvm = CodeGenLLVM { 99 | final_build_dir, 100 | opts, 101 | context, 102 | builder, 103 | program, 104 | file_path: file_path.clone(), 105 | reporter, 106 | target_machine, 107 | entry_point: None, 108 | entry_point_path: file_path.clone(), 109 | is_entry_point: true, 110 | func_table: FuncTable::new(), 111 | struct_table: StructTable::new(), 112 | build_manifest: BuildManifest::default(), 113 | compiler_invoked_single, 114 | current_func_ref: None, 115 | current_block_ref: None, 116 | terminated_blocks: Vec::new(), 117 | string_type: CodeGenLLVM::build_string_type(context), 118 | module: module.clone(), 119 | module_name: module_name.clone(), 120 | loaded_modules: Vec::new(), 121 | dependent_modules: HashMap::new(), 122 | output_kind, 123 | }; 124 | 125 | Ok(codegen_llvm) 126 | } 127 | 128 | pub fn target_machine(module: Rc>) -> TargetMachine { 129 | let module = module.borrow_mut(); 130 | Target::initialize_native(&InitializationConfig::default()).unwrap(); 131 | let target_triple = TargetMachine::get_default_triple(); 132 | let cpu = TargetMachine::get_host_cpu_name().to_string(); 133 | let features = TargetMachine::get_host_cpu_features().to_string(); 134 | let target = Target::from_triple(&target_triple).unwrap(); 135 | let target_machine = target 136 | .create_target_machine( 137 | &target_triple, 138 | &cpu, 139 | &features, 140 | OptimizationLevel::Default, 141 | RelocMode::Default, 142 | CodeModel::Default, 143 | ) 144 | .unwrap(); 145 | module.set_triple(&target_triple); 146 | module.set_data_layout(&target_machine.get_target_data().get_data_layout()); 147 | target_machine 148 | } 149 | 150 | pub fn new_context() -> Context { 151 | Context::create() 152 | } 153 | 154 | pub fn compile(&mut self) { 155 | let scope: ScopeRef<'ctx> = Rc::new(RefCell::new(Scope::new())); 156 | self.build_statements(Rc::clone(&scope), self.program.body.clone()); 157 | 158 | if self.reporter.has_errors() { 159 | self.reporter.display_diags(); 160 | exit(1); 161 | } 162 | 163 | self.ensure_build_directory(self.final_build_dir.clone()); 164 | self.ensure_build_manifest(self.final_build_dir.clone()); 165 | 166 | self.build_entry_point(); 167 | self.optimize(); 168 | if !self.compiler_invoked_single { 169 | self.rebuild_dependent_modules(); 170 | if self.source_code_changed(self.final_build_dir.clone()) || !self.object_file_exists() { 171 | self.save_object_file(self.final_build_dir.clone()); 172 | } 173 | } else { 174 | self.save_object_file(self.final_build_dir.clone()); 175 | } 176 | 177 | self.generate_output(); 178 | tui_compiled(self.file_path.clone()); 179 | } 180 | 181 | pub fn compilation_process_finished(&self) { 182 | tui_compile_finished(); 183 | } 184 | 185 | pub(crate) fn build_alloca( 186 | &self, 187 | var_type: TypeSpecifier, 188 | var_name: String, 189 | loc: Location, 190 | span_end: usize, 191 | ) -> (PointerValue<'ctx>, InternalType<'ctx>) { 192 | let internal_type = self.build_type(var_type.clone(), loc.clone(), span_end); 193 | let alloca = self 194 | .builder 195 | .build_alloca( 196 | internal_type.to_basic_type(self.context.ptr_type(AddressSpace::default())), 197 | &var_name, 198 | ) 199 | .unwrap(); 200 | (alloca, internal_type) 201 | } 202 | 203 | pub(crate) fn build_store(&self, ptr: PointerValue, value: InternalValue<'ctx>) { 204 | let result = match value { 205 | InternalValue::IntValue(int_value, _) => self.builder.build_store(ptr, int_value), 206 | InternalValue::FloatValue(float_value, _) => self.builder.build_store(ptr, float_value), 207 | InternalValue::ArrayValue(array_value, _) => self.builder.build_store(ptr, array_value), 208 | InternalValue::VectorValue(vector_value, _) => self.builder.build_store(ptr, vector_value), 209 | InternalValue::StructValue(struct_value, _) => self.builder.build_store(ptr, struct_value), 210 | InternalValue::StringValue(string_value) => self.builder.build_store(ptr, string_value.struct_value), 211 | InternalValue::PointerValue(pointer_value) => self.builder.build_store(ptr, pointer_value.ptr), 212 | _ => { 213 | display_single_diag(Diag { 214 | level: DiagLevel::Error, 215 | kind: DiagKind::Custom(String::from("Cannot store non-basic value in pointer.")), 216 | location: None, 217 | }); 218 | exit(1); 219 | } 220 | }; 221 | 222 | if let Err(err) = result { 223 | display_single_diag(Diag { 224 | level: DiagLevel::Error, 225 | kind: DiagKind::Custom(format!("Cannot store value in pointer:\n{}", err.to_string())), 226 | location: None, 227 | }); 228 | exit(1); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/modules.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | CodeGenLLVM, 3 | build::BuildManifest, 4 | diag::*, 5 | funcs::{FuncMetadata, FuncTable}, 6 | structs::{StructMetadata, StructTable}, 7 | }; 8 | use ast::{ 9 | ast::{Import, ModulePath, ModuleSegment, StorageClass}, 10 | format::module_segments_as_string, 11 | token::Location, 12 | }; 13 | use inkwell::module::Module; 14 | use std::{cell::RefCell, collections::HashMap, process::exit, rc::Rc}; 15 | use utils::fs::find_file_from_sources; 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct ModuleMetadata<'ctx> { 19 | pub identifier: String, 20 | pub file_path: String, 21 | pub module: Rc>>, 22 | pub func_table: HashMap>, 23 | pub struct_table: HashMap>, 24 | } 25 | 26 | impl<'ctx> CodeGenLLVM<'ctx> { 27 | pub(crate) fn rebuild_dependent_modules(&mut self) { 28 | if let Some(module_deps) = self.dependent_modules.get(&self.file_path) { 29 | for file_path in module_deps { 30 | // skip for rebuilding entry_point module 31 | let module_metadata = self 32 | .loaded_modules 33 | .iter() 34 | .find(|m| *m.file_path == *file_path) 35 | .cloned() 36 | .expect("Failed to get a loaded module by it's file path."); 37 | 38 | dbg!(module_metadata.identifier.clone()); 39 | } 40 | } 41 | } 42 | 43 | pub(crate) fn find_loaded_module(&self, module_identifier: String) -> Option> { 44 | self.loaded_modules 45 | .iter() 46 | .find(|m| m.identifier == module_identifier) 47 | .cloned() 48 | } 49 | 50 | fn build_import_module_path(&mut self, mut segments: Vec, loc: Location, span_end: usize) -> String { 51 | let sources = &self.opts.sources_dir; 52 | let segments_str = module_segments_as_string(segments.clone()); 53 | 54 | let mut begging_path = { 55 | match segments.clone().first().unwrap() { 56 | ModuleSegment::SubModule(identifier) => { 57 | segments.remove(0); 58 | let file_name = identifier.name.clone(); 59 | match find_file_from_sources(file_name, sources.clone()) { 60 | Some(path) => path, 61 | None => { 62 | display_single_diag(Diag { 63 | level: DiagLevel::Error, 64 | kind: DiagKind::ModuleNotFound(segments_str.clone()), 65 | location: Some(DiagLoc { 66 | file: self.file_path.clone(), 67 | line: loc.line, 68 | column: loc.column, 69 | length: span_end, 70 | }), 71 | }); 72 | exit(1); 73 | } 74 | } 75 | } 76 | } 77 | }; 78 | 79 | for module_path in segments { 80 | match module_path { 81 | ModuleSegment::SubModule(identifier) => { 82 | // check current identifier that, it's a sub_module or a thing that must be imported from latest module 83 | let temp_path = format!("{}/{}", begging_path.to_str().unwrap(), identifier.name.clone()); 84 | match find_file_from_sources(temp_path.clone(), sources.clone()) { 85 | Some(new_begging_path) => { 86 | begging_path = new_begging_path; 87 | } 88 | None => { 89 | if let Some(new_begging_path) = 90 | find_file_from_sources(format!("{}.cyr", temp_path), sources.clone()) 91 | { 92 | begging_path = new_begging_path; 93 | } else { 94 | display_single_diag(Diag { 95 | level: DiagLevel::Error, 96 | kind: DiagKind::ModuleNotFound(segments_str.clone()), 97 | location: Some(DiagLoc { 98 | file: self.file_path.clone(), 99 | line: loc.line, 100 | column: loc.column, 101 | length: span_end, 102 | }), 103 | }); 104 | exit(1); 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | if begging_path.is_dir() { 113 | display_single_diag(Diag { 114 | level: DiagLevel::Error, 115 | kind: DiagKind::Custom(format!( 116 | "Import module '{}' as directory is not allowed.", 117 | segments_str.clone() 118 | )), 119 | location: Some(DiagLoc { 120 | file: self.file_path.clone(), 121 | line: loc.line, 122 | column: loc.column, 123 | length: span_end, 124 | }), 125 | }); 126 | exit(1); 127 | } 128 | 129 | begging_path.to_str().unwrap().to_string() 130 | } 131 | 132 | pub(crate) fn build_module_identifier(&self, module_path: ModulePath) -> String { 133 | module_path.alias.unwrap_or({ 134 | match module_path.segments.last().unwrap() { 135 | ModuleSegment::SubModule(identifier) => identifier.name.clone(), 136 | } 137 | }) 138 | } 139 | 140 | fn check_import_twice(&self, module_identifier: String, module_path: ModulePath) { 141 | if let Some(_) = self.find_loaded_module(module_identifier.clone()) { 142 | display_single_diag(Diag { 143 | level: DiagLevel::Error, 144 | kind: DiagKind::Custom(format!( 145 | "Cannot import module '{}' twice.", 146 | module_segments_as_string(module_path.segments) 147 | )), 148 | location: None, 149 | }); 150 | exit(1); 151 | } 152 | } 153 | 154 | pub(crate) fn build_import(&mut self, import: Import) { 155 | for module_path in import.paths.clone() { 156 | let file_path = 157 | self.build_import_module_path(module_path.segments.clone(), import.loc.clone(), import.span.end); 158 | let module_identifier = self.build_module_identifier(module_path.clone()); 159 | self.check_import_twice(module_identifier.clone(), module_path); 160 | self.build_imported_module(file_path.clone(), module_identifier); 161 | } 162 | } 163 | 164 | fn build_imported_module(&mut self, file_path: String, module_identifier: String) -> ModuleMetadata<'ctx> { 165 | let sub_module = Rc::new(RefCell::new(self.context.create_module(&module_identifier))); 166 | let sub_builder = self.context.create_builder(); 167 | let target_machine = CodeGenLLVM::target_machine(Rc::clone(&sub_module)); 168 | let program = parser::parse_program(file_path.clone()).0; 169 | 170 | let mut sub_codegen = CodeGenLLVM { 171 | program, 172 | opts: self.opts.clone(), 173 | context: self.context, 174 | module: Rc::clone(&sub_module), 175 | module_name: module_identifier.clone(), 176 | builder: sub_builder, 177 | target_machine, 178 | build_manifest: BuildManifest::default(), 179 | file_path: file_path.clone(), 180 | reporter: self.reporter.clone(), 181 | entry_point: None, 182 | is_entry_point: false, 183 | entry_point_path: self.entry_point_path.clone(), 184 | func_table: HashMap::new(), 185 | struct_table: HashMap::new(), 186 | compiler_invoked_single: self.compiler_invoked_single, 187 | current_func_ref: None, 188 | current_block_ref: None, 189 | terminated_blocks: Vec::new(), 190 | string_type: self.string_type.clone(), 191 | loaded_modules: Vec::new(), 192 | dependent_modules: HashMap::new(), 193 | output_kind: self.output_kind.clone(), 194 | final_build_dir: self.final_build_dir.clone(), 195 | }; 196 | 197 | // preventing entry_point of being in dependent_modules 198 | if self.file_path != self.entry_point_path { 199 | sub_codegen 200 | .dependent_modules 201 | .insert(file_path.clone(), vec![self.file_path.clone()]); 202 | } 203 | 204 | sub_codegen.compile(); 205 | self.build_manifest = sub_codegen.build_manifest.clone(); 206 | 207 | let module_metadata = ModuleMetadata { 208 | module: Rc::clone(&sub_module), 209 | func_table: self.build_imported_funcs(sub_codegen.func_table), 210 | struct_table: self.build_imported_structs(sub_codegen.struct_table), 211 | identifier: module_identifier, 212 | file_path, 213 | }; 214 | 215 | self.loaded_modules.push(module_metadata.clone()); 216 | 217 | module_metadata 218 | } 219 | 220 | fn build_imported_funcs(&mut self, func_table: FuncTable<'ctx>) -> HashMap> { 221 | let mut imported_funcs: HashMap = HashMap::new(); 222 | 223 | for (_, (_, metadata)) in func_table.iter().enumerate() { 224 | if metadata.func_decl.storage_class == StorageClass::Public 225 | || metadata.func_decl.storage_class == StorageClass::PublicExtern 226 | || metadata.func_decl.storage_class == StorageClass::PublicInline 227 | { 228 | let mut new_metadata = metadata.clone(); 229 | let func_value = self.build_func_decl(new_metadata.func_decl.clone()); 230 | new_metadata.ptr = func_value; 231 | 232 | imported_funcs.insert(metadata.func_decl.renamed_as.clone().unwrap(), new_metadata); 233 | } else { 234 | imported_funcs.insert(metadata.func_decl.renamed_as.clone().unwrap(), metadata.clone()); 235 | } 236 | } 237 | 238 | imported_funcs 239 | } 240 | 241 | // TODO Implement import struct methods 242 | fn build_imported_structs(&self, struct_table: StructTable<'ctx>) -> HashMap> { 243 | let mut imported_structs: HashMap = HashMap::new(); 244 | 245 | for (_, (struct_name, metadata)) in struct_table.iter().enumerate() { 246 | imported_structs.insert(struct_name.clone(), metadata.clone()); 247 | } 248 | 249 | imported_structs 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/opts.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Deserialize, Debug, Clone)] 4 | pub struct Options { 5 | pub project_type: Option, 6 | pub project_name: Option, 7 | pub project_version: Option, 8 | pub cyrus_version: Option, 9 | pub authors: Option>, 10 | pub cpu: String, 11 | pub opt_level: i32, 12 | pub library_path: Vec, 13 | pub libraries: Vec, 14 | pub sources_dir: Vec, 15 | pub build_dir: BuildDir, 16 | } 17 | 18 | #[derive(Deserialize, Debug, Clone)] 19 | pub enum BuildDir { 20 | Default, 21 | Provided(String), 22 | } 23 | 24 | 25 | impl Options { 26 | pub fn default() -> Self { 27 | Self { 28 | project_type: None, 29 | project_name: None, 30 | authors: None, 31 | opt_level: 0, 32 | cpu: String::new(), 33 | library_path: Vec::new(), 34 | libraries: Vec::new(), 35 | build_dir: BuildDir::Default, 36 | cyrus_version: None, 37 | project_version: None, 38 | sources_dir: vec!["./".to_string()], 39 | } 40 | } 41 | 42 | pub fn read_toml(file_path: String) -> Result { 43 | let mut options = Options::default(); 44 | 45 | let file_content = 46 | std::fs::read_to_string(file_path).map_err(|_| "Failed to read file 'Project.toml' content.")?; 47 | 48 | let file_toml: toml::Value = 49 | toml::from_str(&file_content).map_err(|_| "Failed to parse 'Project.toml' content.")?; 50 | 51 | if let Some(value) = file_toml.get("compiler") { 52 | let table = value 53 | .as_table() 54 | .ok_or("Failed to parse 'compiler' options from 'Project.toml'.")?; 55 | 56 | let optimize: String = table 57 | .get("optimize") 58 | .and_then(|v| v.as_str()) 59 | .ok_or("'optimize' key must be string in 'Project.toml'.")? 60 | .try_into() 61 | .unwrap(); 62 | 63 | options.opt_level = match optimize.as_str() { 64 | "none" => 0, 65 | "o1" => 1, 66 | "o2" => 2, 67 | "o3" => 3, 68 | _ => { 69 | return Err("'optimize' key in 'Project.toml' must be one of o1, o2, o3 or none.".to_string()); 70 | } 71 | }; 72 | } 73 | 74 | if let Some(value) = file_toml.get("dependencies") { 75 | let table = value 76 | .as_table() 77 | .ok_or("Failed to parse 'dependencies' from 'Project.toml'.")?; 78 | 79 | options.library_path = table 80 | .get("library_path") 81 | .and_then(|v| v.as_array()) 82 | .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) 83 | .unwrap_or_else(Vec::new); 84 | 85 | options.libraries = table 86 | .get("libraries") 87 | .and_then(|v| v.as_array()) 88 | .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) 89 | .unwrap_or_default(); 90 | } 91 | 92 | if let Some(value) = file_toml.get("project") { 93 | let table = value 94 | .as_table() 95 | .ok_or("Failed to parse 'project' from 'Project.toml'.")?; 96 | 97 | options.project_name = Some( 98 | table 99 | .get("name") 100 | .and_then(|v| v.as_str()) 101 | .ok_or("Failed to parse 'project name' in 'Project.toml'.")? 102 | .to_string(), 103 | ); 104 | 105 | options.sources_dir = table 106 | .get("sources") 107 | .and_then(|v| v.as_array()) 108 | .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) 109 | .unwrap_or_default(); 110 | } 111 | 112 | Ok(options) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use crate::CodeGenLLVM; 2 | use ast::{ 3 | ast::{FuncDecl, FuncParams, StorageClass}, 4 | token::{Location, Span}, 5 | }; 6 | use inkwell::{ 7 | AddressSpace, 8 | module::Linkage, 9 | types::BasicMetadataTypeEnum, 10 | values::{BasicValueEnum, FunctionValue}, 11 | }; 12 | use std::ops::DerefMut; 13 | use utils::generate_random_hex::generate_random_hex; 14 | 15 | impl<'ctx> CodeGenLLVM<'ctx> { 16 | #[allow(unused)] 17 | fn runtime_check_bounds(&self) -> FunctionValue<'ctx> { 18 | let return_type = self.context.i32_type(); 19 | 20 | let func_type = return_type.fn_type( 21 | &[ 22 | BasicMetadataTypeEnum::PointerType(self.context.ptr_type(AddressSpace::default())), 23 | BasicMetadataTypeEnum::IntType(self.context.i32_type()), 24 | ], 25 | false, 26 | ); 27 | 28 | let func = self.module.borrow_mut().deref_mut().add_function( 29 | &format!("check_bounds_{}", generate_random_hex()), 30 | func_type, 31 | Some(Linkage::Private), 32 | ); 33 | 34 | let entry_block = self.context.append_basic_block(func, "entry"); 35 | self.builder.position_at_end(entry_block); 36 | self.builder 37 | .build_return(Some(&BasicValueEnum::IntValue( 38 | self.context.i32_type().const_int(0, false), 39 | ))) 40 | .unwrap(); 41 | 42 | func 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/scope.rs: -------------------------------------------------------------------------------- 1 | use crate::InternalType; 2 | use inkwell::values::PointerValue; 3 | use std::{cell::RefCell, collections::HashMap, rc::Rc}; 4 | 5 | pub type ScopeRef<'a> = Rc>>; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct ScopeRecord<'a> { 9 | pub ptr: PointerValue<'a>, 10 | pub ty: InternalType<'a>, 11 | } 12 | 13 | pub struct Scope<'a> { 14 | pub records: HashMap>>>, 15 | pub parent: Option>>, 16 | } 17 | 18 | impl<'a> Scope<'a> { 19 | pub fn new() -> Self { 20 | Self { 21 | records: HashMap::new(), 22 | parent: None, 23 | } 24 | } 25 | 26 | pub fn get(&self, key: String) -> Option>>> { 27 | match self.records.get(&key) { 28 | Some(value) => Some(Rc::clone(value)), 29 | None => { 30 | if let Some(parent) = &self.parent { 31 | return parent.get(key); 32 | } else { 33 | None 34 | } 35 | } 36 | } 37 | } 38 | 39 | pub fn insert(&mut self, key: String, record: ScopeRecord<'a>) { 40 | self.records.insert(key, Rc::new(RefCell::new(record))); 41 | } 42 | } 43 | 44 | // TODO 45 | // Write unit test for this section 46 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/stmts.rs: -------------------------------------------------------------------------------- 1 | use crate::diag::*; 2 | use crate::scope::ScopeRecord; 3 | use crate::structs::StructMetadata; 4 | use crate::{CodeGenLLVM, scope::ScopeRef}; 5 | use ast::ast::{If, Statement, TypeSpecifier, Variable}; 6 | use ast::token::TokenKind; 7 | use inkwell::AddressSpace; 8 | use inkwell::basic_block::BasicBlock; 9 | use inkwell::values::{AnyValue, BasicValueEnum}; 10 | use std::process::exit; 11 | use std::rc::Rc; 12 | 13 | impl<'ctx> CodeGenLLVM<'ctx> { 14 | pub(crate) fn build_statements(&mut self, scope: ScopeRef<'ctx>, stmts: Vec) { 15 | for stmt in stmts { 16 | self.build_statement(Rc::clone(&scope), stmt.clone()); 17 | } 18 | } 19 | 20 | pub(crate) fn build_statement(&mut self, scope: ScopeRef<'ctx>, stmt: Statement) { 21 | match stmt { 22 | Statement::BlockStatement(block_statement) => { 23 | self.build_statements(Rc::clone(&scope), block_statement.exprs); 24 | } 25 | Statement::Expression(expression) => { 26 | self.build_expr(Rc::clone(&scope), expression); 27 | } 28 | Statement::Variable(variable) => self.build_variable(Rc::clone(&scope), variable), 29 | Statement::Return(statement) => { 30 | self.build_return( 31 | self.internal_value_as_rvalue(self.build_expr(Rc::clone(&scope), statement.argument)), 32 | statement.loc, 33 | statement.span.end, 34 | ); 35 | } 36 | Statement::FuncDef(func_def) => { 37 | if func_def.name == "main" { 38 | if self.entry_point.is_some() { 39 | display_single_diag(Diag { 40 | level: DiagLevel::Error, 41 | kind: DiagKind::Custom(String::from("Multiple entry point not allowed.")), 42 | location: None, 43 | }); 44 | exit(1); 45 | } 46 | 47 | self.entry_point = Some(func_def); 48 | } else { 49 | self.build_func_def(func_def.clone()); 50 | } 51 | } 52 | Statement::FuncDecl(func_decl) => { 53 | self.build_func_decl(func_decl); 54 | } 55 | Statement::If(if_statement) => self.build_if(Rc::clone(&scope), if_statement), 56 | Statement::For(_) => todo!(), 57 | Statement::Switch(_) => todo!(), 58 | Statement::Break(location) => todo!(), 59 | Statement::Continue(location) => todo!(), 60 | Statement::Struct(struct_statement) => { 61 | let struct_type = self.build_struct(struct_statement.clone()); 62 | self.struct_table.insert( 63 | struct_statement.name, 64 | StructMetadata { 65 | struct_type, 66 | fields: struct_statement.fields, 67 | inherits: struct_statement.inherits, 68 | storage_class: struct_statement.storage_class, 69 | }, 70 | ); 71 | } 72 | Statement::Enum(enum_statement) => self.build_enum(enum_statement), 73 | Statement::Import(import) => self.build_import(import), 74 | } 75 | } 76 | 77 | pub(crate) fn block_terminated(&self, block: BasicBlock<'ctx>) -> bool { 78 | self.terminated_blocks.contains(&block) 79 | } 80 | 81 | pub(crate) fn mark_block_terminated(&mut self, block: BasicBlock<'ctx>) { 82 | self.terminated_blocks.push(block); 83 | } 84 | 85 | pub(crate) fn build_if(&mut self, scope: ScopeRef<'ctx>, if_statement: If) { 86 | if let Some(current_func) = self.current_func_ref { 87 | let then_block = self.context.append_basic_block(current_func, "if.then"); 88 | let else_block = self.context.append_basic_block(current_func, "if.else"); 89 | let end_block = self.context.append_basic_block(current_func, "if.end"); 90 | 91 | if let Some(current_block) = self.current_block_ref { 92 | let cond = self.build_cond( 93 | Rc::clone(&scope), 94 | if_statement.condition, 95 | if_statement.loc.clone(), 96 | if_statement.span.end, 97 | ); 98 | 99 | self.builder.position_at_end(current_block); 100 | if !self.block_terminated(current_block) { 101 | self.builder 102 | .build_conditional_branch(cond, then_block, else_block) 103 | .unwrap(); 104 | self.mark_block_terminated(current_block); 105 | } 106 | 107 | self.builder.position_at_end(then_block); 108 | self.current_block_ref = Some(then_block); 109 | self.build_statements(Rc::clone(&scope), if_statement.consequent.exprs); 110 | if !self.block_terminated(then_block) { 111 | self.builder.build_unconditional_branch(end_block).unwrap(); 112 | self.mark_block_terminated(then_block); 113 | } 114 | 115 | let mut current_else_block = else_block; 116 | for else_if in if_statement.branches { 117 | let new_else_block = self.context.append_basic_block(current_func, "else_if"); 118 | let new_then_block = self.context.append_basic_block(current_func, "else_if.then"); 119 | 120 | self.builder.position_at_end(current_else_block); 121 | let else_if_cond = self.build_cond( 122 | Rc::clone(&scope), 123 | else_if.condition, 124 | else_if.loc.clone(), 125 | else_if.span.end, 126 | ); 127 | 128 | if !self.block_terminated(current_else_block) { 129 | self.builder 130 | .build_conditional_branch(else_if_cond, new_then_block, new_else_block) 131 | .unwrap(); 132 | self.mark_block_terminated(current_else_block); 133 | } 134 | 135 | self.builder.position_at_end(new_then_block); 136 | self.current_block_ref = Some(new_then_block); 137 | self.build_statements(Rc::clone(&scope), else_if.consequent.exprs); 138 | if !self.block_terminated(new_then_block) { 139 | self.builder.build_unconditional_branch(end_block).unwrap(); 140 | self.mark_block_terminated(new_then_block); 141 | } 142 | 143 | current_else_block = new_else_block; 144 | } 145 | 146 | // handle the final "else" block 147 | self.builder.position_at_end(current_else_block); 148 | self.current_block_ref = Some(current_else_block); 149 | if let Some(alternate) = if_statement.alternate { 150 | self.build_statements(Rc::clone(&scope), alternate.exprs); 151 | } 152 | if !self.block_terminated(current_else_block) { 153 | self.builder.build_unconditional_branch(end_block).unwrap(); 154 | self.mark_block_terminated(current_else_block); 155 | } 156 | 157 | self.builder.position_at_end(end_block); 158 | self.current_block_ref = Some(end_block); 159 | } else { 160 | display_single_diag(Diag { 161 | level: DiagLevel::Error, 162 | kind: DiagKind::Custom( 163 | "Cannot build if statement without having reference to current block.".to_string(), 164 | ), 165 | location: Some(DiagLoc { 166 | file: self.file_path.clone(), 167 | line: if_statement.loc.line, 168 | column: if_statement.loc.column, 169 | length: if_statement.span.end, 170 | }), 171 | }); 172 | exit(1); 173 | } 174 | } else { 175 | display_single_diag(Diag { 176 | level: DiagLevel::Error, 177 | kind: DiagKind::Custom("Cannot build if statement outside of a function.".to_string()), 178 | location: Some(DiagLoc { 179 | file: self.file_path.clone(), 180 | line: if_statement.loc.line, 181 | column: if_statement.loc.column, 182 | length: if_statement.span.end, 183 | }), 184 | }); 185 | exit(1); 186 | } 187 | } 188 | 189 | pub(crate) fn build_variable(&self, scope: ScopeRef<'ctx>, variable: Variable) { 190 | match variable.ty { 191 | Some(type_specifier) => { 192 | if let TypeSpecifier::TypeToken(type_token) = type_specifier.clone() { 193 | if type_token.kind == TokenKind::Void { 194 | display_single_diag(Diag { 195 | level: DiagLevel::Error, 196 | kind: DiagKind::Custom("Cannot declare a variable with 'void' type.".to_string()), 197 | location: None, 198 | }); 199 | exit(1); 200 | } 201 | } 202 | 203 | let (ptr, ty) = self.build_alloca( 204 | type_specifier.clone(), 205 | variable.name.clone(), 206 | variable.loc.clone(), 207 | variable.span.end, 208 | ); 209 | 210 | if let Some(expr) = variable.expr { 211 | let rvalue = self.internal_value_as_rvalue(self.build_expr(Rc::clone(&scope), expr)); 212 | 213 | if !self.compatible_types(ty.clone(), rvalue.get_type(self.string_type.clone())) { 214 | // FIXME We need accurate type name tracking here 215 | display_single_diag(Diag { 216 | level: DiagLevel::Error, 217 | kind: DiagKind::Custom(format!( 218 | "Cannot assign value of type '{}' to lvalue of type '{}'.", 219 | rvalue 220 | .get_type(self.string_type.clone()) 221 | .to_basic_type(self.context.ptr_type(AddressSpace::default())), 222 | ty.to_basic_type(self.context.ptr_type(AddressSpace::default())), 223 | )), 224 | location: None, 225 | }); 226 | exit(1); 227 | }; 228 | 229 | let final_rvalue = self.implicit_cast( 230 | rvalue, 231 | self.build_type(type_specifier, variable.loc.clone(), variable.span.end), 232 | ); 233 | 234 | self.builder.build_store(ptr, final_rvalue).unwrap(); 235 | } else if ty.is_const_type() && variable.expr.is_none() { 236 | display_single_diag(Diag { 237 | level: DiagLevel::Error, 238 | kind: DiagKind::Custom(format!( 239 | "Variable '{}' is declared as constant but has no initializer.", 240 | variable.name 241 | )), 242 | location: Some(DiagLoc { 243 | file: self.file_path.clone(), 244 | line: variable.loc.line, 245 | column: variable.loc.column, 246 | length: variable.span.end, 247 | }), 248 | }); 249 | exit(1); 250 | } else { 251 | let zero_init = self.build_zero_initialized_internal_value(ty.clone()); 252 | let final_rvalue: BasicValueEnum = 253 | zero_init.to_basic_metadata().as_any_value_enum().try_into().unwrap(); 254 | self.builder.build_store(ptr, final_rvalue).unwrap(); 255 | } 256 | 257 | let mut scope_borrow = scope.borrow_mut(); 258 | 259 | if scope_borrow.get(variable.name.clone()).is_some() { 260 | display_single_diag(Diag { 261 | level: DiagLevel::Error, 262 | kind: DiagKind::Custom(format!( 263 | "Variable '{}' would shadow a previous declaration.", 264 | variable.name 265 | )), 266 | location: Some(DiagLoc { 267 | file: self.file_path.clone(), 268 | line: variable.loc.line, 269 | column: variable.loc.column, 270 | length: variable.span.end, 271 | }), 272 | }); 273 | exit(1); 274 | } 275 | 276 | scope_borrow.insert(variable.name.clone(), ScopeRecord { ptr, ty }); 277 | } 278 | None => { 279 | if let Some(expr) = variable.expr { 280 | let rvalue = self.internal_value_as_rvalue(self.build_expr(Rc::clone(&scope), expr)); 281 | let var_type = rvalue.get_type(self.string_type.clone()); 282 | 283 | let ptr = self 284 | .builder 285 | .build_alloca( 286 | var_type.to_basic_type(self.context.ptr_type(AddressSpace::default())), 287 | &variable.name, 288 | ) 289 | .unwrap(); 290 | 291 | self.build_store(ptr, rvalue); 292 | 293 | scope 294 | .borrow_mut() 295 | .insert(variable.name, ScopeRecord { ptr, ty: var_type }); 296 | } else { 297 | display_single_diag(Diag { 298 | level: DiagLevel::Error, 299 | kind: DiagKind::TypeAnnotationRequired, 300 | location: None, 301 | }); 302 | exit(1); 303 | } 304 | } 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/strings.rs: -------------------------------------------------------------------------------- 1 | use inkwell::{ 2 | AddressSpace, 3 | context::Context, 4 | values::{IntValue, PointerValue}, 5 | }; 6 | 7 | use crate::{CodeGenLLVM, InternalType, InternalValue, StringType, StringValue, values::TypedPointerValue}; 8 | 9 | impl<'ctx> CodeGenLLVM<'ctx> { 10 | pub(crate) fn build_string_type(context: &'ctx Context) -> StringType<'ctx> { 11 | let i8_ptr_type = context.ptr_type(AddressSpace::default()); 12 | let i64_type = context.i64_type(); 13 | 14 | let struct_type = context.opaque_struct_type("str"); 15 | struct_type.set_body(&[i8_ptr_type.into(), i64_type.into()], false); 16 | 17 | StringType { struct_type } 18 | } 19 | 20 | pub(crate) fn build_load_string(&self, string_value: StringValue<'ctx>) -> InternalValue<'ctx> { 21 | let data_str = self 22 | .builder 23 | .build_extract_value(string_value.struct_value, 0, "exv") 24 | .unwrap(); 25 | 26 | InternalValue::PointerValue(TypedPointerValue { 27 | ptr: data_str.into_pointer_value(), 28 | pointee_ty: InternalType::VoidType(self.context.void_type()), 29 | }) 30 | } 31 | 32 | pub(crate) fn build_construct_string_value( 33 | &self, 34 | buffer: PointerValue<'ctx>, 35 | buffer_size: IntValue<'ctx>, 36 | ) -> InternalValue<'ctx> { 37 | let undef = self.string_type.struct_type.get_undef(); 38 | 39 | let str_val = self 40 | .builder 41 | .build_insert_value(undef, buffer, 0, "string.buffer") 42 | .unwrap(); 43 | let str_val2 = self 44 | .builder 45 | .build_insert_value(str_val, buffer_size, 1, "string.length") 46 | .unwrap() 47 | .into_struct_value(); 48 | 49 | InternalValue::StringValue(StringValue { struct_value: str_val2 }) 50 | } 51 | 52 | pub(crate) fn build_zeroinit_string(&self) -> InternalValue<'ctx> { 53 | let const_str = self.context.const_string(b"", true); 54 | let global_str = self 55 | .module 56 | .borrow_mut() 57 | .add_global(const_str.get_type(), None, ".string.empty"); 58 | global_str.set_initializer(&const_str); 59 | global_str.set_constant(true); 60 | 61 | let buffer_size = self 62 | .context 63 | .i64_type() 64 | .const_int(const_str.get_type().len().into(), false); 65 | self.build_construct_string_value(global_str.as_pointer_value(), buffer_size) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/structs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | CodeGenLLVM, InternalValue, 3 | diag::{Diag, DiagKind, DiagLevel, DiagLoc, display_single_diag}, 4 | scope::ScopeRef, 5 | }; 6 | use ast::{ 7 | ast::{Field, Identifier, ModuleImport, StorageClass, Struct, StructInit}, 8 | token::Location, 9 | }; 10 | use inkwell::{ 11 | AddressSpace, 12 | types::{BasicTypeEnum, StructType}, 13 | }; 14 | use std::{collections::HashMap, process::exit}; 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct StructMetadata<'a> { 18 | pub struct_type: StructType<'a>, 19 | pub inherits: Vec, 20 | pub fields: Vec, 21 | pub storage_class: StorageClass, 22 | } 23 | 24 | pub type StructTable<'a> = HashMap>; 25 | 26 | impl<'ctx> CodeGenLLVM<'ctx> { 27 | pub(crate) fn build_struct_fields(&self, fields: Vec) -> Vec { 28 | fields 29 | .iter() 30 | .map(|field| { 31 | self.build_type(field.ty.clone(), field.loc.clone(), field.span.end) 32 | .to_basic_type(self.context.ptr_type(AddressSpace::default())) 33 | }) 34 | .collect() 35 | } 36 | 37 | pub(crate) fn build_struct(&self, struct_statement: Struct) -> StructType<'ctx> { 38 | let field_types = self.build_struct_fields(struct_statement.fields.clone()); 39 | let struct_type = self.context.struct_type(&field_types, false); 40 | if !matches!( 41 | struct_statement.storage_class, 42 | StorageClass::Public | StorageClass::Internal 43 | ) { 44 | display_single_diag(Diag { 45 | level: DiagLevel::Error, 46 | kind: DiagKind::Custom("Structs can only be defined public or internal.".to_string()), 47 | location: Some(DiagLoc { 48 | file: self.file_path.clone(), 49 | line: struct_statement.loc.line, 50 | column: struct_statement.loc.column, 51 | length: struct_statement.span.end, 52 | }), 53 | }); 54 | exit(1); 55 | } 56 | struct_type 57 | } 58 | 59 | pub(crate) fn build_struct_init(&self, scope: ScopeRef<'ctx>, struct_init: StructInit) -> InternalValue<'ctx> { 60 | // FIXME 61 | dbg!(struct_init.clone()); 62 | todo!(); 63 | 64 | // let struct_metadata = self.find_struct( 65 | // Rc::clone(&scope), 66 | // struct_init.struct_name.clone(), 67 | // struct_init.loc.clone(), 68 | // struct_init.span.end, 69 | // ); 70 | 71 | // if struct_metadata.fields.len() != struct_init.field_inits.len() { 72 | // display_single_diag(Diag { 73 | // level: DiagLevel::Error, 74 | // kind: DiagKind::Custom(format!( 75 | // "Struct '{}' has {} fields, but {} fields were provided.", 76 | // struct_init.struct_name.to_string(), 77 | // struct_metadata.fields.len(), 78 | // struct_init.field_inits.len() 79 | // )), 80 | // location: Some(DiagLoc { 81 | // file: self.file_path.clone(), 82 | // line: struct_init.loc.line, 83 | // column: struct_init.loc.column, 84 | // length: struct_init.span.end, 85 | // }), 86 | // }); 87 | // exit(1); 88 | // } 89 | 90 | // let mut struct_value = struct_metadata.struct_type.get_undef(); 91 | 92 | // for field_init in struct_init.field_inits { 93 | // let field_idx = struct_metadata 94 | // .fields 95 | // .iter() 96 | // .position(|field| field.name == field_init.name) 97 | // .unwrap(); 98 | 99 | // let field = struct_metadata.fields.get(field_idx).unwrap(); 100 | 101 | // let field_type = self 102 | // .build_type(field.ty.clone(), field.loc.clone(), field.span.end) 103 | // .to_basic_type(self.context.ptr_type(AddressSpace::default())); 104 | 105 | // let field_any_value = self.build_expr(Rc::clone(&scope), field_init.value.clone()); 106 | 107 | // if field_any_value.get_type(self.string_type.clone()).to_basic_type(self.context.ptr_type(AddressSpace::default())) != field_type { 108 | // display_single_diag(Diag { 109 | // level: DiagLevel::Error, 110 | // kind: DiagKind::Custom(format!( 111 | // "Expected type '{}' but got type '{}'.", 112 | // field_type.to_string(), 113 | // field_any_value.get_type(self.string_type.clone()).to_string() 114 | // )), 115 | // location: Some(DiagLoc { 116 | // file: self.file_path.clone(), 117 | // line: field_init.loc.line, 118 | // column: field_init.loc.column, 119 | // length: struct_init.span.end, 120 | // }), 121 | // }); 122 | // exit(1); 123 | // } 124 | 125 | // let field_value: BasicValueEnum<'ctx> = field_any_value.try_into().unwrap(); 126 | 127 | // struct_value = self 128 | // .builder 129 | // .build_insert_value( 130 | // AggregateValueEnum::StructValue(struct_value), 131 | // field_value, 132 | // field_idx.try_into().unwrap(), 133 | // "insert_data", 134 | // ) 135 | // .unwrap() 136 | // .into_struct_value(); 137 | // } 138 | 139 | // InternalValue::StructValue(struct_value) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::{CodeGenLLVM, build::OutputKind, opts::Options}; 4 | use inkwell::context::Context; 5 | 6 | #[test] 7 | fn test_new_codegen_llvm() { 8 | let context = Context::create(); 9 | let _ = CodeGenLLVM::new( 10 | &context, 11 | String::from("./main.cyr"), 12 | String::from("main.cyr"), 13 | ast::ast::ProgramTree { 14 | body: vec![], 15 | span: ast::token::Span::default(), 16 | }, 17 | Options::default(), 18 | true, 19 | OutputKind::None, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/codegen_llvm/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::CodeGenLLVM; 2 | use crate::InternalValue; 3 | use crate::StringValue; 4 | use crate::diag::*; 5 | use crate::values::Lvalue; 6 | use crate::values::TypedPointerValue; 7 | use ast::ast::ArrayCapacity; 8 | use ast::ast::ArrayTypeSpecifier; 9 | use ast::ast::TypeSpecifier; 10 | use ast::token::*; 11 | use inkwell::AddressSpace; 12 | use inkwell::llvm_sys::prelude::LLVMTypeRef; 13 | use inkwell::types::ArrayType; 14 | use inkwell::types::AsTypeRef; 15 | use inkwell::types::BasicType; 16 | use inkwell::types::BasicTypeEnum; 17 | use inkwell::types::FloatType; 18 | use inkwell::types::IntType; 19 | use inkwell::types::PointerType; 20 | use inkwell::types::StructType; 21 | use inkwell::types::VectorType; 22 | use inkwell::types::VoidType; 23 | use std::process::exit; 24 | 25 | #[derive(Debug, Clone, PartialEq)] 26 | pub(crate) enum InternalType<'a> { 27 | BoolType(IntType<'a>), 28 | IntType(IntType<'a>), 29 | FloatType(FloatType<'a>), 30 | StructType(StructType<'a>), 31 | VectorType(VectorType<'a>), 32 | StringType(StringType<'a>), 33 | VoidType(VoidType<'a>), 34 | PointerType(Box>), 35 | ConstType(Box>), 36 | Lvalue(Box>), 37 | ArrayType(Box>, ArrayType<'a>), 38 | } 39 | 40 | #[derive(Debug, Clone, PartialEq)] 41 | pub(crate) struct LvalueType<'a> { 42 | pub ptr_type: PointerType<'a>, 43 | pub pointee_ty: InternalType<'a>, 44 | } 45 | 46 | #[derive(Debug, Clone, PartialEq)] 47 | pub(crate) struct TypedPointerType<'a> { 48 | pub ptr_type: PointerType<'a>, 49 | pub pointee_ty: InternalType<'a>, 50 | } 51 | 52 | #[derive(Debug, Clone, PartialEq)] 53 | pub(crate) struct StringType<'a> { 54 | pub struct_type: StructType<'a>, 55 | } 56 | 57 | impl<'a> InternalType<'a> { 58 | pub fn into_array_type(&self, size: u32) -> Result, String> { 59 | match self { 60 | InternalType::IntType(int_type) => Ok(InternalType::ArrayType( 61 | Box::new(InternalType::IntType(*int_type)), 62 | int_type.array_type(size), 63 | )), 64 | InternalType::FloatType(float_type) => Ok(InternalType::ArrayType( 65 | Box::new(InternalType::FloatType(*float_type)), 66 | float_type.array_type(size), 67 | )), 68 | InternalType::ArrayType(element_type, array_type) => Ok(InternalType::ArrayType( 69 | element_type.clone(), 70 | array_type.array_type(size), 71 | )), 72 | InternalType::StructType(struct_type) => Ok(InternalType::ArrayType( 73 | Box::new(InternalType::StructType(*struct_type)), 74 | struct_type.array_type(size), 75 | )), 76 | InternalType::VectorType(vector_type) => Ok(InternalType::ArrayType( 77 | Box::new(InternalType::VectorType(*vector_type)), 78 | vector_type.array_type(size), 79 | )), 80 | InternalType::StringType(string_type) => Ok(InternalType::ArrayType( 81 | Box::new(InternalType::StringType(string_type.clone())), 82 | string_type.struct_type.array_type(size), 83 | )), 84 | InternalType::PointerType(typed_pointer_type) => Ok(InternalType::ArrayType( 85 | Box::new(InternalType::PointerType(typed_pointer_type.clone())), 86 | typed_pointer_type.ptr_type.array_type(size), 87 | )), 88 | InternalType::ConstType(internal_type) => internal_type.into_array_type(size), 89 | InternalType::VoidType(_) => Err("VoidType cannot be converted to an array type.".to_string()), 90 | InternalType::Lvalue(_) => Err("Lvalue cannot be converted to an array type.".to_string()), 91 | InternalType::BoolType(int_type) => Ok(InternalType::ArrayType( 92 | Box::new(InternalType::BoolType(*int_type)), 93 | int_type.array_type(size), 94 | )), 95 | } 96 | } 97 | 98 | pub fn into_internal_value( 99 | &self, 100 | value: inkwell::values::BasicValueEnum<'a>, 101 | ) -> Result, &'static str> { 102 | match self { 103 | InternalType::BoolType(_) => Ok(InternalValue::BoolValue(value.into_int_value())), 104 | InternalType::IntType(ty) => Ok(InternalValue::IntValue( 105 | value.into_int_value(), 106 | InternalType::IntType(ty.clone()), 107 | )), 108 | InternalType::FloatType(ty) => Ok(InternalValue::FloatValue( 109 | value.into_float_value(), 110 | InternalType::FloatType(ty.clone()), 111 | )), 112 | InternalType::ArrayType(element_type, ty) => Ok(InternalValue::ArrayValue( 113 | value.into_array_value(), 114 | InternalType::ArrayType(element_type.clone(), ty.clone()), 115 | )), 116 | InternalType::StructType(ty) => Ok(InternalValue::StructValue( 117 | value.into_struct_value(), 118 | InternalType::StructType(ty.clone()), 119 | )), 120 | InternalType::VectorType(ty) => Ok(InternalValue::VectorValue( 121 | value.into_vector_value(), 122 | InternalType::VectorType(ty.clone()), 123 | )), 124 | InternalType::StringType(_) => Ok(InternalValue::StringValue(StringValue { 125 | struct_value: value.into_struct_value(), 126 | })), 127 | InternalType::PointerType(ptr_ty) => Ok(InternalValue::PointerValue(TypedPointerValue { 128 | ptr: value.into_pointer_value(), 129 | pointee_ty: ptr_ty.pointee_ty.clone(), 130 | })), 131 | InternalType::Lvalue(ptr_ty) => Ok(InternalValue::Lvalue(Lvalue { 132 | ptr: value.into_pointer_value(), 133 | pointee_ty: ptr_ty.pointee_ty.clone(), 134 | })), 135 | InternalType::VoidType(ty) => Ok(InternalValue::PointerValue(TypedPointerValue { 136 | ptr: value.into_pointer_value(), 137 | pointee_ty: InternalType::VoidType(*ty), 138 | })), 139 | InternalType::ConstType(internal_type) => internal_type.into_internal_value(value), 140 | } 141 | } 142 | 143 | #[allow(unused)] 144 | pub fn is_int_type(&self) -> bool { 145 | matches!(self, InternalType::IntType(_)) 146 | } 147 | 148 | #[allow(unused)] 149 | pub fn is_float_type(&self) -> bool { 150 | matches!(self, InternalType::FloatType(_)) 151 | } 152 | 153 | #[allow(unused)] 154 | pub fn is_array_type(&self) -> bool { 155 | matches!(self, InternalType::ArrayType(_, _)) 156 | } 157 | 158 | #[allow(unused)] 159 | pub fn is_struct_type(&self) -> bool { 160 | matches!(self, InternalType::StructType(_)) 161 | } 162 | 163 | #[allow(unused)] 164 | pub fn is_vector_type(&self) -> bool { 165 | matches!(self, InternalType::VectorType(_)) 166 | } 167 | 168 | #[allow(unused)] 169 | pub fn is_string_type(&self) -> bool { 170 | matches!(self, InternalType::StringType(_)) 171 | } 172 | 173 | pub fn is_void_type(&self) -> bool { 174 | matches!(self, InternalType::VoidType(_)) 175 | } 176 | 177 | #[allow(unused)] 178 | pub fn is_pointer_type(&self) -> bool { 179 | matches!(self, InternalType::PointerType(_)) 180 | } 181 | 182 | #[allow(unused)] 183 | pub fn is_const_type(&self) -> bool { 184 | matches!(self, InternalType::ConstType(_)) 185 | } 186 | 187 | #[allow(unused)] 188 | pub fn is_lvalue_type(&self) -> bool { 189 | matches!(self, InternalType::Lvalue(_)) 190 | } 191 | 192 | pub fn to_basic_type(&self, ptr_type: PointerType<'a>) -> BasicTypeEnum<'a> { 193 | match self { 194 | InternalType::IntType(t) => (*t).as_basic_type_enum(), 195 | InternalType::FloatType(t) => (*t).as_basic_type_enum(), 196 | InternalType::ArrayType(_, t) => (*t).as_basic_type_enum(), 197 | InternalType::StructType(t) => (*t).as_basic_type_enum(), 198 | InternalType::VectorType(t) => (*t).as_basic_type_enum(), 199 | InternalType::PointerType(t) => t.ptr_type.as_basic_type_enum(), 200 | InternalType::Lvalue(t) => t.ptr_type.as_basic_type_enum(), 201 | InternalType::StringType(t) => (*t).struct_type.as_basic_type_enum(), 202 | InternalType::VoidType(_) => BasicTypeEnum::PointerType(ptr_type), 203 | InternalType::ConstType(t) => t.to_basic_type(ptr_type), 204 | InternalType::BoolType(t) => (*t).as_basic_type_enum(), 205 | } 206 | } 207 | 208 | pub fn as_type_ref(&self) -> LLVMTypeRef { 209 | match self { 210 | InternalType::IntType(t) => t.as_type_ref(), 211 | InternalType::FloatType(t) => t.as_type_ref(), 212 | InternalType::ArrayType(_, t) => t.as_type_ref(), 213 | InternalType::StructType(t) => t.as_type_ref(), 214 | InternalType::VectorType(t) => t.as_type_ref(), 215 | InternalType::PointerType(t) => t.ptr_type.as_type_ref(), 216 | InternalType::Lvalue(t) => t.ptr_type.as_type_ref(), 217 | InternalType::StringType(t) => t.struct_type.as_type_ref(), 218 | InternalType::VoidType(t) => inkwell::types::AnyType::as_any_type_enum(t).as_type_ref(), 219 | InternalType::ConstType(t) => t.as_type_ref(), 220 | InternalType::BoolType(t) => t.as_type_ref(), 221 | } 222 | } 223 | } 224 | 225 | // TODO 226 | // impl<'a> fmt::Display for InternalType<'a> { 227 | // fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 228 | // match self { 229 | // InternalType::IntType(int_type) => { 230 | // match int_type.get_bit_width() { 231 | // 1 => write!(f, "bool"), 232 | // 8 => write!(f, "i8"), 233 | // 16 => write!(f, "i16"), 234 | // 32 => write!(f, "i32"), 235 | // 64 => write!(f, "i64"), 236 | // 128 => write!(f, "i128"), 237 | // } 238 | // }, 239 | // InternalType::FloatType(float_type) => todo!(), 240 | // InternalType::ArrayType(array_type) => todo!(), 241 | // InternalType::StructType(struct_type) => todo!(), 242 | // InternalType::VectorType(vector_type) => todo!(), 243 | // InternalType::StringType(string_type) => todo!(), 244 | // InternalType::VoidType(void_type) => todo!(), 245 | // InternalType::PointerType(typed_pointer_type) => todo!(), 246 | // InternalType::ConstType(internal_type) => todo!(), 247 | // InternalType::Lvalue(lvalue_type) => todo!(), 248 | // } 249 | // } 250 | // } 251 | 252 | impl<'ctx> CodeGenLLVM<'ctx> { 253 | pub(crate) fn compatible_types(&self, lvalue_type: InternalType<'ctx>, rvalue_type: InternalType<'ctx>) -> bool { 254 | match (lvalue_type, rvalue_type) { 255 | (InternalType::IntType(_), InternalType::IntType(_)) => true, 256 | (InternalType::FloatType(_), InternalType::FloatType(_)) => true, 257 | (InternalType::FloatType(_), InternalType::IntType(_)) => true, 258 | (InternalType::IntType(_), InternalType::FloatType(_)) => true, 259 | (InternalType::PointerType(_), InternalType::PointerType(_)) => true, 260 | (InternalType::StructType(_), InternalType::StructType(_)) => true, 261 | (InternalType::VectorType(_), InternalType::VectorType(_)) => true, 262 | (InternalType::ArrayType(element_type1, arr1), InternalType::ArrayType(element_type2, arr2)) => { 263 | (arr1.len() == arr2.len()) && self.compatible_types(*element_type1, *element_type2) 264 | } 265 | (InternalType::StringType(_), InternalType::StringType(_)) => true, 266 | (InternalType::StringType(_), InternalType::PointerType(typed_pointer_type)) => { 267 | // allow const_str assignment to StringType 268 | typed_pointer_type.pointee_ty.is_array_type() 269 | } 270 | (InternalType::PointerType(_), InternalType::StringType(_)) => { 271 | // allow StringType assignment to char* 272 | true 273 | } 274 | (InternalType::VoidType(_), _) => false, 275 | (InternalType::ConstType(inner_type), rvalue_type) => self.compatible_types(*inner_type, rvalue_type), 276 | _ => false, 277 | } 278 | } 279 | 280 | pub(crate) fn build_type( 281 | &self, 282 | type_specifier: TypeSpecifier, 283 | loc: Location, 284 | span_end: usize, 285 | ) -> InternalType<'ctx> { 286 | match type_specifier { 287 | TypeSpecifier::Const(inner_type_specifier) => { 288 | InternalType::ConstType(Box::new(self.build_type(*inner_type_specifier, loc, span_end))) 289 | } 290 | TypeSpecifier::Identifier(identifier) => todo!(), 291 | TypeSpecifier::ModuleImport(module_import) => todo!(), 292 | TypeSpecifier::AddressOf(type_specifier) => todo!(), 293 | TypeSpecifier::Dereference(inner_type_specifier) => { 294 | let pointee_ty = self.build_type(*inner_type_specifier, loc.clone(), span_end); 295 | InternalType::PointerType(Box::new(TypedPointerType { 296 | ptr_type: self.context.ptr_type(AddressSpace::default()).into(), 297 | pointee_ty, 298 | })) 299 | } 300 | TypeSpecifier::Array(array_type_specifier) => self.build_array_type(array_type_specifier, loc, span_end), 301 | TypeSpecifier::TypeToken(token) => self.build_type_token(token, loc.clone()), 302 | } 303 | } 304 | 305 | pub(crate) fn build_type_token(&self, type_token: Token, loc: Location) -> InternalType<'ctx> { 306 | match type_token.kind { 307 | TokenKind::Identifier { name } => { 308 | match self.struct_table.get(&name) { 309 | Some(struct_metadata) => { 310 | dbg!(struct_metadata); 311 | 312 | todo!(); 313 | // TODO 314 | } 315 | None => { 316 | display_single_diag(Diag { 317 | level: DiagLevel::Error, 318 | kind: DiagKind::UndefinedDataType(name), 319 | location: Some(DiagLoc { 320 | file: self.file_path.clone(), 321 | line: loc.line, 322 | column: loc.column, 323 | length: type_token.span.end, 324 | }), 325 | }); 326 | exit(1); 327 | } 328 | } 329 | } 330 | TokenKind::Int | TokenKind::UInt => { 331 | let data_layout = self.target_machine.get_target_data(); 332 | InternalType::IntType(self.context.ptr_sized_int_type(&data_layout, None)) 333 | } 334 | TokenKind::Int8 | TokenKind::UInt8 | TokenKind::Char => InternalType::IntType(self.context.i8_type()), 335 | TokenKind::Int16 | TokenKind::UInt16 => InternalType::IntType(self.context.i16_type()), 336 | TokenKind::Int32 | TokenKind::UInt32 => InternalType::IntType(self.context.i32_type()), 337 | TokenKind::Int64 | TokenKind::UInt64 => InternalType::IntType(self.context.i64_type()), 338 | TokenKind::Int128 | TokenKind::UInt128 => InternalType::IntType(self.context.i128_type()), 339 | TokenKind::Float16 => InternalType::FloatType(self.context.f16_type()), 340 | TokenKind::Float32 => InternalType::FloatType(self.context.f32_type()), 341 | TokenKind::Float64 => InternalType::FloatType(self.context.f64_type()), 342 | TokenKind::Float128 => InternalType::FloatType(self.context.f128_type()), 343 | TokenKind::Void => InternalType::VoidType(self.context.void_type()), 344 | TokenKind::Bool => InternalType::IntType(self.context.bool_type()), 345 | TokenKind::String => InternalType::StringType(self.string_type.clone()), 346 | _ => { 347 | display_single_diag(Diag { 348 | level: DiagLevel::Error, 349 | kind: DiagKind::InvalidTypeToken, 350 | location: Some(DiagLoc { 351 | file: self.file_path.clone(), 352 | line: loc.line, 353 | column: loc.column, 354 | length: type_token.span.end, 355 | }), 356 | }); 357 | exit(1); 358 | } 359 | } 360 | } 361 | 362 | pub(crate) fn build_array_type( 363 | &self, 364 | array_type_specifier: ArrayTypeSpecifier, 365 | loc: Location, 366 | span_end: usize, 367 | ) -> InternalType<'ctx> { 368 | let element_type = self.build_type(*array_type_specifier.element_type, loc.clone(), span_end); 369 | let array_type = match element_type.into_array_type( 370 | self.build_array_capacity(array_type_specifier.size, loc.clone(), span_end) 371 | .try_into() 372 | .unwrap(), 373 | ) { 374 | Ok(t) => t, 375 | Err(err) => { 376 | display_single_diag(Diag { 377 | level: DiagLevel::Error, 378 | kind: DiagKind::Custom(err), 379 | location: Some(DiagLoc { 380 | file: self.file_path.clone(), 381 | line: loc.line, 382 | column: loc.column, 383 | length: span_end, 384 | }), 385 | }); 386 | exit(1); 387 | } 388 | }; 389 | 390 | array_type 391 | } 392 | 393 | pub(crate) fn build_array_capacity(&self, array_capacity: ArrayCapacity, loc: Location, span_end: usize) -> u64 { 394 | match array_capacity { 395 | ArrayCapacity::Fixed(token_kind) => { 396 | if let TokenKind::Literal(literal) = token_kind { 397 | match self.build_literal(literal.clone()) { 398 | InternalValue::IntValue(int_value, ..) => int_value, 399 | _ => { 400 | display_single_diag(Diag { 401 | level: DiagLevel::Error, 402 | kind: DiagKind::InvalidTokenAsArrayCapacity, 403 | location: Some(DiagLoc { 404 | file: self.file_path.clone(), 405 | line: loc.line, 406 | column: loc.column, 407 | length: span_end, 408 | }), 409 | }); 410 | exit(1); 411 | } 412 | } 413 | } else { 414 | display_single_diag(Diag { 415 | level: DiagLevel::Error, 416 | kind: DiagKind::InvalidTypeToken, 417 | location: Some(DiagLoc { 418 | file: self.file_path.clone(), 419 | line: loc.line, 420 | column: loc.column, 421 | length: span_end, 422 | }), 423 | }); 424 | exit(1); 425 | } 426 | } 427 | ArrayCapacity::Dynamic => { 428 | display_single_diag(Diag { 429 | level: DiagLevel::Error, 430 | kind: DiagKind::Custom( 431 | "Cannot build array with dynamic memory management. Consider to use vector instead." 432 | .to_string(), 433 | ), 434 | location: Some(DiagLoc { 435 | file: self.file_path.clone(), 436 | line: loc.line, 437 | column: loc.column, 438 | length: span_end, 439 | }), 440 | }); 441 | exit(1); 442 | } 443 | } 444 | .get_zero_extended_constant() 445 | .unwrap() 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /crates/diag/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diag" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | ast = { path = "../ast", version = "*" } 8 | utils = { path = "../utils", version = "*" } 9 | colorized = "1.0.0" 10 | console = "0.15.11" -------------------------------------------------------------------------------- /crates/diag/src/errors.rs: -------------------------------------------------------------------------------- 1 | use ast::token::{Location, Span}; 2 | use colorized::{Color, Colors}; 3 | use console::user_attended; 4 | use std::fmt::{Debug, Display}; 5 | use utils::{ 6 | purify_string::{saturating_sub, spaces}, 7 | tui::tui_error, 8 | }; 9 | 10 | pub trait CompileTypeErrorType: Display + Debug { 11 | fn context(&self) -> String; 12 | } 13 | #[derive(Debug, Clone)] 14 | pub struct CompileTimeError { 15 | pub etype: ErrorType, 16 | pub file_name: Option, 17 | pub location: Location, 18 | pub source_content: Box, 19 | pub verbose: Option, 20 | pub caret: Option, 21 | } 22 | 23 | const PANEL_LENGTH: usize = 4; 24 | 25 | impl CompileTimeError { 26 | pub fn print(&self) { 27 | println!(); 28 | 29 | let error_message = { 30 | if let Some(verbose) = self.verbose.clone() { 31 | verbose 32 | } else { 33 | self.etype.context() 34 | } 35 | }; 36 | 37 | if let Some(file_name) = self.file_name.clone() { 38 | tui_error(error_message.to_lowercase()); 39 | println!( 40 | " --> {}:{}:{}", 41 | file_name, self.location.line, self.location.column 42 | ); 43 | println!(); 44 | } 45 | 46 | let mut starting_line = saturating_sub(self.location.line, PANEL_LENGTH); 47 | let sources_lines: Vec<&str> = self.source_content.split("\n").collect(); 48 | 49 | while starting_line < self.location.line + PANEL_LENGTH { 50 | if let Some(line_str) = sources_lines.get(starting_line) { 51 | if starting_line + 1 == self.location.line && user_attended() { 52 | print!( 53 | "{}", 54 | format!("{}{} | {}", spaces(2), starting_line + 1, line_str).color(Colors::RedFg) 55 | ); 56 | } else { 57 | print!("{}{} | {}", spaces(2), starting_line + 1, line_str); 58 | } 59 | } else { 60 | break; 61 | } 62 | 63 | starting_line += 1; 64 | print!("\n"); 65 | } 66 | 67 | println!(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /crates/diag/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod errors; 2 | -------------------------------------------------------------------------------- /crates/diag/src/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod errors; 2 | pub mod lexer_errors; 3 | pub mod parser_errors; 4 | mod tests; -------------------------------------------------------------------------------- /crates/layout/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "layout" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | codegen_llvm = { path = "../codegen_llvm", version = "*" } -------------------------------------------------------------------------------- /crates/layout/src/lib.rs: -------------------------------------------------------------------------------- 1 | use codegen_llvm::diag::{Diag, DiagKind, DiagLevel, display_single_diag}; 2 | use std::{ 3 | fs::{self, File}, 4 | io::Write, 5 | path::Path, 6 | }; 7 | 8 | fn cyrus_version() -> String { 9 | env!("CARGO_PKG_VERSION").to_string() 10 | } 11 | 12 | fn create_common_files(output: String) -> Result<(), String> { 13 | if fs::exists(output.clone()).map_err(|err| err.to_string())? { 14 | display_single_diag(Diag { 15 | level: DiagLevel::Error, 16 | kind: DiagKind::Custom(format!("A project named '{}' already exists in this location.", output)), 17 | location: None, 18 | }); 19 | std::process::exit(1); 20 | } 21 | 22 | fs::create_dir(output.clone()).map_err(|_| format!("Failed to create '{}' directory.", output))?; 23 | 24 | let mut gitignore = File::create(format!("{}/.gitignore", output)) 25 | .map_err(|_| format!("Failed to create '{}/.gitignore' file.", output))?; 26 | 27 | gitignore.write("build\n".as_bytes()).map_err(|err| err.to_string())?; 28 | gitignore.write("tmp\n".as_bytes()).map_err(|err| err.to_string())?; 29 | gitignore.write(".env\n".as_bytes()).map_err(|err| err.to_string())?; 30 | 31 | Ok(()) 32 | } 33 | 34 | pub fn create_project(project_name: String) -> Result<(), String> { 35 | create_common_files(project_name.clone())?; 36 | 37 | let mut project_file = File::create(format!("{}/Project.toml", project_name)) 38 | .map_err(|_| "Failed to create 'Project.toml' file.".to_string())?; 39 | 40 | let pure_project_name = Path::new(&project_name.clone()) 41 | .file_name() 42 | .ok_or("Failed to retrieve project directory name.")? 43 | .to_str() 44 | .unwrap() 45 | .to_string(); 46 | 47 | project_file 48 | .write( 49 | format!( 50 | "[project] 51 | name = \"{}\" 52 | version = \"0.0.1\" 53 | type = \"exec\" 54 | authors = [ \" john_doe@mail.com\"] 55 | 56 | [dependencies] 57 | libraries = [] 58 | library_path = [] 59 | 60 | [compiler] 61 | cpu = \"generic\" 62 | optimize = \"o1\" 63 | sources = [\"src/*\"] 64 | version = \"{}\" 65 | ", 66 | pure_project_name, 67 | cyrus_version() 68 | ) 69 | .as_bytes(), 70 | ) 71 | .map_err(|err| err.to_string())?; 72 | 73 | fs::create_dir(format!("{}/src", project_name)) 74 | .map_err(|_| format!("Failed to create '{}/src' directory.", project_name))?; 75 | 76 | let mut main_file = File::create(format!("{}/src/main.cyr", project_name)) 77 | .map_err(|_| format!("Failed to create '{}/src/main.cyr' file.", project_name))?; 78 | 79 | main_file 80 | .write("fn main() {\n\tprintf(\"Hello World\");\n}".as_bytes()) 81 | .map_err(|err| err.to_string())?; 82 | 83 | Ok(()) 84 | } 85 | 86 | pub fn create_library_project(project_name: String) -> Result<(), String> { 87 | create_common_files(project_name.clone())?; 88 | 89 | let mut project_file = File::create(format!("{}/Project.toml", project_name)) 90 | .map_err(|_| "Failed to create 'Project.toml' file.".to_string())?; 91 | 92 | let pure_project_name = Path::new(&project_name.clone()) 93 | .file_name() 94 | .ok_or("Failed to retrieve project directory name.")? 95 | .to_str() 96 | .unwrap() 97 | .to_string(); 98 | 99 | project_file 100 | .write( 101 | format!( 102 | "[project] 103 | name = \"{}\" 104 | version = \"0.0.1\" 105 | type = \"lib\" 106 | authors = [ \" john_doe@mail.com\"] 107 | 108 | [dependencies] 109 | libraries = [] 110 | library_path = [] 111 | 112 | [compiler] 113 | cpu = \"generic\" 114 | optimize = \"o1\" 115 | sources = [\"src/*\"] 116 | version = \"{}\" 117 | ", 118 | pure_project_name, 119 | cyrus_version() 120 | ) 121 | .as_bytes(), 122 | ) 123 | .map_err(|err| err.to_string())?; 124 | 125 | fs::create_dir(format!("{}/src", project_name)) 126 | .map_err(|_| format!("Failed to create '{}/src' directory.", project_name))?; 127 | 128 | let mut main_file = File::create(format!("{}/src/lib.cyr", project_name)) 129 | .map_err(|_| format!("Failed to create '{}/src/lib.cyr' file.", project_name))?; 130 | 131 | main_file 132 | .write( 133 | "/* 134 | Welcome to your new library project! (lib.cyr) 135 | 136 | You can start binding some functions in this library and do whatever you want. 137 | 138 | Some suggestions to get started: 139 | 1. Define your public API functions first 140 | 2. Add documentation comments for each exported function 141 | 3. Organize related functionality into modules 142 | 4. Consider error handling strategies 143 | 144 | Remember to: 145 | - Keep your interfaces clean and simple 146 | - Maintain consistent naming conventions 147 | - Document any platform-specific behavior 148 | - Version your library appropriately 149 | 150 | Happy coding! 151 | */\n\n" 152 | .as_bytes(), 153 | ) 154 | .map_err(|err| err.to_string())?; 155 | 156 | main_file 157 | .write("// extern fn some_library(): *void;\n".as_bytes()) 158 | .map_err(|err| err.to_string())?; 159 | 160 | Ok(()) 161 | } 162 | -------------------------------------------------------------------------------- /crates/lexer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lexer" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | ast = { path = "../ast", version = "*" } 8 | utils = { path = "../utils", version = "*" } 9 | diag = { path = "../diag", version = "*" } 10 | unicode-segmentation = "1.9.0" 11 | 12 | [lib] 13 | name = "lexer" 14 | path = "./src/lib.rs" 15 | 16 | [[bin]] 17 | name = "lexer" 18 | path = "./src/cli.rs" 19 | -------------------------------------------------------------------------------- /crates/lexer/src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use lexer::new_lexer_debugger; 4 | use utils::fs::read_file; 5 | 6 | pub fn main() { 7 | let args: Vec = env::args().collect(); 8 | let file_path = args[1].clone(); 9 | let file_content = read_file(file_path.clone()).0; 10 | 11 | new_lexer_debugger(file_content); 12 | } 13 | -------------------------------------------------------------------------------- /crates/lexer/src/diag.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use ast::token::{Location, Span}; 4 | use diag::errors::{CompileTimeError, CompileTypeErrorType}; 5 | 6 | #[derive(Debug)] 7 | pub enum LexicalErrorType { 8 | UnterminatedStringLiteral, 9 | InvalidFloatLiteral, 10 | InvalidIntegerLiteral, 11 | UnterminatedMultiLineComment, 12 | InvalidChar(char), 13 | EmptyCharLiteral, 14 | } 15 | 16 | impl fmt::Display for LexicalErrorType { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | match self { 19 | LexicalErrorType::UnterminatedStringLiteral => write!(f, "UnterminatedStringLiteral"), 20 | LexicalErrorType::InvalidFloatLiteral => write!(f, "InvalidFloatLiteral"), 21 | LexicalErrorType::InvalidIntegerLiteral => write!(f, "InvalidIntegerLiteral"), 22 | LexicalErrorType::UnterminatedMultiLineComment => write!(f, "UnterminatedMultiLineComment"), 23 | LexicalErrorType::EmptyCharLiteral => write!(f, "EmptyCharLiteral"), 24 | LexicalErrorType::InvalidChar(_) => write!(f, "InvalidChar"), 25 | } 26 | } 27 | } 28 | 29 | impl CompileTypeErrorType for LexicalErrorType { 30 | fn context(&self) -> String { 31 | String::from(match self { 32 | LexicalErrorType::UnterminatedStringLiteral => "Expected terminate string literal with double quote", 33 | LexicalErrorType::InvalidFloatLiteral => "Invalid float literal", 34 | LexicalErrorType::InvalidIntegerLiteral => "Invalid integer literal", 35 | LexicalErrorType::UnterminatedMultiLineComment => "Unterminated multi-line comment", 36 | LexicalErrorType::EmptyCharLiteral => "Empty char literal is invalid", 37 | LexicalErrorType::InvalidChar(ch) => { 38 | return format!("Invalid char: '{}'", ch); 39 | } 40 | }) 41 | } 42 | } 43 | 44 | pub fn lexer_invalid_char_error( 45 | file_name: String, 46 | line: usize, 47 | column: usize, 48 | ch: char, 49 | span: Span, 50 | source_content: Box, 51 | ) { 52 | CompileTimeError { 53 | location: Location::new(line, column), 54 | etype: LexicalErrorType::InvalidChar(ch), 55 | file_name: Some(file_name), 56 | verbose: None, 57 | caret: Some(span), 58 | source_content, 59 | } 60 | .print(); 61 | } 62 | -------------------------------------------------------------------------------- /crates/lexer/src/format.rs: -------------------------------------------------------------------------------- 1 | use crate::Lexer; 2 | use core::fmt; 3 | 4 | impl fmt::Display for Lexer { 5 | fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result { 6 | write!(f, "pos: {}, next_pos: {}, char: {}", self.pos, self.next_pos, self.ch) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /crates/lexer/src/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lexer; -------------------------------------------------------------------------------- /crates/lexer/src/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::Lexer; 4 | use ast::ast::Literal; 5 | use ast::token::{Span, TokenKind}; 6 | 7 | #[test] 8 | fn test_skip_whitespace() { 9 | let mut lexer = Lexer::new( 10 | String::from( 11 | " 12 | Hello World 13 | ", 14 | ), 15 | String::from("test.cyr"), 16 | ); 17 | 18 | lexer.skip_whitespace(); 19 | } 20 | 21 | fn assert_tokens(input: &'static str, expected_tokens: Option<&Vec>, spans: Option<&Vec>) { 22 | let lexer = Lexer::new(input.to_string(), String::from("test_package.cyr")); 23 | let tokens = lexer; 24 | 25 | for (i, token) in tokens.into_iter().enumerate() { 26 | println!("{:?}", token); 27 | 28 | if let Some(list) = expected_tokens { 29 | assert_eq!(token.kind, list[i]); 30 | } 31 | 32 | if let Some(list) = spans { 33 | assert_eq!(token.span.start, list[i].start); 34 | assert_eq!(token.span.end, list[i].end); 35 | } 36 | } 37 | } 38 | 39 | #[test] 40 | fn test_code_1() { 41 | let code = " 42 | if a == 2 { 43 | puts \"Hello World\"; 44 | } 45 | 46 | puts(); 47 | "; 48 | 49 | assert_tokens(code, None, None); 50 | } 51 | 52 | #[test] 53 | fn test_code_2() { 54 | let code = " 55 | fn divide(num1: i32, num2: i32) { 56 | if num2 == 0 { 57 | 1 + 2; 58 | } 59 | 60 | return num1 / num2; 61 | } 62 | 63 | divide(10, 2); 64 | "; 65 | 66 | assert_tokens(code, None, None); 67 | } 68 | 69 | #[test] 70 | fn test_code_3() { 71 | let code = "// Here is sample for loop 72 | for (#i = 0; i < 10; i++) { 73 | puts(\"i -> {i}\"); 74 | }"; 75 | 76 | assert_tokens(code, None, None); 77 | } 78 | 79 | #[test] 80 | fn test_code_4() { 81 | let code = "public fn main(): i32 { 82 | 83 | }"; 84 | 85 | assert_tokens(code, None, None); 86 | } 87 | 88 | #[test] 89 | fn test_boolean_values() { 90 | assert_tokens( 91 | "true == false", 92 | Some(&vec![TokenKind::True, TokenKind::Equal, TokenKind::False]), 93 | None, 94 | ); 95 | assert_tokens("#my_var: bool = true;", None, None); 96 | } 97 | 98 | #[test] 99 | fn test_operators() { 100 | assert_tokens( 101 | "+ - * / % =", 102 | Some(&vec![ 103 | TokenKind::Plus, 104 | TokenKind::Minus, 105 | TokenKind::Asterisk, 106 | TokenKind::Slash, 107 | TokenKind::Percent, 108 | TokenKind::Assign, 109 | ]), 110 | None, 111 | ); 112 | } 113 | 114 | #[test] 115 | fn test_comments() { 116 | assert_tokens("// Single line comment", None, None); 117 | 118 | let code = String::from( 119 | " 120 | // Sample comments 121 | // Another comment line 122 | 1 + 2 123 | // After expression comments work too! 124 | \"It works very well!\" 125 | 126 | /* Multi 127 | Line 128 | Comments 129 | Also works! */ 130 | 131 | 1 + 2 132 | print(); 133 | 134 | // Another comment after multi-line comment. 135 | ", 136 | ); 137 | 138 | let lexer = Lexer::new(code, String::from("test_package.cyr")); 139 | for token in lexer { 140 | println!("{:?}", token); 141 | } 142 | } 143 | 144 | #[test] 145 | fn test_symbols() { 146 | assert_tokens( 147 | "() {} , # |", 148 | Some(&vec![ 149 | TokenKind::LeftParen, 150 | TokenKind::RightParen, 151 | TokenKind::LeftBrace, 152 | TokenKind::RightBrace, 153 | TokenKind::Comma, 154 | TokenKind::Hashtag, 155 | TokenKind::Pipe, 156 | ]), 157 | None, 158 | ); 159 | } 160 | 161 | #[test] 162 | fn test_equals() { 163 | assert_tokens( 164 | "!= , ==", 165 | Some(&vec![TokenKind::NotEqual, TokenKind::Comma, TokenKind::Equal]), 166 | None, 167 | ); 168 | } 169 | 170 | #[test] 171 | fn test_keywords() { 172 | assert_tokens( 173 | "fn match if else return for break continue", 174 | Some(&vec![ 175 | TokenKind::Function, 176 | TokenKind::Match, 177 | TokenKind::If, 178 | TokenKind::Else, 179 | TokenKind::Return, 180 | TokenKind::For, 181 | TokenKind::Break, 182 | TokenKind::Continue, 183 | ]), 184 | None, 185 | ); 186 | } 187 | 188 | #[test] 189 | fn test_less_greater() { 190 | assert_tokens( 191 | "<= >=", 192 | Some(&vec![TokenKind::LessEqual, TokenKind::GreaterEqual]), 193 | None, 194 | ); 195 | } 196 | 197 | #[test] 198 | fn test_and_or() { 199 | assert_tokens("&& ||", Some(&vec![TokenKind::And, TokenKind::Or]), None); 200 | } 201 | 202 | #[test] 203 | fn test_reading_identifier() { 204 | assert_tokens( 205 | "fn foo() {}", 206 | Some(&vec![ 207 | TokenKind::Function, 208 | TokenKind::Identifier { 209 | name: "foo".to_string(), 210 | }, 211 | TokenKind::LeftParen, 212 | TokenKind::RightParen, 213 | TokenKind::LeftBrace, 214 | TokenKind::RightBrace, 215 | ]), 216 | None, 217 | ); 218 | } 219 | 220 | #[test] 221 | fn test_variable_declaration() { 222 | assert_tokens( 223 | "#my_var = 10;", 224 | Some(&vec![ 225 | TokenKind::Hashtag, 226 | TokenKind::Identifier { 227 | name: "my_var".to_string(), 228 | }, 229 | TokenKind::Assign, 230 | TokenKind::Literal(Literal::Integer(10)), 231 | TokenKind::Semicolon, 232 | ]), 233 | None, 234 | ); 235 | } 236 | 237 | #[test] 238 | fn test_function_declaration() { 239 | assert_tokens( 240 | "fn foo_bar(a, b) { return a + b; }", 241 | Some(&vec![ 242 | TokenKind::Function, 243 | TokenKind::Identifier { 244 | name: "foo_bar".to_string(), 245 | }, 246 | TokenKind::LeftParen, 247 | TokenKind::Identifier { name: "a".to_string() }, 248 | TokenKind::Comma, 249 | TokenKind::Identifier { name: "b".to_string() }, 250 | TokenKind::RightParen, 251 | TokenKind::LeftBrace, 252 | TokenKind::Return, 253 | TokenKind::Identifier { name: "a".to_string() }, 254 | TokenKind::Plus, 255 | TokenKind::Identifier { name: "b".to_string() }, 256 | TokenKind::Semicolon, 257 | TokenKind::RightBrace, 258 | ]), 259 | None, 260 | ); 261 | } 262 | 263 | #[test] 264 | fn test_function_call() { 265 | assert_tokens( 266 | "foo_bar()", 267 | Some(&vec![ 268 | TokenKind::Identifier { 269 | name: "foo_bar".to_string(), 270 | }, 271 | TokenKind::LeftParen, 272 | TokenKind::RightParen, 273 | ]), 274 | None, 275 | ); 276 | 277 | assert_tokens( 278 | "foo_bar(1, 2)", 279 | Some(&vec![ 280 | TokenKind::Identifier { 281 | name: "foo_bar".to_string(), 282 | }, 283 | TokenKind::LeftParen, 284 | TokenKind::Literal(Literal::Integer(1)), 285 | TokenKind::Comma, 286 | TokenKind::Literal(Literal::Integer(2)), 287 | TokenKind::RightParen, 288 | ]), 289 | None, 290 | ); 291 | } 292 | 293 | #[test] 294 | fn test_str() { 295 | assert_tokens( 296 | "\"Cyrus-Lang\"", 297 | Some(&vec![TokenKind::Literal(Literal::String("Cyrus-Lang".to_string()))]), 298 | None, 299 | ); 300 | } 301 | 302 | #[test] 303 | fn test_floating_numbers() { 304 | assert_tokens("2.56", Some(&vec![TokenKind::Literal(Literal::Float(2.56))]), None); 305 | } 306 | 307 | #[test] 308 | fn test_increment_and_decrement() { 309 | assert_tokens( 310 | "i++", 311 | Some(&vec![ 312 | TokenKind::Identifier { name: "i".to_string() }, 313 | TokenKind::Increment, 314 | ]), 315 | None, 316 | ); 317 | 318 | assert_tokens( 319 | "i--", 320 | Some(&vec![ 321 | TokenKind::Identifier { name: "i".to_string() }, 322 | TokenKind::Decrement, 323 | ]), 324 | None, 325 | ); 326 | } 327 | 328 | #[test] 329 | fn test_tokenizing_emoji() { 330 | assert_tokens("\"This is 🖤 made by a string.\"", None, None); 331 | assert_tokens("printf(\"Hello 🖤\");", None, None); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /crates/parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parser" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | lexer = { path = "../lexer", version = "*" } 8 | ast = { path = "../ast", version = "*" } 9 | utils = { path = "../utils", version = "*" } 10 | diag = { path = "../diag", version = "*" } 11 | 12 | [lib] 13 | name = "parser" 14 | path = "./src/lib.rs" 15 | 16 | [[bin]] 17 | name = "parser" 18 | path = "./src/cli.rs" 19 | -------------------------------------------------------------------------------- /crates/parser/src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use lexer::Lexer; 4 | use parser::Parser; 5 | use utils::fs::read_file; 6 | 7 | pub fn main() { 8 | let args: Vec = env::args().collect(); 9 | let file_path = args[1].clone(); 10 | let file_content = read_file(file_path.clone()).0; 11 | let mut lexer = Lexer::new(file_content, file_path); 12 | 13 | match Parser::new(&mut lexer).parse() { 14 | Ok(result) => println!("{:#?}", result), 15 | Err(errors) => { 16 | for err in errors { 17 | err.print(); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/parser/src/common.rs: -------------------------------------------------------------------------------- 1 | use crate::ParseError; 2 | use crate::Parser; 3 | use crate::diag::ParserErrorType; 4 | use crate::prec::Precedence; 5 | use ast::ast::*; 6 | use ast::token::*; 7 | use diag::errors::CompileTimeError; 8 | 9 | impl<'a> Parser<'a> { 10 | pub fn parse_identifier(&mut self) -> Result> { 11 | match self.current_token.kind.clone() { 12 | TokenKind::Identifier { name } => Ok(Identifier { 13 | name, 14 | span: self.current_token.span.clone(), 15 | loc: self.current_location(), 16 | }), 17 | _ => { 18 | return Err(CompileTimeError { 19 | location: self.current_location(), 20 | etype: ParserErrorType::ExpectedIdentifier, 21 | file_name: Some(self.lexer.file_name.clone()), 22 | source_content: Box::new(self.lexer.input.clone()), 23 | verbose: None, 24 | caret: Some(self.current_token.span.clone()), 25 | }); 26 | } 27 | } 28 | } 29 | 30 | pub fn matches_type_token(&mut self, token_kind: TokenKind) -> bool { 31 | if PRIMITIVE_TYPES.contains(&token_kind.clone()) { 32 | return true; 33 | } else if let TokenKind::Identifier { .. } = token_kind.clone() { 34 | return true; 35 | } else { 36 | matches!( 37 | token_kind.clone(), 38 | TokenKind::Asterisk | TokenKind::Ampersand | TokenKind::Const 39 | ) 40 | } 41 | } 42 | 43 | pub fn parse_type_specifier(&mut self) -> Result { 44 | let mut base_type = self.parse_base_type_token()?; 45 | 46 | loop { 47 | if self.peek_token_is(TokenKind::Asterisk) { 48 | self.next_token(); 49 | base_type = TypeSpecifier::Dereference(Box::new(base_type)); 50 | } else if self.peek_token_is(TokenKind::Ampersand) { 51 | self.next_token(); 52 | base_type = TypeSpecifier::AddressOf(Box::new(base_type)); 53 | } else if self.peek_token_is(TokenKind::LeftBracket) { 54 | self.next_token(); // consume base_type 55 | base_type = self.parse_array_type(base_type)?; 56 | } else { 57 | break; 58 | } 59 | } 60 | 61 | Ok(base_type) 62 | } 63 | 64 | fn parse_base_type_token(&mut self) -> Result { 65 | let current = self.current_token.clone(); 66 | 67 | let parsed_kind = match current.kind.clone() { 68 | token_kind if PRIMITIVE_TYPES.contains(&token_kind) => Ok(TypeSpecifier::TypeToken(current)), 69 | TokenKind::Const => { 70 | self.next_token(); // consume const 71 | let inner_type = self.parse_base_type_token()?; 72 | Ok(TypeSpecifier::Const(Box::new(inner_type))) 73 | } 74 | TokenKind::Identifier { .. } => { 75 | if self.peek_token_is(TokenKind::DoubleColon) { 76 | let module_import = self.parse_module_import()?; 77 | dbg!(module_import.clone()); 78 | dbg!(self.current_token.kind.clone()); 79 | Ok(TypeSpecifier::ModuleImport(module_import)) 80 | } else { 81 | Ok(TypeSpecifier::Identifier(Identifier { 82 | name: { 83 | if let TokenKind::Identifier { name } = current.kind { 84 | name 85 | } else { 86 | unreachable!() 87 | } 88 | }, 89 | span: current.span, 90 | loc: self.current_location(), 91 | })) 92 | } 93 | } 94 | _ => Err(CompileTimeError { 95 | location: self.current_location(), 96 | etype: ParserErrorType::InvalidTypeToken(current.kind.clone()), 97 | file_name: Some(self.lexer.file_name.clone()), 98 | source_content: Box::new(self.lexer.input.clone()), 99 | verbose: None, 100 | caret: Some(Span::new(current.span.start, self.current_token.span.end)), 101 | }), 102 | }; 103 | 104 | parsed_kind 105 | } 106 | 107 | pub fn parse_array_type(&mut self, base_type_specifier: TypeSpecifier) -> Result { 108 | let mut dimensions: Vec = Vec::new(); 109 | 110 | while self.current_token_is(TokenKind::LeftBracket) { 111 | let array_capacity = self.parse_single_array_capacity()?; 112 | // prevent consuming the latest token_kind here 113 | if self.peek_token_is(TokenKind::LeftBracket) { 114 | self.next_token(); // consume right bracket 115 | } 116 | dimensions.push(array_capacity); 117 | } 118 | 119 | let mut type_specifier = base_type_specifier.clone(); 120 | for dimension in dimensions.iter().rev() { 121 | type_specifier = TypeSpecifier::Array(ArrayTypeSpecifier { 122 | size: dimension.clone(), 123 | element_type: Box::new(type_specifier), 124 | }); 125 | } 126 | 127 | Ok(type_specifier) 128 | } 129 | 130 | pub fn parse_single_array_capacity(&mut self) -> Result { 131 | self.expect_current(TokenKind::LeftBracket)?; 132 | if self.current_token_is(TokenKind::RightBracket) { 133 | return Ok(ArrayCapacity::Dynamic); 134 | } 135 | let capacity = self.current_token.kind.clone(); 136 | self.expect_peek(TokenKind::RightBracket)?; 137 | Ok(ArrayCapacity::Fixed(capacity)) 138 | } 139 | 140 | pub fn parse_single_array_index(&mut self) -> Result { 141 | self.expect_current(TokenKind::LeftBracket)?; 142 | let start = self.current_token.span.start; 143 | 144 | if self.current_token_is(TokenKind::RightBracket) { 145 | return Err(CompileTimeError { 146 | location: self.current_location(), 147 | etype: ParserErrorType::InvalidToken(self.current_token.kind.clone()), 148 | file_name: Some(self.lexer.file_name.clone()), 149 | source_content: Box::new(self.lexer.input.clone()), 150 | verbose: None, 151 | caret: Some(Span::new(start, self.current_token.span.end)), 152 | }); 153 | } 154 | let index = self.parse_expression(Precedence::Lowest)?.0; 155 | self.expect_peek(TokenKind::RightBracket)?; 156 | Ok(index) 157 | } 158 | 159 | pub fn parse_storage_class(&mut self, token: Token) -> Result { 160 | let storage_class = { 161 | if self.current_token_is(TokenKind::Inline) { 162 | self.next_token(); 163 | StorageClass::Inline 164 | } else if self.current_token_is(TokenKind::Extern) { 165 | self.next_token(); 166 | StorageClass::Extern 167 | } else if self.current_token_is(TokenKind::Public) { 168 | self.next_token(); 169 | if self.current_token_is(TokenKind::Inline) { 170 | self.next_token(); 171 | StorageClass::PublicInline 172 | } else if self.current_token_is(TokenKind::Extern) { 173 | self.next_token(); 174 | StorageClass::PublicExtern 175 | } else { 176 | StorageClass::Public 177 | } 178 | } else { 179 | return Err(CompileTimeError { 180 | location: self.current_location(), 181 | etype: ParserErrorType::InvalidToken(token.kind), 182 | file_name: Some(self.lexer.file_name.clone()), 183 | source_content: Box::new(self.lexer.input.clone()), 184 | verbose: None, 185 | caret: Some(token.span.clone()), 186 | }); 187 | } 188 | }; 189 | 190 | Ok(storage_class) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /crates/parser/src/diag.rs: -------------------------------------------------------------------------------- 1 | use ast::token::TokenKind; 2 | use core::fmt; 3 | use diag::errors::CompileTypeErrorType; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum ParserErrorType { 7 | UnexpectedToken(TokenKind, TokenKind), 8 | ExpectedToken(TokenKind), 9 | InvalidTypeToken(TokenKind), 10 | InvalidToken(TokenKind), 11 | MissingClosingBrace, 12 | MissingOpeningBrace, 13 | MissingClosingParen, 14 | MissingOpeningParen, 15 | ExpectedIdentifier, 16 | MissingSemicolon, 17 | MissingComma, 18 | IncompleteConditionalForLoop, 19 | InvalidUntypedArrayConstructor, 20 | } 21 | 22 | impl fmt::Display for ParserErrorType { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | match self { 25 | ParserErrorType::UnexpectedToken(_, _) => write!(f, "UnexpectedToken"), 26 | ParserErrorType::ExpectedToken(_) => write!(f, "ExpectedToken"), 27 | ParserErrorType::InvalidTypeToken(_) => write!(f, "InvalidTypeToken"), 28 | ParserErrorType::InvalidToken(_) => write!(f, "InvalidToken"), 29 | ParserErrorType::MissingClosingBrace => write!(f, "MissingClosingBrace"), 30 | ParserErrorType::MissingOpeningBrace => write!(f, "MissingOpeningBrace"), 31 | ParserErrorType::MissingClosingParen => write!(f, "MissingClosingParen "), 32 | ParserErrorType::MissingOpeningParen => write!(f, "MissingOpeningParen "), 33 | ParserErrorType::ExpectedIdentifier => write!(f, "ExpectedIdentifier"), 34 | ParserErrorType::MissingSemicolon => write!(f, "MissingSemicolon"), 35 | ParserErrorType::MissingComma => write!(f, "MissingComma"), 36 | ParserErrorType::IncompleteConditionalForLoop => write!(f, "IncompleteConditionalForLoop"), 37 | ParserErrorType::InvalidUntypedArrayConstructor => write!(f, "InvalidUntypedArrayConstructor"), 38 | } 39 | } 40 | } 41 | 42 | impl CompileTypeErrorType for ParserErrorType { 43 | fn context(&self) -> String { 44 | match self { 45 | ParserErrorType::UnexpectedToken(current, expected) => { 46 | format!("Expected token '{}' but got '{}'", expected, current) 47 | } 48 | ParserErrorType::ExpectedToken(expected) => { 49 | format!("Expected token '{}'", expected) 50 | } 51 | ParserErrorType::InvalidToken(token_kind) => { 52 | format!("Unexpected token: '{}'", token_kind) 53 | } 54 | ParserErrorType::InvalidTypeToken(token_kind) => { 55 | format!("Expected type token but got '{}'", token_kind) 56 | } 57 | ParserErrorType::MissingClosingBrace => format!("Missing closing brace '}}'"), 58 | ParserErrorType::MissingOpeningBrace => format!("Missing opening brace '{{'"), 59 | ParserErrorType::MissingClosingParen => format!("Missing closing paren ')'"), 60 | ParserErrorType::MissingOpeningParen => format!("Missing opening paren '('"), 61 | ParserErrorType::ExpectedIdentifier => format!("Expected an identifier"), 62 | ParserErrorType::MissingSemicolon => format!("Missing semicolon"), 63 | ParserErrorType::MissingComma => format!("Missing comma"), 64 | ParserErrorType::IncompleteConditionalForLoop => { 65 | format!( 66 | "Defined a conditional for loop with incomplete condition. \n 67 | Consider to add a condition to current for loop or change it into unconditional for loop" 68 | ) 69 | } 70 | ParserErrorType::InvalidUntypedArrayConstructor => { 71 | "If untyped array constructor would not have an item, consider to remove it.".to_string() 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ::diag::errors::CompileTimeError; 2 | use ast::ast::*; 3 | use ast::token::*; 4 | use diag::ParserErrorType; 5 | use lexer::*; 6 | use utils::fs::read_file; 7 | 8 | mod common; 9 | mod diag; 10 | mod exprs; 11 | mod prec; 12 | mod stmts; 13 | mod tests; 14 | 15 | pub type ParseError = CompileTimeError; 16 | 17 | /// Parses the program from the given file path. 18 | /// 19 | /// # Parameters 20 | /// - `file_path`: The path to the file containing the program code. 21 | /// 22 | /// # Returns 23 | /// A tuple containing: 24 | /// - `ProgramTree`: The parsed program representation. 25 | /// - `String`: The name of the file. 26 | pub fn parse_program(file_path: String) -> (ProgramTree, String) { 27 | let file = read_file(file_path.clone()); 28 | let code = file.0; 29 | 30 | let mut lexer = Lexer::new(code, file_path.clone()); 31 | let mut parser = Parser::new(&mut lexer); 32 | 33 | let program = match parser.parse() { 34 | Ok(result) => { 35 | if let Node::ProgramTree(program) = result { 36 | program 37 | } else { 38 | panic!("Expected a program given as input to the compiler but got unknown."); 39 | } 40 | } 41 | Err(errors) => { 42 | parser.display_parser_errors(errors); 43 | panic!(); 44 | } 45 | }; 46 | 47 | (program, file.1) 48 | } 49 | 50 | #[derive(Debug)] 51 | pub struct Parser<'a> { 52 | lexer: &'a mut Lexer, 53 | current_token: Token, 54 | peek_token: Token, 55 | errors: Vec, 56 | } 57 | 58 | impl<'a> Parser<'a> { 59 | pub fn new(lexer: &'a mut Lexer) -> Self { 60 | let current_token = lexer.next_token(); 61 | let peek_token = lexer.next_token(); 62 | 63 | Parser { 64 | lexer, 65 | current_token, 66 | peek_token, 67 | errors: vec![], 68 | } 69 | } 70 | 71 | /// Parses the entire input program and returns it as a `Node::ProgramTree`. 72 | pub fn parse(&mut self) -> Result> { 73 | let program = self.parse_program()?; 74 | Ok(Node::ProgramTree(program)) 75 | } 76 | 77 | /// Parses the program by repeatedly parsing statements until the end of file (EOF) token is encountered. 78 | /// 79 | /// It processes each statement and adds it to the program body. If any errors occur during parsing, 80 | /// they are accumulated and returned after the entire program has been parsed. 81 | pub fn parse_program(&mut self) -> Result> { 82 | let mut program = ProgramTree::new(); 83 | 84 | while self.current_token.kind != TokenKind::EOF { 85 | self.parse_and_add_statement(&mut program); 86 | self.next_token(); 87 | } 88 | 89 | self.finalize_program_parse(program) 90 | } 91 | 92 | /// Parses a statement and adds it to the program, accumulating errors if any. 93 | pub fn parse_and_add_statement(&mut self, program: &mut ProgramTree) { 94 | match self.parse_statement() { 95 | Ok(statement) => program.body.push(statement), 96 | Err(error) => self.errors.push(error), 97 | } 98 | } 99 | 100 | pub fn display_parser_errors(&mut self, errors: Vec>) { 101 | if errors.len() > 0 { 102 | errors[0].print(); 103 | std::process::exit(1); 104 | } 105 | } 106 | 107 | /// Finalizes the program parse by checking for errors. 108 | pub fn finalize_program_parse(&self, program: ProgramTree) -> Result> { 109 | if self.errors.is_empty() { 110 | Ok(program) 111 | } else { 112 | Err(self.errors.clone()) 113 | } 114 | } 115 | 116 | /// Returns the current location (line and column) of the lexer. 117 | pub fn current_location(&mut self) -> Location { 118 | Location { 119 | line: self.lexer.line, 120 | column: self.lexer.column, 121 | } 122 | } 123 | 124 | pub fn next_token(&mut self) -> Token { 125 | self.current_token = self.peek_token.clone(); 126 | self.peek_token = self.lexer.next_token(); 127 | self.peek_token.clone() 128 | } 129 | 130 | pub fn current_token_is(&self, token_kind: TokenKind) -> bool { 131 | self.current_token.kind == token_kind 132 | } 133 | 134 | pub fn peek_token_is(&self, token_kind: TokenKind) -> bool { 135 | self.peek_token.kind == token_kind 136 | } 137 | 138 | /// This function peeks at the next token without advancing the lexer. If the token matches 139 | /// the expected kind, it consumes the token and returns `Ok`. Otherwise, it returns an error 140 | /// with a message indicating the mismatch. 141 | pub fn expect_peek(&mut self, token_kind: TokenKind) -> Result<(), ParseError> { 142 | if self.peek_token_is(token_kind.clone()) { 143 | self.next_token(); // consume current token 144 | return Ok(()); 145 | } 146 | 147 | Err(CompileTimeError { 148 | location: self.current_location(), 149 | etype: ParserErrorType::ExpectedToken(token_kind), 150 | file_name: Some(self.lexer.file_name.clone()), 151 | source_content: Box::new(self.lexer.input.clone()), 152 | verbose: None, 153 | caret: Some(self.current_token.span.clone()), 154 | }) 155 | } 156 | 157 | /// This function checks the current token and consumes it if it matches the expected kind. 158 | /// If the current token does not match, it returns an error indicating the mismatch. 159 | pub fn expect_current(&mut self, token_kind: TokenKind) -> Result<(), ParseError> { 160 | if self.current_token_is(token_kind.clone()) { 161 | self.next_token(); // consume current token 162 | return Ok(()); 163 | } 164 | 165 | Err(CompileTimeError { 166 | location: self.current_location(), 167 | etype: ParserErrorType::UnexpectedToken(self.current_token.kind.clone(), token_kind), 168 | file_name: Some(self.lexer.file_name.clone()), 169 | source_content: Box::new(self.lexer.input.clone()), 170 | verbose: None, 171 | caret: Some(self.current_token.span.clone()), 172 | }) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /crates/parser/src/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parser; 2 | -------------------------------------------------------------------------------- /crates/parser/src/prec.rs: -------------------------------------------------------------------------------- 1 | use ast::token::TokenKind; 2 | use core::fmt; 3 | 4 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 5 | pub enum Precedence { 6 | Lowest, 7 | Equals, // == 8 | LessGreater, // > or < 9 | Sum, // + or = 10 | Product, // * or / 11 | Prefix, // -X or !X 12 | Call, // my_function(x) 13 | Index, // array[index] 14 | Cast, // Type conversion 15 | } 16 | 17 | pub fn token_precedence_of(token_kind: TokenKind) -> Precedence { 18 | match token_kind { 19 | TokenKind::Equal => Precedence::Equals, 20 | TokenKind::NotEqual => Precedence::Equals, 21 | TokenKind::And => Precedence::Equals, 22 | TokenKind::Or => Precedence::Equals, 23 | TokenKind::LessThan => Precedence::LessGreater, 24 | TokenKind::LessEqual => Precedence::LessGreater, 25 | TokenKind::GreaterThan => Precedence::LessGreater, 26 | TokenKind::GreaterEqual => Precedence::LessGreater, 27 | TokenKind::Plus => Precedence::Sum, 28 | TokenKind::Minus => Precedence::Sum, 29 | TokenKind::Asterisk => Precedence::Product, 30 | TokenKind::Slash => Precedence::Product, 31 | TokenKind::Percent => Precedence::Product, 32 | TokenKind::LeftParen => Precedence::Call, 33 | TokenKind::LeftBracket => Precedence::Index, 34 | _ => Precedence::Lowest, 35 | } 36 | } 37 | 38 | impl fmt::Display for Precedence { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | match self { 41 | Precedence::Lowest => write!(f, "lowest"), 42 | Precedence::Equals => write!(f, "equals"), 43 | Precedence::LessGreater => write!(f, "less_greater"), 44 | Precedence::Sum => write!(f, "sum"), 45 | Precedence::Product => write!(f, "product"), 46 | Precedence::Prefix => write!(f, "prefix"), 47 | Precedence::Call => write!(f, "call"), 48 | Precedence::Index => write!(f, "index"), 49 | Precedence::Cast => write!(f, "cast"), 50 | } 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::Precedence; 57 | 58 | #[test] 59 | fn test_compare_precedences() { 60 | assert!(Precedence::Lowest < Precedence::LessGreater); 61 | assert!(Precedence::Call > Precedence::Sum); 62 | assert!(Precedence::Call < Precedence::Cast); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utils" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | ast = {path = "../ast", version = "*"} 8 | colorized = "1.0.0" 9 | console = "0.15.11" 10 | rand = "0.8" 11 | -------------------------------------------------------------------------------- /crates/utils/src/fs.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, File}, 3 | io::Read, 4 | path::{Path, PathBuf}, 5 | process::exit, 6 | }; 7 | 8 | use crate::tui::tui_error; 9 | 10 | // Reads the file and returns the file content and the name of the file. 11 | pub fn read_file(file_path: String) -> (String, String) { 12 | let path = Path::new(file_path.as_str()); 13 | 14 | let mut file = match File::open(path) { 15 | Ok(content) => content, 16 | Err(_) => { 17 | tui_error("No such file or directory.".to_string()); 18 | exit(1); 19 | } 20 | }; 21 | 22 | let mut contents = String::new(); 23 | 24 | match file.read_to_string(&mut contents) { 25 | Err(err) => { 26 | tui_error(format!("Failed to read the file content: {}", err.to_string())); 27 | exit(1); 28 | } 29 | _ => {} 30 | } 31 | 32 | let file_name = path.file_name().unwrap().to_str().unwrap(); 33 | 34 | (contents, file_name.to_string()) 35 | } 36 | 37 | pub fn ensure_output_dir(output_dir: &Path) { 38 | if !output_dir.exists() { 39 | fs::create_dir_all(output_dir).unwrap_or_else(|_| { 40 | tui_error(format!("Failed to create output directory: {}", output_dir.display())); 41 | exit(1); 42 | }); 43 | } else if !output_dir.is_dir() { 44 | tui_error(format!("Output path must be a directory: {}", output_dir.display())); 45 | exit(1); 46 | } 47 | } 48 | 49 | /// Converts an absolute path to a relative path based on the given base directory. 50 | /// Returns `None` if the path is not a child of the base directory. 51 | pub fn absolute_to_relative(absolute_path: String, base_dir: String) -> Option { 52 | let abs_path = Path::new(&absolute_path).canonicalize().ok()?; 53 | let base_path = Path::new(&base_dir).canonicalize().ok()?; 54 | 55 | let relative_path = abs_path.strip_prefix(base_path).ok()?; 56 | 57 | Some(relative_path.to_string_lossy().replace('\\', "/")) 58 | } 59 | 60 | /// Tries to find `file_name` in any of the given `sources` directories. 61 | /// Returns the full path if found, otherwise returns `None`. 62 | pub fn find_file_from_sources(file_name: String, sources: Vec) -> Option { 63 | for source in sources.iter() { 64 | let path = Path::new(source).join(&file_name); 65 | if path.exists() && (path.is_file() || path.is_dir()) { 66 | return Some(path); 67 | } 68 | } 69 | None 70 | } 71 | 72 | pub fn get_directory_of_file(file_path: String) -> Option { 73 | let path = Path::new(file_path.as_str()); 74 | path.parent() 75 | .map(|parent| parent.to_str().unwrap_or_default().to_string()) 76 | } 77 | 78 | pub fn file_stem(file_name: &str) -> Option<&str> { 79 | Path::new(file_name) 80 | .file_stem() // gets "main" from "main.cyr" 81 | .and_then(|s| s.to_str()) // convert OsStr to &str 82 | } 83 | 84 | pub fn dylib_extension() -> &'static str { 85 | if cfg!(target_os = "windows") { 86 | "dll" 87 | } else if cfg!(target_os = "macos") { 88 | "dylib" 89 | } else { 90 | "so" 91 | } 92 | } 93 | 94 | pub fn executable_extension() -> &'static str { 95 | if cfg!(target_os = "windows") { ".exe" } else { "" } 96 | } 97 | -------------------------------------------------------------------------------- /crates/utils/src/generate_random_hex.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, distributions::Alphanumeric}; 2 | 3 | pub fn generate_random_hex() -> String { 4 | let rng = rand::thread_rng(); 5 | let rand_string: String = rng.sample_iter(&Alphanumeric).take(10).map(char::from).collect(); 6 | rand_string 7 | } 8 | -------------------------------------------------------------------------------- /crates/utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod fs; 2 | pub mod generate_random_hex; 3 | pub mod purify_string; 4 | pub mod tui; 5 | -------------------------------------------------------------------------------- /crates/utils/src/purify_string.rs: -------------------------------------------------------------------------------- 1 | pub fn unescape_string(str: String) -> String { 2 | str.replace("\\n", "\n") 3 | .replace("\\t", "\t") 4 | .replace("\\r", "\r") 5 | .replace("\\b", r"\b") 6 | .replace("\\a", r"\a") 7 | .replace("\\v", r"\v") 8 | .replace("\\f", r"\f") 9 | .replace("\\'", r"\'") 10 | .replace("\\\"", "\"") 11 | .replace("\\'", "'") 12 | .replace("\\\\", "\\") 13 | } 14 | 15 | pub fn escape_string(input: &str) -> String { 16 | let mut result = String::new(); 17 | for c in input.chars() { 18 | match c { 19 | '\n' => result.push_str("\\n"), 20 | '\t' => result.push_str("\\t"), 21 | '\r' => result.push_str("\\r"), 22 | '\x08' => result.push_str("\\b"), // backspace 23 | '\x07' => result.push_str("\\a"), // bell 24 | '\x0B' => result.push_str("\\v"), // vertical tab 25 | '\x0C' => result.push_str("\\f"), // form feed 26 | '\\' => result.push_str("\\\\"), 27 | '\"' => result.push_str("\\\""), 28 | '\'' => result.push_str("\\'"), 29 | _ => result.push(c), 30 | } 31 | } 32 | result 33 | } 34 | 35 | pub fn saturating_sub(value: usize, input: usize) -> usize { 36 | if input >= value { 37 | return 0; 38 | } else { 39 | return value.saturating_sub(input); 40 | } 41 | } 42 | 43 | pub fn spaces(n: usize) -> String { 44 | " ".repeat(n) 45 | } 46 | -------------------------------------------------------------------------------- /crates/utils/src/tui.rs: -------------------------------------------------------------------------------- 1 | use colorized::{Color, Colors}; 2 | use console::user_attended; 3 | 4 | pub fn tui_compiled(file_name: String) { 5 | if user_attended() { 6 | println!(" \x1b[1m{}\x1b[0m {}", "Compiled".color(Colors::GreenFg), file_name); 7 | } else { 8 | println!(" {} {}", "Compiled", file_name); 9 | } 10 | } 11 | 12 | pub fn tui_compile_finished() { 13 | if user_attended() { 14 | println!(" \x1b[1m{}\x1b[0m", "Finished".color(Colors::GreenFg)); 15 | } else { 16 | println!(" {}", "Finished"); 17 | } 18 | } 19 | 20 | pub fn tui_error(msg: String) { 21 | if user_attended() { 22 | eprintln!("{}: {}", "error".color(Colors::RedFg), msg) 23 | } else { 24 | eprintln!("{}: {}", "error", msg) 25 | } 26 | } 27 | 28 | pub fn tui_warning(msg: String) { 29 | if user_attended() { 30 | eprintln!("{}: {}", "warning".color(Colors::YellowFg), msg); 31 | } else { 32 | eprintln!("{}: {}", "warning", msg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/main.cyr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyrus-lang/Cyrus/71bd5ca0f9463c70ba225be28036d720db6c27f0/examples/main.cyr -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1739866667, 6 | "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "nixpkgs_2": { 20 | "locked": { 21 | "lastModified": 1736320768, 22 | "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", 23 | "owner": "NixOS", 24 | "repo": "nixpkgs", 25 | "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "NixOS", 30 | "ref": "nixpkgs-unstable", 31 | "repo": "nixpkgs", 32 | "type": "github" 33 | } 34 | }, 35 | "root": { 36 | "inputs": { 37 | "nixpkgs": "nixpkgs", 38 | "rust-overlay": "rust-overlay" 39 | } 40 | }, 41 | "rust-overlay": { 42 | "inputs": { 43 | "nixpkgs": "nixpkgs_2" 44 | }, 45 | "locked": { 46 | "lastModified": 1739845646, 47 | "narHash": "sha256-UGQVBU/yDn6u0kAE4z1PYrOaaf3wl+gAAv5rui2TkFQ=", 48 | "owner": "oxalica", 49 | "repo": "rust-overlay", 50 | "rev": "ab2cd2b8b25ab3f65b8ce4aa701a6d69fbb0210f", 51 | "type": "github" 52 | }, 53 | "original": { 54 | "owner": "oxalica", 55 | "repo": "rust-overlay", 56 | "type": "github" 57 | } 58 | } 59 | }, 60 | "root": "root", 61 | "version": 7 62 | } 63 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Cyrus language flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; 6 | rust-overlay.url = "github:oxalica/rust-overlay"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, rust-overlay }: 10 | let 11 | system = "x86_64-linux"; 12 | overlays = [ (import rust-overlay) ]; 13 | pkgs = import nixpkgs { 14 | inherit system overlays; 15 | }; 16 | rustToolchain = pkgs.rust-bin.nightly.latest.default.override { 17 | extensions = [ "rust-src" "rust-analyzer" "cargo" "clippy" ]; 18 | }; 19 | in 20 | { 21 | packages.${system}.default = pkgs.rustPlatform.buildRustPackage { 22 | pname = "cyrus"; 23 | version = "latest"; 24 | src = ./.; 25 | cargoLock = { 26 | lockFile = ./Cargo.lock; 27 | }; 28 | 29 | nativeBuildInputs = with pkgs; [ 30 | rustToolchain 31 | gcc 32 | libgcc 33 | glibc 34 | gcc_multi 35 | clang-tools 36 | clang 37 | libffi 38 | libffi.dev 39 | isl 40 | llvm_18.lib 41 | llvm_18.dev 42 | libxml2 43 | ]; 44 | 45 | buildPhase = '' 46 | export LIBRARY_PATH="${pkgs.glibc}/lib:${pkgs.gcc_multi}/lib:${pkgs.llvm_18.lib}/lib:${pkgs.libxml2}/lib:$LIBRARY_PATH" 47 | export LLVM_SYS_180_PREFIX="${pkgs.llvm_18.dev}" 48 | cargo build --release 49 | ''; 50 | 51 | installPhase = '' 52 | mkdir -p $out/bin 53 | cp target/release/cyrus $out/bin/ 54 | ''; 55 | 56 | meta = { 57 | license = pkgs.lib.licenses.mit; 58 | description = "Cyrus Programming Language"; 59 | homepage = "https://github.com/cyrus-lang/Cyrus-Lang"; 60 | }; 61 | }; 62 | 63 | devShells.${system}.default = pkgs.mkShell { 64 | name = "cyrus-dev-shell"; 65 | 66 | buildInputs = with pkgs; [ 67 | rustToolchain 68 | gcc 69 | libgcc 70 | glibc 71 | gcc_multi 72 | clang-tools 73 | clang 74 | libffi 75 | libffi.dev 76 | isl 77 | llvm_18.lib 78 | llvm_18.dev 79 | libxml2 80 | ]; 81 | 82 | shellHook = '' 83 | export LIBRARY_PATH="${pkgs.glibc}/lib:${pkgs.gcc_multi}/lib:${pkgs.llvm_18.lib}/lib:${pkgs.libxml2}/lib:$LIBRARY_PATH" 84 | export LLVM_SYS_180_PREFIX="${pkgs.llvm_18.dev}" 85 | alias cyrus="cargo run --" 86 | ''; 87 | }; 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 -------------------------------------------------------------------------------- /scripts/install.ps1: -------------------------------------------------------------------------------- 1 | $INSTALL_DIR = "C:\Windows\System32" 2 | $EXECUTABLE_NAME = "cyrus.exe" 3 | $SOURCE_PATH = ".\cyrus.exe" 4 | 5 | $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent() 6 | $currentPrincipal = New-Object System.Security.Principal.WindowsPrincipal($currentUser) 7 | $adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator 8 | 9 | if (-not $currentPrincipal.IsInRole($adminRole)) { 10 | Write-Host "Please run this script as Administrator." 11 | exit 1 12 | } 13 | 14 | if (-not (Test-Path $SOURCE_PATH)) { 15 | Write-Host "Error: '$SOURCE_PATH' not found. Ensure the Cyrus binary exists in this directory." 16 | exit 1 17 | } 18 | 19 | Copy-Item -Path $SOURCE_PATH -Destination "$INSTALL_DIR\$EXECUTABLE_NAME" -Force 20 | 21 | if (Test-Path "$INSTALL_DIR\$EXECUTABLE_NAME") { 22 | Write-Host "Cyrus has been installed successfully!" 23 | Write-Host "" 24 | Write-Host "You can now run 'cyrus' from anywhere." 25 | Write-Host "" 26 | Write-Host " Quick Usage:" 27 | Write-Host " - Check version: cyrus version" 28 | Write-Host " - Show help: cyrus help" 29 | Write-Host "" 30 | Write-Host "Happy coding with Cyrus! 👾" 31 | } else { 32 | Write-Host "Installation failed." 33 | exit 1 34 | } 35 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | INSTALL_DIR="/usr/bin" 4 | EXECUTABLE_NAME="cyrus" 5 | SOURCE_PATH="./cyrus" 6 | 7 | if [[ $EUID -ne 0 ]]; then 8 | echo "Please run this script as root or use sudo." 9 | exit 1 10 | fi 11 | 12 | if [[ ! -f "$SOURCE_PATH" ]]; then 13 | echo "Error: '$SOURCE_PATH' not found. Make sure the Cyrus binary is exists in this directory." 14 | exit 1 15 | fi 16 | 17 | cp "$SOURCE_PATH" "$INSTALL_DIR/$EXECUTABLE_NAME" 18 | 19 | chmod +x "$INSTALL_DIR/$EXECUTABLE_NAME" 20 | 21 | if command -v "$EXECUTABLE_NAME" &>/dev/null; then 22 | echo "Cyrus has been installed successfully!" 23 | echo "" 24 | echo "You can now run 'cyrus' from anywhere." 25 | echo "" 26 | echo " Quick Usage:" 27 | echo " - Check version: cyrus version" 28 | echo " - Show help: cyrus help" 29 | echo "" 30 | echo "Happy coding with Cyrus Lang! 👾" 31 | else 32 | echo "Installation failed." 33 | exit 1 34 | fi 35 | -------------------------------------------------------------------------------- /stdlib/io.cyr: -------------------------------------------------------------------------------- 1 | extern fn printf(fmt: string, ...string): void; -------------------------------------------------------------------------------- /stdlib/math.cyr: -------------------------------------------------------------------------------- 1 | extern fn acos(x: double): double; 2 | extern fn asin(x: double): double; 3 | extern fn atan(x: double): double; 4 | extern fn atan2(y: double, x: double): double; 5 | extern fn cos(x: double): double; 6 | extern fn sin(x: double): double; 7 | extern fn tan(x: double): double; 8 | extern fn cosh(x: double): double; 9 | extern fn sinh(x: double): double; 10 | extern fn tanh(x: double): double; 11 | extern fn acosh(x: double): double; 12 | extern fn asinh(x: double): double; 13 | extern fn atanh(x: double): double; 14 | extern fn exp(x: double): double; 15 | extern fn exp2(x: double): double; 16 | extern fn expm1(x: double): double; 17 | extern fn log(x: double): double; 18 | extern fn log10(x: double): double; 19 | extern fn log1p(x: double): double; 20 | extern fn log2(x: double): double; 21 | extern fn logb(x: double): double; 22 | extern fn pow(x: double, y: double): double; 23 | extern fn sqrt(x: double): double; 24 | extern fn cbrt(x: double): double; 25 | extern fn hypot(x: double, y: double): double; 26 | extern fn erf(x: double): double; 27 | extern fn erfc(x: double): double; 28 | extern fn tgamma(x: double): double; 29 | extern fn lgamma(x: double): double; 30 | extern fn ceil(x: double): double; 31 | extern fn floor(x: double): double; 32 | extern fn round(x: double): double; 33 | extern fn lround(x: double): i64; 34 | extern fn llround(x: double): i64; 35 | extern fn trunc(x: double): double; 36 | extern fn nearbyint(x: double): double; 37 | extern fn rint(x: double): double; 38 | extern fn lrint(x: double): i64; 39 | extern fn llrint(x: double): i64; 40 | extern fn fabs(x: double): double; // alias of abs 41 | extern fn copysign(x: double, y: double): double; 42 | extern fn nan(tagp: *char): double; 43 | extern fn nextafter(x: double, y: double): double; 44 | extern fn nexttoward(x: double, y: double): double; 45 | extern fn fdim(x: double, y: double): double; 46 | extern fn fmax(x: double, y: double): double; 47 | extern fn fmin(x: double, y: double): double; 48 | extern fn fma(x: double, y: double, z: double): double; 49 | extern fn isfinite(x: double): i32; 50 | extern fn isinf(x: double): i32; 51 | extern fn isnan(x: double): i32; 52 | extern fn isnormal(x: double): i32; 53 | extern fn signbit(x: double): i32; 54 | extern fn ilogb(x: double): i32; 55 | extern fn scalbn(x: double, n: i32): double; 56 | extern fn scalbln(x: double, n: i64): double; 57 | extern fn modf(x: double, iptr: double): double; 58 | extern fn frexp(x: double, eptr: i32): double; 59 | extern fn ldexp(x: double, exp: i32): double; -------------------------------------------------------------------------------- /stdlib/mem.cyr: -------------------------------------------------------------------------------- 1 | extern fn malloc(size: u64): *void; 2 | extern fn free(ptr: *void); --------------------------------------------------------------------------------