├── .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 | 
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 |
--------------------------------------------------------------------------------