From 84e789e2c18667f3a3396f0ae584a4f7035adb41 Mon Sep 17 00:00:00 2001 From: Samuel Date: Fri, 23 Aug 2019 14:01:08 -0400 Subject: [PATCH] Superfluid -- initial hack --- Gemfile | 1 + lab.rb | 249 +++++++++++++++++++++++++++++++++++++ lib/liquid/range_lookup.rb | 2 + lib/liquid/tags/for.rb | 2 +- 4 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 lab.rb diff --git a/Gemfile b/Gemfile index 37ffe1d..c7636a5 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,7 @@ end group :test do gem 'rubocop', '~> 0.53.0' + gem 'awesome_print' platform :mri do gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6' diff --git a/lab.rb b/lab.rb new file mode 100644 index 0000000..74fc048 --- /dev/null +++ b/lab.rb @@ -0,0 +1,249 @@ +require 'liquid' +require 'ap' + +AwesomePrint.defaults = { + raw: true +} + +source = <<~LIQUID + +{% assign prime_max = 10000 %} + +{% for candidate in (3..prime_max) %}{% assign prime = 1 %}{% assign less = candidate | minus: 1 %}{% for divisor in (2..less) %}{% assign rem = candidate | modulo: divisor %}{% if rem == 0 %}{% assign prime = 0 %}{% break %}{% endif %}{% endfor %}{% if prime == 1 %}{{ candidate }}, {% endif %}{% endfor %} +LIQUID + +template = Liquid::Template.parse(source) + +def yell!(message) + system("toilet --gay #{message}") +end + +def derp(message, expr) + yell! message + ap expr + exit +end + +class Output + attr_reader :string + + def initialize(initial_indent) + @string = ''.dup + @indent_level = initial_indent + @indent_str = " " * initial_indent * 2 + end + + def <<(line) + @string << @indent_str << line << "\n" + end + + def indent + @indent_level += 1 + @indent_str = " " * @indent_level * 2 + yield + @indent_level -= 1 + @indent_str = " " * @indent_level * 2 + end +end + +class Compiler + class << self + def compile(template) + compiler = new + compiler.compile(template) + compiler.ruby + end + end + + def initialize + @output = Output.new(2) + end + + def ruby + [ + header, + @output.string, + trailer + ].join("\n") + end + + def compile(node) + case node + when Liquid::Document, Liquid::BlockBody + node.nodelist.collect(&method(:compile)) + when Liquid::Variable + compile_variable(node) + when Liquid::For + compile_for(node) + when Liquid::If + compile_if(node) + when Liquid::Template + compile(node.root) + when Liquid::Assign + compile_assign(node) + when String + compile_echo_literal(node) + when Liquid::Break + line "break" + else + yell! "Weird node" + ap node + exit + end + end + + def header + <<~RUBY + class NilUndefinedMethod + def method_missing(*) + nil + end + + def run(liquid_out, strainer) + RUBY + end + + def trailer + <<~RUBY + end + end + NilUndefinedMethod.new.method(:run) + RUBY + end + + def compile_for(node) + variable_name = node.variable_name + + collection_name = node.collection_name + iter_target_expr = case collection_name + when Liquid::VariableLookup + var(collection_name.name) + when Range + collection_name + when Liquid::RangeLookup + start_expr = make_variable_expr(collection_name.start_obj) + end_expr = make_variable_expr(collection_name.end_obj) + "#{start_expr}..#{end_expr}" + else + derp "Weird iter!", collection_name + end + + line "(#{iter_target_expr}).each do |#{var(variable_name)}|" + indent { compile(node.for_block) } + line "end" + end + + def compile_if(node) + condition = node.blocks.first + left = make_variable_expr(condition.left) + right = make_variable_expr(condition.right) + line "if #{left} #{condition.operator} #{right}" + indent { condition.attachment.nodelist.each(&method(:compile)) } + line "end" + end + + def compile_echo_literal(node) + echo node.inspect + end + + def compile_variable(variable) + echo make_variable_expr(variable) + end + + def compile_assign(node) + from_expr = case node.from + when Liquid::Variable + make_variable_expr(node.from) + else + derp "Weird assign", node + end + + line "#{var(node.to)} = #{from_expr}" + end + + def make_variable_expr(variable) + case variable + when Liquid::Variable + base_expression = case variable.name + when Integer + variable.name + when String + variable.name.inspect + when Liquid::VariableLookup + var(variable.name.name) + else + derp "Bad var name", variable.name + end + variable.filters.inject(base_expression) do |inner, filter| + filter_name = filter.first + args = filter.last.map(&method(:make_variable_expr)) + "strainer.invoke(#{filter_name.inspect}, #{inner}, *[#{args.join(', ')}])" + end + when Liquid::VariableLookup + var(variable.name) + else + variable.inspect + end + end + + private + + def line(string) + output << string + end + + def echo(string) + output << "liquid_out.write(#{string})" + end + + def indent(&block) + output.indent(&block) + end + + def var(name) + "__liquid_#{name}" + end + + attr_reader :output +end + +ap template + +context = Liquid::Context.new +strainer = Liquid::Strainer.create(context) + +system('hr -') + +template = Liquid::Template.parse(source) +ruby = Compiler.compile(template) +puts ruby + +system('hr / ') + +puts +puts "Compose + parse + render" +system('hr - ') + +start = Time.now +template = Liquid::Template.parse(source) +ruby = Compiler.compile(template) +instructions = RubyVM::InstructionSequence.compile(ruby) +instructions.eval.call(STDOUT, strainer) +puts Time.now - start + +puts +puts "Render cached" +system('hr - ') + +start = Time.now +instructions.eval.call(STDOUT, strainer) +puts Time.now - start + +puts +puts "Legacy rendering" +system('hr - ') + +start = Time.now +template = Liquid::Template.parse(source) +puts template.render +puts Time.now - start diff --git a/lib/liquid/range_lookup.rb b/lib/liquid/range_lookup.rb index 93bb420..ee3cf1c 100644 --- a/lib/liquid/range_lookup.rb +++ b/lib/liquid/range_lookup.rb @@ -15,6 +15,8 @@ module Liquid @end_obj = end_obj end + attr_reader :start_obj, :end_obj + def evaluate(context) start_int = to_integer(context.evaluate(@start_obj)) end_int = to_integer(context.evaluate(@end_obj)) diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index 71c2f91..a47002b 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -46,7 +46,7 @@ module Liquid class For < Block Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o - attr_reader :collection_name, :variable_name, :limit, :from + attr_reader :collection_name, :variable_name, :limit, :from, :for_block def initialize(tag_name, markup, options) super