├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── corelib ├── bool.runic ├── corelib.runic ├── f32.runic ├── f64.runic ├── i128.runic ├── i16.runic ├── i32.runic ├── i64.runic ├── i8.runic ├── string.runic ├── u128.runic ├── u16.runic ├── u32.runic ├── u64.runic └── u8.runic ├── doc ├── Makefile ├── runic-compile.1.txt ├── runic-documentation.1.txt ├── runic-interactive.1.txt └── runic.1.txt ├── shard.lock ├── shard.yml ├── src ├── ast.cr ├── c │ └── llvm.cr ├── cli.cr ├── codegen.cr ├── codegen │ ├── control_flow.cr │ ├── debug.cr │ ├── functions.cr │ ├── intrinsics.cr │ ├── literals.cr │ ├── operators.cr │ ├── pointers.cr │ └── structures.cr ├── compiler.cr ├── config.cr ├── data_layout.cr ├── definitions.cr ├── documentation.cr ├── documentation │ ├── generator.cr │ ├── html_generator.cr │ ├── json_generator.cr │ └── yaml_generator.cr ├── errors.cr ├── intrinsics.runic ├── lexer.cr ├── llvm.cr ├── location.cr ├── mangler.cr ├── parser.cr ├── program.cr ├── runic-ast.cr ├── runic-compile.cr ├── runic-documentation.cr ├── runic-interactive.cr ├── runic-lex.cr ├── runic.cr ├── scope.cr ├── semantic.cr ├── semantic │ ├── initialized_instance_variables.cr │ ├── namespace_visitor.cr │ ├── sugar_expander_visitor.cr │ ├── type_visitor.cr │ └── visitor.cr ├── target.cr ├── token.cr ├── type.cr └── version.cr └── test ├── codegen ├── control_flow_test.cr ├── functions_test.cr ├── literals_test.cr ├── operators_test.cr ├── structures_test.cr └── test_helper.cr ├── data_layout_test.cr ├── lexer_test.cr ├── location_test.cr ├── mangler_test.cr ├── parser_test.cr ├── program_test.cr ├── semantic ├── namespace_visitor_test.cr ├── sugar_expander_visitor_test.cr └── type_visitor_test.cr ├── target_test.cr └── test_helper.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /dist 3 | /lib 4 | /libexec 5 | /doc/man1 6 | /src/c/llvm 7 | /Gemfile.lock 8 | *.o 9 | *.ll 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## UNRELEASED 4 | 5 | Initial development: 6 | - Lexer: tokenizes numbers, operators, comments, ... 7 | - Parser: parses literals, variables, unary and binary expressions 8 | - Semantic analyzer: types all expressions of the AST 9 | - Initial tools: 10 | - `runic lex` to lex a source file and print tokens 11 | - `runic ast` to parse a source file and print the AST 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | gem "asciidoctor" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runic-lang/compiler/1d79f5ff706b8935a30f8308ea3ef9fbb4bb65f2/LICENSE -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | .SUFFIXES: 3 | 4 | CRYSTAL = crystal 5 | LLVM_CONFIG = llvm-config-8 6 | C2CR = CFLAGS=`$(LLVM_CONFIG) --cflags` lib/clang/bin/c2cr 7 | 8 | COMMON_SOURCES = src/version.cr src/config.cr 9 | LEXER_SOURCES = $(COMMON_SOURCES) src/definitions.cr src/errors.cr src/lexer.cr src/location.cr src/token.cr 10 | PARSER_SOURCES = $(LEXER_SOURCES) src/parser.cr src/mangler.cr src/ast.cr src/type.cr src/program.cr 11 | SEMANTIC_SOURCES = $(PARSER_SOURCES) src/semantic.cr src/semantic/*.cr 12 | LLVM_SOURCES = src/llvm.cr src/c/llvm.cr 13 | CODEGEN_SOURCES = $(SEMANTIC_SOURCES) src/codegen.cr src/codegen/*.cr $(LLVM_SOURCES) 14 | DOCUMENTATION_SOURCES = $(SEMANTIC_SOURCES) src/documentation.cr src/documentation/*.cr 15 | 16 | COMMANDS = bin/runic 17 | COMMANDS += libexec/runic-ast 18 | COMMANDS += libexec/runic-compile 19 | COMMANDS += libexec/runic-documentation 20 | COMMANDS += libexec/runic-interactive 21 | COMMANDS += libexec/runic-lex 22 | 23 | all: $(COMMANDS) doc 24 | 25 | dist: doc .phony 26 | mkdir -p dist/doc 27 | cp -r VERSION doc/man1 dist/doc 28 | cd dist && ln -sf ../src . 29 | cd dist && make -f ../Makefile CRFLAGS=--release $(COMMANDS) 30 | rm dist/src 31 | cp -r ../corelib . 32 | 33 | bin/runic: src/runic.cr $(COMMON_SOURCES) 34 | @mkdir -p bin 35 | $(CRYSTAL) build -o bin/runic src/runic.cr src/version.cr $(CRFLAGS) 36 | 37 | libexec/runic-lex: src/runic-lex.cr $(LEXER_SOURCES) 38 | @mkdir -p libexec 39 | $(CRYSTAL) build -o libexec/runic-lex src/runic-lex.cr $(CRFLAGS) 40 | 41 | libexec/runic-ast: src/runic-ast.cr $(SEMANTIC_SOURCES) 42 | @mkdir -p libexec 43 | $(CRYSTAL) build -o libexec/runic-ast src/runic-ast.cr $(CRFLAGS) 44 | 45 | libexec/runic-compile: src/runic-compile.cr src/compiler.cr $(CODEGEN_SOURCES) 46 | @mkdir -p libexec 47 | $(CRYSTAL) build -o libexec/runic-compile src/runic-compile.cr $(CRFLAGS) 48 | 49 | libexec/runic-interactive: src/runic-interactive.cr $(CODEGEN_SOURCES) 50 | @mkdir -p libexec 51 | $(CRYSTAL) build -o libexec/runic-interactive src/runic-interactive.cr $(CRFLAGS) 52 | 53 | libexec/runic-documentation: src/runic-documentation.cr $(DOCUMENTATION_SOURCES) 54 | @mkdir -p libexec 55 | $(CRYSTAL) build -o libexec/runic-documentation src/runic-documentation.cr $(CRFLAGS) 56 | 57 | doc: .phony 58 | cd doc && make 59 | 60 | libllvm: lib/clang/bin/c2cr 61 | @mkdir -p src/c/llvm/transforms 62 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Analysis.h > src/c/llvm/analysis.cr 63 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Core.h > src/c/llvm/core.cr 64 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/DebugInfo.h > src/c/llvm/debug_info.cr 65 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/ErrorHandling.h > src/c/llvm/error_handling.cr 66 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/ExecutionEngine.h > src/c/llvm/execution_engine.cr 67 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Initialization.h > src/c/llvm/initialization.cr 68 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Target.h > src/c/llvm/target.cr 69 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/TargetMachine.h > src/c/llvm/target_machine.cr 70 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Transforms/IPO.h > src/c/llvm/transforms/ipo.cr 71 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Transforms/PassManagerBuilder.h > src/c/llvm/transforms/pass_manager_builder.cr 72 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Transforms/Scalar.h > src/c/llvm/transforms/scalar.cr 73 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Transforms/Utils.h > src/c/llvm/transforms/utils.cr 74 | $(C2CR) --remove-enum-prefix=LLVM --remove-enum-suffix llvm-c/Types.h > src/c/llvm/types.cr 75 | 76 | lib/clang/bin/c2cr: .phony 77 | cd lib/clang && make 78 | 79 | clean: .phony 80 | rm -rf $(COMMANDS) dist src/c/llvm 81 | cd doc && make clean 82 | 83 | test: .phony 84 | $(CRYSTAL) run `find test -iname "*_test.cr"` -- --verbose 85 | 86 | .phony: 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Runic language compiler 2 | 3 | A toy project to build a compiler for an in-design toy language named RUNIC. 4 | 5 | Despite being advertised as a toy language and toy project, the end goal is to 6 | have a stable, robust and fast programming language and compiler, that can't be 7 | considered a "toy" anymore. 8 | 9 | ## Goals 10 | 11 | - General purpose compiled language (for libraries, executables, embedded, ...). 12 | - C-like language with good performance (but safer). 13 | - Ruby-like syntax that pleases the eye (but typed). 14 | - Minimal runtime (if any) to manipulate pointers and intrinsics. 15 | 16 | A separate, cross-platform, and optional standard-library may come later, as a 17 | separated project. 18 | 19 | ## Status 20 | 21 | Basic lexer, parser and semantic analyzes are functional (thought limited to the 22 | available feature set). Preliminary code generation has been started. 23 | 24 | Only integer and floating point numbers are supported. External symbols, 25 | function defintions and calling symbols/functions is also supported. Neither 26 | pointers or data structures are available. 27 | 28 | ## Licences 29 | 30 | The Runic compiler is released under the 31 | [CeCILL-C](http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html) license. 32 | The license agreement can be found in LICENSE. 33 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /corelib/bool.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct bool 3 | #[inline] 4 | def ! : bool 5 | if self 6 | false 7 | else 8 | true 9 | end 10 | end 11 | 12 | #[primitive] 13 | def ==(other : bool) : bool 14 | end 15 | 16 | #[inline] 17 | #def ==(other : T) : bool forall T 18 | # false 19 | #end 20 | 21 | #[primitive] 22 | def !=(other : bool) : bool 23 | end 24 | 25 | #[inline] 26 | #def !=(other : T) : bool forall T 27 | # true 28 | #end 29 | 30 | #[inline] 31 | def &&(other : bool) : bool 32 | if self 33 | other 34 | else 35 | false 36 | end 37 | end 38 | 39 | #[inline] 40 | def ||(other : bool) : bool 41 | if self 42 | true 43 | else 44 | other 45 | end 46 | end 47 | 48 | #[inline] 49 | def &(other : bool) : bool 50 | if self 51 | other 52 | else 53 | false 54 | end 55 | end 56 | 57 | #[inline] 58 | def |(other : bool) : bool 59 | if self 60 | true 61 | else 62 | other 63 | end 64 | end 65 | 66 | #[inline] 67 | def ^(other : bool) : bool 68 | self != other 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /corelib/corelib.runic: -------------------------------------------------------------------------------- 1 | require "./bool" 2 | 3 | require "./i8" 4 | require "./u8" 5 | 6 | require "./i16" 7 | require "./u16" 8 | 9 | require "./i32" 10 | require "./u32" 11 | 12 | require "./i64" 13 | require "./u64" 14 | 15 | require "./i128" 16 | require "./u128" 17 | 18 | require "./f32" 19 | require "./f64" 20 | 21 | require "./string" 22 | -------------------------------------------------------------------------------- /corelib/f32.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct f32 3 | # INFINITY = 1_f32 / 0 4 | # MIN_NORMAL = 1.1754943508222875e-38_f32 # 2.0**-126 5 | # MIN = 1.401298464324817e-45_f32 # 2.0**-149 6 | # MAX = 3.4028234663852886e38_f32 # (2.0 - 2**-23) * 2**127 7 | # NaN = 0_f32 / 0 8 | # SIZE = 32 9 | 10 | #[primitive] 11 | def to_i8 : i8 12 | end 13 | 14 | #[primitive] 15 | def to_u8 : u8 16 | end 17 | 18 | #[primitive] 19 | def to_i16 : i16 20 | end 21 | 22 | #[primitive] 23 | def to_u16 : u16 24 | end 25 | 26 | #[primitive] 27 | def to_i32 : i32 28 | end 29 | 30 | #[primitive] 31 | def to_u32 : u32 32 | end 33 | 34 | #[primitive] 35 | def to_i64 : i64 36 | end 37 | 38 | #[primitive] 39 | def to_u64 : u64 40 | end 41 | 42 | #[primitive] 43 | #[feature(i128)] 44 | def to_i128 : i128 45 | end 46 | 47 | #[primitive] 48 | #[feature(i128)] 49 | def to_u128 : u128 50 | end 51 | 52 | #[inline] 53 | def to_f32 : f32 54 | self 55 | end 56 | 57 | #[primitive] 58 | def to_f64 : f64 59 | end 60 | 61 | #[inline] 62 | def to_i : i32 63 | self.to_i32 64 | end 65 | 66 | #[inline] 67 | def to_u : u32 68 | self.to_u32 69 | end 70 | 71 | #[inline] 72 | def to_f : f64 73 | self.to_f64 74 | end 75 | #[inline] 76 | #[inline] 77 | #[inline] 78 | 79 | #[primitive] 80 | def +(other : f32) : f32 81 | end 82 | 83 | #[primitive] 84 | def -(other : f32) : f32 85 | end 86 | 87 | #[primitive] 88 | def *(other : f32) : f32 89 | end 90 | 91 | #[primitive] 92 | def /(other : f32) : f32 93 | end 94 | 95 | #[primitive] 96 | def %(other : f32) : f32 97 | end 98 | 99 | #[primitive] 100 | def **(other : f32) : f32 101 | end 102 | 103 | #[inline] 104 | def abs : f32 105 | if self < 0 106 | -self 107 | else 108 | self 109 | end 110 | end 111 | 112 | #[primitive] 113 | def floor : f32 114 | end 115 | 116 | #[primitive] 117 | def ceil : f32 118 | end 119 | 120 | #[primitive] 121 | def truncate : f32 122 | end 123 | 124 | #[inline] 125 | def //(other : f32) : f32 126 | q = (self / other).truncate() 127 | r = self % other 128 | if r < 0 129 | if other > 0 130 | q - 1 131 | else 132 | q + 1 133 | end 134 | else 135 | q 136 | end 137 | end 138 | 139 | #[inline] 140 | def %%(other : f32) : f32 141 | result = self % other 142 | if result < 0 143 | result + other.abs() 144 | else 145 | result 146 | end 147 | end 148 | 149 | #[primitive] 150 | def ==(other : f32) : bool 151 | end 152 | 153 | #[primitive] 154 | def !=(other : f32) : bool 155 | end 156 | 157 | #[primitive] 158 | def <(other : f32) : bool 159 | end 160 | 161 | #[primitive] 162 | def <=(other : f32) : bool 163 | end 164 | 165 | #[primitive] 166 | def >(other : f32) : bool 167 | end 168 | 169 | #[primitive] 170 | def >=(other : f32) : bool 171 | end 172 | 173 | #[inline] 174 | def <=>(other : f32) : i32 175 | if self < other 176 | -1 177 | elsif self > other 178 | 1 179 | else 180 | 0 181 | end 182 | end 183 | 184 | ##[primitive] 185 | #def - : f32 186 | #end 187 | 188 | ##[primitive] 189 | #def + : f32 190 | #end 191 | 192 | #[inline] 193 | def ! : bool 194 | false 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /corelib/f64.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct f64 3 | # INFINITY = 1_f64 / 0 4 | # MIN_NORMAL = 2.2250738585072014e-308_f64 # 2.0**-1022 5 | # MIN = 4.9e-324_f64 # 2.0**-1074 6 | # MAX = 1.7976931348623157e308_f64 # (2.0 - 2**-52) * 2**1023 7 | # NaN = 0_f64 / 0 8 | # SIZE = 64 9 | 10 | #[primitive] 11 | def to_i8 : i8 12 | end 13 | 14 | #[primitive] 15 | def to_u8 : u8 16 | end 17 | 18 | #[primitive] 19 | def to_i16 : i16 20 | end 21 | 22 | #[primitive] 23 | def to_u16 : u16 24 | end 25 | 26 | #[primitive] 27 | def to_i32 : i32 28 | end 29 | 30 | #[primitive] 31 | def to_u32 : u32 32 | end 33 | 34 | #[primitive] 35 | def to_i64 : i64 36 | end 37 | 38 | #[primitive] 39 | def to_u64 : u64 40 | end 41 | 42 | #[primitive] 43 | #[feature(i128)] 44 | def to_i128 : i128 45 | end 46 | 47 | #[primitive] 48 | #[feature(i128)] 49 | def to_u128 : u128 50 | end 51 | 52 | #[primitive] 53 | def to_f32 : f32 54 | end 55 | 56 | #[inline] 57 | def to_f64 : f64 58 | self 59 | end 60 | 61 | #[inline] 62 | def to_i : i32 63 | self.to_i32 64 | end 65 | 66 | #[inline] 67 | def to_u : u32 68 | self.to_u32 69 | end 70 | 71 | #[inline] 72 | def to_f : f64 73 | self.to_f64 74 | end 75 | #[inline] 76 | #[inline] 77 | #[inline] 78 | 79 | #[primitive] 80 | def +(other : f64) : f64 81 | end 82 | 83 | #[primitive] 84 | def -(other : f64) : f64 85 | end 86 | 87 | #[primitive] 88 | def *(other : f64) : f64 89 | end 90 | 91 | #[primitive] 92 | def /(other : f64) : f64 93 | end 94 | 95 | #[primitive] 96 | def %(other : f64) : f64 97 | end 98 | 99 | #[primitive] 100 | def **(other : f64) : f64 101 | end 102 | 103 | #[inline] 104 | def abs : f64 105 | if self < 0 106 | -self 107 | else 108 | self 109 | end 110 | end 111 | 112 | #[primitive] 113 | def floor : f64 114 | end 115 | 116 | #[primitive] 117 | def ceil : f64 118 | end 119 | 120 | #[primitive] 121 | def truncate : f64 122 | end 123 | 124 | #[inline] 125 | def //(other : f64) : f64 126 | q = (self / other).truncate() 127 | r = self % other 128 | if r < 0 129 | if other > 0 130 | q - 1 131 | else 132 | q + 1 133 | end 134 | else 135 | q 136 | end 137 | end 138 | 139 | #[inline] 140 | def %%(other : f64) : f64 141 | result = self % other 142 | if result < 0 143 | result + other.abs() 144 | else 145 | result 146 | end 147 | end 148 | 149 | #[primitive] 150 | def ==(other : f64) : bool 151 | end 152 | 153 | #[primitive] 154 | def !=(other : f64) : bool 155 | end 156 | 157 | #[primitive] 158 | def <(other : f64) : bool 159 | end 160 | 161 | #[primitive] 162 | def <=(other : f64) : bool 163 | end 164 | 165 | #[primitive] 166 | def >(other : f64) : bool 167 | end 168 | 169 | #[primitive] 170 | def >=(other : f64) : bool 171 | end 172 | 173 | #[inline] 174 | def <=>(other : f64) : i32 175 | if self < other 176 | -1 177 | elsif self > other 178 | 1 179 | else 180 | 0 181 | end 182 | end 183 | 184 | ##[primitive] 185 | #def - : f64 186 | #end 187 | 188 | ##[primitive] 189 | #def + : f64 190 | #end 191 | 192 | #[inline] 193 | def ! : bool 194 | false 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /corelib/i128.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | #[feature(i128)] 3 | struct i128 4 | # MIN = -170141183460469231731687303715884105728_i128 5 | # MAX = 170141183460469231731687303715884105727_i128 6 | # SIZE = 128 7 | 8 | #[primitive] 9 | def to_i8 : i8 10 | end 11 | 12 | #[primitive] 13 | def to_u8 : u8 14 | end 15 | 16 | #[primitive] 17 | def to_i16 : i16 18 | end 19 | 20 | #[primitive] 21 | def to_u16 : u16 22 | end 23 | 24 | #[primitive] 25 | def to_i32 : i32 26 | end 27 | 28 | #[primitive] 29 | def to_u32 : u32 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[inline] 41 | def to_i128 : i128 42 | self 43 | end 44 | 45 | #[primitive] 46 | def to_u128 : u128 47 | end 48 | 49 | #[primitive] 50 | def to_f32 : f32 51 | end 52 | 53 | #[primitive] 54 | def to_f64 : f64 55 | end 56 | 57 | #[inline] 58 | def to_i : i32 59 | self.to_i32 60 | end 61 | 62 | #[inline] 63 | def to_u : u32 64 | self.to_u32 65 | end 66 | 67 | #[inline] 68 | def to_f : f64 69 | self.to_f64 70 | end 71 | 72 | #[primitive] 73 | def +(other : i128) : i128 74 | end 75 | 76 | #[primitive] 77 | def -(other : i128) : i128 78 | end 79 | 80 | #[primitive] 81 | def *(other : i128) : i128 82 | end 83 | 84 | #[primitive] 85 | def div(other : i128) : i128 86 | end 87 | 88 | #[primitive] 89 | def %(other : i128) : i128 90 | end 91 | 92 | #[inline] 93 | def /(other : i128) : float 94 | self.to_f / other.to_f 95 | end 96 | 97 | #[inline] 98 | def abs : i128 99 | if self < 0 100 | -self 101 | else 102 | self 103 | end 104 | end 105 | 106 | #[inline] 107 | def //(other : i128) : i128 108 | q = self.div(other) 109 | r = self % other 110 | if r < 0 111 | if other > 0 112 | q - 1 113 | else 114 | q + 1 115 | end 116 | else 117 | q 118 | end 119 | end 120 | 121 | #[inline] 122 | def %%(other : i128) : i128 123 | result = self % other 124 | if result < 0 125 | result + other.abs() 126 | else 127 | result 128 | end 129 | end 130 | 131 | #[inline] 132 | def **(exponent : i128) : i128 133 | if exponent < 0 134 | 1_i128 // (self ** -exponent) 135 | else 136 | x = self 137 | result = 1_i128 138 | 139 | until exponent == 0 140 | unless (exponent & 1) == 0 141 | result *= x 142 | exponent -= 1 143 | end 144 | 145 | x *= x 146 | exponent >>= 1 147 | end 148 | 149 | result 150 | end 151 | end 152 | 153 | #[primitive] 154 | def ==(other : i128) : bool 155 | end 156 | 157 | #[primitive] 158 | def !=(other : i128) : bool 159 | end 160 | 161 | #[primitive] 162 | def <(other : i128) : bool 163 | end 164 | 165 | #[primitive] 166 | def <=(other : i128) : bool 167 | end 168 | 169 | #[primitive] 170 | def >(other : i128) : bool 171 | end 172 | 173 | #[primitive] 174 | def >=(other : i128) : bool 175 | end 176 | 177 | #[inline] 178 | def <=>(other : i128) : i32 179 | if self < other 180 | -1 181 | elsif self > other 182 | 1 183 | else 184 | 0 185 | end 186 | end 187 | 188 | ##[primitive] 189 | #def - : i128 190 | #end 191 | 192 | ##[inline] 193 | #def + : i128 194 | # self 195 | #end 196 | 197 | #[primitive] 198 | def ~ : i128 199 | end 200 | 201 | #[inline] 202 | def ! : bool 203 | false 204 | end 205 | 206 | #[primitive] 207 | def &(other : i128) : i128 208 | end 209 | 210 | #[primitive] 211 | def |(other : i128) : i128 212 | end 213 | 214 | #[primitive] 215 | def ^(other : i128) : i128 216 | end 217 | 218 | #[primitive] 219 | def <<(other : i128) : i128 220 | end 221 | 222 | #[primitive] 223 | def >>(other : i128) : i128 224 | end 225 | end 226 | -------------------------------------------------------------------------------- /corelib/i16.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct i16 3 | # MIN = -32768_i16 4 | # MAX = 32767_i16 5 | # SIZE = 16 6 | 7 | #[primitive] 8 | def to_i8 : i8 9 | end 10 | 11 | #[primitive] 12 | def to_u8 : u8 13 | end 14 | 15 | #[inline] 16 | def to_i16 : i16 17 | self 18 | end 19 | 20 | #[primitive] 21 | def to_u16 : u16 22 | end 23 | 24 | #[primitive] 25 | def to_i32 : i32 26 | end 27 | 28 | #[primitive] 29 | def to_u32 : u32 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : i16) : i16 75 | end 76 | 77 | #[primitive] 78 | def -(other : i16) : i16 79 | end 80 | 81 | #[primitive] 82 | def *(other : i16) : i16 83 | end 84 | 85 | #[primitive] 86 | def div(other : i16) : i16 87 | end 88 | 89 | #[primitive] 90 | def %(other : i16) : i16 91 | end 92 | 93 | #[inline] 94 | def /(other : i16) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : i16 100 | if self < 0 101 | -self 102 | else 103 | self 104 | end 105 | end 106 | 107 | #[inline] 108 | def //(other : i16) : i16 109 | q = self.div(other) 110 | r = self % other 111 | if r < 0 112 | if other > 0 113 | q - 1 114 | else 115 | q + 1 116 | end 117 | else 118 | q 119 | end 120 | end 121 | 122 | #[inline] 123 | def %%(other : i16) : i16 124 | result = self % other 125 | if result < 0 126 | result + other.abs() 127 | else 128 | result 129 | end 130 | end 131 | 132 | #[inline] 133 | def **(exponent : i16) : i16 134 | if exponent < 0 135 | 1_i16 // (self ** -exponent) 136 | else 137 | x = self 138 | result = 1_i16 139 | 140 | until exponent == 0 141 | unless (exponent & 1) == 0 142 | result *= x 143 | exponent -= 1 144 | end 145 | 146 | x *= x 147 | exponent >>= 1 148 | end 149 | 150 | result 151 | end 152 | end 153 | 154 | #[primitive] 155 | def ==(other : i16) : bool 156 | end 157 | 158 | #[primitive] 159 | def !=(other : i16) : bool 160 | end 161 | 162 | #[primitive] 163 | def <(other : i16) : bool 164 | end 165 | 166 | #[primitive] 167 | def <=(other : i16) : bool 168 | end 169 | 170 | #[primitive] 171 | def >(other : i16) : bool 172 | end 173 | 174 | #[primitive] 175 | def >=(other : i16) : bool 176 | end 177 | 178 | #[inline] 179 | def <=>(other : i16) : i32 180 | if self < other 181 | -1 182 | elsif self > other 183 | 1 184 | else 185 | 0 186 | end 187 | end 188 | 189 | ##[primitive] 190 | #def - : i16 191 | #end 192 | 193 | ##[inline] 194 | #def + : i16 195 | # self 196 | #end 197 | 198 | #[primitive] 199 | def ~ : i16 200 | end 201 | 202 | #[inline] 203 | def ! : bool 204 | false 205 | end 206 | 207 | #[primitive] 208 | def &(other : i16) : i16 209 | end 210 | 211 | #[primitive] 212 | def |(other : i16) : i16 213 | end 214 | 215 | #[primitive] 216 | def ^(other : i16) : i16 217 | end 218 | 219 | #[primitive] 220 | def <<(other : i16) : i16 221 | end 222 | 223 | #[primitive] 224 | def >>(other : i16) : i16 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /corelib/i32.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct i32 3 | # MIN = -2147483648_i32 4 | # MAX = 2147483647_i32 5 | # SIZE = 32 6 | 7 | #[primitive] 8 | def to_i8 : i8 9 | end 10 | 11 | #[primitive] 12 | def to_u8 : u8 13 | end 14 | 15 | #[primitive] 16 | def to_i16 : i16 17 | end 18 | 19 | #[primitive] 20 | def to_u16 : u16 21 | end 22 | 23 | #[inline] 24 | def to_i32 : i32 25 | self 26 | end 27 | 28 | #[primitive] 29 | def to_u32 : u32 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : i32) : i32 75 | end 76 | 77 | #[primitive] 78 | def -(other : i32) : i32 79 | end 80 | 81 | #[primitive] 82 | def *(other : i32) : i32 83 | end 84 | 85 | #[primitive] 86 | def div(other : i32) : i32 87 | end 88 | 89 | #[primitive] 90 | def %(other : i32) : i32 91 | end 92 | 93 | #[inline] 94 | def /(other : i32) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : i32 100 | if self < 0 101 | -self 102 | else 103 | self 104 | end 105 | end 106 | 107 | #[inline] 108 | def //(other : i32) : i32 109 | q = self.div(other) 110 | r = self % other 111 | if r < 0 112 | if other > 0 113 | q - 1 114 | else 115 | q + 1 116 | end 117 | else 118 | q 119 | end 120 | end 121 | 122 | #[inline] 123 | def %%(other : i32) : i32 124 | result = self % other 125 | if result < 0 126 | result + other.abs() 127 | else 128 | result 129 | end 130 | end 131 | 132 | #[inline] 133 | def **(exponent : i32) : i32 134 | if exponent < 0 135 | 1_i32 // (self ** -exponent) 136 | else 137 | x = self 138 | result = 1_i32 139 | 140 | until exponent == 0 141 | unless (exponent & 1) == 0 142 | result *= x 143 | exponent -= 1 144 | end 145 | 146 | x *= x 147 | exponent >>= 1 148 | end 149 | 150 | result 151 | end 152 | end 153 | 154 | #[primitive] 155 | def ==(other : i32) : bool 156 | end 157 | 158 | #[primitive] 159 | def !=(other : i32) : bool 160 | end 161 | 162 | #[primitive] 163 | def <(other : i32) : bool 164 | end 165 | 166 | #[primitive] 167 | def <=(other : i32) : bool 168 | end 169 | 170 | #[primitive] 171 | def >(other : i32) : bool 172 | end 173 | 174 | #[primitive] 175 | def >=(other : i32) : bool 176 | end 177 | 178 | #[inline] 179 | def <=>(other : i32) : i32 180 | if self < other 181 | -1 182 | elsif self > other 183 | 1 184 | else 185 | 0 186 | end 187 | end 188 | 189 | ##[primitive] 190 | #def - : i32 191 | #end 192 | 193 | ##[inline] 194 | #def + : i32 195 | # self 196 | #end 197 | 198 | #[primitive] 199 | def ~ : i32 200 | end 201 | 202 | #[inline] 203 | def ! : bool 204 | false 205 | end 206 | 207 | #[primitive] 208 | def &(other : i32) : i32 209 | end 210 | 211 | #[primitive] 212 | def |(other : i32) : i32 213 | end 214 | 215 | #[primitive] 216 | def ^(other : i32) : i32 217 | end 218 | 219 | #[primitive] 220 | def <<(other : i32) : i32 221 | end 222 | 223 | #[primitive] 224 | def >>(other : i32) : i32 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /corelib/i64.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct i64 3 | # MIN = -9223372036854775808_i64 4 | # MAX = 9223372036854775807_i64 5 | # SIZE = 64 6 | 7 | #[primitive] 8 | def to_i8 : i8 9 | end 10 | 11 | #[primitive] 12 | def to_u8 : u8 13 | end 14 | 15 | #[primitive] 16 | def to_i16 : i16 17 | end 18 | 19 | #[primitive] 20 | def to_u16 : u16 21 | end 22 | 23 | #[primitive] 24 | def to_i32 : i32 25 | end 26 | 27 | #[primitive] 28 | def to_u32 : u32 29 | end 30 | 31 | #[inline] 32 | def to_i64 : i64 33 | self 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : i64) : i64 75 | end 76 | 77 | #[primitive] 78 | def -(other : i64) : i64 79 | end 80 | 81 | #[primitive] 82 | def *(other : i64) : i64 83 | end 84 | 85 | #[primitive] 86 | def div(other : i64) : i64 87 | end 88 | 89 | #[primitive] 90 | def %(other : i64) : i64 91 | end 92 | 93 | #[inline] 94 | def /(other : i64) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : i64 100 | if self < 0 101 | -self 102 | else 103 | self 104 | end 105 | end 106 | 107 | #[inline] 108 | def //(other : i64) : i64 109 | q = self.div(other) 110 | r = self % other 111 | if r < 0 112 | if other > 0 113 | q - 1 114 | else 115 | q + 1 116 | end 117 | else 118 | q 119 | end 120 | end 121 | 122 | #[inline] 123 | def %%(other : i64) : i64 124 | result = self % other 125 | if result < 0 126 | result + other.abs() 127 | else 128 | result 129 | end 130 | end 131 | 132 | #[inline] 133 | def **(exponent : i64) : i64 134 | if exponent < 0 135 | 1_i64 // (self ** -exponent) 136 | else 137 | x = self 138 | result = 1_i64 139 | 140 | until exponent == 0 141 | unless (exponent & 1) == 0 142 | result *= x 143 | exponent -= 1 144 | end 145 | 146 | x *= x 147 | exponent >>= 1 148 | end 149 | 150 | result 151 | end 152 | end 153 | 154 | #[primitive] 155 | def ==(other : i64) : bool 156 | end 157 | 158 | #[primitive] 159 | def !=(other : i64) : bool 160 | end 161 | 162 | #[primitive] 163 | def <(other : i64) : bool 164 | end 165 | 166 | #[primitive] 167 | def <=(other : i64) : bool 168 | end 169 | 170 | #[primitive] 171 | def >(other : i64) : bool 172 | end 173 | 174 | #[primitive] 175 | def >=(other : i64) : bool 176 | end 177 | 178 | #[inline] 179 | def <=>(other : i64) : i32 180 | if self < other 181 | -1 182 | elsif self > other 183 | 1 184 | else 185 | 0 186 | end 187 | end 188 | 189 | ##[primitive] 190 | #def - : i64 191 | #end 192 | 193 | ##[inline] 194 | #def + : i64 195 | # self 196 | #end 197 | 198 | #[primitive] 199 | def ~ : i64 200 | end 201 | 202 | #[inline] 203 | def ! : bool 204 | false 205 | end 206 | 207 | #[primitive] 208 | def &(other : i64) : i64 209 | end 210 | 211 | #[primitive] 212 | def |(other : i64) : i64 213 | end 214 | 215 | #[primitive] 216 | def ^(other : i64) : i64 217 | end 218 | 219 | #[primitive] 220 | def <<(other : i64) : i64 221 | end 222 | 223 | #[primitive] 224 | def >>(other : i64) : i64 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /corelib/i8.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct i8 3 | # MIN = -128_i8 4 | # MAX = 127_i8 5 | # SIZE = 8 6 | 7 | #[inline] 8 | def to_i8 : i8 9 | self 10 | end 11 | 12 | #[primitive] 13 | def to_u8 : u8 14 | end 15 | 16 | #[primitive] 17 | def to_i16 : i16 18 | end 19 | 20 | #[primitive] 21 | def to_u16 : u16 22 | end 23 | 24 | #[primitive] 25 | def to_i32 : i32 26 | end 27 | 28 | #[primitive] 29 | def to_u32 : u32 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : i8) : i8 75 | end 76 | 77 | #[primitive] 78 | def -(other : i8) : i8 79 | end 80 | 81 | #[primitive] 82 | def *(other : i8) : i8 83 | end 84 | 85 | #[primitive] 86 | def div(other : i8) : i8 87 | end 88 | 89 | #[primitive] 90 | def %(other : i8) : i8 91 | end 92 | 93 | #[inline] 94 | def /(other : i8) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : i8 100 | if self < 0 101 | -self 102 | else 103 | self 104 | end 105 | end 106 | 107 | #[inline] 108 | def //(other : i8) : i8 109 | q = self.div(other) 110 | r = self % other 111 | if r < 0 112 | if other > 0 113 | q - 1 114 | else 115 | q + 1 116 | end 117 | else 118 | q 119 | end 120 | end 121 | 122 | #[inline] 123 | def %%(other : i8) : i8 124 | result = self % other 125 | if result < 0 126 | result + other.abs() 127 | else 128 | result 129 | end 130 | end 131 | 132 | #[inline] 133 | def **(exponent : i8) : i8 134 | if exponent < 0 135 | 1_i8 // (self ** -exponent) 136 | else 137 | x = self 138 | result = 1_i8 139 | 140 | until exponent == 0 141 | unless (exponent & 1) == 0 142 | result *= x 143 | exponent -= 1 144 | end 145 | 146 | x *= x 147 | exponent >>= 1 148 | end 149 | 150 | result 151 | end 152 | end 153 | 154 | #[primitive] 155 | def ==(other : i8) : bool 156 | end 157 | 158 | #[primitive] 159 | def !=(other : i8) : bool 160 | end 161 | 162 | #[primitive] 163 | def <(other : i8) : bool 164 | end 165 | 166 | #[primitive] 167 | def <=(other : i8) : bool 168 | end 169 | 170 | #[primitive] 171 | def >(other : i8) : bool 172 | end 173 | 174 | #[primitive] 175 | def >=(other : i8) : bool 176 | end 177 | 178 | #[inline] 179 | def <=>(other : i8) : i32 180 | if self < other 181 | -1 182 | elsif self > other 183 | 1 184 | else 185 | 0 186 | end 187 | end 188 | 189 | ##[primitive] 190 | #def - : i8 191 | #end 192 | 193 | ##[inline] 194 | #def + : i8 195 | # self 196 | #end 197 | 198 | #[primitive] 199 | def ~ : i8 200 | end 201 | 202 | #[inline] 203 | def ! : bool 204 | false 205 | end 206 | 207 | #[primitive] 208 | def &(other : i8) : i8 209 | end 210 | 211 | #[primitive] 212 | def |(other : i8) : i8 213 | end 214 | 215 | #[primitive] 216 | def ^(other : i8) : i8 217 | end 218 | 219 | #[primitive] 220 | def <<(other : i8) : i8 221 | end 222 | 223 | #[primitive] 224 | def >>(other : i8) : i8 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /corelib/string.runic: -------------------------------------------------------------------------------- 1 | struct String 2 | @ptr : u8* 3 | @bytesize : uint 4 | 5 | def initialize(ptr : u8*, bytesize : uint) 6 | @ptr = ptr 7 | @bytesize = bytesize 8 | end 9 | 10 | def bytesize 11 | @bytesize 12 | end 13 | 14 | def to_unsafe 15 | @ptr 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /corelib/u128.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | #[feature(i128)] 3 | struct u128 4 | # MIN = 0_u128 5 | # MAX = 340282366920938463463374607431768211455_u128 6 | # SIZE = 128 7 | 8 | #[primitive] 9 | def to_i8 : i8 10 | end 11 | 12 | #[primitive] 13 | def to_u8 : u8 14 | end 15 | 16 | #[primitive] 17 | def to_i16 : i16 18 | end 19 | 20 | #[primitive] 21 | def to_u16 : u16 22 | end 23 | 24 | #[primitive] 25 | def to_i32 : i32 26 | end 27 | 28 | #[primitive] 29 | def to_u32 : u32 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | def to_i128 : i128 42 | end 43 | 44 | #[inline] 45 | def to_u128 : u128 46 | self 47 | end 48 | 49 | #[primitive] 50 | def to_f32 : f32 51 | end 52 | 53 | #[primitive] 54 | def to_f64 : f64 55 | end 56 | 57 | #[inline] 58 | def to_i : i32 59 | self.to_i32 60 | end 61 | 62 | #[inline] 63 | def to_u : u32 64 | self.to_u32 65 | end 66 | 67 | #[inline] 68 | def to_f : f64 69 | self.to_f64 70 | end 71 | 72 | #[primitive] 73 | def +(other : u128) : u128 74 | end 75 | 76 | #[primitive] 77 | def -(other : u128) : u128 78 | end 79 | 80 | #[primitive] 81 | def *(other : u128) : u128 82 | end 83 | 84 | #[primitive] 85 | def div(other : u128) : u128 86 | end 87 | 88 | #[primitive] 89 | def %(other : u128) : u128 90 | end 91 | 92 | #[inline] 93 | def /(other : u128) : float 94 | self.to_f / other.to_f 95 | end 96 | 97 | #[inline] 98 | def abs : u128 99 | self 100 | end 101 | 102 | #[inline] 103 | def //(other : u128) : u128 104 | self.div(other) 105 | end 106 | 107 | #[inline] 108 | def %%(other : u128) : u128 109 | self % other 110 | end 111 | 112 | #[inline] 113 | def **(exponent : u128) : u128 114 | x = self 115 | result = 1_u128 116 | 117 | until exponent == 0 118 | unless (exponent & 1) == 0 119 | result *= x 120 | exponent -= 1 121 | end 122 | 123 | x *= x 124 | exponent >>= 1 125 | end 126 | 127 | result 128 | end 129 | 130 | #[primitive] 131 | def ==(other : u128) : bool 132 | end 133 | 134 | #[primitive] 135 | def !=(other : u128) : bool 136 | end 137 | 138 | #[primitive] 139 | def <(other : u128) : bool 140 | end 141 | 142 | #[primitive] 143 | def <=(other : u128) : bool 144 | end 145 | 146 | #[primitive] 147 | def >(other : u128) : bool 148 | end 149 | 150 | #[primitive] 151 | def >=(other : u128) : bool 152 | end 153 | 154 | #[inline] 155 | def <=>(other : u128) : i32 156 | if self < other 157 | -1 158 | elsif self > other 159 | 1 160 | else 161 | 0 162 | end 163 | end 164 | 165 | #[primitive] 166 | def ~ : u128 167 | end 168 | 169 | #[inline] 170 | def ! : bool 171 | false 172 | end 173 | 174 | #[primitive] 175 | def &(other : u128) : u128 176 | end 177 | 178 | #[primitive] 179 | def |(other : u128) : u128 180 | end 181 | 182 | #[primitive] 183 | def ^(other : u128) : u128 184 | end 185 | 186 | #[primitive] 187 | def <<(other : u128) : u128 188 | end 189 | 190 | #[primitive] 191 | def >>(other : u128) : u128 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /corelib/u16.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct u16 3 | # MIN = 0_u16 4 | # MAX = 65535_u16 5 | # SIZE = 16 6 | 7 | #[primitive] 8 | def to_i8 : i8 9 | end 10 | 11 | #[primitive] 12 | def to_u8 : u8 13 | end 14 | 15 | #[primitive] 16 | def to_i16 : i16 17 | end 18 | 19 | #[inline] 20 | def to_u16 : u16 21 | self 22 | end 23 | 24 | #[primitive] 25 | def to_i32 : i32 26 | end 27 | 28 | #[primitive] 29 | def to_u32 : u32 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : u16) : u16 75 | end 76 | 77 | #[primitive] 78 | def -(other : u16) : u16 79 | end 80 | 81 | #[primitive] 82 | def *(other : u16) : u16 83 | end 84 | 85 | #[primitive] 86 | def div(other : u16) : u16 87 | end 88 | 89 | #[primitive] 90 | def %(other : u16) : u16 91 | end 92 | 93 | #[inline] 94 | def /(other : u16) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : u16 100 | self 101 | end 102 | 103 | #[inline] 104 | def //(other : u16) : u16 105 | self.div(other) 106 | end 107 | 108 | #[inline] 109 | def %%(other : u16) : u16 110 | self % other 111 | end 112 | 113 | #[inline] 114 | def **(exponent : u16) : u16 115 | x = self 116 | result = 1_u16 117 | 118 | until exponent == 0 119 | unless (exponent & 1) == 0 120 | result *= x 121 | exponent -= 1 122 | end 123 | 124 | x *= x 125 | exponent >>= 1 126 | end 127 | 128 | result 129 | end 130 | 131 | #[primitive] 132 | def ==(other : u16) : bool 133 | end 134 | 135 | #[primitive] 136 | def !=(other : u16) : bool 137 | end 138 | 139 | #[primitive] 140 | def <(other : u16) : bool 141 | end 142 | 143 | #[primitive] 144 | def <=(other : u16) : bool 145 | end 146 | 147 | #[primitive] 148 | def >(other : u16) : bool 149 | end 150 | 151 | #[primitive] 152 | def >=(other : u16) : bool 153 | end 154 | 155 | #[inline] 156 | def <=>(other : u16) : i32 157 | if self < other 158 | -1 159 | elsif self > other 160 | 1 161 | else 162 | 0 163 | end 164 | end 165 | 166 | #[primitive] 167 | def ~ : u16 168 | end 169 | 170 | #[inline] 171 | def ! : bool 172 | false 173 | end 174 | 175 | #[primitive] 176 | def &(other : u16) : u16 177 | end 178 | 179 | #[primitive] 180 | def |(other : u16) : u16 181 | end 182 | 183 | #[primitive] 184 | def ^(other : u16) : u16 185 | end 186 | 187 | #[primitive] 188 | def <<(other : u16) : u16 189 | end 190 | 191 | #[primitive] 192 | def >>(other : u16) : u16 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /corelib/u32.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct u32 3 | # MIN = 0_u32 4 | # MAX = 4294967295_u32 5 | # SIZE = 32 6 | 7 | #[primitive] 8 | def to_i8 : i8 9 | end 10 | 11 | #[primitive] 12 | def to_u8 : u8 13 | end 14 | 15 | #[primitive] 16 | def to_i16 : i16 17 | end 18 | 19 | #[primitive] 20 | def to_u16 : u16 21 | end 22 | 23 | #[primitive] 24 | def to_i32 : i32 25 | end 26 | 27 | #[inline] 28 | def to_u32 : u32 29 | self 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : u32) : u32 75 | end 76 | 77 | #[primitive] 78 | def -(other : u32) : u32 79 | end 80 | 81 | #[primitive] 82 | def *(other : u32) : u32 83 | end 84 | 85 | #[primitive] 86 | def div(other : u32) : u32 87 | end 88 | 89 | #[primitive] 90 | def %(other : u32) : u32 91 | end 92 | 93 | #[inline] 94 | def /(other : u32) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : u32 100 | self 101 | end 102 | 103 | #[inline] 104 | def //(other : u32) : u32 105 | self.div(other) 106 | end 107 | 108 | #[inline] 109 | def %%(other : u32) : u32 110 | self % other 111 | end 112 | 113 | #[inline] 114 | def **(exponent : u32) : u32 115 | x = self 116 | result = 1_u32 117 | 118 | until exponent == 0 119 | unless (exponent & 1) == 0 120 | result *= x 121 | exponent -= 1 122 | end 123 | 124 | x *= x 125 | exponent >>= 1 126 | end 127 | 128 | result 129 | end 130 | 131 | #[primitive] 132 | def ==(other : u32) : bool 133 | end 134 | 135 | #[primitive] 136 | def !=(other : u32) : bool 137 | end 138 | 139 | #[primitive] 140 | def <(other : u32) : bool 141 | end 142 | 143 | #[primitive] 144 | def <=(other : u32) : bool 145 | end 146 | 147 | #[primitive] 148 | def >(other : u32) : bool 149 | end 150 | 151 | #[primitive] 152 | def >=(other : u32) : bool 153 | end 154 | 155 | #[inline] 156 | def <=>(other : u32) : i32 157 | if self < other 158 | -1 159 | elsif self > other 160 | 1 161 | else 162 | 0 163 | end 164 | end 165 | 166 | #[primitive] 167 | def ~ : u32 168 | end 169 | 170 | #[inline] 171 | def ! : bool 172 | false 173 | end 174 | 175 | #[primitive] 176 | def &(other : u32) : u32 177 | end 178 | 179 | #[primitive] 180 | def |(other : u32) : u32 181 | end 182 | 183 | #[primitive] 184 | def ^(other : u32) : u32 185 | end 186 | 187 | #[primitive] 188 | def <<(other : u32) : u32 189 | end 190 | 191 | #[primitive] 192 | def >>(other : u32) : u32 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /corelib/u64.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct u64 3 | # MIN = 0_u64 4 | # MAX = 18446744073709551615_u64 5 | # SIZE = 64 6 | 7 | #[primitive] 8 | def to_i8 : i8 9 | end 10 | 11 | #[primitive] 12 | def to_u8 : u8 13 | end 14 | 15 | #[primitive] 16 | def to_i16 : i16 17 | end 18 | 19 | #[primitive] 20 | def to_u16 : u16 21 | end 22 | 23 | #[primitive] 24 | def to_i32 : i32 25 | end 26 | 27 | #[primitive] 28 | def to_u32 : u32 29 | end 30 | 31 | #[primitive] 32 | def to_i64 : i64 33 | end 34 | 35 | #[inline] 36 | def to_u64 : u64 37 | self 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : u64) : u64 75 | end 76 | 77 | #[primitive] 78 | def -(other : u64) : u64 79 | end 80 | 81 | #[primitive] 82 | def *(other : u64) : u64 83 | end 84 | 85 | #[primitive] 86 | def div(other : u64) : u64 87 | end 88 | 89 | #[primitive] 90 | def %(other : u64) : u64 91 | end 92 | 93 | #[inline] 94 | def /(other : u64) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : u64 100 | self 101 | end 102 | 103 | #[inline] 104 | def //(other : u64) : u64 105 | self.div(other) 106 | end 107 | 108 | #[inline] 109 | def %%(other : u64) : u64 110 | self % other 111 | end 112 | 113 | #[inline] 114 | def **(exponent : u64) : u64 115 | x = self 116 | result = 1_u64 117 | 118 | until exponent == 0 119 | unless (exponent & 1) == 0 120 | result *= x 121 | exponent -= 1 122 | end 123 | 124 | x *= x 125 | exponent >>= 1 126 | end 127 | 128 | result 129 | end 130 | 131 | #[primitive] 132 | def ==(other : u64) : bool 133 | end 134 | 135 | #[primitive] 136 | def !=(other : u64) : bool 137 | end 138 | 139 | #[primitive] 140 | def <(other : u64) : bool 141 | end 142 | 143 | #[primitive] 144 | def <=(other : u64) : bool 145 | end 146 | 147 | #[primitive] 148 | def >(other : u64) : bool 149 | end 150 | 151 | #[primitive] 152 | def >=(other : u64) : bool 153 | end 154 | 155 | #[inline] 156 | def <=>(other : u64) : i32 157 | if self < other 158 | -1 159 | elsif self > other 160 | 1 161 | else 162 | 0 163 | end 164 | end 165 | 166 | #[primitive] 167 | def ~ : u64 168 | end 169 | 170 | #[inline] 171 | def ! : bool 172 | false 173 | end 174 | 175 | #[primitive] 176 | def &(other : u64) : u64 177 | end 178 | 179 | #[primitive] 180 | def |(other : u64) : u64 181 | end 182 | 183 | #[primitive] 184 | def ^(other : u64) : u64 185 | end 186 | 187 | #[primitive] 188 | def <<(other : u64) : u64 189 | end 190 | 191 | #[primitive] 192 | def >>(other : u64) : u64 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /corelib/u8.runic: -------------------------------------------------------------------------------- 1 | #[primitive] 2 | struct u8 3 | # MIN = 0_u8 4 | # MAX = 255_u8 5 | # SIZE = 8 6 | 7 | #[primitive] 8 | def to_i8 : i8 9 | end 10 | 11 | #[inline] 12 | def to_u8 : u8 13 | self 14 | end 15 | 16 | #[primitive] 17 | def to_i16 : i16 18 | end 19 | 20 | #[primitive] 21 | def to_u16 : u16 22 | end 23 | 24 | #[primitive] 25 | def to_i32 : i32 26 | end 27 | 28 | #[primitive] 29 | def to_u32 : u32 30 | end 31 | 32 | #[primitive] 33 | def to_i64 : i64 34 | end 35 | 36 | #[primitive] 37 | def to_u64 : u64 38 | end 39 | 40 | #[primitive] 41 | #[feature(i128)] 42 | def to_i128 : i128 43 | end 44 | 45 | #[primitive] 46 | #[feature(i128)] 47 | def to_u128 : u128 48 | end 49 | 50 | #[primitive] 51 | def to_f32 : f32 52 | end 53 | 54 | #[primitive] 55 | def to_f64 : f64 56 | end 57 | 58 | #[inline] 59 | def to_i : i32 60 | self.to_i32 61 | end 62 | 63 | #[inline] 64 | def to_u : u32 65 | self.to_u32 66 | end 67 | 68 | #[inline] 69 | def to_f : f64 70 | self.to_f64 71 | end 72 | 73 | #[primitive] 74 | def +(other : u8) : u8 75 | end 76 | 77 | #[primitive] 78 | def -(other : u8) : u8 79 | end 80 | 81 | #[primitive] 82 | def *(other : u8) : u8 83 | end 84 | 85 | #[primitive] 86 | def div(other : u8) : u8 87 | end 88 | 89 | #[primitive] 90 | def %(other : u8) : u8 91 | end 92 | 93 | #[inline] 94 | def /(other : u8) : float 95 | self.to_f / other.to_f 96 | end 97 | 98 | #[inline] 99 | def abs : u8 100 | self 101 | end 102 | 103 | #[inline] 104 | def //(other : u8) : u8 105 | self.div(other) 106 | end 107 | 108 | #[inline] 109 | def %%(other : u8) : u8 110 | self % other 111 | end 112 | 113 | #[inline] 114 | def **(exponent : u8) : u8 115 | x = self 116 | result = 1_u8 117 | 118 | until exponent == 0 119 | unless (exponent & 1) == 0 120 | result *= x 121 | exponent -= 1 122 | end 123 | 124 | x *= x 125 | exponent >>= 1 126 | end 127 | 128 | result 129 | end 130 | 131 | #[primitive] 132 | def ==(other : u8) : bool 133 | end 134 | 135 | #[primitive] 136 | def !=(other : u8) : bool 137 | end 138 | 139 | #[primitive] 140 | def <(other : u8) : bool 141 | end 142 | 143 | #[primitive] 144 | def <=(other : u8) : bool 145 | end 146 | 147 | #[primitive] 148 | def >(other : u8) : bool 149 | end 150 | 151 | #[primitive] 152 | def >=(other : u8) : bool 153 | end 154 | 155 | #[inline] 156 | def <=>(other : u8) : i32 157 | if self < other 158 | -1 159 | elsif self > other 160 | 1 161 | else 162 | 0 163 | end 164 | end 165 | 166 | #[primitive] 167 | def ~ : u8 168 | end 169 | 170 | #[inline] 171 | def ! : bool 172 | false 173 | end 174 | 175 | #[primitive] 176 | def &(other : u8) : u8 177 | end 178 | 179 | #[primitive] 180 | def |(other : u8) : u8 181 | end 182 | 183 | #[primitive] 184 | def ^(other : u8) : u8 185 | end 186 | 187 | #[primitive] 188 | def <<(other : u8) : u8 189 | end 190 | 191 | #[primitive] 192 | def >>(other : u8) : u8 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | MANPAGES += man1/runic.1 2 | MANPAGES += man1/runic-compile.1 3 | MANPAGES += man1/runic-documentation.1 4 | MANPAGES += man1/runic-interactive.1 5 | 6 | .PHONY: clean 7 | 8 | all: $(MANPAGES) 9 | 10 | man1/%.1: %.1.txt 11 | @mkdir -p man1 12 | asciidoctor -b manpage -o $@ $< 13 | 14 | clean: 15 | rm -rf man1 16 | -------------------------------------------------------------------------------- /doc/runic-compile.1.txt: -------------------------------------------------------------------------------- 1 | runic-compile(1) 2 | ================ 3 | 4 | NAME 5 | ---- 6 | runic-compile - compile object files 7 | 8 | SYNOPSYS 9 | -------- 10 | [verse] 11 | *runic c[ompile]* [-O] [--emit=] [-g] [--debug] [--no-debug] 12 | [--target=] [--cpu=] [--features=<+opt+opt>] 13 | [-o ] [--output=] [--no-corelib] [--corelib=] 14 | 15 | 16 | DESCRIPTION 17 | ----------- 18 | 19 | Compiles a runic source program into machine code. 20 | 21 | 22 | OPTIONS 23 | ------- 24 | 25 | -o :: 26 | --output=:: 27 | Emit the compiled program to the specified file. Defaults to the original 28 | filename without the _.runic_ extension but _.o_ or _.ll_, depending on 29 | _--emit_. 30 | 31 | --emit=:: 32 | You may want to generate something in particular, not always an object file: 33 | _llvm_;; 34 | Generate LLVM IR (_.ll_); 35 | _object_;; 36 | Generate an object file (_.o_), the default; 37 | 38 | -g:: 39 | --debug:: 40 | Generate full debug information, including local variables names and 41 | arguments, in addition to the default source code locations and function 42 | names. 43 | 44 | --no-debug:: 45 | Don't generate any debug information, not even source code location 46 | and function names. 47 | 48 | -O:: 49 | Choose the optimization level to use: 50 | _-O0_;; 51 | means no optimization, fastest to compile, and most debuggable code. 52 | _-O1_;; 53 | enables only a few optimizations. 54 | _-O_;; 55 | _-O2_;; 56 | enables most optimizations. 57 | _-O3_;; 58 | enable aggressive optimizations for the longest compilation time and 59 | maybe generate larger code (for faster execution). 60 | 61 | --no-corelib:: 62 | Don't load the corelib. 63 | 64 | --corelib=:: 65 | Load an alternative core library by specifying it's path (relative or 66 | absolute); for example: `--corelib=./src/arduino.runic`. 67 | 68 | Cross Compilation Options: 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | 71 | --target=:: 72 | You can set the target triple to compile the program for. For example 73 | _--target=arm-unknown-linux-androideabi_. Defaults to the host target LLVM 74 | was built on. 75 | 76 | --cpu=:: 77 | You may specify a specific CPU to target. Supports any CPU that LLVM 78 | supports. For example _--cpu=cortex-a12_ 79 | 80 | --features=:: 81 | You may specify additional features for the target. For example 82 | _--features=+crypto+hf_. 83 | 84 | SEE ALSO 85 | -------- 86 | 87 | _runic(1)_;; 88 | _runic-interactive(1)_;; 89 | -------------------------------------------------------------------------------- /doc/runic-documentation.1.txt: -------------------------------------------------------------------------------- 1 | runic-documentation(1) 2 | ====================== 3 | 4 | NAME 5 | ---- 6 | runic-documentation - generate documentation from runic programs 7 | 8 | SYNOPSYS 9 | -------- 10 | [verse] 11 | *runic doc[umentation]* [-f ] [--format=] [--help] 12 | [-o ] [--output=] [...] [ ...] 13 | 14 | DESCRIPTION 15 | ----------- 16 | 17 | Generates human (HTML) or machine (YAML, JSON) documentation from a runic source 18 | code program. 19 | 20 | If one or many runic source files are specified, generates a documentation file 21 | for one each of them. If one or many directories are specified, recursively 22 | searches the directory for _.runic_ source files then generates a documentation 23 | file for each found file. If no source file or directory are specified, searches 24 | the _src_ folder for _.runic_ source files. 25 | 26 | OPTIONS 27 | ------- 28 | 29 | -o :: 30 | --output=:: 31 | Emit the generated documentation to the specified folder. Defaults to _doc_. 32 | 33 | -f :: 34 | --format=:: 35 | Emit the generated documentation in the specified format. Supports _html_ 36 | (default), _json_ and _yaml_. 37 | 38 | SEE ALSO 39 | -------- 40 | 41 | _runic(1)_;; 42 | -------------------------------------------------------------------------------- /doc/runic-interactive.1.txt: -------------------------------------------------------------------------------- 1 | runic-interactive(1) 2 | ==================== 3 | 4 | NAME 5 | ---- 6 | runic-interactive - interactive session. 7 | 8 | SYNOPSYS 9 | -------- 10 | [verse] 11 | *runic i[nteractive]* [--no-corelib] [--corelib=] [--debug] 12 | [--no-optimize] [--help] 13 | 14 | DESCRIPTION 15 | ----------- 16 | 17 | Starts an interactive session where you can type runic code and execute it right 18 | away. 19 | 20 | OPTIONS 21 | ------- 22 | 23 | --no-corelib:: 24 | Don't load the corelib. 25 | 26 | --corelib=:: 27 | Load an alternative core library by specifying it's path (relative or 28 | absolute); for example: `--corelib=./src/arduino.runic`. 29 | 30 | --debug:: 31 | Print the generated LLVM IR whenever a function is generated. 32 | 33 | --no-optimize:: 34 | Don't run the LLVM Function Pass Manager to optimize generated functions. 35 | 36 | SEE ALSO 37 | -------- 38 | 39 | _runic(1)_;; 40 | _runic-compile(1)_;; 41 | -------------------------------------------------------------------------------- /doc/runic.1.txt: -------------------------------------------------------------------------------- 1 | runic(1) 2 | ======== 3 | 4 | NAME 5 | ---- 6 | runic - toy compiler 7 | 8 | SYNOPSYS 9 | -------- 10 | **runic** [--version] [--help] [] 11 | 12 | DESCRIPTION 13 | ----------- 14 | Runic is a toy compiler for a toy compiled language still in design. The goal is 15 | to have a beautiful, performant, safe and developer friendly programming 16 | language. 17 | 18 | OPTIONS 19 | ------- 20 | 21 | -v:: 22 | --version:: 23 | Prints the release version that the _runic_ program came from. 24 | 25 | -h:: 26 | --help:: 27 | Prints usage synopsys. You may type _git help _ for detailed help 28 | on a particular command. 29 | 30 | RUNIC COMMANDS 31 | -------------- 32 | 33 | Any binary starting with _runic-_ and accessible in your _PATH_ may be invoked 34 | as a _runic_ command. For example a _runic-foo_ binary can be invoked as 35 | _runic foo_. 36 | 37 | Main commands 38 | ~~~~~~~~~~~~~ 39 | 40 | _runic-compile_(1):: 41 | Compile a runic source program to an object file. 42 | 43 | _runic-documentation_(1):: 44 | Generate documentation for a runic source program to different formats, for 45 | example HTML and JSON. 46 | 47 | _runic-interactive_(1):: 48 | Start an interactive session. 49 | 50 | SEE ALSO 51 | -------- 52 | 53 | _runic-compile(1)_;; 54 | _runic-interactive(1)_;; 55 | -------------------------------------------------------------------------------- /shard.lock: -------------------------------------------------------------------------------- 1 | version: 1.0 2 | shards: 3 | clang: 4 | github: ysbaddaden/clang.cr 5 | version: 0.3.0 6 | 7 | minitest: 8 | github: ysbaddaden/minitest.cr 9 | version: 0.5.1 10 | 11 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: runic-compiler 2 | version: 0.1.0 3 | 4 | authors: 5 | - Julien Portalier 6 | 7 | description: | 8 | Runic language compiler. 9 | 10 | dependencies: 11 | clang: 12 | github: ysbaddaden/clang.cr 13 | version: ">= 0.3.0" 14 | 15 | development_dependencies: 16 | minitest: 17 | github: ysbaddaden/minitest.cr 18 | version: ">= 0.4.3" 19 | 20 | libraries: 21 | LLVM: 8 22 | 23 | crystal: 0.29.0 24 | 25 | license: CeCILL-C 26 | -------------------------------------------------------------------------------- /src/c/llvm.cr: -------------------------------------------------------------------------------- 1 | @[Link(ldflags: "`llvm-config-8 --cxxflags --ldflags --libs --system-libs`")] 2 | @[Link("stdc++")] 3 | lib LibC 4 | {% if flag?(:aarch64) || flag?(:x86_64) %} 5 | alias UintptrT = UInt64 6 | {% elsif flag?(:i686) || flag?(:arm) %} 7 | alias UintptrT = UInt32 8 | {% else %} 9 | {% raise "unsupported target" %} 10 | {% end %} 11 | 12 | alias Uint64T = UInt64 13 | 14 | {% begin %} 15 | LLVM_AVAILABLE_TARGETS = {{ `llvm-config-8 --targets-built`.stringify.chomp.split(' ') }} 16 | {% end %} 17 | 18 | {% for target in LLVM_AVAILABLE_TARGETS %} 19 | fun LLVMInitialize{{target.id}}TargetInfo() : Void 20 | fun LLVMInitialize{{target.id}}Target() : Void 21 | fun LLVMInitialize{{target.id}}TargetMC() : Void 22 | fun LLVMInitialize{{target.id}}AsmPrinter() : Void 23 | fun LLVMInitialize{{target.id}}AsmParser() : Void 24 | {% end %} 25 | 26 | # FIXME: not generated automatically by c2cr (?) 27 | alias LLVMModuleFlagEntry = LLVMOpaqueModuleFlagEntry 28 | alias LLVMValueMetadataEntry = LLVMOpaqueValueMetadataEntry 29 | LLVMAttributeReturnIndex = 0_u32 30 | LLVMAttributeFunctionIndex = ~0_u32 31 | end 32 | 33 | require "./llvm/analysis" 34 | require "./llvm/types" 35 | require "./llvm/core" 36 | require "./llvm/error_handling" 37 | require "./llvm/execution_engine" 38 | require "./llvm/initialization" 39 | require "./llvm/target" 40 | require "./llvm/target_machine" 41 | require "./llvm/transforms/ipo" 42 | require "./llvm/transforms/pass_manager_builder" 43 | require "./llvm/transforms/scalar" 44 | require "./llvm/transforms/utils" 45 | -------------------------------------------------------------------------------- /src/cli.cr: -------------------------------------------------------------------------------- 1 | require "./version" 2 | 3 | module Runic 4 | class CLI 5 | def initialize 6 | @filenames = [] of String 7 | @index = -1 8 | @argument = "" 9 | end 10 | 11 | def parse 12 | while argument = consume? 13 | @argument = argument 14 | yield @argument 15 | end 16 | end 17 | 18 | def consume? 19 | ARGV[@index += 1]? 20 | end 21 | 22 | def remaining_arguments 23 | ARGV[(@index + 1)..-1] 24 | end 25 | 26 | def argument_value(name) 27 | if pos = ARGV[@index].index('=') 28 | return ARGV[@index][(pos + 1)..-1] 29 | end 30 | 31 | if value = ARGV[@index += 1]? 32 | return value unless value.starts_with?('-') 33 | end 34 | 35 | abort "fatal : missing value for {{name.id}}" 36 | end 37 | 38 | def filename 39 | if @argument.starts_with?('-') 40 | unknown_option! 41 | else 42 | @argument 43 | end 44 | end 45 | 46 | def unknown_option! 47 | abort "Unknown option: #{@argument}" 48 | end 49 | 50 | def report_version(name : String) 51 | puts "#{name} version #{Runic.version_string}" 52 | exit 0 53 | end 54 | 55 | def fatal(message) 56 | abort "fatal : #{message}" 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /src/codegen.cr: -------------------------------------------------------------------------------- 1 | require "./codegen/control_flow" 2 | require "./codegen/debug" 3 | require "./codegen/functions" 4 | require "./codegen/literals" 5 | require "./codegen/operators" 6 | require "./codegen/pointers" 7 | require "./codegen/structures" 8 | require "./data_layout" 9 | require "./llvm" 10 | require "./errors" 11 | require "./scope" 12 | 13 | module Runic 14 | class Codegen 15 | EXTERN_LINKAGE = LibC::LLVMLinkage::External 16 | PUBLIC_LINKAGE = LibC::LLVMLinkage::External 17 | PRIVATE_LINKAGE = LibC::LLVMLinkage::Internal 18 | 19 | @debug : Debug 20 | @opt_level : LibC::LLVMCodeGenOptLevel 21 | 22 | def initialize(@program : Program, debug = DebugLevel::Default, @opt_level = LibC::LLVMCodeGenOptLevel::CodeGenLevelDefault, @optimize = true) 23 | @context = LibC.LLVMContextCreate() 24 | @builder = LibC.LLVMCreateBuilderInContext(@context) 25 | @module = LibC.LLVMModuleCreateWithNameInContext("main", @context) 26 | 27 | # custom types (i.e. structs): 28 | @llvm_types = {} of String => LibC::LLVMTypeRef 29 | 30 | # global constant values: 31 | @constant_values = {} of String => LibC::LLVMValueRef 32 | 33 | # local variables (i.e. pointers) 34 | @scope = Scope(LibC::LLVMValueRef).new 35 | 36 | # name(+index) of current struct ivars: 37 | @ivars = [] of String 38 | 39 | if debug.none? 40 | @debug = Debug::NULL.new(debug) 41 | else 42 | @debug = Debug::DWARF.new(@module, @builder, @context, debug, @optimize) 43 | @debug.codegen = self 44 | end 45 | end 46 | 47 | def finalize 48 | if fpm = @function_pass_manager 49 | LibC.LLVMDisposePassManager(fpm) 50 | end 51 | if mpm = @module_pass_manager 52 | LibC.LLVMDisposePassManager(mpm) 53 | end 54 | if pmb = @pass_manager_builder 55 | LibC.LLVMPassManagerBuilderDispose(pmb) 56 | end 57 | LibC.LLVMDisposeModule(@module) 58 | LibC.LLVMDisposeBuilder(@builder) 59 | LibC.LLVMContextDispose(@context) 60 | end 61 | 62 | 63 | def data_layout 64 | @data_layout ||= DataLayout.from(@module) 65 | end 66 | 67 | def data_layout=(layout) 68 | @data_layout = @target_data = nil 69 | LibC.LLVMSetModuleDataLayout(@module, layout) 70 | end 71 | 72 | def target_triple=(triple) 73 | @data_layout = @target_data = nil 74 | LibC.LLVMSetTarget(@module, triple) 75 | end 76 | 77 | private def target_data 78 | @target_data ||= LibC.LLVMGetModuleDataLayout(@module) 79 | end 80 | 81 | 82 | def verify 83 | @debug.flush 84 | 85 | if LibC.LLVMVerifyModule(@module, LibC::LLVMVerifierFailureAction::ReturnStatus, nil) == 1 86 | emit_llvm("dump.ll") 87 | raise CodegenError.new("module validation failed") 88 | end 89 | end 90 | 91 | def emit_llvm(path : String) 92 | @debug.flush 93 | 94 | if LibC.LLVMPrintModuleToFile(@module, path, out err_msg) == 1 95 | msg = String.new(err_msg) 96 | LibC.LLVMDisposeMessage(err_msg) 97 | raise CodegenError.new(msg) 98 | end 99 | end 100 | 101 | def emit_llvm(value : LibC::LLVMValueRef) 102 | ll = LibC.LLVMPrintValueToString(value) 103 | begin 104 | String.new(ll) 105 | ensure 106 | LibC.LLVMDisposeMessage(ll) 107 | end 108 | end 109 | 110 | def emit_object(target_machine, path) 111 | @debug.flush 112 | 113 | # write object file 114 | if LibC.LLVMTargetMachineEmitToFile(target_machine, @module, path, 115 | LibC::LLVMCodeGenFileType::Object, out emit_err_msg) == 1 116 | msg = String.new(emit_err_msg) 117 | LibC.LLVMDisposeMessage(emit_err_msg) 118 | raise CodegenError.new(msg) 119 | end 120 | end 121 | 122 | def execute(ret : T.class, func : LibC::LLVMValueRef) : T? forall T 123 | execute(ret, func) do |func_ptr| 124 | Proc(T) 125 | .new(func_ptr, Pointer(Void).null) 126 | .call 127 | end 128 | end 129 | 130 | def execute(ret : String.class, func : LibC::LLVMValueRef) : String? 131 | execute(ret, func) do |func_ptr| 132 | sret = uninitialized {UInt8*, Int32} 133 | 134 | Proc(Pointer({UInt8*, Int32}), Void) 135 | .new(func_ptr, Pointer(Void).null) 136 | .call(pointerof(sret)) 137 | 138 | String.new(sret[0], sret[1]) 139 | end 140 | end 141 | 142 | private def execute(ret, func) 143 | # (re)inject module since it may have been removed 144 | LibC.LLVMAddModule(execution_engine, @module) 145 | 146 | # get pointer to compiled function, cast to proc and execute 147 | if func_ptr = LibC.LLVMGetPointerToGlobal(execution_engine, func) 148 | yield func_ptr 149 | end 150 | ensure 151 | # remove module so next run will recompile code 152 | if LibC.LLVMRemoveModule(execution_engine, @module, out mod, out err_msg) == 1 153 | STDERR.puts(String.new(err_msg)) 154 | LibC.LLVMDisposeMessage(err_msg) 155 | exit 156 | end 157 | end 158 | 159 | 160 | @execution_engine : LibC::LLVMExecutionEngineRef? 161 | 162 | private def execution_engine 163 | if ee = @execution_engine 164 | return ee 165 | end 166 | if LibC.LLVMCreateJITCompilerForModule(out engine, @module, 0, out err_msg) == 1 167 | STDERR.puts(String.new(err_msg)) 168 | LibC.LLVMDisposeMessage(err_msg) 169 | exit 170 | end 171 | @execution_engine = engine 172 | end 173 | 174 | 175 | @pass_manager_builder : LibC::LLVMPassManagerBuilderRef? 176 | @function_pass_manager : LibC::LLVMPassManagerRef? 177 | @module_pass_manager : LibC::LLVMPassManagerRef? 178 | 179 | def optimize 180 | return unless @optimize 181 | 182 | if fpm = function_pass_manager 183 | LibC.LLVMInitializeFunctionPassManager(fpm) 184 | 185 | func = LibC.LLVMGetFirstFunction(@module) 186 | while func 187 | LibC.LLVMRunFunctionPassManager(fpm, func) 188 | func = LibC.LLVMGetNextFunction(func) 189 | end 190 | 191 | LibC.LLVMFinalizeFunctionPassManager(fpm) 192 | end 193 | 194 | if mpm = module_pass_manager 195 | LibC.LLVMRunPassManager(mpm, @module) 196 | end 197 | end 198 | 199 | private def pass_manager_builder 200 | @pass_manager_builder ||= begin 201 | pmb = LibC.LLVMPassManagerBuilderCreate() 202 | LibC.LLVMPassManagerBuilderSetOptLevel(pmb, @opt_level) 203 | LibC.LLVMPassManagerBuilderSetSizeLevel(pmb, 0) # 1 => -Os, 2 => -Oz 204 | pmb 205 | end 206 | end 207 | 208 | private def function_pass_manager 209 | @function_pass_manager ||= LibC.LLVMCreateFunctionPassManagerForModule(@module).tap do |pm| 210 | LibC.LLVMPassManagerBuilderPopulateFunctionPassManager(pass_manager_builder, pm) 211 | pm 212 | end 213 | end 214 | 215 | private def module_pass_manager 216 | @module_pass_manager ||= LibC.LLVMCreatePassManager().tap do |pm| 217 | LibC.LLVMPassManagerBuilderPopulateModulePassManager(pass_manager_builder, pm) 218 | LibC.LLVMAddAlwaysInlinerPass(pm) 219 | end 220 | end 221 | 222 | 223 | def codegen(path : String) : Nil 224 | @debug.path = path 225 | @debug.program = @program 226 | @program.each { |node| codegen(node) } 227 | end 228 | 229 | def codegen(nodes : AST::Body) : LibC::LLVMValueRef 230 | nodes.reduce(llvm_void_value) { |_, node| codegen(node) } 231 | end 232 | 233 | def codegen(node : AST::Module) 234 | raise "FATAL: can't codegen module statement" 235 | end 236 | 237 | def codegen(node : AST::Require) 238 | raise "FATAL: can't codegen require statement" 239 | end 240 | 241 | 242 | private def build_alloca(node : AST::Variable) 243 | alloca = build_alloca(node) 244 | yield alloca 245 | alloca 246 | end 247 | 248 | private def build_alloca(node : AST::Variable) 249 | @debug.emit_location(node) 250 | LibC.LLVMBuildAlloca(@builder, llvm_type(node.type), "") 251 | end 252 | 253 | # Returns false (0_i1) if the expression evaluates to false or a null 254 | # pointer. Returns true (1_i1) otherwise. 255 | private def build_condition(node : AST::Node) 256 | value = codegen(node) 257 | 258 | if node.bool? 259 | value 260 | #elsif node.pointer? 261 | # is_null = LibC.LLVMBuildIsNull(@builder, value, "") 262 | # bool_false = LibC.LLVMConstInt(llvm_type("bool"), 0, 0) 263 | # LibC.LLVMBuildICmp(@builder, LibC::LLVMIntPredicate::IntEQ, is_null, bool_false, "") 264 | else 265 | LibC.LLVMConstInt(llvm_type("bool"), 1, 0) 266 | end 267 | end 268 | 269 | # The `codegen` methods must return a value, but sometimes they must return 270 | # void, that is nothing, in this case we return a zero value —semantic 271 | # analysis verified the value is never used. 272 | private def llvm_void_value 273 | LibC.LLVMConstInt(llvm_type("i32"), 0, 0) 274 | end 275 | 276 | private def llvm_type(node : AST::Struct) 277 | build_llvm_struct(node.name) 278 | end 279 | 280 | private def llvm_type(node : AST::Node) 281 | llvm_type(node.type) 282 | end 283 | 284 | private def llvm_type(node : String) 285 | llvm_type(Type.new(node)) 286 | end 287 | 288 | private def llvm_type(type : Type) 289 | if type.pointer? 290 | LibC.LLVMPointerType(llvm_type(type.pointee_type), 0) 291 | else 292 | case type.name 293 | when "bool" 294 | LibC.LLVMInt1TypeInContext(@context) 295 | when "i8", "u8" 296 | LibC.LLVMInt8TypeInContext(@context) 297 | when "i16", "u16" 298 | LibC.LLVMInt16TypeInContext(@context) 299 | when "i32", "u32" 300 | LibC.LLVMInt32TypeInContext(@context) 301 | when "i64", "u64" 302 | LibC.LLVMInt64TypeInContext(@context) 303 | when "i128", "u128" 304 | LibC.LLVMInt128TypeInContext(@context) 305 | when "f64" 306 | LibC.LLVMDoubleTypeInContext(@context) 307 | when "f32" 308 | LibC.LLVMFloatTypeInContext(@context) 309 | #when "long", "ulong" 310 | # LibC.LLVMInt32TypeInContext(@context) # 32-bit: x86, arm, mips, ... 311 | # LibC.LLVMInt64TypeInContext(@context) # 64-bit: x86_64, aarch64, mips64, ... 312 | when "void" 313 | LibC.LLVMVoidTypeInContext(@context) 314 | else 315 | build_llvm_struct(type.name) 316 | end 317 | end 318 | end 319 | 320 | private def build_llvm_struct(name : String) 321 | @llvm_types[name] ||= LibC.LLVMStructCreateNamed(@context, "struct.#{name}") 322 | end 323 | 324 | 325 | protected def sizeof(node : AST::Struct) 326 | LibC.LLVMABISizeOfType(target_data, llvm_type(node.name)) 327 | end 328 | 329 | protected def sizeof(node : AST::Node) 330 | LibC.LLVMABISizeOfType(target_data, llvm_type(node.type)) 331 | end 332 | 333 | #protected def alignment(node : AST::Struct) 334 | # LibC.LLVMABIAlignmentOfType(target_data, llvm_type(node.name)) 335 | #end 336 | 337 | #protected def alignment(node : AST::Node) 338 | # LibC.LLVMABIAlignmentOfType(target_data, llvm_type(node.type)) 339 | #end 340 | 341 | protected def offsetof(node : AST::Struct, ivar : AST::InstanceVariable) 342 | index = -1 343 | 344 | node.variables.each_with_index do |v, i| 345 | index = i if v.name == ivar.name 346 | end 347 | 348 | if index == -1 349 | raise CodegenError.new("can't take offsetof of unknown instance variable '@#{ivar.name}' for struct #{node.name}") 350 | end 351 | 352 | LibC.LLVMOffsetOfElement(target_data, llvm_type(node.name), index) 353 | end 354 | end 355 | end 356 | -------------------------------------------------------------------------------- /src/codegen/control_flow.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | class Codegen 3 | def codegen(node : AST::If) : LibC::LLVMValueRef 4 | @debug.emit_location(node) 5 | 6 | entry_block = LibC.LLVMGetInsertBlock(@builder) 7 | parent_block = LibC.LLVMGetBasicBlockParent(entry_block) 8 | then_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "then") 9 | end_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "end") 10 | 11 | count = node.alternative ? 2 : 1 12 | blocks = Array(LibC::LLVMBasicBlockRef).new(count) 13 | values = Array(LibC::LLVMValueRef).new(count) 14 | 15 | # then block: 16 | LibC.LLVMPositionBuilderAtEnd(@builder, then_block) 17 | values << codegen(node.body) 18 | blocks << LibC.LLVMGetInsertBlock(@builder) 19 | LibC.LLVMBuildBr(@builder, end_block) 20 | 21 | # if condition: 22 | LibC.LLVMPositionBuilderAtEnd(@builder, entry_block) 23 | condition = build_condition(node.condition) 24 | 25 | if body = node.alternative 26 | else_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "else") 27 | LibC.LLVMMoveBasicBlockBefore(else_block, end_block) 28 | 29 | # true -> then, false -> else 30 | LibC.LLVMBuildCondBr(@builder, condition, then_block, else_block) 31 | 32 | # else block: 33 | LibC.LLVMPositionBuilderAtEnd(@builder, else_block) 34 | values << codegen(body) 35 | blocks << LibC.LLVMGetInsertBlock(@builder) 36 | LibC.LLVMBuildBr(@builder, end_block) 37 | else 38 | # true -> then, false -> end 39 | LibC.LLVMBuildCondBr(@builder, condition, then_block, end_block) 40 | end 41 | 42 | # merge block: 43 | LibC.LLVMPositionBuilderAtEnd(@builder, end_block) 44 | 45 | if node.type == "void" 46 | # return invalid value (semantic analysis prevents its usage) 47 | return llvm_void_value 48 | end 49 | 50 | # return value 51 | phi = LibC.LLVMBuildPhi(@builder, llvm_type(node), "") 52 | LibC.LLVMAddIncoming(phi, values, blocks, count) 53 | phi 54 | end 55 | 56 | def codegen(node : AST::Unless) : LibC::LLVMValueRef 57 | @debug.emit_location(node) 58 | condition = build_condition(node.condition) 59 | 60 | entry_block = LibC.LLVMGetInsertBlock(@builder) 61 | parent_block = LibC.LLVMGetBasicBlockParent(entry_block) 62 | then_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "then") 63 | end_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "end") 64 | 65 | # true -> end, false -> then 66 | LibC.LLVMBuildCondBr(@builder, condition, end_block, then_block) 67 | 68 | # then block: 69 | LibC.LLVMPositionBuilderAtEnd(@builder, then_block) 70 | value = codegen(node.body) 71 | LibC.LLVMBuildBr(@builder, end_block) 72 | 73 | # merge block: 74 | LibC.LLVMPositionBuilderAtEnd(@builder, end_block) 75 | 76 | llvm_void_value 77 | end 78 | 79 | def codegen(node : AST::While) : LibC::LLVMValueRef 80 | @debug.emit_location(node) 81 | 82 | entry_block = LibC.LLVMGetInsertBlock(@builder) 83 | parent_block = LibC.LLVMGetBasicBlockParent(entry_block) 84 | loop_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "while") 85 | do_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "do") 86 | end_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "end") 87 | 88 | LibC.LLVMBuildBr(@builder, loop_block) 89 | 90 | # -> loop 91 | LibC.LLVMPositionBuilderAtEnd(@builder, loop_block) 92 | condition = build_condition(node.condition) 93 | 94 | # true -> do, false -> end 95 | LibC.LLVMBuildCondBr(@builder, condition, do_block, end_block) 96 | 97 | # do block: 98 | LibC.LLVMPositionBuilderAtEnd(@builder, do_block) 99 | value = codegen(node.body) 100 | LibC.LLVMBuildBr(@builder, loop_block) 101 | 102 | # merge block: 103 | LibC.LLVMPositionBuilderAtEnd(@builder, end_block) 104 | 105 | llvm_void_value 106 | end 107 | 108 | def codegen(node : AST::Until) : LibC::LLVMValueRef 109 | @debug.emit_location(node) 110 | 111 | entry_block = LibC.LLVMGetInsertBlock(@builder) 112 | parent_block = LibC.LLVMGetBasicBlockParent(entry_block) 113 | loop_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "until") 114 | do_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "do") 115 | end_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "end") 116 | 117 | LibC.LLVMBuildBr(@builder, loop_block) 118 | 119 | # -> loop 120 | LibC.LLVMPositionBuilderAtEnd(@builder, loop_block) 121 | condition = build_condition(node.condition) 122 | 123 | # true -> end, false -> do 124 | LibC.LLVMBuildCondBr(@builder, condition, end_block, do_block) 125 | 126 | # do block: 127 | LibC.LLVMPositionBuilderAtEnd(@builder, do_block) 128 | value = codegen(node.body) 129 | LibC.LLVMBuildBr(@builder, loop_block) 130 | 131 | # merge block: 132 | LibC.LLVMPositionBuilderAtEnd(@builder, end_block) 133 | 134 | llvm_void_value 135 | end 136 | 137 | def codegen(node : AST::Case) : LibC::LLVMValueRef 138 | @debug.emit_location(node) 139 | 140 | count = node.cases.size + (node.alternative ? 1 : 0) 141 | blocks = Array(LibC::LLVMBasicBlockRef).new(count) 142 | values = Array(LibC::LLVMValueRef).new(count) 143 | 144 | entry_block = LibC.LLVMGetInsertBlock(@builder) 145 | parent_block = LibC.LLVMGetBasicBlockParent(entry_block) 146 | end_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "end") 147 | ref_block = end_block 148 | 149 | value = codegen(node.value) 150 | 151 | if body = node.alternative 152 | else_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "else") 153 | LibC.LLVMMoveBasicBlockBefore(else_block, end_block) 154 | ref_block = else_block 155 | 156 | switch = LibC.LLVMBuildSwitch(@builder, value, else_block, count) 157 | 158 | LibC.LLVMPositionBuilderAtEnd(@builder, else_block) 159 | values << codegen(body) 160 | blocks << LibC.LLVMGetInsertBlock(@builder) 161 | LibC.LLVMBuildBr(@builder, end_block) 162 | else 163 | switch = LibC.LLVMBuildSwitch(@builder, value, end_block, count) 164 | end 165 | 166 | node.cases.each do |n| 167 | when_block = LibC.LLVMAppendBasicBlockInContext(@context, parent_block, "when") 168 | LibC.LLVMMoveBasicBlockBefore(when_block, ref_block) 169 | 170 | LibC.LLVMPositionBuilderAtEnd(@builder, when_block) 171 | values << codegen(n.body) 172 | blocks << LibC.LLVMGetInsertBlock(@builder) 173 | LibC.LLVMBuildBr(@builder, end_block) 174 | 175 | n.conditions.each do |condition| 176 | LibC.LLVMAddCase(switch, codegen(condition), when_block) 177 | end 178 | end 179 | 180 | LibC.LLVMPositionBuilderAtEnd(@builder, end_block) 181 | 182 | if node.type == "void" 183 | return llvm_void_value 184 | end 185 | 186 | phi = LibC.LLVMBuildPhi(@builder, llvm_type(node), "") 187 | LibC.LLVMAddIncoming(phi, values, blocks, count) 188 | phi 189 | end 190 | 191 | def codegen(node : AST::When) : LibC::LLVMValueRef 192 | raise CodegenError.new("#{node.class.name} can't be generated directly") 193 | end 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /src/codegen/intrinsics.cr: -------------------------------------------------------------------------------- 1 | require "./functions" 2 | 3 | module Runic 4 | class Codegen 5 | # Returns the LLVM definition of a LLVM intrinsic for the given name and 6 | # overloads. Declares the LLVM definition automatically. 7 | # 8 | # ``` 9 | # intrinsic("llvm.floor", Type.new("f32")) # => searches llvm.floor.f32 10 | # ``` 11 | protected def llvm_intrinsic(name : String, *types : Type) 12 | overload_types = types.map do |type| 13 | case type.name 14 | when "i8", "u8" then "i8" 15 | when "i16", "u16" then "i16" 16 | when "i32", "u32" then "i32" 17 | when "i64", "u64" then "i64" 18 | when "i128", "u128" then "i128" 19 | when "f32" then "f32" 20 | when "f64" then "f64" 21 | else raise CodegenError.new("unsupported overload type '#{type}' for '#{name}' intrinsic") 22 | end 23 | end 24 | 25 | overload_name = String.build do |str| 26 | str << "llvm." 27 | str << name 28 | overload_types.each do |type| 29 | str << '.' 30 | type.to_s(str) 31 | end 32 | end 33 | 34 | if func = LibC.LLVMGetNamedFunction(@module, overload_name) 35 | func 36 | else 37 | declare_llvm_intrinsic(overload_name) 38 | end 39 | end 40 | 41 | private def declare_llvm_intrinsic(name : String) : LibC::LLVMValueRef 42 | case name 43 | when "llvm.floor.f32", 44 | "llvm.ceil.f32", 45 | "llvm.trunc.f32" 46 | declare_llvm_intrinsic_function(name, "f32", "f32") 47 | 48 | when "llvm.floor.f64", 49 | "llvm.ceil.f64", 50 | "llvm.trunc.f64" 51 | declare_llvm_intrinsic_function(name, "f64", "f64") 52 | 53 | when "llvm.pow.f32" 54 | declare_llvm_intrinsic_function(name, "f32", "f32", "f32") 55 | 56 | when "llvm.pow.f64" 57 | declare_llvm_intrinsic_function(name, "f64", "f64", "f64") 58 | 59 | else 60 | raise CodegenError.new("intrinsic '#{name}': no such definition") 61 | end 62 | end 63 | 64 | private def declare_llvm_intrinsic_function(name : String, type : String, *args : String) : LibC::LLVMValueRef 65 | param_types = args.map { |arg| llvm_type(arg) }.to_a 66 | return_type = llvm_type(type) 67 | func_type = LibC.LLVMFunctionType(return_type, param_types, param_types.size, 0) 68 | func = LibC.LLVMAddFunction(@module, name, func_type) 69 | LibC.LLVMSetLinkage(func, EXTERN_LINKAGE) 70 | func 71 | end 72 | 73 | protected def builtin_cast_to_unsigned(variable_name, src_type, dst_type) 74 | value = LibC.LLVMBuildLoad(@builder, @scope.get(variable_name), variable_name) 75 | 76 | case src_type 77 | when .unsigned? 78 | if src_type.bits < dst_type.bits 79 | LibC.LLVMBuildZExt(@builder, value, llvm_type(dst_type), "") 80 | else 81 | LibC.LLVMBuildTrunc(@builder, value, llvm_type(dst_type), "") 82 | end 83 | when .integer? 84 | if src_type.bits < dst_type.bits 85 | LibC.LLVMBuildSExt(@builder, value, llvm_type(dst_type), "") 86 | else 87 | LibC.LLVMBuildTrunc(@builder, value, llvm_type(dst_type), "") 88 | end 89 | when .float? 90 | LibC.LLVMBuildFPToUI(@builder, value, llvm_type(dst_type), "") 91 | else 92 | raise "unreachable" 93 | end 94 | end 95 | 96 | protected def builtin_cast_to_signed(variable_name, src_type, dst_type) 97 | value = LibC.LLVMBuildLoad(@builder, @scope.get(variable_name), variable_name) 98 | 99 | case src_type 100 | when .unsigned? 101 | if src_type.bits < dst_type.bits 102 | LibC.LLVMBuildZExt(@builder, value, llvm_type(dst_type), "") 103 | else 104 | LibC.LLVMBuildTrunc(@builder, value, llvm_type(dst_type), "") 105 | end 106 | when .integer? 107 | if src_type.bits < dst_type.bits 108 | LibC.LLVMBuildSExt(@builder, value, llvm_type(dst_type), "") 109 | else 110 | LibC.LLVMBuildTrunc(@builder, value, llvm_type(dst_type), "") 111 | end 112 | when .float? 113 | LibC.LLVMBuildFPToSI(@builder, value, llvm_type(dst_type), "") 114 | else 115 | raise "unreachable" 116 | end 117 | end 118 | 119 | protected def builtin_cast_to_float(variable_name, src_type, dst_type) 120 | value = LibC.LLVMBuildLoad(@builder, @scope.get(variable_name), variable_name) 121 | 122 | case src_type 123 | when .unsigned? 124 | LibC.LLVMBuildUIToFP(@builder, value, llvm_type(dst_type), "") 125 | when .integer? 126 | LibC.LLVMBuildSIToFP(@builder, value, llvm_type(dst_type), "") 127 | when .float? 128 | if src_type.bits < dst_type.bits 129 | LibC.LLVMBuildFPExt(@builder, value, llvm_type(dst_type), "") 130 | else 131 | LibC.LLVMBuildFPTrunc(@builder, value, llvm_type(dst_type), "") 132 | end 133 | else 134 | raise "unreachable" 135 | end 136 | end 137 | 138 | protected def builtin_div(lhs_name, rhs_name, type) 139 | lhs = LibC.LLVMBuildLoad(@builder, @scope.get(lhs_name), lhs_name) 140 | rhs = LibC.LLVMBuildLoad(@builder, @scope.get(rhs_name), rhs_name) 141 | 142 | case type 143 | when .unsigned? 144 | LibC.LLVMBuildUDiv(@builder, lhs, rhs, "") 145 | when .integer? 146 | LibC.LLVMBuildSDiv(@builder, lhs, rhs, "") 147 | when .float? 148 | LibC.LLVMBuildFDiv(@builder, lhs, rhs, "") 149 | else 150 | raise "unreachable" 151 | end 152 | end 153 | 154 | protected def builtin_floor(variable_name, type) 155 | value = LibC.LLVMBuildLoad(@builder, @scope.get(variable_name), variable_name) 156 | func = llvm_intrinsic("floor", type) 157 | LibC.LLVMBuildCall(@builder, func, [value], 1, "") 158 | end 159 | 160 | protected def builtin_ceil(variable_name, type) 161 | value = LibC.LLVMBuildLoad(@builder, @scope.get(variable_name), variable_name) 162 | func = llvm_intrinsic("ceil", type) 163 | LibC.LLVMBuildCall(@builder, func, [value], 1, "") 164 | end 165 | 166 | protected def builtin_truncate(variable_name, type) 167 | value = LibC.LLVMBuildLoad(@builder, @scope.get(variable_name), variable_name) 168 | func = llvm_intrinsic("trunc", type) 169 | LibC.LLVMBuildCall(@builder, func, [value], 1, "") 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /src/codegen/literals.cr: -------------------------------------------------------------------------------- 1 | require "../codegen" 2 | 3 | module Runic 4 | class Codegen 5 | def codegen(node : AST::Boolean) : LibC::LLVMValueRef 6 | @debug.emit_location(node) 7 | LibC.LLVMConstInt(llvm_type(node), node.value == "true" ? 1 : 0, 0) 8 | end 9 | 10 | def codegen(node : AST::Integer) : LibC::LLVMValueRef 11 | @debug.emit_location(node) 12 | 13 | if node.value.starts_with?('0') && node.value.size > 2 14 | value = node.value[2..-1] 15 | else 16 | value = node.negative ? "-#{node.value}" : node.value 17 | end 18 | 19 | LibC.LLVMConstIntOfStringAndSize(llvm_type(node), value, value.bytesize, node.radix) 20 | end 21 | 22 | def codegen(node : AST::Float) : LibC::LLVMValueRef 23 | @debug.emit_location(node) 24 | value = node.negative ? "-#{node.value}" : node.value 25 | LibC.LLVMConstRealOfStringAndSize(llvm_type(node), value, value.bytesize) 26 | end 27 | 28 | def codegen(node : AST::StringLiteral) : LibC::LLVMValueRef 29 | @debug.emit_location(node) 30 | 31 | if st = @program.structs["String"]? 32 | if fn = st.method("initialize") 33 | if func = LibC.LLVMGetNamedFunction(@module, fn.symbol_name) 34 | slf = LibC.LLVMBuildAlloca(@builder, llvm_type("String"), "") 35 | args = [ 36 | slf, 37 | LibC.LLVMBuildGlobalStringPtr(@builder, node.value, ""), # ptr 38 | LibC.LLVMConstInt(llvm_type("i32"), node.bytesize, false), # bytesize TODO: uint (should be arch-dependent) 39 | ] 40 | LibC.LLVMBuildCall(@builder, func, args, args.size, "") 41 | return LibC.LLVMBuildLoad(@builder, slf, "") 42 | end 43 | end 44 | end 45 | 46 | raise CodegenError.new("undefined function 'String::initialize'") 47 | end 48 | 49 | def codegen(node : AST::Variable) : LibC::LLVMValueRef 50 | if alloca = @scope.get(node.name) 51 | @debug.emit_location(node) 52 | LibC.LLVMBuildLoad(@builder, alloca, node.name) 53 | else 54 | raise CodegenError.new("using variable before definition: #{node.name}") 55 | end 56 | end 57 | 58 | def codegen(node : AST::Constant) : LibC::LLVMValueRef 59 | if value = @constant_values[node.name]? 60 | @debug.emit_location(node) 61 | value 62 | else 63 | raise CodegenError.new("using constant before definition: #{node.name}") 64 | end 65 | end 66 | 67 | def codegen(node : AST::ConstantDefinition) : LibC::LLVMValueRef 68 | codegen(node.value).tap do |value| 69 | if @constant_values[node.name]? 70 | raise CodegenError.new("constant #{node.name} has already been initialized") 71 | end 72 | @constant_values[node.name] = value 73 | end 74 | end 75 | 76 | def codegen(node : AST::Alloca) : LibC::LLVMValueRef 77 | @debug.emit_location(node) 78 | LibC.LLVMBuildAlloca(@builder, llvm_type(node.type), "") 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /src/codegen/pointers.cr: -------------------------------------------------------------------------------- 1 | require "../codegen" 2 | 3 | module Runic 4 | class Codegen 5 | def codegen(node : AST::Reference) : LibC::LLVMValueRef 6 | case pointee = node.pointee 7 | when AST::Variable 8 | if alloca = @scope.get(pointee.name) 9 | alloca 10 | else 11 | raise CodegenError.new("using variable before definition: #{pointee.name}") 12 | end 13 | when AST::Alloca 14 | codegen(node.pointee) 15 | else 16 | # must store the expression in an alloca to pass its value by reference; 17 | # this should only happen to pass 'self' in expressions such as `"hello".to_unsafe` 18 | value = codegen(node.pointee) 19 | 20 | @debug.emit_location(node) 21 | alloca = LibC.LLVMBuildAlloca(@builder, llvm_type(node.pointee.type), "") 22 | LibC.LLVMBuildStore(@builder, value, alloca) 23 | alloca 24 | 25 | #raise "BUG: unknown reference pointee #{pointee.class.name} (only variables and allocas are supported)" 26 | end 27 | end 28 | 29 | def codegen(node : AST::Dereference) : LibC::LLVMValueRef 30 | pointer = codegen(node.pointee) 31 | 32 | @debug.emit_location(node) 33 | LibC.LLVMBuildLoad(@builder, pointer, "") 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /src/codegen/structures.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | class Codegen 3 | def codegen(node : AST::Struct) : LibC::LLVMValueRef 4 | type = build_llvm_struct(node.name) 5 | elements = node.variables.map { |t| llvm_type(t) } 6 | packed = node.attribute?("packed") ? 1 : 0 7 | LibC.LLVMStructSetBody(type, elements, elements.size, packed) 8 | 9 | node.variables.each { |ivar| @ivars << ivar.name } 10 | node.methods.each { |fn| codegen(fn) } 11 | 12 | @ivars.clear 13 | llvm_void_value 14 | end 15 | 16 | def codegen(node : AST::InstanceVariable) : LibC::LLVMValueRef 17 | LibC.LLVMBuildLoad(@builder, build_ivar(node.name), "") 18 | end 19 | 20 | def build_ivar(name : String) : LibC::LLVMValueRef 21 | if slf_ptr = @scope.get("self") 22 | if index = @ivars.index(name) 23 | slf = LibC.LLVMBuildLoad(@builder, slf_ptr, "") 24 | LibC.LLVMBuildStructGEP(@builder, slf, index, "") 25 | else 26 | raise CodegenError.new("undefined instance variable @#{name}") 27 | end 28 | else 29 | raise CodegenError.new("can't access instance variable in non struct-method") 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /src/compiler.cr: -------------------------------------------------------------------------------- 1 | require "./codegen" 2 | require "./parser" 3 | require "./program" 4 | require "./semantic" 5 | 6 | module Runic 7 | struct Compiler 8 | getter target_triple : String 9 | getter cpu : String 10 | getter features : String 11 | @target : LibC::LLVMTargetRef? 12 | 13 | getter opt_level : LibC::LLVMCodeGenOptLevel 14 | getter reloc_mode : LibC::LLVMRelocMode 15 | getter code_model : LibC::LLVMCodeModel 16 | 17 | def initialize( 18 | target_triple = nil, 19 | @cpu = "generic", 20 | @features = "", 21 | @opt_level = LibC::LLVMCodeGenOptLevel::CodeGenLevelDefault, 22 | @reloc_mode = LibC::LLVMRelocMode::RelocPIC, 23 | @code_model = LibC::LLVMCodeModel::Default, 24 | @debug = DebugLevel::Default, 25 | ) 26 | @target_triple = target_triple ||= String.new(LibC.LLVMGetDefaultTargetTriple()) 27 | @program = Program.new 28 | @codegen = Codegen.new( 29 | @program, 30 | debug: @debug, 31 | opt_level: opt_level, 32 | optimize: !opt_level.code_gen_level_none? 33 | ) 34 | LLVM.init(target_triple) 35 | LLVM.init_global_pass_registry unless opt_level.code_gen_level_none? 36 | @codegen.target_triple = target_triple 37 | @codegen.data_layout = data_layout 38 | end 39 | 40 | def parse(path : String) : Nil 41 | File.open(path, "r") do |io| 42 | lexer = Lexer.new(io, path) 43 | parser = Parser.new(lexer, top_level_expressions: false) 44 | parser.parse do |node| 45 | case node 46 | when AST::Require 47 | if require_path = @program.resolve_require(node) 48 | parse(require_path) 49 | end 50 | else 51 | @program.register(node) 52 | end 53 | end 54 | end 55 | end 56 | 57 | def analyze : Nil 58 | Semantic.analyze(@program) 59 | end 60 | 61 | def codegen(path : String) : Nil 62 | @codegen.codegen(path) 63 | end 64 | 65 | def emit_llvm(output : String) : Nil 66 | @codegen.verify 67 | @codegen.optimize 68 | @codegen.emit_llvm(output) 69 | end 70 | 71 | def emit_object(output : String) : Nil 72 | @codegen.verify 73 | @codegen.optimize 74 | @codegen.emit_object(target_machine, output) 75 | end 76 | 77 | def target 78 | @target ||= begin 79 | if LibC.LLVMGetTargetFromTriple(target_triple, out target, out err_msg) == 1 80 | msg = String.new(err_msg) 81 | LibC.LLVMDisposeMessage(err_msg) 82 | raise msg 83 | end 84 | target 85 | end 86 | end 87 | 88 | def target_machine 89 | @target_machine ||= LibC.LLVMCreateTargetMachine(target, target_triple, cpu, features, opt_level, reloc_mode, code_model) 90 | end 91 | 92 | def data_layout 93 | LibC.LLVMCreateTargetDataLayout(target_machine) 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /src/config.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | def self.root 3 | ENV.fetch("RUNIC_ROOT") do 4 | File.expand_path("../..", Process.executable_path.not_nil!) 5 | end 6 | end 7 | 8 | def self.libexec 9 | File.join(root, "libexec") 10 | end 11 | 12 | def self.corelib 13 | File.join(root, "corelib", "corelib.runic") 14 | end 15 | 16 | def self.manpages 17 | File.join(root, "doc", "man1") 18 | end 19 | 20 | def self.open_manpage(command) 21 | manpage = File.join(manpages, "runic-#{command}.1") 22 | Process.exec("man", {manpage}) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/data_layout.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | class DataLayout 3 | enum Mangling 4 | None = 0 5 | ELF 6 | MachO 7 | Mips 8 | WinCOFF 9 | WinCOFFX86 10 | end 11 | 12 | enum Endian 13 | Big 14 | Little 15 | end 16 | 17 | enum AlignType 18 | Integer 19 | Float 20 | Vector 21 | Aggregate 22 | end 23 | 24 | record Align, 25 | type : AlignType, 26 | bit_size : Int32, 27 | abi_align : Int32, 28 | pref_align : Int32 29 | 30 | record PointerAlign, 31 | addr_space : Int32, 32 | abi_align : Int32, 33 | pref_align : Int32, 34 | mem_size : Int32, 35 | index_size : Int32 36 | 37 | def self.from(_module : LibC::LLVMModuleRef) : self 38 | ptr = LibC.LLVMGetDataLayoutStr(_module) 39 | parse(String.new(ptr)) 40 | end 41 | 42 | def self.parse(layout : String) : self 43 | new.tap(&.parse(layout)) 44 | end 45 | 46 | getter mangling 47 | getter pointer_align 48 | getter stack_natural_align 49 | getter native_integers 50 | 51 | def initialize 52 | @mangling = Mangling::None 53 | @endianess = Endian::Little 54 | @pointer_align = PointerAlign.new(0, 8, 8, 8, 8) 55 | @stack_natural_align = 0 56 | @native_integers = [] of Int32 57 | 58 | @alignments = [ 59 | Align.new(AlignType::Integer, 1, 1, 1), # i1 60 | Align.new(AlignType::Integer, 8, 1, 1), # i8 61 | Align.new(AlignType::Integer, 16, 2, 2), # i16 62 | Align.new(AlignType::Integer, 32, 4, 4), # i32 63 | Align.new(AlignType::Integer, 64, 4, 8), # i64 64 | Align.new(AlignType::Float, 16, 2, 2), # half 65 | Align.new(AlignType::Float, 32, 4, 4), # float 66 | Align.new(AlignType::Float, 64, 4, 8), # double 67 | Align.new(AlignType::Float, 128, 16, 16), # ppcf128, quad, ... 68 | Align.new(AlignType::Vector, 64, 8, 8), # v2i32, v1i64, ... 69 | Align.new(AlignType::Vector, 128, 16, 16), # v16i8, v8i16, v4i32, ... 70 | Align.new(AlignType::Aggregate, 0, 0, 8) # struct 71 | ] 72 | end 73 | 74 | def big_endian? 75 | @endianess.big? 76 | end 77 | 78 | def little_endian? 79 | @endianess.little? 80 | end 81 | 82 | def alignment(type : AlignType, bit_size : Int32) : Align 83 | align = @alignments.find { |align| align.type == type && align.bit_size == bit_size } 84 | raise "BUG: missing data layout align for #{type} at #{bit_size} bits" unless align 85 | align 86 | end 87 | 88 | def pointer_size_in_bits 89 | 8_u64 * @pointer_align.mem_size 90 | end 91 | 92 | def parse(str : String) : Nil 93 | str.split('-') do |element| 94 | case element[0] 95 | when 'm' 96 | parse_mangling(element[2..-1]) 97 | when 'E' 98 | @endianess = Endian::Big 99 | when 'e' 100 | @endianess = Endian::Little 101 | when 'p' 102 | parse_pointer_align(element[1..-1]) 103 | when 'i' 104 | parse_align(AlignType::Integer, element[1..-1]) 105 | when 'f' 106 | parse_align(AlignType::Float, element[1..-1]) 107 | when 'v' 108 | parse_align(AlignType::Vector, element[1..-1]) 109 | when 'a' 110 | parse_align(AlignType::Aggregate, element[1..-1]) 111 | when 'n' 112 | element[1..-1].split(':') { |s| @native_integers << s.to_i } 113 | when 'S' 114 | @stack_natural_align = in_bytes(element[1..-1]) 115 | else 116 | # shut up, crystal 117 | end 118 | end 119 | end 120 | 121 | private def parse_mangling(value) 122 | case value 123 | when "e" then @mangling = Mangling::ELF 124 | when "o" then @mangling = Mangling::MachO 125 | when "m" then @mangling = Mangling::Mips 126 | when "w" then @mangling = Mangling::WinCOFF 127 | when "x" then @mangling = Mangling::WinCOFFX86 128 | else # shut up, crystal 129 | end 130 | end 131 | 132 | private def parse_pointer_align(str) : Nil 133 | values = str.split(':') 134 | 135 | addr_space = values[0].to_i? || 0 136 | mem_size = in_bytes(values[1]) 137 | abi_align = in_bytes(values[2]) 138 | 139 | if s = values[3]? 140 | pref_align = in_bytes(s) 141 | else 142 | pref_align = abi_align 143 | end 144 | 145 | if s = values[4]? 146 | index_size = in_bytes(s) 147 | else 148 | index_size = mem_size 149 | end 150 | 151 | @pointer_align = PointerAlign.new(addr_space, mem_size, abi_align, pref_align, index_size) 152 | end 153 | 154 | private def parse_align(type, str) : Nil 155 | values = str.split(':') 156 | size = values[0].to_i? || 0 157 | abi_align = in_bytes(values[1]) 158 | 159 | if s = values[2]? 160 | pref_align = in_bytes(s) 161 | else 162 | pref_align = abi_align 163 | end 164 | 165 | index, align = find_alignment_lower_bound(type, size) 166 | 167 | if align.type == type && align.bit_size == size 168 | @alignments[index] = Align.new(type, size, abi_align, pref_align) 169 | else 170 | @alignments.insert(index, Align.new(type, size, abi_align, pref_align)) 171 | end 172 | end 173 | 174 | private def find_alignment_lower_bound(type, bit_size) 175 | found = false 176 | 177 | @alignments.each_with_index do |align, index| 178 | if align.type == type 179 | return {index, align} if align.bit_size >= bit_size 180 | found = true 181 | elsif found 182 | return {index, align} 183 | end 184 | end 185 | 186 | raise "unreachable" 187 | end 188 | 189 | private def in_bytes(s : String) 190 | s.to_i // 8 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /src/definitions.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | module INTRINSICS 3 | BOOLS = %w(bool) 4 | SIGNED = %w(int long i8 i16 i32 i64 i128) 5 | UNSIGNED = %w(uint ulong u8 u16 u32 u64 u128) 6 | INTEGERS = SIGNED + UNSIGNED 7 | FLOATS = %w(float f32 f64) # f16, f80, f128 (?) 8 | end 9 | 10 | module OPERATORS 11 | # TODO: typeof and sizeof are unary operators 12 | UNARY = %w(~ ! + - & *) 13 | ASSIGNMENT = %w(= += -= *= **= /= //= %= %%= &= &&= |= ||= ^= <<= >>=) 14 | LOGICAL = %w(== != <=> < <= > >= || &&) 15 | BINARY = %w(+ - * ** / // % %% & | ^ << >>) 16 | ALL = (UNARY + ASSIGNMENT + LOGICAL + BINARY).uniq 17 | 18 | # Returns the precedence score for an operator. A low value means a lower 19 | # precedence than a high value. 20 | def self.precedence(operator) 21 | case operator 22 | when "=" then 5 23 | when "+=" then 5 24 | when "-=" then 5 25 | when "*=" then 5 26 | when "**=" then 5 27 | when "/=" then 5 28 | when "//=" then 5 29 | when "%=" then 5 30 | when "%%=" then 5 31 | when "&=" then 5 32 | when "&&=" then 5 33 | when "|=" then 5 34 | when "||=" then 5 35 | when "^=" then 5 36 | when "<<=" then 5 37 | when ">>=" then 5 38 | 39 | when "||" then 10 40 | when "&&" then 12 41 | 42 | when "==" then 20 43 | when "!=" then 20 44 | when "<=>" then 20 45 | 46 | when "<" then 30 47 | when "<=" then 30 48 | when ">" then 30 49 | when ">=" then 30 50 | 51 | when "|" then 32 52 | when "^" then 32 53 | when "&" then 34 54 | 55 | when "<<" then 40 56 | when ">>" then 40 57 | 58 | when "+" then 50 59 | when "-" then 50 60 | 61 | when "*" then 60 62 | when "/" then 60 63 | when "//" then 60 64 | when "%" then 60 65 | when "%%" then 60 66 | 67 | when "**" then 70 68 | 69 | # always break on unknown token 70 | else -1 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /src/documentation.cr: -------------------------------------------------------------------------------- 1 | require "./lexer" 2 | require "./parser" 3 | require "./semantic" 4 | require "./documentation/generator" 5 | 6 | module Runic 7 | class Documentation 8 | def initialize(@sources : Array(String)) 9 | @program = Program.new 10 | end 11 | 12 | def generate(generator : Generator) 13 | parse 14 | generator.generate(@program) 15 | end 16 | 17 | def parse 18 | @sources.each do |source| 19 | if Dir.exists?(source) 20 | search_directory(source) 21 | elsif File.exists?(source) 22 | parse(source) 23 | else 24 | # STDERR.puts "ERROR: no such file or directory '#{source}'." 25 | end 26 | end 27 | 28 | Semantic.analyze(@program) 29 | end 30 | 31 | private def search_directory(path : String) 32 | Dir.open(path) do |dir| 33 | dir.each_child do |filename| 34 | if Dir.exists?(filename) 35 | search_directory File.join(path, filename) 36 | elsif filename.ends_with?(".runic") 37 | parse File.join(path, filename) 38 | else 39 | # STDERR.puts "WARN: skipping '#{filename}' in '#{path}'" 40 | end 41 | end 42 | end 43 | end 44 | 45 | private def parse(path : String) 46 | # STDERR.puts "parsing #{path}" 47 | 48 | File.open(path) do |file| 49 | lexer = Lexer.new(file, path) 50 | parser = Parser.new(lexer) 51 | parser.parse { |node| @program.register(node) } 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/documentation/generator.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | class Documentation 3 | abstract struct Generator 4 | def initialize(@output : String) 5 | end 6 | 7 | abstract def generate(program : Program) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/documentation/html_generator.cr: -------------------------------------------------------------------------------- 1 | require "html" 2 | require "./generator" 3 | 4 | module HTML 5 | class Builder 6 | private getter io : IO 7 | 8 | def initialize(@io) 9 | end 10 | 11 | def document 12 | io << "\n" 13 | element("html") { yield } 14 | end 15 | 16 | def element(name : String, content : String, **attributes) 17 | element(name, **attributes) { text(content) } 18 | end 19 | 20 | def element(name : String, **attributes) 21 | io << '<' << name 22 | attributes.each do |key, value| 23 | io << ' ' 24 | key.to_s(io) 25 | io << "=\"" 26 | HTML.escape(value, io) 27 | io << '"' 28 | end 29 | io << '>' 30 | yield 31 | io << "' 32 | end 33 | 34 | def text(content : String) 35 | HTML.escape(content, io) 36 | end 37 | 38 | def raw(content : String) 39 | io.puts content 40 | end 41 | end 42 | 43 | def self.build(io : IO) 44 | html = Builder.new(io) 45 | html.document do 46 | yield html 47 | end 48 | end 49 | end 50 | 51 | module Runic 52 | class Documentation 53 | struct HTMLGenerator < Generator 54 | @git_hash : String 55 | @git_remote : String? 56 | 57 | def initialize(output : String) 58 | super output 59 | @git_hash = `git log -1 --pretty=format:%H 2>/dev/null` 60 | @git_remote = search_git_remote if $?.success? 61 | end 62 | 63 | private def search_git_remote 64 | `git remote -v`.split('\n').each do |remote| 65 | if idx = remote.index("github.com") 66 | if stop = remote.index('/', idx) 67 | if stop = remote.index('.', stop + 1) 68 | return remote[idx...stop].sub(':', '/') 69 | else 70 | return remote[idx..-1].sub(':', '/') 71 | end 72 | end 73 | end 74 | end 75 | end 76 | 77 | def generate(program : Program) 78 | # TODO: constants 79 | # TODO: externs 80 | 81 | toplevel = AST::Struct.new("toplevel", [] of String, "", Location.new("toplevel.runic")) 82 | program.functions.each { |_, node| toplevel.methods << node } 83 | generate(toplevel) 84 | 85 | program.structs.each { |_, node| generate(node) } 86 | end 87 | 88 | private def generate(node : AST::Struct) 89 | File.open(File.join(@output, "#{node.name}.html"), "w") do |io| 90 | document(io, title: node.name) do |html| 91 | html.element("div", id: "contents") do 92 | html.element("div") do 93 | 94 | html.element("h1") do 95 | unless node.name == "toplevel" 96 | if node.attribute?("primitive") 97 | html.element("small", "primitive") 98 | else 99 | html.element("small", "struct") 100 | end 101 | end 102 | html.text " " 103 | html.text node.name 104 | end 105 | 106 | html.element("p") do 107 | html.text node.documentation 108 | end 109 | 110 | # html.element("h2", "Functions", id: "#functions") 111 | 112 | node.methods.each do |fn| 113 | html.element("article", id: "function-#{fn.original_name}") do 114 | html.element("h3") do 115 | html.element("code") { signature(html, fn) } 116 | location(html, fn) 117 | end 118 | 119 | html.element("p") do 120 | html.text fn.prototype.documentation 121 | end 122 | end 123 | end 124 | end 125 | end 126 | 127 | sidebar(html) do 128 | html.element("h2", "functions") 129 | node.methods.each { |fn| sidebar_entry(html, fn) } 130 | end 131 | end 132 | end 133 | end 134 | 135 | private def location(html : HTML::Builder, fn : AST::Function) 136 | if (commit = @git_hash) && (remote = @git_remote) 137 | if remote.starts_with?("github.com") 138 | href = "https://#{remote}/blob/#{commit}/#{fn.location.file}#L#{fn.location.line}" 139 | end 140 | end 141 | if href 142 | html.element("a", "", href: href, class: "view-source", title: fn.location.to_s) 143 | else 144 | html.element("a", "", class: "view-source", title: fn.location.to_s) 145 | end 146 | end 147 | 148 | private def signature(html : HTML::Builder, fn : AST::Function) 149 | html.text "def " 150 | html.element("a", fn.original_name, href: "#function-#{fn.original_name}") 151 | 152 | args = fn.args 153 | if args.first.try(&.name) == "self" 154 | (args = args.dup).shift 155 | end 156 | 157 | unless args.empty? 158 | html.text "(" 159 | args.each_with_index do |arg, index| 160 | html.text ", " unless index == 0 161 | html.text arg.name 162 | html.text " : " 163 | html.element("a", arg.type.to_s, class: "type") 164 | end 165 | html.text ")" 166 | end 167 | 168 | html.text " : " 169 | html.element("a", fn.type.to_s, class: "type") 170 | end 171 | 172 | private def document(io : IO, title : String) 173 | HTML.build(io) do |html| 174 | html.element("head") do 175 | html.element("title", title) 176 | html.element("style") do 177 | html.raw <<-CSS 178 | * { font-family: inherit; } 179 | body { 180 | display: grid; 181 | min-height: 100vh; 182 | padding: 0; 183 | margin: 0; 184 | -ms-grid-template-rows: 1fr; 185 | -ms-grid-template-columns: 15% 1fr; 186 | grid-template-rows: 1fr; 187 | grid-template-columns: 15% 1fr; 188 | font: 16px/26px Georgia, serif; 189 | } 190 | code { 191 | font-family: Menlo, monospace; 192 | font-size: 0.8em; 193 | } 194 | h1 { font-size: 26px; margin: 0 0 1em; } 195 | h1 small { font-size: 1rem; color: #6a6a6a; } 196 | h2 { font-size: 20px; } 197 | h3 { font-size: 16px; } 198 | h3, p { margin: 1em 0; } 199 | article { 200 | margin: 1em 0 2em; 201 | padding: 0 1em; 202 | } 203 | a { color: #78ab00; } 204 | 205 | #sidebar { 206 | -ms-grid-row: 1; 207 | -ms-grid-column: 1; 208 | grid-row: 1; 209 | grid-column: 1 / 2; 210 | order: -1; 211 | background: #383838; 212 | color: #d8d8d8; 213 | padding: 0 0 1em; 214 | } 215 | #sidebar h2 { 216 | text-align: center; 217 | } 218 | #sidebar ul { 219 | list-style: none; 220 | padding-left: 0; 221 | margin: 1em 0; 222 | } 223 | #sidebar a { 224 | display: block; 225 | padding: 0 1em; 226 | color: #bacf00; 227 | } 228 | 229 | #contents { 230 | -ms-grid-row: 1; 231 | -ms-grid-column: 2; 232 | grid-row: 1; 233 | grid-column: 2 / 3; 234 | max-width: 50em; 235 | padding: 1em; 236 | } 237 | #contents h3 { 238 | background: #f8f8f8; 239 | padding: 0.5em 1em; 240 | margin: -0.5em -1em; 241 | } 242 | 243 | .view-source { 244 | float: right; 245 | font-size: 14px; 246 | color: #aaa; 247 | text-decoration: none; 248 | } 249 | .view-source:hover { 250 | color: #666; 251 | } 252 | 253 | .type { 254 | color: #00bacf; 255 | } 256 | CSS 257 | end 258 | end 259 | 260 | html.element("body") do 261 | yield html 262 | end 263 | end 264 | end 265 | 266 | private def sidebar(html : HTML::Builder) 267 | html.element("nav", id: "sidebar") do 268 | html.element("ul") do 269 | yield 270 | end 271 | end 272 | end 273 | 274 | private def sidebar_entry(html : HTML::Builder, fn : AST::Function) 275 | html.element("li") do 276 | html.element("a", fn.original_name, href: "#function-#{fn.original_name}") 277 | end 278 | end 279 | end 280 | end 281 | end 282 | -------------------------------------------------------------------------------- /src/documentation/json_generator.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module Runic 4 | class Documentation 5 | struct JSONGenerator < Generator 6 | def generate(program : Program) 7 | document do |json| 8 | # TODO: constants 9 | # TODO: externs 10 | 11 | json.scalar "functions" 12 | program.functions.each do |_, node| 13 | generate(json, node) 14 | end 15 | 16 | json.scalar "structs" 17 | program.structs.each do |_, node| 18 | generate(json, node) 19 | end 20 | end 21 | end 22 | 23 | private def document 24 | File.open(File.join(@output, "index.json"), "w") do |file| 25 | JSON.build(file) do |json| 26 | json.object { yield json } 27 | end 28 | end 29 | end 30 | 31 | private def generate(json : JSON::Builder, node : AST::Struct) 32 | json.object do 33 | json.scalar "name" 34 | json.scalar node.name 35 | 36 | array(json, "attributes", node.attributes) do |attribute| 37 | json.scalar attribute 38 | end 39 | 40 | array(json, "methods", node.methods) do |fn| 41 | generate(json, fn) 42 | end 43 | 44 | json.scalar "location" 45 | json.scalar node.location.to_s 46 | 47 | json.scalar "documentation" 48 | json.scalar node.documentation 49 | end 50 | end 51 | 52 | private def generate(json : JSON::Builder, fn : AST::Function) 53 | json.object do 54 | json.scalar "name" 55 | json.scalar fn.original_name 56 | 57 | array(json, "arguments", fn.args) do |arg, index| 58 | next if index == 0 && arg.name == "self" 59 | json.object do 60 | json.scalar arg.name 61 | json.scalar arg.type.to_s 62 | end 63 | end 64 | 65 | json.scalar "return" 66 | json.scalar fn.type.to_s 67 | 68 | json.scalar "location" 69 | json.scalar fn.location.to_s 70 | 71 | json.scalar "documentation" 72 | json.scalar fn.prototype.documentation 73 | end 74 | end 75 | 76 | private def array(json : JSON::Builder, name, array : Enumerable) 77 | json.scalar name 78 | json.array do 79 | array.each_with_index do |value, index| 80 | yield value, index 81 | end 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /src/documentation/yaml_generator.cr: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | 3 | module Runic 4 | class Documentation 5 | struct YAMLGenerator < Generator 6 | def generate(program : Program) 7 | document do |yaml| 8 | # TODO: constants 9 | # TODO: externs 10 | 11 | yaml.scalar "functions" 12 | program.functions.each do |_, node| 13 | generate(yaml, node) 14 | end 15 | 16 | yaml.scalar "structs" 17 | yaml.sequence do 18 | program.structs.each do |_, node| 19 | generate(yaml, node) 20 | end 21 | end 22 | end 23 | end 24 | 25 | private def document 26 | File.open(File.join(@output, "index.yaml"), "w") do |file| 27 | YAML.build(file) do |yaml| 28 | yaml.mapping { yield yaml } 29 | end 30 | end 31 | end 32 | 33 | private def generate(yaml : YAML::Builder, node : AST::Struct) 34 | yaml.mapping do 35 | yaml.scalar "name" 36 | yaml.scalar node.name 37 | 38 | sequence(yaml, "attributes", node.attributes) do |name| 39 | yaml.scalar name 40 | end 41 | 42 | sequence(yaml, "methods", node.methods) do |fn| 43 | generate(yaml, fn) 44 | end 45 | 46 | yaml.scalar "location" 47 | yaml.scalar node.location.to_s 48 | 49 | yaml.scalar "documentation" 50 | yaml.scalar node.documentation 51 | end 52 | end 53 | 54 | private def generate(yaml : YAML::Builder, fn : AST::Function) 55 | yaml.mapping do 56 | yaml.scalar "name" 57 | yaml.scalar fn.original_name 58 | 59 | sequence(yaml, "attributes", fn.attributes) do |name| 60 | yaml.scalar name 61 | end 62 | 63 | sequence(yaml, "arguments", fn.args) do |arg, index| 64 | next if index == 0 && arg.name == "self" 65 | yaml.mapping do 66 | yaml.scalar arg.name 67 | yaml.scalar arg.type.to_s 68 | end 69 | end 70 | 71 | yaml.scalar "return" 72 | yaml.scalar fn.type.to_s 73 | 74 | yaml.scalar "location" 75 | yaml.scalar fn.location.to_s 76 | 77 | yaml.scalar "documentation" 78 | yaml.scalar fn.prototype.documentation 79 | end 80 | end 81 | 82 | private def sequence(yaml : YAML::Builder, name, sequence : Enumerable) 83 | yaml.scalar name 84 | yaml.sequence do 85 | sequence.each_with_index do |value, index| 86 | yield value, index 87 | end 88 | end 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /src/errors.cr: -------------------------------------------------------------------------------- 1 | require "colorize" 2 | require "./ast" 3 | 4 | module Runic 5 | class Error < Exception 6 | def original_message 7 | @message 8 | end 9 | 10 | def pretty_report(io : IO, source = true) 11 | io.print "#{self.class.name[7..-1]}: #{original_message}\n".colorize(:yellow).mode(:bold) 12 | return unless source 13 | 14 | io.print "\nat #{location}\n\n" 15 | 16 | File.open(location.file) do |source| 17 | # skip previous lines 18 | (location.line - 1).times { source.gets } 19 | 20 | # print source line 21 | prefix = " #{location.line} | " 22 | io.print prefix.colorize(:dark_gray) 23 | io.print source.gets(chomp: false) 24 | 25 | # print ^ location pointer 26 | (prefix.size + location.column - 1).times { io.print ' ' } 27 | io.print "^\n".colorize(:green).mode(:bold) 28 | io.print "\n" 29 | end 30 | end 31 | end 32 | 33 | class ParseError < Error 34 | getter location : Location 35 | 36 | def self.new(message, node : AST::Node) 37 | new(message, node.location) 38 | end 39 | 40 | def initialize(@message, @location) 41 | end 42 | 43 | def message 44 | "#{@message} at #{@location}" 45 | end 46 | end 47 | 48 | class SyntaxError < Error 49 | getter location : Location 50 | 51 | def self.new(message, node : AST::Node) 52 | new(message, node.location) 53 | end 54 | 55 | def initialize(@message, @location) 56 | end 57 | 58 | def message 59 | "#{@message} at #{@location}" 60 | end 61 | end 62 | 63 | class SemanticError < Error 64 | getter location : Location 65 | 66 | def self.new(message, node : AST::Node) 67 | new(message, node.location) 68 | end 69 | 70 | def initialize(@message, @location) 71 | end 72 | 73 | def message 74 | "#{@message} at #{@location}" 75 | end 76 | end 77 | 78 | class CodegenError < Error 79 | end 80 | 81 | class ConflictError < Error 82 | getter original_location : Location 83 | getter location : Location 84 | 85 | def initialize(@message, @original_location, @location) 86 | end 87 | 88 | def message 89 | "#{@message} at #{@location} (previous at #{@original_location})" 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /src/intrinsics.runic: -------------------------------------------------------------------------------- 1 | # extern llvm.va_start(i8*) : void 2 | # extern llvm.va_end(i8*) : void 3 | # extern llvm.va_copy(i8*, i8*) : void 4 | 5 | # extern llvm.memcpy.p0i8.p0i8.i32(i8*, i8*, i32, i32, bool) : void 6 | # extern llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i32, bool) : void 7 | # extern llvm.memmove.p0i8.p0i8.i32(i8*, i8*, i32, i32, bool) : void 8 | # extern llvm.memmove.p0i8.p0i8.i64(i8*, i8*, i64, i32, bool) : void 9 | # extern llvm.memset.p0i8.i32(i8*, i8, i32, i32, bool) : void 10 | # extern llvm.memset.p0i8.i64(i8*, i8, i64, i32, bool) : void 11 | 12 | # extern @llvm.memset.element.unordered.atomic.p0i8.i32(i8*, i8, i32, i32) : void 13 | # extern @llvm.memset.element.unordered.atomic.p0i8.i64(i8*, i8, i64, i32) : void 14 | 15 | extern llvm.sqrt.f32(f32) : f32 16 | extern llvm.powi.f32(f32, i32) : f32 17 | extern llvm.sin.f32(f32) : f32 18 | extern llvm.cos.f32(f32) : f32 19 | extern llvm.pow.f32(f32, f32) : f32 20 | extern llvm.exp.f32(f32) : f32 21 | extern llvm.exp2.f32(f32) : f32 22 | extern llvm.log.f32(f32) : f32 23 | extern llvm.log10.f32(f32) : f32 24 | extern llvm.log2.f32(f32) : f32 25 | extern llvm.fma.f32(f32, f32, z : f32) : f32 26 | extern llvm.fabs.f32(f32) : f32 27 | extern llvm.minnum.f32(f32, f32) : f32 28 | extern llvm.maxnum.f32(f32, f32) : f32 29 | extern llvm.copysign.f32(f32, f32) : f32 30 | extern llvm.floor.f32(f32) : f32 31 | extern llvm.ceil.f32(f32) : f32 32 | extern llvm.trunc.f32(f32) : f32 33 | extern llvm.rint.f32(f32) : f32 34 | extern llvm.nearbyint.f32(f32) : f32 35 | extern llvm.round.f32(f32) : f32 36 | extern llvm.canonicalize.f32(f32) : f32 37 | extern llvm.fmuladd.f32(f32, f32, f32) : f32 38 | 39 | extern llvm.sqrt.f64(f64) : f64 40 | extern llvm.powi.f64(f64, i32) : f64 41 | extern llvm.sin.f64(f64) : f64 42 | extern llvm.cos.f64(f64) : f64 43 | extern llvm.pow.f64(f64, f64) : f64 44 | extern llvm.exp.f64(f64) : f64 45 | extern llvm.exp2.f64(f64) : f64 46 | extern llvm.log.f64(f64) : f64 47 | extern llvm.log10.f64(f64) : f64 48 | extern llvm.log2.f64(f64) : f64 49 | extern llvm.fma.f64(f64, f64, z : f64) : f64 50 | extern llvm.fabs.f64(f64) : f64 51 | extern llvm.minnum.f64(f64, f64) : f64 52 | extern llvm.maxnum.f64(f64, f64) : f64 53 | extern llvm.copysign.f64(f64, f64) : f64 54 | extern llvm.floor.f64(f64) : f64 55 | extern llvm.ceil.f64(f64) : f64 56 | extern llvm.trunc.f64(f64) : f64 57 | extern llvm.rint.f64(f64) : f64 58 | extern llvm.nearbyint.f64(f64) : f64 59 | extern llvm.round.f64(f64) : f64 60 | extern llvm.canonicalize.f64(f64) : f64 61 | extern llvm.fmuladd.f64(f64, f64, f64) : f64 62 | 63 | extern llvm.bitreverse.i8(i8) : i8 64 | extern llvm.bswap.i8(i8) : i8 65 | extern llvm.ctpop.i8(i8) : i8 66 | extern llvm.ctlz.i8(i8) : i8 67 | extern llvm.cttz.i8(i8) : i8 68 | # extern llvm.sadd.with.overflow.i8(i8, i8) : {i8, bool} 69 | # extern llvm.uadd.with.overflow.i8(i8, i8) : {i8, bool} 70 | # extern llvm.ssub.with.overflow.i8(i8, i8) : {i8, bool} 71 | # extern llvm.usub.with.overflow.i8(i8, i8) : {i8, bool} 72 | # extern llvm.smul.with.overflow.i8(i8, i8) : {i8, bool} 73 | # extern llvm.umul.with.overflow.i8(i8, i8) : {i8, bool} 74 | 75 | extern llvm.bitreverse.i16(i16) : i16 76 | extern llvm.bswap.i16(i16) : i16 77 | extern llvm.ctpop.i16(i16) : i16 78 | extern llvm.ctlz.i16(i16) : i16 79 | extern llvm.cttz.i16(i16) : i16 80 | # extern llvm.sadd.with.overflow.i16(i16, i16) : {i16, bool} 81 | # extern llvm.uadd.with.overflow.i16(i16, i16) : {i16, bool} 82 | # extern llvm.ssub.with.overflow.i16(i16, i16) : {i16, bool} 83 | # extern llvm.usub.with.overflow.i16(i16, i16) : {i16, bool} 84 | # extern llvm.smul.with.overflow.i16(i16, i16) : {i16, bool} 85 | # extern llvm.umul.with.overflow.i16(i16, i16) : {i16, bool} 86 | 87 | extern llvm.bitreverse.i32(i32) : i32 88 | extern llvm.bswap.i32(i32) : i32 89 | extern llvm.ctpop.i32(i32) : i32 90 | extern llvm.ctlz.i32(i32) : i32 91 | extern llvm.cttz.i32(i32) : i32 92 | # extern llvm.sadd.with.overflow.i32(i32, i32) : {i32, bool} 93 | # extern llvm.uadd.with.overflow.i32(i32, i32) : {i32, bool} 94 | # extern llvm.ssub.with.overflow.i32(i32, i32) : {i32, bool} 95 | # extern llvm.usub.with.overflow.i32(i32, i32) : {i32, bool} 96 | # extern llvm.smul.with.overflow.i32(i32, i32) : {i32, bool} 97 | # extern llvm.umul.with.overflow.i32(i32, i32) : {i32, bool} 98 | 99 | extern llvm.bitreverse.i64(i64) : i64 100 | extern llvm.bswap.i64(i64) : i64 101 | extern llvm.ctpop.i64(i64) : i64 102 | extern llvm.ctlz.i64(i64) : i64 103 | extern llvm.cttz.i64(i64) : i64 104 | # extern llvm.sadd.with.overflow.i64(i64, i64) : {i64, bool} 105 | # extern llvm.uadd.with.overflow.i64(i64, i64) : {i64, bool} 106 | # extern llvm.ssub.with.overflow.i64(i64, i64) : {i64, bool} 107 | # extern llvm.usub.with.overflow.i64(i64, i64) : {i64, bool} 108 | # extern llvm.smul.with.overflow.i64(i64, i64) : {i64, bool} 109 | # extern llvm.umul.with.overflow.i64(i64, i64) : {i64, bool} 110 | 111 | extern llvm.bitreverse.i128(i128) : i128 112 | extern llvm.bswap.i128(i128) : i128 113 | extern llvm.ctpop.i128(i128) : i128 114 | extern llvm.ctlz.i128(i128) : i128 115 | extern llvm.cttz.i128(i128) : i128 116 | # extern llvm.sadd.with.overflow.i128(i128, i128) : {i128, bool} 117 | # extern llvm.uadd.with.overflow.i128(i128, i128) : {i128, bool} 118 | # extern llvm.ssub.with.overflow.i128(i128, i128) : {i128, bool} 119 | # extern llvm.usub.with.overflow.i128(i128, i128) : {i128, bool} 120 | # extern llvm.smul.with.overflow.i128(i128, i128) : {i128, bool} 121 | # extern llvm.umul.with.overflow.i128(i128, i128) : {i128, bool} 122 | -------------------------------------------------------------------------------- /src/llvm.cr: -------------------------------------------------------------------------------- 1 | require "./c/llvm" 2 | 3 | module LLVM 4 | {% begin %} 5 | {% for target in LibC::LLVM_AVAILABLE_TARGETS %} 6 | @@init_{{target.id}} = false 7 | {% end %} 8 | {% end %} 9 | 10 | macro llvm_init(arch) 11 | {% if LibC::LLVM_AVAILABLE_TARGETS.any?(&.starts_with?(arch.stringify)) %} 12 | return if @@init_{{arch.id}} 13 | @@init_{{arch.id}} = true 14 | LibC.LLVMInitialize{{arch.id}}TargetInfo() 15 | LibC.LLVMInitialize{{arch.id}}Target() 16 | LibC.LLVMInitialize{{arch.id}}TargetMC() 17 | LibC.LLVMInitialize{{arch.id}}AsmPrinter() 18 | LibC.LLVMInitialize{{arch.id}}AsmParser() 19 | {% else %} 20 | raise "LLVM was built without the {{arch.id}} arch" 21 | {% end %} 22 | end 23 | 24 | def self.init_native : Nil 25 | init String.new(LibC.LLVMGetDefaultTargetTriple) 26 | LibC.LLVMLinkInMCJIT() 27 | end 28 | 29 | def self.init(triple : String) : Nil 30 | arch = triple.split('-').first 31 | 32 | case arch.downcase 33 | when "amd64", /i.86/, "x86_64" 34 | llvm_init X86 35 | when .starts_with?("aarch64") 36 | llvm_init AArch64 37 | when .starts_with?("arm") 38 | llvm_init ARM 39 | when .starts_with?("mips") 40 | llvm_init Mips 41 | when .starts_with?("ppc") 42 | llvm_init PowerPC 43 | when .starts_with?("sparcs") 44 | llvm_init Sparc 45 | else 46 | # shut up, crystal 47 | end 48 | end 49 | 50 | def self.init_global_pass_registry 51 | registry = LibC.LLVMGetGlobalPassRegistry() 52 | LibC.LLVMInitializeCore(registry) 53 | LibC.LLVMInitializeTransformUtils(registry) 54 | LibC.LLVMInitializeScalarOpts(registry) 55 | LibC.LLVMInitializeObjCARCOpts(registry) 56 | LibC.LLVMInitializeVectorization(registry) 57 | LibC.LLVMInitializeInstCombine(registry) 58 | LibC.LLVMInitializeAggressiveInstCombiner(registry) 59 | LibC.LLVMInitializeIPO(registry) 60 | LibC.LLVMInitializeInstrumentation(registry) 61 | LibC.LLVMInitializeAnalysis(registry) 62 | LibC.LLVMInitializeIPA(registry) 63 | LibC.LLVMInitializeCodeGen(registry) 64 | LibC.LLVMInitializeTarget(registry) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /src/location.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | struct Location 3 | getter file : String 4 | getter line : Int32 5 | getter column : Int32 6 | 7 | def initialize(@file, @line = 1, @column = 1) 8 | end 9 | 10 | def increment_line : Nil 11 | @line += 1 12 | @column = 1 13 | end 14 | 15 | def increment_column : Nil 16 | @column += 1 17 | end 18 | 19 | def to_s(io : IO) 20 | io << file 21 | io << ':' 22 | line.to_s(io) 23 | io << ':' 24 | column.to_s(io) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/mangler.cr: -------------------------------------------------------------------------------- 1 | require "./ast" 2 | 3 | module Runic 4 | module Mangler 5 | def self.mangle(fn : AST::Prototype, namespace : String? = nil) : String 6 | String.build do |str| 7 | str << "_Z" 8 | 9 | function(fn, str) 10 | 11 | if fn.args.any? 12 | fn.args.each do |arg| 13 | type(arg.type, str) 14 | end 15 | else 16 | str << 'v' 17 | end 18 | end 19 | end 20 | 21 | private def self.function(fn, str) 22 | # Itanium C++ ABI reference: 23 | # 24 | special(fn.name, str) do |name, str| 25 | case name 26 | when "+" then str << (unary?(fn) ? "ps" : "pl") 27 | when "-" then str << (unary?(fn) ? "ng" : "mi") 28 | when "~" then str << "co" 29 | when "*" then str << "ml" # ptr: de(reference) 30 | when "/" then str << "dv" 31 | when "%" then str << "rm" 32 | when "&" then str << "an" # ptr: ad(dress) 33 | when "|" then str << "or" 34 | when "^" then str << "eo" 35 | #when "=" then str << "as" 36 | #when "+=" then str << "pL" 37 | #when "-=" then str << "mI" 38 | #when "*=" then str << "mL" 39 | #when "/=" then str << "dV" 40 | #when "%=" then str << "rM" 41 | #when "&=" then str << "aN" 42 | #when "|=" then str << "oR" 43 | #when "^=" then str << "eO" 44 | when "<<" then str << "ls" 45 | when ">>" then str << "rs" 46 | #when "<<=" then str << "lS" 47 | #when ">>=" then str << "rS" 48 | when "==" then str << "eq" 49 | when "!=" then str << "ne" 50 | when "<" then str << "lt" 51 | when ">" then str << "gt" 52 | when "<=" then str << "le" 53 | when ">=" then str << "ge" 54 | when "<=>" then str << "ss" 55 | when "!" then str << "nt" 56 | when "&&" then str << "aa" 57 | when "||" then str << "oo" 58 | #when "new" then str << "nw" 59 | 60 | # extended operators 61 | when "//" then str << "v2dv" 62 | when "%%" then str << "v2rm" 63 | when "**" then str << "v2pw" 64 | 65 | else 66 | special(name, str) 67 | end 68 | end 69 | end 70 | 71 | private def self.special(name, str) 72 | special(name, str) do |ns, str| 73 | str << ns.bytesize 74 | escape(ns, str) 75 | end 76 | end 77 | 78 | private def self.special(name, str, &block) : Nil 79 | if name.includes?("::") 80 | str << 'N' 81 | name.split("::") { |ns| yield ns, str } 82 | str << 'E' 83 | else 84 | yield name, str 85 | end 86 | end 87 | 88 | private def self.unary?(fn) 89 | fn.args.size == 1 && fn.args.first.name == "self" 90 | end 91 | 92 | # TODO: escape chars that would be invalid symbols 93 | private def self.escape(name, str) : Nil 94 | str << name 95 | end 96 | 97 | # Itanium C++ ABI reference: 98 | # 99 | private def self.type(type : Type, str : String::Builder) : Nil 100 | if type.pointer? 101 | str << 'P' 102 | type(type.pointee_type, str) 103 | else 104 | case type.name 105 | when "void" then str << 'v' 106 | when "bool" then str << 'b' 107 | when "i8" then str << 'a' 108 | when "u8" then str << 'h' 109 | when "i16" then str << 's' 110 | when "u16" then str << 't' 111 | when "i32" then str << 'i' 112 | when "u32" then str << 'j' 113 | when "i64" then str << 'x' 114 | when "u64" then str << 'y' 115 | when "i128" then str << 'n' 116 | when "u128" then str << 'o' 117 | when "f32" then str << 'f' 118 | when "f64" then str << 'd' 119 | #when "f80" then str << 'e' 120 | #when "f128" then str << 'g' 121 | #when "..." then str << 'z' 122 | #when "char" then str << "Di" # char32_t 123 | else 124 | special(type.name, str) 125 | end 126 | end 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /src/program.cr: -------------------------------------------------------------------------------- 1 | require "./ast" 2 | 3 | module Runic 4 | class Program 5 | REQUIRE_PATH = [Dir.current] 6 | 7 | getter requires 8 | getter constants 9 | getter externs 10 | getter structs 11 | getter functions 12 | 13 | def initialize 14 | @requires = [] of String 15 | @constants = {} of String => AST::ConstantDefinition 16 | @externs = {} of String => AST::Prototype 17 | @structs = {} of String => AST::Struct 18 | 19 | # TODO: functions should be an Array(AST::Function) 20 | @functions = {} of String => AST::Function 21 | end 22 | 23 | def each 24 | @constants.each { |_, node| yield node } 25 | @externs.each { |_, node| yield node } 26 | @structs.each { |_, node| yield node } 27 | @functions.each { |_, node| yield node } 28 | end 29 | 30 | def resolve_require(node : AST::Require, relative_path : String? = nil) : String? 31 | path = node.path 32 | 33 | if path.starts_with?("/") 34 | raise SemanticError.new("can't require absolute file #{path}", node) 35 | end 36 | 37 | unless path.ends_with?(".runic") 38 | path = "#{path}.runic" 39 | end 40 | 41 | if path.starts_with?("./") || path.starts_with?("../") 42 | relative_path ||= File.dirname(node.location.file) 43 | path = File.expand_path(path, relative_path) 44 | elsif found = search_require_path(path) 45 | path = found 46 | else 47 | raise SemanticError.new("can't find #{path}", node) 48 | end 49 | 50 | unless File.exists?(path) 51 | raise SemanticError.new("can't find #{path}", node) 52 | end 53 | 54 | return if @requires.includes?(path) 55 | @requires << path 56 | 57 | path 58 | end 59 | 60 | def require(node : AST::Require, relative_path : String? = nil) : Bool 61 | if path = resolve_require(node, relative_path) 62 | self.require(path) 63 | else 64 | false 65 | end 66 | end 67 | 68 | def require(path : String) : Bool 69 | File.open(path, "r") do |file| 70 | lexer = Lexer.new(file, path, interactive: false) 71 | parser = Parser.new(lexer, top_level_expressions: false) 72 | 73 | while node = parser.next 74 | case node 75 | when AST::Require 76 | self.require(node) 77 | else 78 | self.register(node) 79 | end 80 | end 81 | end 82 | 83 | true 84 | rescue File::NotFoundError 85 | false 86 | end 87 | 88 | private def search_require_path(name : String) : String? 89 | REQUIRE_PATH.each do |require_path| 90 | path = File.join(require_path, name) 91 | return path if File.exists?(path) 92 | end 93 | end 94 | 95 | def register(node : AST::ConstantDefinition) : Nil 96 | if previous = @constants[node.name]? 97 | raise ConflictError.new("can't redefine constant #{node.name}", previous.location, node.location) 98 | end 99 | @constants[node.name] = node 100 | end 101 | 102 | def register(node : AST::Prototype) : Nil 103 | if previous = @externs[node.name]? 104 | return if node.matches?(previous) 105 | raise ConflictError.new("prototype #{node} doesn't match previous definition", previous.location, node.location) 106 | end 107 | @externs[node.name] = node 108 | end 109 | 110 | # Merely replaces the previous function definition, without validating the 111 | # new definition against the previous definition (number of args, arg types 112 | # and return type). 113 | # 114 | # TODO: function overloads + overwrite previous definition if it matches the 115 | # new definition. 116 | def register(node : AST::Function) : Nil 117 | if previous = @functions[node.name]? 118 | unless node.matches?(previous) 119 | raise ConflictError.new("function overloads aren't supported (yet)", previous.location, node.location) 120 | end 121 | end 122 | 123 | @functions[node.name] = node 124 | 125 | #@functions.each_with_index do |previous, index| 126 | # if previous.name == node.name && node.matches?(previous) 127 | # @functions[index] = node 128 | # return 129 | # end 130 | #end 131 | #@functions << node 132 | end 133 | 134 | def register(node : AST::Struct) : Nil 135 | if st = @structs[node.name]? 136 | raise "FATAL: struct #{node.name} has already been registered" 137 | else 138 | @structs[node.name] = node 139 | end 140 | end 141 | 142 | # :nodoc: 143 | def register(node : AST::Node) : Nil 144 | # raise "Program#register(#{node.class.name}) should be unreachable" 145 | end 146 | 147 | # TODO: search struct constants first 148 | def resolve(node : AST::Constant) : AST::ConstantDefinition 149 | if const = @constants[node.name]? 150 | const #.lhs.as(AST::Constant) 151 | else 152 | raise SemanticError.new("undefined constant #{node.name}", node) 153 | end 154 | end 155 | 156 | def resolve(node : AST::Call) : AST::Function | AST::Prototype 157 | function = 158 | if receiver = node.receiver 159 | resolve_type(receiver).method(node) 160 | else 161 | @functions[node.callee]? 162 | end 163 | 164 | if function 165 | function 166 | elsif prototype = @externs[node.callee]? 167 | prototype 168 | else 169 | raise SemanticError.new("undefined method #{node}", node) 170 | end 171 | end 172 | 173 | def resolve(node : AST::Binary) : AST::Function 174 | if function = resolve_type(node.lhs).operator(node) 175 | function 176 | else 177 | raise SemanticError.new("undefined operator #{node.lhs.type}##{node.operator}(#{node.rhs.type})", node) 178 | end 179 | end 180 | 181 | def resolve?(node : AST::Binary) : AST::Function? 182 | resolve_type(node.lhs).operator(node) 183 | end 184 | 185 | def resolve(node : AST::Unary) : AST::Function 186 | if function = resolve_type(node.expression).operator(node) 187 | function 188 | else 189 | raise SemanticError.new("undefined operator #{node.expression.type}##{node.operator}", node) 190 | end 191 | end 192 | 193 | def resolve?(node : AST::Unary) : AST::Function? 194 | resolve_type(node.expression).operator(node) 195 | end 196 | 197 | def resolve_type(node : AST::Reference) : AST::Struct 198 | resolve_type?(node.pointee_type.name) || 199 | raise SemanticError.new("undefined struct #{node.type.name}", node.location) 200 | end 201 | 202 | def resolve_type(node : AST::Node) : AST::Struct 203 | resolve_type?(node.type.name) || 204 | raise SemanticError.new("undefined struct #{node.type.name}", node.location) 205 | end 206 | 207 | def resolve_type?(name : String) : AST::Struct? 208 | @structs[name]? 209 | end 210 | end 211 | end 212 | -------------------------------------------------------------------------------- /src/runic-ast.cr: -------------------------------------------------------------------------------- 1 | require "./cli" 2 | require "./config" 3 | require "./parser" 4 | require "./semantic" 5 | 6 | module Runic 7 | module Command 8 | struct AST 9 | @semantic : Bool 10 | @location : Bool 11 | 12 | def initialize(source : IO, @file : String, @semantic : Bool, @location : Bool, corelib : String?) 13 | @nested = 0 14 | lexer = Lexer.new(source, @file) 15 | @program = Program.new 16 | @parser = Parser.new(lexer, top_level_expressions: true) 17 | 18 | if @semantic && corelib 19 | @program.require(corelib) 20 | end 21 | end 22 | 23 | def run 24 | nodes = [] of Runic::AST::Node 25 | 26 | while node = @parser.next 27 | if @semantic && node.is_a?(Runic::AST::Require) 28 | @program.require(node) 29 | end 30 | @program.register(node) 31 | nodes << node 32 | end 33 | 34 | if @semantic 35 | Semantic.analyze(@program) 36 | end 37 | 38 | nodes.each do |node| 39 | to_h(node) 40 | end 41 | end 42 | 43 | def to_h(node : Runic::AST::Integer) 44 | print "- integer: #{node.sign}#{node.value}#{to_options(node)}" 45 | end 46 | 47 | def to_h(node : Runic::AST::Float) 48 | print "- float: #{node.sign}#{node.value}#{to_options(node)}" 49 | end 50 | 51 | def to_h(node : Runic::AST::StringLiteral) 52 | print "- string: #{node.value}#{to_options(node)}" 53 | end 54 | 55 | def to_h(node : Runic::AST::Boolean) 56 | print "- boolean: #{node.value}#{to_options(node)}" 57 | end 58 | 59 | def to_h(node : Runic::AST::Variable) 60 | print "- variable: #{node.name}#{to_options(node)}" 61 | end 62 | 63 | def to_h(node : Runic::AST::InstanceVariable) 64 | print "- ivar: #{node.name}#{to_options(node)}" 65 | end 66 | 67 | def to_h(node : Runic::AST::Constant) 68 | print "- constant: #{node.name}#{to_options(node)}" 69 | end 70 | 71 | def to_h(node : Runic::AST::Alloca) 72 | print "- alloca: #{to_options(node)}" 73 | end 74 | 75 | def to_h(node : Runic::AST::Reference) 76 | print "- reference:#{to_options(node)}" 77 | nested { to_h(node.pointee) } 78 | end 79 | 80 | def to_h(node : Runic::AST::Dereference) 81 | print "- dereference:#{to_options(node)}" 82 | nested { to_h(node.pointee) } 83 | end 84 | 85 | def to_h(node : Runic::AST::ConstantDefinition) 86 | print "- constant: #{node.name}#{to_options(node)}" 87 | print " value:" 88 | nested { to_h(node.value) } 89 | end 90 | 91 | def to_h(node : Runic::AST::Assignment) 92 | print "- assignment: #{node.operator}#{to_options(node)}" 93 | print " lhs:" 94 | nested { to_h(node.lhs) } 95 | print " rhs:" 96 | nested { to_h(node.rhs) } 97 | end 98 | 99 | def to_h(node : Runic::AST::Binary) 100 | print "- binary: #{node.operator}#{to_options(node)}" 101 | print " lhs:" 102 | nested { to_h(node.lhs) } 103 | print " rhs:" 104 | nested { to_h(node.rhs) } 105 | end 106 | 107 | def to_h(node : Runic::AST::Unary) 108 | print "- unary: #{node.operator}#{to_options(node)}" 109 | nested { to_h(node.expression) } 110 | end 111 | 112 | def to_h(node : Runic::AST::Prototype) 113 | args = node.args.map { |arg| "#{arg.name} : #{arg.type}" }.join(", ") 114 | print "- extern #{node.name}(#{args}) : #{node.type}#{to_options(node, type: false)}" 115 | end 116 | 117 | def to_h(node : Runic::AST::Function) 118 | args = node.args 119 | if node.args.first?.try(&.name) == "self" 120 | (args = args.dup).shift 121 | end 122 | args = args.map { |arg| "#{arg.name} : #{arg.type?}" } 123 | print "- def #{node.name}(#{args.join(", ")}) : #{node.type?}#{to_options(node, type: false)}", linefeed: node.attributes.empty? 124 | ::puts " [#{node.attributes.join(", ")}]" unless node.attributes.empty? 125 | 126 | unless node.attributes.includes?("primitive") 127 | print " body:" 128 | nested do 129 | node.body.each { |n| to_h(n) } 130 | end 131 | end 132 | end 133 | 134 | def to_h(node : Runic::AST::Call) 135 | print "- call #{node.callee}#{to_options(node)}" 136 | if receiver = node.receiver 137 | print " receiver:" 138 | nested do 139 | to_h(receiver) 140 | end 141 | end 142 | 143 | unless node.args.empty? 144 | print " args:" 145 | nested do 146 | node.args.each_with_index do |arg, index| 147 | next if node.receiver && index == 0 148 | to_h(arg) 149 | end 150 | end 151 | end 152 | 153 | unless node.kwargs.empty? 154 | print " kwargs:" 155 | nested do 156 | node.kwargs.each do |name, arg| 157 | print "#{name}: " 158 | to_h(arg) 159 | end 160 | end 161 | end 162 | end 163 | 164 | def to_h(node : Runic::AST::If) 165 | print "- if : #{node.type?}#{to_options(node, type: false)}" 166 | 167 | print " condition:" 168 | nested do 169 | to_h(node.condition) 170 | end 171 | 172 | print " body:" 173 | nested do 174 | node.body.each { |n| to_h(n) } 175 | end 176 | 177 | if alternative = node.alternative 178 | print " else:" 179 | nested do 180 | alternative.each { |n| to_h(n) } 181 | end 182 | end 183 | end 184 | 185 | def to_h(node : Runic::AST::Unless) 186 | print "- unless : #{node.type?}#{to_options(node, type: false)}" 187 | 188 | print " condition:" 189 | nested do 190 | to_h(node.condition) 191 | end 192 | 193 | print " body:" 194 | nested do 195 | node.body.each { |n| to_h(n) } 196 | end 197 | end 198 | 199 | def to_h(node : Runic::AST::While) 200 | print "- while : #{node.type?}#{to_options(node, type: false)}" 201 | 202 | print " condition:" 203 | nested do 204 | to_h(node.condition) 205 | end 206 | 207 | print " body:" 208 | nested do 209 | node.body.each { |n| to_h(n) } 210 | end 211 | end 212 | 213 | def to_h(node : Runic::AST::Until) 214 | print "- until : #{node.type?}#{to_options(node, type: false)}" 215 | 216 | print " condition:" 217 | nested do 218 | to_h(node.condition) 219 | end 220 | 221 | print " body:" 222 | nested do 223 | node.body.each { |n| to_h(n) } 224 | end 225 | end 226 | 227 | def to_h(node : Runic::AST::Case) 228 | print "- case : #{node.type?}#{to_options(node, type: false)}" 229 | 230 | print " value:" 231 | nested do 232 | to_h(node.value) 233 | end 234 | 235 | nested do 236 | node.cases.each { |n| to_h(n) } 237 | end 238 | 239 | if body = node.alternative 240 | print " else:" 241 | nested do 242 | body.each { |n| to_h(n) } 243 | end 244 | end 245 | end 246 | 247 | def to_h(node : Runic::AST::When) 248 | print "when : #{node.type?}#{to_options(node, type: false)}" 249 | 250 | print " conditions:" 251 | nested do 252 | node.conditions.each { |n| to_h(n) } 253 | end 254 | 255 | print " body:" 256 | nested do 257 | node.body.each { |n| to_h(n) } 258 | end 259 | end 260 | 261 | def to_h(node : Runic::AST::Body) 262 | node.expressions.each { |n| to_h(n) } 263 | end 264 | 265 | def to_h(node : Runic::AST::Module) 266 | print "- module #{node.name}" 267 | 268 | nested do 269 | node.modules.each { |n| to_h(n) } 270 | end 271 | 272 | nested do 273 | node.structs.each { |n| to_h(n) } 274 | end 275 | end 276 | 277 | def to_h(node : Runic::AST::Require) 278 | print "- require #{node.path.inspect}" 279 | end 280 | 281 | def to_h(node : Runic::AST::Struct) 282 | print "- struct #{node.name} [#{node.attributes.join(", ")}]" 283 | 284 | nested do 285 | node.variables.each { |n| to_h(n) } 286 | end 287 | 288 | nested do 289 | node.methods.each { |n| to_h(n) } 290 | end 291 | end 292 | 293 | def to_options(node : Runic::AST::Node, type = true) 294 | String.build do |str| 295 | if type && @semantic 296 | str << " (" 297 | (node.type? || "??").to_s(str) 298 | str << ')' 299 | end 300 | if @location 301 | str << " at " 302 | node.location.to_s(str) 303 | end 304 | end 305 | end 306 | 307 | def print(string, linefeed = true) 308 | @nested.times { ::print ' ' } 309 | if linefeed 310 | ::puts string 311 | else 312 | ::print string 313 | end 314 | end 315 | 316 | def nested 317 | @nested += 4 318 | yield 319 | ensure 320 | @nested -= 4 321 | end 322 | end 323 | end 324 | end 325 | 326 | filenames = [] of String 327 | semantic = false 328 | location = false 329 | corelib = Runic.corelib 330 | 331 | cli = Runic::CLI.new 332 | cli.parse do |arg| 333 | case arg 334 | when "--semantic" 335 | semantic = true 336 | when "--location" 337 | location = true 338 | when "--corelib" 339 | corelib = cli.argument_value("--corelib") 340 | when "--no-corelib" 341 | corelib = nil 342 | when "--version", "version" 343 | cli.report_version("runic-ast") 344 | when "--help" 345 | STDERR.puts "" 346 | exit 0 347 | else 348 | filenames << cli.filename 349 | end 350 | end 351 | 352 | case filenames.size 353 | when 0 354 | ast = Runic::Command::AST.new(STDIN, "", semantic, location, corelib) 355 | STDERR.puts "reading from stdin..." 356 | ast.run 357 | when 1 358 | filename = filenames.first 359 | 360 | if File.exists?(filename) 361 | File.open(filename, "r") do |io| 362 | Runic::Command::AST.new(io, filename, semantic, location, corelib).run 363 | rescue error : Runic::SyntaxError 364 | error.pretty_report(STDERR) 365 | exit 1 366 | rescue error : Runic::SemanticError 367 | error.pretty_report(STDERR) 368 | exit 1 369 | end 370 | else 371 | cli.fatal "no such file or directory '#{filename}'." 372 | end 373 | else 374 | cli.fatal "you may only specify one file to parse." 375 | end 376 | -------------------------------------------------------------------------------- /src/runic-compile.cr: -------------------------------------------------------------------------------- 1 | require "./cli" 2 | require "./compiler" 3 | require "./config" 4 | 5 | filenames = [] of String 6 | output = nil 7 | target_triple = nil 8 | cpu = "generic" 9 | features = "" 10 | opt_level = LibC::LLVMCodeGenOptLevel::CodeGenLevelDefault 11 | debug = Runic::DebugLevel::Default 12 | emit = "object" 13 | corelib = Runic.corelib 14 | 15 | cli = Runic::CLI.new 16 | cli.parse do |arg| 17 | case arg 18 | when "-o", "--output" 19 | output = cli.argument_value("--output") 20 | when .starts_with?("--target") 21 | target_triple = cli.argument_value("--target") 22 | when .starts_with?("--cpu") 23 | cpu = cli.argument_value("--cpu") 24 | when .starts_with?("--features") 25 | features = cli.argument_value("--features") 26 | when "-O0" 27 | opt_level = LibC::LLVMCodeGenOptLevel::CodeGenLevelNone 28 | when "-O1" 29 | opt_level = LibC::LLVMCodeGenOptLevel::CodeGenLevelLess 30 | when "-O", "-O2" 31 | opt_level = LibC::LLVMCodeGenOptLevel::CodeGenLevelDefault 32 | when "-O3" 33 | opt_level = LibC::LLVMCodeGenOptLevel::CodeGenLevelAggressive 34 | when "-g", "--debug" 35 | debug = Runic::DebugLevel::Full 36 | when "--no-debug" 37 | debug = Runic::DebugLevel::None 38 | when .starts_with?("--emit") 39 | emit = cli.argument_value("--emit") 40 | when .starts_with?("--corelib") 41 | corelib = cli.argument_value("--corelib") 42 | when .starts_with?("--no-corelib") 43 | corelib = nil 44 | when "--version", "version" 45 | cli.report_version("runic-compile") 46 | when "--help" 47 | Runic.open_manpage("compile") 48 | else 49 | filenames << cli.filename 50 | end 51 | end 52 | 53 | begin 54 | if filenames.empty? 55 | cli.fatal "no input file." 56 | end 57 | 58 | filename = filenames.first 59 | 60 | compiler = Runic::Compiler.new( 61 | target_triple, 62 | cpu: cpu, 63 | features: features, 64 | opt_level: opt_level, 65 | debug: debug 66 | ) 67 | 68 | if corelib 69 | corelib = "#{corelib}.runic" unless corelib.ends_with?(".runic") 70 | compiler.parse(corelib) 71 | end 72 | 73 | filenames.each do |filename| 74 | if File.exists?(filename) 75 | compiler.parse(filename) 76 | else 77 | cli.fatal "no such file or directory '#{filename}'." 78 | end 79 | end 80 | 81 | compiler.analyze 82 | compiler.codegen(filename) 83 | 84 | case emit 85 | when "object" 86 | output ||= File.basename(filename, File.extname(filename)) + ".o" 87 | compiler.emit_object(output) 88 | when "llvm" 89 | output ||= File.basename(filename, File.extname(filename)) + ".ll" 90 | compiler.emit_llvm(output) 91 | else 92 | abort "Unknown emit option '#{emit}'." 93 | end 94 | 95 | rescue error : Runic::SyntaxError 96 | error.pretty_report(STDERR) 97 | exit 1 98 | 99 | rescue error : Runic::SemanticError 100 | error.pretty_report(STDERR) 101 | exit 1 102 | 103 | # rescue error : Runic::CodegenError 104 | # error.pretty_report(STDERR) 105 | # exit 1 106 | end 107 | -------------------------------------------------------------------------------- /src/runic-documentation.cr: -------------------------------------------------------------------------------- 1 | require "./cli" 2 | require "./config" 3 | require "./documentation" 4 | require "./documentation/html_generator" 5 | require "./documentation/json_generator" 6 | require "./documentation/yaml_generator" 7 | 8 | output = "doc" 9 | sources = [] of String 10 | format = "html" 11 | 12 | cli = Runic::CLI.new 13 | cli.parse do |arg| 14 | case arg 15 | when "-o", .starts_with?("--output") 16 | output = cli.argument_value("--output") 17 | when "-f", .starts_with?("--format") 18 | format = cli.argument_value("--format") 19 | when "--version" 20 | cli.report_version("runic-documentation") 21 | when "--help" 22 | Runic.open_manpage("documentation") 23 | else 24 | sources << cli.filename 25 | end 26 | end 27 | 28 | if sources.empty? 29 | sources << "src" 30 | end 31 | 32 | case format 33 | when "html" 34 | generator_class = Runic::Documentation::HTMLGenerator 35 | when "yaml" 36 | generator_class = Runic::Documentation::YAMLGenerator 37 | when "json" 38 | generator_class = Runic::Documentation::JSONGenerator 39 | else 40 | cli.fatal "unsupported format '#{format}'" 41 | end 42 | 43 | begin 44 | rdoc = Runic::Documentation.new(sources) 45 | rdoc.generate(generator_class.new(output)) 46 | rescue error : Runic::SyntaxError 47 | error.pretty_report(STDERR) 48 | exit 1 49 | rescue error : Runic::SemanticError 50 | error.pretty_report(STDERR) 51 | exit 1 52 | end 53 | -------------------------------------------------------------------------------- /src/runic-interactive.cr: -------------------------------------------------------------------------------- 1 | require "./cli" 2 | require "./lexer" 3 | require "./parser" 4 | require "./semantic" 5 | require "./codegen" 6 | require "./config" 7 | 8 | module Runic 9 | module Command 10 | class Interactive 11 | def initialize(corelib : String?, @debug = false, optimize = true) 12 | LLVM.init_native 13 | LLVM.init_global_pass_registry if optimize 14 | 15 | @lexer = Lexer.new(STDIN, "stdin", interactive: true) 16 | @parser = Parser.new(@lexer, top_level_expressions: true, interactive: true) 17 | @program = Program.new 18 | @semantic = Semantic.new(@program) 19 | @generator = Codegen.new(@program, debug: DebugLevel::None, optimize: optimize) 20 | 21 | if corelib 22 | @program.require(corelib) 23 | @semantic.visit(@program) 24 | @program.each { |node| @generator.codegen(node) } 25 | end 26 | end 27 | 28 | def main_loop : Nil 29 | loop do 30 | print ">> " 31 | handle 32 | end 33 | end 34 | 35 | private def handle : Nil 36 | token = @parser.peek 37 | 38 | begin 39 | case token.type 40 | when :eof 41 | exit 0 42 | when :linefeed 43 | @parser.skip 44 | return 45 | when :keyword 46 | case token.value 47 | when "def" 48 | handle_definition 49 | when "extern" 50 | handle_extern 51 | when "require" 52 | handle_require 53 | else 54 | handle_top_level_expression 55 | end 56 | else 57 | handle_top_level_expression 58 | end 59 | rescue error : SyntaxError 60 | error.pretty_report(STDOUT, source: false) 61 | return 62 | rescue error : SemanticError 63 | error.pretty_report(STDOUT, source: false) 64 | return 65 | rescue ex 66 | @parser.skip 67 | puts "ERROR: #{ex.message}" 68 | ex.backtrace.each { |line| puts(line) } 69 | end 70 | 71 | # skip trailing linefeed 72 | if @parser.peek.type == :linefeed 73 | @parser.skip 74 | end 75 | end 76 | 77 | def handle_require : Nil 78 | if @program.require(@parser.next.as(AST::Require), Dir.current) 79 | @semantic.visit(@program) 80 | @program.each { |node| @generator.codegen(node) } 81 | puts "=> true" 82 | else 83 | puts "=> false" 84 | end 85 | end 86 | 87 | def handle_extern : Nil 88 | node = @parser.next.not_nil! 89 | @semantic.visit(node) 90 | debug @generator.codegen(node) 91 | end 92 | 93 | def handle_definition : Nil 94 | node = @parser.next.not_nil! 95 | @semantic.visit(node) 96 | debug @generator.codegen(node) 97 | end 98 | 99 | def handle_top_level_expression : Nil 100 | node = @parser.next.not_nil! 101 | @semantic.visit(node) 102 | 103 | func = @generator.codegen(wrap_expression(node)) 104 | debug func 105 | 106 | begin 107 | case node.type.name 108 | when "bool" 109 | result = @generator.execute(Bool, func) 110 | 111 | when "i8" 112 | result = @generator.execute(Int8, func) 113 | when "i16" 114 | result = @generator.execute(Int16, func) 115 | when "i32" 116 | result = @generator.execute(Int32, func) 117 | when "i64" 118 | result = @generator.execute(Int64, func) 119 | 120 | when "u8" 121 | result = @generator.execute(UInt8, func) 122 | when "u16" 123 | result = @generator.execute(UInt16, func) 124 | when "u32" 125 | result = @generator.execute(UInt32, func) 126 | when "u64" 127 | result = @generator.execute(UInt64, func) 128 | 129 | when "f32" 130 | result = @generator.execute(Float32, func) 131 | when "f64" 132 | result = @generator.execute(Float64, func) 133 | 134 | when "String" 135 | result = @generator.execute(String, func) 136 | 137 | else 138 | puts "WARNING: unsupported return type '#{node.type}' (yet)" 139 | return 140 | end 141 | print "=> " 142 | puts result.inspect 143 | ensure 144 | LibC.LLVMDeleteFunction(func) 145 | end 146 | end 147 | 148 | private def wrap_expression(node : AST::Node) 149 | prototype = AST::Prototype.new("__anon_expr", [] of AST::Argument, node.type, "", node.location) 150 | body = AST::Body.new([node] of AST::Node, node.location) 151 | AST::Function.new(prototype, [] of String, body, node.location) 152 | end 153 | 154 | private def debug(value) 155 | return unless @debug 156 | STDERR.puts @generator.emit_llvm(value) 157 | end 158 | end 159 | end 160 | end 161 | 162 | debug = false 163 | optimize = true 164 | corelib = Runic.corelib 165 | 166 | cli = Runic::CLI.new 167 | cli.parse do |arg| 168 | case arg 169 | when "--corelib" 170 | corelib = cli.argument_value("--corelib") 171 | when "--no-corelib" 172 | corelib = nil 173 | when "--debug" 174 | debug = true 175 | when "--no-optimize" 176 | optimize = false 177 | when "--version" 178 | cli.report_version("runic-interactive") 179 | when "--help" 180 | Runic.open_manpage("interactive") 181 | else 182 | cli.unknown_option! 183 | end 184 | end 185 | 186 | Runic::Command::Interactive.new(corelib, debug, optimize).main_loop 187 | -------------------------------------------------------------------------------- /src/runic-lex.cr: -------------------------------------------------------------------------------- 1 | require "./cli" 2 | require "./lexer" 3 | 4 | module Runic 5 | module Command 6 | struct Lex 7 | def initialize(io, filename) 8 | @lexer = Runic::Lexer.new(io, filename) 9 | end 10 | 11 | def run 12 | loop do 13 | token = @lexer.next 14 | break if token.type == :eof 15 | p token 16 | end 17 | end 18 | end 19 | end 20 | end 21 | 22 | filenames = [] of String 23 | 24 | cli = Runic::CLI.new 25 | cli.parse do |arg| 26 | case arg 27 | when "--version", "version" 28 | cli.report_version("runic-lex") 29 | when "--help", "help" 30 | STDERR.puts "" 31 | exit 0 32 | else 33 | filenames << cli.filename 34 | end 35 | end 36 | 37 | case filenames.size 38 | when 0 39 | STDERR.puts "reading from stdin..." 40 | Runic::Command::Lex.new(STDIN, "").run 41 | when 1 42 | filename = filenames.first 43 | 44 | if File.exists?(filename) 45 | File.open(filename, "r") do |io| 46 | Runic::Command::Lex.new(io, filename).run 47 | end 48 | else 49 | cli.fatal "no such file or directory '#{filename}'" 50 | end 51 | else 52 | cli.fatal "you may only specify one file to parse." 53 | end 54 | -------------------------------------------------------------------------------- /src/runic.cr: -------------------------------------------------------------------------------- 1 | require "./cli" 2 | require "./config" 3 | 4 | module Runic 5 | def self.process_options(args) 6 | if args.empty? 7 | print_help_message 8 | exit 0 9 | end 10 | 11 | cli = Runic::CLI.new 12 | 13 | cli.parse do |arg| 14 | case arg 15 | when "--version", "version" 16 | cli.report_version("runic") 17 | exit 0 18 | when "--help" 19 | print_help_message 20 | exit 0 21 | when "help" 22 | if command = cli.consume? 23 | open_manpage(aliased(command)) 24 | else 25 | print_help_message 26 | exit 0 27 | end 28 | else 29 | if arg.starts_with?('-') 30 | cli.unknown_option! 31 | else 32 | return {aliased(arg), cli.remaining_arguments} 33 | end 34 | end 35 | end 36 | 37 | raise "unreachable" 38 | end 39 | 40 | private def self.aliased(command) 41 | case command 42 | when "c" then "compile" 43 | when "i" then "interactive" 44 | when "doc" then "documentation" 45 | else command 46 | end 47 | end 48 | 49 | def self.print_help_message 50 | STDERR.puts <<-EOF 51 | usage : runic [--version] [--help] 52 | 53 | Some available commands are: 54 | c[ompile] Compiles runic source into .o object files (or .ll LLVM IR) 55 | i[nteractive] Starts an interactive session 56 | doc[umentation] Generates documentation 57 | 58 | You may type 'runic help ' to read about a specific command. 59 | EOF 60 | end 61 | end 62 | 63 | command, args = Runic.process_options(ARGV) 64 | executable = File.join(Runic.libexec, "runic-#{command}") 65 | 66 | if File.exists?(executable) 67 | Process.exec(executable, args, env: { 68 | "RUNIC_ROOT" => Runic.root, 69 | "PATH" => ":#{ENV["PATH"]}", 70 | }) 71 | else 72 | abort "runic : '#{command}' is not a runic command. See 'runic help'." 73 | end 74 | -------------------------------------------------------------------------------- /src/scope.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | class Scope(T) 3 | class Values(T) 4 | getter parent : Values(T)? 5 | 6 | def initialize(@name : Symbol, @parent) 7 | end 8 | 9 | def []=(name : String, value : T) 10 | map[name] = value 11 | end 12 | 13 | def []?(name : String) 14 | map[name]? 15 | end 16 | 17 | private def map 18 | @map ||= {} of String => T 19 | end 20 | end 21 | 22 | def initialize 23 | @current = Values(T).new(:global, nil) 24 | end 25 | 26 | def push(name : Symbol) 27 | @current = Values(T).new(name, @current) 28 | begin 29 | yield 30 | ensure 31 | @current = @current.parent.not_nil! 32 | end 33 | end 34 | 35 | def fetch(name : String) : T 36 | @current[name] ||= yield 37 | end 38 | 39 | def get(name : String) : T? 40 | values = @current 41 | 42 | while values 43 | if value = values[name]? 44 | return value 45 | end 46 | values = values.parent 47 | end 48 | end 49 | 50 | def set(name : String, value : T) : T 51 | @current[name] = value 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/semantic.cr: -------------------------------------------------------------------------------- 1 | require "./ast" 2 | require "./program" 3 | require "./semantic/*" 4 | 5 | module Runic 6 | class Semantic 7 | DEFAULT_VISITORS = [ 8 | SugarExpanderVisitor, 9 | NamespaceVisitor, 10 | TypeVisitor, 11 | ] 12 | 13 | def self.analyze(program : Program, visitors = DEFAULT_VISITORS) : Semantic 14 | new(program, DEFAULT_VISITORS).tap do |semantic| 15 | semantic.visit(program) 16 | end 17 | end 18 | 19 | @visitors : Array(Visitor) 20 | 21 | def initialize(program : Program, visitors = DEFAULT_VISITORS) 22 | @visitors = visitors.map { |klass| klass.new(program).as(Visitor) } 23 | end 24 | 25 | # Visits all nodes from a program, one visitor at a time, each visitor 26 | # walking as much of the AST as needed. 27 | def visit(program : Program) 28 | @visitors.each do |visitor| 29 | program.each { |node| visitor.visit(node) } 30 | end 31 | end 32 | 33 | # Visits the node with each visitor. Should be used for interactive modes, 34 | # where expressions are parsed and visited immediately. 35 | def visit(node : AST::Node) : Nil 36 | @visitors.each(&.visit(node)) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /src/semantic/initialized_instance_variables.cr: -------------------------------------------------------------------------------- 1 | require "../ast" 2 | #require "../errors" 3 | 4 | module Runic 5 | class Semantic 6 | class InitializedInstanceVariables 7 | @struct : AST::Struct? 8 | @initializer : AST::Function? 9 | 10 | def initialize 11 | @tree = Array(Array(Set(AST::InstanceVariable))).new 12 | end 13 | 14 | # Returns the instance variable definition of the visited struct. Raises a 15 | # SemanticError exception if the instance variable is undefined or 16 | # uninitialized. 17 | def [](node : AST::InstanceVariable) : AST::InstanceVariable 18 | if ivar = @struct.as(AST::Struct).variables.find { |v| v.name == node.name } 19 | ivar 20 | else 21 | raise SemanticError.new("undefined instance variable '@#{node.name}'", node.location) 22 | end 23 | end 24 | 25 | def initialized?(ivar : AST::InstanceVariable) : Bool 26 | unless @initializer 27 | # not an initialize method? ivars are always initialized 28 | return true 29 | end 30 | 31 | # inside initialize methods, ivars may only be initialized in the 32 | # current branch or a parent branch: 33 | @tree.reverse_each do |branches| 34 | return true if branches.last.includes?(ivar) 35 | end 36 | 37 | false 38 | end 39 | 40 | def visit(node : AST::Struct) : Nil 41 | original, @struct = @struct, node 42 | begin 43 | yield 44 | ensure 45 | @struct = original 46 | end 47 | end 48 | 49 | def visit_initializer(@initializer) : Nil 50 | push 51 | add_branch 52 | yield 53 | verify 54 | ensure 55 | @initializer = nil 56 | @tree.clear 57 | end 58 | 59 | private def enabled? 60 | !@initializer.nil? 61 | end 62 | 63 | # Called before each control flow node. Creates a new scope to collect 64 | # assigned variables: 65 | def push 66 | return unless enabled? 67 | @tree << Array(Set(AST::InstanceVariable)).new 68 | end 69 | 70 | # Called before each branch of a control flow node. Creates a new list to 71 | # collect assigned variables for the current branch: 72 | def add_branch : Nil 73 | return unless enabled? 74 | @tree.last << Set(AST::InstanceVariable).new 75 | end 76 | 77 | # Called whenever an ivar is assigned. Collects the assignment into the 78 | # current branch (i.e. last branch of last scope). 79 | def assigned(ivar : AST::InstanceVariable) : Nil 80 | return unless enabled? 81 | @tree.last.last << ivar 82 | end 83 | 84 | # Called after all branches of a control flow node have been visited. 85 | # Collects ivars assigned in all branches and adds them to the last branch 86 | # of the previous scope: 87 | def collect : Nil 88 | return unless enabled? 89 | 90 | branches = @tree.pop 91 | branch = @tree.last.last 92 | 93 | ivars = branches.reduce { |a, e| a & e } 94 | branch.concat(ivars) 95 | end 96 | 97 | # Called after all branches of a control flow node (end) have been 98 | # visited. Collects ivars assigned in all branches and adds them to the 99 | # last branch of the previous scope: 100 | def reset : Nil 101 | return unless enabled? 102 | @tree.pop 103 | end 104 | 105 | private def verify 106 | initialized_ivars = @tree.first.last 107 | st = @struct.as(AST::Struct) 108 | initializer = @initializer.as(AST::Function) 109 | 110 | st.variables.each do |ivar| 111 | next if initialized_ivars.includes?(ivar) 112 | raise SemanticError.new("instance variable '@#{ivar.name}' of struct #{st.name} isn't always initialized", initializer.location) 113 | end 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /src/semantic/namespace_visitor.cr: -------------------------------------------------------------------------------- 1 | require "./visitor" 2 | 3 | module Runic 4 | class Semantic 5 | class NamespaceVisitor < Visitor 6 | # Expands name of sub-modules and structs to have an absolute path; 7 | # recursively visits modules; registers sub-structs. 8 | def visit(node : AST::Module) : Nil 9 | node.modules.each do |mod| 10 | mod.name = "#{node.name}::#{mod.name}" 11 | visit(mod) 12 | end 13 | 14 | node.structs.each do |st| 15 | st.name = "#{node.name}::#{st.name}" 16 | visit(st) 17 | @program.register(st) 18 | end 19 | end 20 | 21 | # Expands name of struct methods to have an absolute path; injects `self` 22 | # variable as first method argument. 23 | def visit(node : AST::Struct) : Nil 24 | node.methods.each do |fn| 25 | fn.prototype.name = "#{node.name}::#{fn.prototype.name}" 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /src/semantic/sugar_expander_visitor.cr: -------------------------------------------------------------------------------- 1 | require "./visitor" 2 | 3 | module Runic 4 | class Semantic 5 | class SugarExpanderVisitor < Visitor 6 | # Expands assignment operators such as `a += 1` to `a = a + 1`. 7 | # 8 | # FIXME: only valid because only variables are assignable, but later more 9 | # complex expressions may be assigned a value, so the expansion 10 | # should save the expression to a temporary variable (unless it's a 11 | # simple case). 12 | def visit(node : AST::Assignment) : Nil 13 | super 14 | 15 | unless node.operator == "=" 16 | operator = node.operator.chomp('=') 17 | node.operator = "=" 18 | node.rhs = AST::Binary.new(operator, node.lhs.dup, node.rhs, node.location) 19 | end 20 | end 21 | 22 | # Recursively expands the name of nested modules and structs to include 23 | # the module name. 24 | def visit(node : AST::Module) : Nil 25 | node.modules.each do |n| 26 | n.name = "#{node.name}::#{n.name}" 27 | visit(n) 28 | end 29 | 30 | node.structs.each do |n| 31 | n.name = "#{node.name}::#{n.name}" 32 | visit(n) 33 | end 34 | end 35 | 36 | # Injects *self* as first argument of struct methods. 37 | def visit(node : AST::Struct) : Nil 38 | self_type = 39 | if node.attribute?("primitive") 40 | # self is passed by value for primitive types (bool, int, float) 41 | Type.new(node.name) 42 | else 43 | # self is passed by reference for other structs: 44 | Type.new("#{node.name}*") 45 | end 46 | 47 | node.methods.each do |n| 48 | n.args.unshift(AST::Argument.new("self", self_type, nil, n.location)) 49 | visit(n) 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/semantic/visitor.cr: -------------------------------------------------------------------------------- 1 | require "../semantic" 2 | 3 | module Runic 4 | class Semantic 5 | abstract class Visitor 6 | def initialize(@program : Program) 7 | end 8 | 9 | def visit(node : AST::Literal) : Nil 10 | end 11 | 12 | def visit(node : AST::Constant) : Nil 13 | end 14 | 15 | def visit(node : AST::ConstantDefinition) : Nil 16 | visit(node.value) 17 | end 18 | 19 | def visit(node : AST::Alloca) : Nil 20 | end 21 | 22 | def visit(node : AST::Variable) : Nil 23 | end 24 | 25 | def visit(node : AST::InstanceVariable) : Nil 26 | end 27 | 28 | def visit(node : AST::Reference) : Nil 29 | visit(node.pointee) 30 | end 31 | 32 | def visit(node : AST::Dereference) : Nil 33 | visit(node.pointee) 34 | end 35 | 36 | def visit(node : AST::Argument) : Nil 37 | if n = node.default 38 | visit(n) 39 | end 40 | end 41 | 42 | def visit(node : AST::Assignment) : Nil 43 | visit(node.lhs) 44 | visit(node.rhs) 45 | end 46 | 47 | def visit(node : AST::Binary) : Nil 48 | visit(node.lhs) 49 | visit(node.rhs) 50 | end 51 | 52 | def visit(node : AST::Unary) : Nil 53 | visit(node.expression) 54 | end 55 | 56 | def visit(node : AST::Call) : Nil 57 | if n = node.receiver 58 | visit(n) 59 | end 60 | 61 | visit(node.args) 62 | node.kwargs.each_value { |n| visit(n) } 63 | end 64 | 65 | def visit(node : AST::If) : Nil 66 | visit(node.condition) 67 | visit(node.body) 68 | 69 | if n = node.alternative 70 | visit(n) 71 | end 72 | end 73 | 74 | def visit(node : AST::Unless) : Nil 75 | visit(node.condition) 76 | visit(node.body) 77 | end 78 | 79 | def visit(node : AST::While) : Nil 80 | visit(node.condition) 81 | visit(node.body) 82 | end 83 | 84 | def visit(node : AST::Until) : Nil 85 | visit(node.condition) 86 | visit(node.body) 87 | end 88 | 89 | def visit(node : AST::Case) : Nil 90 | visit(node.cases) 91 | 92 | if n = node.alternative 93 | visit(n) 94 | end 95 | end 96 | 97 | def visit(node : AST::When) : Nil 98 | visit(node.conditions) 99 | visit(node.body) 100 | end 101 | 102 | def visit(node : AST::Body) : Nil 103 | visit(node.expressions) 104 | end 105 | 106 | def visit(node : AST::Prototype) : Nil 107 | visit(node.args) 108 | end 109 | 110 | def visit(node : AST::Function) : Nil 111 | visit(node.args) 112 | visit(node.body) 113 | end 114 | 115 | def visit(node : AST::Module) : Nil 116 | visit(node.modules) 117 | visit(node.structs) 118 | end 119 | 120 | def visit(node : AST::Require) : Nil 121 | end 122 | 123 | def visit(node : AST::Struct) : Nil 124 | #visit(node.variables) 125 | visit(node.methods) 126 | end 127 | 128 | def visit(nodes : Array(AST::Node)) : Nil 129 | nodes.each { |n| visit(n) } 130 | end 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /src/target.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | struct Target 3 | getter triple : String 4 | getter architecture : String 5 | getter system : String 6 | getter version : String 7 | getter environment : String 8 | 9 | def initialize(triple : String, @cpu = "", @features = "") 10 | @triple = triple.downcase 11 | 12 | parts = @triple.split('-') 13 | @architecture = parts[0] 14 | 15 | case parts.size 16 | when 4 17 | @system = parts[2] 18 | @version = "" 19 | @environment = parts[3] 20 | when 3 21 | # NOTE: consider a whitelist (linux, windows, ...) 22 | if parts[1] == "unknown" || parts[1] == "none" || parts[1] == "apple" 23 | @system, @version = parse_system(parts[2]) 24 | @environment = "" 25 | else 26 | @system = parts[1] 27 | @version = "" 28 | @environment = parts[2] 29 | end 30 | when 2 31 | @system, @version = parse_system(parts[1]) 32 | @environment = "" 33 | else 34 | raise "unsupported target triple: #{@triple}" 35 | end 36 | end 37 | 38 | private def parse_system(string : String) 39 | index = 0 40 | string.each_char do |char| 41 | break unless ('a'..'z').includes?(char) 42 | index += 1 43 | end 44 | 45 | system = string[0...index] 46 | system = "darwin" if system == "macosx" 47 | 48 | {system, string[index..-1]} 49 | end 50 | 51 | def features 52 | # Enables conservative FPU for hard-float capable ARM targets (unless 53 | # CPU or a FPU is already defined: 54 | if architecture.starts_with?("arm") && environment.ends_with?("eabihf") && @cpu.empty? && !@features.includes?("fp") 55 | "+vfp2#{@features}" 56 | else 57 | @features 58 | end 59 | end 60 | 61 | def to_flags 62 | flags = Set(String).new 63 | 64 | case architecture 65 | when "amd64", "x86_64" 66 | flags << "X86_64" 67 | when "arm64", "aarch64" 68 | flags << "AARCH64" 69 | when "i386", "i486", "i586", "i686" 70 | flags << "X86" 71 | else 72 | flags << architecture.upcase 73 | 74 | if architecture.starts_with?("arm") 75 | flags << "ARM" 76 | flags << "ARMHF" if environment.ends_with?("eabihf") 77 | end 78 | end 79 | 80 | flags << system.upcase 81 | case system 82 | when "darwin", "freebsd", "linux", "openbsd" 83 | flags << "UNIX" 84 | when "windows" 85 | flags << "WIN32" if environment == "msvc" || environment == "gnu" 86 | when "ios" 87 | flags << "DARWIN" 88 | else 89 | # shut up, crystal 90 | end 91 | 92 | unless environment.empty? 93 | flags << environment.upcase 94 | 95 | case environment 96 | when .starts_with?("gnu") 97 | flags << "GNU" 98 | when .starts_with?("musl") 99 | flags << "MUSL" 100 | when .starts_with?("android") 101 | flags << "ANDROID" 102 | when "cygnus" 103 | flags << "UNIX" 104 | else 105 | # shut up, crystal 106 | end 107 | end 108 | 109 | flags 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /src/token.cr: -------------------------------------------------------------------------------- 1 | require "./definitions" 2 | 3 | module Runic 4 | struct Token 5 | getter type : Symbol 6 | getter value : String 7 | getter location : Location 8 | getter literal_type : String? 9 | 10 | def initialize(@type, @value, @location, @literal_type = nil) 11 | end 12 | 13 | {% for type in %i( 14 | attribute 15 | call 16 | comment 17 | eof 18 | float 19 | identifier 20 | integer 21 | ivar 22 | keyword 23 | linefeed 24 | operator 25 | semicolon 26 | ) %} 27 | def {{type.id}}? 28 | @type == {{type}} 29 | end 30 | {% end %} 31 | 32 | def assignment? 33 | operator? && OPERATORS::ASSIGNMENT.includes?(value) 34 | end 35 | 36 | def to_s(io) 37 | case type 38 | when :attribute 39 | io << "#[" 40 | value.to_s(io) 41 | io << ']' 42 | when :call 43 | super 44 | when :comment 45 | io << "comment" 46 | when :eof 47 | io << "EOF" 48 | when :float, :integer, :keyword 49 | value.inspect(io) 50 | when :identifier 51 | value.to_s(io) 52 | when :ivar 53 | io << '@' 54 | value.to_s(io) 55 | when :linefeed 56 | io << "LF" 57 | when :operator 58 | io << value 59 | when :semicolon 60 | io << value 61 | else 62 | super 63 | end 64 | end 65 | 66 | def inspect(io) 67 | value.to_s(io) 68 | io << ' ' 69 | type.inspect(io) 70 | io << ' ' 71 | location.to_s(io) 72 | 73 | if literal_type 74 | io << " (" 75 | literal_type.to_s(io) 76 | io << ')' 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /src/type.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | struct Type 3 | include Comparable(Type) 4 | 5 | getter name : String 6 | delegate :inspect, to: name 7 | 8 | # :nodoc: 9 | def self.new(type : Type) : Type 10 | type 11 | end 12 | 13 | def initialize(@name : String) 14 | end 15 | 16 | def bits 17 | if float? || integer? 18 | name[1..-1].to_i 19 | elsif bool? 20 | 1 21 | else 22 | raise "unknown type size: '#{name}'" 23 | end 24 | end 25 | 26 | def <=>(other : Type) 27 | bits <=> other.bits 28 | end 29 | 30 | def ==(other : Type) 31 | name == other.name 32 | end 33 | 34 | def ==(other : String) 35 | name == other 36 | end 37 | 38 | def primitive? 39 | void? || bool? || number? 40 | end 41 | 42 | def void? 43 | name == "void" 44 | end 45 | 46 | def bool? 47 | name == "bool" 48 | end 49 | 50 | def integer? 51 | INTRINSICS::INTEGERS.includes?(name) 52 | end 53 | 54 | def unsigned? 55 | INTRINSICS::UNSIGNED.includes?(name) 56 | end 57 | 58 | def signed? 59 | INTRINSICS::SIGNED.includes?(name) 60 | end 61 | 62 | def float? 63 | INTRINSICS::FLOATS.includes?(name) 64 | end 65 | 66 | def number? 67 | integer? || float? 68 | end 69 | 70 | def pointer? 71 | name.ends_with?('*') 72 | end 73 | 74 | def pointee_type 75 | raise "BUG: #{self} isn't a pointer type!" unless pointer? 76 | Type.new(name[0..-2]) 77 | end 78 | 79 | def aggregate? 80 | !primitive? && !pointer? 81 | end 82 | 83 | def to_s(io : IO) 84 | io << name 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /src/version.cr: -------------------------------------------------------------------------------- 1 | module Runic 2 | def self.version_string 3 | {{ `cat #{__DIR__}/../VERSION`.stringify.strip }} 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/codegen/control_flow_test.cr: -------------------------------------------------------------------------------- 1 | require "./test_helper" 2 | 3 | class Runic::Codegen::ControlFlowTest < Runic::CodegenTest 4 | def test_if_expression 5 | assert_equal 20, execute <<-RUNIC 6 | def foo(a : int) 7 | if a > 10 8 | a - 2 9 | else 10 | a + 1 11 | end 12 | end 13 | foo(1) + foo(20) 14 | RUNIC 15 | 16 | assert_equal 19, execute <<-RUNIC 17 | def bar(a : int) 18 | if a > 10 19 | a = a - 2 20 | end 21 | a 22 | end 23 | bar(1) + bar(20) 24 | RUNIC 25 | end 26 | 27 | def test_unless_expression 28 | assert_equal 22, execute <<-RUNIC 29 | def foo(a : int) 30 | unless a > 10 31 | a = a + 1 32 | end 33 | a 34 | end 35 | foo(1) + foo(20) 36 | RUNIC 37 | end 38 | 39 | def test_while_expression 40 | assert_equal 10, execute <<-RUNIC 41 | def foo(a : int) 42 | while a < 10 43 | a = a + 1 44 | end 45 | a 46 | end 47 | foo(1) 48 | RUNIC 49 | end 50 | 51 | def test_until_expression 52 | assert_equal 0, execute <<-RUNIC 53 | def foo(a : int) 54 | until a == 0 55 | a = a - 1 56 | end 57 | a 58 | end 59 | foo(10) 60 | RUNIC 61 | end 62 | 63 | def test_case_expression 64 | assert_equal 25, execute <<-RUNIC 65 | def foo(a : int) 66 | case a 67 | when 1, 2 68 | a + 1 69 | when 3 70 | a + 2 71 | when 4, 5, 6 72 | a + 3 73 | else 74 | a 75 | end 76 | end 77 | foo(10) + foo(1) + foo(3) + foo(5) 78 | RUNIC 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/codegen/functions_test.cr: -------------------------------------------------------------------------------- 1 | require "./test_helper" 2 | 3 | class Runic::Codegen::FunctionsTest < Runic::CodegenTest 4 | def test_function_definition 5 | source = <<-RUNIC 6 | def runic_add(a : int, b : int) 7 | a + b 8 | end 9 | runic_add(1, 2) 10 | RUNIC 11 | assert_equal 3, execute(source) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/codegen/literals_test.cr: -------------------------------------------------------------------------------- 1 | require "./test_helper" 2 | 3 | class Runic::Codegen::LiteralsTest < Runic::CodegenTest 4 | def test_booleans 5 | assert_equal true, execute("true") 6 | assert_equal false, execute("false") 7 | end 8 | 9 | def test_integers 10 | assert_equal 1, execute("1") 11 | assert_equal 120918209182128, execute("120918209182128") 12 | assert_equal Int32::MIN, execute(Int32::MIN.to_s) 13 | assert_equal UInt64::MAX, execute("#{UInt64::MAX}_u64") 14 | end 15 | 16 | def test_floats 17 | assert_equal 1.0, execute("1.0") 18 | assert_equal -2.0, execute("-2.0") 19 | assert_equal -12345.6789012345, execute("-12345.6789012345") 20 | assert_equal 12345.6789012345, execute("12345.6789012345") 21 | end 22 | 23 | def test_strings 24 | assert_equal "lorem ipsum", execute(%["lorem ipsum"]) 25 | 26 | ptr = execute(%[a = "hello world"; a.to_unsafe]) 27 | flunk unless ptr.is_a?(UInt8*) 28 | assert_equal "hello world".bytes.to_unsafe.to_slice(11), ptr.to_slice(11) 29 | end 30 | 31 | def test_constant_definition 32 | source = <<-RUNIC 33 | INCREMENT = 1 34 | 35 | def increment(a : int) 36 | a + INCREMENT 37 | end 38 | 39 | increment(10) 40 | RUNIC 41 | assert_equal 11, execute(source) 42 | end 43 | 44 | def test_variables 45 | assert_equal 123.5, execute("a = 123.5; a") 46 | assert_equal 9801391209182, execute("foo = 9801391209182; foo") 47 | end 48 | 49 | def test_shadows_variable_for_scope_duration 50 | # mutates 'a' (i32) with no local shadow (of type) 51 | assert_equal 10, execute <<-RUNIC 52 | a = 2 53 | if a < 10; a = 10; end 54 | a 55 | RUNIC 56 | 57 | # shadows 'a' (i32) as f64 for the duration of the then block, 58 | # then restores the original 'a' (i32) afterward 59 | assert_equal 2, execute <<-RUNIC 60 | a = 2 61 | if a < 10; a = 10.0; end 62 | a 63 | RUNIC 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/codegen/structures_test.cr: -------------------------------------------------------------------------------- 1 | require "./test_helper" 2 | 3 | class Runic::Codegen::StructuresTest < Runic::CodegenTest 4 | def test_stack_constructor 5 | assert_equal 5, execute(<<-RUNIC) 6 | struct Point 7 | @x : i32 8 | @y : i32 9 | 10 | def initialize(x : i32, y : i32) 11 | @x = x 12 | @y = y 13 | end 14 | 15 | def x; @x; end 16 | def y; @y; end 17 | end 18 | 19 | a = Point(2, 1) 20 | b = Point(1, 3) 21 | a.x + b.y 22 | RUNIC 23 | end 24 | 25 | def test_byval 26 | assert_equal 1, execute(<<-RUNIC) 27 | struct Foo 28 | @value : i32 29 | 30 | def initialize(value : i32) 31 | @value = value 32 | end 33 | 34 | def value 35 | @value 36 | end 37 | 38 | def set(value : i32) 39 | @value = value 40 | end 41 | end 42 | 43 | def get(foo : Foo) : i32 44 | foo.value 45 | end 46 | 47 | def set(foo : Foo, value : i32) : i32 48 | foo.set(value) 49 | end 50 | 51 | foo = Foo(1) # init foo value to 1 52 | set(foo, 2) # passes byval, so mutates a copy of foo 53 | get(foo) # returns foo's value (1) 54 | RUNIC 55 | end 56 | 57 | def test_sret 58 | assert_equal 21, execute(<<-RUNIC) 59 | struct Vec2 60 | @x : i64 61 | @y : i64 62 | @z : i64 63 | 64 | def initialize(x : i64, y : i64, z : i64) 65 | @x = x 66 | @y = y 67 | @z = z 68 | end 69 | 70 | def x; @x; end 71 | def y; @y; end 72 | def z; @z; end 73 | 74 | def add(other : Vec2) 75 | Vec2(@x + other.x, @y + other.y, @z + other.z) 76 | end 77 | end 78 | 79 | a = Vec2(1, 2, 3) 80 | b = Vec2(4, 5, 6) 81 | c = a.add(b) 82 | c.x + c.y + c.z 83 | RUNIC 84 | end 85 | 86 | def test_operator_methods 87 | assert_equal 21, execute(<<-RUNIC) 88 | struct Vec2 89 | @x : i64 90 | @y : i64 91 | @z : i64 92 | 93 | def initialize(x : i64, y : i64, z : i64) 94 | @x = x 95 | @y = y 96 | @z = z 97 | end 98 | 99 | def x; @x; end 100 | def y; @y; end 101 | def z; @z; end 102 | 103 | def +(other : Vec2) 104 | Vec2(@x + other.x, @y + other.y, @z + other.z) 105 | end 106 | end 107 | 108 | a = Vec2(1, 2, 3) 109 | b = Vec2(4, 5, 6) 110 | c = a + b 111 | c.x + c.y + c.z 112 | RUNIC 113 | end 114 | 115 | def test_setter_methods 116 | assert_equal 321, execute(<<-RUNIC) 117 | struct Bar 118 | @value : i32 119 | 120 | def initialize(value : i32) 121 | @value = value 122 | end 123 | 124 | def value 125 | @value 126 | end 127 | 128 | def value=(value : i32) 129 | @value = value 130 | end 131 | end 132 | 133 | bar = Bar(123) 134 | bar.value = 321 135 | bar.value 136 | RUNIC 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /test/codegen/test_helper.cr: -------------------------------------------------------------------------------- 1 | require "../test_helper" 2 | require "../../src/codegen" 3 | 4 | LLVM.init_native 5 | 6 | module Runic 7 | class CodegenTest < Minitest::Test 8 | def setup 9 | require_corelib 10 | end 11 | 12 | protected def execute(source : String) 13 | # function to wrap the expression(s) 14 | prototype = AST::Prototype.new("__anon_expr", [] of AST::Argument, nil, "", Location.new("")) 15 | body = AST::Body.new([] of AST::Node, Location.new("")) 16 | main = AST::Function.new(prototype, [] of String, body, Location.new("")) 17 | 18 | # parse + analysis + codegen of expression(s) 19 | parse_each(source) do |node| 20 | case node 21 | when AST::ConstantDefinition, AST::Function, AST::Struct 22 | program.register(node) 23 | else 24 | main.body << node 25 | next 26 | end 27 | 28 | semantic.visit(node) 29 | generator.codegen(node) 30 | end 31 | 32 | # analysis + codegen wrap function 33 | semantic.visit(main) 34 | func = generator.codegen(main) 35 | 36 | # JIT execution + return primitive result 37 | begin 38 | case main.type.name 39 | when "bool" then return generator.execute(Bool, func) 40 | when "i8" then return generator.execute(Int8, func) 41 | when "i16" then return generator.execute(Int16, func) 42 | when "i32" then return generator.execute(Int32, func) 43 | when "i64" then return generator.execute(Int64, func) 44 | when "i128" then return generator.execute(Int128, func) 45 | when "u8" then return generator.execute(UInt8, func) 46 | when "u16" then return generator.execute(UInt16, func) 47 | when "u32" then return generator.execute(UInt32, func) 48 | when "u64" then return generator.execute(UInt64, func) 49 | when "u128" then return generator.execute(UInt128, func) 50 | when "f32" then return generator.execute(Float32, func) 51 | when "f64" then return generator.execute(Float64, func) 52 | when "u8*" then return generator.execute(Pointer(UInt8), func) 53 | when "String" then return generator.execute(String, func) 54 | else raise "unsupported return type '#{main.type}' (yet)" 55 | end 56 | ensure 57 | # clear the wrap function from JIT execution 58 | LibC.LLVMDeleteFunction(func) 59 | end 60 | end 61 | 62 | private def semantic 63 | @semantic ||= Semantic.new(program) 64 | end 65 | 66 | private def generator 67 | @codegen ||= Codegen.new(program, debug: DebugLevel::None, optimize: true) 68 | end 69 | 70 | private def require_corelib 71 | super 72 | program.each { |node| generator.codegen(node) } 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/data_layout_test.cr: -------------------------------------------------------------------------------- 1 | require "./test_helper" 2 | require "../src/data_layout" 3 | 4 | module Runic 5 | class DataLayoutTest < Minitest::Test 6 | def test_x86_64_linux_gnu 7 | layout = DataLayout.parse("e-m:e-i64:64-f80:128-n8:16:32:64-S128") 8 | assert layout.little_endian? 9 | assert layout.mangling.elf? 10 | assert_equal DataLayout::PointerAlign.new(0, 8, 8, 8, 8), layout.pointer_align 11 | assert_equal DataLayout::Align.new(:integer, 8, 1, 1), layout.alignment(:integer, 8) 12 | assert_equal DataLayout::Align.new(:integer, 16, 2, 2), layout.alignment(:integer, 16) 13 | assert_equal DataLayout::Align.new(:integer, 32, 4, 4), layout.alignment(:integer, 32) 14 | assert_equal DataLayout::Align.new(:integer, 64, 8, 8), layout.alignment(:integer, 64) 15 | assert_equal DataLayout::Align.new(:float, 80, 16, 16), layout.alignment(:float, 80) 16 | assert_equal [8, 16, 32, 64], layout.native_integers 17 | assert_equal 16, layout.stack_natural_align 18 | end 19 | 20 | def test_x86_64_macosx_darwin 21 | layout = DataLayout.parse("e-m:o-i64:64-f80:128-n8:16:32:64-S128") 22 | assert layout.mangling.mach_o? 23 | end 24 | 25 | def test_x86_64_windows_win32 26 | layout = DataLayout.parse("e-m:w-i64:64-f80:128-n8:16:32:64-S128") 27 | assert layout.mangling.win_coff? 28 | end 29 | 30 | def test_i686_linux_gnu 31 | layout = DataLayout.parse("e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128") 32 | assert layout.mangling.elf? 33 | assert_equal DataLayout::PointerAlign.new(0, 4, 4, 4, 4), layout.pointer_align 34 | assert_equal DataLayout::Align.new(:integer, 8, 1, 1), layout.alignment(:integer, 8) 35 | assert_equal DataLayout::Align.new(:integer, 16, 2, 2), layout.alignment(:integer, 16) 36 | assert_equal DataLayout::Align.new(:integer, 32, 4, 4), layout.alignment(:integer, 32) 37 | assert_equal DataLayout::Align.new(:integer, 64, 4, 8), layout.alignment(:integer, 64) 38 | assert_equal DataLayout::Align.new(:float, 64, 4, 8), layout.alignment(:float, 64) 39 | assert_equal DataLayout::Align.new(:float, 80, 4, 4), layout.alignment(:float, 80) 40 | assert_equal [8, 16, 32], layout.native_integers 41 | assert_equal 16, layout.stack_natural_align 42 | end 43 | 44 | def test_i686_windows_win32 45 | layout = DataLayout.parse("e-m:x-p:32:32-i64:64-f80:32-n8:16:32-S32") 46 | assert layout.mangling.win_coffx86? 47 | assert_equal DataLayout::PointerAlign.new(0, 4, 4, 4, 4), layout.pointer_align 48 | assert_equal DataLayout::Align.new(:integer, 8, 1, 1), layout.alignment(:integer, 8) 49 | assert_equal DataLayout::Align.new(:integer, 16, 2, 2), layout.alignment(:integer, 16) 50 | assert_equal DataLayout::Align.new(:integer, 32, 4, 4), layout.alignment(:integer, 32) 51 | assert_equal DataLayout::Align.new(:integer, 64, 8, 8), layout.alignment(:integer, 64) 52 | assert_equal DataLayout::Align.new(:float, 64, 4, 8), layout.alignment(:float, 64) 53 | assert_equal DataLayout::Align.new(:float, 80, 4, 4), layout.alignment(:float, 80) 54 | assert_equal [8, 16, 32], layout.native_integers 55 | assert_equal 4, layout.stack_natural_align 56 | end 57 | 58 | def test_arm_linux_gnueabi 59 | layout = DataLayout.parse("e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64") 60 | assert_equal DataLayout::PointerAlign.new(0, 4, 4, 4, 4), layout.pointer_align 61 | assert_equal DataLayout::Align.new(:integer, 8, 1, 1), layout.alignment(:integer, 8) 62 | assert_equal DataLayout::Align.new(:integer, 16, 2, 2), layout.alignment(:integer, 16) 63 | assert_equal DataLayout::Align.new(:integer, 32, 4, 4), layout.alignment(:integer, 32) 64 | assert_equal DataLayout::Align.new(:integer, 64, 8, 8), layout.alignment(:integer, 64) 65 | assert_equal DataLayout::Align.new(:float, 64, 4, 8), layout.alignment(:float, 64) 66 | assert_equal DataLayout::Align.new(:vector, 128, 8, 16), layout.alignment(:vector, 128) 67 | assert_equal DataLayout::Align.new(:aggregate, 0, 0, 4), layout.alignment(:aggregate, 0) 68 | assert_equal [32], layout.native_integers 69 | assert_equal 8, layout.stack_natural_align 70 | end 71 | 72 | def test_aarch64_linux_gnu 73 | layout = DataLayout.parse("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128") 74 | assert_equal DataLayout::PointerAlign.new(0, 8, 8, 8, 8), layout.pointer_align 75 | assert_equal DataLayout::Align.new(:integer, 8, 1, 4), layout.alignment(:integer, 8) 76 | assert_equal DataLayout::Align.new(:integer, 16, 2, 4), layout.alignment(:integer, 16) 77 | assert_equal DataLayout::Align.new(:integer, 32, 4, 4), layout.alignment(:integer, 32) 78 | assert_equal DataLayout::Align.new(:integer, 64, 8, 8), layout.alignment(:integer, 64) 79 | assert_equal DataLayout::Align.new(:integer, 128, 16, 16), layout.alignment(:integer, 128) 80 | assert_equal DataLayout::Align.new(:float, 64, 4, 8), layout.alignment(:float, 64) 81 | assert_equal [32, 64], layout.native_integers 82 | assert_equal 16, layout.stack_natural_align 83 | end 84 | 85 | def test_mips_linux_gnu 86 | layout = DataLayout.parse("E-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64") 87 | assert layout.big_endian? 88 | assert layout.mangling.mips? 89 | assert_equal DataLayout::PointerAlign.new(0, 4, 4, 4, 4), layout.pointer_align 90 | assert_equal DataLayout::Align.new(:integer, 8, 1, 4), layout.alignment(:integer, 8) 91 | assert_equal DataLayout::Align.new(:integer, 16, 2, 4), layout.alignment(:integer, 16) 92 | assert_equal DataLayout::Align.new(:integer, 32, 4, 4), layout.alignment(:integer, 32) 93 | assert_equal DataLayout::Align.new(:integer, 64, 8, 8), layout.alignment(:integer, 64) 94 | assert_equal [32], layout.native_integers 95 | assert_equal 8, layout.stack_natural_align 96 | end 97 | 98 | def test_mipsel_linux_gnu 99 | layout = DataLayout.parse("e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64") 100 | assert layout.little_endian? 101 | assert layout.mangling.mips? 102 | end 103 | 104 | def test_mips64_linux_gnuabi64 105 | layout = DataLayout.parse("E-m:e-i8:8:32-i16:16:32-i64:64-n32:64-S128") 106 | assert layout.big_endian? 107 | assert layout.mangling.elf? 108 | assert_equal DataLayout::PointerAlign.new(0, 8, 8, 8, 8), layout.pointer_align 109 | assert_equal DataLayout::Align.new(:integer, 8, 1, 4), layout.alignment(:integer, 8) 110 | assert_equal DataLayout::Align.new(:integer, 16, 2, 4), layout.alignment(:integer, 16) 111 | assert_equal DataLayout::Align.new(:integer, 32, 4, 4), layout.alignment(:integer, 32) 112 | assert_equal DataLayout::Align.new(:integer, 64, 8, 8), layout.alignment(:integer, 64) 113 | assert_equal [32, 64], layout.native_integers 114 | assert_equal 16, layout.stack_natural_align 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /test/location_test.cr: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "../src/location" 3 | 4 | module Runic 5 | class LocationTest < Minitest::Test 6 | def test_new 7 | location = Location.new("/path/to/some/file.runic") 8 | assert_equal "/path/to/some/file.runic", location.file 9 | assert_equal 1, location.line 10 | assert_equal 1, location.column 11 | 12 | location.increment_column 13 | assert_equal 1, location.line 14 | assert_equal 2, location.column 15 | 16 | location.increment_column 17 | location.increment_column 18 | assert_equal 1, location.line 19 | assert_equal 4, location.column 20 | 21 | location.increment_line 22 | assert_equal 2, location.line 23 | assert_equal 1, location.column 24 | 25 | location.increment_line 26 | location.increment_line 27 | assert_equal 4, location.line 28 | assert_equal 1, location.column 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/mangler_test.cr: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "../src/mangler" 3 | 4 | module Runic 5 | class ManglerTest < Minitest::Test 6 | def test_mangle_global_functions 7 | assert_mangle "_Z5myFunv", "myFun" 8 | assert_mangle "_Z5myFunb", "myFun", ["bool"] 9 | assert_mangle "_Z5myFunasixn", "myFun", ["i8", "i16", "i32", "i64", "i128"] 10 | assert_mangle "_Z5myFunhtjyo", "myFun", ["u8", "u16", "u32", "u64", "u128"] 11 | assert_mangle "_Z5myFunfd", "myFun", ["f32", "f64"] 12 | assert_mangle "_Z5myFuni6MyType9OtherType", "myFun", ["i32", "MyType", "OtherType"] 13 | assert_mangle "_Z9incrementPix", "increment", ["i32*", "i64"] 14 | end 15 | 16 | def test_mangle_namespaced_functions 17 | assert_mangle "_ZN2NS5myFunEv", "NS::myFun" 18 | assert_mangle "_ZN2NS5myFunEfd", "NS::myFun", ["f32", "f64"] 19 | assert_mangle "_ZN2NS5myFunEN2NS6MyTypeE", "NS::myFun", ["NS::MyType"] 20 | assert_mangle "_ZN4Some6Nested3funEiN2NS6MyTypeEj", "Some::Nested::fun", ["i32", "NS::MyType", "u32"] 21 | end 22 | 23 | def test_mangle_operators 24 | assert_mangle "_ZN3i32plEii", "i32::+", ["i32", "i32"] 25 | assert_mangle "_ZN3i32geEii", "i32::>=", ["i32", "i32"] 26 | assert_mangle "_ZN4i128ltEni", "i128::<", ["i128", "i32"] 27 | #assert_mangle "_ZN3Sys4FilenwEv", "Sys::File::new" 28 | end 29 | 30 | def assert_mangle(expected, fn_name, fn_args = [] of String) 31 | location = Location.new("") 32 | args = fn_args.map { |type| AST::Argument.new("", Type.new(type), nil, location) } 33 | prototype = AST::Prototype.new(fn_name, args, "void", "", location) 34 | assert_equal expected, Mangler.mangle(prototype) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/program_test.cr: -------------------------------------------------------------------------------- 1 | require "./test_helper" 2 | 3 | module Runic 4 | class ProgramTest < Minitest::Test 5 | def test_register_constants 6 | node = register("FOO = 1").as(AST::ConstantDefinition) 7 | assert_same node, program.resolve(AST::Constant.new("FOO", location)) 8 | end 9 | 10 | private def register(source) 11 | parse_each(source, top_level_expressions: false) do |node| 12 | program.register(node) 13 | return node 14 | end 15 | end 16 | 17 | private def location 18 | Location.new("MEMORY") 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/semantic/namespace_visitor_test.cr: -------------------------------------------------------------------------------- 1 | require "../test_helper" 2 | 3 | module Runic 4 | class Semantic 5 | class NamespaceVisitorTest < Minitest::Test 6 | def test_expands_module_and_struct_and_method_names 7 | node = visit <<-RUNIC 8 | module App 9 | module Auth 10 | struct User 11 | def age; end 12 | def name; end 13 | end 14 | struct Group; end 15 | end 16 | end 17 | RUNIC 18 | 19 | app = node.as(AST::Module) 20 | assert_equal "App", app.name 21 | 22 | auth = app.modules.first 23 | assert_equal "App::Auth", auth.name 24 | 25 | assert_equal %w(App::Auth::User App::Auth::Group), auth.structs.map(&.name) 26 | assert_equal %w(App::Auth::User App::Auth::Group), program.structs.map(&.first) 27 | 28 | user = auth.structs.first 29 | assert_equal %w(App::Auth::User::age App::Auth::User::name), user.methods.map(&.name) 30 | end 31 | 32 | def test_expands_struct_method_names 33 | node = visit <<-RUNIC 34 | struct User 35 | def age; end 36 | def name; end 37 | end 38 | RUNIC 39 | 40 | methods = node.as(AST::Struct).methods 41 | assert_equal "User::age", methods[0].name 42 | assert_equal "User::name", methods[1].name 43 | end 44 | 45 | protected def visitors 46 | @visitor ||= [NamespaceVisitor.new(program)] 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/semantic/sugar_expander_visitor_test.cr: -------------------------------------------------------------------------------- 1 | require "../test_helper" 2 | 3 | module Runic 4 | class Semantic 5 | class SugarExpanderVisitorTest < Minitest::Test 6 | def test_expands_assignment_operators 7 | node = visit("a += 1").as(AST::Assignment) 8 | 9 | assert_equal "=", node.operator 10 | assert_equal "a", node.lhs.as(AST::Variable).name 11 | 12 | subnode = node.rhs.as(AST::Binary) 13 | assert_equal "+", subnode.operator 14 | assert_equal "a", subnode.lhs.as(AST::Variable).name 15 | assert_equal "1", subnode.rhs.as(AST::Integer).value 16 | end 17 | 18 | def test_expands_assignment_operators_recursively 19 | node = visit("a += (b *= 1)").as(AST::Assignment) 20 | 21 | assert_equal "=", node.operator 22 | assert_equal "a", node.lhs.as(AST::Variable).name 23 | 24 | add = node.rhs.as(AST::Binary) 25 | assert_equal "+", add.operator 26 | assert_equal "a", add.lhs.as(AST::Variable).name 27 | 28 | assign = add.rhs.as(AST::Assignment) 29 | assert_equal "=", assign.operator 30 | assert_equal "b", assign.lhs.as(AST::Variable).name 31 | 32 | mul = assign.rhs.as(AST::Binary) 33 | assert_equal "*", mul.operator 34 | assert_equal "b", mul.lhs.as(AST::Variable).name 35 | assert_equal "1", mul.rhs.as(AST::Integer).value 36 | end 37 | 38 | def test_injects_self_as_first_arg_to_struct_methods 39 | node = visit <<-RUNIC 40 | struct User 41 | def age(since : i32) 42 | end 43 | end 44 | RUNIC 45 | 46 | fn = node.as(AST::Struct).methods.first 47 | assert_equal 2, fn.args.size 48 | assert_equal "self", fn.args[0].name 49 | assert_equal "since", fn.args[1].name 50 | assert_equal Type.new("User*"), fn.args[0].type 51 | assert_equal Type.new("i32"), fn.args[1].type 52 | end 53 | 54 | protected def visitors 55 | @visitor ||= [SugarExpanderVisitor.new(program)] 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /test/target_test.cr: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "../src/target" 3 | 4 | module Runic 5 | class TargetTest < Minitest::Test 6 | def test_parses_x86_architectures 7 | target = Target.new("i386-unknown-linux-gnu") 8 | assert_equal "i386", target.architecture 9 | assert_includes target.to_flags, "X86" 10 | 11 | target = Target.new("i486-unknown-linux-gnu") 12 | assert_equal "i486", target.architecture 13 | assert_includes target.to_flags, "X86" 14 | 15 | target = Target.new("i586-unknown-linux-gnu") 16 | assert_equal "i586", target.architecture 17 | assert_includes target.to_flags, "X86" 18 | 19 | target = Target.new("i686-unknown-linux-gnu") 20 | assert_equal "i686", target.architecture 21 | assert_includes target.to_flags, "X86" 22 | 23 | target = Target.new("x86_64-unknown-linux-gnu") 24 | assert_equal "x86_64", target.architecture 25 | assert_includes target.to_flags, "X86_64" 26 | 27 | target = Target.new("amd64-unknown-linux-gnu") 28 | assert_equal "amd64", target.architecture 29 | assert_includes target.to_flags, "X86_64" 30 | end 31 | 32 | def test_parses_arm_architectures 33 | target = Target.new("arm-unknown-linux-gnu") 34 | assert_equal "arm", target.architecture 35 | assert_includes target.to_flags, "ARM" 36 | 37 | target = Target.new("armv7-unknown-linux-gnu") 38 | assert_equal "armv7", target.architecture 39 | assert_includes target.to_flags, "ARM" 40 | assert_includes target.to_flags, "ARMV7" 41 | 42 | target = Target.new("aarch64-unknown-linux-gnu") 43 | assert_equal "aarch64", target.architecture 44 | assert_includes target.to_flags, "AARCH64" 45 | 46 | target = Target.new("arm64-apple-ios") 47 | assert_equal "arm64", target.architecture 48 | assert_includes target.to_flags, "AARCH64" 49 | end 50 | 51 | def test_enables_fpu_for_arm_targets 52 | # not enabled for soft-float environment: 53 | target = Target.new("arm-linux-gnueabi") 54 | refute_includes target.features, "+vfp2" 55 | 56 | target = Target.new("arm-linux-musleabi") 57 | refute_includes target.features, "+vfp2" 58 | 59 | # enabled for harf-float environments: 60 | target = Target.new("arm-linux-gnueabihf") 61 | assert_includes target.features, "+vfp2" 62 | 63 | target = Target.new("armv6z-linux-musleabihf") 64 | assert_includes target.features, "+vfp2" 65 | 66 | target = Target.new("arm-linux-androideabihf") 67 | assert_includes target.features, "+vfp2" 68 | 69 | # not enabled if CPU is specified (LLVM will select better FPU): 70 | target = Target.new("armv6z-linux-gnueabihf", "arm1176jzf-s") 71 | refute_includes target.features, "+vfp2" 72 | 73 | # not enabled if an FPU is specified: 74 | target = Target.new("armv6z-linux-gnueabihf", "", "+vfp4") 75 | refute_includes target.features, "+vfp2" 76 | assert_includes target.features, "+vfp4" 77 | end 78 | 79 | def test_parses_linux_targets 80 | target = Target.new("i386-pc-linux-gnu") 81 | assert_equal "linux", target.system 82 | assert_equal "gnu", target.environment 83 | assert_includes target.to_flags, "LINUX" 84 | assert_includes target.to_flags, "GNU" 85 | 86 | target = Target.new("x86_64-unknown-linux-gnu") 87 | assert_equal "linux", target.system 88 | assert_equal "gnu", target.environment 89 | assert_includes target.to_flags, "LINUX" 90 | assert_includes target.to_flags, "GNU" 91 | 92 | target = Target.new("arm-linux-musleabi") 93 | assert_equal "linux", target.system 94 | assert_equal "musleabi", target.environment 95 | assert_includes target.to_flags, "LINUX" 96 | assert_includes target.to_flags, "MUSL" 97 | assert_includes target.to_flags, "MUSLEABI" 98 | 99 | target = Target.new("arm-linux-musleabihf") 100 | assert_equal "linux", target.system 101 | assert_equal "musleabihf", target.environment 102 | assert_includes target.to_flags, "ARMHF" 103 | assert_includes target.to_flags, "LINUX" 104 | assert_includes target.to_flags, "MUSL" 105 | assert_includes target.to_flags, "MUSLEABIHF" 106 | 107 | target = Target.new("armv7-unknown-linux-gnueabi") 108 | assert_equal "linux", target.system 109 | assert_equal "gnueabi", target.environment 110 | assert_includes target.to_flags, "LINUX" 111 | assert_includes target.to_flags, "GNU" 112 | assert_includes target.to_flags, "GNUEABI" 113 | 114 | target = Target.new("arm-unknown-linux-gnueabihf") 115 | assert_equal "linux", target.system 116 | assert_equal "gnueabihf", target.environment 117 | assert_includes target.to_flags, "ARMHF" 118 | assert_includes target.to_flags, "LINUX" 119 | assert_includes target.to_flags, "GNU" 120 | assert_includes target.to_flags, "GNUEABIHF" 121 | 122 | target = Target.new("aarch64-unknown-linux-gnu") 123 | assert_equal "linux", target.system 124 | assert_equal "gnu", target.environment 125 | assert_includes target.to_flags, "LINUX" 126 | assert_includes target.to_flags, "GNU" 127 | end 128 | 129 | def test_parses_android_targets 130 | target = Target.new("arm64-unknown-linux-android") 131 | assert_equal "linux", target.system 132 | assert_equal "android", target.environment 133 | assert_includes target.to_flags, "LINUX" 134 | assert_includes target.to_flags, "ANDROID" 135 | 136 | target = Target.new("armv7-unknown-linux-androideabi") 137 | assert_equal "linux", target.system 138 | assert_equal "androideabi", target.environment 139 | assert_includes target.to_flags, "LINUX" 140 | assert_includes target.to_flags, "ANDROID" 141 | assert_includes target.to_flags, "ANDROIDEABI" 142 | end 143 | 144 | def test_parses_darwin_targets 145 | target = Target.new("i686-apple-macosx10.9") 146 | assert_equal "darwin", target.system 147 | assert_equal "10.9", target.version 148 | 149 | target = Target.new("x86_64-apple-darwin9") 150 | assert_equal "darwin", target.system 151 | assert_equal "9", target.version 152 | 153 | target = Target.new("armv7-apple-ios") 154 | assert_equal "ios", target.system 155 | assert_includes target.to_flags, "IOS" 156 | assert_includes target.to_flags, "DARWIN" 157 | end 158 | 159 | def test_parses_openbsd_targets 160 | target = Target.new("amd64-openbsd6.2") 161 | assert_equal "openbsd", target.system 162 | assert_equal "6.2", target.version 163 | assert_empty target.environment 164 | 165 | target = Target.new("i686-unknown-openbsd6.1") 166 | assert_equal "openbsd", target.system 167 | assert_equal "6.1", target.version 168 | assert_empty target.environment 169 | end 170 | 171 | def test_parses_freebsd_targets 172 | target = Target.new("i686-freebsd11.1") 173 | assert_equal "freebsd", target.system 174 | assert_equal "11.1", target.version 175 | assert_empty target.environment 176 | 177 | target = Target.new("x86_64-unknown-freebsd10.3") 178 | assert_equal "freebsd", target.system 179 | assert_equal "10.3", target.version 180 | assert_empty target.environment 181 | end 182 | 183 | def test_parses_windows_targets 184 | target = Target.new("i686-pc-windows-msvc") 185 | assert_equal "windows", target.system 186 | assert_equal "msvc", target.environment 187 | assert_includes target.to_flags, "WINDOWS" 188 | assert_includes target.to_flags, "MSVC" 189 | assert_includes target.to_flags, "WIN32" 190 | 191 | target = Target.new("i586-pc-windows-gnu") 192 | assert_equal "windows", target.system 193 | assert_equal "gnu", target.environment 194 | assert_includes target.to_flags, "WINDOWS" 195 | assert_includes target.to_flags, "GNU" 196 | assert_includes target.to_flags, "WIN32" 197 | 198 | target = Target.new("x86_64-windows-cygnus") 199 | assert_equal "windows", target.system 200 | assert_equal "cygnus", target.environment 201 | assert_includes target.to_flags, "WINDOWS" 202 | assert_includes target.to_flags, "CYGNUS" 203 | assert_includes target.to_flags, "UNIX" 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /test/test_helper.cr: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "../src/lexer" 3 | require "../src/parser" 4 | require "../src/program" 5 | require "../src/semantic" 6 | 7 | class Minitest::Test 8 | protected def visit(source) 9 | parse_each(source) do |node| 10 | visitors.each(&.visit(node)) 11 | return node 12 | end 13 | raise "unreachable" 14 | end 15 | 16 | protected def visitors 17 | raise "ERROR: #{self.class.name}#visitors must be implemented." 18 | end 19 | 20 | protected def require_corelib 21 | self.require(File.expand_path("../corelib/corelib", __DIR__)) 22 | Runic::Semantic.analyze(program) 23 | end 24 | 25 | protected def require(path) 26 | path += ".runic" unless path.ends_with?(".runic") 27 | File.open(path, "r") do |io| 28 | parser(io, top_level_expressions: false).parse do |node| 29 | case node 30 | when Runic::AST::Require 31 | if require_path = program.resolve_require(node, File.dirname(path)) 32 | self.require(require_path) 33 | end 34 | else 35 | program.register(node) 36 | end 37 | end 38 | end 39 | end 40 | 41 | protected def parse_each(source, top_level_expressions = true) 42 | parser(source, top_level_expressions).parse do |node| 43 | yield node 44 | end 45 | end 46 | 47 | protected def parse_all(source, top_level_expressions = true) 48 | parser(source, top_level_expressions).parse do |node| 49 | program.register(node) 50 | end 51 | end 52 | 53 | protected def parser(source, top_level_expressions = true) 54 | Runic::Parser.new(lex(source), top_level_expressions) 55 | end 56 | 57 | protected def lex(source : String) 58 | Runic::Lexer.new(IO::Memory.new(source)) 59 | end 60 | 61 | protected def lex(io : IO) 62 | Runic::Lexer.new(io) 63 | end 64 | 65 | protected def program 66 | @program ||= Runic::Program.new 67 | end 68 | end 69 | --------------------------------------------------------------------------------