Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.

Commit d76516d

Browse files
HertzDevilPapierkorb
authored andcommitted
bindgen/template/*, fix #no_op?, do_block doc
1 parent cee676d commit d76516d

8 files changed

Lines changed: 151 additions & 111 deletions

File tree

spec/bindgen/template_spec.cr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ describe "Template" do
2323
Bindgen::Template::None.new.no_op?.should be_true
2424
end
2525

26+
it "returns true for string templates with only `%`" do
27+
Bindgen::Template::Basic.new("%").no_op?.should be_true
28+
Bindgen::Template::Basic.new("%", simple: true).no_op?.should be_true
29+
end
30+
2631
it "returns false for any other templates" do
2732
conversion1 = Bindgen::Template::Basic.new("%x")
2833
conversion2 = Bindgen::Template::Basic.new("%x", simple: true)

src/bindgen/call_builder/crystal_from_cpp.cr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ module Bindgen
66
end
77

88
# Calls the *method*, using the *proc_name* to call-through to Crystal.
9+
#
10+
# If `do_block` is true, the generated `Proc` expression will use
11+
# `do ... end` instead of `{ ... }`. This allows embedding the code body
12+
# inside a string conversion template, without the code block being
13+
# interpreted as an environment variable (see also `Template::Basic`).
914
def build(method : Parser::Method, receiver = "self", do_block = false) : Call
1015
pass = Crystal::Pass.new(@db)
1116
argument = Crystal::Argument.new(@db)

src/bindgen/template.cr

Lines changed: 2 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require "./template/*"
2+
13
module Bindgen
24
# Conversion templates for `Call::Result`. They govern how a call result's
35
# code should be transformed to become usable under a certain platform.
@@ -12,116 +14,5 @@ module Bindgen
1214
Basic.new(pattern, simple: simple)
1315
end
1416
end
15-
16-
# Base class of the templates.
17-
abstract class Base
18-
# Performs a template substitution.
19-
abstract def template(code : String) : String
20-
21-
# Is this a no-operation template?
22-
def no_op?
23-
{{ @type == Bindgen::Template::None }}
24-
end
25-
26-
# Combines two templates into one. The *other* template is run after the
27-
# current template.
28-
def followed_by(other : Base) : Base
29-
return other if no_op?
30-
return self if other.no_op?
31-
Sequence.new(self, other)
32-
end
33-
end
34-
35-
# The no-op template that returns *code* unmodified.
36-
class None < Base
37-
def template(code) : String
38-
code
39-
end
40-
41-
def ==(_other : self)
42-
true
43-
end
44-
end
45-
46-
# The default template. Uses `Util.template` to substitute *code* into the
47-
# given *pattern*.
48-
#
49-
# If *simple* is given, the template will only use a subset of the features;
50-
# `%` performs substitution, whereas the escape sequence `%%` outputs a
51-
# literal percent sign.
52-
class Basic < Base
53-
def initialize(@pattern : String, @simple = false)
54-
end
55-
56-
def template(code) : String
57-
if @simple
58-
@pattern.gsub(/%+/) do |m|
59-
literals = "%" * (m.size // 2)
60-
out_code = code if m.size % 2 == 1
61-
"#{literals}#{out_code}"
62-
end
63-
else
64-
Util.template(@pattern, code)
65-
end
66-
end
67-
68-
def_equals @pattern, @simple
69-
end
70-
71-
# Compound template which runs *code* through multiple child templater, in
72-
# the order they are given in the constructor.
73-
class Sequence < Base
74-
getter children = Array(Base).new
75-
76-
def initialize(*conversions : Base)
77-
conversions.each do |conversion|
78-
# Flatten `Sequence` templates automatically.
79-
if conversion.is_a?(Sequence)
80-
@children.concat(conversion.children)
81-
else
82-
@children << conversion
83-
end
84-
end
85-
end
86-
87-
def template(code) : String
88-
@children.each do |conversion|
89-
code = conversion.template(code)
90-
end
91-
92-
code
93-
end
94-
95-
def_equals @children
96-
end
97-
98-
# Template to transform `Proc`s for Crystal wrapper types into `Proc`
99-
# expressions for binding types.
100-
class ProcFromWrapper < Base
101-
def initialize(@proc_type : Parser::Type, @db : TypeDatabase)
102-
end
103-
104-
def template(code) : String
105-
args = @proc_type.template.not_nil!.arguments
106-
func_args = args[1..-1].map_with_index do |type, i|
107-
Parser::Argument.new("arg#{i}", type)
108-
end
109-
proc_call = Parser::Method.build(
110-
name: "call",
111-
return_type: args.first,
112-
arguments: func_args,
113-
class_name: "Proc",
114-
)
115-
116-
# The templated code shouldn't contain braces, since an outer template
117-
# that comes from a config file might treat the code block as an
118-
# environment variable.
119-
builder = CallBuilder::CrystalFromCpp.new(@db)
120-
call = builder.build(proc_call, receiver: "_proc_", do_block: true)
121-
call.body.to_code(call, Graph::Platform::Crystal)
122-
end
123-
124-
def_equals @proc_type, @db
125-
end
12617
end
12718
end

src/bindgen/template/base.cr

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module Bindgen
2+
module Template
3+
# Base class of the templates.
4+
abstract class Base
5+
# Performs a template substitution.
6+
abstract def template(code : String) : String
7+
8+
# Is this a no-operation template?
9+
abstract def no_op? : Bool
10+
11+
# Combines two templates into one. The *other* template is run after the
12+
# current template.
13+
def followed_by(other : Base) : Base
14+
return other if no_op?
15+
return self if other.no_op?
16+
Sequence.new(self, other)
17+
end
18+
end
19+
end
20+
end

src/bindgen/template/basic.cr

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module Bindgen
2+
module Template
3+
# The default template. Uses `Util.template` to substitute *code* into the
4+
# given *pattern*.
5+
#
6+
# If *simple* is given, the template will only use a subset of the features;
7+
# `%` performs substitution, whereas the escape sequence `%%` outputs a
8+
# literal percent sign.
9+
class Basic < Base
10+
def initialize(@pattern : String, @simple = false)
11+
end
12+
13+
def no_op? : Bool
14+
@pattern == "%"
15+
end
16+
17+
def template(code) : String
18+
if @simple
19+
@pattern.gsub(/%+/) do |m|
20+
literals = "%" * (m.size // 2)
21+
out_code = code if m.size % 2 == 1
22+
"#{literals}#{out_code}"
23+
end
24+
else
25+
Util.template(@pattern, code)
26+
end
27+
end
28+
29+
def_equals @pattern, @simple
30+
end
31+
end
32+
end

src/bindgen/template/none.cr

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module Bindgen
2+
module Template
3+
# The no-op template that returns *code* unmodified.
4+
class None < Base
5+
def no_op? : Bool
6+
true
7+
end
8+
9+
def template(code) : String
10+
code
11+
end
12+
13+
def ==(_other : self)
14+
true
15+
end
16+
end
17+
end
18+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module Bindgen
2+
module Template
3+
# Custom template to transform `Proc`s for Crystal wrapper types into `Proc`
4+
# expressions for binding types.
5+
class ProcFromWrapper < Base
6+
def initialize(@proc_type : Parser::Type, @db : TypeDatabase)
7+
end
8+
9+
def no_op? : Bool
10+
false
11+
end
12+
13+
def template(code) : String
14+
args = @proc_type.template.not_nil!.arguments
15+
func_args = args[1..-1].map_with_index do |type, i|
16+
Parser::Argument.new("arg#{i}", type)
17+
end
18+
proc_call = Parser::Method.build(
19+
name: "call",
20+
return_type: args.first,
21+
arguments: func_args,
22+
class_name: "Proc",
23+
)
24+
25+
builder = CallBuilder::CrystalFromCpp.new(@db)
26+
call = builder.build(proc_call, receiver: "_proc_", do_block: true)
27+
call.body.to_code(call, Graph::Platform::Crystal)
28+
end
29+
30+
def_equals @proc_type, @db
31+
end
32+
end
33+
end

src/bindgen/template/sequence.cr

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module Bindgen
2+
module Template
3+
# Compound template which runs *code* through multiple child templates, in
4+
# the order they are given in the constructor.
5+
class Sequence < Base
6+
getter children = Array(Base).new
7+
getter? no_op : Bool = true
8+
9+
def initialize(*conversions : Base)
10+
conversions.each do |conversion|
11+
# Flatten `Sequence` templates automatically.
12+
if conversion.is_a?(Sequence)
13+
@children.concat(conversion.children)
14+
else
15+
@children << conversion
16+
end
17+
18+
# `Sequence` templates returned by `#followed_by` are by construction
19+
# never no-op, but we'll compute this in case `Sequence` is manually
20+
# created.
21+
@no_op &&= conversion.no_op?
22+
end
23+
end
24+
25+
def template(code) : String
26+
@children.each do |conversion|
27+
code = conversion.template(code)
28+
end
29+
30+
code
31+
end
32+
33+
def_equals @children
34+
end
35+
end
36+
end

0 commit comments

Comments
 (0)