├── .gitignore ├── .travis.yml ├── LICENSE ├── MACROS.md ├── NODES.md ├── README.md ├── shard.yml ├── spec ├── cppize_spec.cr └── spec_helper.cr ├── src ├── ast_search.cr ├── cppize.cr ├── cppize │ ├── class_data.cr │ ├── get_name.cr │ ├── lines.cr │ ├── macros │ │ ├── __comment__.cr │ │ ├── __cpp__.cr │ │ ├── __cppize_version__.cr │ │ ├── __stdlib__.cr │ │ └── define.cr │ ├── nodes │ │ ├── alias.cr │ │ ├── arg.cr │ │ ├── array_literal.cr │ │ ├── assign.cr │ │ ├── attribute.cr │ │ ├── binary_op.cr │ │ ├── block.cr │ │ ├── bool_literal.cr │ │ ├── break.cr │ │ ├── call.cr │ │ ├── case.cr │ │ ├── cast.cr │ │ ├── char_literal.cr │ │ ├── class_def.cr │ │ ├── class_var.cr │ │ ├── def.cr │ │ ├── enum_def.cr │ │ ├── expressions.cr │ │ ├── external_var.cr │ │ ├── fun_def.cr │ │ ├── global.cr │ │ ├── if.cr │ │ ├── include.cr │ │ ├── instance_var.cr │ │ ├── is_a.cr │ │ ├── lib_def.cr │ │ ├── macro_id.cr │ │ ├── module_def.cr │ │ ├── multiassign.cr │ │ ├── next.cr │ │ ├── nil_literal.cr │ │ ├── nilable_cast.cr │ │ ├── not.cr │ │ ├── number_literal.cr │ │ ├── pointerof.cr │ │ ├── proc_literal.cr │ │ ├── proc_notation.cr │ │ ├── range_literal.cr │ │ ├── regex_literal.cr │ │ ├── require.cr │ │ ├── return.cr │ │ ├── self.cr │ │ ├── sizeof.cr │ │ ├── splat.cr │ │ ├── string_interpolation.cr │ │ ├── string_literal.cr │ │ ├── type_declaration.cr │ │ ├── unless.cr │ │ ├── until.cr │ │ ├── var.cr │ │ └── while.cr │ ├── transpiler_error.cr │ ├── unique_name.cr │ └── version.cr └── transpiler.cr └── test ├── req ├── abs.cr ├── enum.cr ├── lambda_def.cr ├── module.cr └── regex.cr ├── required.cr └── test.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /libs/ 3 | /lib/ 4 | /.crystal/ 5 | /.shards/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language : crystal 2 | sudo: required 3 | addons: 4 | apt: 5 | sources: 6 | - llvm-toolchain-precise-3.6 7 | - ubuntu-toolchain-r-test 8 | packages: 9 | - llvm-3.6-dev 10 | - libllvm3.6 11 | - llvm-3.6 12 | - llvm-3.6-runtime 13 | - libstdc++6 14 | - libedit-dev 15 | - g++-6 16 | 17 | script: 18 | - g++-6 -v 19 | - sudo g++-6 -o /opt/crystal/src/llvm/ext/llvm_ext.o -c /opt/crystal/src/llvm/ext/llvm_ext.cc $(llvm-config-3.6 --cxxflags) 20 | - crystal spec -s 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 unn4m3d 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MACROS.md: -------------------------------------------------------------------------------- 1 | # Implemented macros 2 | 3 | ### \_\_comment\_\_ 4 | 5 | This macro places its arguments into `/* multiline comment */` 6 | 7 | ### \_\_cpp\_\_ 8 | 9 | If called with block, this macro uses first argument or empty string as block header and places transpiled body into a block. 10 | 11 | 12 | For example, 13 | ```crystal 14 | __cpp__ "int main()" do 15 | __cpp__ "return 0" 16 | end 17 | ``` 18 | results in 19 | 20 | ```cpp 21 | int main() 22 | { 23 | return 0; 24 | } 25 | ``` 26 | 27 | If there is no block given, this macro places each argument into output file 28 | 29 | ```crystal 30 | __cpp__ "foo()" 31 | __cpp__ "bar()","baz()" 32 | ``` 33 | 34 | Results in 35 | 36 | ```cpp 37 | foo(); 38 | bar(); 39 | baz(); 40 | ``` 41 | 42 | :warning: **WARNING** This macro won't emit anything unless called with literal arguments 43 | 44 | ### \_\_cppize\_version\_\_ 45 | 46 | This macro call turns into string literal containing cppize version string 47 | 48 | ### define 49 | 50 | Defines given symbols 51 | 52 | ### \_\_stdlib\_\_ 53 | 54 | If called with block, this macro places block body into stdlib namespace (defined in `Cppize::Transpiler::STDLIB_NAMESPACE`) 55 | If called without block, this macro evaluates to name of stlib namespace 56 | 57 | 58 | ### undef 59 | 60 | Undefines given symbols 61 | -------------------------------------------------------------------------------- /NODES.md: -------------------------------------------------------------------------------- 1 | AST Nodes support: 2 | == 3 | 4 | Node | Status | Notes 5 | --------------|---------------|-------------- 6 | And | :white_check_mark: Supported | 7 | Arg | :heavy_exclamation_mark: Partial | 8 | Alias | :heavy_exclamation_mark: Partial | :warning: Specialized aliases are not supported yet 9 | ArrayLiteral | :heavy_exclamation_mark: Partial | :warning: Empty array literals without type restrictions are not supported
:warning: Needs to be implemented in stdlib 10 | Assign | :white_check_mark: Supported | 11 | Attribute | :white_check_mark: Supported | :warning: Only one attribute is implemented 12 | BinaryOp | :white_check_mark: Supported | 13 | Block | :white_check_mark: Supported | 14 | BoolLiteral | :white_check_mark: Supported | :warning: Needs to be implemented in stdlib 15 | Break | :white_check_mark: Supported | 16 | Call | :heavy_exclamation_mark: Partial | :warning: `NamedArgument`s are not supported 17 | Case | :heavy_exclamation_mark: Partial | :warning: This is **NOT** expression
:warning: Cannot compare types yet 18 | Cast | :bangbang: Experimental | :memo: Implemented using `static_cast`.
:memo: `-funsafe-cast` command line option tells transpiler to use C-style casts 19 | CharLiteral | :white_check_mark: Supported | :warning: Needs to be implemented in stdlib 20 | ClassDef | :heavy_exclamation_mark: Partial | :warning: Variadic templates are not supported
:warning: Named Type Vars are not supported 21 | ClassVar | :white_check_mark: Supported | 22 | CStructOrUnionDef | :x: Not supported | 23 | Def | :heavy_exclamation_mark: Partial | :warning: Splats are partially supported; see Splat for details 24 | EnumDef | :heavy_exclamation_mark: Partial | :warning: Only enums without base type are supported 25 | Expressions | :white_check_mark: Supported | 26 | ExternalVar | :heavy_exclamation_mark: Partial | :warning: 27 | FunDef | :white_check_mark: Supported | 28 | Generic | :white_check_mark: Supported | 29 | Global | :bangbang: Experimental | 30 | HashLiteral | :x: Not supported | 31 | If | :white_check_mark: Supported | 32 | ImplicitObj | :x: Not supported | 33 | Include | :bangbang: Experimental | 34 | InstanceSizeOf| :white_check_mark: Supported | 35 | InstanceVar | :white_check_mark: Supported | 36 | IsA | :bangbang: Experimental | 37 | LibDef | :heavy_exclamation_mark: Partial | 38 | Macro | :x: Not supported | 39 | MacroFor | :x: Not supported | 40 | MacroIf | :x: Not supported | 41 | MagicConstant | :x: Not supported | 42 | ModuleDef | :heavy_exclamation_mark: Partial | 43 | MultiAssign | :white_check_mark: Supported | 44 | NamedArgument | :x: Not supported | 45 | NamedTupleLiteral | :x: Not supported | 46 | Next | :heavy_exclamation_mark: Partial | :warning: Doesn't exits the block
:warning: Cannot have a value 47 | NilableCast | :x: Not supported | 48 | NilLiteral | :white_check_mark: Supported | 49 | Nop | :white_check_mark: Supported | 50 | Not | :white_check_mark: Supported | 51 | NumberLiteral | :white_check_mark: Supported | :warning: Needs to be implemented in stdlib 52 | Or | :white_check_mark: Supported | 53 | Out | :x: Not supported | 54 | Path | :heavy_exclamation_mark: Partial | 55 | PointerOf | :white_check_mark: Supported | :warning: Needs to be implemented in stdlib 56 | ProcLiteral | :white_check_mark: Supported| 57 | ProcNotation | :white_check_mark: Supported | :warning: Needs to be implemented in stdlib 58 | RangeLiteral | :white_check_mark: Supported | :warning: Needs to be implemented in stdlib 59 | RegexLiteral | :bangbang: Experimental | 60 | Require | :heavy_exclamation_mark: Partial | 61 | RespondsTo | :x: Not supported | 62 | Return | :heavy_exclamation_mark: Partial | 63 | Self | :white_check_mark: Supported | 64 | SizeOf | :heavy_exclamation_mark: Partial | :warning: Behaves the same way as `InstanceSizeOf` 65 | Splat | :heavy_exclamation_mark: Partial | :warning: There is no support for calls with variable number of arguments yet
:warning: See Splats section below 66 | StringInterpolation | :white_check_mark: Supported | 67 | StringLiteral | :heavy_exclamation_mark: Partial | :warning: Needs to be implemented in stdlib
:warning: Escape sequences are not supported 68 | SymbolLiteral | :x: Not supported | 69 | TupleLiteral | :x: Not supported | 70 | TypeDeclaration | :white_check_mark: Supported | 71 | TypeNode | :white_check_mark: Supported | 72 | Underscore | :x: Not supported | 73 | UninitializedVar | :x: Not supported | 74 | Union | :bangbang: Experimental | :warning: Needs to be implemented in stdlib 75 | Unless | :white_check_mark: Supported | 76 | Var | :bangbang: Experimental | 77 | VisibilityModifier | :white_check_mark: Supported | 78 | When | :white_check_mark: Supported | 79 | While | :white_check_mark: Supported | 80 | 81 | 82 | Splats 83 | -- 84 | 85 | Splats are supported partially. For example: 86 | 87 | ```crystal 88 | def a(b,*c,d) 89 | #... 90 | end 91 | 92 | #OK 93 | a foo, bar, baz, biz 94 | 95 | #OK 96 | a foo, *bar 97 | 98 | #WRONG 99 | a *baz 100 | 101 | #WRONG 102 | a *baz,foo 103 | 104 | ``` 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cppize 2 | 3 | ![Build status](https://travis-ci.org/unn4m3d/cppize.svg?branch=master) 4 | 5 | Crystal-to-C++ transpiler [WIP] 6 | 7 | Generated code can be compiled with c++14 compiler (tested with g++ 6.2.0) 8 | 9 | List of supported AST nodes can be found [here](NODES.md) 10 | 11 | You can try it [here](https://unn4m3d.github,io/cppize) 12 | 13 | ## CLI Usage 14 | 15 | 1. Compile `src/cppize/transpiler.cr` (it may take some time as it `require`s Crystal parser) 16 | 2. Launch compiled executable with `-h` flag to view all command line flags 17 | 18 | #### Implemented `-fFEATURE`s 19 | Flag | Description 20 | ------|--------------- 21 | `-funsafe-cast` | Tells transpiler to transpile casts to C-style casts instead of `static_cast`s 22 | `-fprimitive-types` | Tells transpiler to use fundamental C++ types when possible 23 | `-fauto-module-type`| Allows transpiler to detect if module is included
:warning: This option can slow down transpilation 24 | `-fimplicit-static` | Enables static module methods' calls 25 | 26 | ## Library Usage 27 | ```crystal 28 | # Initialize transpiler 29 | transpiler = Cppize::Transpiler.new 30 | 31 | # Set error and warning callbacks 32 | transpiler.on_warning{|e| puts e.to_s} 33 | transpiler.on_error{|e| puts e.to_s; exit 1} 34 | 35 | # Transpile file 36 | transpiled_code = transpiler.parse_and_transpile_file("./file.cpp.cr") 37 | # Transpile code from IO 38 | transpiled_code = transpiler.parse_and_transpile_file(File.open("./file.cpp.cr"),"./file.cpp.cr") 39 | # Transpile code from string 40 | transpiled_code = transpiler.parse_and_transpile_file("def foo; bar(true) end","") 41 | ``` 42 | 43 | ## Things to improve in already supported AST nodes 44 | 45 | 3. Improve automatic return 46 | 4. Improve module type detection (namespace / includable) 47 | 48 | 49 | 50 | ## Development 51 | 52 | #### Implementing nodes 53 | 54 | See [src/cppize/nodes/expressions.cr](src/cppize/nodes/expressions.cr) for example 55 | 56 | #### Adding transpile-time macros 57 | 58 | See [src/cppize/macros/\_\_cpp\_\_.cr](src/cppize/macros/__cpp__.cr) for example 59 | 60 | ## Contributing 61 | 62 | 1. Fork it ( https://github.com/unn4m3d/cppize/fork ) 63 | 2. Create your feature branch (git checkout -b my-new-feature) 64 | 3. Commit your changes (git commit -am 'Add some feature') 65 | 4. Push to the branch (git push origin my-new-feature) 66 | 5. Create a new Pull Request 67 | 68 | ## Contributors 69 | 70 | - [unn4m3d](https://github.com/unn4m3d) unn4m3d - creator, maintainer 71 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: cppize 2 | version: 0.1.0 3 | 4 | authors: 5 | - unn4m3d 6 | 7 | license: MIT 8 | -------------------------------------------------------------------------------- /spec/cppize_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Cppize do 4 | # TODO: Write tests 5 | 6 | it "works" do 7 | begin 8 | Cppize::Transpiler.new(false).parse_and_transpile("puts 0","test") 9 | true.should eq(true) 10 | rescue ex 11 | ex.should eq("") 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/cppize" 3 | -------------------------------------------------------------------------------- /src/ast_search.cr: -------------------------------------------------------------------------------- 1 | abstract class Crystal::ASTNode 2 | 3 | def children 4 | if self.responds_to?(:expressions) 5 | self.expressions 6 | elsif self.responds_to?(:body) 7 | if self.body.nil? 8 | nil 9 | else 10 | self.body.as(ASTNode).children || self.body 11 | end 12 | else 13 | nil 14 | end 15 | end 16 | 17 | def search_of_type(node_type : Class, recur : Bool = false) 18 | array = [children].flatten.select { |x| x.class == node_type } 19 | 20 | if recur 21 | array.each { |elem| array += elem.search_of_type(node_type, recur) unless elem.nil? } 22 | end 23 | array.select { |x| !x.nil? } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /src/cppize.cr: -------------------------------------------------------------------------------- 1 | require "./cppize/*" 2 | require "./ast_search" 3 | require "compiler/crystal/**" 4 | 5 | class Crystal::Program 6 | @crystal_path : CrystalPath? 7 | end 8 | 9 | class ArgumentError 10 | def initialize(message : String? = nil, cause : Exception? = nil) 11 | super message, cause 12 | end 13 | end 14 | 15 | module Cppize 16 | include Crystal 17 | 18 | @[Flags] 19 | enum Warning : UInt64 20 | REDECLARATION 21 | FD_SKIP 22 | KIND_INF 23 | NAME_MISMATCH 24 | LONG_PATH 25 | NESTED 26 | ARGUMENT_MISMATCH 27 | end 28 | 29 | def self.warning_list 30 | Warning.names.map(&.downcase.gsub("_","-")) 31 | end 32 | 33 | def self.warning_from_string(str) 34 | i = warning_list.index str 35 | if i.nil? 36 | return 0 37 | else 38 | return 2u64**i 39 | end 40 | 41 | end 42 | 43 | class Transpiler 44 | property options 45 | 46 | STDLIB_NAMESPACE = "Crystal::" 47 | 48 | @@features = Hash(String,Proc(String,Void)).new 49 | 50 | def self.features_list 51 | @@features.keys 52 | end 53 | 54 | @attribute_set = [] of Attribute 55 | 56 | property enabled_warnings 57 | 58 | @enabled_warnings : UInt64 59 | 60 | @enabled_warnings = 0u64 61 | 62 | macro register_node(klass,*opts,&block) 63 | def transpile(node : {{klass}}, *tr_options) 64 | should_return? = if tr_options.size == 1 && tr_options[0]?.is_a?(Bool) # To keep and old behaviour 65 | tr_options[0]? 66 | else 67 | tr_options.includes?(:should_return) 68 | end 69 | %code = try_tr(node) do 70 | {{block.body}} 71 | end 72 | 73 | {% unless opts.includes? :keep_attributes %} 74 | @attribute_set = [] of Attribute 75 | {% end %} 76 | %code 77 | end 78 | end 79 | 80 | def tr_uid(s) 81 | s.gsub(/^::/,"").gsub(/::::/,"::").gsub(/<.*>/,"") 82 | end 83 | 84 | def register_feature(feature : String, &on_enable : Proc(String,Void)) 85 | @@features[feature] = on_enable 86 | end 87 | 88 | @forward_decl_classes = Lines.new 89 | @forward_decl_defs = Lines.new 90 | @global_vars = Lines.new 91 | @lib_defs = Lines.new 92 | @classes = ClassDataHash.new 93 | @defs = Lines.new 94 | @globals_list = [] of String 95 | 96 | @options = Hash(String, String?).new 97 | 98 | alias UnitInfo = NamedTuple(id: String, type: Symbol) 99 | alias Scope = Hash(String, NamedTuple(symbol_type: Symbol, value: ASTNode?)) 100 | 101 | @scopes = Array(Scope).new 102 | @typenames = [] of Array(String) 103 | @unit_stack = [ {id: "::", type: :top_level} ] of UnitInfo 104 | @current_namespace = [] of String 105 | @in_class = false 106 | @current_class = [] of String 107 | @current_visibility : Visibility? = nil 108 | 109 | protected def current_cid 110 | if @current_class.empty? 111 | "" 112 | else 113 | @current_class.zip(@typenames).map{|x| "#{x.first}< #{x.last.join(", ")} >"}.join("::") 114 | end 115 | end 116 | 117 | protected def full_cid 118 | (@current_namespace+[current_cid]).join("::") 119 | end 120 | 121 | getter current_filename 122 | 123 | @current_filename = "" 124 | @ast : ASTNode? 125 | 126 | def find_var(name : String) : NamedTuple(symbol_type: Symbol, value: ASTNode?) 127 | if name == "self" 128 | return {symbol_type: :pointer, value: nil} 129 | end 130 | @scopes.each do |h| 131 | if h.has_key? name 132 | return h[name] 133 | end 134 | end 135 | {symbol_type: :undefined, value: nil} 136 | end 137 | 138 | property? failsafe : Bool 139 | property? use_preprocessor_defs : Bool 140 | @use_preprocessor_defs = false 141 | 142 | def initialize(@failsafe = false) 143 | end 144 | 145 | def post_initialize! 146 | @@features.each do |k,v| 147 | if @options.has_key? k 148 | v.call k 149 | end 150 | end 151 | end 152 | 153 | CORE_TYPES = [ 154 | "Int", "Int8", "Int16", "Int32", "Int64", 155 | "UInt", "UInt8", "UInt16", "UInt32", "UInt64", 156 | "Char", "String", "Array", "StaticArray", "Pointer", 157 | "Size", "Object", "Hash", "Numeric", "Float", 158 | "Float32", "Float64", "LongFloat", 159 | ] 160 | 161 | BUILTIN_TYPES = [ 162 | "Void", "Auto", "NativeInt", 163 | ] 164 | 165 | COMMENT = %( 166 | /* 167 | Autogenerated with Cppize 168 | Cppize is an open-source Crystal-to-C++ transpiler 169 | https://github.com/unn4m3d/cppize 170 | */ 171 | ) 172 | 173 | protected def initial_defines 174 | lines = [] of String 175 | lines << "#define CPPIZE_NO_RTTI" if options.has_key? "no-rtti" 176 | lines << "#define CPPIZE_USE_PRIMITIVE_TYPES" if options.has_key? "primitive-types" 177 | lines << "#define CPPIZE_NO_EXCEPTIONS" if options.has_key? "no-exceptions" 178 | lines << "#define CPPIZE_NO_STD_STRING" if options.has_key? "no-std-string" 179 | lines << "#include " unless options.has_key? "no-stdlib" 180 | lines << "#include " unless options.has_key? "no-splats" 181 | lines << "using #{STDLIB_NAMESPACE.sub(/::$/,"")};" unless options.has_key? "no-stdlib" || options.has_key? "no-using-stdlib" || STDLIB_NAMESPACE.empty? 182 | lines.join("\n") 183 | end 184 | 185 | @includes = Array(Include?).new 186 | 187 | def parse_and_transpile(code : String, file : String = "") 188 | begin 189 | @current_filename = file 190 | @ast = Parser.parse(code) 191 | unless @ast.nil? 192 | @includes = @ast.not_nil!.search_of_type(Include,true).map{|x| x.as(Include?)} 193 | end 194 | code = transpile @ast 195 | # predef = Lines.new(@failsafe) do |l| 196 | # end.to_s 197 | 198 | [ 199 | COMMENT, 200 | initial_defines, 201 | "/* :CPPIZE: Classes' forward declarations */", 202 | @forward_decl_classes, 203 | "/* :CPPIZE: Functions' forward declarations */", 204 | @forward_decl_defs, 205 | "/* :CPPIZE: Global vars */", 206 | @global_vars, 207 | "/* :CPPIZE: Bindings */", 208 | @lib_defs, 209 | "/* :CPPIZE: Classes */", 210 | @classes, 211 | "/* :CPPIZE: Functions */", 212 | @defs, 213 | code, 214 | ].map(&.to_s).join("\n\n") 215 | rescue e : Error 216 | @on_error.call(e) 217 | "" 218 | end 219 | end 220 | 221 | def parse_and_transpile(file : IO, name : String = "") 222 | parse_and_transpile file.gets_to_end, name 223 | end 224 | 225 | def parse_and_transpile_file(file : String) 226 | parse_and_transpile File.read(file),file 227 | end 228 | 229 | alias ErrorHandler = Error -> (Void|NoReturn) 230 | 231 | @on_error : ErrorHandler 232 | @on_warning : ErrorHandler 233 | 234 | @on_error = ->(e : Error){ 235 | puts e.to_s 236 | exit 1 237 | } 238 | 239 | @on_warning = ->(e : Error){ 240 | puts "/*WARNING :\n #{e.message}\n*/" 241 | } 242 | 243 | def on_warning(&o : ErrorHandler) 244 | @on_warning = o 245 | end 246 | 247 | def on_error(&o : ErrorHandler) 248 | @on_error = o 249 | end 250 | 251 | def warn(w : Error) 252 | @on_warning.call(w) 253 | end 254 | 255 | def warn(m : String, n : ASTNode?=nil, c : Error?=nil, f : String = @current_filename) 256 | warn(Error.new m,n,c,f) 257 | end 258 | 259 | def warning(w,&b) 260 | if (@enabled_warnings.to_u64 & w.to_u64) != 0 261 | b.call 262 | end 263 | end 264 | 265 | protected def pretty_signature(d : Def) : String 266 | restrictions = d.args.map &.restriction 267 | "#{d.name}(#{restrictions.map(&.to_s).join(",")})" + (d.return_type ? " : #{d.return_type.to_s}" : "") 268 | end 269 | 270 | register_node ASTNode do 271 | if @failsafe 272 | "#warning Node type #{node.class} isn't supported yet" 273 | else 274 | raise Error.new("Node type #{node.class} isn't supported yet", node, nil, @current_filename) 275 | end 276 | end 277 | 278 | protected def transpile_type(_n : String) 279 | if CORE_TYPES.includes?(_n) 280 | "#{STDLIB_NAMESPACE}#{_n}" 281 | elsif BUILTIN_TYPES.includes?(_n) 282 | _n.downcase 283 | else 284 | _n 285 | end 286 | end 287 | 288 | register_node Union do 289 | "#{STDLIB_NAMESPACE}Union< #{node.types.map{|x| transpile x}.join(", ")} >" 290 | end 291 | 292 | register_node TypeNode do 293 | (should_return? ? "return " : "") + transpile_type(node.to_s) 294 | end 295 | 296 | register_node Path do 297 | #try_tr node do 298 | node.names[0] = transpile_type node.names.first 299 | (should_return? ? "return " : "") + (node.global? ? "::" : "") + "#{node.names.join("::")}" 300 | #end 301 | end 302 | 303 | register_node Generic do 304 | (should_return? ? "return " : "") + "#{transpile node.name}< #{node.type_vars.map { |x| transpile x }.join(",")} >" 305 | end 306 | 307 | register_node Nop do 308 | "" 309 | end 310 | 311 | protected def transpile(node : Nil, *o) 312 | "" 313 | end 314 | 315 | protected def translate_name(name : String) 316 | if CPP_OPERATORS.includes? name 317 | "operator #{name}" 318 | elsif ADDITIONAL_OPERATORS.has_key? name 319 | ADDITIONAL_OPERATORS[name] 320 | else 321 | name.sub(/^(.*)=$/) { |m| "set_#{m}" } 322 | .sub(/^(.*)\?$/) { |m| "is_#{m}" } 323 | .sub(/^(.*)!$/) { |m| "#{m}_" } 324 | .gsub(/[!\?=@]/, "") 325 | end 326 | end 327 | 328 | protected def try_tr(node : ASTNode | Visibility, &block : Proc(String)) 329 | begin 330 | return block.call 331 | rescue e : Error 332 | raise Error.new(e.message.to_s,nil,e,@current_filename) 333 | end 334 | return "" 335 | end 336 | 337 | protected def transpile(v : Visibility, *o) 338 | return try_tr v do 339 | case v 340 | when .public? 341 | "public" 342 | when .private? 343 | "private" 344 | when .protected? 345 | "protected" 346 | else 347 | "private" 348 | end 349 | end 350 | end 351 | end 352 | end 353 | 354 | require "./cppize/nodes/*" 355 | -------------------------------------------------------------------------------- /src/cppize/class_data.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class ClassData 3 | property dependencies : Array(String) 4 | property lines : Lines 5 | property name : String 6 | property dep_source : ClassDataHash? 7 | property header : String 8 | property c_deps = Array(String).new 9 | @lines = Lines.new 10 | @header = "" 11 | 12 | def initialize(@name) 13 | @dependencies = [] of String 14 | end 15 | 16 | def initialize(@name, *depends) 17 | @dependencies = depends.to_a 18 | end 19 | 20 | def depends_on?(name : String) 21 | return false if dep_source.nil? 22 | @dependencies.any?{|x| dep_source.not_nil![x].name == name || dep_source.not_nil![x].depends_on? name } 23 | end 24 | 25 | def depends_on?(c : self) 26 | depends_on? c.name 27 | end 28 | 29 | def <=>(other : self) 30 | if other.depends_on? self 31 | 1 32 | elsif self.depends_on? other 33 | -1 34 | else 35 | 0 36 | end 37 | end 38 | 39 | delegate :line, to: lines 40 | 41 | def block(a=nil,&b) 42 | lines.block(a,&b) 43 | end 44 | 45 | def to_s 46 | Lines.new do |l| 47 | l.line nil 48 | l.block @header do 49 | l.line lines.to_s 50 | end 51 | end.to_s + ";" 52 | end 53 | end 54 | 55 | class ClassDataHash < Hash(String,ClassData) 56 | def to_s 57 | keys = self.keys.sort{|x,y| x <=> y} 58 | keys.map{|x| self[x].to_s}.join("\n\n") 59 | end 60 | 61 | def []=(k,v : ClassData) 62 | v.dep_source = self 63 | super k.to_s,v 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /src/cppize/get_name.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | NAMES_DELIMITER = ";" 4 | def get_name(node : Generic) 5 | transpile node.name 6 | end 7 | 8 | def get_name(node : Path) 9 | transpile node 10 | end 11 | 12 | def get_name(node : ASTNode) 13 | transpile node 14 | end 15 | 16 | def get_name(node : Nil) 17 | "" 18 | end 19 | 20 | def get_name(node : Union) 21 | node.types.map{|x| transpile x}.join(NAMES_DELIMITER) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/cppize/lines.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Lines 3 | property? failsafe : Bool 4 | 5 | def initialize(@failsafe = true, &block : Lines -> _) 6 | @ident = 0 7 | @code = "" 8 | block.call self if block 9 | end 10 | 11 | def initialize(@failsafe = true) 12 | @ident = 0 13 | @code = "" 14 | end 15 | 16 | def line(str : String, do_not_place_semicolon : Bool = false) 17 | lines = str.to_s.split("\n") 18 | old_ident = lines.first.match(/^[\s\t]+/) || [""] 19 | lines.each do |l| 20 | @code += "\t"*@ident + l.sub(old_ident[0], "") 21 | @code += ";" if !do_not_place_semicolon && lines.size == 1 22 | @code += "\n" 23 | end 24 | end 25 | 26 | def line(s : Nil) 27 | end 28 | 29 | def block(header : String? = nil, &block) 30 | line header, true if header 31 | line "{", true 32 | @ident += 1 33 | begin 34 | block.call 35 | rescue ex : Transpiler::Error 36 | unless ex.catched? 37 | line "#error #{ex.message}", true 38 | ex.catched = true 39 | end 40 | raise ex unless @failsafe 41 | ensure 42 | @ident -= 1 43 | line "}", true 44 | end 45 | end 46 | 47 | def to_s 48 | @code 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/cppize/macros/__comment__.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | add_macro(:__comment__) do |_self, call| 4 | "/*#{call.args.reduce("") do |memo,arg| 5 | m = memo.to_s 6 | if arg.responds_to? :value 7 | m + arg.value.to_s + "\n" 8 | else 9 | m + arg.to_s 10 | end 11 | end}*/" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/cppize/macros/__cpp__.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | add_macro(:__cpp__) do |_self, call| 4 | Lines.new do |l| 5 | if call.block.nil? 6 | call.args.each do |arg| 7 | if arg.responds_to? :value 8 | l.line arg.value.to_s, true 9 | end 10 | end 11 | else 12 | b = call.block.not_nil! 13 | h = call.args.first? 14 | if h.responds_to? :value 15 | l.block h.value.to_s do 16 | l.line _self.transpile b.body 17 | end 18 | end 19 | end 20 | end.to_s 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /src/cppize/macros/__cppize_version__.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | add_macro(:__cppize_version__) do |_self, call| 4 | StringLiteral.new(VERSION).stringify.to_s 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/macros/__stdlib__.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | add_macro(:__stdlib__) do |_self,call| 4 | if call.block.nil? 5 | STDLIB_NAMESPACE 6 | else 7 | _self.transpile ModuleDef.new(Path.new([STDLIB_NAMESPACE.sub(/::$/,"")]),call.block.not_nil!.body) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/macros/define.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | property defines 4 | 5 | @defines = Array(String).new 6 | add_macro(:define) do |_self, call| 7 | if _self.use_preprocessor_defs? 8 | Lines.new do |l| 9 | call.args.each do |arg| 10 | if arg.responds_to? :value 11 | l.line "#define #{arg.value.to_s}", true 12 | else 13 | _self.warning Warning::ARGUMENT_MISMATCH do 14 | _self.warn "Only literals should be passed to define",call,nil,_self.current_filename 15 | end 16 | l.line "#define #{arg.to_s}", true 17 | end 18 | end 19 | end 20 | else 21 | call.args.each do |arg| 22 | if arg.responds_to? :value 23 | _self.defines.push arg.value.to_s 24 | else 25 | _self.warning Warning::ARGUMENT_MISMATCH do 26 | _self.warn "Only literals should be passed to define",call,nil,_self.current_filename 27 | end 28 | _self.defines.push arg.to_s 29 | end 30 | end 31 | end 32 | "" 33 | end 34 | 35 | add_macro(:undef) do |_self, call| 36 | if _self.use_preprocessor_defs? 37 | Lines.new do |l| 38 | call.args.each do |arg| 39 | if arg.responds_to? :value 40 | l.line "#undef #{arg.value.to_s}", true 41 | else 42 | _self.warning Warning::ARGUMENT_MISMATCH do 43 | _self.warn "Only literals should be passed to undef",call,nil,_self.current_filename 44 | end 45 | l.line "#undef #{arg.to_s}", true 46 | end 47 | end 48 | end 49 | else 50 | call.args.each do |arg| 51 | if arg.responds_to? :value 52 | _self.defines.delete arg.value.to_s 53 | else 54 | _self.warning Warning::ARGUMENT_MISMATCH do 55 | _self.warn "Only literals should be passed to undef",call,nil,_self.current_filename 56 | end 57 | _self.defines.delete arg.to_s 58 | end 59 | end 60 | end 61 | "" 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /src/cppize/nodes/alias.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Alias do 4 | "template using #{node.name} = #{transpile node.value}<_Alias_T_#{node.name}...>" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/arg.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Arg do 4 | restr = node.restriction ? transpile node.restriction : ARG_TYPE_PREFIX + node.name 5 | def_v = (node.default_value ? " = " + transpile node.default_value : "") 6 | if tr_options.includes? :splat 7 | unless def_v.empty? 8 | warning Warning::ARGUMENT_MISMATCH do 9 | warn "Default values of splats are not supported",node,nil,@current_filename 10 | end 11 | end 12 | "#{restr}... #{translate_name node.name}" 13 | elsif tr_options.includes? :tuple 14 | unless def_v.empty? 15 | warning Warning::ARGUMENT_MISMATCH do 16 | warn "Default values of splats are not supported",node,nil,@current_filename 17 | end 18 | end 19 | "#{STDLIB_NAMESPACE}Tuple< #{restr} > #{translate_name node.name}" 20 | else 21 | "#{restr} #{translate_name node.name}#{def_v}" 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /src/cppize/nodes/array_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node ArrayLiteral do 4 | if node.elements.empty? && !node.of && !tr_options.includes? :allow_empty_arrays 5 | raise Error.new("You must specify array type of empty array literals",node,nil,@current_filename) 6 | end 7 | 8 | arr_type = (node.of ? transpile node.of : "decltype(#{transpile node.elements.first})") 9 | 10 | (should_return? ? "return " : "") + "#{STDLIB_NAMESPACE}Array< #{arr_type} >{ #{node.elements.map{|x| transpile x}.join(", ")} }" 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/cppize/nodes/assign.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Assign do 4 | (should_return? ? "return" : "") + "#{transpile node.target} = #{transpile node.value}" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/attribute.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Attribute, :keep_attributes do 4 | @attribute_set << node 5 | "" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/cppize/nodes/binary_op.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node And do 4 | (should_return? ? "return " : "")+"(#{transpile node.left} && #{transpile node.right})" 5 | end 6 | 7 | register_node Or do 8 | (should_return? ? "return " : "")+"(#{transpile node.left} || #{transpile node.right})" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/block.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Block do 4 | if should_return? 5 | "return #{transpile node}" 6 | else 7 | 8 | args = node.args.map{|x| "auto #{x.name}"}.join(", ") 9 | 10 | Lines.new do |l| 11 | l.line nil 12 | l.block "[&](#{args})" do 13 | l.line transpile(node.body,:should_return) 14 | end 15 | end.to_s 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/cppize/nodes/bool_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node BoolLiteral do 4 | (should_return? ? "return " : "") + (node.value ? "1" : "0") + "_crbool" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/break.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Break do 4 | if node.exp 5 | raise Error.new("Breaks with expressions are not supported",node,nil,@current_filename) 6 | else 7 | "break" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/call.cr: -------------------------------------------------------------------------------- 1 | require "../macros/*" 2 | 3 | module Cppize 4 | class Transpiler 5 | @@macros = Hash(String, Proc(Transpiler, Call, String)).new 6 | 7 | def self.add_macro(name, &block : Transpiler, Call -> String) 8 | @@macros[name.to_s] = block 9 | end 10 | 11 | CPP_OPERATORS = %w(+ - * / % >> << >= <= > < == != & && | || ^ ~) 12 | 13 | ADDITIONAL_OPERATORS = { 14 | "===" => "equals", 15 | "=~" => "find", 16 | "<=>" => "diff", 17 | "**" => "pow", 18 | "class" => "get_type", 19 | "new" => "__new" 20 | } 21 | 22 | register_node Call do 23 | if should_return? 24 | "return #{transpile node};" 25 | else 26 | args = node.args.map { |x| transpile x }.join(", ") 27 | 28 | b_arg = node.block_arg 29 | if b_arg.responds_to?(:name) 30 | args += ", #{b_arg.name}" 31 | end 32 | if node.block 33 | args += "#{node.args.empty? ? "" : ","} #{transpile node.block.not_nil!}" 34 | end 35 | 36 | if node.obj 37 | if CPP_OPERATORS.includes? node.name 38 | if node.args.empty? 39 | "(#{node.name} #{transpile node.obj})" 40 | else 41 | "(#{transpile node.obj} #{node.name} #{transpile node.args.first})" 42 | end 43 | elsif node.name == "[]" 44 | "#{transpile node.obj}[#{transpile node.args.first}]" 45 | elsif node.name == "[]=" 46 | "#{transpile node.obj}[#{transpile node.args.first}] = #{transpile node.args[1]}" 47 | else 48 | 49 | name = ADDITIONAL_OPERATORS.has_key?(node.name) ? ADDITIONAL_OPERATORS[node.name] : translate_name node.name 50 | if node.obj.is_a? Self 51 | "this->#{name}(#{args})" 52 | elsif node.obj.is_a? Path 53 | if search_unit_type(node.obj.as(Path)) == :class_module && options.has_key? "implicit-static" 54 | "(#{transpile node.obj}::__static_#{translate_name name}(#{args}))" 55 | else 56 | "(#{transpile node.obj}::#{translate_name name}(#{args}))" 57 | end 58 | else 59 | "(#{transpile node.obj}->#{translate_name name}(#{args}))" 60 | end 61 | end 62 | else 63 | if @@macros.has_key? node.name 64 | @@macros[node.name].call self, node 65 | else 66 | "#{translate_name node.name}(#{args})" 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /src/cppize/nodes/case.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Case do 4 | Lines.new do |l| 5 | l.line nil 6 | #l.block("switch(#{transpile node.cond})") do 7 | node.whens.each_with_index do |wh,i| 8 | head = (i == 0 ? "if" : "else if") 9 | cond = wh.conds.reduce("") do |memo,e| 10 | del = (memo.to_s.empty? ? "" : " || ") 11 | memo.to_s + "#{del}((#{transpile node.cond}).compare(#{transpile e}))" 12 | end 13 | l.block("#{head}(#{cond})") do 14 | l.line transpile wh.body 15 | end 16 | end 17 | if node.else 18 | l.block "else" do 19 | l.line transpile node.else 20 | end 21 | end 22 | #end 23 | end.to_s 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/cppize/nodes/cast.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Cast do 4 | if options.has_key?("unsafe-cast") 5 | (should_return? ? "return " : "") + "((#{transpile node.to})#{transpile node.obj})" 6 | else 7 | (should_return? ? "return " : "") + "static_cast<#{transpile node.to}>(#{transpile node.obj})" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/char_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node CharLiteral do 4 | "'#{escape_cpp_string node.value.to_s}'_crchar" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/class_def.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node ClassDef do 4 | raw_uid = ([full_cid] + node.name.names).join("::") 5 | unit_id = tr_uid raw_uid 6 | @unit_types[unit_id] = :class 7 | typenames = [] of String 8 | 9 | unless node.type_vars.nil? 10 | typenames += node.type_vars.not_nil! 11 | end 12 | 13 | ancestor = (node.superclass ? transpile node.superclass : "#{STDLIB_NAMESPACE}Object") 14 | ancestor = "public #{ancestor}" 15 | includes = node.search_of_type(Include).map{|x| "public virtual #{transpile x.as(Include).name}" } 16 | 17 | if node.name.names.size == 1 18 | if @in_class 19 | warning Warning::NESTED do 20 | warn "Forward declaration of class #{unit_id} is inside another class. This may cause severe issues",node,nil,@current_filename 21 | end 22 | @classes[tr_uid "#{(@current_namespace+@current_class).join("::")}"].line "class #{translate_name node.name.names.first}" 23 | else 24 | @forward_decl_classes.line "class #{translate_name node.name.names.first}" 25 | end 26 | else 27 | warning Warning::LONG_PATH do 28 | warn "Declaring a class with path containing more than 1 name. Ask developer to rewrite it using nested classes and modules",node,nil,@current_filename 29 | end 30 | target_id = tr_uid (@current_namespace+@current_class+node.name.names[1..-1]).join("::") 31 | target_type = search_unit_type target_id 32 | case target_type 33 | when :namespace 34 | if node.name.global? 35 | @forward_decl_classes.block "namespace #{node.name.names[1..-1].join("::")}" do 36 | @forward_decl_classes.line "class #{translate_name node.name.names.last}" 37 | end 38 | else 39 | @forward_decl_classes.block "namespace #{target_id}" do 40 | @forward_decl_classes.line "class #{translate_name node.name.names.last}" 41 | end 42 | end 43 | when :class || :class_module 44 | if @classes.has_key? target_id 45 | if typenames.empty? 46 | @classes[target_id].line "class #{translate_name node.name.names.last}" 47 | else 48 | @classes[target_id].line "template< #{typenames.map{|x| "typename #{x}"}.join(", ")} > class #{get_name node.name}" 49 | end 50 | else 51 | warning Warning::FD_SKIP do 52 | warn "Cannot forward-declare class #{node.name.names.join("::")} as its parent class is not defined yet" 53 | end 54 | end 55 | else 56 | warning Warning::FD_SKIP|Warning::KIND_INF do 57 | warn "Cannot infer kind of parent unit of class #{node.name.names.join("::")}, skipping forward declaration",node,nil,@current_filename 58 | end 59 | end 60 | end 61 | 62 | typenames += @typenames.flatten 63 | 64 | 65 | inherits = ([ancestor]+includes).join(", ") 66 | 67 | @classes[unit_id] ||= ClassData.new unit_id 68 | if @classes[unit_id].header.strip.empty? 69 | if typenames.empty? 70 | @classes[unit_id].header = "class #{unit_id} : #{inherits}" 71 | else 72 | @classes[unit_id].header = "template< #{typenames.map{|x| "typename #{x}"}.join(", ")} > class #{raw_uid} : #{inherits}" 73 | end 74 | else 75 | warning Warning::REDECLARATION do 76 | warn "Class #{get_name node.name} is already declared" 77 | end 78 | end 79 | 80 | @unit_stack << {id: unit_id, type: :class} 81 | old_in_class,@in_class = @in_class, true 82 | @current_class += node.name.names 83 | (node.name.names.size-1).times{@typenames << [] of String} 84 | @typenames << typenames 85 | @classes[unit_id].line transpile node.body 86 | @in_class = old_in_class 87 | node.name.names.size.times do 88 | @current_class.pop 89 | @typenames.pop 90 | end 91 | @attribute_set.each do |attr| 92 | if attr.name == "Header" 93 | next if attr.named_args.nil? 94 | attr.named_args.not_nil!.each do |arg| 95 | case arg.name 96 | when "local" 97 | @classes[unit_id].c_deps << %("#{arg.value.as(StringLiteral).value}") 98 | when "system" || "global" 99 | @classes[unit_id].c_deps << "<#{arg.value.as(StringLiteral).value}>" 100 | else 101 | raise Error.new("Wrong type of header : #{arg.name}",node,nil,@current_filename) 102 | end 103 | end 104 | end 105 | end 106 | @unit_stack.pop 107 | "" 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /src/cppize/nodes/class_var.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node ClassVar do 4 | if @unit_stack.last[:type] == :class_def 5 | (should_return? ? "return " : "") + node.name 6 | else 7 | "static #{translate_name node.name}" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/def.cr: -------------------------------------------------------------------------------- 1 | require "../lines" 2 | 3 | module Cppize 4 | class Transpiler 5 | @template_defs = [] of String 6 | 7 | ARG_TYPE_PREFIX = "_T_" 8 | 9 | register_node Def do 10 | Lines.new(@failsafe) do |l| 11 | typenames = [] of String 12 | 13 | typenames += node.args.select{|x| !x.restriction}.map{|x| ARG_TYPE_PREFIX + x.name} 14 | _args = [] of String 15 | node.args.each_with_index do |arg,i| 16 | if node.splat_index && node.splat_index.not_nil! == i 17 | _args << transpile(arg,:tuple) 18 | else 19 | _args << transpile arg 20 | end 21 | end 22 | args = _args.join(", ") 23 | 24 | @scopes << Scope.new if @scopes.empty? 25 | node.args.each do |arg| 26 | @scopes.first[arg.name] = {symbol_type: :object, value: arg} 27 | end 28 | 29 | if node.block_arg 30 | block = node.block_arg.not_nil! 31 | typenames << ARG_TYPE_PREFIX + block.name + "_ret" 32 | typenames << "... " + ARG_TYPE_PREFIX + block.name + "_args" 33 | pref = ARG_TYPE_PREFIX 34 | b_type = "#{pref}#{block.name}_ret, #{pref}#{block.name}_args..." 35 | arg_str = "#{args.empty? ? "" : ","} #{STDLIB_NAMESPACE}Proc<#{b_type}> #{block.name}" 36 | args += arg_str 37 | @scopes.first[block.name] = {symbol_type: :object, value: block} 38 | end 39 | 40 | def_type = (node.return_type ? transpile node.return_type : "auto") 41 | 42 | _marr = [] of String 43 | _marr << "static" if node.receiver.is_a?(Self) 44 | if options.has_key?("all-virtual") || @attribute_set.map(&.name).includes?("Virtual") || node.abstract? 45 | _marr << "virtual" 46 | end 47 | 48 | modifiers = _marr.join(" ") 49 | modifiers += " " unless _marr.empty? 50 | common_signature = "#{translate_name node.name}(#{args})" 51 | local_template = (typenames.size > 0 ? "template<#{typenames.map{|x| "typename #{x}"}.join(", ")} > " : "") 52 | local_signature = "#{local_template}#{modifiers}#{def_type} #{common_signature}" 53 | 54 | template_name = "#{full_cid}::#{node.name}" 55 | 56 | unless node.args.all? &.restriction 57 | @template_defs << template_name unless @template_defs.includes? template_name 58 | end 59 | 60 | typenames += @typenames.flatten 61 | 62 | global_template = ((typenames.size > 0 || @template_defs.includes? template_name) ? "template<#{typenames.map{|x| "typename #{x}"}.join(", ")} > " : "") 63 | 64 | 65 | if @in_class 66 | if @current_visibility != node.visibility 67 | l.line "public:", true 68 | @current_visibility = node.visibility 69 | end 70 | l.line local_signature 71 | 72 | if @unit_stack.last[:type] == :class_module && options.has_key? "implicit-static" 73 | l.line transpile Def.new( 74 | "__static_"+node.name, 75 | node.args, 76 | Call.new( 77 | Call.new( 78 | Path.new("#{@current_class}#{@typenames.last.empty? ? "" : "<" + @typenames.last.join(", ") + " >"}"), 79 | "__new" 80 | ), 81 | node.name, 82 | node.args.map{|x| Var.new(x.name).as(ASTNode)} 83 | ), 84 | Self.new 85 | ) 86 | end 87 | 88 | global_signature = "#{global_template}#{modifiers}#{def_type} #{full_cid}::#{common_signature}" 89 | 90 | @unit_stack << {id: global_signature, type: :class_def} 91 | 92 | @defs.block global_signature do 93 | if def_type == "void" 94 | @defs.line transpile(node.body) 95 | else 96 | @defs.line transpile(node.body,:should_return) 97 | end 98 | end 99 | 100 | @unit_stack.pop 101 | 102 | else 103 | #global_signature = "#{global_template} #{modifiers} #{def_type} #{namesp}#{common_signature}" 104 | if @current_namespace.empty? 105 | @forward_decl_defs.line local_signature 106 | else 107 | @forward_decl_defs.block "namespace #{@current_namespace.join("::")}" do 108 | @forward_decl_defs.line local_signature 109 | end 110 | end 111 | 112 | l.block local_signature do 113 | if def_type == "void" 114 | l.line transpile(node.body) 115 | else 116 | l.line transpile(node.body,:should_return) 117 | end 118 | end 119 | end 120 | 121 | l.line add_def_with_splat node 122 | end.to_s 123 | end 124 | 125 | protected def add_def_with_splat(d : Def) 126 | return nil if d.splat_index.nil? 127 | typenames = [] of String 128 | 129 | typenames += d.args.select{|x| !x.restriction}.map{|x| ARG_TYPE_PREFIX + x.name} 130 | 131 | idx = d.splat_index.not_nil! 132 | 133 | args = [] of String 134 | pass_args = [] of String 135 | 136 | d.args.each_with_index do |x,i| 137 | if idx == i 138 | args << transpile(x, :splat) 139 | pass_args << "#{STDLIB_NAMESPACE}make_tuple(#{x.name}...)" 140 | else 141 | args << transpile x 142 | pass_args << translate_name x.name 143 | end 144 | end 145 | args_str = args.join(", ") 146 | 147 | def_type = (d.return_type ? transpile d.return_type : "auto") 148 | 149 | _marr = [] of String 150 | _marr << "static" if d.receiver.is_a?(Self) 151 | _marr << "virtual" if options.has_key? "all-virtual" || @attribute_set.index{|x| x.name == "Virtual" } || d.abstract? 152 | 153 | modifiers = _marr.join(" ") 154 | modifiers += " " unless _marr.empty? 155 | local_template = (typenames.size > 0 ? "template<#{typenames.map{|x| "typename #{x}"}.join(", ")} > " : "") 156 | 157 | typenames += @typenames.flatten 158 | template_name = "#{full_cid}::#{d.name}" 159 | 160 | global_template = ((typenames.size > 0 || @template_defs.includes? template_name) ? "template<#{typenames.map{|x| "typename #{x}"}.join(", ")} > " : "") 161 | 162 | local_signature = "#{local_template}#{modifiers}#{def_type} #{translate_name d.name}(#{args_str})" 163 | if @in_class 164 | global_signature = "#{global_template}#{modifiers}#{def_type} #{full_cid}::#{translate_name d.name}(#{args_str})" 165 | 166 | @defs.block global_signature do 167 | @defs.line "return #{d.name}(#{pass_args.join(", ")})" 168 | end 169 | return local_signature 170 | else 171 | if @current_namespace.empty? 172 | @forward_decl_defs.line local_signature 173 | else 174 | @forward_decl_defs.block "namespace #{@current_namespace.join("::")}" do 175 | @forward_decl_defs.line local_signature 176 | end 177 | end 178 | 179 | Lines.new @failsafe do |l| 180 | l.block(local_signature) do 181 | l.line "return #{d.name}(#{pass_args.join(", ")})" 182 | end 183 | end.to_s 184 | end 185 | end 186 | end 187 | end 188 | -------------------------------------------------------------------------------- /src/cppize/nodes/enum_def.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node EnumDef do 4 | if node.base_type 5 | raise Error.new("Only enums without base type are supported",node,nil,@current_filename) 6 | end 7 | 8 | Lines.new(@failsafe) do |l| 9 | l.line "// Generated from #{node.name}", true 10 | l.block("enum class #{transpile node.name}") do 11 | node.members.each do |member| 12 | if member.responds_to? :name 13 | l.line(member.name.to_s + ",", true) 14 | end 15 | end 16 | end 17 | end.to_s + ";" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/cppize/nodes/expressions.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | 4 | register_node Expressions do 5 | Lines.new do |l| 6 | if should_return? 7 | node.expressions[0..-2].each do |e| 8 | l.line transpile e 9 | end 10 | 11 | expr = node.expressions.last 12 | ret = "" 13 | case expr 14 | when Return || Nop || TypeDeclaration || If || While || TypeNode 15 | else 16 | ret = "return " 17 | end 18 | l.line ret + transpile(expr) 19 | else 20 | node.expressions.each do |e| 21 | l.line transpile e 22 | end 23 | end 24 | end.to_s 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/cppize/nodes/external_var.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node ExternalVar do 4 | unless node.real_name.nil? 5 | warning Warning::NAME_MISMATCH do 6 | warn "External var cannot have different identifier than its real name",node,nil,@current_filename 7 | end 8 | end 9 | 10 | "extern \"C\" #{transpile node.type_spec} #{translate_name node.name}" 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/cppize/nodes/fun_def.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node FunDef do 4 | args = node.args.map do |arg| 5 | "#{transpile arg.restriction} #{arg.name}" 6 | end.join(", ") 7 | 8 | _type = transpile node.return_type 9 | 10 | _type += "::unsafe_type" unless (_type == "void" || options.has_key?("primitive-types")) 11 | 12 | if node.name == node.real_name 13 | "extern \"C\" #{_type} #{node.real_name}(#{args})" 14 | else 15 | Lines.new @failsafe do |l| 16 | l.block "namespace __fun_def_pseudonyms" do 17 | l.line "extern \"C\" #{_type} #{node.real_name}(#{args})" 18 | end 19 | 20 | l.block "auto #{node.name}(#{args})" do 21 | l.line "return __fun_def_pseudonyms::#{node.real_name}(#{node.args.map &.name})" 22 | end 23 | end.to_s 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/cppize/nodes/global.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Global do 4 | unless @globals_list.includes? node.name 5 | raise Error.new("Global variable $#{node.name} is not declared yet",node,nil,@current_filename) 6 | end 7 | "__globals::#{translate_name node.name}" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/cppize/nodes/if.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node If do 4 | if should_return? || tr_options.includes? :ternary_if 5 | (should_return? ? "return " : "") + 6 | "((#{transpile node.cond}) ? (#{transpile node.then, :ternary_if}) : (#{transpile node.else, :ternary_if}))" 7 | else 8 | Lines.new(@failsafe) do |l| 9 | l.line nil 10 | l.block("if(#{transpile node.cond})") do 11 | l.line "#{transpile node.then}" 12 | end 13 | 14 | if node.else 15 | if node.else.is_a?(If) 16 | l.line "else #{transpile node.else}" 17 | else 18 | l.block("else") do 19 | l.line transpile node.else 20 | end 21 | end 22 | end 23 | end.to_s 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/cppize/nodes/include.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Include do 4 | if @in_class 5 | "" 6 | else 7 | "using namespace #{transpile node.name}" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/instance_var.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node InstanceVar do 4 | if @unit_stack.last[:type] == :class_def 5 | (should_return? ? "return " : "") + "this->#{translate_name node.name}" 6 | else 7 | (should_return? ? "return " : "") + translate_name node.name 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/is_a.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node IsA do 4 | "#{transpile node.obj}.is_a<#{transpile node.const} >()" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/lib_def.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node LibDef do 4 | @lib_defs.block "namespace #{node.name}" do 5 | @lib_defs.line transpile node.body 6 | end 7 | "" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/cppize/nodes/macro_id.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node MacroId do 4 | (should_return? ? "return " : "") + node.to_s 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/module_def.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | 4 | @unit_types = Hash(String,Symbol).new 5 | 6 | def search_unit_type(name : String) 7 | return @unit_types[tr_uid name]? || :undefined 8 | end 9 | 10 | def search_unit_type(name : Path) 11 | if name.global? 12 | return @unit_types[name.names.join("::")]? || :undefined 13 | end 14 | 15 | uid = tr_uid "#{@current_namespace}::#{@current_class}::#{name.names.join("::")}" 16 | return @unit_types[uid]? || :undefined 17 | end 18 | 19 | register_node ModuleDef do 20 | unit_id = tr_uid ([full_cid] + node.name.names).join("::") 21 | includes = node.search_of_type(Include) 22 | ancestors = includes.size > 0 ? ": #{includes.map { |x| "public virtual " + transpile x.as(Include).name }.join(", ")}" : ": public virtual #{STDLIB_NAMESPACE}Module" 23 | included? = false 24 | if options.has_key? "auto-module-type" 25 | included? = (@includes.count do |x| 26 | x.as(Include).name.to_s.sub(/^::/,"") == ([full_cid] + node.name.names).join("::") 27 | end) 28 | end 29 | 30 | typenames = [] of String 31 | 32 | unless node.type_vars.nil? 33 | typenames += node.type_vars.not_nil! 34 | end 35 | 36 | if typenames.empty? && !included? && includes.empty? && !@in_class 37 | Lines.new do |l| 38 | l.line nil 39 | l.block("namespace #{node.name.names.join("::")}") do 40 | @unit_stack << {id: unit_id, type: :namespace} 41 | @unit_types[unit_id] = :namespace 42 | @current_namespace += node.name.names 43 | l.line transpile node.body 44 | node.name.names.size.times{@current_namespace.pop} 45 | @unit_stack.pop 46 | end 47 | end.to_s 48 | else 49 | ancestors = (includes.empty? ? ": public #{STDLIB_NAMESPACE}Module" : ": "+includes.map{|x| "public virtual #{transpile x.as(Include).name}"}.join(", ")) 50 | target_id = if node.name.names.size == 1 51 | tr_uid full_cid 52 | else 53 | warning Warning::LONG_PATH do 54 | warn "Please use nested classes and modules instead of long paths",node,nil,@current_filename 55 | end 56 | [tr_uid(full_cid),node.name.names[1..-1]].flatten.join("::") 57 | end 58 | 59 | local_template = if typenames.empty? 60 | "" 61 | else 62 | "template< #{typenames.map{|x| "typename #{x}"}.join(", ")} > " 63 | end 64 | 65 | if @classes.has_key? target_id 66 | @classes[target_id].block "#{local_template} class #{node.name.names.last}#{ancestors}" do 67 | @current_class += node.name.names 68 | @unit_types[unit_id] = :class_module 69 | @unit_stack << {id: unit_id, type: :class_module} 70 | (node.name.names.size-1).times{@typenames << [] of String} 71 | @typenames << typenames 72 | oic,@in_class = @in_class,true 73 | @classes[target_id].line transpile node.body 74 | node.name.names.size.times do 75 | @typenames.pop 76 | @current_class.pop 77 | end 78 | @unit_stack.pop 79 | @in_class = oic 80 | end 81 | else 82 | if @in_class 83 | raise Error.new "Cannot declare module as its parent is not defined",node,nil,@current_filename 84 | else 85 | if @classes.has_key? unit_id 86 | warning Warning::REDECLARATION do 87 | warn "#{unit_id} is already defined", node,nil, @current_filename 88 | end 89 | else 90 | @classes[unit_id] ||= ClassData.new unit_id 91 | end 92 | if @classes[unit_id].header.empty? 93 | @classes[unit_id].header = "#{local_template} class #{node.name.names.last}#{ancestors}" 94 | @current_class += node.name.names 95 | (node.name.names.size-1).times{@typenames << [] of String} 96 | @typenames << typenames 97 | oic,@in_class = @in_class,true 98 | @unit_types[unit_id] = :class_module 99 | @unit_stack << {id: unit_id, type: :class_module} 100 | @classes[unit_id].line transpile node.body 101 | @in_class = oic 102 | @unit_stack.pop 103 | node.name.names.size.times do 104 | @typenames.pop 105 | @current_class.pop 106 | end 107 | 108 | else 109 | warning Warning::REDECLARATION do 110 | warn "Class #{unit_id} is already declared, skipping declaration",node,nil,@current_filename 111 | end 112 | end 113 | end 114 | end 115 | "" 116 | end 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /src/cppize/nodes/multiassign.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node MultiAssign do 4 | names = node.values.map{|x| unique_name} 5 | Lines.new do |l| 6 | names.each_with_index do |e,i| 7 | l.line "auto #{e} = #{transpile node.values[i]}" 8 | @scopes << Scope.new unless @scopes.size > 0 9 | @scopes.last[e] = {symbol_type: :object, value: Var.new(e) } 10 | end 11 | names.each_with_index do |e,i| 12 | l.line transpile Assign.new(node.targets[i], Var.new(e) ) 13 | end 14 | end.to_s 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/cppize/nodes/next.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Next do 4 | if node.exp 5 | raise Error.new("Nexts with expressions are not supported") 6 | else 7 | "continue" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/nil_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node NilLiteral do 4 | (should_return? ? "return " : "") + "#{STDLIB_NAMESPACE}NIL" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/nilable_cast.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node NilableCast do 4 | (should_return? ? "return " : "") + "#{STDLIB_NAMESPACE}nilable_cast<#{transpile node.to}>(#{transpile node.obj})" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/not.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Not do 4 | "!(#{transpile node.exp})" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/number_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node NumberLiteral do 4 | (should_return? ? "return " : "") + "#{node.value.gsub("_", "'")}_cr#{node.kind}" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/pointerof.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node PointerOf do 4 | "#{STDLIB_NAMESPACE}pointerof(#{transpile node.exp})" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/proc_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node ProcLiteral do 4 | @scopes << Scope.new if @scopes.empty? 5 | args = node.def.args.map do |arg| 6 | restr = (arg.restriction ? transpile arg.restriction : "auto") 7 | def_v = (arg.default_value ? " = #{transpile arg.default_value}" : "") 8 | @scopes.first[arg.name] = {symbol_type: :object, value: arg} 9 | "#{restr} #{arg.name}#{def_v}" 10 | end.join(", ") 11 | 12 | Lines.new do |l| 13 | l.line nil 14 | l.block("[&](#{args})") do 15 | l.line transpile(node.def.body,:should_return) 16 | end 17 | end.to_s 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/cppize/nodes/proc_notation.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node ProcNotation do 4 | if node.inputs.not_nil!.any? &.is_a?(Underscore) 5 | raise Error.new("Underscore isn't supported yet",node,nil,@current_filename) 6 | end 7 | args = node.inputs.not_nil!.map{|x| transpile x}.join(", ") 8 | "#{STDLIB_NAMESPACE}Proc< #{node.output ? transpile node.output : "void"}" + (node.inputs.not_nil!.empty? ? "" : ", "+args) + " >" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/range_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node RangeLiteral do 4 | (should_return? ? "return " : "") + 5 | "#{STDLIB_NAMESPACE}Range< decltype(#{transpile node.from}) >(#{transpile node.from},#{transpile node.to},#{node.exclusive? ? "true" : "false"})" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/cppize/nodes/regex_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | protected def escape_regex(r : String) : String 4 | escape_cpp_string r.gsub("\\","\\\\") 5 | end 6 | 7 | register_node RegexLiteral do 8 | (should_return? ? "return " : "") + "#{STDLIB_NAMESPACE}Regex(#{transpile node.value, :regex},\"#{node.options}\")" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/require.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | @library_path : Array(String) 4 | @library_path = (if options.has_key?("no-stdlib") 5 | [File.join(File.dirname(Process.executable_path || ""),"stdlib/build")] 6 | else 7 | [""] 8 | end) + (ENV["CRYSTAL_STDLIB_PATH"]? || "").split(":") 9 | 10 | @required = Array(String).new 11 | 12 | def add_library_path(p) 13 | @library_path << p 14 | end 15 | 16 | def search_file(str) 17 | @library_path.map do |path| 18 | Dir.glob(File.join(path,str)) 19 | end.select{|x| !x.empty?}.last? 20 | end 21 | 22 | register_node Require do 23 | path = node.string 24 | path += ".cr" if File.extname(path).empty? 25 | _f = [] of String 26 | if path.match(/^\.{1,2}\//) 27 | _f = Dir.glob(File.expand_path(path,File.dirname(@current_filename))) 28 | else 29 | _f = search_file path 30 | if _f.nil? 31 | raise Error.new("Cannot find #{path}!",node,nil,@current_filename) 32 | end 33 | end 34 | 35 | filename = _f.not_nil! 36 | 37 | old_filename = @current_filename 38 | str = Lines.new do |l| 39 | begin 40 | if filename.empty? 41 | raise Error.new("Cannot find #{path}") 42 | else 43 | filename.each do |f| 44 | ast = Parser.parse(File.read(f)) 45 | @includes += ast.search_of_type(Include,true).map{|x| x.as(Include?)} 46 | unless @required.includes? File.expand_path f 47 | @required << File.expand_path f 48 | l.line("// BEGIN #{f} (#{path})") 49 | @current_filename = f 50 | l.line(transpile(ast)) 51 | l.line("// END #{f}") 52 | end 53 | end 54 | end 55 | rescue ex : Error 56 | raise Error.new(ex.message || "Error opening #{path}",node,ex,@current_filename) 57 | end 58 | end.to_s 59 | @current_filename = old_filename 60 | str 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /src/cppize/nodes/return.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Return do 4 | if node.exp 5 | code = transpile node.exp, :should_return 6 | code += ";" unless code.ends_with?(";") 7 | code 8 | else 9 | "return;" 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/cppize/nodes/self.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Self do 4 | "this" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/sizeof.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node SizeOf|InstanceSizeOf do 4 | (should_return? ? "return " : "") + "#{STDLIB_NAMESPACE}sizeof(#{node.exp})" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/splat.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node Splat do 4 | "#{STDLIB_NAMESPACE}splat(#{transpile node.exp, :ternary_if})" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/cppize/nodes/string_interpolation.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | register_node StringInterpolation do 4 | if tr_options.includes? :regex 5 | (should_return? ? "return " : "")+node.expressions.map{|x| "(#{transpile x, :regex})->to_s()"}.join(" + ") 6 | else 7 | (should_return? ? "return " : "")+node.expressions.map{|x| "(#{transpile x})->to_s()"}.join(" + ") 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/cppize/nodes/string_literal.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | protected def escape_cpp_string(str : String) 4 | str.gsub(/(?") 11 | @node_stack = [] of ASTNode 12 | if cause.is_a?(self) 13 | @node_stack = cause.as(self).node_stack 14 | end 15 | 16 | unless node.nil? 17 | @node_stack.unshift(node.not_nil!) 18 | end 19 | super message, cause 20 | end 21 | 22 | def initialize(message : String, node : ASTNode? = nil, cause : Exception? = nil,@real_filename : String = "") 23 | @node_stack = [] of ASTNode 24 | super message, cause 25 | end 26 | 27 | protected def l2s(l : Location?, file : String? = nil) 28 | file ||= "unknown>" 29 | unless l.nil? 30 | unless l.not_nil!.filename.nil? 31 | file = l.not_nil!.filename.not_nil! 32 | end 33 | end 34 | if l.nil? 35 | "at #{file} [] " 36 | else 37 | "at #{file} [line #{l.not_nil!.line_number}; col #{l.not_nil!.column_number}]" 38 | end 39 | end 40 | 41 | protected def l2h(l : Location?, file : String? = nil) 42 | file ||= "" 43 | unless l.nil? 44 | unless l.not_nil!.filename.nil? 45 | _f = l.not_nil!.filename.not_nil! 46 | if _f.is_a? VirtualFile 47 | file = _f.source 48 | else 49 | file = _f.to_s 50 | end 51 | end 52 | end 53 | 54 | if l.nil? 55 | { 56 | "file" => file, 57 | "line" => "unknown", 58 | "column"=>"unknown" 59 | } 60 | else 61 | { 62 | "file" => file, 63 | "line" => l.line_number, 64 | "column"=>l.column_number 65 | } 66 | end 67 | end 68 | 69 | def to_s(trace : Bool = false) 70 | str = message.to_s + "\n" 71 | if node_stack.size > 0 72 | str += "\n\t" + node_stack.map do |x| 73 | s = "Caused by node #{x.class.name} #{l2s x.location,@real_filename}" 74 | s += " (End at #{l2s x.end_location,@real_filename})" 75 | end.join("\n\t") + "\n" 76 | end 77 | 78 | if trace 79 | str += "\n\t" + backtrace.join("\n\t") 80 | end 81 | 82 | str 83 | end 84 | 85 | def to_h 86 | { 87 | message: self.message, 88 | backtrace: self.backtrace, 89 | nodes: node_stack.map do |x| 90 | { 91 | node_type: x.class.name, 92 | node_start: l2h(x.location, @real_filename), 93 | node_end: l2h(x.end_location, @real_filename), 94 | } 95 | end, 96 | filename: @real_filename 97 | }.to_h 98 | end 99 | 100 | def to_json 101 | to_h.to_json 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /src/cppize/unique_name.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | class Transpiler 3 | @unique_counter = 0u64 4 | def unique_name 5 | @unique_counter += 1 6 | "__temp_#{@unique_counter}_" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/cppize/version.cr: -------------------------------------------------------------------------------- 1 | module Cppize 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /src/transpiler.cr: -------------------------------------------------------------------------------- 1 | require "./cppize" 2 | require "./colorize" 3 | 4 | transpiler = Cppize::Transpiler.new false 5 | code = "" 6 | input = "" 7 | use_stdin = false 8 | output = nil 9 | use_stdout = false 10 | do_not_colorize = false 11 | verbose = false 12 | 13 | OptionParser.parse! do |opts| 14 | opts.banner = "Cppize v#{Cppize::VERSION}\n\nAvailable features :\n\t#{Cppize::Transpiler.features_list.map{|x| "-f#{x}"}.join("\n\t")} " + 15 | "\n\nAvailable warning types :\n\t#{Cppize.warning_list.map{|x| "-W#{x}"}.join("\n\t")}" 16 | 17 | opts.on("-v", "--version", "Prints version and exits") do 18 | puts "#{Cppize::VERSION}" 19 | exit 20 | end 21 | 22 | opts.on("-D SYM", "--define SYM", "Defines preprocessor symbol SYM") do |s| 23 | if transpiler.use_preprocessor_defs? 24 | code += "#define #{s}\n" 25 | else 26 | transpiler.defines << s 27 | end 28 | end 29 | 30 | opts.on("-d", "--preprocessor-defines", "Tells transpiler to use C preprocessor instead of internal conditional compilation system") do 31 | transpiler.use_preprocessor_defs = true 32 | end 33 | 34 | opts.on("-f FEATURE", "--feature FEATURE", "Tells transpiler to use feature") do |f| 35 | fparts = f.split("=", 2) 36 | transpiler.options[fparts.first] = (fparts.size > 1 ? fparts[1] : "") 37 | end 38 | 39 | opts.on("-o OUT", "--output OUT", "Sets output file") do |o| 40 | output = o 41 | use_stdout = (o.strip == "-") 42 | end 43 | 44 | opts.on("-s", "--use-stdin", "Reads code from stdin") do 45 | use_stdin = true 46 | input = "" 47 | end 48 | 49 | opts.on("-h", "--help", "Prints this help and exits") do 50 | puts opts 51 | exit 52 | end 53 | 54 | opts.on("-M","--monochrome","Do not colorize errors and warnings") do 55 | do_not_colorize = true 56 | end 57 | 58 | opts.on("-WWARNING","--warning WARNING","Enable warnings of given type") do |w| 59 | if w.strip == "all" 60 | transpiler.enabled_warnings = 0xFFFFFFFFFFFFFFFF 61 | elsif w.strip == "none" 62 | transpiler.enabled_warnings = 0u64 63 | else 64 | transpiler.enabled_warnings |= Cppize.warning_from_string w.strip.downcase 65 | end 66 | end 67 | 68 | opts.on("-V","--verbose","Run verbosely") do 69 | verbose = true 70 | end 71 | end 72 | 73 | if !do_not_colorize && !use_stdout 74 | transpiler.on_error do |e| 75 | puts e.to_s.colorize.fore(:red) 76 | exit 1 77 | end 78 | 79 | transpiler.on_warning do |e| 80 | if verbose 81 | puts e.to_s.colorize.fore(:yellow) 82 | else 83 | puts e.message.colorize.fore(:yellow) 84 | end 85 | end 86 | end 87 | 88 | unless use_stdin 89 | begin 90 | input = ARGV.shift 91 | rescue ex : IndexError 92 | raise Cppize::Transpiler::Error.new("No input specified") unless input 93 | end 94 | end 95 | 96 | def gets_to_end 97 | string = "" 98 | while line = gets 99 | string += line 100 | end 101 | string 102 | end 103 | 104 | input_c = "" 105 | if use_stdin 106 | input_c = gets_to_end 107 | else 108 | input_c = File.read(input) 109 | end 110 | 111 | unless output 112 | if use_stdin 113 | use_stdout = true 114 | else 115 | output = input.sub(/\.(cpp\.)?cr^/, ".gen.cpp") 116 | end 117 | end 118 | 119 | transpiler.post_initialize! 120 | 121 | code += transpiler.parse_and_transpile(input_c,input) 122 | 123 | if use_stdout 124 | puts code 125 | else 126 | File.open(output.to_s, "w") { |f| f.puts(code) } 127 | end 128 | -------------------------------------------------------------------------------- /test/req/abs.cr: -------------------------------------------------------------------------------- 1 | abstract class Abs 2 | @[Virtual] 3 | abstract def lol 4 | end 5 | -------------------------------------------------------------------------------- /test/req/enum.cr: -------------------------------------------------------------------------------- 1 | enum TestEnum 2 | TEST 3 | UNTEST 4 | UNITTEST 5 | end 6 | -------------------------------------------------------------------------------- /test/req/lambda_def.cr: -------------------------------------------------------------------------------- 1 | def lmethod(a,b,c,&block) 2 | block.call(a,b,c) 3 | end 4 | 5 | def cmethod 6 | lmethod do |a,b,c| 7 | "#{a+b}#{c}" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/req/module.cr: -------------------------------------------------------------------------------- 1 | module MyModule 2 | module TemplateM(A,B,C) 3 | class MyClass 4 | @class_var : A 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/req/regex.cr: -------------------------------------------------------------------------------- 1 | def regex_test(a : String) 2 | a.match(/a[b-c]\d/i) 3 | end 4 | -------------------------------------------------------------------------------- /test/required.cr: -------------------------------------------------------------------------------- 1 | def lol 2 | 0 3 | end 4 | -------------------------------------------------------------------------------- /test/test.cr: -------------------------------------------------------------------------------- 1 | require "./req/*.cr" 2 | require "./required.cr" 3 | 4 | __cpp__ "void test()" do 5 | return 0 6 | end 7 | --------------------------------------------------------------------------------