-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ddafb1e
commit a125289
Showing
31 changed files
with
531 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
require 'babl/codegen/context' | ||
require 'babl/codegen/expression' | ||
require 'babl/codegen/resource' | ||
require 'babl/codegen/generator' | ||
require 'babl/codegen/linked_expression' | ||
require 'babl/codegen/variable' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
require 'babl/errors' | ||
|
||
module Babl | ||
module Codegen | ||
class Context | ||
attr_reader :key, :object, :parent, :pins | ||
|
||
def initialize(object, key = nil, parent = nil, pins = nil) | ||
@key = key | ||
@object = object | ||
@parent = parent | ||
@pins = pins | ||
end | ||
|
||
# Standard navigation (enter into property) | ||
def move_forward(new_object, key) | ||
Context.new(new_object, key, self, pins) | ||
end | ||
|
||
# Go back to parent | ||
def move_backward | ||
raise Errors::InvalidTemplate, 'There is no parent element' unless parent | ||
Context.new(parent.object, parent.key, parent.parent, pins) | ||
end | ||
|
||
# Go to a pinned context | ||
def goto_pin(ref) | ||
pin = pins&.[](ref) | ||
raise Errors::InvalidTemplate, 'Pin reference cannot be used here' unless pin | ||
Context.new(pin.object, pin.key, pin.parent, (pin.pins || {}).merge(pins)) | ||
end | ||
|
||
# Associate a pin to current context | ||
def create_pin(ref) | ||
Context.new(object, key, parent, (pins || {}).merge(ref => self)) | ||
end | ||
|
||
def formatted_stack | ||
stack_trace = ([:__root__] + stack).join('.') | ||
"BABL @ #{stack_trace}" | ||
end | ||
|
||
# Return an array containing the navigation history | ||
def stack | ||
(parent ? parent.stack : []) + [key].compact | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
require 'babl/utils' | ||
|
||
module Babl | ||
module Codegen | ||
class Expression < Utils::Value.new(:inline, :inline_outside, :code) | ||
def initialize(inline: false, inline_outside: false, &block) | ||
super(inline, inline_outside, block) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
require 'babl/utils/value' | ||
|
||
module Babl | ||
module Codegen | ||
class Generator | ||
class InlineResolver | ||
attr_reader :assigned_vars, :resolver | ||
|
||
def initialize(resolver, assigned_vars) | ||
@resolver = resolver | ||
@assigned_vars = assigned_vars | ||
end | ||
|
||
def resolve(val, vars = {}) | ||
case val | ||
when Expression then resolver.resolve(val, assigned_vars.merge(vars)) | ||
when Variable then (assigned_vars[val] && ('(' + assigned_vars[val] + ')')) || resolver.resolve(val) | ||
else resolver.resolve(val) | ||
end | ||
end | ||
end | ||
|
||
class Resolver | ||
attr_reader :generator, :argument_names, :called_linked_expressions, :expr | ||
|
||
def initialize(generator, expr) | ||
@expr = expr | ||
@generator = generator | ||
@argument_names = {} | ||
@called_linked_expressions = [] | ||
end | ||
|
||
def resolve(*args) | ||
case args.first | ||
when Variable then variable(*args) | ||
when Resource then resource(*args) | ||
when Expression then expression(*args) | ||
end | ||
end | ||
|
||
def expression(other_expr, assigned_vars = {}) | ||
linked_other = generator.link(other_expr) | ||
|
||
if linked_other.expression.inline && generator.allowed_inlining.include?(linked_other) && expr.inline_outside | ||
inline_resolver = InlineResolver.new(self, assigned_vars) | ||
'(' + linked_other.expression.code.call(inline_resolver) + ')' | ||
else | ||
called_linked_expressions << linked_other | ||
params = linked_other.inputs.map { |rv| | ||
(assigned_vars[rv] && ('(' + assigned_vars[rv] + ')')) || variable(rv) | ||
}.join(',') | ||
linked_other.name + (params.empty? ? '' : '(' + params + ')') | ||
end | ||
end | ||
|
||
def variable(var) | ||
argument_names[var] ||= "v#{argument_names.size}" | ||
end | ||
|
||
def resource(res) | ||
generator.resource_name(res) | ||
end | ||
end | ||
|
||
attr_reader :linked_expressions, :root_expression, :method_names, :evaluator_inputs, | ||
:resources, :allowed_inlining, :linked_root_expression | ||
|
||
def initialize(root_expression, *evaluator_inputs) | ||
@evaluator_inputs = evaluator_inputs | ||
@root_expression = root_expression | ||
@allowed_inlining = Set.new | ||
@method_names = {} | ||
@resources = {} | ||
@linked_expressions = {} | ||
|
||
# First pass: we link all expressions together without inlining. | ||
@linked_root_expression = link(root_expression) | ||
|
||
# Second pass: we have collected data about how much time each expression is used | ||
# so we can selectivety enable inlining when appropriate. | ||
|
||
loop do | ||
prev_inline_size = allowed_inlining.size | ||
compute_allowed_inlining | ||
@linked_expressions = {} | ||
@linked_root_expression = link(root_expression) | ||
break if prev_inline_size == allowed_inlining.size | ||
end | ||
end | ||
|
||
def compute_allowed_inlining | ||
# Inline expressions which are only called once | ||
linked_expressions.values | ||
.flat_map { |le| le.called_linked_expressions.map { |called_le| [called_le, le] } } | ||
.group_by(&:first) | ||
.map { |le, called_by_les| [le.code, le, called_by_les.map(&:last).size] } | ||
.group_by(&:first) | ||
.each { |_code, stats| | ||
total = stats.sum { |_code, _le, times| times } | ||
next if total > 1 | ||
stats.each { |_code, le, _times| allowed_inlining << le } | ||
} | ||
|
||
# Inline expressions taking no parameter | ||
linked_expressions.values | ||
.select { |le| le.inputs.empty? } | ||
.each { |le| allowed_inlining << le } | ||
end | ||
|
||
def called_linked_expressions(root) | ||
[root] + root.called_linked_expressions.flat_map { |le| called_linked_expressions(le) } | ||
# linked_expressions.values.flat_map(&:called_linked_expressions).uniq + [linked_root_expression] | ||
end | ||
|
||
def compile | ||
body = called_linked_expressions(linked_root_expression).map(&:code).uniq.join("\n") | ||
linked_root_expr = linked_expressions[root_expression] | ||
|
||
ordered_variables = linked_root_expr.inputs.map { |rv| "v#{evaluator_inputs.index(rv)}" } | ||
raise Errors::InvalidTemplate, 'Codegen failed' if ordered_variables.include?('v') | ||
|
||
body << <<~RUBY | ||
def evaluate(#{Array.new(evaluator_inputs.size) { |i| "v#{i}" }.join(',')}) | ||
#{linked_root_expr.name}(#{ordered_variables.join(',')}) | ||
end | ||
RUBY | ||
puts body | ||
|
||
Class.new.tap { |clazz| | ||
resources.each { |k, v| clazz.const_set(v, k.value) } | ||
clazz.class_eval(body) | ||
}.new | ||
end | ||
|
||
def link(expr) | ||
return linked_expressions[expr] if linked_expressions[expr] | ||
|
||
resolver = Resolver.new(self, expr) | ||
body = expr.code.call(resolver) | ||
args = resolver.argument_names.values | ||
name = method_name(body, args) | ||
|
||
linked_expressions[expr] = LinkedExpression.new( | ||
name, resolver.argument_names.keys, expr, resolver.called_linked_expressions, <<~RUBY) | ||
def #{name}(#{args.join(',')}) | ||
#{body} | ||
end | ||
RUBY | ||
end | ||
|
||
def resource_name(resource) | ||
@resources[resource] ||= "R#{@resources.size}" | ||
end | ||
|
||
def method_name(body, args) | ||
@method_names[[body, args]] ||= "x#{@method_names.size}" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
require 'babl/utils' | ||
|
||
module Babl | ||
module Codegen | ||
LinkedExpression = Utils::Value.new(:name, :inputs, :expression, :called_linked_expressions, :code) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
require 'babl/utils' | ||
|
||
module Babl | ||
module Codegen | ||
Resource = Utils::Value.new(:value) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
require 'babl/utils/value' | ||
|
||
module Babl | ||
module Codegen | ||
class Variable | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.