Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions spec/bindgen/processor/extern_c_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ describe Bindgen::Processor::ExternC do

doc = Bindgen::Parser::Document.new
db = Bindgen::TypeDatabase.new(Bindgen::TypeDatabase::Configuration.new, "boehmgc-cpp")
db.add("HasToCpp", to_cpp: "TO_CPP", copy_structure: true)
db.add("HasFromCpp", from_cpp: "FROM_CPP", copy_structure: true)
db.add("HasToCpp", to_cpp: Bindgen::Template.from_string("TO_CPP"), copy_structure: true)
db.add("HasFromCpp", from_cpp: Bindgen::Template.from_string("FROM_CPP"), copy_structure: true)
db.add("PassByValue", pass_by: Bindgen::TypeDatabase::PassBy::Reference)

extern_c_void_func = Bindgen::Parser::Method.new(
Expand Down
95 changes: 95 additions & 0 deletions spec/bindgen/template_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
require "../spec_helper"

describe "Template" do
describe ".from_string" do
it "constructs the no-op template from nil" do
Bindgen::Template.from_string(nil).should be_a(Bindgen::Template::None)
end

it "can construct a simple template from a string" do
Bindgen::Template.from_string("%x", simple: true).should eq(
Bindgen::Template::Basic.new("%x", simple: true))
end

it "can construct a full template from a string" do
expected = Bindgen::Template::Basic.new("%x")
Bindgen::Template.from_string("%x").should eq(expected)
Bindgen::Template.from_string("%x", simple: false).should eq(expected)
end
end

describe "#no_op?" do
it "returns true for the no-op template" do
Bindgen::Template::None.new.no_op?.should be_true
end

it "returns true for string templates with only `%`" do
Bindgen::Template::Basic.new("%").no_op?.should be_true
Bindgen::Template::Basic.new("%", simple: true).no_op?.should be_true
end

it "returns false for any other templates" do
conversion1 = Bindgen::Template::Basic.new("%x")
conversion2 = Bindgen::Template::Basic.new("%x", simple: true)
conversion3 = Bindgen::Template::Sequence.new(conversion1, conversion2)

conversion1.no_op?.should be_false
conversion2.no_op?.should be_false
conversion3.no_op?.should be_false
end
end

describe "#followed_by" do
it "composes two templates" do
conversion1 = Bindgen::Template::Basic.new("%x")
conversion2 = Bindgen::Template::Basic.new("%y")
conversion3 = Bindgen::Template::Sequence.new(conversion1, conversion2)

conversion1.followed_by(conversion2).should eq(conversion3)
end

it "is #no_op? only when both templates are #no_op?" do
op = Bindgen::Template::Basic.new("%x")
no = Bindgen::Template::None.new

op.followed_by(op).no_op?.should be_false
op.followed_by(no).no_op?.should be_false
no.followed_by(op).no_op?.should be_false
no.followed_by(no).no_op?.should be_true
end
end

describe "None#template" do
it "returns the code unmodified" do
Bindgen::Template::None.new.template("123").should eq("123")
end
end

describe "Basic#template" do
it "substitutes % with the supplied code" do
Bindgen::Template::Basic.new("a%b%c").template("123").should eq("a123b123c")
end

it "may access environment variables if it is a full template" do
key, value = ENV.first
Bindgen::Template::Basic.new("%{#{key}}%").template("123").should eq("123#{value}123")
end

it "substitutes %% with % if it is a simple template" do
Bindgen::Template::Basic.new("%%a%%%b%%%%c", simple: true).template("123").should eq("%a%123b%%c")
end
end

describe "Sequence#template" do
it "composes multiple templates" do
a_conv = Bindgen::Template::Basic.new("%_a")
b_conv = Bindgen::Template::Basic.new("%_b")

conversion = Bindgen::Template::Sequence.new(a_conv, b_conv)
conversion.template("c").should eq("c_a_b")

conversion = Bindgen::Template::Sequence.new(a_conv, a_conv, a_conv, b_conv, b_conv, a_conv)
conversion.template("c").should eq("c_a_a_a_b_b_a")
end
end
end
21 changes: 11 additions & 10 deletions src/bindgen/call.cr
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,21 @@ module Bindgen

# Call result type configuration.
class Result < Expression
# Conversion template (`Util.template`) to get the data out of the method,
# ready to be returned back.
getter conversion : String?
# Conversion template to get the data out of the method, ready to be
# returned back.
getter conversion : Template::Base

def initialize(@type, @type_name, @reference, @pointer, @conversion, @nilable = false)
def initialize(@type, @type_name, @reference, @pointer, @conversion = Template::None.new, @nilable = false)
end

# Applies the result's conversion template to a piece of code.
def apply_conversion(code : String) : String
conversion.template(code)
end

# Converts the result into an argument of *name*.
def to_argument(name : String, default = nil) : Argument
call = name
templ = @conversion # Conversion template
call = Util.template(templ, name) if templ
call = apply_conversion(name)

Argument.new(
type: @type,
Expand All @@ -67,9 +70,7 @@ module Bindgen
class ProcResult < Result
# Converts the result into an argument of *name*.
def to_argument(name : String, block = false) : Argument
call = name
templ = @conversion # Conversion template
call = Util.template(templ, name) if templ
call = apply_conversion(name)

ProcArgument.new(
block: block,
Expand Down
7 changes: 1 addition & 6 deletions src/bindgen/call_builder/cpp_call.cr
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ module Bindgen
def to_code(call : Call, _platform : Graph::Platform) : String
pass_args = call.arguments.map(&.call).join(", ")
code = %[#{call.name}(#{pass_args})]

if templ = call.result.conversion
code = Util.template(templ, code)
end

code
call.result.apply_conversion(code)
end
end
end
Expand Down
7 changes: 1 addition & 6 deletions src/bindgen/call_builder/cpp_qobject_connect.cr
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ module Bindgen

inner = @proc.body.to_code(@proc, platform)
code = %[QObject::connect(_self_, (#{ptr})&#{call.name}, [_proc_](#{lambda_args}){ #{inner}; })]

if templ = call.result.conversion
code = Util.template(templ, code)
end

code
call.result.apply_conversion(code)
end
end
end
Expand Down
7 changes: 1 addition & 6 deletions src/bindgen/call_builder/cpp_to_crystal_proc.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,7 @@ module Bindgen
def to_code(call : Call, platform : Graph::Platform) : String
pass_args = call.arguments.map(&.call).join(", ")
code = %[#{call.name}(#{pass_args})]

if templ = call.result.conversion
code = Util.template(templ, code)
end

code
call.result.apply_conversion(code)
end
end
end
Expand Down
6 changes: 1 addition & 5 deletions src/bindgen/call_builder/crystal_binding.cr
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@ module Bindgen
post = @post_hook

pass_args = call.arguments.map(&.call).join(", ")
code = %[Binding.#{call.name}(#{pass_args})]

if templ = call.result.conversion
code = Util.template(templ, code)
end
code = call.result.apply_conversion %[Binding.#{call.name}(#{pass_args})]

# Support for pre- and post hooks.
String.build do |b|
Expand Down
32 changes: 15 additions & 17 deletions src/bindgen/call_builder/crystal_from_cpp.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ module Bindgen
end

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

Expand All @@ -26,32 +31,25 @@ module Bindgen
name: method.crystal_name,
result: result,
arguments: arguments,
body: Body.new(@db, receiver),
body: Body.new(@db, receiver, do_block),
)
end

# Combines the results *outer* to *inner*.
private def combine_result(outer, inner)
conv_out = outer.conversion
conv_in = inner.conversion

if conv_out && conv_in
conversion = Util.template(conv_out, conv_in)
else
conversion = conv_out || conv_in
end
combined_conversion = inner.conversion.followed_by(outer.conversion)
Comment thread
HertzDevil marked this conversation as resolved.

Call::Result.new(
type: outer.type,
type_name: outer.type_name,
pointer: outer.pointer,
reference: outer.reference,
conversion: conversion,
conversion: combined_conversion,
)
end

class Body < Call::Body
def initialize(@db : TypeDatabase, @receiver : String)
def initialize(@db : TypeDatabase, @receiver : String, @do_block : Bool)
end

def to_code(call : Call, platform : Graph::Platform) : String
Expand All @@ -67,12 +65,12 @@ module Bindgen
block_arg_names = call.arguments.map(&.name).join(", ")
block_args = "|#{block_arg_names}|" unless pass_args.empty?

body = "#{@receiver}.#{call.name}(#{pass_args})"
if templ = call.result.conversion
body = Util.template(templ, body)
body = call.result.apply_conversion "#{@receiver}.#{call.name}(#{pass_args})"
if @do_block
%[Proc(#{proc_args}).new do #{block_args} #{body} end]
else
%[Proc(#{proc_args}).new{#{block_args} #{body} }]
end

%[Proc(#{proc_args}).new{#{block_args} #{body} }]
end
end
end
Expand Down
1 change: 0 additions & 1 deletion src/bindgen/call_builder/crystal_superclass.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ module Bindgen
type_name: superklass.name,
reference: false,
pointer: 0,
conversion: nil,
)

Call.new(
Expand Down
1 change: 0 additions & 1 deletion src/bindgen/call_builder/crystal_superclass_init.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ module Bindgen
type_name: method.name,
reference: false,
pointer: 0,
conversion: nil,
)

Call.new(
Expand Down
6 changes: 1 addition & 5 deletions src/bindgen/call_builder/crystal_wrapper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ module Bindgen

class MethodBody < Body
def encapsulate(call, code)
if templ = call.result.conversion
Util.template(templ, code)
else
code
end
call.result.apply_conversion(code)
end
end

Expand Down
18 changes: 10 additions & 8 deletions src/bindgen/cpp/cookbook.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ module Bindgen
# setting. See `Configuration#platform` for the code, and `TEMPLATE.yml`
# for user-facing documentation.
#
# The functions all return a templating string, to be used with
# `Util.template`. If no conversion is necessary, they return `nil`.
# The functions all return a `Template::Basic` when conversion is
# necessary, and `Template::None` otherwise.
abstract class Cookbook
# Finds and creates a `Cookbook` by name.
def self.create_by_name(name) : Cookbook
Expand All @@ -27,10 +27,10 @@ module Bindgen
end
end

# Finds the template (if any) to pass *type* as-is as *pass_by*. The
# returned template takes an expression returning something of *type* and
# turns it into something that can be *pass_by*'d on.
def find(type : Parser::Type, pass_by : TypeDatabase::PassBy) : String?
# Finds the template to pass *type* as-is as *pass_by*. The returned
# template takes an expression returning something of *type* and turns it
# into something that can be *pass_by*'d on.
def find(type : Parser::Type, pass_by : TypeDatabase::PassBy) : Template::Base
is_ref = type.reference?
is_ptr = !is_ref && type.pointer > 0

Expand All @@ -39,8 +39,8 @@ module Bindgen

# Same, but provides an override of *type*s reference and pointer
# qualification.
def find(base_name : String, is_reference, is_pointer, pass_by : TypeDatabase::PassBy) : String?
case pass_by
def find(base_name : String, is_reference, is_pointer, pass_by : TypeDatabase::PassBy) : Template::Base
template_string = case pass_by
when .original?
nil # No conversion required.
when .reference?
Expand Down Expand Up @@ -68,6 +68,8 @@ module Bindgen
nil
end
end

Template.from_string(template_string, simple: true)
end

# Provides a template to convert a value to a pointer.
Expand Down
11 changes: 7 additions & 4 deletions src/bindgen/cpp/pass.cr
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,16 @@ module Bindgen
ptr = 0
end

template = Template::None.new

if rules = @db[type]?
template = rules.to_cpp
type_name = rules.cpp_type || type_name
pass_by = rules.pass_by unless rules.pass_by.original?
is_ref, ptr = reconfigure_pass_type(pass_by, is_ref, ptr)
end

if template.nil?
if template.no_op?
pass_by = type_config_to_pass_by(is_ref, ptr) if pass_by.original?
template = conversion_template(pass_by, type, type_name)
end
Expand Down Expand Up @@ -139,14 +141,16 @@ module Bindgen
pass_by = TypeDatabase::PassBy::Pointer
end

template = Template::None.new

if rules = @db[type]?
template = rules.from_cpp
type_name = rules.cpp_type || type_name
pass_by = rules.pass_by unless rules.pass_by.original?
is_ref, ptr = reconfigure_pass_type(pass_by, is_ref, ptr)
end

if template.nil?
if template.no_op?
pass_by = type_config_to_pass_by(is_ref, ptr) if pass_by.original?
template = conversion_template(pass_by, type, type_name)
end
Expand Down Expand Up @@ -196,7 +200,7 @@ module Bindgen

# Finds the conversion template to go from *type* to the desired target
# type configuration.
private def conversion_template(pass_by, type, type_name) : String?
private def conversion_template(pass_by, type, type_name) : Template::Base
original_ref = type.reference?
original_ptr = type_pointer_depth(type) > 0

Expand All @@ -213,7 +217,6 @@ module Bindgen
type_name: type_name,
reference: type.reference?,
pointer: type_pointer_depth(type),
conversion: nil,
)
end
end
Expand Down
Loading