From 900cc443a03351d19803ade9b1f9495aa8c30a81 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Aug 2020 20:01:00 +0800 Subject: [PATCH 1/7] Call::Result#apply_conversion --- src/bindgen/call.cr | 23 ++++++++++++------- src/bindgen/call_builder/cpp_call.cr | 7 +----- .../call_builder/cpp_qobject_connect.cr | 7 +----- .../call_builder/cpp_to_crystal_proc.cr | 7 +----- src/bindgen/call_builder/crystal_binding.cr | 6 +---- src/bindgen/call_builder/crystal_from_cpp.cr | 6 +---- src/bindgen/call_builder/crystal_wrapper.cr | 6 +---- src/bindgen/processor/instance_properties.cr | 14 ++--------- src/bindgen/util.cr | 4 ++-- 9 files changed, 25 insertions(+), 55 deletions(-) diff --git a/src/bindgen/call.cr b/src/bindgen/call.cr index 99f1431..750b1ac 100644 --- a/src/bindgen/call.cr +++ b/src/bindgen/call.cr @@ -37,18 +37,27 @@ 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. + # Conversion template to get the data out of the method, ready to be + # returned back. getter conversion : String? def initialize(@type, @type_name, @reference, @pointer, @conversion, @nilable = false) end + # Applies the result's conversion template to a piece of code, if a + # templater is present. + def apply_conversion(code : String) : String + case templ = conversion + when String + Util.template(templ, code) + else + code + end + 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, @@ -67,9 +76,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, diff --git a/src/bindgen/call_builder/cpp_call.cr b/src/bindgen/call_builder/cpp_call.cr index a4ea7f8..79252b1 100644 --- a/src/bindgen/call_builder/cpp_call.cr +++ b/src/bindgen/call_builder/cpp_call.cr @@ -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 diff --git a/src/bindgen/call_builder/cpp_qobject_connect.cr b/src/bindgen/call_builder/cpp_qobject_connect.cr index ed0078f..5d8b17b 100644 --- a/src/bindgen/call_builder/cpp_qobject_connect.cr +++ b/src/bindgen/call_builder/cpp_qobject_connect.cr @@ -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 diff --git a/src/bindgen/call_builder/cpp_to_crystal_proc.cr b/src/bindgen/call_builder/cpp_to_crystal_proc.cr index 7626299..41f3c23 100644 --- a/src/bindgen/call_builder/cpp_to_crystal_proc.cr +++ b/src/bindgen/call_builder/cpp_to_crystal_proc.cr @@ -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 diff --git a/src/bindgen/call_builder/crystal_binding.cr b/src/bindgen/call_builder/crystal_binding.cr index 54753b8..3943954 100644 --- a/src/bindgen/call_builder/crystal_binding.cr +++ b/src/bindgen/call_builder/crystal_binding.cr @@ -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| diff --git a/src/bindgen/call_builder/crystal_from_cpp.cr b/src/bindgen/call_builder/crystal_from_cpp.cr index ed4ee67..67b01d4 100644 --- a/src/bindgen/call_builder/crystal_from_cpp.cr +++ b/src/bindgen/call_builder/crystal_from_cpp.cr @@ -67,11 +67,7 @@ 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) - end - + body = call.result.apply_conversion "#{@receiver}.#{call.name}(#{pass_args})" %[Proc(#{proc_args}).new{#{block_args} #{body} }] end end diff --git a/src/bindgen/call_builder/crystal_wrapper.cr b/src/bindgen/call_builder/crystal_wrapper.cr index f0e5bca..e2ab47e 100644 --- a/src/bindgen/call_builder/crystal_wrapper.cr +++ b/src/bindgen/call_builder/crystal_wrapper.cr @@ -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 diff --git a/src/bindgen/processor/instance_properties.cr b/src/bindgen/processor/instance_properties.cr index 2debaa5..00e31a8 100644 --- a/src/bindgen/processor/instance_properties.cr +++ b/src/bindgen/processor/instance_properties.cr @@ -76,12 +76,7 @@ module Bindgen private class GetterBody < Call::Body def to_code(call : Call, _platform : Graph::Platform) : String code = call.name - - if templ = call.result.conversion - code = Util.template(templ, code) - end - - code + call.result.apply_conversion(code) end end @@ -116,12 +111,7 @@ module Bindgen private class SetterBody < Call::Body def to_code(call : Call, _platform : Graph::Platform) : String code = call.arguments.first.call - - if templ = call.result.conversion - code = Util.template(templ, code) - end - - "#{call.name} = #{code}" + "#{call.name} = #{call.result.apply_conversion code}" end end end diff --git a/src/bindgen/util.cr b/src/bindgen/util.cr index f4a547c..1785ee7 100644 --- a/src/bindgen/util.cr +++ b/src/bindgen/util.cr @@ -33,8 +33,8 @@ module Bindgen # `NAME` is unset, can be provided through a pipe symbol: `{NAME|default}`. # # It's possible to fall back to the character expansion: `{NAME|%}` - def self.template(haystack : String, replacement : String? = nil, env = ENV) - haystack.gsub(/(%)|{([^}|]+)(?:\|([^}]+))?}/) do |_, match| + def self.template(haystack : String, replacement : String? = nil, env = ENV) : String + haystack.gsub(/(%)|\{([^}|]+)(?:\|([^}]+))?\}/) do |_, match| expansion = match[1]? env_var = match[2]? alternative = match[3]? From 59759c44b49891eda881953d68a42402ec338394 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Aug 2020 21:45:45 +0800 Subject: [PATCH 2/7] Bindgen::Template hierarchy --- spec/bindgen/processor/extern_c_spec.cr | 4 +- src/bindgen/call.cr | 14 +--- src/bindgen/call_builder/crystal_from_cpp.cr | 11 +-- .../call_builder/crystal_superclass.cr | 1 - .../call_builder/crystal_superclass_init.cr | 1 - src/bindgen/cpp/cookbook.cr | 18 ++-- src/bindgen/cpp/pass.cr | 11 ++- src/bindgen/crystal/pass.cr | 21 +++-- src/bindgen/processor/crystal_binding.cr | 1 - src/bindgen/processor/crystal_wrapper.cr | 1 - src/bindgen/processor/extern_c.cr | 6 +- .../processor/instantiate_containers.cr | 22 +++-- src/bindgen/processor/virtual_override.cr | 2 - src/bindgen/template.cr | 83 +++++++++++++++++++ src/bindgen/type_database.cr | 35 ++++++-- src/bindgen/type_helper.cr | 1 - 16 files changed, 171 insertions(+), 61 deletions(-) create mode 100644 src/bindgen/template.cr diff --git a/spec/bindgen/processor/extern_c_spec.cr b/spec/bindgen/processor/extern_c_spec.cr index c5c6db6..b9807ce 100644 --- a/spec/bindgen/processor/extern_c_spec.cr +++ b/spec/bindgen/processor/extern_c_spec.cr @@ -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( diff --git a/src/bindgen/call.cr b/src/bindgen/call.cr index 750b1ac..791782c 100644 --- a/src/bindgen/call.cr +++ b/src/bindgen/call.cr @@ -39,20 +39,14 @@ module Bindgen class Result < Expression # Conversion template to get the data out of the method, ready to be # returned back. - getter conversion : String? + 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, if a - # templater is present. + # Applies the result's conversion template to a piece of code. def apply_conversion(code : String) : String - case templ = conversion - when String - Util.template(templ, code) - else - code - end + conversion.template(code) end # Converts the result into an argument of *name*. diff --git a/src/bindgen/call_builder/crystal_from_cpp.cr b/src/bindgen/call_builder/crystal_from_cpp.cr index 67b01d4..b1cadaf 100644 --- a/src/bindgen/call_builder/crystal_from_cpp.cr +++ b/src/bindgen/call_builder/crystal_from_cpp.cr @@ -32,21 +32,14 @@ module Bindgen # 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) Call::Result.new( type: outer.type, type_name: outer.type_name, pointer: outer.pointer, reference: outer.reference, - conversion: conversion, + conversion: combined_conversion, ) end diff --git a/src/bindgen/call_builder/crystal_superclass.cr b/src/bindgen/call_builder/crystal_superclass.cr index b07fcb9..8d04b99 100644 --- a/src/bindgen/call_builder/crystal_superclass.cr +++ b/src/bindgen/call_builder/crystal_superclass.cr @@ -14,7 +14,6 @@ module Bindgen type_name: superklass.name, reference: false, pointer: 0, - conversion: nil, ) Call.new( diff --git a/src/bindgen/call_builder/crystal_superclass_init.cr b/src/bindgen/call_builder/crystal_superclass_init.cr index d22f91c..8fd2045 100644 --- a/src/bindgen/call_builder/crystal_superclass_init.cr +++ b/src/bindgen/call_builder/crystal_superclass_init.cr @@ -13,7 +13,6 @@ module Bindgen type_name: method.name, reference: false, pointer: 0, - conversion: nil, ) Call.new( diff --git a/src/bindgen/cpp/cookbook.cr b/src/bindgen/cpp/cookbook.cr index e9f4ab4..05ac1fb 100644 --- a/src/bindgen/cpp/cookbook.cr +++ b/src/bindgen/cpp/cookbook.cr @@ -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::Simple` 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 @@ -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 @@ -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? @@ -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. diff --git a/src/bindgen/cpp/pass.cr b/src/bindgen/cpp/pass.cr index bf419f7..cd565b2 100644 --- a/src/bindgen/cpp/pass.cr +++ b/src/bindgen/cpp/pass.cr @@ -77,6 +77,8 @@ module Bindgen ptr = 0 end + template = Template::None.new + if rules = @db[type]? template = rules.to_cpp type_name = rules.cpp_type || type_name @@ -84,7 +86,7 @@ module Bindgen 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 @@ -139,6 +141,8 @@ 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 @@ -146,7 +150,7 @@ module Bindgen 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 @@ -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 @@ -213,7 +217,6 @@ module Bindgen type_name: type_name, reference: type.reference?, pointer: type_pointer_depth(type), - conversion: nil, ) end end diff --git a/src/bindgen/crystal/pass.cr b/src/bindgen/crystal/pass.cr index da69ab9..2bcbef2 100644 --- a/src/bindgen/crystal/pass.cr +++ b/src/bindgen/crystal/pass.cr @@ -55,9 +55,12 @@ module Bindgen type_name, in_lib = typer.binding(type) type_name = typer.qualified(type_name, in_lib) if qualified + template = Template::None.new + if rules = @db[type]? template = type_template(rules.converter, rules.from_crystal, "wrap") - template ||= "%.to_unsafe" if to_unsafe && !rules.builtin && !type.builtin? + template = Template.from_string("%.to_unsafe", simple: true) if + template.no_op? && to_unsafe && !rules.builtin && !type.builtin? is_ref, ptr = reconfigure_pass_type(rules.pass_by, is_ref, ptr) end @@ -99,7 +102,7 @@ module Bindgen ptr += 1 if is_ref # Translate reference to pointer is_ref = false - {is_ref, ptr, type_name, nil, nilable} + {is_ref, ptr, type_name, Template::None.new, nilable} end end @@ -146,6 +149,8 @@ module Bindgen type_name, _ = typer.binding(type) end + template = Template::None.new + if rules = @db[type]? unless is_constructor template = type_template(rules.converter, rules.to_crystal, "unwrap") @@ -165,6 +170,8 @@ module Bindgen local_type_name, in_lib = typer.wrapper(type) type_name = typer.qualified(local_type_name, in_lib) + template = Template::None.new + if rules = @db[type]? if rules.kind.class? ptr -= 1 @@ -175,7 +182,7 @@ module Bindgen nilable = false end - if !rules.builtin && !is_constructor && !rules.converter && !rules.to_crystal && !in_lib && !rules.kind.enum? + if !rules.builtin && !is_constructor && !rules.converter && rules.to_crystal.no_op? && !in_lib && !rules.kind.enum? template = wrapper_initialize_template(rules, type_name, nilable) end @@ -226,9 +233,9 @@ module Bindgen # *converter* is set by the user as `converter:` field in the type # configuration, while *translator* is influenced by `to_crystal:` or # `from_crystal:`. - private def type_template(converter, translator, conv_name) + private def type_template(converter, translator, conv_name) : Template::Base if converter - "#{converter}.#{conv_name}(%)" + Template.from_string "#{converter}.#{conv_name}(%)", simple: true else translator end @@ -244,11 +251,13 @@ module Bindgen end end - if nilable + template_string = if nilable %[%.try {|ptr| #{type_name}.new(unwrap: ptr) unless ptr.null?}] else "#{type_name}.new(unwrap: %)" end + + Template.from_string template_string, simple: true end end end diff --git a/src/bindgen/processor/crystal_binding.cr b/src/bindgen/processor/crystal_binding.cr index b9e0b83..43e6a93 100644 --- a/src/bindgen/processor/crystal_binding.cr +++ b/src/bindgen/processor/crystal_binding.cr @@ -11,7 +11,6 @@ module Bindgen type_name: "Void", reference: false, pointer: 0, - conversion: nil, nilable: false, ) diff --git a/src/bindgen/processor/crystal_wrapper.cr b/src/bindgen/processor/crystal_wrapper.cr index b306dab..dc616be 100644 --- a/src/bindgen/processor/crystal_wrapper.cr +++ b/src/bindgen/processor/crystal_wrapper.cr @@ -42,7 +42,6 @@ module Bindgen type_name: type_name, pointer: type.pointer, reference: false, - conversion: nil, ) end diff --git a/src/bindgen/processor/extern_c.cr b/src/bindgen/processor/extern_c.cr index 0e87b62..c181786 100644 --- a/src/bindgen/processor/extern_c.cr +++ b/src/bindgen/processor/extern_c.cr @@ -35,11 +35,11 @@ module Bindgen # Conversion checks pass = Cpp::Pass.new(@db) any_arg_uses_conversion = method.origin.arguments.any? do |arg| - pass.to_cpp(arg).conversion != nil + !pass.to_cpp(arg).conversion.no_op? end - return if pass.to_crystal(method.origin.return_type).conversion != nil # Rule 3 - return if any_arg_uses_conversion # Rule 2 + return if !pass.to_crystal(method.origin.return_type).conversion.no_op? # Rule 3 + return if any_arg_uses_conversion # Rule 2 # If we end up here, the method can be bound to directly. method.set_tag(Graph::Method::EXPLICIT_BIND_TAG, method.origin.name) diff --git a/src/bindgen/processor/instantiate_containers.cr b/src/bindgen/processor/instantiate_containers.cr index cd01473..4e3cfbf 100644 --- a/src/bindgen/processor/instantiate_containers.cr +++ b/src/bindgen/processor/instantiate_containers.cr @@ -87,11 +87,10 @@ module Bindgen type_name: type.full_name, reference: false, pointer: 0, - conversion: nil, ) Graph::Alias.new( # Build the `typedef`. -origin: origin, + origin: origin, name: klass.name, parent: host, ) @@ -111,10 +110,21 @@ origin: origin, rules.binding_type = "Void" rules.crystal_type ||= "Enumerable(#{result.type_name})" rules.cpp_type ||= cpp_type_name - rules.to_crystal ||= "#{klass.name}.new(unwrap: %)" - rules.from_crystal ||= "BindgenHelper.wrap_container(#{klass.name}, %).to_unsafe" - rules.from_cpp ||= @db.cookbook.value_to_pointer(klass.name) - rules.to_cpp ||= @db.cookbook.pointer_to_reference(klass.name) + + if rules.to_crystal.no_op? + rules.to_crystal = Template.from_string( + "#{klass.name}.new(unwrap: %)", simple: true) + end + if rules.from_crystal.no_op? + rules.from_crystal = Template.from_string( + "BindgenHelper.wrap_container(#{klass.name}, %).to_unsafe", simple: true) + end + if rules.from_cpp.no_op? + rules.from_cpp = Template.from_string(@db.cookbook.value_to_pointer(klass.name)) + end + if rules.to_cpp.no_op? + rules.to_cpp = Template.from_string(@db.cookbook.pointer_to_reference(klass.name)) + end end # Name of *container* with *instance* for diagnostic purposes. diff --git a/src/bindgen/processor/virtual_override.cr b/src/bindgen/processor/virtual_override.cr index fabbdba..850fa4a 100644 --- a/src/bindgen/processor/virtual_override.cr +++ b/src/bindgen/processor/virtual_override.cr @@ -302,7 +302,6 @@ module Bindgen type_name: pass.crystal_proc_name(proc_type), reference: false, pointer: 0, - conversion: nil, ) end @@ -316,7 +315,6 @@ module Bindgen type_name: proc_type.base_name, reference: false, pointer: 0, - conversion: nil, ) end diff --git a/src/bindgen/template.cr b/src/bindgen/template.cr new file mode 100644 index 0000000..598157b --- /dev/null +++ b/src/bindgen/template.cr @@ -0,0 +1,83 @@ +module Bindgen + # Conversion templates for `Call::Result`. They govern how a call result's + # code should be transformed to become usable under a certain platform. + module Template + # Constructs a template from the string *pattern*. No-op if the *pattern* + # is `nil`. If *simple* is true, the resulting template does not support + # environment variables. + def self.from_string(pattern : String?, *, simple = false) : Base + if pattern.nil? + None.new + elsif simple + Simple.new(pattern) + else + Full.new(pattern) + end + end + + # Base class of the templates. + abstract class Base + # Performs a template substitution. + abstract def template(code : String) : String + + # Is this a no-operation template? + def no_op? + {{ @type == Bindgen::Template::None }} + end + + # Combines two templates into one. The *other* template is run after the + # current template. + def followed_by(other : self) : self + return other if no_op? + return self if other.no_op? + Seq.new(first: self, second: other) + end + end + + # The no-op template that returns *code* unmodified. + class None < Base + def template(code) : String + code + end + end + + # A simple template implementing a subset of `Util.template`. Only + # supports `%`, which can be escaped by using `%%` instead. + class Simple < Base + def initialize(@pattern : String) + end + + def template(code) : String + @pattern.gsub(/%+/) do |m| + literals = "%" * (m.size // 2) + out_code = code if m.size % 2 == 1 + "#{literals}#{out_code}" + end + end + end + + # The default template. Uses `Util.template` to substitute *code* into the + # given *pattern*. + class Full < Base + def initialize(@pattern : String) + end + + def template(code) : String + Util.template(@pattern, code) + end + end + + # Compound template which runs *code* through two child templaters. + class Seq < Base + @first : Base + @second : Base + + def initialize(@first, @second) + end + + def template(code) : String + @second.template(@first.template(code)) + end + end + end +end diff --git a/src/bindgen/type_database.cr b/src/bindgen/type_database.cr index b2996c8..5e209d1 100644 --- a/src/bindgen/type_database.cr +++ b/src/bindgen/type_database.cr @@ -18,6 +18,13 @@ module Bindgen end end + # YAML converter for building a conversion template from a string. + module ConversionTemplateConverter + def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Template::Base + Template.from_string(Union(String, Nil).new(ctx, node), simple: false) + end + end + # Configuration of instance variables. class InstanceVariableConfig include YAML::Serializable @@ -70,20 +77,36 @@ module Bindgen binding_type: {type: String, nilable: true}, # Template code ran to turn the real C++ type into the crystal type. - from_cpp: {type: String, nilable: true}, + from_cpp: { + type: Template::Base, + default: Template::None.new, + converter: ConversionTemplateConverter, + }, # Template code ran to turn the crystal type into the real C++ type. - to_cpp: {type: String, nilable: true}, + to_cpp: { + type: Template::Base, + default: Template::None.new, + converter: ConversionTemplateConverter, + }, # Converter for this type in Crystal. Takes precedence over the # `#to_crystal` and `#from_crystal` fields. converter: {type: String, nilable: true}, # Template code ran to turn the binding type to Crystal. - to_crystal: {type: String, nilable: true}, + to_crystal: { + type: Template::Base, + default: Template::None.new, + converter: ConversionTemplateConverter, + }, # Template code ran to turn the Crystal type for the binding. - from_crystal: {type: String, nilable: true}, + from_crystal: { + type: Template::Base, + default: Template::None.new, + converter: ConversionTemplateConverter, + }, # How to pass this type to C++? pass_by: { @@ -164,8 +187,8 @@ module Bindgen def initialize( @crystal_type = nil, @cpp_type = nil, @binding_type = nil, - @from_cpp = nil, @to_cpp = nil, @converter = nil, - @from_crystal = nil, @to_crystal = nil, + @from_cpp = Template::None.new, @to_cpp = Template::None.new, @converter = nil, + @from_crystal = Template::None.new, @to_crystal = Template::None.new, @kind = Parser::Type::Kind::Class, @ignore = false, @pass_by = PassBy::Original, @wrapper_pass_by = nil, @sub_class = true, @copy_structure = false, @generate_wrapper = true, diff --git a/src/bindgen/type_helper.cr b/src/bindgen/type_helper.cr index bdbbd6e..35a598c 100644 --- a/src/bindgen/type_helper.cr +++ b/src/bindgen/type_helper.cr @@ -21,7 +21,6 @@ module Bindgen type_name: type.base_name, reference: type.reference?, pointer: type_pointer_depth(type), - conversion: nil, ) end From eafa6d55ecfd552a774e509d7c148a3fbaa355b8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Aug 2020 22:09:26 +0800 Subject: [PATCH 3/7] specs for templates --- spec/bindgen/template_spec.cr | 88 +++++++++++++++++++++++++++++++++++ src/bindgen/template.cr | 16 ++++++- 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 spec/bindgen/template_spec.cr diff --git a/spec/bindgen/template_spec.cr b/spec/bindgen/template_spec.cr new file mode 100644 index 0000000..209518c --- /dev/null +++ b/spec/bindgen/template_spec.cr @@ -0,0 +1,88 @@ +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::Simple.new("%x")) + end + + it "can construct a full template from a string" do + expected = Bindgen::Template::Full.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 false for any other templates" do + conversion1 = Bindgen::Template::Simple.new("%x") + conversion2 = Bindgen::Template::Full.new("%x") + conversion3 = Bindgen::Template::Seq.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::Simple.new("%x") + conversion2 = Bindgen::Template::Full.new("%x") + conversion3 = Bindgen::Template::Seq.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::Simple.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 "Simple#template" do + it "substitutes % with the supplied code" do + Bindgen::Template::Simple.new("a%b%c").template("123").should eq("a123b123c") + end + + it "substitutes %% with %" do + Bindgen::Template::Simple.new("%%a%%%b%%%%c").template("123").should eq("%a%123b%%c") + end + end + + describe "Full#template" do + it "follows Util.template rules for template substitution" do + key, value = ENV.first + Bindgen::Template::Full.new("%{#{key}}%").template("123").should eq("123#{value}123") + end + end + + describe "Seq#template" do + it "composes two templates" do + first = Bindgen::Template::Simple.new("%_a") + second = Bindgen::Template::Simple.new("%_b") + conversion = Bindgen::Template::Seq.new(first: first, second: second) + conversion.template("c").should eq("c_a_b") + end + end +end diff --git a/src/bindgen/template.cr b/src/bindgen/template.cr index 598157b..8698d5f 100644 --- a/src/bindgen/template.cr +++ b/src/bindgen/template.cr @@ -27,7 +27,7 @@ module Bindgen # Combines two templates into one. The *other* template is run after the # current template. - def followed_by(other : self) : self + def followed_by(other : Base) : Base return other if no_op? return self if other.no_op? Seq.new(first: self, second: other) @@ -39,6 +39,14 @@ module Bindgen def template(code) : String code end + + def ==(_other : self) + true + end + + def hash(hasher) + hasher + end end # A simple template implementing a subset of `Util.template`. Only @@ -54,6 +62,8 @@ module Bindgen "#{literals}#{out_code}" end end + + def_equals_and_hash @pattern end # The default template. Uses `Util.template` to substitute *code* into the @@ -65,6 +75,8 @@ module Bindgen def template(code) : String Util.template(@pattern, code) end + + def_equals_and_hash @pattern end # Compound template which runs *code* through two child templaters. @@ -78,6 +90,8 @@ module Bindgen def template(code) : String @second.template(@first.template(code)) end + + def_equals_and_hash @first, @second end end end From b0866e84194b153291da92825f5c84f05f5b9c7a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Aug 2020 00:56:44 +0800 Subject: [PATCH 4/7] template for Proc conversion --- src/bindgen/call_builder/crystal_from_cpp.cr | 12 ++++--- src/bindgen/crystal/typename.cr | 7 ++-- src/bindgen/template.cr | 38 ++++++++++++++++---- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/bindgen/call_builder/crystal_from_cpp.cr b/src/bindgen/call_builder/crystal_from_cpp.cr index b1cadaf..5f5b691 100644 --- a/src/bindgen/call_builder/crystal_from_cpp.cr +++ b/src/bindgen/call_builder/crystal_from_cpp.cr @@ -6,7 +6,7 @@ module Bindgen end # Calls the *method*, using the *proc_name* to call-through to Crystal. - def build(method : Parser::Method, receiver = "self") : Call + def build(method : Parser::Method, receiver = "self", do_block = false) : Call pass = Crystal::Pass.new(@db) argument = Crystal::Argument.new(@db) @@ -26,7 +26,7 @@ module Bindgen name: method.crystal_name, result: result, arguments: arguments, - body: Body.new(@db, receiver), + body: Body.new(@db, receiver, do_block), ) end @@ -44,7 +44,7 @@ module Bindgen 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 @@ -61,7 +61,11 @@ module Bindgen block_args = "|#{block_arg_names}|" unless pass_args.empty? body = call.result.apply_conversion "#{@receiver}.#{call.name}(#{pass_args})" - %[Proc(#{proc_args}).new{#{block_args} #{body} }] + if @do_block + %[Proc(#{proc_args}).new do #{block_args} #{body} end] + else + %[Proc(#{proc_args}).new{#{block_args} #{body} }] + end end end end diff --git a/src/bindgen/crystal/typename.cr b/src/bindgen/crystal/typename.cr index 5fd0663..b493e2f 100644 --- a/src/bindgen/crystal/typename.cr +++ b/src/bindgen/crystal/typename.cr @@ -50,8 +50,11 @@ module Bindgen rules = @db[type]? return {type.base_name, !type.builtin?} if rules.nil? - in_lib = rules.copy_structure # Copied structures end up in Binding - in_lib ||= !rules.kind.enum? && !rules.builtin && !type.builtin? + # Copied structures end up in Binding + in_lib = rules.copy_structure + # The `Void` check is required for `InstantiateContainers`, which marks + # their binding types as built-in + in_lib ||= !rules.kind.enum? && rules.binding_type != "Void" && !type.builtin? if name = rules.lib_type {name, in_lib} diff --git a/src/bindgen/template.cr b/src/bindgen/template.cr index 8698d5f..44da44f 100644 --- a/src/bindgen/template.cr +++ b/src/bindgen/template.cr @@ -43,10 +43,6 @@ module Bindgen def ==(_other : self) true end - - def hash(hasher) - hasher - end end # A simple template implementing a subset of `Util.template`. Only @@ -63,7 +59,7 @@ module Bindgen end end - def_equals_and_hash @pattern + def_equals @pattern end # The default template. Uses `Util.template` to substitute *code* into the @@ -76,7 +72,7 @@ module Bindgen Util.template(@pattern, code) end - def_equals_and_hash @pattern + def_equals @pattern end # Compound template which runs *code* through two child templaters. @@ -91,7 +87,35 @@ module Bindgen @second.template(@first.template(code)) end - def_equals_and_hash @first, @second + def_equals @first, @second + end + + # Template to transform `Proc`s for Crystal wrapper types into `Proc` + # expressions for binding types. + class ProcFromWrapper < Base + def initialize(@proc_type : Parser::Type, @db : TypeDatabase) + end + + def template(code) : String + args = @proc_type.template.not_nil!.arguments + func_args = args[1..-1].map_with_index do |type, i| + Parser::Argument.new("arg#{i}", type) + end + proc_call = Parser::Method.build( + name: "call", + return_type: args.first, + arguments: func_args, + class_name: "Proc", + ) + + # The templated code shouldn't contain braces, since an outer `Full` + # template might treat the code block as an environment variable. + builder = CallBuilder::CrystalFromCpp.new(@db) + call = builder.build(proc_call, receiver: "_proc_", do_block: true) + call.body.to_code(call, Graph::Platform::Crystal) + end + + def_equals @proc_type, @db end end end From d05ac97915637339c8477dd0589c121b2a3f2b8e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Aug 2020 01:57:10 +0800 Subject: [PATCH 5/7] Simple, Full -> Basic --- spec/bindgen/template_spec.cr | 34 ++++++++++++-------------- src/bindgen/cpp/cookbook.cr | 2 +- src/bindgen/template.cr | 46 +++++++++++++++-------------------- 3 files changed, 37 insertions(+), 45 deletions(-) diff --git a/spec/bindgen/template_spec.cr b/spec/bindgen/template_spec.cr index 209518c..678920c 100644 --- a/spec/bindgen/template_spec.cr +++ b/spec/bindgen/template_spec.cr @@ -8,11 +8,11 @@ describe "Template" do it "can construct a simple template from a string" do Bindgen::Template.from_string("%x", simple: true).should eq( - Bindgen::Template::Simple.new("%x")) + Bindgen::Template::Basic.new("%x", simple: true)) end it "can construct a full template from a string" do - expected = Bindgen::Template::Full.new("%x") + 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 @@ -24,8 +24,8 @@ describe "Template" do end it "returns false for any other templates" do - conversion1 = Bindgen::Template::Simple.new("%x") - conversion2 = Bindgen::Template::Full.new("%x") + conversion1 = Bindgen::Template::Basic.new("%x") + conversion2 = Bindgen::Template::Basic.new("%x", simple: true) conversion3 = Bindgen::Template::Seq.new(conversion1, conversion2) conversion1.no_op?.should be_false @@ -36,15 +36,15 @@ describe "Template" do describe "#followed_by" do it "composes two templates" do - conversion1 = Bindgen::Template::Simple.new("%x") - conversion2 = Bindgen::Template::Full.new("%x") + conversion1 = Bindgen::Template::Basic.new("%x") + conversion2 = Bindgen::Template::Basic.new("%y") conversion3 = Bindgen::Template::Seq.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::Simple.new("%x") + op = Bindgen::Template::Basic.new("%x") no = Bindgen::Template::None.new op.followed_by(op).no_op?.should be_false @@ -60,27 +60,25 @@ describe "Template" do end end - describe "Simple#template" do + describe "Basic#template" do it "substitutes % with the supplied code" do - Bindgen::Template::Simple.new("a%b%c").template("123").should eq("a123b123c") + Bindgen::Template::Basic.new("a%b%c").template("123").should eq("a123b123c") end - it "substitutes %% with %" do - Bindgen::Template::Simple.new("%%a%%%b%%%%c").template("123").should eq("%a%123b%%c") + 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 - end - describe "Full#template" do - it "follows Util.template rules for template substitution" do - key, value = ENV.first - Bindgen::Template::Full.new("%{#{key}}%").template("123").should eq("123#{value}123") + 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 "Seq#template" do it "composes two templates" do - first = Bindgen::Template::Simple.new("%_a") - second = Bindgen::Template::Simple.new("%_b") + first = Bindgen::Template::Basic.new("%_a") + second = Bindgen::Template::Basic.new("%_b") conversion = Bindgen::Template::Seq.new(first: first, second: second) conversion.template("c").should eq("c_a_b") end diff --git a/src/bindgen/cpp/cookbook.cr b/src/bindgen/cpp/cookbook.cr index 05ac1fb..b3d88f6 100644 --- a/src/bindgen/cpp/cookbook.cr +++ b/src/bindgen/cpp/cookbook.cr @@ -8,7 +8,7 @@ module Bindgen # setting. See `Configuration#platform` for the code, and `TEMPLATE.yml` # for user-facing documentation. # - # The functions all return a `Template::Simple` when conversion is + # 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. diff --git a/src/bindgen/template.cr b/src/bindgen/template.cr index 44da44f..0579deb 100644 --- a/src/bindgen/template.cr +++ b/src/bindgen/template.cr @@ -8,10 +8,8 @@ module Bindgen def self.from_string(pattern : String?, *, simple = false) : Base if pattern.nil? None.new - elsif simple - Simple.new(pattern) else - Full.new(pattern) + Basic.new(pattern, simple: simple) end end @@ -45,34 +43,29 @@ module Bindgen end end - # A simple template implementing a subset of `Util.template`. Only - # supports `%`, which can be escaped by using `%%` instead. - class Simple < Base - def initialize(@pattern : String) - end - - def template(code) : String - @pattern.gsub(/%+/) do |m| - literals = "%" * (m.size // 2) - out_code = code if m.size % 2 == 1 - "#{literals}#{out_code}" - end - end - - def_equals @pattern - end - # The default template. Uses `Util.template` to substitute *code* into the # given *pattern*. - class Full < Base - def initialize(@pattern : String) + # + # If *simple* is given, the template will only use a subset of the features; + # `%` performs substitution, whereas the escape sequence `%%` outputs a + # literal percent sign. + class Basic < Base + def initialize(@pattern : String, @simple = false) end def template(code) : String - Util.template(@pattern, code) + if @simple + @pattern.gsub(/%+/) do |m| + literals = "%" * (m.size // 2) + out_code = code if m.size % 2 == 1 + "#{literals}#{out_code}" + end + else + Util.template(@pattern, code) + end end - def_equals @pattern + def_equals @pattern, @simple end # Compound template which runs *code* through two child templaters. @@ -108,8 +101,9 @@ module Bindgen class_name: "Proc", ) - # The templated code shouldn't contain braces, since an outer `Full` - # template might treat the code block as an environment variable. + # The templated code shouldn't contain braces, since an outer template + # that comes from a config file might treat the code block as an + # environment variable. builder = CallBuilder::CrystalFromCpp.new(@db) call = builder.build(proc_call, receiver: "_proc_", do_block: true) call.body.to_code(call, Graph::Platform::Crystal) From 3d81178cdce28dc426b102b3f7ec885c5403e1cd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Aug 2020 02:05:38 +0800 Subject: [PATCH 6/7] Template::Seq -> Sequence --- spec/bindgen/template_spec.cr | 18 +++++++++++------- src/bindgen/template.cr | 30 +++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/spec/bindgen/template_spec.cr b/spec/bindgen/template_spec.cr index 678920c..9790762 100644 --- a/spec/bindgen/template_spec.cr +++ b/spec/bindgen/template_spec.cr @@ -26,7 +26,7 @@ describe "Template" do 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::Seq.new(conversion1, conversion2) + conversion3 = Bindgen::Template::Sequence.new(conversion1, conversion2) conversion1.no_op?.should be_false conversion2.no_op?.should be_false @@ -38,7 +38,7 @@ describe "Template" do it "composes two templates" do conversion1 = Bindgen::Template::Basic.new("%x") conversion2 = Bindgen::Template::Basic.new("%y") - conversion3 = Bindgen::Template::Seq.new(conversion1, conversion2) + conversion3 = Bindgen::Template::Sequence.new(conversion1, conversion2) conversion1.followed_by(conversion2).should eq(conversion3) end @@ -75,12 +75,16 @@ describe "Template" do end end - describe "Seq#template" do - it "composes two templates" do - first = Bindgen::Template::Basic.new("%_a") - second = Bindgen::Template::Basic.new("%_b") - conversion = Bindgen::Template::Seq.new(first: first, second: second) + 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 diff --git a/src/bindgen/template.cr b/src/bindgen/template.cr index 0579deb..3d1c0b8 100644 --- a/src/bindgen/template.cr +++ b/src/bindgen/template.cr @@ -28,7 +28,7 @@ module Bindgen def followed_by(other : Base) : Base return other if no_op? return self if other.no_op? - Seq.new(first: self, second: other) + Sequence.new(self, other) end end @@ -68,19 +68,31 @@ module Bindgen def_equals @pattern, @simple end - # Compound template which runs *code* through two child templaters. - class Seq < Base - @first : Base - @second : Base - - def initialize(@first, @second) + # Compound template which runs *code* through multiple child templater, in + # the order they are given in the constructor. + class Sequence < Base + getter children = Array(Base).new + + def initialize(*conversions : Base) + conversions.each do |conversion| + # Flatten `Sequence` templates automatically. + if conversion.is_a?(Sequence) + @children.concat(conversion.children) + else + @children << conversion + end + end end def template(code) : String - @second.template(@first.template(code)) + @children.each do |conversion| + code = conversion.template(code) + end + + code end - def_equals @first, @second + def_equals @children end # Template to transform `Proc`s for Crystal wrapper types into `Proc` From 75e3cd2501a4b2560b9f986b31af1eee873c9149 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 22 Aug 2020 02:56:20 +0800 Subject: [PATCH 7/7] bindgen/template/*, fix #no_op?, do_block doc --- spec/bindgen/template_spec.cr | 5 + src/bindgen/call_builder/crystal_from_cpp.cr | 5 + src/bindgen/template.cr | 113 +------------------ src/bindgen/template/base.cr | 20 ++++ src/bindgen/template/basic.cr | 32 ++++++ src/bindgen/template/none.cr | 18 +++ src/bindgen/template/proc_from_wrapper.cr | 33 ++++++ src/bindgen/template/sequence.cr | 36 ++++++ 8 files changed, 151 insertions(+), 111 deletions(-) create mode 100644 src/bindgen/template/base.cr create mode 100644 src/bindgen/template/basic.cr create mode 100644 src/bindgen/template/none.cr create mode 100644 src/bindgen/template/proc_from_wrapper.cr create mode 100644 src/bindgen/template/sequence.cr diff --git a/spec/bindgen/template_spec.cr b/spec/bindgen/template_spec.cr index 9790762..2cdefc2 100644 --- a/spec/bindgen/template_spec.cr +++ b/spec/bindgen/template_spec.cr @@ -23,6 +23,11 @@ describe "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) diff --git a/src/bindgen/call_builder/crystal_from_cpp.cr b/src/bindgen/call_builder/crystal_from_cpp.cr index 5f5b691..7a67755 100644 --- a/src/bindgen/call_builder/crystal_from_cpp.cr +++ b/src/bindgen/call_builder/crystal_from_cpp.cr @@ -6,6 +6,11 @@ module Bindgen end # Calls the *method*, using the *proc_name* to call-through to Crystal. + # + # 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 pass = Crystal::Pass.new(@db) argument = Crystal::Argument.new(@db) diff --git a/src/bindgen/template.cr b/src/bindgen/template.cr index 3d1c0b8..e85d739 100644 --- a/src/bindgen/template.cr +++ b/src/bindgen/template.cr @@ -1,3 +1,5 @@ +require "./template/*" + module Bindgen # Conversion templates for `Call::Result`. They govern how a call result's # code should be transformed to become usable under a certain platform. @@ -12,116 +14,5 @@ module Bindgen Basic.new(pattern, simple: simple) end end - - # Base class of the templates. - abstract class Base - # Performs a template substitution. - abstract def template(code : String) : String - - # Is this a no-operation template? - def no_op? - {{ @type == Bindgen::Template::None }} - end - - # Combines two templates into one. The *other* template is run after the - # current template. - def followed_by(other : Base) : Base - return other if no_op? - return self if other.no_op? - Sequence.new(self, other) - end - end - - # The no-op template that returns *code* unmodified. - class None < Base - def template(code) : String - code - end - - def ==(_other : self) - true - end - end - - # The default template. Uses `Util.template` to substitute *code* into the - # given *pattern*. - # - # If *simple* is given, the template will only use a subset of the features; - # `%` performs substitution, whereas the escape sequence `%%` outputs a - # literal percent sign. - class Basic < Base - def initialize(@pattern : String, @simple = false) - end - - def template(code) : String - if @simple - @pattern.gsub(/%+/) do |m| - literals = "%" * (m.size // 2) - out_code = code if m.size % 2 == 1 - "#{literals}#{out_code}" - end - else - Util.template(@pattern, code) - end - end - - def_equals @pattern, @simple - end - - # Compound template which runs *code* through multiple child templater, in - # the order they are given in the constructor. - class Sequence < Base - getter children = Array(Base).new - - def initialize(*conversions : Base) - conversions.each do |conversion| - # Flatten `Sequence` templates automatically. - if conversion.is_a?(Sequence) - @children.concat(conversion.children) - else - @children << conversion - end - end - end - - def template(code) : String - @children.each do |conversion| - code = conversion.template(code) - end - - code - end - - def_equals @children - end - - # Template to transform `Proc`s for Crystal wrapper types into `Proc` - # expressions for binding types. - class ProcFromWrapper < Base - def initialize(@proc_type : Parser::Type, @db : TypeDatabase) - end - - def template(code) : String - args = @proc_type.template.not_nil!.arguments - func_args = args[1..-1].map_with_index do |type, i| - Parser::Argument.new("arg#{i}", type) - end - proc_call = Parser::Method.build( - name: "call", - return_type: args.first, - arguments: func_args, - class_name: "Proc", - ) - - # The templated code shouldn't contain braces, since an outer template - # that comes from a config file might treat the code block as an - # environment variable. - builder = CallBuilder::CrystalFromCpp.new(@db) - call = builder.build(proc_call, receiver: "_proc_", do_block: true) - call.body.to_code(call, Graph::Platform::Crystal) - end - - def_equals @proc_type, @db - end end end diff --git a/src/bindgen/template/base.cr b/src/bindgen/template/base.cr new file mode 100644 index 0000000..e289dee --- /dev/null +++ b/src/bindgen/template/base.cr @@ -0,0 +1,20 @@ +module Bindgen + module Template + # Base class of the templates. + abstract class Base + # Performs a template substitution. + abstract def template(code : String) : String + + # Is this a no-operation template? + abstract def no_op? : Bool + + # Combines two templates into one. The *other* template is run after the + # current template. + def followed_by(other : Base) : Base + return other if no_op? + return self if other.no_op? + Sequence.new(self, other) + end + end + end +end diff --git a/src/bindgen/template/basic.cr b/src/bindgen/template/basic.cr new file mode 100644 index 0000000..c5e8ecb --- /dev/null +++ b/src/bindgen/template/basic.cr @@ -0,0 +1,32 @@ +module Bindgen + module Template + # The default template. Uses `Util.template` to substitute *code* into the + # given *pattern*. + # + # If *simple* is given, the template will only use a subset of the features; + # `%` performs substitution, whereas the escape sequence `%%` outputs a + # literal percent sign. + class Basic < Base + def initialize(@pattern : String, @simple = false) + end + + def no_op? : Bool + @pattern == "%" + end + + def template(code) : String + if @simple + @pattern.gsub(/%+/) do |m| + literals = "%" * (m.size // 2) + out_code = code if m.size % 2 == 1 + "#{literals}#{out_code}" + end + else + Util.template(@pattern, code) + end + end + + def_equals @pattern, @simple + end + end +end diff --git a/src/bindgen/template/none.cr b/src/bindgen/template/none.cr new file mode 100644 index 0000000..0de2b6b --- /dev/null +++ b/src/bindgen/template/none.cr @@ -0,0 +1,18 @@ +module Bindgen + module Template + # The no-op template that returns *code* unmodified. + class None < Base + def no_op? : Bool + true + end + + def template(code) : String + code + end + + def ==(_other : self) + true + end + end + end +end diff --git a/src/bindgen/template/proc_from_wrapper.cr b/src/bindgen/template/proc_from_wrapper.cr new file mode 100644 index 0000000..0d94436 --- /dev/null +++ b/src/bindgen/template/proc_from_wrapper.cr @@ -0,0 +1,33 @@ +module Bindgen + module Template + # Custom template to transform `Proc`s for Crystal wrapper types into `Proc` + # expressions for binding types. + class ProcFromWrapper < Base + def initialize(@proc_type : Parser::Type, @db : TypeDatabase) + end + + def no_op? : Bool + false + end + + def template(code) : String + args = @proc_type.template.not_nil!.arguments + func_args = args[1..-1].map_with_index do |type, i| + Parser::Argument.new("arg#{i}", type) + end + proc_call = Parser::Method.build( + name: "call", + return_type: args.first, + arguments: func_args, + class_name: "Proc", + ) + + builder = CallBuilder::CrystalFromCpp.new(@db) + call = builder.build(proc_call, receiver: "_proc_", do_block: true) + call.body.to_code(call, Graph::Platform::Crystal) + end + + def_equals @proc_type, @db + end + end +end diff --git a/src/bindgen/template/sequence.cr b/src/bindgen/template/sequence.cr new file mode 100644 index 0000000..74d1371 --- /dev/null +++ b/src/bindgen/template/sequence.cr @@ -0,0 +1,36 @@ +module Bindgen + module Template + # Compound template which runs *code* through multiple child templates, in + # the order they are given in the constructor. + class Sequence < Base + getter children = Array(Base).new + getter? no_op : Bool = true + + def initialize(*conversions : Base) + conversions.each do |conversion| + # Flatten `Sequence` templates automatically. + if conversion.is_a?(Sequence) + @children.concat(conversion.children) + else + @children << conversion + end + + # `Sequence` templates returned by `#followed_by` are by construction + # never no-op, but we'll compute this in case `Sequence` is manually + # created. + @no_op &&= conversion.no_op? + end + end + + def template(code) : String + @children.each do |conversion| + code = conversion.template(code) + end + + code + end + + def_equals @children + end + end +end