├── .github ├── FUNDING.yml └── workflows │ └── typos.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── _typos.toml ├── crates ├── rush-analyzer │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── analyzer.rs │ │ ├── ast.rs │ │ ├── diagnostic.rs │ │ ├── lib.rs │ │ ├── macros.rs │ │ └── main.rs ├── rush-cli │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ └── src │ │ ├── c.rs │ │ ├── cli.rs │ │ ├── llvm.rs │ │ ├── main.rs │ │ ├── riscv.rs │ │ ├── vm.rs │ │ ├── wasm.rs │ │ └── x86.rs ├── rush-compiler-llvm │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── llvm_ir_test │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── main.c │ │ ├── main.cpp │ │ └── main2.ll │ └── src │ │ ├── compiler.rs │ │ ├── corelib.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ └── main.rs ├── rush-compiler-risc-v │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── corelib │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── main.c │ │ └── src │ │ │ ├── cast.s │ │ │ ├── exit.s │ │ │ └── pow.s │ ├── notes.md │ ├── samples │ │ ├── fib.rush │ │ ├── infix_logic.rush │ │ ├── main_recursion.rush │ │ ├── sqrt.rush │ │ └── test.rush │ └── src │ │ ├── call.rs │ │ ├── compiler.rs │ │ ├── corelib.rs │ │ ├── instruction.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── register.rs │ │ └── utils.rs ├── rush-compiler-wasm │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── compiler.rs │ │ ├── corelib.rs │ │ ├── instructions.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── types.rs │ │ └── utils.rs ├── rush-compiler-x86-64 │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── Makefile │ ├── corelib │ │ ├── .gitignore │ │ ├── Makefile │ │ └── src │ │ │ └── corelib.s │ └── src │ │ ├── call.rs │ │ ├── compiler.rs │ │ ├── condition.rs │ │ ├── infix.rs │ │ ├── instruction.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── register.rs │ │ └── value.rs ├── rush-interpreter-tree │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── interpreter.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── ops.rs │ │ └── value.rs ├── rush-interpreter-vm │ ├── .gitignore │ ├── Cargo.toml │ ├── Makefile │ ├── README.md │ └── src │ │ ├── compiler.rs │ │ ├── instruction.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── value.rs │ │ └── vm.rs ├── rush-ls │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── rush-parser │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── ast.rs │ │ ├── error.rs │ │ ├── lexer.rs │ │ ├── lib.rs │ │ ├── macros.rs │ │ ├── parser.rs │ │ ├── span.rs │ │ └── token.rs └── rush-transpiler-c │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── c_ast.rs │ ├── char.rush │ ├── lib.rs │ ├── main.rs │ ├── pow.rush │ └── transpiler.rs ├── dprint.json ├── grammar.ebnf ├── logo ├── old │ ├── rush.png │ ├── rush.xcf │ └── rush_white.png ├── rush_logo.pdf ├── rush_logo.png ├── rush_logo.svg ├── rush_logo_bg.png ├── rush_logo_bg.svg ├── rush_logo_outline.png ├── rush_logo_outline.svg ├── rush_logo_small.png ├── rush_logo_small.svg ├── rush_logo_small_bg.png ├── rush_logo_small_bg.svg ├── rush_logo_small_outline.png └── rush_logo_small_outline.svg ├── samples ├── main.rush └── tests │ ├── basic │ ├── approx_apery.rush │ ├── approx_e.rush │ ├── approx_pi.rush │ ├── blocks.rush │ ├── char.rush │ ├── complete.rush │ ├── exit_0.rush │ ├── fib.rush │ ├── float_casts.rush │ ├── globals.rush │ ├── if_else.rush │ ├── loops.rush │ ├── nan.rush │ ├── nested_calls.rush │ ├── playground_basic.rush │ ├── pow.rush │ ├── wasm_test.rush │ ├── wrapping.rush │ └── x64_test.rush │ ├── exits │ ├── final_fn_expr.rush │ ├── for_1.rush │ ├── for_2.rush │ ├── for_3.rush │ ├── if_else.rush │ ├── in_call.rush │ ├── infix.rush │ ├── let.rush │ ├── logical_and.rush │ ├── logical_or.rush │ ├── nested_exit.rush │ └── while.rush │ ├── main.py │ └── pointers │ ├── as_return_type.rush │ ├── assignment_edge_cases.rush │ ├── assignments.rush │ ├── basic.rush │ ├── depth.rush │ ├── for_loop.rush │ ├── globals.rush │ ├── in_params.rush │ ├── shadow_ref.rush │ ├── swap.rush │ └── types.rush └── specification.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: rubixdev 2 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | name: Typo Check 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | run: 9 | name: Spell Check with Typos 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Actions Repository 13 | uses: actions/checkout@v2 14 | 15 | - name: Check spelling of file.txt 16 | uses: crate-ci/typos@master 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.4.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-toml 11 | - id: check-added-large-files 12 | - repo: https://github.com/doublify/pre-commit-rust 13 | rev: v1.0 14 | hooks: 15 | - id: clippy 16 | - repo: https://github.com/adamchainz/pre-commit-dprint 17 | rev: v0.35.3 18 | hooks: 19 | - id: dprint 20 | - repo: https://github.com/crate-ci/typos 21 | rev: v1.14.3 22 | hooks: 23 | - id: typos 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/rush-ls", 4 | "crates/rush-cli", 5 | "crates/rush-parser", 6 | "crates/rush-analyzer", 7 | "crates/rush-interpreter-tree", 8 | "crates/rush-interpreter-vm", 9 | "crates/rush-compiler-wasm", 10 | "crates/rush-compiler-llvm", 11 | "crates/rush-compiler-risc-v", 12 | "crates/rush-compiler-x86-64", 13 | "crates/rush-transpiler-c", 14 | ] 15 | package.authors = ["RubixDev", "MikMuellerDev"] 16 | package.edition = "2021" 17 | package.license = "GPL-3.0-only" 18 | package.repository = "https://github.com/rush-rs/rush.git" 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The rush Programming Language 2 | 3 | A simple programming language for researching different ways of program 4 | execution and compilation. 5 | 6 | > **Note:** Since this project is part of a research project, the language 7 | > cannot be considered _production-ready_. 8 | 9 | ## Projects Which Are Part of rush 10 | 11 | ### Program Analysis 12 | 13 | - [Lexer & Parser](./crates/rush-parser/) 14 | - [Semantic Analyzer](./crates/rush-analyzer/) 15 | 16 | ### Interpreters 17 | 18 | - [Tree-walking Interpreter](./crates/rush-interpreter-tree/) 19 | - [Virtual Machine Interpreter](./crates/rush-interpreter-vm/) 20 | 21 | ### Compilers 22 | 23 | - [Wasm Compiler](./crates/rush-compiler-wasm/) 24 | - [LLVM Compiler](./crates/rush-compiler-llvm/) 25 | - [RISC-V Compiler](./crates/rush-compiler-risc-v/) 26 | - [x86_64 Compiler](./crates/rush-compiler-x86-64/) 27 | 28 | ### Transpilers 29 | 30 | - [ANSI C Transpiler](./crates/rush-transpiler-c/) 31 | 32 | ### Toolchain 33 | 34 | - [Language Server](./crates/rush-ls/) 35 | - [rush CLI](./crates/rush-cli/) 36 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-identifiers] 2 | OLT = "OLT" 3 | fle = "fle" 4 | -------------------------------------------------------------------------------- /crates/rush-analyzer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-analyzer" 3 | version = "0.1.2" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "A semantic analyzer and type-annotator for the rush programming language" 9 | 10 | [dependencies] 11 | rush-parser = { path = "../rush-parser", version = "0.1.2" } 12 | -------------------------------------------------------------------------------- /crates/rush-analyzer/README.md: -------------------------------------------------------------------------------- 1 | # Semantic Analyzer 2 | 3 | A crate which performs semantic analysis and tree-annotation on rush programs. 4 | 5 | Almost every rush compiler backend requires the semantic correctness of its 6 | input tree. Furthermore, the backends often require precise type information at 7 | compile time in order to compile the correct output. 8 | 9 | Therefore, the _analyzer_ traverses an 10 | [AST](https://en.wikipedia.org//wiki/Abstract_syntax_tree) generated by the 11 | [`rush-parser`](https://github.com/rush-rs/rush/tree/main/crates/rush-parser) 12 | and adds type information whilst validating its semantic correctness. 13 | 14 | This way, many compiler backends can be significantly simplified due to the lack 15 | of unnecessary checks during program compilation. 16 | -------------------------------------------------------------------------------- /crates/rush-analyzer/src/ast.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use rush_parser::ast::{AssignOp, InfixOp, PrefixOp, Type}; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub struct AnalyzedProgram<'src> { 7 | pub globals: Vec>, 8 | pub functions: Vec>, 9 | pub main_fn: AnalyzedBlock<'src>, 10 | pub used_builtins: HashSet<&'src str>, 11 | } 12 | 13 | #[derive(Debug, Clone, PartialEq)] 14 | pub struct AnalyzedFunctionDefinition<'src> { 15 | pub used: bool, 16 | pub name: &'src str, 17 | pub params: Vec>, 18 | pub return_type: Type, 19 | pub block: AnalyzedBlock<'src>, 20 | } 21 | 22 | #[derive(Debug, Clone, PartialEq, Eq)] 23 | pub struct AnalyzedParameter<'src> { 24 | pub mutable: bool, 25 | pub name: &'src str, 26 | pub type_: Type, 27 | } 28 | 29 | #[derive(Debug, Clone, PartialEq)] 30 | pub struct AnalyzedBlock<'src> { 31 | pub result_type: Type, 32 | pub stmts: Vec>, 33 | pub expr: Option>, 34 | } 35 | 36 | #[derive(Debug, Clone, PartialEq)] 37 | pub enum AnalyzedStatement<'src> { 38 | Let(AnalyzedLetStmt<'src>), 39 | Return(AnalyzedReturnStmt<'src>), 40 | Loop(AnalyzedLoopStmt<'src>), 41 | While(AnalyzedWhileStmt<'src>), 42 | For(AnalyzedForStmt<'src>), 43 | Break, 44 | Continue, 45 | Expr(AnalyzedExpression<'src>), 46 | } 47 | 48 | impl AnalyzedStatement<'_> { 49 | pub fn result_type(&self) -> Type { 50 | match self { 51 | Self::Let(_) => Type::Unit, 52 | Self::Return(_) => Type::Never, 53 | Self::Loop(node) => match node.never_terminates { 54 | true => Type::Never, 55 | false => Type::Unit, 56 | }, // Used for detecting never-ending loops 57 | Self::While(node) => match node.never_terminates { 58 | true => Type::Never, 59 | false => Type::Unit, 60 | }, // Used for detecting never-ending loops 61 | Self::For(node) => match node.never_terminates { 62 | true => Type::Never, 63 | false => Type::Unit, 64 | }, // Used for detecting never-ending loops 65 | Self::Break => Type::Never, 66 | Self::Continue => Type::Never, 67 | Self::Expr(expr) => expr.result_type(), 68 | } 69 | } 70 | 71 | pub fn constant(&self) -> bool { 72 | match self { 73 | Self::Expr(expr) => expr.constant(), 74 | _ => false, 75 | } 76 | } 77 | } 78 | 79 | #[derive(Debug, Clone, PartialEq)] 80 | pub struct AnalyzedLetStmt<'src> { 81 | pub name: &'src str, 82 | pub expr: AnalyzedExpression<'src>, 83 | pub mutable: bool, 84 | pub used: bool, 85 | } 86 | 87 | pub type AnalyzedReturnStmt<'src> = Option>; 88 | 89 | #[derive(Debug, Clone, PartialEq)] 90 | pub struct AnalyzedLoopStmt<'src> { 91 | pub block: AnalyzedBlock<'src>, 92 | pub never_terminates: bool, 93 | } 94 | 95 | #[derive(Debug, Clone, PartialEq)] 96 | pub struct AnalyzedWhileStmt<'src> { 97 | pub cond: AnalyzedExpression<'src>, 98 | pub block: AnalyzedBlock<'src>, 99 | pub never_terminates: bool, 100 | } 101 | 102 | #[derive(Debug, Clone, PartialEq)] 103 | pub struct AnalyzedForStmt<'src> { 104 | pub ident: &'src str, 105 | pub initializer: AnalyzedExpression<'src>, 106 | pub cond: AnalyzedExpression<'src>, 107 | pub update: AnalyzedExpression<'src>, 108 | pub block: AnalyzedBlock<'src>, 109 | pub never_terminates: bool, 110 | } 111 | 112 | #[derive(Debug, Clone, PartialEq)] 113 | pub enum AnalyzedExpression<'src> { 114 | Block(Box>), 115 | If(Box>), 116 | Int(i64), 117 | Float(f64), 118 | Bool(bool), 119 | Char(u8), 120 | Ident(AnalyzedIdentExpr<'src>), 121 | Prefix(Box>), 122 | Infix(Box>), 123 | Assign(Box>), 124 | Call(Box>), 125 | Cast(Box>), 126 | Grouped(Box>), 127 | } 128 | 129 | impl AnalyzedExpression<'_> { 130 | pub fn result_type(&self) -> Type { 131 | match self { 132 | Self::Int(_) => Type::Int(0), 133 | Self::Float(_) => Type::Float(0), 134 | Self::Bool(_) => Type::Bool(0), 135 | Self::Char(_) => Type::Char(0), 136 | Self::Ident(expr) => expr.result_type, 137 | Self::Prefix(expr) => expr.result_type, 138 | Self::Infix(expr) => expr.result_type, 139 | Self::Assign(expr) => expr.result_type, 140 | Self::Call(expr) => expr.result_type, 141 | Self::Cast(expr) => expr.result_type, 142 | Self::If(expr) => expr.result_type, 143 | Self::Block(expr) => expr.result_type, 144 | Self::Grouped(expr) => expr.result_type(), 145 | } 146 | } 147 | 148 | pub fn constant(&self) -> bool { 149 | matches!( 150 | self, 151 | Self::Int(_) | Self::Float(_) | Self::Bool(_) | Self::Char(_) 152 | ) 153 | } 154 | 155 | pub fn as_constant(&self) -> Option { 156 | match self { 157 | AnalyzedExpression::Int(_) 158 | | AnalyzedExpression::Float(_) 159 | | AnalyzedExpression::Bool(_) 160 | | AnalyzedExpression::Char(_) => { 161 | // this clone is cheap, as inner values of these variants all impl `Copy` 162 | Some(self.clone()) 163 | } 164 | _ => None, 165 | } 166 | } 167 | } 168 | 169 | #[derive(Debug, Clone, PartialEq)] 170 | pub struct AnalyzedIfExpr<'src> { 171 | pub result_type: Type, 172 | pub cond: AnalyzedExpression<'src>, 173 | pub then_block: AnalyzedBlock<'src>, 174 | pub else_block: Option>, 175 | } 176 | 177 | #[derive(Debug, Clone, PartialEq, Eq)] 178 | pub struct AnalyzedIdentExpr<'src> { 179 | pub result_type: Type, 180 | pub ident: &'src str, 181 | } 182 | 183 | #[derive(Debug, Clone, PartialEq)] 184 | pub struct AnalyzedPrefixExpr<'src> { 185 | pub result_type: Type, 186 | pub op: PrefixOp, 187 | pub expr: AnalyzedExpression<'src>, 188 | } 189 | 190 | #[derive(Debug, Clone, PartialEq)] 191 | pub struct AnalyzedInfixExpr<'src> { 192 | pub result_type: Type, 193 | pub lhs: AnalyzedExpression<'src>, 194 | pub op: InfixOp, 195 | pub rhs: AnalyzedExpression<'src>, 196 | } 197 | 198 | #[derive(Debug, Clone, PartialEq)] 199 | pub struct AnalyzedAssignExpr<'src> { 200 | pub result_type: Type, 201 | pub assignee: &'src str, 202 | pub assignee_ptr_count: usize, 203 | pub op: AssignOp, 204 | pub expr: AnalyzedExpression<'src>, 205 | } 206 | 207 | #[derive(Debug, Clone, PartialEq)] 208 | pub struct AnalyzedCallExpr<'src> { 209 | pub result_type: Type, 210 | pub func: &'src str, 211 | pub args: Vec>, 212 | } 213 | 214 | #[derive(Debug, Clone, PartialEq)] 215 | pub struct AnalyzedCastExpr<'src> { 216 | pub result_type: Type, 217 | pub expr: AnalyzedExpression<'src>, 218 | pub type_: Type, 219 | } 220 | -------------------------------------------------------------------------------- /crates/rush-analyzer/src/diagnostic.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | 6 | use rush_parser::{Error, Span}; 7 | 8 | #[derive(PartialEq, Eq, Debug, Clone)] 9 | pub struct Diagnostic<'src> { 10 | pub level: DiagnosticLevel, 11 | pub message: Cow<'static, str>, 12 | pub notes: Vec>, 13 | pub span: Span<'src>, 14 | pub source: &'src str, 15 | } 16 | 17 | impl<'src> From> for Diagnostic<'src> { 18 | fn from(err: Error<'src>) -> Self { 19 | Self::new( 20 | DiagnosticLevel::Error(ErrorKind::Syntax), 21 | err.message, 22 | vec![], 23 | err.span, 24 | err.source, 25 | ) 26 | } 27 | } 28 | 29 | impl<'src> From>> for Diagnostic<'src> { 30 | fn from(err: Box>) -> Self { 31 | Self::from(*err) 32 | } 33 | } 34 | 35 | impl<'src> Diagnostic<'src> { 36 | pub fn new( 37 | level: DiagnosticLevel, 38 | message: impl Into>, 39 | notes: Vec>, 40 | span: Span<'src>, 41 | source: &'src str, 42 | ) -> Self { 43 | Self { 44 | level, 45 | message: message.into(), 46 | notes, 47 | span, 48 | source, 49 | } 50 | } 51 | } 52 | 53 | impl Display for Diagnostic<'_> { 54 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 55 | let ansi_col = |col: u8, bold: bool| -> Cow<'static, str> { 56 | match (f.alternate(), bold) { 57 | (true, true) => format!("\x1b[1;{col}m").into(), 58 | (true, false) => format!("\x1b[{col}m").into(), 59 | (false, _) => "".into(), 60 | } 61 | }; 62 | let ansi_reset = match f.alternate() { 63 | true => "\x1b[0m", 64 | false => "", 65 | }; 66 | 67 | let lines: Vec<_> = self.source.split('\n').collect(); 68 | 69 | let (raw_marker, raw_marker_single, color) = match self.level { 70 | DiagnosticLevel::Hint => ("~", "^", 5), // magenta 71 | DiagnosticLevel::Info => ("~", "^", 4), // blue 72 | DiagnosticLevel::Warning => ("~", "^", 3), // yellow 73 | DiagnosticLevel::Error(_) => ("^", "^", 1), // red 74 | }; 75 | 76 | let notes: String = self 77 | .notes 78 | .iter() 79 | .map(|note| { 80 | format!( 81 | "\n {color}note:{ansi_reset} {note}", 82 | color = ansi_col(36, true), 83 | ) 84 | }) 85 | .collect(); 86 | 87 | // take special action if the source code is empty or there is no useful span 88 | if self.source.is_empty() || self.span.is_empty() { 89 | return writeln!( 90 | f, 91 | " {color}{lvl}{reset_col} in {path}{ansi_reset} \n {msg}{notes}", 92 | color = ansi_col(color + 30, true), 93 | lvl = self.level, 94 | reset_col = ansi_col(39, false), 95 | path = self.span.start.path, 96 | msg = self.message, 97 | ); 98 | } 99 | 100 | let line1 = match self.span.start.line > 1 { 101 | true => format!( 102 | "\n {}{: >3} | {ansi_reset}{}", 103 | ansi_col(90, false), 104 | self.span.start.line - 1, 105 | lines[self.span.start.line - 2], 106 | ), 107 | false => String::new(), 108 | }; 109 | 110 | let line2 = format!( 111 | " {}{: >3} | {ansi_reset}{}", 112 | ansi_col(90, false), 113 | self.span.start.line, 114 | lines[self.span.start.line - 1] 115 | ); 116 | 117 | let line3 = match self.span.start.line < lines.len() { 118 | true => format!( 119 | "\n {}{: >3} | {ansi_reset}{}", 120 | ansi_col(90, false), 121 | self.span.start.line + 1, 122 | lines[self.span.start.line] 123 | ), 124 | false => String::new(), 125 | }; 126 | 127 | let markers = match ( 128 | self.span.start.line == self.span.end.line, 129 | self.span.start.column + 1 == self.span.end.column, 130 | ) { 131 | // same line, wide column difference 132 | (true, false) => raw_marker.repeat(self.span.end.column - self.span.start.column), 133 | // same line, just one column difference 134 | (true, true) => raw_marker_single.to_string(), 135 | // multiline span 136 | (_, _) => { 137 | format!( 138 | "{marker} ...\n{space}{color}+ {line_count} more line{s}{ansi_reset}", 139 | marker = raw_marker 140 | .repeat(lines[self.span.start.line - 1].len() - self.span.start.column + 1), 141 | space = " ".repeat(self.span.start.column + 6), 142 | color = ansi_col(32, true), 143 | line_count = self.span.end.line - self.span.start.line, 144 | s = match self.span.end.line - self.span.start.line == 1 { 145 | true => "", 146 | false => "s", 147 | }, 148 | ) 149 | } 150 | }; 151 | 152 | let marker = format!( 153 | "{space}{color}{markers}{ansi_reset}", 154 | color = ansi_col(color + 30, true), 155 | space = " ".repeat(self.span.start.column + 6), 156 | ); 157 | 158 | writeln!( 159 | f, 160 | " {color}{lvl}{reset_col} at {path}:{line}:{col}{ansi_reset}\n{line1}\n{line2}\n{marker}{line3}\n\n {color}{msg}{ansi_reset}{notes}", 161 | color = ansi_col(color + 30, true), 162 | lvl = self.level, 163 | reset_col = ansi_col(39, false), 164 | path = self.span.start.path, 165 | line = self.span.start.line, 166 | col = self.span.start.column, 167 | msg = self.message, 168 | ) 169 | } 170 | } 171 | 172 | #[derive(PartialEq, Eq, Debug, Clone)] 173 | pub enum DiagnosticLevel { 174 | Hint, 175 | Info, 176 | Warning, 177 | Error(ErrorKind), 178 | } 179 | 180 | impl Display for DiagnosticLevel { 181 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 182 | match self { 183 | Self::Hint | Self::Info | Self::Warning => write!(f, "{self:?}"), 184 | Self::Error(kind) => write!(f, "{kind}"), 185 | } 186 | } 187 | } 188 | 189 | #[derive(PartialEq, Eq, Debug, Clone)] 190 | pub enum ErrorKind { 191 | Syntax, 192 | Type, 193 | Semantic, 194 | Reference, 195 | } 196 | 197 | impl Display for ErrorKind { 198 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 199 | write!(f, "{self:?}Error") 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /crates/rush-analyzer/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod macros; 3 | 4 | mod analyzer; 5 | pub mod ast; 6 | mod diagnostic; 7 | 8 | use std::iter; 9 | 10 | pub use analyzer::*; 11 | use ast::AnalyzedProgram; 12 | pub use diagnostic::*; 13 | 14 | pub use rush_parser::ast::{AssignOp, InfixOp, PrefixOp, Type}; 15 | use rush_parser::{Lexer, Parser}; 16 | 17 | /// Analyzes rush source code and returns an analyzed (annotated) AST. 18 | /// The `Ok(_)` variant also returns non-error diagnostics. 19 | /// However, the `Err(_)` variant returns a `Vec` which contains at least one error 20 | pub fn analyze<'src>( 21 | text: &'src str, 22 | path: &'src str, 23 | ) -> Result<(AnalyzedProgram<'src>, Vec>), Vec>> { 24 | let lexer = Lexer::new(text, path); 25 | 26 | let parser = Parser::new(lexer); 27 | let (ast, errs) = parser.parse(); 28 | 29 | match (ast, errs.len()) { 30 | (Err(critical), _) => Err(errs 31 | .into_iter() 32 | .map(Diagnostic::from) 33 | .chain(iter::once((*critical).into())) 34 | .collect()), 35 | (Ok(ast), _) => { 36 | let analyzer = Analyzer::new(text); 37 | 38 | // saves potential issues of the parser as diagnostics 39 | let mut parser_diagnostics: Vec = 40 | errs.into_iter().map(Diagnostic::from).collect(); 41 | 42 | let (analyzed_ast, mut analyzer_diagnostics) = match analyzer.analyze(ast) { 43 | Ok(res) => res, 44 | Err(mut analyzer_diagnostics) => { 45 | parser_diagnostics.append(&mut analyzer_diagnostics); 46 | return Err(parser_diagnostics); 47 | } 48 | }; 49 | 50 | // append the analyzer diagnostics to the parser errors 51 | parser_diagnostics.append(&mut analyzer_diagnostics); 52 | 53 | // return the `Err(_)` variant if the diagnostics contain at least 1 error 54 | match parser_diagnostics 55 | .iter() 56 | .any(|diagnostic| matches!(diagnostic.level, DiagnosticLevel::Error(_))) 57 | { 58 | true => Err(parser_diagnostics), 59 | false => Ok((analyzed_ast, parser_diagnostics)), 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/rush-analyzer/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! analyzed_tree { 3 | ((None)) => { None }; 4 | ((Some($($node:tt)*))) => { Some(analyzed_tree!(($($node)*))) }; 5 | 6 | (( 7 | Program, 8 | globals: [$($global:tt),* $(,)?], 9 | functions: [$($func:tt),* $(,)?], 10 | main_fn: $main_fn:tt, 11 | used_builtins: [$($name:expr),* $(,)?] $(,)? 12 | )) => { 13 | AnalyzedProgram { 14 | globals: vec![$(analyzed_tree!($global)),*], 15 | functions: vec![$(analyzed_tree!($func)),*], 16 | main_fn: analyzed_tree!($main_fn), 17 | used_builtins: HashSet::from([$($name),*]), 18 | } 19 | }; 20 | (( 21 | FunctionDefinition, 22 | used: $used:expr, 23 | name: $name:expr, 24 | params: [$($param:tt),* $(,)?], 25 | return_type: $return_type:expr, 26 | block: $block:tt $(,)? 27 | )) => { 28 | AnalyzedFunctionDefinition { 29 | used: $used, 30 | name: $name, 31 | params: vec![$(analyzed_tree!($param)),*], 32 | return_type: $return_type, 33 | block: analyzed_tree!($block), 34 | } 35 | }; 36 | (( 37 | Parameter, 38 | mutable: $mutable:expr, 39 | name: $name:expr, 40 | type: $type:expr $(,)? 41 | )) => { 42 | AnalyzedParameter { 43 | mutable: $mutable, 44 | name: $name, 45 | type_: $type, 46 | } 47 | }; 48 | 49 | (( 50 | Let, 51 | name: $name:expr, 52 | expr: $expr:tt $(,)? 53 | )) => { 54 | AnalyzedLetStmt { 55 | name: $name, 56 | expr: analyzed_tree!($expr), 57 | } 58 | }; 59 | ((LetStmt $($rest:tt)*)) => { 60 | AnalyzedStatement::Let(analyzed_tree!((Let $($rest)*))) 61 | }; 62 | ((ReturnStmt, $expr:tt $(,)?)) => { 63 | AnalyzedStatement::Return(analyzed_tree!($expr)) 64 | }; 65 | ((ExprStmt, $expr:tt $(,)?)) => { 66 | AnalyzedStatement::Expr(analyzed_tree!($expr)) 67 | }; 68 | 69 | (( 70 | Block -> $result_type:expr, 71 | stmts: [$($stmt:tt),* $(,)?], 72 | expr: $expr:tt $(,)? 73 | )) => { 74 | AnalyzedBlock { 75 | result_type: $result_type, 76 | stmts: vec![$(analyzed_tree!($stmt)),*], 77 | expr: analyzed_tree!($expr), 78 | } 79 | }; 80 | ((BlockExpr $($rest:tt)*)) => { 81 | AnalyzedExpression::Block(analyzed_tree!((Block $($rest)*)).into()) 82 | }; 83 | (( 84 | IfExpr -> $result_type:expr, 85 | cond: $cond:tt, 86 | then_block: $then_block:tt, 87 | else_block: $else_block:tt $(,)? 88 | )) => { 89 | AnalyzedExpression::If(AnalyzedIfExpr { 90 | result_type: $result_type, 91 | cond: analyzed_tree!($cond), 92 | then_block: analyzed_tree!($then_block), 93 | else_block: analyzed_tree!($else_block), 94 | }.into()) 95 | }; 96 | ((Int $expr:expr)) => { AnalyzedExpression::Int($expr) }; 97 | ((Float $expr:expr)) => { AnalyzedExpression::Float($expr) }; 98 | ((Bool $expr:expr)) => { AnalyzedExpression::Bool($expr) }; 99 | ((Char $expr:expr)) => { AnalyzedExpression::Char($expr) }; 100 | ((Ident -> $type:expr, $ident:expr)) => { 101 | AnalyzedExpression::Ident(AnalyzedIdentExpr { 102 | result_type: $type, 103 | ident: $ident, 104 | }) 105 | }; 106 | ((Grouped $expr:tt)) => { 107 | AnalyzedExpression::Grouped(analyzed_tree!($expr).into()) 108 | }; 109 | (( 110 | PrefixExpr -> $result_type:expr, 111 | op: $op:expr, 112 | expr: $expr:tt $(,)? 113 | )) => { 114 | AnalyzedExpression::Prefix(AnalyzedPrefixExpr { 115 | result_type: $result_type, 116 | op: $op, 117 | expr: analyzed_tree!($expr), 118 | }.into()) 119 | }; 120 | (( 121 | InfixExpr -> $result_type:expr, 122 | lhs: $lhs:tt, 123 | op: $op:expr, 124 | rhs: $rhs:tt $(,)? 125 | )) => { 126 | AnalyzedExpression::Infix(AnalyzedInfixExpr { 127 | result_type: $result_type, 128 | lhs: analyzed_tree!($lhs), 129 | op: $op, 130 | rhs: analyzed_tree!($rhs), 131 | }.into()) 132 | }; 133 | (( 134 | AssignExpr -> $result_type:expr, 135 | assignee: $assignee:expr, 136 | op: $op:expr, 137 | expr: $expr:tt $(,)? 138 | )) => { 139 | AnalyzedExpression::Assign(AnalyzedAssignExpr { 140 | result_type: $result_type, 141 | assignee: $assignee, 142 | op: $op, 143 | expr: analyzed_tree!($expr), 144 | }.into()) 145 | }; 146 | (( 147 | CallExpr -> $result_type:expr, 148 | func: $func:expr, 149 | args: [$($arg:tt),* $(,)?] $(,)? 150 | )) => { 151 | AnalyzedExpression::Call(AnalyzedCallExpr { 152 | result_type: $result_type, 153 | func: $func, 154 | args: vec![$(analyzed_tree!($arg)),*], 155 | }.into()) 156 | }; 157 | (( 158 | CastExpr -> $result_type:expr, 159 | expr: $expr:tt, 160 | type: $type:expr $(,)? 161 | )) => { 162 | AnalyzedExpression::Cast(AnalyzedCastExpr { 163 | result_type: $result_type, 164 | expr: analyzed_tree!($expr), 165 | type_: $type, 166 | }.into()) 167 | }; 168 | } 169 | -------------------------------------------------------------------------------- /crates/rush-analyzer/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, process, time::Instant}; 2 | 3 | fn main() { 4 | let start = Instant::now(); 5 | let path = env::args().nth(1).unwrap(); 6 | let code = fs::read_to_string(&path).unwrap(); 7 | match rush_analyzer::analyze(&code, &path) { 8 | Ok(res) => { 9 | for diagnostic in &res.1 { 10 | println!("{diagnostic:#}"); 11 | } 12 | res.0 13 | } 14 | Err(diagnostics) => { 15 | for diagnostic in diagnostics { 16 | println!("{diagnostic:#}"); 17 | } 18 | eprintln!("Analyzer detected issues"); 19 | process::exit(1); 20 | } 21 | }; 22 | if Some("-t".to_string()) == env::args().nth(2) { 23 | println!("{:?}", start.elapsed()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/rush-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-cli" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [features] 12 | default = [] 13 | llvm = ["dep:rush-compiler-llvm"] 14 | 15 | [dependencies] 16 | anyhow = "1.0.66" 17 | clap = { version = "4.0.24", features = ["derive"] } 18 | rush-analyzer = { path = "../rush-analyzer" } 19 | rush-compiler-llvm = { path = "../rush-compiler-llvm", optional = true } 20 | rush-compiler-risc-v = { path = "../rush-compiler-risc-v" } 21 | rush-compiler-wasm = { path = "../rush-compiler-wasm" } 22 | rush-compiler-x86-64 = { path = "../rush-compiler-x86-64" } 23 | rush-interpreter-tree = { path = "../rush-interpreter-tree" } 24 | rush-interpreter-vm = { path = "../rush-interpreter-vm" } 25 | rush-ls = { path = "../rush-ls" } 26 | rush-transpiler-c = { path = "../rush-transpiler-c" } 27 | tempfile = "3.3.0" 28 | tokio = "1.26.0" 29 | -------------------------------------------------------------------------------- /crates/rush-cli/README.md: -------------------------------------------------------------------------------- 1 | # Command Line Interface 2 | 3 | This tool combines all of rush's backends and packages them in an easy-to-use 4 | CLI. 5 | 6 | ## Prerequisites 7 | 8 | - The [Rust](https://www.rust-lang.org/tools/install) programming language (with 9 | Cargo) 10 | - The dependencies of the 11 | [x86_64 Compiler](https://github.com/rush-rs/rush/tree/main/crates/rush-compiler-x86-64) 12 | - The dependencies of the 13 | [RISC-V Compiler](https://github.com/rush-rs/rush/tree/main/crates/rush-compiler-risc-v) 14 | - GNU Make 15 | 16 | ### A Note on LLVM 17 | 18 | - Since the LLVM compiler backend requires a valid LLVM installation, the `llvm` 19 | feature is disabled by default. 20 | 21 | ## Installation 22 | 23 | - Clone the rush repository 24 | - Navigate to the CLI directory 25 | - Install the CLI 26 | 27 | ```bash 28 | git clone https://github.com/rush-rs/rush && \ 29 | cd rush/crates/rush-cli && \ 30 | cargo install --path=. 31 | ``` 32 | 33 | ### With the Additional LLVM Feature 34 | 35 | > **Note:** A valid LLVM installation is required. For detailed instructions, 36 | > please refer to the [Inkwell](https://github.com/TheDan64/inkwell) crate. 37 | 38 | ```bash 39 | git clone https://github.com/rush-rs/rush && \ 40 | cd rush/crates/rush-cli && \ 41 | cargo install --path=. -F llvm 42 | ``` 43 | 44 | ## Usage 45 | 46 | - Prerequisite: A file ending in `.rush` and a valid CLI installation 47 | - After installation, the `rush-cli` command should be available 48 | 49 | ### Compilation 50 | 51 | - If the command is executed, a list of possible backends is displayed 52 | - Any of these backends can be set 53 | - An optional output name can be set using the `-o filename` flag 54 | 55 | ```bash 56 | rush-cli build fib.rush --backend=your-backend 57 | ``` 58 | 59 | #### Example: Compilation using RISC-V / x86_64 60 | 61 | ```bash 62 | # RISC-V: 63 | rush-cli build fib.rush --backend=risc-v -o my_output 64 | # x86_64: 65 | rush-cli build fib.rush --backend=x86-64 -o my_output 66 | ``` 67 | 68 | - Now, there is a file named `my_output` and one named `my_output.s` 69 | - The former is an executable binary, the latter is the generated assembly file 70 | 71 | ### Execution 72 | 73 | - If the command below is executed, a list of available run-backends is 74 | displayed 75 | - Any of these backends may be used in order to run the rush program 76 | 77 | ```bash 78 | rush-cli run fib.rush --backend=your-backend 79 | ``` 80 | 81 | #### Example: Running a rush Program 82 | 83 | ```bash 84 | # RISC-V: 85 | rush-cli run fib.rush --backend=risc-v 86 | # VM 87 | rush-cli run fib.rush --backend=vm 88 | # Tree-walking interpreter 89 | rush-cli run fib.rush --backend=tree 90 | ``` 91 | 92 | - All of these run-commands will omit any output files as temporary directories 93 | are used for the build artifacts 94 | -------------------------------------------------------------------------------- /crates/rush-cli/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::ErrorKind, 3 | path::Path, 4 | process::{Command, Stdio}, 5 | }; 6 | 7 | fn command_executable(path: &str) -> bool { 8 | match Command::new(path).spawn() { 9 | Ok(_) => true, 10 | Err(e) if e.kind() == ErrorKind::NotFound => false, 11 | Err(e) => panic!("Unknown error: `{e}`"), 12 | } 13 | } 14 | 15 | const ASSEMBLERS: &[&str] = &["riscv64-alpine-linux-musl-as", "riscv64-linux-gnu-as"]; 16 | const ARCHIVERS: &[&str] = &["riscv64-alpine-linux-musl-ar", "riscv64-linux-gnu-ar"]; 17 | const GCC_VARIANTS: &[&str] = &["riscv-none-elf-gcc", "riscv64-linux-gnu-gcc"]; 18 | 19 | fn main() { 20 | //// X86_64 corelib //// 21 | // rebuild if the corelib directory has changed 22 | println!("cargo:rerun-if-changed=../rush-compiler-x86-64/corelib/src"); 23 | println!("cargo:rerun-if-changed=../rush-compiler-x86-64/corelib/Makefile"); 24 | 25 | let path = Path::canonicalize(Path::new("../rush-compiler-x86-64/corelib/")).unwrap(); 26 | let out = Command::new("make") 27 | .arg("libcore.a") 28 | .current_dir(path) 29 | .stderr(Stdio::piped()) 30 | .output() 31 | .unwrap(); 32 | 33 | match out.status.success() { 34 | true => {} 35 | false => panic!( 36 | "building x86 `libcore.a` terminated with code {}: {}", 37 | out.status.code().unwrap(), 38 | String::from_utf8_lossy(&out.stderr) 39 | ), 40 | } 41 | 42 | //// RISC-V corelib //// 43 | // rebuild if the corelib directory has changed 44 | println!("cargo:rerun-if-changed=../rush-compiler-risc-v/corelib/src"); 45 | println!("cargo:rerun-if-changed=../rush-compiler-x86-64/corelib/Makefile"); 46 | 47 | // determine RISC-V assembler 48 | let mut assembler_bin = None; 49 | for assembler in ASSEMBLERS { 50 | if command_executable(assembler) { 51 | assembler_bin = Some(assembler); 52 | break; 53 | } 54 | } 55 | 56 | // determine RISC-V archiver 57 | let mut archiver_bin = None; 58 | for archiver in ARCHIVERS { 59 | if command_executable(archiver) { 60 | archiver_bin = Some(archiver); 61 | break; 62 | } 63 | } 64 | 65 | // determine RISC-V GCC 66 | let mut gcc_bin = None; 67 | for gcc in GCC_VARIANTS { 68 | if command_executable(gcc) { 69 | gcc_bin = Some(gcc); 70 | break; 71 | } 72 | } 73 | 74 | let path = Path::canonicalize(Path::new("../rush-compiler-risc-v/corelib/")).unwrap(); 75 | let out = Command::new("make") 76 | .arg("all") 77 | .arg(format!( 78 | "assembler={}", 79 | assembler_bin.expect("No suitable RISC-V assembler detected") 80 | )) 81 | .arg(format!( 82 | "archiver={}", 83 | archiver_bin.expect("No suitable RISC-V archiver detected") 84 | )) 85 | .arg(format!( 86 | "gcc={}", 87 | gcc_bin.expect("No suitable RISC-V gcc detected") 88 | )) 89 | .current_dir(path) 90 | .stderr(Stdio::piped()) 91 | .output() 92 | .unwrap(); 93 | 94 | match out.status.success() { 95 | true => {} 96 | false => panic!( 97 | "building RISC-V `libcore.a` terminated with code {}: {}", 98 | out.status.code().unwrap(), 99 | String::from_utf8_lossy(&out.stderr) 100 | ), 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/rush-cli/src/c.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | path::PathBuf, 4 | process::{Command, Stdio}, 5 | }; 6 | 7 | use anyhow::{anyhow, bail, Context}; 8 | use rush_analyzer::ast::AnalyzedProgram; 9 | use rush_transpiler_c::Transpiler; 10 | use tempfile::tempdir; 11 | 12 | use crate::cli::{BuildArgs, RunArgs}; 13 | 14 | pub fn compile(ast: AnalyzedProgram, args: BuildArgs) -> anyhow::Result<()> { 15 | let c = Transpiler::new(false).transpile(ast).to_string(); 16 | 17 | // get output path 18 | let output = match args.output_file { 19 | Some(out) => out, 20 | None => { 21 | let mut path = PathBuf::from( 22 | args.path 23 | .file_stem() 24 | .with_context(|| "cannot get filestem of input file")?, 25 | ); 26 | path.set_extension("c"); 27 | path 28 | } 29 | }; 30 | 31 | fs::write(&output, c) 32 | .with_context(|| format!("cannot write to `{file}`", file = output.to_string_lossy()))?; 33 | 34 | Ok(()) 35 | } 36 | 37 | pub fn run(ast: AnalyzedProgram, args: RunArgs) -> anyhow::Result { 38 | let tmpdir = tempdir()?; 39 | 40 | let mut args: BuildArgs = args.try_into()?; 41 | 42 | let c_path = tmpdir.path().join("output.c"); 43 | args.output_file = Some(c_path.clone()); 44 | compile(ast, args)?; 45 | 46 | let bin_path = tmpdir.path().join("output"); 47 | 48 | let process = Command::new("gcc") 49 | .arg(c_path) 50 | .arg("-std=c89") 51 | .arg("-o") 52 | .arg(&bin_path) 53 | .stderr(Stdio::piped()) 54 | .spawn()?; 55 | 56 | let out = process.wait_with_output()?; 57 | match out.status.success() { 58 | true => {} 59 | false => bail!( 60 | "compiling C to binary terminated with code {}: {}", 61 | out.status.code().unwrap_or(1), 62 | String::from_utf8_lossy(&out.stderr), 63 | ), 64 | } 65 | 66 | match Command::new(bin_path).output()?.status.code() { 67 | Some(code) => Ok(code as i64), 68 | None => Err(anyhow!("could not get exit-code of rush bin process")), 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/rush-cli/src/llvm.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | process::{Command, Stdio}, 4 | }; 5 | 6 | use anyhow::{bail, Context}; 7 | use rush_analyzer::ast::AnalyzedProgram; 8 | use rush_compiler_llvm::inkwell::targets::{TargetMachine, TargetTriple}; 9 | use tempfile::tempdir; 10 | 11 | use crate::cli::{BuildArgs, RunArgs}; 12 | 13 | pub fn compile(ast: AnalyzedProgram, args: BuildArgs) -> anyhow::Result<()> { 14 | let target = match args.llvm_target.as_ref() { 15 | Some(triplet) => TargetTriple::create(triplet), 16 | None => TargetMachine::get_default_triple(), 17 | }; 18 | 19 | let (obj, ir) = match rush_compiler_llvm::compile( 20 | ast, 21 | target, 22 | args.llvm_opt.into(), 23 | args.llvm_target.is_none(), // only compile a main fn if target is native 24 | ) { 25 | Ok(res) => res, 26 | Err(err) => bail!(format!("llvm error: {err}")), 27 | }; 28 | 29 | if args.llvm_show_ir { 30 | println!("{ir}"); 31 | eprintln!("LLVM IR printed: omitting output file generation"); 32 | return Ok(()); 33 | } 34 | 35 | // get output path 36 | let output = match args.output_file { 37 | Some(out) => out.with_extension("o"), 38 | None => args.path.with_extension("o"), 39 | }; 40 | 41 | fs::write(&output, obj.as_slice()) 42 | .with_context(|| format!("cannot write to `{}`", output.to_string_lossy()))?; 43 | 44 | // if a non-native target is used, quit here 45 | if args.llvm_target.is_some() { 46 | return Ok(()); 47 | } 48 | 49 | let bin_path = &output.with_extension(""); 50 | 51 | // invoke gcc to link the file 52 | let command = Command::new("gcc") 53 | .arg(&output) 54 | .arg("-o") 55 | .arg(bin_path) 56 | .stderr(Stdio::inherit()) 57 | .output() 58 | .with_context(|| "could not invoke `gcc`")?; 59 | 60 | if !command.status.success() { 61 | bail!( 62 | "invoking gcc failed with exit-code {code}", 63 | code = command.status.code().unwrap() 64 | ) 65 | } 66 | 67 | Ok(()) 68 | } 69 | 70 | pub fn run(ast: AnalyzedProgram, args: RunArgs) -> anyhow::Result { 71 | let tmpdir = tempdir()?; 72 | 73 | let mut args: BuildArgs = args.try_into()?; 74 | 75 | let executable_path = tmpdir.path().join("output"); 76 | args.output_file = Some(executable_path.clone()); 77 | 78 | compile(ast, args)?; 79 | 80 | let command = Command::new(executable_path.to_string_lossy().to_string()) 81 | .stderr(Stdio::inherit()) 82 | .stdout(Stdio::inherit()) 83 | .output() 84 | .with_context(|| "could not invoke compiled binary")?; 85 | 86 | command 87 | .status 88 | .code() 89 | .map(|c| c as i64) 90 | .with_context(|| "could not capture exit-code of compiled binary") 91 | } 92 | -------------------------------------------------------------------------------- /crates/rush-cli/src/riscv.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | io::ErrorKind, 4 | path::PathBuf, 5 | process::{Command, Stdio}, 6 | }; 7 | 8 | use anyhow::{anyhow, bail, Context}; 9 | use rush_analyzer::ast::AnalyzedProgram; 10 | use rush_compiler_risc_v::{CommentConfig, Compiler}; 11 | use tempfile::TempDir; 12 | 13 | use crate::cli::{BuildArgs, RunArgs}; 14 | 15 | fn command_executable(path: &str) -> anyhow::Result { 16 | match Command::new(path) 17 | .stdout(Stdio::piped()) 18 | .stderr(Stdio::piped()) 19 | .spawn() 20 | { 21 | Ok(_) => Ok(true), 22 | Err(e) if e.kind() == ErrorKind::NotFound => Ok(false), 23 | Err(e) => Err(anyhow!("Unexpected command error: `{e}`")), 24 | } 25 | } 26 | 27 | const GCC_VARIANTS: &[&str] = &["riscv-none-elf-gcc", "riscv64-linux-gnu-gcc"]; 28 | 29 | pub fn compile(ast: AnalyzedProgram, args: BuildArgs, tmpdir: &TempDir) -> anyhow::Result<()> { 30 | let asm = Compiler::new().compile(ast, &CommentConfig::Emit { line_width: 32 }); 31 | 32 | // get output path 33 | let output = match args.output_file { 34 | Some(mut out) => { 35 | out.set_extension("s"); 36 | out 37 | } 38 | None => { 39 | let mut path = PathBuf::from( 40 | args.path 41 | .file_stem() 42 | .with_context(|| "cannot get filestem of input file")?, 43 | ); 44 | path.set_extension("s"); 45 | path 46 | } 47 | }; 48 | 49 | fs::write(&output, asm) 50 | .with_context(|| format!("cannot write to `{file}`", file = output.to_string_lossy()))?; 51 | 52 | let mut bin_path = output.clone(); 53 | bin_path.set_extension(""); 54 | let libcore_file_path = tmpdir.path().join("libcore.a"); 55 | let libcore_bytes = 56 | include_bytes!("../../rush-compiler-risc-v/corelib/libcore-rush-riscv-lp64d.a"); 57 | fs::write(&libcore_file_path, libcore_bytes)?; 58 | 59 | // determine RISC-V GCC 60 | let mut gcc_bin = None; 61 | for gcc in GCC_VARIANTS { 62 | if command_executable(gcc).with_context(|| "Could not determine GCC backend")? { 63 | gcc_bin = Some(gcc); 64 | break; 65 | } 66 | } 67 | let Some(gcc) = gcc_bin else { 68 | bail!( 69 | "No suitable RISC-V GCC backend has been detected. options: [{}]", 70 | GCC_VARIANTS.join(", ") 71 | ) 72 | }; 73 | 74 | let process = Command::new(gcc) 75 | .args(["-nostdlib", "-static"]) 76 | .arg(output) 77 | .arg(libcore_file_path) 78 | .arg("-o") 79 | .arg(&bin_path) 80 | .stderr(Stdio::piped()) 81 | .spawn() 82 | .with_context(|| format!("could not invoke `{gcc}`"))?; 83 | 84 | let out = process.wait_with_output()?; 85 | match out.status.success() { 86 | true => {} 87 | false => bail!( 88 | "compiling assembly to binary terminated with code {}: {}", 89 | out.status.code().unwrap_or(1), 90 | String::from_utf8_lossy(&out.stderr), 91 | ), 92 | } 93 | 94 | Ok(()) 95 | } 96 | 97 | pub fn run(ast: AnalyzedProgram, args: RunArgs) -> anyhow::Result { 98 | let tmpdir = tempfile::tempdir()?; 99 | 100 | let mut args: BuildArgs = args.try_into()?; 101 | let asm_path = tmpdir.path().join("output.s"); 102 | let bin_path = tmpdir.path().join("output"); 103 | args.output_file = Some(asm_path); 104 | 105 | compile(ast, args, &tmpdir)?; 106 | 107 | match Command::new("qemu-riscv64") 108 | .arg(bin_path) 109 | .output() 110 | .with_context(|| "could not invoke `qemu-riscv64`")? 111 | .status 112 | .code() 113 | { 114 | Some(code) => Ok(code as i64), 115 | None => Err(anyhow!("could not get exit-code of rush bin process")), 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/rush-cli/src/vm.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use anyhow::{bail, Context}; 4 | use rush_analyzer::ast::AnalyzedProgram; 5 | use rush_interpreter_vm::Vm; 6 | 7 | use crate::cli::{BuildArgs, RunArgs}; 8 | 9 | const VM_MEM_SIZE: usize = 10_024; 10 | 11 | pub fn compile(ast: AnalyzedProgram, args: BuildArgs) -> anyhow::Result<()> { 12 | let program = rush_interpreter_vm::compile(ast); 13 | 14 | // get output path 15 | let output = match args.output_file { 16 | Some(out) => out, 17 | None => args.path.with_extension("s"), 18 | }; 19 | 20 | fs::write(&output, program.to_string()) 21 | .with_context(|| format!("cannot write to `{}`", output.to_string_lossy()))?; 22 | 23 | Ok(()) 24 | } 25 | 26 | pub fn run(ast: AnalyzedProgram, args: RunArgs) -> anyhow::Result { 27 | let mut vm: Vm = Vm::new(); 28 | let program = rush_interpreter_vm::compile(ast); 29 | 30 | match args.vm_speed { 31 | Some(clock) => { 32 | if clock == 0 { 33 | bail!("attempted to use a clock speed of 0, must be > 0") 34 | } 35 | vm.debug_run(program, clock) 36 | } 37 | None => vm.run(program), 38 | } 39 | .with_context(|| "VM crashed") 40 | } 41 | -------------------------------------------------------------------------------- /crates/rush-cli/src/wasm.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::PathBuf}; 2 | 3 | use anyhow::{bail, Context}; 4 | use rush_analyzer::ast::AnalyzedProgram; 5 | use rush_compiler_wasm::Compiler; 6 | 7 | use crate::cli::BuildArgs; 8 | 9 | pub fn compile(ast: AnalyzedProgram, args: BuildArgs) -> anyhow::Result<()> { 10 | let wasm = match Compiler::new().compile(ast) { 11 | Ok(wasm) => wasm, 12 | Err(err) => bail!(err), 13 | }; 14 | 15 | // get output path 16 | let output = match args.output_file { 17 | Some(out) => out, 18 | None => { 19 | let mut path = PathBuf::from( 20 | args.path 21 | .file_stem() 22 | .with_context(|| "cannot get filestem of input file")?, 23 | ); 24 | path.set_extension("wasm"); 25 | path 26 | } 27 | }; 28 | 29 | fs::write(&output, wasm) 30 | .with_context(|| format!("cannot write to `{file}`", file = output.to_string_lossy()))?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /crates/rush-cli/src/x86.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | path::PathBuf, 4 | process::{Command, Stdio}, 5 | }; 6 | 7 | use anyhow::{anyhow, bail, Context}; 8 | use rush_analyzer::ast::AnalyzedProgram; 9 | use rush_compiler_x86_64::{CommentConfig, Compiler}; 10 | use tempfile::tempdir; 11 | 12 | use crate::cli::{BuildArgs, RunArgs}; 13 | 14 | pub fn compile(ast: AnalyzedProgram, args: BuildArgs) -> anyhow::Result { 15 | let asm = Compiler::new().compile(ast, CommentConfig::default()); 16 | 17 | // get output path 18 | let output_path = match args.output_file { 19 | Some(out) => out, 20 | None => PathBuf::from( 21 | args.path 22 | .file_stem() 23 | .with_context(|| "cannot get filestem of input file")?, 24 | ), 25 | }; 26 | 27 | let mut asm_path = output_path.clone(); 28 | asm_path.set_extension("s"); 29 | fs::write(&asm_path, asm).with_context(|| { 30 | format!( 31 | "cannot write to `{file}`", 32 | file = output_path.to_string_lossy() 33 | ) 34 | })?; 35 | 36 | let tmpdir = tempdir()?; 37 | let libcore_file_path = tmpdir.path().join("libcore.a"); 38 | let libcore_bytes = include_bytes!("../../rush-compiler-x86-64/corelib/libcore.a"); 39 | fs::write(&libcore_file_path, libcore_bytes)?; 40 | 41 | let process = Command::new("gcc") 42 | .arg(asm_path) 43 | .arg(libcore_file_path) 44 | .arg("-nostdlib") 45 | .arg("-o") 46 | .arg(&output_path) 47 | .stderr(Stdio::piped()) 48 | .spawn()?; 49 | 50 | let out = process.wait_with_output()?; 51 | match out.status.success() { 52 | true => {} 53 | false => bail!( 54 | "compiling assembly to binary terminated with code {}: {}", 55 | out.status.code().unwrap_or(1), 56 | String::from_utf8_lossy(&out.stderr), 57 | ), 58 | } 59 | 60 | Ok(output_path) 61 | } 62 | 63 | pub fn run(ast: AnalyzedProgram, args: RunArgs) -> anyhow::Result { 64 | #[cfg(not(target_arch = "x86_64"))] 65 | bail!("not running on `x86_64`"); 66 | 67 | let args: BuildArgs = args.try_into()?; 68 | let bin_output = compile(ast, args)?; 69 | 70 | match Command::new( 71 | bin_output 72 | .canonicalize() 73 | .with_context(|| "could not canonicalize output path")?, 74 | ) 75 | .output() 76 | .with_context(|| "could not run binary")? 77 | .status 78 | .code() 79 | { 80 | Some(code) => Ok(code as i64), 81 | None => Err(anyhow!("could not get exit-code of rush bin process")), 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/.gitignore: -------------------------------------------------------------------------------- 1 | output* 2 | test 3 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-compiler-llvm" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [dependencies] 10 | inkwell = { git = "https://github.com/TheDan64/inkwell", rev = "c77d05639c9b73d9bede713789b9707d45863530", features = ["llvm14-0"] } 11 | rush-analyzer = { path = "../rush-analyzer/", version = "0.1.0" } 12 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/Makefile: -------------------------------------------------------------------------------- 1 | src := ../../samples/complete.rush 2 | out := test 3 | 4 | run: $(src) src 5 | cargo r $(src) 6 | gcc output.o -o $(out) 7 | ./$(out) 8 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/README.md: -------------------------------------------------------------------------------- 1 | # LLVM Compiler Backend 2 | 3 | A multiple-target rush compiler backend using the _LLVM_ framework and the 4 | [Inkwell](https://github.com/TheDan64/inkwell) crate. 5 | 6 | ## Prerequisites 7 | 8 | In order to compile the _LLVM_ backend, please ensure that you have a valid 9 | _LLVM_ installation on your machine. For further information and 10 | troubleshooting, please consult the 11 | [llvm-sys](https://docs.rs/crate/llvm-sys/latest) crate's documentation as it is 12 | used by [Inkwell](https://github.com/TheDan64/inkwell). 13 | 14 | ## Compiling and Running rush Code 15 | 16 | ### Compilation 17 | 18 | - Prerequisite: A file ending in `.rush` which contains the program. 19 | - Execute following command as it will generate the `output.o` file from the 20 | source program. 21 | 22 | ```bash 23 | cargo run your-file.rush 24 | ``` 25 | 26 | ### Linking 27 | 28 | - Since rush programs depend on some sort of standard library, linking the `.o` 29 | file using `gcc` will likely result in a valid program. 30 | - In order to link the `output.o` alongside your system's C library, issue 31 | following command. 32 | 33 | ```bash 34 | gcc output.o -o output 35 | ``` 36 | 37 | ### Running the Program 38 | 39 | - Since the output of the previous command is a binary, it can be executed using 40 | the following command. 41 | - The suffix containing `echo ...` is optional and merely prints out the 42 | program's exit-code. 43 | 44 | ```bash 45 | ./output ; echo $? 46 | ``` 47 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/llvm_ir_test/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | main.s 3 | main.ll 4 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/llvm_ir_test/Makefile: -------------------------------------------------------------------------------- 1 | run: build 2 | ./main 3 | 4 | # builds the main.cpp file 5 | build: 6 | clang main.cpp -o main 7 | 8 | # generates the IR from the main.cpp file 9 | ir: 10 | clang -S -emit-llvm main.cpp -o main.ll 11 | 12 | # builds an executable from the IR file 13 | build-ir: 14 | llc main.ll 15 | clang main.s -o main 16 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/llvm_ir_test/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void foo(int a, int b) { 4 | exit(a + b); 5 | } 6 | 7 | int main() { 8 | foo(1, 2); 9 | } 10 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/llvm_ir_test/main.cpp: -------------------------------------------------------------------------------- 1 | // this file is used for testing the LLVM IR 2 | 3 | extern "C" // disables name mangling 4 | int 5 | add(int a, int b) { 6 | return a + b; 7 | } 8 | 9 | extern "C" int sub(int a, int b) { return a - b; } 10 | 11 | extern "C" int mul(int a, int b) { return a * b; } 12 | 13 | extern "C" int div(int a, int b) { return a / b; } 14 | 15 | extern "C" int pow(int base, int exp) { 16 | if (exp == 1) { 17 | return base; 18 | } else { 19 | return pow(base * base, exp - 1); 20 | } 21 | } 22 | 23 | int main() { return pow(3, 2); } 24 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/llvm_ir_test/main2.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'main.c' 2 | source_filename = "main.c" 3 | target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-pc-linux-gnu" 5 | 6 | ; Function Attrs: noinline nounwind optnone sspstrong uwtable 7 | define dso_local i32 @main() #0 { 8 | %1 = alloca i32, align 4 9 | store i32 0, i32* %1, align 4 10 | call void @exit(i32 noundef 1) #2 11 | unreachable 12 | } 13 | 14 | ; Function Attrs: noreturn nounwind 15 | declare void @exit(i32 noundef) #1 16 | 17 | attributes #0 = { noinline nounwind optnone sspstrong uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 18 | attributes #1 = { noreturn nounwind "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 19 | attributes #2 = { noreturn nounwind } 20 | 21 | !llvm.module.flags = !{!0, !1, !2, !3, !4} 22 | !llvm.ident = !{!5} 23 | 24 | !0 = !{i32 1, !"wchar_size", i32 4} 25 | !1 = !{i32 7, !"PIC Level", i32 2} 26 | !2 = !{i32 7, !"PIE Level", i32 2} 27 | !3 = !{i32 7, !"uwtable", i32 1} 28 | !4 = !{i32 7, !"frame-pointer", i32 2} 29 | !5 = !{!"clang version 14.0.6"} 30 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | use inkwell::support::LLVMString; 4 | 5 | pub type Result = std::result::Result; 6 | 7 | #[derive(Debug)] 8 | pub enum Error { 9 | Llvm(String), 10 | NoTarget, 11 | } 12 | 13 | impl Display for Error { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 15 | write!( 16 | f, 17 | "{}", 18 | match self { 19 | Self::Llvm(message) => format!("llvm: {message}"), 20 | Self::NoTarget => "invalid target: no such target".to_string(), 21 | } 22 | ) 23 | } 24 | } 25 | 26 | impl From for Error { 27 | fn from(err: LLVMString) -> Self { 28 | Self::Llvm(err.to_string()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod compiler; 2 | mod corelib; 3 | mod error; 4 | 5 | pub use compiler::*; 6 | pub use error::*; 7 | pub use inkwell; 8 | use inkwell::{ 9 | context::Context, memory_buffer::MemoryBuffer, targets::TargetTriple, OptimizationLevel, 10 | }; 11 | use rush_analyzer::ast::AnalyzedProgram; 12 | 13 | /// Compiles a rush AST into LLVM IR and an object file. 14 | /// The `main_fn` param specifies whether the entry is the main function or `_start`. 15 | pub fn compile( 16 | ast: AnalyzedProgram, 17 | target: TargetTriple, 18 | optimization: OptimizationLevel, 19 | compile_main_fn: bool, 20 | ) -> Result<(MemoryBuffer, String)> { 21 | let context = Context::create(); 22 | let mut compiler = Compiler::new(&context, target, optimization, compile_main_fn)?; 23 | compiler.compile(&ast) 24 | } 25 | -------------------------------------------------------------------------------- /crates/rush-compiler-llvm/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs}; 2 | 3 | use inkwell::{context::Context, targets::TargetMachine}; 4 | use rush_compiler_llvm::Compiler; 5 | 6 | fn main() { 7 | let filename = env::args().nth(1).unwrap(); 8 | let file = fs::read_to_string(&filename).unwrap(); 9 | let ast = match rush_analyzer::analyze(&file, &filename) { 10 | Ok(res) => { 11 | for diagnostic in &res.1 { 12 | println!("{diagnostic:#}"); 13 | } 14 | res.0 15 | } 16 | Err(diagnostics) => { 17 | for diagnostic in diagnostics { 18 | println!("{diagnostic:#}"); 19 | } 20 | panic!("Analyzer detected issues"); 21 | } 22 | }; 23 | 24 | let context = Context::create(); 25 | let mut compiler = Compiler::new( 26 | &context, 27 | TargetMachine::get_default_triple(), 28 | inkwell::OptimizationLevel::None, 29 | true, 30 | ) 31 | .unwrap(); 32 | 33 | let (obj, ir) = compiler.compile(&ast).unwrap(); 34 | fs::write("./output.ll", ir).unwrap(); 35 | fs::write("./output.o", obj.as_slice()).unwrap(); 36 | } 37 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | output.s 3 | output.o 4 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-compiler-risc-v" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | rush-analyzer = { version = "0.1.0", path = "../rush-analyzer" } 13 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/Makefile: -------------------------------------------------------------------------------- 1 | src := output.s 2 | out := test 3 | 4 | build: src 5 | riscv64-linux-gnu-as -g $(src) -o output.o 6 | riscv64-linux-gnu-ld output.o -L corelib -lcore-rush-riscv-lp64d -static -nostdlib -no-relax -o $(out) 7 | 8 | run: build 9 | qemu-riscv64 ./$(out) 10 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/README.md: -------------------------------------------------------------------------------- 1 | # RISC-V Compiler Backend 2 | 3 | A single-target rush compiler backend which generates 4 | [RISC-V](https://riscv.org/) assembly files without the need for external 5 | dependencies. 6 | 7 | ## Prerequisites 8 | 9 | Since the compiler itself requires no external dependencies, only the RISC-V 10 | toolchain and additional software is to be installed. Hence, program execution 11 | requires an assembler, a linker, and an emulator. 12 | 13 | ### Toolchain 14 | 15 | On Arch-Linux based systems, the following packages can be installed to set up a 16 | working toolchain. 17 | 18 | - `riscv64-linux-gnu-gcc` for assembling and linking 19 | - `riscv64-linux-gnu-binutils` (optional) 20 | 21 | ### Emulator 22 | 23 | On Arch-Linux based systems, the `qemu-system-riscv` package provides an 24 | emulator for RISC-V processors. 25 | 26 | ## Compiling and Running rush Code 27 | 28 | ### Compilation of rush Code 29 | 30 | - Prerequisite: A file ending in `.rush` which contains the program. 31 | - Execute following command as it will generate the `output.s` file from the 32 | source program. 33 | 34 | ```bash 35 | cargo run your-program.rush 36 | ``` 37 | 38 | Since RISC-V targeted rush programs depend on a special [corelib](./corelib), 39 | linking demands more steps than usual. 40 | 41 | ### Assembling the Corelib 42 | 43 | - Navigate inside the [corelib](./corelib/) subdirectory. 44 | - Enter the following command in order to compile the corelib for several RISC-V 45 | ABIs as it should execute successfully and produce several files ending in 46 | `.a` 47 | 48 | ```bash 49 | make all 50 | ``` 51 | 52 | ### Final Assembling & Linking Alongside the Corelib 53 | 54 | - This project includes a `Makefile` which contains the `build` target. 55 | - Issuing the following command should produce an executable binary file in the 56 | current directory. 57 | 58 | ```bash 59 | make build 60 | ``` 61 | 62 | ### Running RISC-V Binaries 63 | 64 | - Since you have installed a RISC-V emulator prior to reading this section, 65 | following command can be used to run the binary using the emulator. 66 | - The suffix containing `echo ...` is optional and merely prints out the 67 | program's exit-code. 68 | 69 | ```bash 70 | qemu-riscv64 ./your-output ; echo $? 71 | ``` 72 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/corelib/.gitignore: -------------------------------------------------------------------------------- 1 | libcore*.a 2 | main 3 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/corelib/Makefile: -------------------------------------------------------------------------------- 1 | src := pow exit cast 2 | obj := pow.o exit.o cast.o 3 | dest := libcore-rush-riscv 4 | 5 | # default RISC-V ABI 6 | abis := lp64 lp64d 7 | 8 | assembler := riscv64-linux-gnu-as 9 | archiver := riscv64-linux-gnu-ar 10 | gcc := riscv64-linux-gnu-gcc 11 | 12 | # creates an `.a` file of the entire corelib 13 | libcore.a: src/pow.s src/exit.s src/cast.s 14 | for abi in $(abis); do \ 15 | for file in $(src); do \ 16 | $(assembler) -mabi=$$abi src/$$file.s -o $$file.o; \ 17 | done; \ 18 | $(archiver) -cq $(dest)-$$abi.a $(obj); \ 19 | rm $(obj); \ 20 | done 21 | 22 | all: clean libcore.a 23 | 24 | clean: 25 | rm -f *.a 26 | 27 | # runs `main.c` alongside the corelib in order to test it 28 | test: libcore.a 29 | $(gcc) -nostdlib -static main.c -L . -lcore-rush-riscv-lp64d -o main 30 | qemu-riscv64 ./main 31 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/corelib/main.c: -------------------------------------------------------------------------------- 1 | extern void exit(int); 2 | extern int __rush_internal_pow_int(int, int); 3 | 4 | extern char __rush_internal_cast_int_to_char(int); 5 | extern char __rush_internal_cast_float_to_char(double); 6 | 7 | void _start() { 8 | int pow_res = __rush_internal_pow_int(2, 7); 9 | char char_1 = __rush_internal_cast_int_to_char(197); 10 | char char_2 = __rush_internal_cast_float_to_char(197.0); 11 | exit(char_2); 12 | } 13 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/corelib/src/cast.s: -------------------------------------------------------------------------------- 1 | # This file is part of the rush corelib for RISC-V 2 | # - Authors: MikMuellerDev 3 | # This file, alongside others is linked to form a RISC-V targeted rush program. 4 | 5 | # Converts a i64 into a u8 char. 6 | # If the source is < 0, the result will be 0. 7 | # Furthermore, if the source is > 127, the result will be 127. 8 | # fn __rush_internal_cast_int_to_char(from: int) -> char 9 | .global __rush_internal_cast_int_to_char 10 | 11 | __rush_internal_cast_int_to_char: 12 | # if from < 0; return 0 13 | blt a0, zero, return_0 14 | # if from > 127; return 127 15 | li t0, 127 16 | bgt a0, t0, return_127 17 | ret # return argument 18 | 19 | 20 | # Converts a f64 into a u8 char. 21 | # If the source is < 0.0, the result will be 0. 22 | # Furthermore, if the source is > 127.0, the result will be 127. 23 | # fn __rush_internal_cast_float_to_char(from: float) -> char 24 | .global __rush_internal_cast_float_to_char 25 | 26 | __rush_internal_cast_float_to_char: 27 | # if from < 0.0; return 0 28 | fld ft0, float_0, t0 29 | flt.d t0, fa0, ft0 30 | bne t0, zero, return_0 31 | # if from > 127.0; return 127 32 | fld ft0, float_127, t0 33 | fgt.d t0, fa0, ft0 34 | bne t0, zero, return_127 35 | # truncate argument to int 36 | fcvt.w.d a0, fa0, rdn 37 | ret # return truncated value 38 | 39 | 40 | ### UTILS ### 41 | return_0: 42 | li a0, 0 43 | ret 44 | 45 | return_127: 46 | li a0, 127 47 | ret 48 | 49 | ### CONSTANTS ### 50 | .section .rodata 51 | float_0: 52 | .dword 0x0000000000000000 53 | 54 | float_127: 55 | .dword 0x405fc00000000000 56 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/corelib/src/exit.s: -------------------------------------------------------------------------------- 1 | # This file is part of the rush corelib for RISC-V 2 | # - Authors: MikMuellerDev 3 | # This file, alongside others is linked to form a RISC-V targeted rush program. 4 | 5 | # Calls the Linux kernel to exit using the specified argument. 6 | # Any code after this function call is unreachable, therefore its return type is !. 7 | # fn exit(code: int) -> ! 8 | .global exit 9 | 10 | exit: 11 | li a7, 93 # syscall type is `exit` 12 | ecall # exit code is already in `a0` 13 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/corelib/src/pow.s: -------------------------------------------------------------------------------- 1 | # This file is part of the rush corelib for RISC-V 2 | # - Authors: MikMuellerDev 3 | # This file, alongside others is linked to form a RISC-V targeted rush program. 4 | 5 | # Calculates the nth power of the specified base. 6 | # If the exponent is 0, the result is 1. 7 | # Furthermore, if the exponent is < 0, the result is 0 (simulates truncated float). 8 | # fn __rush_internal_pow_int(base: int, exp: int) -> int 9 | .global __rush_internal_pow_int 10 | 11 | __rush_internal_pow_int: 12 | # if exp == 0; return 1 13 | beq a1, zero, return_1 14 | # if exp < 0; return 0 15 | blt a1, zero, return_0 16 | 17 | li t0, 1 # acc = 1 18 | 19 | loop_head: 20 | # if exp < 2; { break } 21 | li t1, 2 22 | blt a1, t1, after_loop 23 | 24 | # if (exp & 1) == 1 25 | andi t1, a1, 1 26 | li t2, 1 27 | beq t1, t2, inc_acc 28 | 29 | loop_body: 30 | srli a1, a1, 1 # exp /= 2 31 | mul a0, a0, a0 32 | j loop_head # continue 33 | 34 | inc_acc: 35 | mul t0, t0, a0 36 | j loop_body 37 | 38 | after_loop: 39 | mul a0, t0, a0 # acc * base 40 | ret 41 | 42 | return_0: 43 | li a0, 0 44 | ret 45 | 46 | return_1: 47 | li a0, 1 48 | ret 49 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/notes.md: -------------------------------------------------------------------------------- 1 | # RISC-V Notes 2 | 3 | ## Stack Layout 4 | 5 | In the current implementation, the stack pointer `sp` includes legal memory 6 | locations whilst the frame pointer `fp` points to the address after the last 7 | legal one. 8 | 9 | ``` 10 | 0(sp) 8(sp) 16(sp) 24(sp) 32(sp) 11 | -32(fp) -24(fp) -16(fp) -8(fp) 0(fp) 12 | | | | | | 13 | +--------+--------+--------+--------+ 14 | | | | | | 15 | illegal memory <- | fp | ra | ax | ax | -> illegal memory 16 | | | | | | 17 | +--------+--------+--------+--------+ 18 | ^ ^ 19 | | | 20 | sp fp 21 | ``` 22 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/samples/fib.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | exit(fib(10)); 3 | } 4 | 5 | fn fib(n: int) -> int { 6 | if n < 2 { 7 | n 8 | } else { 9 | fib(n - 1) + fib(n - 2) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/samples/infix_logic.rush: -------------------------------------------------------------------------------- 1 | let mut count = 0; 2 | 3 | let true_ = true; 4 | let false_ = false; 5 | 6 | fn main() { 7 | count += (true_ || false_ || increments_count()) as int; // += 1 8 | count += (false_ | false_) as int; // += 0 9 | count += (true_ | true_) as int; // += 1 10 | count += (false_ | increments_count() | false_ | true_) as int; // += 2 11 | count += (false_ && false_) as int; // += 0 12 | count += (false_ && true_ && false_) as int; // += 0 13 | count += (true_ && true_ && true_) as int; // += 1 14 | count += (true_ && true_ && (false_ || true_ || increments_count())) as int; // += 1 15 | false && exit(0); // nothing 16 | false || exit(count); // 6 17 | } 18 | 19 | fn increments_count() -> bool { 20 | count += 1; 21 | false 22 | } 23 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/samples/main_recursion.rush: -------------------------------------------------------------------------------- 1 | let mut cnt = 0; 2 | 3 | fn main() { 4 | if cnt == 100 { 5 | exit(cnt); 6 | } 7 | incr(); 8 | } 9 | 10 | fn incr() { 11 | cnt += 1; 12 | main() 13 | } 14 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/samples/sqrt.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let sqrt = sqrt(15); 3 | let success = sqrt > 3.8 && sqrt < 3.9; // should be true 4 | exit(success as int); 5 | } 6 | 7 | fn sqrt(n: int) -> float { 8 | let mut res = 1.0; 9 | 10 | while (abs(res * res - n as float) >= 0.001) { 11 | res = ((n as float / res) + res) / 2.0; 12 | } 13 | 14 | res 15 | } 16 | 17 | fn abs(n: float) -> float { 18 | if (n < 0.0) { 19 | -1.0 * n 20 | } else { 21 | n 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/samples/test.rush: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/src/corelib.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | compiler::Compiler, 3 | instruction::Instruction, 4 | register::{FloatRegister, IntRegister}, 5 | }; 6 | 7 | impl<'tree> Compiler<'tree> { 8 | /// Helper function for the `**` and `**=` operators. 9 | /// Because the RISC-V ISA does not support the pow instruction, the corelib is used. 10 | /// This function calls the `__rush_internal_pow_int` function in the rush corelib. 11 | pub(crate) fn __rush_internal_pow_int( 12 | &mut self, 13 | base: IntRegister, 14 | exponent: IntRegister, 15 | ) -> IntRegister { 16 | // before the function is called, all currently used registers are saved 17 | let regs_on_stack = self 18 | .used_registers 19 | .clone() 20 | .iter() 21 | .map(|(reg, size)| (*reg, self.spill_reg(*reg, *size), *size)) 22 | .collect(); 23 | 24 | // prepare the arguments 25 | if base != IntRegister::A0 { 26 | self.insert(Instruction::Mv(IntRegister::A0, base)); 27 | } 28 | if exponent != IntRegister::A1 { 29 | self.insert(Instruction::Mv(IntRegister::A1, exponent)); 30 | } 31 | 32 | // perform the function call 33 | self.insert(Instruction::Call("__rush_internal_pow_int".into())); 34 | 35 | // restore all saved registers 36 | self.restore_regs_after_call(Some(IntRegister::A0.to_reg()), regs_on_stack) 37 | .expect("is int") 38 | .into() 39 | } 40 | 41 | /// Calls the `__rush_internal_cast_int_to_char` function in the rush corelib. 42 | pub(crate) fn __rush_internal_cast_int_to_char(&mut self, src: IntRegister) -> IntRegister { 43 | // before the function is called, all currently used registers are saved 44 | let regs_on_stack = self 45 | .used_registers 46 | .clone() 47 | .iter() 48 | .map(|(reg, size)| (*reg, self.spill_reg(*reg, *size), *size)) 49 | .collect(); 50 | 51 | // prepare the argument 52 | if src != IntRegister::A0 { 53 | self.insert(Instruction::Mv(IntRegister::A0, src)); 54 | } 55 | 56 | // perform the function call 57 | self.insert(Instruction::Call("__rush_internal_cast_int_to_char".into())); 58 | 59 | // restore all saved registers 60 | self.restore_regs_after_call(Some(IntRegister::A0.to_reg()), regs_on_stack) 61 | .expect("is char") 62 | .into() 63 | } 64 | 65 | /// Calls the `__rush_internal_cast_float_to_char` function in the rush corelib. 66 | pub(crate) fn __rush_internal_cast_float_to_char(&mut self, src: FloatRegister) -> IntRegister { 67 | // before the function is called, all currently used registers are saved 68 | let regs_on_stack = self 69 | .used_registers 70 | .clone() 71 | .iter() 72 | .map(|(reg, size)| (*reg, self.spill_reg(*reg, *size), *size)) 73 | .collect(); 74 | 75 | // prepare the argument 76 | if src != FloatRegister::Fa0 { 77 | self.insert(Instruction::Fmv(FloatRegister::Fa0, src)); 78 | } 79 | 80 | // perform the function call 81 | self.insert(Instruction::Call( 82 | "__rush_internal_cast_float_to_char".into(), 83 | )); 84 | 85 | // restore all saved registers 86 | self.restore_regs_after_call(Some(IntRegister::A0.to_reg()), regs_on_stack) 87 | .expect("is char") 88 | .into() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use compiler::Compiler; 2 | pub use instruction::CommentConfig; 3 | use rush_analyzer::Diagnostic; 4 | 5 | mod call; 6 | mod compiler; 7 | mod corelib; 8 | mod instruction; 9 | mod register; 10 | mod utils; 11 | 12 | /// Compiles rush source code to a RISC-V assembly. 13 | /// The `Ok(_)` variant also returns non-error diagnostics. 14 | /// The `Err(_)` variant returns a `Vec` which contains at least one error. 15 | pub fn compile<'tree>( 16 | text: &'tree str, 17 | path: &'tree str, 18 | comment_config: &CommentConfig, 19 | ) -> Result<(String, Vec>), Vec>> { 20 | let (tree, diagnostics) = rush_analyzer::analyze(text, path)?; 21 | let asm = Compiler::new().compile(tree, comment_config); 22 | Ok((asm, diagnostics)) 23 | } 24 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, process, time::Instant}; 2 | 3 | use rush_compiler_risc_v::CommentConfig; 4 | 5 | fn main() { 6 | let path = env::args().nth(1).unwrap(); 7 | let line_width = env::args() 8 | .nth(2) 9 | .unwrap_or("32".to_string()) 10 | .parse() 11 | .unwrap(); 12 | let code = fs::read_to_string(&path).unwrap(); 13 | let start = Instant::now(); 14 | let (out, diagnostics) = 15 | rush_compiler_risc_v::compile(&code, &path, &CommentConfig::Emit { line_width }) 16 | .unwrap_or_else(|diagnostics| { 17 | println!( 18 | "{}", 19 | diagnostics 20 | .iter() 21 | .map(|d| format!("{d:#}")) 22 | .collect::>() 23 | .join("\n\n") 24 | ); 25 | process::exit(1) 26 | }); 27 | 28 | println!( 29 | "{}", 30 | diagnostics 31 | .iter() 32 | .map(|d| format!("{d:#}")) 33 | .collect::>() 34 | .join("\n\n") 35 | ); 36 | 37 | if Some("-t".to_string()) == env::args().nth(3) { 38 | println!("compile: {:?}", start.elapsed()); 39 | } 40 | fs::write("output.s", out).unwrap(); 41 | } 42 | -------------------------------------------------------------------------------- /crates/rush-compiler-risc-v/src/register.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 4 | pub enum Register { 5 | Int(IntRegister), 6 | Float(FloatRegister), 7 | } 8 | 9 | impl Display for Register { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | write!( 12 | f, 13 | "{}", 14 | match self { 15 | Register::Int(reg) => reg.to_string(), 16 | Register::Float(reg) => reg.to_string(), 17 | } 18 | ) 19 | } 20 | } 21 | 22 | impl From for Register { 23 | fn from(src: IntRegister) -> Self { 24 | Self::Int(src) 25 | } 26 | } 27 | 28 | impl From for Register { 29 | fn from(src: FloatRegister) -> Self { 30 | Self::Float(src) 31 | } 32 | } 33 | 34 | pub(crate) const INT_REGISTERS: &[IntRegister] = &[ 35 | IntRegister::A0, 36 | IntRegister::A1, 37 | IntRegister::A2, 38 | IntRegister::A3, 39 | IntRegister::A4, 40 | IntRegister::A5, 41 | IntRegister::A6, 42 | IntRegister::A7, 43 | IntRegister::T0, 44 | IntRegister::T1, 45 | IntRegister::T2, 46 | IntRegister::T3, 47 | IntRegister::T4, 48 | IntRegister::T5, 49 | IntRegister::S1, 50 | IntRegister::S2, 51 | IntRegister::S3, 52 | IntRegister::S4, 53 | IntRegister::S5, 54 | IntRegister::S6, 55 | IntRegister::S7, 56 | IntRegister::S8, 57 | IntRegister::S9, 58 | IntRegister::S10, 59 | IntRegister::S11, 60 | ]; 61 | 62 | pub(crate) const FLOAT_REGISTERS: &[FloatRegister] = &[ 63 | FloatRegister::Fa0, 64 | FloatRegister::Fa1, 65 | FloatRegister::Fa2, 66 | FloatRegister::Fa3, 67 | FloatRegister::Fa4, 68 | FloatRegister::Fa5, 69 | FloatRegister::Fa6, 70 | FloatRegister::Fa7, 71 | FloatRegister::Ft0, 72 | FloatRegister::Ft1, 73 | FloatRegister::Ft2, 74 | FloatRegister::Ft3, 75 | FloatRegister::Ft4, 76 | FloatRegister::Ft5, 77 | FloatRegister::Ft6, 78 | FloatRegister::Ft7, 79 | FloatRegister::Ft8, 80 | FloatRegister::Ft9, 81 | FloatRegister::Ft10, 82 | FloatRegister::Ft11, 83 | FloatRegister::Fs0, 84 | FloatRegister::Fs1, 85 | FloatRegister::Fs2, 86 | FloatRegister::Fs3, 87 | FloatRegister::Fs4, 88 | FloatRegister::Fs5, 89 | FloatRegister::Fs6, 90 | FloatRegister::Fs7, 91 | FloatRegister::Fs8, 92 | FloatRegister::Fs9, 93 | FloatRegister::Fs10, 94 | FloatRegister::Fs11, 95 | ]; 96 | 97 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 98 | pub enum IntRegister { 99 | // special 100 | Zero, 101 | Ra, 102 | // pointers 103 | Sp, 104 | // Gp: DISABLED: not needed by the compiler, 105 | // Tp: DISABLED: not needed by the compiler, 106 | Fp, 107 | // temporaries 108 | T0, 109 | T1, 110 | T2, 111 | T3, 112 | T4, 113 | T5, 114 | // T6: DISABLED: used during instruction code generation for temporary purposes, 115 | // saved 116 | S1, 117 | S2, 118 | S3, 119 | S4, 120 | S5, 121 | S6, 122 | S7, 123 | S8, 124 | S9, 125 | S10, 126 | S11, 127 | // args 128 | A0, 129 | A1, 130 | A2, 131 | A3, 132 | A4, 133 | A5, 134 | A6, 135 | A7, 136 | } 137 | 138 | impl IntRegister { 139 | pub(crate) fn nth_param(n: usize) -> Option { 140 | [ 141 | Self::A0, 142 | Self::A1, 143 | Self::A2, 144 | Self::A3, 145 | Self::A4, 146 | Self::A5, 147 | Self::A6, 148 | Self::A7, 149 | ] 150 | .into_iter() 151 | .nth(n) 152 | } 153 | 154 | #[inline] 155 | pub(crate) fn to_reg(self) -> Register { 156 | Register::Int(self) 157 | } 158 | } 159 | 160 | impl From for IntRegister { 161 | fn from(src: Register) -> Self { 162 | match src { 163 | Register::Int(reg) => reg, 164 | Register::Float(_) => panic!("cannot convert float register into int register"), 165 | } 166 | } 167 | } 168 | 169 | impl Display for IntRegister { 170 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 171 | // lowercase debug display 172 | write!(f, "{}", format!("{self:?}").to_lowercase()) 173 | } 174 | } 175 | 176 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 177 | pub enum FloatRegister { 178 | // temporaries 179 | Ft0, 180 | Ft1, 181 | Ft2, 182 | Ft3, 183 | Ft4, 184 | Ft5, 185 | Ft6, 186 | Ft7, 187 | Ft8, 188 | Ft9, 189 | Ft10, 190 | Ft11, 191 | // saved 192 | Fs0, 193 | Fs1, 194 | Fs2, 195 | Fs3, 196 | Fs4, 197 | Fs5, 198 | Fs6, 199 | Fs7, 200 | Fs8, 201 | Fs9, 202 | Fs10, 203 | Fs11, 204 | // args 205 | Fa0, 206 | Fa1, 207 | Fa2, 208 | Fa3, 209 | Fa4, 210 | Fa5, 211 | Fa6, 212 | Fa7, 213 | } 214 | 215 | impl FloatRegister { 216 | pub(crate) fn nth_param(n: usize) -> Option { 217 | [ 218 | Self::Fa0, 219 | Self::Fa1, 220 | Self::Fa2, 221 | Self::Fa3, 222 | Self::Fa4, 223 | Self::Fa5, 224 | Self::Fa6, 225 | Self::Fa7, 226 | ] 227 | .into_iter() 228 | .nth(n) 229 | } 230 | 231 | #[inline] 232 | pub(crate) fn to_reg(self) -> Register { 233 | Register::Float(self) 234 | } 235 | } 236 | 237 | impl From for FloatRegister { 238 | fn from(src: Register) -> Self { 239 | match src { 240 | Register::Float(reg) => reg, 241 | Register::Int(_) => panic!("cannot convert int register into float register"), 242 | } 243 | } 244 | } 245 | 246 | impl Display for FloatRegister { 247 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 248 | // lowercase debug display 249 | write!(f, "{}", format!("{self:?}").to_lowercase()) 250 | } 251 | } 252 | 253 | #[cfg(test)] 254 | mod tests { 255 | use super::*; 256 | 257 | #[test] 258 | fn test_register_display() { 259 | let tests = [ 260 | (IntRegister::Zero, "zero"), 261 | (IntRegister::A0, "a0"), 262 | (IntRegister::Ra, "ra"), 263 | (IntRegister::Fp, "fp"), 264 | (IntRegister::T0, "t0"), 265 | (IntRegister::S6, "s6"), 266 | ]; 267 | 268 | for (reg, display) in tests { 269 | assert_eq!(reg.to_string(), display); 270 | } 271 | } 272 | 273 | #[test] 274 | fn test_float_register_display() { 275 | let tests = [ 276 | (FloatRegister::Ft0, "ft0"), 277 | (FloatRegister::Fs1, "fs1"), 278 | (FloatRegister::Fa2, "fa2"), 279 | (FloatRegister::Fs5, "fs5"), 280 | ]; 281 | 282 | for (reg, display) in tests { 283 | assert_eq!(reg.to_string(), display); 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /crates/rush-compiler-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | /*.wasm 2 | -------------------------------------------------------------------------------- /crates/rush-compiler-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-compiler-wasm" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | leb128 = "0.2.5" 13 | rush-analyzer = { path = "../rush-analyzer", version = "0.1.0" } 14 | -------------------------------------------------------------------------------- /crates/rush-compiler-wasm/README.md: -------------------------------------------------------------------------------- 1 | # WebAssembly Compiler Backend 2 | 3 | A single-target rush compiler backend which generates 4 | [Wasm](https://webassembly.org/) files without the need for external 5 | dependencies. 6 | 7 | ## Prerequisites 8 | 9 | Since the compiler itself requires no external dependencies, only a WebAssembly 10 | runtime is to be installed. Hence, program execution requires a 11 | [Wasm runtime](https://wasmer.io/). 12 | 13 | ### Runtime 14 | 15 | On Arch-Linux based systems, the following package can be installed to set up a 16 | working Wasm runtime. 17 | 18 | - `wasmer` for executing `.wasm` files 19 | 20 | ## Compiling and Running rush Code 21 | 22 | ### Compilation of rush Code 23 | 24 | - Prerequisite: A file ending in `.rush` which contains the program. 25 | - Execute following command as it will generate the `output.wasm` file from the 26 | source program. 27 | 28 | ```bash 29 | cargo run your-program.rush 30 | ``` 31 | 32 | ### Running Wasm Files 33 | 34 | - Since you have installed a Wasm runtime prior to reading this section, 35 | following command can be used to execute the `.wasm` file using this runtime. 36 | 37 | ```bash 38 | wasmer output.wasm 39 | ``` 40 | -------------------------------------------------------------------------------- /crates/rush-compiler-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use compiler::Compiler; 2 | use rush_analyzer::Diagnostic; 3 | 4 | mod compiler; 5 | mod corelib; 6 | mod instructions; 7 | mod types; 8 | mod utils; 9 | 10 | pub type CompileResult<'src> = Result<(Vec, Vec>), Vec>>; 11 | 12 | /// Compiles rush source code to a binary WebAssembly module. 13 | /// The `Ok(_)` variant also returns non-error diagnostics. 14 | /// The `Err(_)` variant returns a `Vec` which contains at least one error. 15 | pub fn compile<'src>(text: &'src str, path: &'src str) -> Result, String> { 16 | let (tree, diagnostics) = match rush_analyzer::analyze(text, path) { 17 | Ok(tree) => tree, 18 | Err(diagnostics) => return Ok(Err(diagnostics)), 19 | }; 20 | let bin = Compiler::new().compile(tree)?; 21 | Ok(Ok((bin, diagnostics))) 22 | } 23 | -------------------------------------------------------------------------------- /crates/rush-compiler-wasm/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, time::Instant}; 2 | 3 | fn main() { 4 | let start = Instant::now(); 5 | let path = env::args().nth(1).unwrap(); 6 | let code = fs::read_to_string(&path).unwrap(); 7 | let bytes = rush_compiler_wasm::compile(&code, &path) 8 | .unwrap() 9 | .unwrap() 10 | .0; 11 | fs::write("output.wasm", bytes).unwrap(); 12 | println!("{:?}", start.elapsed()); 13 | } 14 | -------------------------------------------------------------------------------- /crates/rush-compiler-wasm/src/types.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// i32 = 0x7F 4 | pub const I32: u8 = 0x7F; 5 | 6 | /// i64 = 0x7E 7 | pub const I64: u8 = 0x7E; 8 | 9 | /// f32 = 0x7D 10 | pub const F32: u8 = 0x7D; 11 | 12 | /// f64 = 0x7C 13 | pub const F64: u8 = 0x7C; 14 | 15 | /// v128 = 0x7B 16 | pub const V128: u8 = 0x7B; 17 | 18 | /// funcref = 0x70 19 | pub const FUNCREF: u8 = 0x70; 20 | 21 | /// externref = 0x6F 22 | pub const EXTERNREF: u8 = 0x6F; 23 | 24 | /// func = 0x60 25 | pub const FUNC: u8 = 0x60; 26 | 27 | /// void = 0x40 28 | pub const VOID: u8 = 0x40; 29 | -------------------------------------------------------------------------------- /crates/rush-compiler-wasm/src/utils.rs: -------------------------------------------------------------------------------- 1 | use rush_analyzer::Type; 2 | 3 | use crate::types; 4 | 5 | pub trait Leb128 { 6 | /// Convert the given number to a `Vec` in unsigned LEB128 encoding 7 | fn to_uleb128(&self) -> Vec { 8 | let mut buf = vec![]; 9 | self.write_uleb128(&mut buf); 10 | buf 11 | } 12 | 13 | /// Write the given number into a `Vec` in unsigned LEB128 encoding 14 | fn write_uleb128(&self, buf: &mut Vec); 15 | 16 | /// Convert the given number to a `Vec` in signed LEB128 encoding 17 | fn to_sleb128(&self) -> Vec { 18 | let mut buf = vec![]; 19 | self.write_sleb128(&mut buf); 20 | buf 21 | } 22 | 23 | /// Write the given number into a `Vec` in signed LEB128 encoding 24 | fn write_sleb128(&self, buf: &mut Vec); 25 | } 26 | 27 | impl Leb128 for u64 { 28 | fn write_uleb128(&self, buf: &mut Vec) { 29 | if *self < 128 { 30 | buf.push(*self as u8); 31 | return; 32 | } 33 | leb128::write::unsigned(buf, *self).expect("writing to a Vec should never fail"); 34 | } 35 | 36 | fn write_sleb128(&self, buf: &mut Vec) { 37 | if *self < 64 { 38 | buf.push(*self as u8); 39 | return; 40 | } 41 | leb128::write::signed(buf, *self as i64).expect("writing to a Vec should never fail"); 42 | } 43 | } 44 | 45 | macro_rules! leb128_impl { 46 | ($($type:ty),* $(,)?) => {$( 47 | impl Leb128 for $type { 48 | fn write_uleb128(&self, buf: &mut Vec) { 49 | (*self as u64).write_uleb128(buf); 50 | } 51 | 52 | fn write_sleb128(&self, buf: &mut Vec) { 53 | (*self as u64).write_sleb128(buf); 54 | } 55 | } 56 | )*}; 57 | } 58 | 59 | leb128_impl!(bool, i8, u8, i16, u16, i32, u32, i64, isize, usize); 60 | 61 | /// Convert a [`Type`] into its Wasm equivalent 62 | pub fn type_to_byte(type_: Type) -> Result, String> { 63 | Ok(match type_ { 64 | Type::Int(0) => Some(types::I64), 65 | Type::Float(0) => Some(types::F64), 66 | Type::Bool(0) => Some(types::I32), 67 | Type::Char(0) => Some(types::I32), 68 | Type::Unit | Type::Never => None, 69 | Type::Unknown => unreachable!("the analyzer guarantees one of the above to match"), 70 | _ => return Err("Pointers are not supported in the Wasm backend".to_string()), 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/.gitignore: -------------------------------------------------------------------------------- 1 | /output.* 2 | /output 3 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-compiler-x86-64" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [dependencies] 10 | rush-analyzer = { path = "../rush-analyzer", version = "0.1.2" } 11 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | gcc output.s corelib/libcore.a -nostdlib -o out 3 | ./out 4 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/corelib/.gitignore: -------------------------------------------------------------------------------- 1 | /*.a 2 | /*.o 3 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/corelib/Makefile: -------------------------------------------------------------------------------- 1 | libcore.a: src/corelib.s 2 | as --64 src/corelib.s -o corelib.o 3 | ! [ -f libcore.a ] || rm libcore.a 4 | ar -vq libcore.a corelib.o 5 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/corelib/src/corelib.s: -------------------------------------------------------------------------------- 1 | .intel_syntax 2 | 3 | .global __rush_internal_cast_int_to_char 4 | .global __rush_internal_cast_float_to_char 5 | .global __rush_internal_pow_int 6 | .global exit 7 | 8 | ############################### 9 | ########## CONSTANTS ########## 10 | ############################### 11 | 12 | .section .rodata 13 | 14 | float_0: 15 | .quad 0x0000000000000000 16 | 17 | float_127: 18 | .quad 0x405fc00000000000 19 | 20 | .section .text 21 | 22 | ########################### 23 | ########## UTILS ########## 24 | ########################### 25 | 26 | return_0: 27 | mov %rax, 0 28 | ret 29 | 30 | return_1: 31 | mov %rax, 1 32 | ret 33 | 34 | return_127: 35 | mov %rax, 127 36 | ret 37 | 38 | ############################### 39 | ########## FUNCTIONS ########## 40 | ############################### 41 | 42 | # fn __rush_internal_cast_int_to_char(input: int) -> char { 43 | __rush_internal_cast_int_to_char: 44 | # if input < 0 { return '\x00'; } 45 | cmp %rdi, 0 46 | jl return_0 47 | 48 | # if input > 0x7F { return '\x7F'; } 49 | cmp %rdi, 0x7F 50 | jg return_127 51 | 52 | # return input; 53 | mov %rax, %rdi 54 | ret 55 | # } 56 | 57 | # fn __rush_internal_cast_float_to_char(input: float) -> char { 58 | __rush_internal_cast_float_to_char: 59 | # if input < 0 { return '\x00'; } 60 | ucomisd %xmm0, qword ptr [%rip + float_0] 61 | jb return_0 62 | 63 | # if input > 0x7F { return '\x7F'; } 64 | ucomisd %xmm0, qword ptr [%rip + float_127] 65 | ja return_127 66 | 67 | # return input as int; 68 | cvttsd2si %rax, %xmm0 69 | ret 70 | # } 71 | 72 | # fn __rush_internal_pow_int(mut base: int, mut exponent: int) -> int { 73 | __rush_internal_pow_int: 74 | # if exponent < 0 { return 0; } 75 | cmp %rsi, 0 76 | jl return_0 77 | 78 | # if exponent == 0 { return 1; } 79 | cmp %rsi, 0 80 | je return_1 81 | 82 | # let mut accumulator = 1; 83 | mov %rax, 1 84 | 85 | # loop { 86 | loop: 87 | # if exponent <= 1 { break; } 88 | cmp %rsi, 1 89 | jle after_loop 90 | 91 | # if (exponent & 1) == 1 { accumulator *= base; } 92 | mov %rdx, %rsi 93 | and %rdx, 1 94 | cmp %rdx, 1 95 | jne after_if 96 | ## accumulator *= base 97 | imul %rax, %rdi 98 | after_if: 99 | 100 | # exponent >> 1; 101 | shr %rsi, 1 102 | 103 | # base *= base; 104 | imul %rdi, %rdi 105 | 106 | # continue; 107 | jmp loop 108 | after_loop: 109 | # } 110 | 111 | # accumulator *= base; 112 | imul %rax, %rdi 113 | 114 | # return accumulator; 115 | ret 116 | # } 117 | 118 | # fn exit(code: int) { 119 | exit: 120 | mov %rax, 60 # 60 = sys_exit 121 | syscall # exit code is already set in %rdi 122 | # } 123 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/src/condition.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | use rush_analyzer::{ 4 | ast::{AnalyzedExpression, AnalyzedPrefixExpr}, 5 | InfixOp, PrefixOp, Type, 6 | }; 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | pub enum Condition { 10 | Above, 11 | AboveOrEqual, 12 | Below, 13 | BelowOrEqual, 14 | 15 | Equal, 16 | NotEqual, 17 | 18 | Greater, 19 | GreaterOrEqual, 20 | Less, 21 | LessOrEqual, 22 | 23 | Parity, 24 | NotParity, 25 | } 26 | 27 | impl Condition { 28 | pub fn try_from_op(op: InfixOp, signed: bool) -> Option { 29 | match op { 30 | InfixOp::Eq => Some(Self::Equal), 31 | InfixOp::Neq => Some(Self::NotEqual), 32 | InfixOp::Lt if signed => Some(Self::Less), 33 | InfixOp::Gt if signed => Some(Self::Greater), 34 | InfixOp::Lte if signed => Some(Self::LessOrEqual), 35 | InfixOp::Gte if signed => Some(Self::GreaterOrEqual), 36 | InfixOp::Lt => Some(Self::Below), 37 | InfixOp::Gt => Some(Self::Above), 38 | InfixOp::Lte => Some(Self::BelowOrEqual), 39 | InfixOp::Gte => Some(Self::AboveOrEqual), 40 | _ => None, 41 | } 42 | } 43 | 44 | pub fn try_from_expr( 45 | mut expr: AnalyzedExpression<'_>, 46 | ) -> Result<(Self, AnalyzedExpression<'_>, AnalyzedExpression<'_>), AnalyzedExpression<'_>> 47 | { 48 | let mut negate = false; 49 | loop { 50 | match expr { 51 | AnalyzedExpression::Prefix(prefix_expr) if prefix_expr.op == PrefixOp::Not => { 52 | expr = prefix_expr.expr; 53 | negate ^= true; 54 | } 55 | AnalyzedExpression::Infix(infix_expr) => { 56 | return match Self::try_from_op( 57 | infix_expr.op, 58 | infix_expr.lhs.result_type() == Type::Int(0), 59 | ) { 60 | Some(cond) => Ok(( 61 | if negate { cond.negated() } else { cond }, 62 | infix_expr.lhs, 63 | infix_expr.rhs, 64 | )), 65 | None => Err(AnalyzedExpression::Infix(infix_expr)), 66 | }; 67 | } 68 | expr => { 69 | return Err(match negate { 70 | true => AnalyzedExpression::Prefix( 71 | AnalyzedPrefixExpr { 72 | result_type: Type::Bool(0), 73 | op: PrefixOp::Not, 74 | expr, 75 | } 76 | .into(), 77 | ), 78 | false => expr, 79 | }) 80 | } 81 | } 82 | } 83 | } 84 | 85 | pub fn negated(self) -> Self { 86 | match self { 87 | Self::Above => Self::BelowOrEqual, 88 | Self::AboveOrEqual => Self::Below, 89 | Self::Below => Self::AboveOrEqual, 90 | Self::BelowOrEqual => Self::Above, 91 | Self::Equal => Self::NotEqual, 92 | Self::NotEqual => Self::Equal, 93 | Self::Greater => Self::LessOrEqual, 94 | Self::GreaterOrEqual => Self::Less, 95 | Self::Less => Self::GreaterOrEqual, 96 | Self::LessOrEqual => Self::Greater, 97 | Self::Parity => Self::NotParity, 98 | Self::NotParity => Self::Parity, 99 | } 100 | } 101 | } 102 | 103 | impl Display for Condition { 104 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 105 | write!( 106 | f, 107 | "{}", 108 | match self { 109 | Condition::Above => "a", 110 | Condition::AboveOrEqual => "ae", 111 | Condition::Below => "b", 112 | Condition::BelowOrEqual => "be", 113 | Condition::Equal => "e", 114 | Condition::NotEqual => "ne", 115 | Condition::Greater => "g", 116 | Condition::GreaterOrEqual => "ge", 117 | Condition::Less => "l", 118 | Condition::LessOrEqual => "le", 119 | Condition::Parity => "p", 120 | Condition::NotParity => "np", 121 | } 122 | ) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod call; 2 | mod compiler; 3 | mod condition; 4 | mod infix; 5 | mod instruction; 6 | mod register; 7 | mod value; 8 | 9 | pub use compiler::Compiler; 10 | use rush_analyzer::Diagnostic; 11 | 12 | /// Compiles rush source code to x86_64 assembly in Intel syntax. 13 | /// The `Ok(_)` variant also returns non-error diagnostics. 14 | /// The `Err(_)` variant returns a `Vec` which contains at least one error. 15 | pub fn compile<'src>( 16 | text: &'src str, 17 | path: &'src str, 18 | comment_config: CommentConfig, 19 | ) -> Result<(String, Vec>), Vec>> { 20 | let (tree, diagnostics) = rush_analyzer::analyze(text, path)?; 21 | let asm = Compiler::new().compile(tree, comment_config); 22 | Ok((asm, diagnostics)) 23 | } 24 | 25 | pub enum CommentConfig { 26 | NoComments, 27 | Emit { line_width: usize }, 28 | } 29 | 30 | impl Default for CommentConfig { 31 | fn default() -> Self { 32 | CommentConfig::Emit { line_width: 65 } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, time::Instant}; 2 | 3 | use rush_compiler_x86_64::CommentConfig; 4 | 5 | fn main() { 6 | let args = env::args().collect::>(); 7 | 8 | let comment_config = match args.get(2).unwrap_or(&String::from("65")).parse::() { 9 | Ok(0) | Err(_) => CommentConfig::NoComments, 10 | Ok(line_width) => CommentConfig::Emit { line_width }, 11 | }; 12 | 13 | let start = Instant::now(); 14 | let path = &args[1]; 15 | let code = fs::read_to_string(path).unwrap(); 16 | let bytes = rush_compiler_x86_64::compile(&code, path, comment_config) 17 | .unwrap() 18 | .0; 19 | fs::write("output.s", bytes).unwrap(); 20 | println!("{:?}", start.elapsed()); 21 | } 22 | -------------------------------------------------------------------------------- /crates/rush-compiler-x86-64/src/value.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | fmt::{self, Display, Formatter}, 4 | rc::Rc, 5 | }; 6 | 7 | use rush_analyzer::Type; 8 | 9 | use crate::register::{FloatRegister, IntRegister, Register}; 10 | 11 | #[derive(Debug, Clone, PartialEq, Eq)] 12 | pub enum Value { 13 | Int(IntValue), 14 | Float(FloatValue), 15 | } 16 | 17 | impl Value { 18 | pub fn expect_int(self, msg: impl Display) -> IntValue { 19 | match self { 20 | Self::Int(int) => int, 21 | _ => panic!("called `expect_int` on non-int variant: {msg}"), 22 | } 23 | } 24 | } 25 | 26 | #[derive(Debug, Clone, PartialEq, Eq)] 27 | pub enum FloatValue { 28 | Register(FloatRegister), 29 | Ptr(Pointer), 30 | } 31 | 32 | #[derive(Debug, Clone, PartialEq, Eq)] 33 | pub enum IntValue { 34 | Register(IntRegister), 35 | Ptr(Pointer), 36 | Immediate(i64), 37 | } 38 | 39 | #[derive(Debug, Clone, PartialEq, Eq)] 40 | pub struct Pointer { 41 | pub size: Size, 42 | pub base: IntRegister, 43 | pub offset: Offset, 44 | } 45 | 46 | impl Pointer { 47 | pub fn new(size: Size, base: IntRegister, offset: Offset) -> Self { 48 | Self { size, base, offset } 49 | } 50 | 51 | pub fn in_size(self, size: Size) -> Self { 52 | Self { size, ..self } 53 | } 54 | } 55 | 56 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 57 | pub enum Size { 58 | Byte = 1, 59 | Word = 2, 60 | Dword = 4, 61 | Qword = 8, 62 | Oword = 16, 63 | } 64 | 65 | impl Size { 66 | pub fn byte_count(&self) -> i64 { 67 | *self as i64 68 | } 69 | 70 | pub fn mask(&self) -> i64 { 71 | match self { 72 | Size::Byte => 0xff, 73 | Size::Word => 0xffff, 74 | Size::Dword => 0xffff_ffff, 75 | Size::Qword => -1, 76 | Size::Oword => panic!("oword mask too large for 64 bits"), 77 | } 78 | } 79 | } 80 | 81 | impl TryFrom for Size { 82 | type Error = (); 83 | 84 | fn try_from(value: Type) -> Result { 85 | match value { 86 | Type::Int(0) | Type::Float(0) => Ok(Size::Qword), 87 | Type::Bool(0) | Type::Char(0) => Ok(Size::Byte), 88 | // pointers are 64-bit on x86_64 89 | Type::Int(_) | Type::Float(_) | Type::Bool(_) | Type::Char(_) => Ok(Size::Qword), 90 | Type::Unit | Type::Never => Err(()), 91 | Type::Unknown => unreachable!("the analyzer guarantees one of the above to match"), 92 | } 93 | } 94 | } 95 | 96 | #[derive(Debug, Clone, PartialEq, Eq)] 97 | pub enum Offset { 98 | Immediate(i64), 99 | Label(Rc), 100 | } 101 | 102 | ///////////////////////////////////////////////// 103 | 104 | impl Display for Value { 105 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 106 | match self { 107 | Value::Int(int) => write!(f, "{int}"), 108 | Value::Float(float) => write!(f, "{float}"), 109 | } 110 | } 111 | } 112 | 113 | impl Display for FloatValue { 114 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 115 | match self { 116 | FloatValue::Register(reg) => write!(f, "{reg}"), 117 | FloatValue::Ptr(ptr) => write!(f, "{ptr}"), 118 | } 119 | } 120 | } 121 | 122 | impl Display for IntValue { 123 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 124 | match self { 125 | IntValue::Register(reg) => write!(f, "{reg}"), 126 | IntValue::Ptr(ptr) => write!(f, "{ptr}"), 127 | IntValue::Immediate(num) => write!(f, "{num}"), 128 | } 129 | } 130 | } 131 | 132 | impl Display for Pointer { 133 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 134 | let Pointer { size, base, offset } = self; 135 | write!(f, "{size} ptr [{base}{offset}]") 136 | } 137 | } 138 | 139 | impl Display for Size { 140 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 141 | write!( 142 | f, 143 | "{}", 144 | match f.alternate() { 145 | true => match self { 146 | Size::Byte => "byte", 147 | Size::Word => "short", 148 | Size::Dword => "long", 149 | Size::Qword => "quad", 150 | Size::Oword => "octa", 151 | }, 152 | false => match self { 153 | Size::Byte => "byte", 154 | Size::Word => "word", 155 | Size::Dword => "dword", 156 | Size::Qword => "qword", 157 | Size::Oword => "oword", 158 | }, 159 | } 160 | ) 161 | } 162 | } 163 | 164 | impl Display for Offset { 165 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 166 | match self { 167 | Offset::Immediate(num) => match num.cmp(&0) { 168 | Ordering::Less => write!(f, "{num}"), 169 | Ordering::Equal => Ok(()), 170 | Ordering::Greater => write!(f, "+{num}"), 171 | }, 172 | Offset::Label(label) => write!(f, "+{label}"), 173 | } 174 | } 175 | } 176 | 177 | ///////////////////////////////////////////////// 178 | 179 | impl From for Value { 180 | fn from(reg: Register) -> Self { 181 | match reg { 182 | Register::Int(reg) => Self::Int(IntValue::Register(reg)), 183 | Register::Float(reg) => Self::Float(FloatValue::Register(reg)), 184 | } 185 | } 186 | } 187 | 188 | impl From for FloatValue { 189 | fn from(reg: FloatRegister) -> Self { 190 | Self::Register(reg) 191 | } 192 | } 193 | 194 | impl From for FloatValue { 195 | fn from(ptr: Pointer) -> Self { 196 | Self::Ptr(ptr) 197 | } 198 | } 199 | 200 | impl From for IntValue { 201 | fn from(reg: IntRegister) -> Self { 202 | Self::Register(reg) 203 | } 204 | } 205 | 206 | impl From for IntValue { 207 | fn from(ptr: Pointer) -> Self { 208 | Self::Ptr(ptr) 209 | } 210 | } 211 | 212 | impl From for IntValue { 213 | fn from(num: i64) -> Self { 214 | Self::Immediate(num) 215 | } 216 | } 217 | 218 | impl From for Offset { 219 | fn from(num: i64) -> Self { 220 | Self::Immediate(num) 221 | } 222 | } 223 | 224 | impl From> for Offset { 225 | fn from(label: Rc) -> Self { 226 | Self::Label(label) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /crates/rush-interpreter-tree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-interpreter-tree" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | rush-analyzer = { path = "../rush-analyzer", version = "0.1.0" } 13 | -------------------------------------------------------------------------------- /crates/rush-interpreter-tree/README.md: -------------------------------------------------------------------------------- 1 | # Tree-walking Interpreter Backend 2 | 3 | A rush interpreter which uses a tree-traversing algorithm for program execution. 4 | 5 | ## Running rush Code 6 | 7 | - Prerequisite: A file ending in `.rush` which contains the program. 8 | - Execute the following command in order to run the program. 9 | 10 | ```bash 11 | cargo run your-file.rush 12 | ``` 13 | -------------------------------------------------------------------------------- /crates/rush-interpreter-tree/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod interpreter; 2 | mod ops; 3 | mod value; 4 | 5 | use std::fmt::Debug; 6 | 7 | pub use interpreter::Interpreter; 8 | use rush_analyzer::Diagnostic; 9 | 10 | /// Interprets rush source code by walking the analyzed tree. 11 | /// The `Ok(_)` variant returns the exit code and non-error diagnostics. 12 | /// The `Err(_)` variant returns a [`RunError`]. 13 | pub fn run<'src>( 14 | text: &'src str, 15 | path: &'src str, 16 | ) -> Result<(i64, Vec>), RunError<'src>> { 17 | let (tree, diagnostics) = rush_analyzer::analyze(text, path)?; 18 | let code = Interpreter::new().run(tree)?; 19 | Ok((code, diagnostics)) 20 | } 21 | 22 | pub enum RunError<'src> { 23 | Analyzer(Vec>), 24 | Runtime(interpreter::Error), 25 | } 26 | 27 | impl<'src> From>> for RunError<'src> { 28 | fn from(diagnostics: Vec>) -> Self { 29 | Self::Analyzer(diagnostics) 30 | } 31 | } 32 | 33 | impl From for RunError<'_> { 34 | fn from(err: interpreter::Error) -> Self { 35 | Self::Runtime(err) 36 | } 37 | } 38 | 39 | impl Debug for RunError<'_> { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | match self { 42 | RunError::Analyzer(diagnostics) => write!(f, "{diagnostics:?}"), 43 | RunError::Runtime(err) => write!(f, "{err:?}"), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/rush-interpreter-tree/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, process, time::Instant}; 2 | 3 | fn main() { 4 | let start = Instant::now(); 5 | let path = env::args().nth(1).unwrap(); 6 | let code = fs::read_to_string(&path).unwrap(); 7 | let exit_code = rush_interpreter_tree::run(&code, &path).unwrap().0; 8 | println!("Program exited with code {exit_code}"); 9 | println!("{:?}", start.elapsed()); 10 | process::exit(exit_code as i32); 11 | } 12 | -------------------------------------------------------------------------------- /crates/rush-interpreter-tree/src/ops.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, 4 | }; 5 | 6 | use crate::value::Value; 7 | 8 | impl Not for Value { 9 | type Output = Self; 10 | 11 | fn not(self) -> Self::Output { 12 | match self { 13 | Value::Int(num) => (!num).into(), 14 | Value::Bool(bool) => (!bool).into(), 15 | _ => unreachable!("the analyzer guarantees one of the above to match"), 16 | } 17 | } 18 | } 19 | 20 | impl Neg for Value { 21 | type Output = Self; 22 | 23 | fn neg(self) -> Self::Output { 24 | match self { 25 | Value::Int(num) => (-num).into(), 26 | Value::Float(num) => (-num).into(), 27 | _ => unreachable!("the analyzer guarantees one of the above to match"), 28 | } 29 | } 30 | } 31 | 32 | impl Add for Value { 33 | type Output = Self; 34 | 35 | fn add(self, rhs: Self) -> Self::Output { 36 | match (self, rhs) { 37 | (Value::Int(left), Value::Int(right)) => left.wrapping_add(right).into(), 38 | (Value::Float(left), Value::Float(right)) => (left + right).into(), 39 | (Value::Char(left), Value::Char(right)) => (left.wrapping_add(right) & 0x7f).into(), 40 | _ => unreachable!("the analyzer guarantees one of the above to match"), 41 | } 42 | } 43 | } 44 | 45 | impl Sub for Value { 46 | type Output = Self; 47 | 48 | fn sub(self, rhs: Self) -> Self::Output { 49 | match (self, rhs) { 50 | (Value::Int(left), Value::Int(right)) => left.wrapping_sub(right).into(), 51 | (Value::Float(left), Value::Float(right)) => (left - right).into(), 52 | (Value::Char(left), Value::Char(right)) => (left.wrapping_sub(right) & 0x7f).into(), 53 | _ => unreachable!("the analyzer guarantees one of the above to match"), 54 | } 55 | } 56 | } 57 | 58 | impl Mul for Value { 59 | type Output = Self; 60 | 61 | fn mul(self, rhs: Self) -> Self::Output { 62 | match (self, rhs) { 63 | (Value::Int(left), Value::Int(right)) => left.wrapping_mul(right).into(), 64 | (Value::Float(left), Value::Float(right)) => (left * right).into(), 65 | _ => unreachable!("the analyzer guarantees one of the above to match"), 66 | } 67 | } 68 | } 69 | 70 | impl Div for Value { 71 | type Output = Result; 72 | 73 | fn div(self, rhs: Self) -> Self::Output { 74 | match (self, rhs) { 75 | (_, Value::Int(0)) => Err("division by zero"), 76 | (Value::Int(left), Value::Int(right)) => Ok(left.wrapping_div(right).into()), 77 | (Value::Float(left), Value::Float(right)) => Ok((left / right).into()), 78 | _ => unreachable!("the analyzer guarantees one of the above to match"), 79 | } 80 | } 81 | } 82 | 83 | impl Rem for Value { 84 | type Output = Result; 85 | 86 | fn rem(self, rhs: Self) -> Self::Output { 87 | match (self, rhs) { 88 | (_, Value::Int(0)) => Err("division by zero"), 89 | (Value::Int(left), Value::Int(right)) => Ok(left.wrapping_rem(right).into()), 90 | _ => unreachable!("the analyzer guarantees one of the above to match"), 91 | } 92 | } 93 | } 94 | 95 | impl Value { 96 | pub fn pow(self, exp: Self) -> Self { 97 | match (self, exp) { 98 | (Value::Int(_), Value::Int(exp)) if exp < 0 => 0_i64.into(), 99 | (Value::Int(base), Value::Int(exp)) => base.wrapping_pow(exp as u32).into(), 100 | _ => unreachable!("the analyzer guarantees one of the above to match"), 101 | } 102 | } 103 | } 104 | 105 | impl PartialOrd for Value { 106 | fn partial_cmp(&self, other: &Self) -> Option { 107 | match (self, other) { 108 | (Value::Int(left), Value::Int(right)) => left.partial_cmp(right), 109 | (Value::Float(left), Value::Float(right)) => left.partial_cmp(right), 110 | (Value::Char(left), Value::Char(right)) => left.partial_cmp(right), 111 | _ => unreachable!("the analyzer guarantees one of the above to match"), 112 | } 113 | } 114 | } 115 | 116 | impl Shl for Value { 117 | type Output = Result; 118 | 119 | fn shl(self, rhs: Self) -> Self::Output { 120 | match (self, rhs) { 121 | (Value::Int(_), Value::Int(right)) if !(0..=63).contains(&right) => Err(format!( 122 | "tried to shift left by {right}, which is outside `0..=63`" 123 | )), 124 | (Value::Int(left), Value::Int(right)) => Ok(left.shl(right).into()), 125 | _ => unreachable!("the analyzer guarantees one of the above to match"), 126 | } 127 | } 128 | } 129 | 130 | impl Shr for Value { 131 | type Output = Result; 132 | 133 | fn shr(self, rhs: Self) -> Self::Output { 134 | match (self, rhs) { 135 | (Value::Int(_), Value::Int(right)) if !(0..=63).contains(&right) => Err(format!( 136 | "tried to shift right by {right}, which is outside `0..=63`" 137 | )), 138 | (Value::Int(left), Value::Int(right)) => Ok(left.shr(right).into()), 139 | _ => unreachable!("the analyzer guarantees one of the above to match"), 140 | } 141 | } 142 | } 143 | 144 | impl BitOr for Value { 145 | type Output = Self; 146 | 147 | fn bitor(self, rhs: Self) -> Self::Output { 148 | match (self, rhs) { 149 | (Value::Int(left), Value::Int(right)) => (left | right).into(), 150 | (Value::Bool(left), Value::Bool(right)) => (left | right).into(), 151 | _ => unreachable!("the analyzer guarantees one of the above to match"), 152 | } 153 | } 154 | } 155 | 156 | impl BitAnd for Value { 157 | type Output = Self; 158 | 159 | fn bitand(self, rhs: Self) -> Self::Output { 160 | match (self, rhs) { 161 | (Value::Int(left), Value::Int(right)) => (left & right).into(), 162 | (Value::Bool(left), Value::Bool(right)) => (left & right).into(), 163 | _ => unreachable!("the analyzer guarantees one of the above to match"), 164 | } 165 | } 166 | } 167 | 168 | impl BitXor for Value { 169 | type Output = Self; 170 | 171 | fn bitxor(self, rhs: Self) -> Self::Output { 172 | match (self, rhs) { 173 | (Value::Int(left), Value::Int(right)) => (left ^ right).into(), 174 | (Value::Bool(left), Value::Bool(right)) => (left ^ right).into(), 175 | _ => unreachable!("the analyzer guarantees one of the above to match"), 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /crates/rush-interpreter-tree/src/value.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::interpreter; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub enum Value { 7 | Int(i64), 8 | Float(f64), 9 | Char(u8), 10 | Bool(bool), 11 | Unit, 12 | Ptr(Rc>), 13 | } 14 | 15 | impl Value { 16 | pub fn wrapped(self) -> Rc> { 17 | Rc::new(RefCell::new(self)) 18 | } 19 | } 20 | 21 | #[derive(Clone, Debug)] 22 | pub enum InterruptKind { 23 | Return(Value), 24 | Break, 25 | Continue, 26 | Error(interpreter::Error), 27 | Exit(i64), 28 | } 29 | 30 | impl InterruptKind { 31 | pub fn into_value(self) -> Result { 32 | match self { 33 | Self::Return(val) => Ok(val), 34 | kind @ (Self::Error(_) | Self::Exit(_)) => Err(kind), 35 | _ => Ok(Value::Unit), 36 | } 37 | } 38 | } 39 | 40 | impl From for InterruptKind 41 | where 42 | S: Into, 43 | { 44 | fn from(msg: S) -> Self { 45 | Self::Error(msg.into()) 46 | } 47 | } 48 | 49 | ////////////////////////////////// 50 | 51 | macro_rules! from_impl { 52 | ($variant:ident, $type:ty) => { 53 | impl From<$type> for Value { 54 | fn from(val: $type) -> Self { 55 | Self::$variant(val) 56 | } 57 | } 58 | 59 | impl From<&$type> for Value { 60 | fn from(val: &$type) -> Self { 61 | Self::from(*val) 62 | } 63 | } 64 | 65 | impl From<&mut $type> for Value { 66 | fn from(val: &mut $type) -> Self { 67 | Self::from(*val) 68 | } 69 | } 70 | }; 71 | } 72 | 73 | from_impl!(Int, i64); 74 | from_impl!(Float, f64); 75 | from_impl!(Char, u8); 76 | from_impl!(Bool, bool); 77 | 78 | ////////////////////////////////// 79 | 80 | macro_rules! unwrap_impl { 81 | ($variant:ident, $res:ty, $name:ident) => { 82 | pub fn $name(self) -> $res { 83 | match self { 84 | Self::$variant(val) => val, 85 | other => panic!(concat!("called ", stringify!($name), " on `{:?}`"), other), 86 | } 87 | } 88 | }; 89 | } 90 | 91 | impl Value { 92 | unwrap_impl!(Int, i64, unwrap_int); 93 | unwrap_impl!(Bool, bool, unwrap_bool); 94 | unwrap_impl!(Ptr, Rc>, unwrap_ptr); 95 | } 96 | -------------------------------------------------------------------------------- /crates/rush-interpreter-vm/.gitignore: -------------------------------------------------------------------------------- 1 | output.s 2 | -------------------------------------------------------------------------------- /crates/rush-interpreter-vm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-interpreter-vm" 3 | version = "0.1.2" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "A rush interpreter leveraging a micro-compiler alongside a VM for its runtime" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | rush-analyzer = { version = "0.1.0", path = "../rush-analyzer" } 14 | -------------------------------------------------------------------------------- /crates/rush-interpreter-vm/Makefile: -------------------------------------------------------------------------------- 1 | src := ../../samples/test.rush 2 | out := test 3 | 4 | run: $(src) src 5 | cargo r $(src) 6 | -------------------------------------------------------------------------------- /crates/rush-interpreter-vm/README.md: -------------------------------------------------------------------------------- 1 | # VM Interpreter Backend 2 | 3 | A rush interpreter leveraging a micro-compiler alongside a VM for its runtime. 4 | 5 | ## Running rush Code 6 | 7 | - Prerequisite: A file ending in `.rush` which contains the program. 8 | - Execute the following command in order to run the program. 9 | 10 | ```bash 11 | cargo run your-file.rush 12 | ``` 13 | 14 | ### Debugging Output 15 | 16 | - When debugging output is desired, following command is to be used. 17 | - The `1` at the end specifies how many instructions per second are executed by 18 | the VM. 19 | - In this case, the VM will operate at its minimum speed. 20 | - Hence, a larger value will result in faster program execution 21 | 22 | ```bash 23 | cargo run your-file.rush debug 1 24 | ``` 25 | -------------------------------------------------------------------------------- /crates/rush-interpreter-vm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | 3 | mod compiler; 4 | mod instruction; 5 | mod value; 6 | mod vm; 7 | 8 | pub use compiler::Compiler; 9 | pub use instruction::Instruction; 10 | use instruction::Program; 11 | use rush_analyzer::ast::AnalyzedProgram; 12 | pub use rush_analyzer::Diagnostic; 13 | pub use value::Value; 14 | pub use vm::{RuntimeError, RuntimeErrorKind, Vm}; 15 | 16 | /// Compiles rush source code to VM instructions. 17 | /// The `Ok(_)` variant is a [`CompilationResult`] which also includes any non-error diagnostics. 18 | /// The `Err(_)` variant returns a `Vec` which contains at least one error. 19 | pub fn compile(ast: AnalyzedProgram<'_>) -> Program { 20 | Compiler::new().compile(ast) 21 | } 22 | 23 | /// Executes the given program using the VM. 24 | /// The `Ok(_)` variant also returns non-error diagnostics. 25 | /// The `Err(_)` variant returns a `Vec` which contains at least one error. 26 | pub fn run(ast: AnalyzedProgram<'_>) -> Result { 27 | let program = Compiler::new().compile(ast); 28 | let mut vm: Vm = Vm::new(); 29 | let exit_code = vm.run(program)?; 30 | Ok(exit_code) 31 | } 32 | 33 | /// Executes the given program using the VM on debug mode. 34 | /// The `Ok(_)` variant also returns non-error diagnostics. 35 | /// The `Err(_)` variant returns a `Vec` which contains at least one error. 36 | pub fn debug_run( 37 | ast: AnalyzedProgram<'_>, 38 | clock_hz: u64, 39 | ) -> Result { 40 | let program = Compiler::new().compile(ast); 41 | let mut vm: Vm = Vm::new(); 42 | let exit_code = vm.debug_run(program, clock_hz)?; 43 | Ok(exit_code) 44 | } 45 | -------------------------------------------------------------------------------- /crates/rush-interpreter-vm/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, process, time::Instant}; 2 | 3 | use rush_interpreter_vm::Vm; 4 | 5 | const MEM_SIZE: usize = 500; 6 | 7 | fn main() { 8 | let path = env::args().nth(1).unwrap(); 9 | let debug = matches!(env::args().nth(2), Some(arg) if arg == "debug"); 10 | 11 | let clock = if debug { 12 | env::args().nth(3).unwrap().parse().unwrap() 13 | } else { 14 | 0 15 | }; 16 | 17 | let code = fs::read_to_string(&path).unwrap(); 18 | 19 | let mut start = Instant::now(); 20 | 21 | let (ast, _) = rush_analyzer::analyze(&code, &path).unwrap(); 22 | 23 | let program = rush_interpreter_vm::compile(ast); 24 | 25 | if debug { 26 | println!("{program}"); 27 | } 28 | 29 | println!("compilation: {:?}", start.elapsed()); 30 | 31 | start = Instant::now(); 32 | 33 | let mut vm: Vm = Vm::new(); 34 | 35 | let code = match debug { 36 | true => vm.debug_run(program, clock), 37 | false => vm.run(program), 38 | } 39 | .unwrap_or_else(|err| { 40 | eprintln!("\x1b[1;31mVM crashed\x1b[1;0m: {} -> {}", err.kind, err.msg); 41 | process::exit(1); 42 | }); 43 | println!("Program exited with code {code}"); 44 | println!("execution: {:?}", start.elapsed()); 45 | process::exit(code as i32); 46 | } 47 | -------------------------------------------------------------------------------- /crates/rush-ls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-ls" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | rush-analyzer = { path = "../rush-analyzer/", version = "0.1.0" } 13 | tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "io-std"] } 14 | tower-lsp = "0.17.0" 15 | -------------------------------------------------------------------------------- /crates/rush-ls/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rush_analyzer::DiagnosticLevel; 2 | use tower_lsp::jsonrpc::Result; 3 | use tower_lsp::lsp_types::*; 4 | use tower_lsp::{Client, LanguageServer, LspService, Server}; 5 | 6 | struct Backend { 7 | client: Client, 8 | } 9 | 10 | #[tower_lsp::async_trait] 11 | impl LanguageServer for Backend { 12 | async fn initialize(&self, _: InitializeParams) -> Result { 13 | Ok(InitializeResult { 14 | capabilities: ServerCapabilities { 15 | text_document_sync: Some(TextDocumentSyncCapability::Kind( 16 | TextDocumentSyncKind::FULL, 17 | )), 18 | ..ServerCapabilities::default() 19 | }, 20 | server_info: Some(ServerInfo { 21 | name: env!("CARGO_PKG_NAME").to_string(), 22 | version: Some(env!("CARGO_PKG_VERSION").to_string()), 23 | }), 24 | }) 25 | } 26 | 27 | async fn shutdown(&self) -> Result<()> { 28 | Ok(()) 29 | } 30 | 31 | async fn did_open(&self, params: DidOpenTextDocumentParams) { 32 | self.create_diagnostics(TextDocumentItem { 33 | language_id: "rush".to_string(), 34 | uri: params.text_document.uri, 35 | text: params.text_document.text, 36 | version: params.text_document.version, 37 | }) 38 | .await 39 | } 40 | 41 | async fn did_change(&self, mut params: DidChangeTextDocumentParams) { 42 | self.create_diagnostics(TextDocumentItem { 43 | language_id: "rush".to_string(), 44 | uri: params.text_document.uri, 45 | version: params.text_document.version, 46 | text: std::mem::take(&mut params.content_changes[0].text), 47 | }) 48 | .await 49 | } 50 | } 51 | 52 | impl Backend { 53 | async fn create_diagnostics(&self, params: TextDocumentItem) { 54 | // obtain the diagnostics from the analyzer 55 | let raw_diagnostics = match rush_analyzer::analyze(¶ms.text, params.uri.as_str()) { 56 | Ok((_, diagnostics)) => diagnostics, 57 | Err(diagnostics) => diagnostics, 58 | }; 59 | // transform the diagnostics into the LSP form 60 | let diagnostics = raw_diagnostics 61 | .iter() 62 | .map(|diagnostic| { 63 | Diagnostic::new( 64 | Range::new( 65 | Position::new( 66 | (diagnostic.span.start.line - 1) as u32, 67 | (diagnostic.span.start.column - 1) as u32, 68 | ), 69 | Position::new( 70 | (diagnostic.span.end.line - 1) as u32, 71 | (diagnostic.span.end.column - 1) as u32, 72 | ), 73 | ), 74 | Some(match diagnostic.level { 75 | DiagnosticLevel::Hint => DiagnosticSeverity::HINT, 76 | DiagnosticLevel::Info => DiagnosticSeverity::INFORMATION, 77 | DiagnosticLevel::Warning => DiagnosticSeverity::WARNING, 78 | DiagnosticLevel::Error(_) => DiagnosticSeverity::ERROR, 79 | }), 80 | None, 81 | Some("rush-analyzer".to_string()), 82 | format!( 83 | "{}{}", 84 | if let DiagnosticLevel::Error(kind) = &diagnostic.level { 85 | format!("{}: ", kind) 86 | } else { 87 | "".to_string() 88 | }, 89 | diagnostic.message 90 | ), 91 | None, 92 | None, 93 | ) 94 | }) 95 | .collect(); 96 | 97 | self.client 98 | .publish_diagnostics(params.uri.clone(), diagnostics, Some(params.version)) 99 | .await; 100 | } 101 | } 102 | 103 | pub async fn start_service() { 104 | let stdin = tokio::io::stdin(); 105 | let stdout = tokio::io::stdout(); 106 | 107 | let (service, socket) = LspService::new(|client| Backend { client }); 108 | Server::new(stdin, stdout, socket).serve(service).await; 109 | } 110 | -------------------------------------------------------------------------------- /crates/rush-ls/src/main.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main] 2 | async fn main() { 3 | rush_ls::start_service().await 4 | } 5 | -------------------------------------------------------------------------------- /crates/rush-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-parser" 3 | version = "0.1.2" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "A lexer and parser for the rush programming language" 9 | 10 | [dependencies] 11 | either = "1.8.0" 12 | -------------------------------------------------------------------------------- /crates/rush-parser/README.md: -------------------------------------------------------------------------------- 1 | # rush Parser 2 | 3 | A crate which performs syntactic analysis on rush programs whilst creating an 4 | [AST](https://en.wikipedia.org//wiki/Abstract_syntax_tree). 5 | 6 | This crate implements a [lexer](https://en.wikipedia.org/wiki/Lexical_analysis) 7 | and a [parser](https://en.wikipedia.org/wiki/Parsing) for the rush programming 8 | language. This parser is a 9 | [recursive descent parser](https://en.wikipedia.org/wiki/Recursive_descent_parser), 10 | meaning it uses a top-down approach. For infix expressions, an 11 | [operator precedence algorithm](https://en.wikipedia.org/wiki/Operator-precedence_parser) 12 | is used. The AST generated by this crate is often validated and annotated by the 13 | [`rush-analyzer`](https://github.com/rush-rs/rush/tree/main/crates/rush-analyzer) 14 | before being fed into a compiler. 15 | -------------------------------------------------------------------------------- /crates/rush-parser/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::Span; 2 | 3 | pub type Result<'src, T> = std::result::Result>>; 4 | 5 | #[derive(Debug, PartialEq, Eq)] 6 | pub struct Error<'src> { 7 | pub message: String, 8 | pub span: Span<'src>, 9 | pub source: &'src str, 10 | } 11 | 12 | impl<'src> Error<'src> { 13 | pub fn new(message: String, span: Span<'src>, source: &'src str) -> Self { 14 | Self { 15 | message, 16 | span, 17 | source, 18 | } 19 | } 20 | 21 | pub fn new_boxed(message: String, span: Span<'src>, source: &'src str) -> Box { 22 | Self::new(message, span, source).into() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/rush-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! span { 3 | ($start:literal .. $end:literal) => { 4 | $crate::Location { 5 | line: 1, 6 | column: $start + 1, 7 | char_idx: $start, 8 | byte_idx: $start, 9 | path: "", 10 | } 11 | .until($crate::Location { 12 | line: 1, 13 | column: $end + 1, 14 | char_idx: $end, 15 | byte_idx: $end, 16 | path: "", 17 | }) 18 | }; 19 | } 20 | 21 | #[macro_use] 22 | mod macros; 23 | 24 | pub mod ast; 25 | mod error; 26 | mod lexer; 27 | mod parser; 28 | mod span; 29 | mod token; 30 | 31 | pub use error::*; 32 | pub use lexer::*; 33 | pub use parser::*; 34 | pub use span::*; 35 | pub use token::*; 36 | -------------------------------------------------------------------------------- /crates/rush-parser/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! tree { 3 | ((None)) => { None }; 4 | ((Some($($node:tt)*))) => { Some(tree!(($($node)*))) }; 5 | 6 | (( 7 | Program @ $start:literal .. $end:literal, 8 | functions: [$($func:tt),* $(,)?], 9 | globals: [$($global:tt),* $(,)?] $(,)? 10 | )) => { 11 | Program { 12 | span: span!($start..$end), 13 | functions: vec![$(tree!($func)),*], 14 | globals: vec![$(tree!($global)),*], 15 | } 16 | }; 17 | (( 18 | FunctionDefinition @ $start:literal .. $end:literal, 19 | name: $ident:tt, 20 | params @ $params_start:literal .. $params_end:literal: [$($param:tt),* $(,)?], 21 | return_type: $return_type:tt, 22 | block: $block:tt $(,)? 23 | )) => { 24 | FunctionDefinition { 25 | span: span!($start..$end), 26 | name: tree!($ident), 27 | params: tree!((vec![$(tree!($param)),*], @ $params_start..$params_end)), 28 | return_type: tree!($return_type), 29 | block: tree!($block), 30 | } 31 | }; 32 | (( 33 | Parameter, 34 | mutable: $mutable:expr, 35 | name: $ident:tt, 36 | type: $type:tt $(,)? 37 | )) => { 38 | Parameter { 39 | mutable: $mutable, 40 | name: tree!($ident), 41 | type_: tree!($type), 42 | } 43 | }; 44 | 45 | (( 46 | Let @ $start:literal .. $end:literal, 47 | mutable: $mut:literal, 48 | name: $ident:tt, 49 | type: $type:tt, 50 | expr: $expr:tt $(,)? 51 | )) => { 52 | LetStmt { 53 | span: span!($start..$end), 54 | mutable: $mut, 55 | name: tree!($ident), 56 | type_: tree!($type), 57 | expr: tree!($expr), 58 | } 59 | }; 60 | ((LetStmt $($rest:tt)*)) => { 61 | Statement::Let(tree!((Let $($rest)*))) 62 | }; 63 | ((ReturnStmt @ $start:literal .. $end:literal, $expr:tt $(,)?)) => { 64 | Statement::Return(ReturnStmt { 65 | span: span!($start..$end), 66 | expr: tree!($expr), 67 | }) 68 | }; 69 | ((ExprStmt @ $start:literal .. $end:literal, $expr:tt $(,)?)) => { 70 | Statement::Expr(ExprStmt { 71 | span: span!($start..$end), 72 | expr: tree!($expr), 73 | }) 74 | }; 75 | 76 | (( 77 | Block @ $start:literal .. $end:literal, 78 | stmts: [$($stmt:tt),* $(,)?], 79 | expr: $expr:tt $(,)? 80 | )) => { 81 | Block { 82 | span: span!($start..$end), 83 | stmts: vec![$(tree!($stmt)),*], 84 | expr: tree!($expr), 85 | } 86 | }; 87 | ((BlockExpr $($rest:tt)*)) => { 88 | Expression::Block(tree!((Block $($rest)*)).into()) 89 | }; 90 | (( 91 | IfExpr @ $start:literal .. $end:literal, 92 | cond: $cond:tt, 93 | then_block: $then_block:tt, 94 | else_block: $else_block:tt $(,)? 95 | )) => { 96 | Expression::If(IfExpr { 97 | span: span!($start..$end), 98 | cond: tree!($cond), 99 | then_block: tree!($then_block), 100 | else_block: tree!($else_block), 101 | }.into()) 102 | }; 103 | ((Int $($rest:tt)*)) => { Expression::Int(tree!(($($rest)*))) }; 104 | ((Float $($rest:tt)*)) => { Expression::Float(tree!(($($rest)*))) }; 105 | ((Bool $($rest:tt)*)) => { Expression::Bool(tree!(($($rest)*))) }; 106 | ((Char $($rest:tt)*)) => { Expression::Char(tree!(($($rest)*))) }; 107 | ((Ident $($rest:tt)*)) => { Expression::Ident(tree!(($($rest)*))) }; 108 | ((Grouped @ $start:literal .. $end:literal, $expr:tt)) => { 109 | Expression::Grouped(Spanned { 110 | span: span!($start..$end), 111 | inner: tree!($expr).into(), 112 | }) 113 | }; 114 | (( 115 | PrefixExpr @ $start:literal .. $end:literal, 116 | op: $op:expr, 117 | expr: $expr:tt $(,)? 118 | )) => { 119 | Expression::Prefix(PrefixExpr { 120 | span: span!($start..$end), 121 | op: $op, 122 | expr: tree!($expr), 123 | }.into()) 124 | }; 125 | (( 126 | InfixExpr @ $start:literal .. $end:literal, 127 | lhs: $lhs:tt, 128 | op: $op:expr, 129 | rhs: $rhs:tt $(,)? 130 | )) => { 131 | Expression::Infix(InfixExpr { 132 | span: span!($start..$end), 133 | lhs: tree!($lhs), 134 | op: $op, 135 | rhs: tree!($rhs), 136 | }.into()) 137 | }; 138 | (( 139 | AssignExpr @ $start:literal .. $end:literal, 140 | assignee: $assignee:tt, 141 | assignee_ptr_count: $assignee_ptr_count:expr, 142 | op: $op:expr, 143 | expr: $expr:tt $(,)? 144 | )) => { 145 | Expression::Assign(AssignExpr { 146 | span: span!($start..$end), 147 | assignee: tree!($assignee), 148 | assignee_ptr_count: $assignee_ptr_count, 149 | op: $op, 150 | expr: tree!($expr), 151 | }.into()) 152 | }; 153 | (( 154 | CallExpr @ $start:literal .. $end:literal, 155 | func: $func:tt, 156 | args: [$($arg:tt),* $(,)?] $(,)? 157 | )) => { 158 | Expression::Call(CallExpr { 159 | span: span!($start..$end), 160 | func: tree!($func), 161 | args: vec![$(tree!($arg)),*], 162 | }.into()) 163 | }; 164 | (( 165 | CastExpr @ $start:literal .. $end:literal, 166 | expr: $expr:tt, 167 | type: $type:expr $(,)? 168 | )) => { 169 | Expression::Cast(CastExpr { 170 | span: span!($start..$end), 171 | expr: tree!($expr), 172 | type_: $type, 173 | }.into()) 174 | }; 175 | 176 | (($value:expr, @ $start:literal .. $end:literal)) => { 177 | Spanned { 178 | span: span!($start..$end), 179 | inner: $value, 180 | } 181 | }; 182 | } 183 | 184 | #[macro_export] 185 | macro_rules! tokens { 186 | ($($kind:ident $(($($tt:tt)*))? @ $start:literal .. $end:literal),* $(,)?) => { 187 | [$(TokenKind::$kind $(($($tt)*))? .spanned(span!($start..$end))),*] 188 | }; 189 | } 190 | -------------------------------------------------------------------------------- /crates/rush-parser/src/span.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | #[derive(Clone, Copy, PartialEq, Eq)] 4 | pub struct Span<'src> { 5 | pub start: Location<'src>, 6 | pub end: Location<'src>, 7 | } 8 | 9 | impl<'src> Span<'src> { 10 | pub fn new(start: Location<'src>, end: Location<'src>) -> Self { 11 | Self { start, end } 12 | } 13 | 14 | pub fn dummy() -> Self { 15 | Self { 16 | start: Location::new(""), 17 | end: Location::new(""), 18 | } 19 | } 20 | 21 | pub fn is_empty(&self) -> bool { 22 | self.start.byte_idx == self.end.byte_idx 23 | } 24 | } 25 | 26 | impl Debug for Span<'_> { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | match f.alternate() { 29 | true => write!( 30 | f, 31 | "{}:{}..{}:{}", 32 | self.start.line, self.start.column, self.end.line, self.end.column 33 | ), 34 | false => write!(f, "{}..{}", self.start.byte_idx, self.end.byte_idx), 35 | } 36 | } 37 | } 38 | 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 40 | pub struct Location<'src> { 41 | pub line: usize, 42 | pub column: usize, 43 | pub char_idx: usize, 44 | pub byte_idx: usize, 45 | pub path: &'src str, 46 | } 47 | 48 | impl<'src> Location<'src> { 49 | pub fn new(path: &'src str) -> Self { 50 | Self { 51 | line: 1, 52 | column: 1, 53 | char_idx: 0, 54 | byte_idx: 0, 55 | path, 56 | } 57 | } 58 | } 59 | 60 | impl<'src> Location<'src> { 61 | pub fn advance(&mut self, newline: bool, byte_idx_offset: usize) { 62 | self.char_idx += 1; 63 | self.byte_idx += byte_idx_offset; 64 | if newline { 65 | self.line += 1; 66 | self.column = 1; 67 | } else { 68 | self.column += 1; 69 | } 70 | } 71 | 72 | pub fn until(self, end: Location<'src>) -> Span { 73 | Span { start: self, end } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/rush-transpiler-c/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | output.c 3 | -------------------------------------------------------------------------------- /crates/rush-transpiler-c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush-transpiler-c" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | rush-analyzer = { version = "0.1.0", path = "../rush-analyzer" } 13 | -------------------------------------------------------------------------------- /crates/rush-transpiler-c/README.md: -------------------------------------------------------------------------------- 1 | # C Transpiler Backend 2 | 3 | A rush transpiler which generates 4 | [ANSI C](https://en.wikipedia.org/wiki/C_(programming_language)#ANSI_C_and_ISO_C) 5 | code without the need for external dependencies. 6 | 7 | ## Running rush Code 8 | 9 | - Prerequisite: A file ending in `.rush` which contains the program. 10 | - Execute the following command in order to run the program. 11 | 12 | ```bash 13 | cargo run your-file.rush 14 | ``` 15 | 16 | - Execute the following command in order to compile and then run the emitted `C` 17 | file 18 | - The `-std=c89` flag is completely optional but shows that the transpiler 19 | indeed generates `ANSI C`. 20 | 21 | ```bash 22 | gcc output.c -std=c89 -o out && ./out 23 | ``` 24 | -------------------------------------------------------------------------------- /crates/rush-transpiler-c/src/char.rush: -------------------------------------------------------------------------------- 1 | fn __rush_internal_cast_int_to_char(from: int) -> int { 2 | if from > 127 { 3 | 127 4 | } else if from < 0 { 5 | 0 6 | } else { 7 | from 8 | } 9 | } 10 | 11 | fn __rush_internal_cast_float_to_char(from: float) -> int { 12 | if from > 127.0 { 13 | 127 14 | } else if from < 0.0 { 15 | 0 16 | } else { 17 | from as int 18 | } 19 | } 20 | 21 | fn __rush_internal_add_char(lhs: char, rhs: char) -> char { 22 | ((lhs as int + rhs as int) & 127) as char 23 | } 24 | 25 | fn __rush_internal_sub_char(lhs: char, rhs: char) -> char { 26 | ((lhs as int - rhs as int) & 127) as char 27 | } 28 | 29 | fn main() {} 30 | -------------------------------------------------------------------------------- /crates/rush-transpiler-c/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rush_analyzer::Diagnostic; 2 | pub use transpiler::Transpiler; 3 | 4 | mod c_ast; 5 | mod transpiler; 6 | 7 | /// Transpiles rush source code to C89 / C90. 8 | /// The `Ok(_)` variant also returns non-error diagnostics. 9 | /// The `Err(_)` variant returns a `Vec` which contains at least one error. 10 | pub fn transpile<'tree>( 11 | text: &'tree str, 12 | path: &'tree str, 13 | emit_comments: bool, 14 | ) -> Result<(String, Vec>), Vec>> { 15 | let (tree, diagnostics) = rush_analyzer::analyze(text, path)?; 16 | let c_ast = Transpiler::new(emit_comments).transpile(tree); 17 | Ok((c_ast.to_string(), diagnostics)) 18 | } 19 | -------------------------------------------------------------------------------- /crates/rush-transpiler-c/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, process, time::Instant}; 2 | 3 | fn main() { 4 | let path = env::args().nth(1).unwrap(); 5 | let emit_comments = env::args().nth(2) == Some("true".to_string()); 6 | let code = fs::read_to_string(&path).unwrap(); 7 | let start = Instant::now(); 8 | let (out, diagnostics) = rush_transpiler_c::transpile(&code, &path, emit_comments) 9 | .unwrap_or_else(|diagnostics| { 10 | println!( 11 | "{}", 12 | diagnostics 13 | .iter() 14 | .map(|d| format!("{d:#}")) 15 | .collect::>() 16 | .join("\n\n") 17 | ); 18 | process::exit(1) 19 | }); 20 | 21 | println!( 22 | "{}", 23 | diagnostics 24 | .iter() 25 | .map(|d| format!("{d:#}")) 26 | .collect::>() 27 | .join("\n\n") 28 | ); 29 | 30 | println!("tanspile: {:?}", start.elapsed()); 31 | fs::write("output.c", out).unwrap(); 32 | } 33 | -------------------------------------------------------------------------------- /crates/rush-transpiler-c/src/pow.rush: -------------------------------------------------------------------------------- 1 | fn __rush_internal_pow_int(mut base: int, mut exp: int) -> int { 2 | if exp < 0 { 3 | return 0; 4 | } 5 | if exp == 0 { 6 | return 1; 7 | } 8 | 9 | let mut acc = 1; 10 | while exp > 1 { 11 | if (exp & 1) == 1 { 12 | acc *= base; 13 | } 14 | exp /= 2; 15 | base *= base; 16 | } 17 | 18 | acc * base 19 | } 20 | 21 | fn main() {} 22 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "lineWidth": 120, 3 | "indentWidth": 4, 4 | "useTabs": false, 5 | 6 | "json": { 7 | "indentWidth": 2, 8 | "array.preferSingleLine": true 9 | }, 10 | 11 | "markdown": { 12 | "lineWidth": 80, 13 | "textWrap": "always" 14 | }, 15 | 16 | "toml": { 17 | }, 18 | 19 | "rustfmt": { 20 | "max_width": 100 21 | }, 22 | 23 | "ebnf": { 24 | "lineWidth": 60, 25 | "indentWidth": 2 26 | }, 27 | 28 | "prettier": { 29 | "printWidth": 100, 30 | "tabWidth": 4, 31 | "semi": false, 32 | "singleQuote": true, 33 | "trailingComma": "all", 34 | "arrowParens": "avoid", 35 | "proseWrap": "always", 36 | 37 | "plugin.jsDoc": true, 38 | "yml.tabWidth": 2, 39 | "yaml.tabWidth": 2, 40 | "json.tabWidth": 2, 41 | "jsonc.tabWidth": 2, 42 | "json5.tabWidth": 2 43 | }, 44 | 45 | "exec": { 46 | "associations": "**/*.{py}", 47 | 48 | "blue": "blue - --stdin-filename {{file_path}}", 49 | "blue.associations": "**/*.py" 50 | }, 51 | 52 | "includes": ["**/*"], 53 | "excludes": ["**/target", ".git"], 54 | "plugins": [ 55 | "https://plugins.dprint.dev/json-0.17.0.wasm", 56 | "https://plugins.dprint.dev/markdown-0.15.1.wasm", 57 | "https://plugins.dprint.dev/toml-0.5.4.wasm", 58 | "https://plugins.dprint.dev/rustfmt-0.6.2.json@886c6f3161cf020c2d75160262b0f56d74a521e05cfb91ec4f956650c8ca76ca", 59 | "https://plugins.dprint.dev/RubixDev/ebnf-v0.1.1.wasm", 60 | "https://plugins.dprint.dev/prettier-0.17.0.json@5e42cbabab9906ad32ffe1aa67f15824b1c39408840f0afeaddc495dd362e177", 61 | "https://plugins.dprint.dev/exec-0.3.2.json@8efbbb3fcfbdf84142c3c438fbdeaf1637152a020032127c837b2b14e23261c3" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /grammar.ebnf: -------------------------------------------------------------------------------- 1 | Program = { Item } ; 2 | 3 | Item = FunctionDefinition | LetStmt ; 4 | FunctionDefinition = 'fn' , ident , '(' , [ ParameterList ] , ')' 5 | , [ '->' , Type ] , Block ; 6 | ParameterList = Parameter , { ',' , Parameter } , [ ',' ] ; 7 | Parameter = [ 'mut' ] , ident , ':' , Type ; 8 | 9 | Block = '{' , { Statement } , [ Expression ] , '}' ; 10 | Type = { '*' } , ( ident 11 | | '(' , ')' ) ; 12 | 13 | Statement = LetStmt | ReturnStmt | LoopStmt | WhileStmt | ForStmt 14 | | BreakStmt | ContinueStmt | ExprStmt ; 15 | LetStmt = 'let' , [ 'mut' ] , ident , [ ':' , Type ] , '=' 16 | , Expression , ';' ; 17 | ReturnStmt = 'return' , [ Expression ] , ';' ; 18 | LoopStmt = 'loop' , Block , [ ';' ] ; 19 | WhileStmt = 'while' , Expression , Block , [ ';' ] ; 20 | ForStmt = 'for' , ident , '=' , Expression , ';' , Expression 21 | , ';' , Expression , Block , [ ';' ] ; 22 | BreakStmt = 'break' , ';' ; 23 | ContinueStmt = 'continue' , ';' ; 24 | ExprStmt = ExprWithoutBlock , ';' 25 | | ExprWithBlock , [ ';' ] ; 26 | 27 | Expression = ExprWithoutBlock | ExprWithBlock ; 28 | ExprWithBlock = Block | IfExpr ; 29 | IfExpr = 'if' , Expression , Block , [ 'else' , ( IfExpr 30 | | Block ) ] ; 31 | ExprWithoutBlock = int 32 | | float 33 | | bool 34 | | char 35 | | ident 36 | | PrefixExpr 37 | | InfixExpr 38 | | AssignExpr 39 | | CallExpr 40 | | CastExpr 41 | | '(' , Expression , ')' ; 42 | PrefixExpr = PREFIX_OPERATOR , Expression ; 43 | InfixExpr = Expression , INFIX_OPERATOR , Expression ; 44 | (* The left hand side can only be an `ident` or a `PrefixExpr` with the `*` operator *) 45 | AssignExpr = Expression , ASSIGN_OPERATOR , Expression ; 46 | CallExpr = ident , '(' , [ ArgumentList ] , ')' ; 47 | ArgumentList = Expression , { ',' , Expression } , [ ',' ] ; 48 | CastExpr = Expression , 'as' , Type ; 49 | 50 | ident = LETTER , { LETTER | DIGIT } ; 51 | int = DIGIT , { DIGIT | '_' } 52 | | '0x' , HEX , { HEX | '_' } ; 53 | float = DIGIT , { DIGIT | '_' } , ( '.' , DIGIT , { DIGIT | '_' } 54 | | 'f' ) ; 55 | char = "'" , ( ASCII_CHAR - '\' 56 | | '\' , ( ESCAPE_CHAR 57 | | "'" 58 | | 'x' , 2 * HEX ) ) , "'" ; 59 | bool = 'true' | 'false' ; 60 | 61 | comment = '//' , { CHAR } , ? LF ? 62 | | '/*' , { CHAR } , '*/' ; 63 | 64 | LETTER = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' 65 | | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' 66 | | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | 'a' 67 | | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' 68 | | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' 69 | | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | '_' ; 70 | DIGIT = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' 71 | | '9' ; 72 | HEX = DIGIT | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' 73 | | 'b' | 'c' | 'd' | 'e' | 'f' ; 74 | CHAR = ? any UTF-8 character ? ; 75 | ASCII_CHAR = ? any ASCII character ? ; 76 | ESCAPE_CHAR = '\' | 'b' | 'n' | 'r' | 't' ; 77 | 78 | PREFIX_OPERATOR = '!' | '-' | '&' | '*' ; 79 | INFIX_OPERATOR = ARITHMETIC_OPERATOR | RELATIONAL_OPERATOR 80 | | BITWISE_OPERATOR | LOGICAL_OPERATOR ; 81 | ARITHMETIC_OPERATOR = '+' | '-' | '*' | '/' | '%' | '**' ; 82 | RELATIONAL_OPERATOR = '==' | '!=' | '<' | '>' | '<=' | '>=' ; 83 | BITWISE_OPERATOR = '<<' | '>>' | '|' | '&' | '^' ; 84 | LOGICAL_OPERATOR = '&&' | '||' ; 85 | ASSIGN_OPERATOR = '=' | '+=' | '-=' | '*=' | '/=' | '%=' 86 | | '**=' | '<<=' | '>>=' | '|=' | '&=' | '^=' ; 87 | -------------------------------------------------------------------------------- /logo/old/rush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/old/rush.png -------------------------------------------------------------------------------- /logo/old/rush.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/old/rush.xcf -------------------------------------------------------------------------------- /logo/old/rush_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/old/rush_white.png -------------------------------------------------------------------------------- /logo/rush_logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/rush_logo.pdf -------------------------------------------------------------------------------- /logo/rush_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/rush_logo.png -------------------------------------------------------------------------------- /logo/rush_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /logo/rush_logo_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/rush_logo_bg.png -------------------------------------------------------------------------------- /logo/rush_logo_bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /logo/rush_logo_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/rush_logo_outline.png -------------------------------------------------------------------------------- /logo/rush_logo_outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /logo/rush_logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/rush_logo_small.png -------------------------------------------------------------------------------- /logo/rush_logo_small.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /logo/rush_logo_small_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/rush_logo_small_bg.png -------------------------------------------------------------------------------- /logo/rush_logo_small_bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /logo/rush_logo_small_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-rs/rush/dbcbfa8177fb3529bbf709d6c8452941b71305d5/logo/rush_logo_small_outline.png -------------------------------------------------------------------------------- /logo/rush_logo_small_outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/main.rush: -------------------------------------------------------------------------------- 1 | ////////////// PART ONE ////////////// 2 | 3 | // rush supports global variables 4 | let mut answer = 42; 5 | 6 | fn main() { 7 | let mut answer: int = 42; // int (i64) 8 | let pi: float = 3.14159265358979323846264338327950; // float (f64) 9 | let is_good: bool = false; // bool 10 | let _char = 'a'; // ASCII char (u8) 11 | 12 | // Re-assignments 13 | answer += 3; 14 | answer -= 3; 15 | answer *= 3; 16 | answer /= 3; 17 | // not for floats: 18 | answer %= 3; 19 | answer **= 3; 20 | answer |= 3; 21 | answer &= 3; 22 | answer ^= 3; 23 | answer >>= 3; 24 | answer <<= 3; 25 | 26 | let answer: float = 4_2f; // float literal, underscores are ignored, f forces float type 27 | // integer = DIGIT , { DIGIT | '_' } ; 28 | // [0-9][0-9_]* 29 | // float = DIGIT , { DIGIT | '_' } , ( '.' , DIGIT , { DIGIT | '_' } | 'f' ) ; 30 | // [0-9][0-9_]*(\.[0-9][0-9_]*|f) 31 | answer as int; // cast between primitive types 32 | 33 | (1 + 2) * 3 / 4 % 5 ** 6 - 7; // arithmetic operators 34 | 1 >> 2 << 3 | 4 & 5 ^ 6; // bitwise operators 35 | true || false && true; // logical operators 36 | 1 < 2; 1 <= 3; 1 > 0; 1 >= -1; 1 == 1; 1 != 2; // relational operators 37 | 38 | let _float = if answer > pi || is_good { 39 | answer 40 | } else if pi > answer { 41 | pi 42 | } else { 43 | is_good as float 44 | }; 45 | 46 | for i = 0; i < 100; i += 1 { 47 | if i % 2 == 0 { 48 | continue; 49 | } 50 | if i == 80 { 51 | break; 52 | } 53 | } 54 | 55 | let mut a = 0; 56 | while a < 10 { 57 | a += 1; 58 | } 59 | 60 | let mut a = 0; 61 | loop { 62 | if a == 10 { 63 | break; 64 | } 65 | a += 1; 66 | } 67 | 68 | exit(1); // -> ! // exit(code: int) -> (), uses WASI proc_exit(i32) when used with Wasm 69 | } 70 | 71 | fn _add(left: int, right: int) -> int { 72 | left + right 73 | } 74 | 75 | ////////////// PART TWO ////////////// 76 | 77 | /* 78 | fn main() { 79 | let name = "rush"; 80 | let array: [int; 3] = [1, 2, 3]; 81 | println(name, "rush"); 82 | } 83 | */ 84 | -------------------------------------------------------------------------------- /samples/tests/basic/approx_apery.rush: -------------------------------------------------------------------------------- 1 | let APERY = 1.2020569; 2 | 3 | fn main() { 4 | let mut res = 1f; 5 | 6 | for i = 2; i < 30000; i += 1 { 7 | res += (1f / (i ** 3) as float); 8 | } 9 | 10 | let FAC = 10000000f; 11 | if (APERY * FAC) as int == (res * FAC) as int { 12 | // success 13 | exit(0); 14 | } else { 15 | // failure 16 | exit(1); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/tests/basic/approx_e.rush: -------------------------------------------------------------------------------- 1 | let E = 2.718281828459045; 2 | 3 | fn main() { 4 | let mut e = 1f; 5 | let mut f = 1f; 6 | 7 | for i = 2; i < 100_000; i += 1 { 8 | e += 1f / f; 9 | f *= i as float; 10 | } 11 | 12 | let result = (e * 1000000000000000f) as int; 13 | let expected = (E * 1000000000000000f) as int; 14 | 15 | if result == expected { 16 | // success 17 | exit(0); 18 | } else { 19 | // failure 20 | exit(1); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/tests/basic/approx_pi.rush: -------------------------------------------------------------------------------- 1 | let ITERATIONS = 1000000; 2 | 3 | fn main() { 4 | let mut k = 3f; 5 | let mut s = 1f; 6 | 7 | for i = 0; i < ITERATIONS; i+= 1 { 8 | s = s-((1f / k) * ((-1) ** i) as float); 9 | k += 2f; 10 | } 11 | 12 | let result = ((4f * s) * 100000f) as int; 13 | 14 | if result == 314159 { 15 | // success, the computation was successful 16 | exit(0); 17 | } else { 18 | // failure 19 | exit(1); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/tests/basic/blocks.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let a = { 3 | let mut b = 10; 4 | { 5 | b += 10; 6 | }; 7 | { 8 | let b = 10; 9 | b; 10 | }; 11 | { 12 | b 13 | } 14 | }; 15 | 16 | let b = {}; 17 | 18 | call_exit(a, b) 19 | } 20 | 21 | fn call_exit(code: int, a: ()) { 22 | exit(code) 23 | } 24 | -------------------------------------------------------------------------------- /samples/tests/basic/char.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let a = 'a'; 3 | let b = 'b'; 4 | let c = 'c'; 5 | 6 | let true_ = a < b 7 | && b <= b 8 | && a >= a 9 | && b > a 10 | && !(a > b) 11 | && !(b < a) 12 | && !(b >= c) 13 | && !(c <= a); 14 | 15 | let add_overflow = a + b; // 67 16 | let sub_overflow = a - c; // 126 17 | 18 | let zero = -1 as char; 19 | let char_max = 128 as char; 20 | let normal = 33 as char; 21 | 22 | // expected exit-code: 100 23 | exit( 24 | add_overflow as int 25 | + sub_overflow as int 26 | + true_ as int 27 | + zero as int 28 | - char_max as int 29 | + normal as int 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /samples/tests/basic/exit_0.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | } 3 | -------------------------------------------------------------------------------- /samples/tests/basic/fib.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | exit(fib_rec(10) + fib_iter(10)) 3 | } 4 | 5 | // fib_rec(10) = 55 6 | fn fib_rec(n: int) -> int { 7 | if n < 2 { 8 | n 9 | } else { 10 | fib_rec(n - 2) + fib_rec(n - 1) 11 | } 12 | } 13 | 14 | // fib_iter(10) = 55 15 | fn fib_iter(mut n: int) -> int { 16 | if n == 0 { 17 | return 0; 18 | } 19 | 20 | let mut prev = 1; 21 | let mut curr = 1; 22 | while n > 2 { 23 | n -= 1; 24 | let tmp = curr + prev; 25 | prev = curr; 26 | curr = tmp; 27 | } 28 | curr 29 | } 30 | -------------------------------------------------------------------------------- /samples/tests/basic/float_casts.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let nine = 9.9; 3 | let eq = (nine as int == 9.9 as int); 4 | 5 | let min_nine = -9.9; 6 | let minus_eq = (min_nine as int == -9.9 as int); 7 | 8 | exit((eq && minus_eq) as int + 1) 9 | } 10 | -------------------------------------------------------------------------------- /samples/tests/basic/globals.rush: -------------------------------------------------------------------------------- 1 | let const = 42; 2 | let mut mut_ = 1; 3 | 4 | fn main() { 5 | if mut_ != 1 { 6 | exit(99) 7 | } 8 | mutate_mut(); 9 | exit(const + mut_); 10 | } 11 | 12 | fn mutate_mut() { 13 | mut_ += 1 14 | } 15 | -------------------------------------------------------------------------------- /samples/tests/basic/if_else.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let true_ = true; 3 | 4 | let a = if true_ { 5 | if !!true_ && true_ { 6 | let mut a = 0; 7 | for i = 0; i < 10; i += 1 { 8 | a += 1; 9 | } 10 | a 11 | } else { 12 | exit(99) // unreachable 13 | } 14 | } else { 15 | exit(99) // unreachable 16 | }; 17 | 18 | let b = if (!true_ && true_) || (!true_) { 19 | exit(99) // unreachable 20 | } else { 21 | let mut a = 0; 22 | for i = 0; (i < 10) && true_; i += 1 { 23 | a += 1; 24 | } 25 | a 26 | }; 27 | 28 | exit(a + b) 29 | } 30 | -------------------------------------------------------------------------------- /samples/tests/basic/loops.rush: -------------------------------------------------------------------------------- 1 | // expected return-value: 15 2 | fn a() -> int { 3 | let mut a = 0; 4 | for i = 0; i < 10; i += 1 { 5 | loop { 6 | loop { 7 | loop { 8 | for j = 10; j > 0; j -= 1 { 9 | a += 1 10 | } 11 | for i = 0; i < 10; i += 1 { 12 | continue; 13 | a += 1 14 | } 15 | while false { 16 | // this will never run 17 | } 18 | break; 19 | } 20 | break; 21 | } 22 | break; 23 | } 24 | a -= 1 25 | } 26 | 27 | let mut b = 0; 28 | while a > 85 { 29 | a -= 1; 30 | b += 1; 31 | } 32 | 33 | let mut c = 0f; 34 | for i = 0; i < b; i += 1 { 35 | for i = 0; i < 3; i += 1 { 36 | for i = 0; i < 5; i += 1 { 37 | for i = 0; true; { continue; } { 38 | } 39 | exit(99) // unreachable 40 | } 41 | 42 | c += 1.0; 43 | 44 | for i = 0; i < 5; i += 1 { 45 | for i = 0; { continue; }; {} { 46 | exit(99) // unreachable 47 | } 48 | exit(99) // unreachable 49 | } 50 | 51 | for i = {continue;}; true; {} {} 52 | exit(99) // unreachable 53 | } 54 | } 55 | 56 | c as int 57 | } 58 | 59 | // expected return-value: 1 60 | fn breaks() -> int { 61 | let mut c = 0; 62 | loop { 63 | for i = 0; true; {break;} { 64 | c += 1 65 | } 66 | } 67 | // c is now 1 68 | loop { 69 | for i = 0; { break; }; {} { 70 | exit(99) // unreachable 71 | } 72 | exit(99) // unreachable 73 | } 74 | 75 | loop { 76 | for i = {break;}; true; {} { 77 | exit(99) // unreachable 78 | } 79 | exit(99) // unreachable 80 | } 81 | 82 | c 83 | } 84 | 85 | // expected return-value: 35 86 | fn continues() -> int { 87 | let mut c = 0; 88 | while c < 5 { 89 | for i = 0; true; {continue;} { 90 | c += 1 91 | } 92 | exit(99) // unreachable 93 | } 94 | // c is now 5 95 | 96 | let mut d = 0; 97 | while d < 10 { 98 | d += 1; 99 | for i = 0; { continue; }; {} { 100 | exit(99) // unreachable 101 | } 102 | exit(99) // unreachable 103 | } 104 | // d is now 10 105 | 106 | let mut e = 0; 107 | while e < 20 { 108 | e += 1; 109 | for i = {continue;}; true; {} { 110 | exit(99) // unreachable 111 | } 112 | exit(99) // unreachable 113 | } 114 | // e is now 20 115 | c + d + e 116 | } 117 | 118 | 119 | // expected return-value: 6 120 | fn whiles() -> int { 121 | let mut c = 0; 122 | 123 | while c < 4 { 124 | c += 1; 125 | while { continue; } { 126 | exit(99) // unreachable 127 | } 128 | exit(99) // unreachable 129 | } 130 | // c is now 4 131 | 132 | let mut d = 2; 133 | loop { 134 | while { break; } { 135 | exit(99) // unreachable 136 | } 137 | exit(99) // unreachable 138 | } 139 | // d is still 2 140 | 141 | c + d 142 | } 143 | 144 | // expected return-value: 5 145 | fn block_cond() -> int { 146 | let mut a = 0; 147 | for i = 0; { 148 | let true_ = true; 149 | if i < 5 && (true_ || false) { 150 | let mut b = 0; 151 | for i = 0; i < 10; i += 1 { 152 | b += 1; 153 | continue; 154 | } 155 | b == 10 156 | } else { 157 | false 158 | } 159 | }; i += 1 { 160 | a += 1 161 | } 162 | a 163 | } 164 | 165 | // expected return-value: 10 166 | fn unit() -> int { 167 | let mut a = 0; 168 | let mut b = 0; 169 | for i = unit_(); a < 10; a += 1 { 170 | b += 1 171 | } 172 | b 173 | } 174 | 175 | fn unit_() {} 176 | 177 | // expected return-value: 15 178 | fn scoping() -> int { 179 | let i = 10; 180 | let mut a = 0; 181 | for i = 0; i < 5; i += 1 { 182 | a += 1 183 | } 184 | i + a 185 | } 186 | 187 | fn main() { 188 | exit(continues() - a() + breaks() - whiles() + block_cond() + unit() + scoping()) 189 | } 190 | -------------------------------------------------------------------------------- /samples/tests/basic/nan.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let zero = 0.0; 3 | let nan = zero / zero; 4 | 5 | 6 | let res = !(nan == nan) && 7 | nan != nan && 8 | !(nan > nan) && 9 | !(nan >= nan) && 10 | !(nan < nan) && 11 | !(nan <= nan); 12 | 13 | exit(res as int + 10) 14 | } 15 | -------------------------------------------------------------------------------- /samples/tests/basic/nested_calls.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | exit(fib(fib(6))) 3 | } 4 | 5 | fn fib(n: int) -> int { 6 | if n < 2 { 7 | n 8 | } else { 9 | fib(n - 2) + fib(n - 1) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/tests/basic/playground_basic.rush: -------------------------------------------------------------------------------- 1 | // Welcome to the playground of the rush programming language. 2 | // For more information, please visit `https://rush-lang.de` 3 | 4 | let mut A_GLOBAL_VARIABLE = 41; 5 | let FOUR = 4; 6 | let TWO = 2; 7 | 8 | // Every rush program needs to contain one main function. 9 | fn main(/* It cannot take any parameters */) /* Nor can it return a value. */ { 10 | A_GLOBAL_VARIABLE += 1; 11 | 12 | let result = 13 | types() 14 | - infix_expressions() 15 | + prefix_expressions(5) 16 | - pointers() as int 17 | - control_flow() 18 | - A_GLOBAL_VARIABLE 19 | - FOUR ** 2 20 | - TWO ** 3 21 | - 100000000 / 100000000 ; 22 | 23 | 24 | exit(result); 25 | } 26 | 27 | fn infix_expressions() -> int /* This function returns an integer */ { 28 | // Logical operators are evaluated lazily 29 | let result_0 = false && exit(99); 30 | let result_1 = true || exit(99); 31 | // Due to this, the `exit` function was not called these two times 32 | 33 | let character = 'z'; 34 | let number = (character - 'a') as int; // This results in 25. 35 | number + 1; // This is a statement and therefore does not return. 36 | // return 0; // Functions can also return values explicitly. 37 | number + 17 + result_0 as int + result_1 as int // The value of `number` + 1 is returned implicitly (no semicolon) 38 | } 39 | 40 | fn prefix_expressions(input: int) -> int { 41 | let output = input as float / 3f; 42 | -output as int // Floats are rounded down when using `as` 43 | } 44 | 45 | fn pointers() -> float { 46 | let mut a_float = 3.14159265; // The source variable needs to be mutable 47 | let pointer_to_a = &a_float; // A pointer is created by using the `&` operator 48 | *pointer_to_a += 2f; // When assigning to the variable behind a pointer, 49 | // the `*` (deref) operator is used. 50 | *pointer_to_a // This returns the value stored in `a_float` 51 | } 52 | 53 | fn control_flow() -> int { 54 | let mut counter_0 = 0; 55 | loop { // Unconditional loop 56 | if counter_0 == 10 { 57 | break; 58 | } 59 | counter_0 += 1; 60 | } 61 | 62 | let mut counter_1 = 0; 63 | while counter_1 < 10 { // While loop 64 | counter_1 +=1; 65 | } 66 | 67 | let mut counter_2 = 0; 68 | for i = 0; i < 10; i += 1 { // For loop 69 | counter_2 += 1; 70 | } 71 | 72 | counter_0 + counter_1 - counter_2 73 | } 74 | 75 | fn types() -> int { 76 | let integer = 42; 77 | let float_0 = 3.1415; 78 | let float_1 = 2f; 79 | let float_2 = float_0 + float_1; 80 | let bool = true | false; 81 | let char = 'x'; 82 | 83 | integer 84 | + float_2 as int 85 | + bool as int 86 | + char as int 87 | } 88 | -------------------------------------------------------------------------------- /samples/tests/basic/pow.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let sixteen = 2 ** 4; 3 | 4 | let two = 2; 5 | let three = 3; 6 | let eight = two ** three; 7 | 8 | let high_exp = 4294967298; // larger than u32::MAX 9 | 10 | // = 26 11 | exit( 12 | sixteen 13 | - eight 14 | + 3 ** 4294967298 // = 9 15 | + 3 ** high_exp // = 9 16 | + 2 ** -2 // = 0 17 | + 2 ** -two // = 0 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /samples/tests/basic/wasm_test.rush: -------------------------------------------------------------------------------- 1 | let mut a = 234; 2 | let b = 2; 3 | 4 | fn set() { 5 | a += b; 6 | } 7 | 8 | // Should be 37 9 | fn main() { 10 | set(); 11 | exit(a - fib(10) - pow(12, 2)); 12 | } 13 | 14 | fn fib(mut n: int) -> int { 15 | if n == 0 { 16 | return 0; 17 | } 18 | 19 | let mut prev = 1; 20 | let mut curr = 1; 21 | while n > 2 { 22 | n -= 1; 23 | let tmp = curr + prev; 24 | prev = curr; 25 | curr = tmp; 26 | } 27 | curr 28 | } 29 | 30 | fn pow(base: int, mut exponent: int) -> int { 31 | if exponent < 0 { 32 | return 0; 33 | } 34 | let mut accumulator = 1; 35 | while exponent != 0 { 36 | exponent -= 1; 37 | accumulator *= base; 38 | } 39 | accumulator 40 | } 41 | 42 | fn fib_recursive(n: int) -> int { 43 | if n < 2 { 44 | n 45 | } else { 46 | fib_recursive(n - 1) + fib_recursive(n - 2) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /samples/tests/basic/wrapping.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let int_max = !(1 << 63); 3 | let char_max = '\x7f'; 4 | 5 | let four = int_max + 6 + int_max; 6 | let eight = 6 - int_max - int_max; 7 | let ten = char_max + '\x0b'; 8 | let three = '\x02' - char_max; 9 | 10 | // = 42 11 | exit(ten as int * three as int + eight + four); 12 | } 13 | -------------------------------------------------------------------------------- /samples/tests/basic/x64_test.rush: -------------------------------------------------------------------------------- 1 | fn call() -> int { add(get(), get()) } 2 | fn add(a: int, b: int) -> int { a + b } 3 | fn get() -> int { 1 } 4 | 5 | fn add4(a: int, b: int, c: int, d: int) -> int { a + b + c + d } 6 | 7 | fn sized_args(_a: int, _b: bool, _c: bool, d: int) -> int { d } 8 | 9 | let global_a = 1 + 2; 10 | let mut global_b = 'b'; 11 | let _global_c = 3; 12 | 13 | fn main() { 14 | let mut c = 0; 15 | loop { 16 | c += 1; 17 | if c == 3 { 18 | c += 2; 19 | continue; 20 | } else if c == 4 { 21 | c = 20; 22 | break; 23 | } else if c == 10 { 24 | break; 25 | } 26 | } 27 | 28 | let a = 1; 29 | 1; 30 | 3.0; 31 | 'a'; 32 | let _x = (1 << 31) - 1; 33 | let yes = !false; 34 | let no = !yes; 35 | let hundred = 100; 36 | let nine = 9f; 37 | let base = 2; 38 | global_b = (global_b as int - global_a) as char; 39 | global_b += '\x01'; 40 | global_b -= '\x02'; 41 | exit( 42 | 1 + 2 + a + call() - float(3.5) as int 43 | + params(1, 2.0, '\x03', true, 5, 6, 7, 8, 9, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0) 44 | + sized_args(42, yes, no, sized_args(30, no, yes, 6)) 45 | + add4(0, 0, 10 / 3, 10 % 3) 46 | + (3 << a) 47 | + (6 >> a) 48 | + -hundred + (-nine) as int 49 | + (global_b - '\x05') as int 50 | + base**3 51 | + comparisons() + float_comparisons() 52 | + casts() 53 | + !hundred 54 | - assignments() 55 | + { 56 | let a = 'a'; 57 | a 58 | } as int 59 | - c 60 | ); // = 170 61 | } 62 | 63 | fn comparisons() -> int { 64 | let a = 3; 65 | let x = 5; 66 | 67 | let b = a > 1; 68 | let c = a == 3; 69 | let d = 4 != a; 70 | let e = a < x; 71 | 72 | (b & c & d & e) as int 73 | } 74 | 75 | fn float_comparisons() -> int { 76 | let a = 3.0; 77 | let x = 5.5; 78 | 79 | let b = a > 1.2; 80 | let c = a == 3.0; 81 | let d = 4.2 != a; 82 | let e = a < x; 83 | 84 | (b & c & d & e) as int 85 | } 86 | 87 | fn casts() -> int { 88 | let a = 300; 89 | 90 | let b = a as char; // = '\x7F' 91 | let c = b as bool; // = true 92 | let d = c as float; // = 1.0 93 | let e = d as int; // = 1 94 | let f = e as bool; // = true 95 | let g = f as char; // = '\x01' 96 | let h = g as float; // = 1.0 97 | let i = h as char; // = '\x01' 98 | let j = i as int; // = 1 99 | let k = j as float; // = 1.0 100 | let l = k as bool; // = true 101 | let m = l as int; // = 1 102 | 103 | m 104 | } 105 | 106 | fn unit() {} 107 | 108 | fn assignments() -> int { 109 | let mut _x = unit(); 110 | _x = unit(); 111 | 112 | let mut a = 300; 113 | 114 | a += 10; // = 310 115 | a -= 5; // = 305 116 | a *= 2; // = 610 117 | a /= 3; // = 203 118 | a %= 10; // = 3 119 | a <<= 2; // = 12 120 | a >>= 1; // = 6 121 | a |= 3; // = 7 122 | a &= 13; // = 5 123 | a ^= 7; // = 2 124 | a **= 3; // = 8 125 | a += a; // = 16 126 | 127 | a 128 | } 129 | 130 | fn float(a: float) -> float { 131 | a * 2.0 - 4.0 132 | } 133 | 134 | fn _ops(b: int) -> int { 135 | 3 + b; 136 | 4 - b; 137 | 5 * b; 138 | 6 | b; 139 | 7 ^ b; 140 | 8 & b 141 | } 142 | 143 | fn _vars() -> char { 144 | let _c = true; 145 | let d = 'a'; 146 | let _a = 1; 147 | let _b = 2f; 148 | d 149 | } 150 | 151 | fn params( 152 | a: int, // %rdi 153 | b: float, // %xmm0 154 | c: char, // %sil 155 | d: bool, // %dl 156 | e: int, // %rcx 157 | f: int, // %r8 158 | g: int, // %r9 159 | h: int, // [%rbp+16] 160 | i: int, // [%rbp+24] 161 | j: float, // %xmm1 162 | k: float, // %xmm2 163 | l: float, // %xmm3 164 | m: float, // %xmm4 165 | n: float, // %xmm5 166 | o: float, // %xmm6 167 | p: float, // %xmm7 168 | q: float, // [%rbp+32] 169 | r: float, // [%rbp+40] 170 | s: float, // [%rbp+48] 171 | ) -> int { 172 | a + b as int + c as int + d as int + e + f + g + h + i + (j + k + l + m + n + o + p + q + r + s) as int 173 | } 174 | -------------------------------------------------------------------------------- /samples/tests/exits/final_fn_expr.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = do_something(); 3 | } 4 | 5 | fn do_something() -> int { 6 | exit(15) + 42 7 | } 8 | -------------------------------------------------------------------------------- /samples/tests/exits/for_1.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let true_ = true; 3 | for _ = exit(9); true_; {} { 4 | // unreachable 5 | exit(99) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/tests/exits/for_2.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | for i = 10; exit(i); i += 1 { 3 | // unreachable 4 | exit(i) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /samples/tests/exits/for_3.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | for i = 10; i < 20; exit(i) { 3 | // must still reachable 4 | i += 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /samples/tests/exits/if_else.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | if exit(6) { 3 | exit(99) 4 | } else { 5 | exit(100) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/tests/exits/in_call.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | foo(exit(16)) 3 | } 4 | 5 | fn foo(a: int) -> int { 6 | exit(a) 7 | } 8 | -------------------------------------------------------------------------------- /samples/tests/exits/infix.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let one = 1; 3 | let res = one + exit(5); 4 | exit(res) 5 | } 6 | -------------------------------------------------------------------------------- /samples/tests/exits/let.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let unit = unit(); 3 | let never = exit(14); 4 | } 5 | 6 | fn unit() {} 7 | -------------------------------------------------------------------------------- /samples/tests/exits/logical_and.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let true_ = true; 3 | let is_false = !true_ && exit(99); 4 | exit( (true_ && exit(is_false as int + 13)) as int); 5 | } 6 | -------------------------------------------------------------------------------- /samples/tests/exits/logical_or.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let true_ = true; 3 | let is_true = true_ || exit(99); 4 | exit( (!true_ || exit(is_true as int + 11)) as int); 5 | } 6 | -------------------------------------------------------------------------------- /samples/tests/exits/nested_exit.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | exit(exit(7)) 3 | } 4 | -------------------------------------------------------------------------------- /samples/tests/exits/while.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | while exit(8) { 3 | // unreachable 4 | exit(99) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /samples/tests/main.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | import subprocess 3 | import os 4 | import sys 5 | 6 | # maps an input file to a desired exit-code 7 | tests = { 8 | './basic/complete.rush': 50, 9 | './basic/loops.rush': 45, 10 | './basic/float_casts.rush': 2, 11 | './basic/pow.rush': 26, 12 | './basic/fib.rush': 110, 13 | './basic/globals.rush': 44, 14 | './basic/if_else.rush': 20, 15 | './basic/nan.rush': 11, 16 | './basic/char.rush': 100, 17 | './basic/wrapping.rush': 42, 18 | './basic/exit_0.rush': 0, 19 | './basic/blocks.rush': 20, 20 | './basic/nested_calls.rush': 21, 21 | './basic/approx_pi.rush': 0, 22 | './basic/approx_e.rush': 0, 23 | './basic/approx_apery.rush': 0, 24 | './basic/wasm_test.rush': 37, 25 | './basic/x64_test.rush': 170, 26 | './basic/playground_basic.rush': 42, 27 | # 'evil exits' test if the `!` type can occur everywhere 28 | './exits/infix.rush': 5, 29 | './exits/if_else.rush': 6, 30 | './exits/nested_exit.rush': 7, 31 | './exits/while.rush': 8, 32 | './exits/for_1.rush': 9, 33 | './exits/for_2.rush': 10, 34 | './exits/for_3.rush': 11, 35 | './exits/logical_or.rush': 12, 36 | './exits/logical_and.rush': 13, 37 | './exits/let.rush': 14, 38 | './exits/final_fn_expr.rush': 15, 39 | './exits/in_call.rush': 16, 40 | # pointers 41 | './pointers/basic.rush': 42, 42 | './pointers/assignments.rush': 43, 43 | './pointers/depth.rush': 121, 44 | './pointers/globals.rush': 44, 45 | './pointers/in_params.rush': 45, 46 | './pointers/types.rush': 46, 47 | './pointers/swap.rush': 47, 48 | './pointers/for_loop.rush': 15, 49 | './pointers/assignment_edge_cases.rush': 104, 50 | './pointers/as_return_type.rush': 69, 51 | './pointers/shadow_ref.rush': 42, 52 | } 53 | 54 | # saves the backend and any additional commands to be executed after `cargo r` 55 | backends = { 56 | 'rush-interpreter-tree': '', 57 | 'rush-interpreter-vm': '', 58 | 'rush-compiler-wasm': 'exit $(wasmer output.wasm 2>&1 >/dev/null | cut -d ":" -f 4 )', 59 | 'rush-compiler-llvm': 'gcc output.o -o test && ./test', 60 | 'rush-compiler-x86-64': """ 61 | gcc output.s -L corelib/ -lcore -nostdlib -o output \ 62 | && ./output 63 | """, 64 | 'rush-compiler-risc-v': """ 65 | riscv64-linux-gnu-as output.s -o output.o \ 66 | && riscv64-linux-gnu-ld output.o -L corelib -lcore-rush-riscv-lp64d -static -nostdlib -no-relax -o test \ 67 | && qemu-riscv64 ./test 68 | """, 69 | 'rush-transpiler-c': 'gcc output.c -o out && ./out', 70 | } 71 | 72 | 73 | def run(): 74 | failed = [] 75 | tests_ran = 0 76 | 77 | backends_ran = set() 78 | failed_backends = set() 79 | 80 | for name, cmd in backends.items(): 81 | if not name.endswith(sys.argv[2] if len(sys.argv) == 3 else ''): 82 | continue 83 | backends_ran.add(name) 84 | os.chdir(f'../../crates/{name}') 85 | for file, code in tests.items(): 86 | 87 | if name == 'rush-compiler-wasm' and str(file).startswith( 88 | './pointers' 89 | ): 90 | print(f'\x1b[2m\x1b[2mSKIP\x1b[1;0m: {file.ljust(15)} {name}') 91 | continue 92 | 93 | tests_ran += 1 94 | if not run_test(file, code, name, cmd): 95 | failed_backends.add(name) 96 | failed += [[name, file]] 97 | 98 | print('=== Summary ===') 99 | if tests_ran == 0: 100 | print(' => WARNING: no tests executed') 101 | else: 102 | print(f' => {len(failed)} of {tests_ran} test(s) failed') 103 | for name, _ in backends.items(): 104 | if name in backends_ran: 105 | if name in failed_backends: 106 | print(f' \x1b[1;31mFAIL\x1b[1;0m: {name}') 107 | else: 108 | print(f' \x1b[1;32mPASS\x1b[1;0m: {name}') 109 | else: 110 | print(f' \x1b[2mSKIP\x1b[1;0m: {name}') 111 | 112 | if failed: 113 | sys.exit(1) 114 | 115 | 116 | def run_test(file: str, code: int, name: str, cmd: str): 117 | print(f'\x1b[2m\x1b[1;39mRUNNING\x1b[1;0m: {file.ljust(15)} {name}') 118 | 119 | command = 'cargo r ../../samples/tests/' + file 120 | if cmd: 121 | command += '&&' + cmd 122 | 123 | p = subprocess.run( 124 | command, 125 | shell=True, 126 | stdout=subprocess.PIPE, 127 | stderr=subprocess.PIPE, 128 | ) 129 | 130 | print('\x1b[1A', end='') 131 | 132 | if p.returncode != code: 133 | print( 134 | f'\x1b[1;31mFAIL\x1b[1;0m: {file}/{name} => expected {code}, got {p.returncode}' 135 | ) 136 | return False 137 | 138 | print(f'\x1b[1;32mPASS\x1b[1;0m: {file.ljust(15)} {name} {" " * 10}') 139 | return True 140 | 141 | 142 | if __name__ == '__main__': 143 | if len(sys.argv) < 2 or len(sys.argv) > 3: 144 | print( 145 | f'Expected at least one, at most two arguments, got {len(sys.argv) - 1}' 146 | ) 147 | exit(1) 148 | 149 | if sys.argv[1] == 'run': 150 | run() 151 | elif sys.argv[1] == 'count-tests': 152 | print(len(tests)) 153 | else: 154 | print(f'Invalid command-line argument: {sys.argv[1]}') 155 | exit(1) 156 | -------------------------------------------------------------------------------- /samples/tests/pointers/as_return_type.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _res = returns_pointer(); 3 | exit(69) 4 | } 5 | 6 | fn returns_pointer() -> **int { 7 | let mut a = 42; 8 | let mut to_a = &a; 9 | &to_a 10 | } 11 | -------------------------------------------------------------------------------- /samples/tests/pointers/assignment_edge_cases.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut two = 2; 3 | let mut to_two = &two; 4 | let to_to_two = &to_two; 5 | 6 | **to_to_two **= 3; // two is now 8 7 | 8 | let mut a = 'a'; 9 | let to_a = &a; 10 | 11 | let mut b = 'b'; 12 | let to_b = &b; 13 | 14 | *to_a += *to_b; // a is now 67 as char 15 | 16 | *to_a -= 'c'; // a is now 96 as char 17 | 18 | // expected exit-code: 104 19 | exit(a as int + two) 20 | } 21 | -------------------------------------------------------------------------------- /samples/tests/pointers/assignments.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | //// int //// 3 | let mut two = 2; 4 | let to_two: *int = &two; 5 | *to_two **= 3; // two is now 8 6 | *to_two += 2 ** 2; // two is now 12 7 | 8 | //// bool //// 9 | let mut false_ = false; 10 | let to_false: *bool = &false_; 11 | *to_false = true; // false_ is now true 12 | 13 | //// char //// 14 | let mut a = 'a'; 15 | let to_a: *char = &a; 16 | *to_a += '3'; // a is now 20 17 | 18 | //// float //// 19 | let mut five = 5f; 20 | let to_five: *float = &five; 21 | *to_five *= 2.0; // five is now 10 22 | 23 | // expected exit-code: 43 24 | exit( 25 | *to_two // 12 26 | + *to_false as int // 1 27 | + *to_a as int // 20 28 | + *to_five as int // 10 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /samples/tests/pointers/basic.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut answer = 42; 3 | let to_answer = &answer; 4 | exit(*to_answer) 5 | } 6 | -------------------------------------------------------------------------------- /samples/tests/pointers/depth.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | //// int //// 3 | let mut base = 5; 4 | let mut a = &base; 5 | let mut b = &a; 6 | let mut c = &b; 7 | let mut d = &c; 8 | let mut f = &d; 9 | let mut g = &f; 10 | let mut h = &g; 11 | let mut i = &h; 12 | let mut j = &i; 13 | let mut k = &j; 14 | let mut l = &k; 15 | let mut m = &l; 16 | let mut n = &m; 17 | let mut o = &n; 18 | let mut p = &o; 19 | let mut q = &p; 20 | let mut r = &q; 21 | let mut s = &r; 22 | let mut t = &s; 23 | let mut u = &t; 24 | let mut v = &u; 25 | let mut w = &v; 26 | let mut x = &w; 27 | let mut y = &x; 28 | let mut z0 = &y; 29 | *************************z0 *= 2; // base is now 10 30 | 31 | //// bool //// 32 | let mut base = false; 33 | let mut a = &base; 34 | let mut b = &a; 35 | let mut c = &b; 36 | let mut d = &c; 37 | let mut f = &d; 38 | let mut g = &f; 39 | let mut h = &g; 40 | let mut i = &h; 41 | let mut j = &i; 42 | let mut k = &j; 43 | let mut l = &k; 44 | let mut m = &l; 45 | let mut n = &m; 46 | let mut o = &n; 47 | let mut p = &o; 48 | let mut q = &p; 49 | let mut r = &q; 50 | let mut s = &r; 51 | let mut t = &s; 52 | let mut u = &t; 53 | let mut v = &u; 54 | let mut w = &v; 55 | let mut x = &w; 56 | let mut y = &x; 57 | let mut z1 = &y; 58 | *************************z1 = true; // base is now true 59 | 60 | //// char //// 61 | let mut base = 'a'; 62 | let mut a = &base; 63 | let mut b = &a; 64 | let mut c = &b; 65 | let mut d = &c; 66 | let mut f = &d; 67 | let mut g = &f; 68 | let mut h = &g; 69 | let mut i = &h; 70 | let mut j = &i; 71 | let mut k = &j; 72 | let mut l = &k; 73 | let mut m = &l; 74 | let mut n = &m; 75 | let mut o = &n; 76 | let mut p = &o; 77 | let mut q = &p; 78 | let mut r = &q; 79 | let mut s = &r; 80 | let mut t = &s; 81 | let mut u = &t; 82 | let mut v = &u; 83 | let mut w = &v; 84 | let mut x = &w; 85 | let mut y = &x; 86 | let mut z2 = &y; 87 | *************************z2 += 3 as char; // base is now 100 88 | 89 | //// float //// 90 | let mut base = 4.1; 91 | let mut a = &base; 92 | let mut b = &a; 93 | let mut c = &b; 94 | let mut d = &c; 95 | let mut f = &d; 96 | let mut g = &f; 97 | let mut h = &g; 98 | let mut i = &h; 99 | let mut j = &i; 100 | let mut k = &j; 101 | let mut l = &k; 102 | let mut m = &l; 103 | let mut n = &m; 104 | let mut o = &n; 105 | let mut p = &o; 106 | let mut q = &p; 107 | let mut r = &q; 108 | let mut s = &r; 109 | let mut t = &s; 110 | let mut u = &t; 111 | let mut v = &u; 112 | let mut w = &v; 113 | let mut x = &w; 114 | let mut y = &x; 115 | let mut z3 = &y; 116 | *************************z3 += 5.9; // base is now 10.0 117 | 118 | // expected exit-code: 121 119 | exit( 120 | *************************z0 // 10 121 | + *************************z1 as int // 1 122 | + *************************z2 as int // 100 123 | + *************************z3 as int // 10 */ 124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /samples/tests/pointers/for_loop.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut ten = 0; 3 | let mut count = 0; 4 | 5 | for ptr = &ten; *ptr < 10; *ptr += 1 { 6 | if *ptr %2 == 0 { 7 | count += 1; 8 | } 9 | } 10 | 11 | // expected exit-code: 15 12 | exit(ten + count) 13 | } 14 | -------------------------------------------------------------------------------- /samples/tests/pointers/globals.rush: -------------------------------------------------------------------------------- 1 | let mut two = 2; 2 | 3 | fn main() { 4 | let to_two = &two; 5 | *to_two += 2; 6 | mutate(); 7 | *to_two += 4; 8 | exit(*to_two) 9 | } 10 | 11 | fn mutate() { 12 | let mut to_two = &two; 13 | let to_to_two = &to_two; 14 | 15 | **to_to_two *= 10; // two is now 40 16 | } 17 | -------------------------------------------------------------------------------- /samples/tests/pointers/in_params.rush: -------------------------------------------------------------------------------- 1 | // This example will exit with code `2` on RISC-V. 2 | // Because both `bar` and `baz` store their first variable at the same offset, 3 | // the value to which `res` points to is overwritten by the call to `baz`. 4 | // Therefore, in order to use pointers safely, functions should not return 5 | // pointers but can take them as their parameters. 6 | /* 7 | fn main() { 8 | let res = bar(); 9 | baz(); 10 | exit(*res) 11 | } 12 | 13 | fn bar() -> *int { 14 | let mut one = 1; 15 | &one 16 | } 17 | 18 | fn baz() { 19 | let _two = 2; 20 | } 21 | */ 22 | 23 | fn main() { 24 | let mut forty = 40; 25 | mutate(&forty); 26 | exit(forty); 27 | } 28 | 29 | fn mutate(mut ptr: *int) { 30 | *ptr += 2; 31 | mutate_2(&ptr); 32 | } 33 | 34 | fn mutate_2(ptr: **int) { 35 | **ptr += 3 36 | } 37 | -------------------------------------------------------------------------------- /samples/tests/pointers/shadow_ref.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut a = 41; 3 | let a = &a; 4 | *a += 1; 5 | exit(*a); 6 | } 7 | -------------------------------------------------------------------------------- /samples/tests/pointers/swap.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut ten = 10; 3 | let mut twenty = 20; 4 | 5 | let mut to_ten = &ten; 6 | let mut to_twenty = &twenty; 7 | 8 | *to_ten *= 3; // 30 9 | *to_twenty *= 3; // 60 10 | 11 | // pointers are swapped here 12 | to_ten = &twenty; 13 | to_twenty = &ten; 14 | 15 | *to_ten -= 5; // 60 - 5 16 | *to_twenty += 5; // 30 + 5 17 | 18 | exit(twenty - ten + 27) 19 | } 20 | -------------------------------------------------------------------------------- /samples/tests/pointers/types.rush: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut int = 0; 3 | let mut bool = false; 4 | let mut char = 0 as char; 5 | let mut float = 0.0; 6 | 7 | let mut to_int = ∫ 8 | let mut to_bool = &bool; 9 | let mut to_char = &char; 10 | let mut to_float = &float; 11 | 12 | mutate( 13 | &to_int, 14 | &to_bool, 15 | &to_char, 16 | &to_float, 17 | ); 18 | 19 | // using the results of the subsequent calls would be unsafe 20 | let _ = int(); 21 | let _ = bool(); 22 | let _ = char(); 23 | let _ = float(); 24 | 25 | exit( 26 | char as int 27 | - (int + bool as int + float as int + 10) 28 | ) 29 | } 30 | 31 | fn mutate( 32 | a: **int, 33 | b: **bool, 34 | c: **char, 35 | d: **float, 36 | ) { 37 | **a += 20; 38 | **b = true; 39 | **c = 'a'; 40 | **d += 20.0; 41 | } 42 | 43 | // the subsequent functions are not actually used 44 | // however, they are still included in this file 45 | // in order to test if they break compilation 46 | 47 | fn int() -> **int { 48 | let mut a = 0; 49 | let mut b = &a; 50 | &b 51 | } 52 | 53 | fn bool() -> **bool { 54 | let mut a = true; 55 | let mut b = &a; 56 | &b 57 | } 58 | 59 | fn char() -> **char { 60 | let mut a = 'a'; 61 | let mut b = &a; 62 | &b 63 | } 64 | 65 | fn float() -> **float { 66 | let mut a = 1.0; 67 | let mut b = &a; 68 | &b 69 | } 70 | -------------------------------------------------------------------------------- /specification.md: -------------------------------------------------------------------------------- 1 | # Semantic Language Specification 2 | 3 | For an overview about rush's syntax, please consult the 4 | [grammar](./grammar.ebnf). 5 | 6 | ## Semantics 7 | 8 | Each rush program consists of an arbitrary amount of functions and global 9 | variables. In order to create a valid program, the [`main`](#the-main-function) 10 | function needs to be declared. 11 | 12 | ### Functions 13 | 14 | In rush, there cannot be top-level code other than function declarations and 15 | globals. 16 | 17 | #### The Main Function 18 | 19 | The `main` function serves as the entry to a rush program, so that code starts 20 | executing from here. This concept is very similar to the `main` function in 21 | [Rust](https://www.rust-lang.org/) or in 22 | [C](https://en.wikipedia.org/wiki/C_(programming_language)). The function 23 | signature of the `main` function has to look like this: 24 | 25 | ```rs 26 | fn main() { 27 | // ... 28 | } 29 | ``` 30 | 31 | Therefore the `main` function cannot take any arguments or return a non-unit 32 | type value. 33 | 34 | #### Builtin Functions 35 | 36 | ##### Exit 37 | 38 | ```rs 39 | fn exit(code: i32) -> !; 40 | ``` 41 | 42 | The `exit` function calls the operating system, demanding to quit the program 43 | with the specified exit-code. 44 | 45 | ## Types 46 | 47 | | Notation | Example Value | Size | Values | 48 | | ----------- | ------------- | ------ | ------------------------------ | 49 | | `int` | 42 | 64 bit | $- 2 ^{63} \le x \lt 2 ^ {63}$ | 50 | | `float` | 3.1415 | 64 bit | IEEE float values | 51 | | `char` | 'a' | 8 bit | $0 \le x \le 127$ | 52 | | `bool` | true | 1 bit | `true` and `false` | 53 | | `()` (unit) | no value | 1 bit | no values | 54 | 55 | ## Prefix Operators 56 | 57 | | Operator | Operand Type | Produces (Type) | 58 | | -------- | ------------- | --------------- | 59 | | - | `int` | `int` | 60 | | ! | `bool`, `int` | same as operand | 61 | 62 | ## Infix Operators 63 | 64 | ### Arithmetic Operators 65 | 66 | | Operator | Operand Types | Produces (Type) | 67 | | -------- | ---------------------- | ---------------- | 68 | | + | `int`, `char`, `float` | same as operands | 69 | | - | `int`, `char`, `float` | same as operands | 70 | | * | `int`, `float` | same as operands | 71 | | / | `int`, `float` | same as operands | 72 | | % | `int` | `int` | 73 | | ** | `int` | `int` | 74 | 75 | > **Note:** Division by zero using `/` or `%` is undefined behavior and may vary 76 | > per backend. 77 | 78 | ### Bitwise Operators 79 | 80 | | Operator | Operand Types | Produces (Type) | 81 | | -------- | ------------- | ---------------- | 82 | | << | `int` | `int` | 83 | | >> | `int` | `int` | 84 | | \| | `int`, `bool` | same as operands | 85 | | \& | `int`, `bool` | same as operands | 86 | | \^ | `int`, `bool` | same as operands | 87 | 88 | > **Note:** Shifting by a number outside the range `0..=63` is undefined 89 | > behavior and may vary per backend. 90 | 91 | ### Logical Operators 92 | 93 | | Operator | Operand Types | Produces (Type) | 94 | | -------- | ------------------------------ | --------------- | 95 | | && | `bool` | `bool` | 96 | | \|\| | `bool` | `bool` | 97 | | < | `int`, `char`, `float` | `bool` | 98 | | <= | `int`, `char`, `float` | `bool` | 99 | | > | `int`, `char`, `float` | `bool` | 100 | | >= | `int`, `char`, `float` | `bool` | 101 | | == | `int`, `float`, `bool`, `char` | `bool` | 102 | | != | `int`, `float`, `bool`, `char` | `bool` | 103 | 104 | > **Note:** All logical infix operators require values of equal types on their 105 | > left- and right-hand sides. 106 | 107 | ## Type Cast 108 | 109 | The rush language supports conversion between types. The basic syntax looks like 110 | this: 111 | 112 | 113 | ```rs 114 | value as type 115 | ``` 116 | 117 | ### Valid Casts 118 | 119 | | From | To | Notes | 120 | | ------- | ------- | ------------------------------------------ | 121 | | `int` | `int` | redundant | 122 | | `int` | `float` | | 123 | | `int` | `bool` | `res` = `int` != 0 | 124 | | `int` | `char` | [defined here](#a-note-on-casting-to-char) | 125 | | `float` | `int` | truncate | 126 | | `float` | `float` | redundant | 127 | | `float` | `bool` | `res` = `float` != 0.0 | 128 | | `float` | `char` | [defined here](#a-note-on-casting-to-char) | 129 | | `bool` | `bool` | redundant | 130 | | `bool` | `int` | `true` = 1 \| `false` = 0 | 131 | | `bool` | `float` | | 132 | | `bool` | `char` | | 133 | | `char` | `char` | redundant | 134 | | `char` | `int` | | 135 | | `char` | `float` | | 136 | | `char` | `bool` | `res` = `int(char)` != 0 | 137 | 138 | ### A Note on Casting to Char 139 | 140 | When casting `int` or `float` values to char, any source value $\lt 0$ is 141 | transformed into a `char` with the value $0$. Furthermore, if the source value 142 | is $\gt127$, the resulting char will have the value $127$. These limitations are 143 | due to chars only containing valid `ASCII` characters which lie in the range $0 144 | \le x \le 127$. 145 | --------------------------------------------------------------------------------