├── ruby ├── liquidcpp │ ├── .gitignore │ ├── ext │ │ └── liquidcpp │ │ │ └── extconf.rb │ ├── Rakefile │ ├── lib │ │ └── liquidcpp.rb │ └── t │ │ └── test.rb └── liquidcpp-dir │ ├── .gitignore │ ├── t │ └── test.rb │ ├── liquidcpp-dir.gemspec │ └── lib │ └── liquidcpp-dir.rb ├── perl ├── .gitignore ├── WWW-Shopify-Liquid-XS-0.01.tar.gz ├── MANIFEST ├── test.pl └── Makefile.PL ├── .gitignore ├── src ├── liquid.h ├── web.h ├── optimizer.h ├── optimizer.cpp ├── dialect.h ├── context.cpp ├── compiler.h ├── renderer.h ├── parser.h ├── renderer.cpp ├── interface.h ├── rapidjsonvariable.h ├── cppvariable.h ├── interface.cpp ├── context.h └── common.h ├── CMakeLists.txt ├── extended ├── t │ └── 01sanity.cpp └── src │ └── dialect.cpp ├── Makefile ├── Liquid.cbp ├── configure └── README.md /ruby/liquidcpp/.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | *.o 3 | *.so 4 | tmp 5 | *.gem 6 | pkg 7 | -------------------------------------------------------------------------------- /ruby/liquidcpp-dir/.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | *.o 3 | *.so 4 | tmp 5 | *.gem 6 | pkg 7 | -------------------------------------------------------------------------------- /perl/.gitignore: -------------------------------------------------------------------------------- 1 | MYMETA.json 2 | MYMETA.yml 3 | *.o 4 | pm_to_blib 5 | blib 6 | Makefile 7 | XS.bs 8 | XS.c 9 | -------------------------------------------------------------------------------- /perl/WWW-Shopify-Liquid-XS-0.01.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamharrison/liquid-cpp/HEAD/perl/WWW-Shopify-Liquid-XS-0.01.tar.gz -------------------------------------------------------------------------------- /perl/MANIFEST: -------------------------------------------------------------------------------- 1 | Changes 2 | Makefile.PL 3 | MANIFEST 4 | ppport.h 5 | README 6 | XS.xs 7 | t/WWW-Shopify-Liquid-XS.t 8 | lib/WWW/Shopify/Liquid/XS.pm 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | t/*.o 4 | *.layout 5 | build 6 | *callgrind* 7 | CMakeFiles 8 | CMakeCache.txt 9 | cmake_install.cmake 10 | libliquid.a 11 | -------------------------------------------------------------------------------- /ruby/liquidcpp-dir/t/test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'liquidc-dir' 4 | 5 | template = Liquid::Template.parse("{{ a }}") 6 | 7 | puts template.render({ a => 1 }) 8 | -------------------------------------------------------------------------------- /src/liquid.h: -------------------------------------------------------------------------------- 1 | 2 | #ifdef __cplusplus 3 | #include "context.h" 4 | #include "lexer.h" 5 | #include "parser.h" 6 | #include "renderer.h" 7 | #include "dialect.h" 8 | #include "cppvariable.h" 9 | #endif 10 | #include "interface.h" 11 | -------------------------------------------------------------------------------- /src/web.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDDIALECTSWEB_H 2 | #define LIQUIDDIALECTSWEB_H 3 | #include "dialect.h" 4 | 5 | #if LIQUID_INCLUDE_WEB_DIALECT 6 | namespace Liquid { 7 | struct Context; 8 | 9 | struct WebDialect : Dialect { 10 | static void implement(Context& context); 11 | }; 12 | } 13 | #endif 14 | 15 | #endif 16 | 17 | -------------------------------------------------------------------------------- /ruby/liquidcpp/ext/liquidcpp/extconf.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'mkmf' 4 | 5 | extension_name = 'liquidcpp/liquidcpp' 6 | 7 | dir_config(extension_name); 8 | 9 | # $CFLAGS += " -O0 -g" 10 | $CFLAGS += " -O3" 11 | 12 | $libs = append_library($libs, "stdc++"); 13 | $libs = append_library($libs, "liquid"); 14 | 15 | create_makefile(extension_name); 16 | -------------------------------------------------------------------------------- /src/optimizer.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDOPTIMIZER_H 2 | #define LIQUIDOPTIMIZER_H 3 | 4 | #include "common.h" 5 | 6 | namespace Liquid { 7 | struct Renderer; 8 | struct Node; 9 | 10 | struct Optimizer { 11 | Renderer& renderer; 12 | 13 | Optimizer(Renderer& renderer); 14 | void optimize(Node& ast, Variable store); 15 | }; 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /ruby/liquidcpp-dir/liquidcpp-dir.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new "liquidcpp-dir", "0.1.0" do |s| 2 | s.name = 'liquidcpp-dir' 3 | s.version = '1.0.0' 4 | s.date = '2021-01-03' 5 | s.summary = "LiquidCPP Drop-In-Replacement" 6 | s.description = "A gem that replaces the normal liquid classes with LiquidC versions." 7 | s.authors = ["Adam Harrison"] 8 | s.email = 'adamdharrison@gmail.com' 9 | s.homepage = 'https://github.com/adamharrison/liquid-cpp' 10 | s.license = 'MIT' 11 | s.files = ["lib/liquidcpp-dir.rb"] 12 | s.add_runtime_dependency 'liquidcpp', '~> 1.0' 13 | end 14 | -------------------------------------------------------------------------------- /perl/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | package Test; 7 | 8 | my @a = (1,2,3,5,6,7); 9 | 10 | sub a { return "B"; } 11 | 12 | package main; 13 | 14 | use WWW::Shopify::Liquid::Dialect::Web; 15 | use WWW::Shopify::Liquid::XS; 16 | 17 | my $liquid = WWW::Shopify::Liquid::XS->new; 18 | WWW::Shopify::Liquid::Dialect::Web->apply($liquid); 19 | 20 | my $block = '{{ a.a }}'; 21 | my $ast = $liquid->parse_text($block); 22 | $liquid->renderer->make_method_calls(1); 23 | 24 | my $text = $liquid->render_ast({ a => (bless { }, "Test") }, $ast); 25 | 26 | print STDERR "TEXT: $text END\n"; 27 | 28 | $liquid = undef; 29 | 30 | print STDERR "DONE\n"; 31 | -------------------------------------------------------------------------------- /ruby/liquidcpp/Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/extensiontask" 2 | 3 | spec = Gem::Specification.new "liquidcpp", "0.1.0" do |s| 4 | s.name = 'liquidcpp' 5 | s.version = '1.0.0' 6 | s.date = '2020-12-05' 7 | s.summary = "LiquidCPP" 8 | s.description = "The ruby gem that hooks into the liquid-cpp library." 9 | s.authors = ["Adam Harrison"] 10 | s.email = 'adamdharrison@gmail.com' 11 | s.homepage = 'https://github.com/adamharrison/liquid-cpp' 12 | s.license = 'MIT' 13 | s.extensions = ["ext/liquidcpp/extconf.rb"] 14 | s.files = ["ext/liquidcpp/extconf.rb", "ext/liquidcpp/liquid.c", "lib/liquidcpp.rb", "lib/liquidcpp.so"] 15 | end 16 | 17 | Gem::PackageTask.new(spec) do |pkg| 18 | end 19 | 20 | Rake::ExtensionTask.new('liquidcpp', spec) 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | # set the project name 4 | project(Liquid VERSION 0.1) 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED True) 8 | 9 | if(NOT CMAKE_BUILD_TYPE) 10 | set(CMAKE_BUILD_TYPE Release) 11 | endif() 12 | 13 | set(CMAKE_CXX_FLAGS "-Wall -fPIC") 14 | set(CMAKE_CXX_FLAGS_DEBUG "-g ") 15 | set(CMAKE_CXX_FLAGS_RELEASE "-O2 -s") 16 | FILE(GLOB CPPSources src/*.cpp) 17 | 18 | add_library( liquid ${CPPSources}) 19 | target_link_libraries( liquid ) 20 | 21 | FILE(GLOB HSources src/*.h) 22 | include(GNUInstallDirs) 23 | 24 | # add_definitions(-DLIQUID_INCLUDE_WEB_DIALECT -DLIQUID_INCLUDE_RAPIDJSON_VARIABLE) 25 | 26 | install(TARGETS liquid DESTINATION lib) 27 | install(FILES ${HSources} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/liquid) 28 | -------------------------------------------------------------------------------- /perl/Makefile.PL: -------------------------------------------------------------------------------- 1 | use 5.024002; 2 | use ExtUtils::MakeMaker; 3 | # See lib/ExtUtils/MakeMaker.pm for details of how to influence 4 | # the contents of the Makefile that is written. 5 | WriteMakefile( 6 | NAME => 'WWW::Shopify::Liquid::XS', 7 | VERSION_FROM => 'lib/WWW/Shopify/Liquid/XS.pm', # finds $VERSION, requires EU::MM from perl >= 5.5 8 | PREREQ_PM => {}, # e.g., Module::Name => 1.1 9 | ABSTRACT_FROM => 'lib/WWW/Shopify/Liquid/XS.pm', # retrieve abstract from module 10 | AUTHOR => 'Adam Harrison ', 11 | LICENSE => 'mit', 12 | #Value must be from legacy list of licenses here 13 | #http://search.cpan.org/perldoc?Module%3A%3ABuild%3A%3AAPI 14 | LIBS => ['-L../bin -lliquid -lcrypto -lstdc++'], # e.g., '-lm' 15 | DEFINE => '-DLIQUID_INCLUDE_WEB_DIALECT', # e.g., '-DHAVE_SOMETHING' 16 | INC => '-I.', # e.g., '-I. -I/usr/include/other' 17 | # Un-comment this if you add C files to link with later: 18 | # OBJECT => '$(O_FILES)', # link all the C files too 19 | ); 20 | -------------------------------------------------------------------------------- /src/optimizer.cpp: -------------------------------------------------------------------------------- 1 | #include "optimizer.h" 2 | #include "renderer.h" 3 | 4 | namespace Liquid { 5 | 6 | Optimizer::Optimizer(Renderer& renderer) : renderer(renderer) { 7 | renderer.currentRenderingDepth = 0; 8 | } 9 | 10 | void Optimizer::optimize(Node& ast, Variable store) { 11 | bool hasAnyNonRendered = false; 12 | if (!ast.type || ast.type->optimization == LIQUID_OPTIMIZATION_SCHEME_SHIELD) 13 | return; 14 | for (size_t i = 0; i < ast.children.size(); ++i) { 15 | if (ast.children[i]->type) 16 | optimize(*ast.children[i].get(), store); 17 | if (ast.children[i]->type) { 18 | if (ast.children[i]->type->type == NodeType::Type::ARGUMENTS) { 19 | for (auto& child : ast.children[i]->children) { 20 | if (child->type) 21 | hasAnyNonRendered = true; 22 | } 23 | } else { 24 | hasAnyNonRendered = true; 25 | } 26 | } 27 | } 28 | 29 | if (hasAnyNonRendered) { 30 | if (ast.type->optimization == LIQUID_OPTIMIZATION_SCHEME_PARTIAL) 31 | ast.type->optimize(*this, ast, store); 32 | } else if (ast.type->optimization != LIQUID_OPTIMIZATION_SCHEME_NONE) { 33 | ast.type->optimize(*this, ast, store); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ruby/liquidcpp/lib/liquidcpp.rb: -------------------------------------------------------------------------------- 1 | require "liquidcpp/liquidcpp" 2 | require "json" 3 | 4 | class LiquidCPP 5 | alias_method :orig_initialize, :initialize 6 | def initialize(*args) 7 | orig_initialize(*args) 8 | self.registerFilter("date", 1, 1, LiquidCPP::OPTIMIZATION_SCHEME_NONE, Proc.new { |renderer, node, store, operand, argument| 9 | timeOp = nil 10 | case operand 11 | when String 12 | if operand == 'now' then 13 | timeOp = Time.now 14 | elsif operand.to_i.to_s == operand then #epoch integer enterd as a String 15 | timeOp = Time.at(operand.to_i) 16 | else #raw date entered as string. "2021-09-01T00:00:00-00:00" ex 17 | begin 18 | timeOp = Time.parse(operand) 19 | rescue => e #junk string entered 20 | timeOp = nil 21 | end 22 | end 23 | when Time 24 | timeOp = operand 25 | when Integer #epoch int 26 | timeOp = Time.at(operand) 27 | else 28 | timeOp = nil 29 | end 30 | next unless timeOp && timeOp.class == Time 31 | timeUTC = timeOp.utc 32 | timeUTC.strftime(argument[0].to_s) 33 | }) 34 | self.registerFilter("json", 0, 0, LiquidCPP::OPTIMIZATION_SCHEME_NONE, Proc.new { |renderer, node, store, operand, argument| 35 | operand.to_json 36 | }) 37 | end 38 | end -------------------------------------------------------------------------------- /extended/t/01sanity.cpp: -------------------------------------------------------------------------------- 1 | #include "../src/context.h" 2 | #include "../src/lexer.h" 3 | #include "../src/parser.h" 4 | #include "../src/renderer.h" 5 | #include "../src/dialect.h" 6 | #include "../src/cppvariable.h" 7 | 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace Liquid; 13 | 14 | Context& getContext() { 15 | static bool setup = false; 16 | static Context context; 17 | if (!setup) { 18 | StandardDialect::implementPermissive(context); 19 | #ifdef LIQUID_INCLUDE_WEB_DIALECT 20 | WebDialect::implement(context); 21 | #endif 22 | setup = true; 23 | } 24 | return context; 25 | } 26 | 27 | Renderer& getRenderer() { 28 | static Renderer renderer(getContext(), CPPVariableResolver()); 29 | return renderer; 30 | } 31 | Parser& getParser() { 32 | static Parser parser(getContext()); 33 | return parser; 34 | } 35 | 36 | TEST(sanity, extended) { 37 | 38 | CPPVariable hash = { }; 39 | hash["a"] = 1; 40 | Node ast; 41 | std::string str; 42 | 43 | ast = getParser().parse("{% scss %}.a { .b { margin-left: 2px; } }{% endscss %}"); 44 | str = getRenderer().render(ast, hash); 45 | ASSERT_EQ(str, ".a .b {\n margin-left: 2px; }\n"); 46 | 47 | ast = getParser().parse("{% minify_css %}.a .b { margin-left: 2px; }{% endminify_css %}"); 48 | str = getRenderer().render(ast, hash); 49 | ASSERT_EQ(str, ".a .b{margin-left:2px}"); 50 | 51 | ast = getParser().parse("{% minify_js %}var a = 1 + 2 + 3 + 4; console.log(a);{% endminify_js %}"); 52 | str = getRenderer().render(ast, hash); 53 | ASSERT_EQ(str, "var a=1+2+3+4;console.log(a);"); 54 | 55 | } 56 | 57 | int main(int argc, char **argv) { 58 | ::testing::InitGoogleTest(&argc, argv); 59 | return RUN_ALL_TESTS(); 60 | }; 61 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ODIR=obj 2 | SDIR=src 3 | BDIR=bin 4 | TDIR=t 5 | CXX=g++ 6 | CC=gcc 7 | CFLAGS=-Wall -fexceptions -fPIC -DLIQUID_INCLUDE_WEB_DIALECT -DLIQUID_INCLUDE_RAPIDJSON_VARIABLE -Wno-deprecated-declarations 8 | CXXFLAGS=$(CFLAGS) -std=c++17 9 | LDFLAGS=-lcrypto -lssl 10 | AR=ar 11 | RUBY_DIR=ruby 12 | PERL_DIR=perl 13 | SOURCES=$(wildcard $(SDIR)/*.cpp) $(wildcard $(SDIR)/*.c) $(wildcard $(TDIR)/*.cpp) 14 | TESTSOURCES=$(wildcard $(TDIR)/*.cpp) 15 | OBJECTS=$(patsubst %.cpp,%.o,$(patsubst %.c,%.o,$(patsubst $(SDIR)/%,$(ODIR)/%,$(SOURCES)))) 16 | NONLIBRARYSOURCES=$(TESTSOURCES) 17 | LIBRARYSOURCES=$(filter-out $(NONLIBRARYSOURCES),$(SOURCES)) 18 | LIBRARYOBJECTS=$(patsubst %.cpp,%.o,$(patsubst %.c,%.o,$(patsubst $(SDIR)/%,$(ODIR)/%,$(LIBRARYSOURCES)))) 19 | LIBRARY = $(BDIR)/libliquid.a 20 | DEPENDS=$(wildcard $(ODIR)/*.d) 21 | DEBUGFLAGS=-g3 -pthread -gdwarf-4 -Wno-deprecated -fno-elide-type -fdiagnostics-show-template-tree -Wall -Werror -Wextra -Wvla -Wextra-semi -Wnull-dereference -fvar-tracking-assignments -Wduplicated-cond -Wduplicated-branches -Wsuggest-override -Wno-unused-parameter 22 | 23 | $(BDIR)/01sanity: $(LIBRARY) $(ODIR)/01sanity.o 24 | $(CXX) $(ODIR)/01sanity.o -L$(BDIR) -lliquid -lgtest -lpthread -o $(BDIR)/01sanity $(LDFLAGS) 25 | 26 | test: CFLAGS := $(CFLAGS) $(DEBUGFLAGS) 27 | test: $(BDIR)/01sanity 28 | 29 | $(LIBRARY): $(LIBRARYOBJECTS) 30 | $(AR) -r -s $(LIBRARY) $(LIBRARYOBJECTS) 31 | 32 | $(ODIR)/%.d: $(SDIR)/%.cpp 33 | $(CXX) $(CXXFLAGS) -MM $< -MMD -MP -MT '$(patsubst %.d,%.o,$@)' -MF $@ 34 | 35 | $(ODIR)/%.d: $(TDIR)/%.cpp 36 | $(CXX) $(CXXFLAGS) -MM $< -MMD -MP -MT '$(patsubst %.d,%.o,$@)' -MF $@ 37 | 38 | $(ODIR)/%.o: $(SDIR)/%.cpp $(ODIR)/%.d 39 | $(CXX) $(CXXFLAGS) -c $< -o $@ 40 | 41 | $(ODIR)/%.o: $(TDIR)/%.cpp $(ODIR)/%.d 42 | $(CXX) $(CXXFLAGS) -c $< -o $@ 43 | 44 | $(ODIR)/%.o: $(SDIR)/%.d 45 | $(CC) $(CFLAGS) -M $< -MMD -MP -MF $@ 46 | 47 | $(ODIR)/%.o: $(SDIR)/%.c $(ODIR)/%.d 48 | $(CC) $(CFLAGS) -c $< -o $@ 49 | 50 | .PRECIOUS: $(ODIR)/%.d 51 | 52 | perl : $(LIBRARY) 53 | cd $(PERL_DIR) && perl Makefile.PL && ln -f -s ../$(LIBRARY) 54 | $(MAKE) -C $(PERL_DIR) clean 55 | $(MAKE) -C $(PERL_DIR) 56 | 57 | library: $(LIBRARY) 58 | 59 | cleanlibrary: clean 60 | 61 | directories: $(ODIR) $(BDIR) $(TDIR) 62 | 63 | cleantest: clean 64 | 65 | $(ODIR): 66 | mkdir -p $(ODIR) 67 | $(BDIR): 68 | mkdir -p $(BDIR) 69 | $(TDIR): 70 | mkdir -p $(TDIR) 71 | 72 | -include $(DEPENDS) 73 | 74 | libraryRelease: CFLAGS := $(CFLAGS) -O3 -s 75 | libraryRelease: library 76 | 77 | libraryDebug: CFLAGS := $(CFLAGS) $(DEBUGFLAGS) 78 | libraryDebug: library 79 | 80 | clean: 81 | rm -f $(ODIR)/*.o $(ODIR)/*.d $(LIBRARY) 82 | 83 | -------------------------------------------------------------------------------- /extended/src/dialect.cpp: -------------------------------------------------------------------------------- 1 | 2 | struct MinifyJSNode : TagNodeType { 3 | MinifyJSNode() : TagNodeType(Composition::ENCLOSED, "minify_js", 0, 1) { } 4 | Node render(Renderer& renderer, const Node& node, Variable store) const { 5 | return Variant(LiquidJS::JsMinify(renderer.retrieveRenderedNode(*node.children.back().get(), store).getString())); 6 | } 7 | }; 8 | 9 | struct MinifyCSSNode : TagNodeType { 10 | MinifyCSSNode() : TagNodeType(Composition::ENCLOSED, "minify_css", 0, 1) { } 11 | Node render(Renderer& renderer, const Node& node, Variable store) const { 12 | return Variant(LiquidCSS::CssMinify(renderer.retrieveRenderedNode(*node.children.back().get(), store).getString())); 13 | } 14 | }; 15 | 16 | struct MinifyHTMLNode : TagNodeType { 17 | MinifyHTMLNode() : TagNodeType(Composition::ENCLOSED, "minify_html", 0, 1) { } 18 | 19 | Node render(Renderer& renderer, const Node& node, Variable store) const { 20 | return renderer.retrieveRenderedNode(*node.children.back().get(), store); 21 | } 22 | }; 23 | 24 | struct SCSSNode : TagNodeType { 25 | SCSSNode() : TagNodeType(Composition::ENCLOSED, "scss", 0, 1) { } 26 | 27 | Node render(Renderer& renderer, const Node& node, Variable store) const { 28 | string s = renderer.retrieveRenderedNode(*node.children.back().get(), store).getString(); 29 | 30 | char* text = sass_copy_c_string(s.c_str()); 31 | struct Sass_Data_Context* data_ctx = sass_make_data_context(text); 32 | struct Sass_Context* ctx = sass_data_context_get_context(data_ctx); 33 | // context is set up, call the compile step now 34 | // int status = sass_compile_data_context(data_ctx); 35 | // if (status != 0) { 36 | // sass_delete_data_context(data_ctx); 37 | // return Node(); 38 | // } 39 | // string result = string(sass_context_get_output_string(ctx)); 40 | // sass_delete_data_context(data_ctx); 41 | // return Variant(move(result)); 42 | // } 43 | // }; 44 | -------------------------------------------------------------------------------- /src/dialect.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDDIALECT_H 2 | #define LIQUIDDIALECT_H 3 | #include "common.h" 4 | 5 | namespace Liquid { 6 | struct Context; 7 | 8 | // Standard dialect of liquid, that implements core Liquid as described here: 9 | // https://shopify.dev/docs/themes/liquid/reference, https://github.com/Shopify/liquid 10 | // There are a number of non-standard features we can invoke which make life easier if you're actually writing Liquid. 11 | // As such, you can either enable/disable features one at a time, or call `implementStrict`, `implementShopify`, or `implementPermissive` for the 12 | // defaults that are least/least/most permissive. 13 | struct Dialect {}; 14 | 15 | // The only tags missing from the standard set of non-web tags is {% include %}. 16 | struct StandardDialect :Dialect { 17 | 18 | static void implement( 19 | Context& context, 20 | // If true, makes it so that {% assign %} can only target whole variable names; no derefernces allowed. So no {% assign a.b = 1 %}. 21 | bool globalAssignsOnly, 22 | // If true, ignores parentheses, and mostly ignores order of operations. 23 | bool disallowParentheses, 24 | // If true, disables all operators outside of the {% assign %} tag, or the {% if %} and {% unless %} tags (for comparaison operators) as in Shopify. 25 | bool assignConditionalOperatorsOnly, 26 | // if true, removes the ablility to use filters anywhere but {% assign %} and output tags {{ }}. 27 | bool assignOutputFiltersOnly, 28 | // If true, removed the ability to specify arrays with [] notation. 29 | bool disableArrayLiterals, 30 | // Determines whether something is truthy/falsy. 31 | // In strict; this is set to FALSY_NIL, which emulates ruby; meaning only NIL, and the false boolean are false. 32 | // In permissive; this is set to FALSY_EMPTY_STRING | FALSY_NIL | FALSY_0, which emulates perl, where all those are treated as false (the sane option, really). 33 | EFalsiness falsiness, 34 | // Determines to what degree things should be coerced when performing operations. 35 | ECoercion coerciveness 36 | ); 37 | 38 | static void implementStrict(Context& context) { 39 | implement( 40 | context, 41 | true, 42 | true, 43 | true, 44 | true, 45 | true, 46 | FALSY_NIL, 47 | COERCE_NONE 48 | ); 49 | } 50 | 51 | static void implementShopify(Context& context) { 52 | implementStrict(context); 53 | } 54 | 55 | static void implementPermissive(Context& context) { 56 | implement( 57 | context, 58 | false, 59 | false, 60 | false, 61 | false, 62 | false, 63 | (EFalsiness)(FALSY_NIL | FALSY_0 | FALSY_EMPTY_STRING), 64 | COERCE_ALL 65 | ); 66 | } 67 | }; 68 | } 69 | 70 | #ifdef LIQUID_INCLUDE_WEB_DIALECT 71 | #include "web.h" 72 | #endif 73 | 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /ruby/liquidcpp/t/test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'liquid' 4 | require 'liquid/c' 5 | require 'liquidcpp' 6 | 7 | context = LiquidCPP.new("strict") 8 | renderer = LiquidCPP::Renderer.new(context) 9 | parser = LiquidCPP::Parser.new(context) 10 | compiler = LiquidCPP::Compiler.new(context) 11 | optimizer = LiquidCPP::Optimizer.new(renderer) 12 | 13 | context.registerFilter("test", -1, -1, LiquidCPP::OPTIMIZATION_SCHEME_NONE, Proc.new { |renderer, node, stash, operand| 14 | "operand" + operand 15 | }) 16 | context.registerTag("enclosingtag", LiquidCPP::TAG_TYPE_ENCLOSING, -1, -1, LiquidCPP::OPTIMIZATION_SCHEME_NONE, Proc.new { |renderer, node, stash, child, arguments| 17 | 'enclosed' 18 | }) 19 | context.registerTag("freetag", LiquidCPP::TAG_TYPE_FREE, -1, -1, LiquidCPP::OPTIMIZATION_SCHEME_NONE, Proc.new { |renderer, node, stash, arguments| 20 | 'free' 21 | }) 22 | 23 | require 'json' 24 | 25 | tmpl = parser.parseTemplate("{{ product.id }}") 26 | json = JSON.parse('{"product":{"id":1}}') 27 | 28 | puts "TEST1: " + renderer.render(json, tmpl) 29 | 30 | 31 | 32 | text = renderer.render({ }, parser.parseTemplate("{% freetag %}")) 33 | puts "TEST: " + text 34 | raise "No warnings: " + parser.warnings.to_json + "." if parser.warnings.size > 0 35 | 36 | text = renderer.render({ }, parser.parseTemplate("{{ \"a\" | test }}")) 37 | puts "TEST: " + text 38 | raise "No warnings: " + parser.warnings.to_json + "." if parser.warnings.size > 0 39 | 40 | text = renderer.render({ }, parser.parseTemplate("{% enclosingtag i %}{% endenclosingtag %}")) 41 | puts "TEST: " + text 42 | raise "No warnings: " + parser.warnings.to_json + "." if parser.warnings.size > 0 43 | 44 | 45 | puts renderer.render({ }, parser.parseTemplate("{% endif %}")) 46 | puts parser.warnings 47 | puts renderer.warnings 48 | 49 | templateContent = "{% if a %}asdfghj {{ a }}{% else %}asdfjlsjkhgsjlkhglsdfjkgdfhs{% for i in (1..100) %}{{ i }}fasdfsdf{% endfor %}{% endif %}" 50 | 51 | 52 | puts "A" 53 | 54 | template1 = parser.parseTemplate(templateContent) 55 | optimizer.optimize({ }, template1); 56 | template2 = compiler.compileTemplate(template1) 57 | template3 = Liquid::Template.parse(templateContent) 58 | 59 | # raise compiler.decompileProgram(template2) 60 | 61 | iterations = 10000 62 | 63 | start = Time.now 64 | 65 | i = 0 66 | (1..iterations).each { |x| 67 | ++i 68 | } 69 | 70 | puts (Time.now - start)*1000 71 | 72 | start = Time.now 73 | 74 | s = nil 75 | (1..iterations).each { |x| 76 | s = renderer.render({ "a" => false }, template1) 77 | } 78 | puts s 79 | 80 | puts (Time.now - start)*1000 81 | 82 | start = Time.now 83 | 84 | s = nil 85 | (1..iterations).each { |x| 86 | s = renderer.render({ "a" => false }, template2) 87 | } 88 | puts s 89 | 90 | puts (Time.now - start)*1000 91 | 92 | 93 | start = Time.now 94 | 95 | (1..iterations).each { |x| 96 | s = template3.render({ "a" => false }) 97 | } 98 | puts s 99 | 100 | puts (Time.now - start)*1000 101 | 102 | 103 | templateContent = '{{ "asdadasdAS" | test }}' 104 | 105 | puts "WAT"; 106 | 107 | templateNew = parser.parseTemplate(templateContent); 108 | 109 | puts "TEMPLATE NEW: #{templateNew}"; 110 | 111 | puts "TEST: " + renderer.render({ }, templateNew); 112 | 113 | puts "D"; 114 | -------------------------------------------------------------------------------- /Liquid.cbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 109 | 110 | -------------------------------------------------------------------------------- /ruby/liquidcpp-dir/lib/liquidcpp-dir.rb: -------------------------------------------------------------------------------- 1 | require 'liquidcpp' 2 | 3 | module Liquid 4 | 5 | class Tag 6 | end 7 | 8 | class Block < Tag 9 | attr_accessor :body 10 | 11 | def render(stash) 12 | return self.body 13 | end 14 | end 15 | 16 | class Include < Tag 17 | def render(stash) 18 | 19 | end 20 | end 21 | 22 | class LocalFileSystem 23 | attr_accessor :root 24 | 25 | def initialize(path) 26 | self.root = path 27 | end 28 | 29 | def full_path(path) 30 | 31 | end 32 | end 33 | 34 | 35 | 36 | class Error < StandardError 37 | def self.translate_error(e) 38 | if e.is_a?(LiquidCPP::Parser::Error) 39 | case e.type 40 | when 2 41 | error = SyntaxError.new("Unknown tag '" + e.message + "'", e.line, e.file) 42 | when 7 43 | error = SyntaxError.new("Invalid arguments for '" + e.args[0] + "', expected " + e.args[1] + ", got" + e.args[1], e.line, e.file) 44 | else 45 | error = SyntaxError.new(e.message, e.line, e.file) 46 | end 47 | end 48 | end 49 | end 50 | 51 | class SyntaxError < Error 52 | attr_accessor :line_number 53 | attr_accessor :template_name 54 | 55 | def initialize(message, line_number, template_name) 56 | super("Liquid syntax error: " + message) 57 | self.line_number = line_number 58 | self.template_name = template_name 59 | end 60 | end 61 | 62 | class ArgumentError < Error 63 | 64 | end 65 | 66 | class FileSystemError < Error 67 | 68 | end 69 | 70 | class ContextError < Error 71 | 72 | end 73 | 74 | class StackLevelError < Error 75 | 76 | end 77 | 78 | class MemoryError < Error 79 | 80 | end 81 | 82 | class ZeroDivisionError < Error 83 | 84 | end 85 | 86 | class FloatDomainError < Error 87 | 88 | end 89 | 90 | class UndefinedVariable < Error 91 | 92 | end 93 | 94 | class UndefinedDropMethod < Error 95 | 96 | end 97 | 98 | class UndefinedFilter < Error 99 | 100 | end 101 | 102 | class MethodOverrideError < Error 103 | 104 | end 105 | 106 | class DisabledError < Error 107 | 108 | end 109 | 110 | class InternalError < Error 111 | 112 | end 113 | 114 | class Template 115 | attr_reader :template 116 | attr_accessor :errors 117 | 118 | @@globalContext = LiquidCPP.new("strict") 119 | @@globalErrorMode = :lax 120 | 121 | def self.error_mode 122 | @@globalErrorMode 123 | end 124 | 125 | def self.error_mode=new_mode 126 | @@globalErrorMode = new_mode 127 | end 128 | 129 | def initialize(str, attrs) 130 | parser = LiquidCPP::Parser.new(@@globalContext) 131 | error_mode = attrs[:error_mode] || @@globalErrorMode 132 | error = nil 133 | begin 134 | @template = parser.parse(str) 135 | warnings = parser.warnings 136 | if error_mode != :lax && warnings.size > 0 137 | self.errors = warnings 138 | if error_mode == :strict 139 | raise self.errors[0] 140 | end 141 | end 142 | # Raise on exceptions that :lax raises on, which is apparently not accepting of "everything" as the docs say. 143 | raise warnings.select { |x| x.type == 2 }.first if warnings.select { |x| x.type == 2 }.size > 0 144 | rescue LiquidCPP::Parser::Error => e 145 | error = Liquid::Error.translate_error(e) 146 | end 147 | raise error if error 148 | end 149 | 150 | def self.parse(str, attrs = {}) 151 | return Liquid::Template.new(str, attrs) 152 | end 153 | 154 | def render(stash, attrs = {}) 155 | renderer = LiquidCPP::Renderer.new(@@globalContext) 156 | renderer.setStrictVariables(true) if attrs[:strict_variables] 157 | renderer.setStrictFilters(true) if attrs[:strict_filters] 158 | result = renderer.render(stash, self.template) 159 | self.errors = renderer.warnings 160 | return result 161 | end 162 | 163 | # I don't know wtf they were thinking. 164 | def self.register_filter(mod) 165 | mod.public_instance_methods.each { |x| 166 | @@globalContext.registerFilter(x.to_s, -1, -1, LiquidC::OPTIMIZATION_SCHEME_FULL, Proc.new{ |renderer, node, stash, operand| 167 | mod[x].call(operand) 168 | }) 169 | } 170 | end 171 | 172 | def self.register_tag(name, klass) 173 | if klass.is_a?(Liquid::Block) 174 | @@globalContext.registerTag(name, LiquidC::TAG_TYPE_ENCLOSING, -1, -1, LiquidC::OPTIMIZATION_SCHEME_FULL, Proc.new { |renderer, node, stash, child, arguments| 175 | klass.new(name, *arguments).render(stash) 176 | }) 177 | else 178 | @@globalContext.registerTag(name, LiquidC::TAG_TYPE_FREE, -1, -1, LiquidC::OPTIMIZATION_SCHEME_FULL, Proc.new { |renderer, node, stash, child, arguments| 179 | tag = klass.new(name, *arguments) 180 | tag.instance_variable_set(:body, child) 181 | tag.render(stash) 182 | }) 183 | end 184 | end 185 | end 186 | 187 | Liquid::Template.registerTag("include", Liquid::Include) 188 | end 189 | -------------------------------------------------------------------------------- /src/context.cpp: -------------------------------------------------------------------------------- 1 | #include "context.h" 2 | #include "optimizer.h" 3 | #include "compiler.h" 4 | #include "dialect.h" 5 | 6 | namespace Liquid { 7 | 8 | bool NodeType::optimize(Optimizer& optimizer, Node& node, Variable store) const { 9 | node = render(optimizer.renderer, node, store); 10 | return true; 11 | } 12 | 13 | bool Context::VariableNode::optimize(Optimizer& optimizer, Node& node, Variable store) const { 14 | auto storePointer = optimizer.renderer.getVariable(node, store); 15 | if (!storePointer.first) 16 | return false; 17 | node = Node(optimizer.renderer.parseVariant(storePointer.second)); 18 | return true; 19 | } 20 | 21 | Node NodeType::render(Renderer& renderer, const Node& node, Variable store) const { 22 | if (!userRenderFunction) 23 | return Node(); 24 | userRenderFunction(LiquidRenderer{&renderer}, LiquidNode{const_cast(&node)}, store, userData); 25 | return renderer.returnValue; 26 | } 27 | 28 | 29 | 30 | Node OperatorNodeType::getOperand(Renderer& renderer, const Node& node, Variable store, int idx) const { 31 | if (renderer.mode == Renderer::ExecutionMode::INTERPRETER) { 32 | return static_cast(renderer).getStack(-1 - idx); 33 | } else { 34 | return renderer.retrieveRenderedNode(*node.children[idx].get(), store); 35 | } 36 | } 37 | 38 | 39 | Node FilterNodeType::getOperand(Renderer& renderer, const Node& node, Variable store) const { 40 | if (renderer.mode == Renderer::ExecutionMode::INTERPRETER) { 41 | return static_cast(renderer).getStack(-1); 42 | } else { 43 | return renderer.retrieveRenderedNode(*node.children[0].get(), store); 44 | } 45 | } 46 | 47 | 48 | Node FilterNodeType::getArgument(Renderer& renderer, const Node& node, Variable store, int idx) const { 49 | if (renderer.mode == Renderer::ExecutionMode::INTERPRETER) { 50 | return static_cast(renderer).getStack(-1 - (idx+1)); 51 | } else { 52 | int offset = node.type->type == NodeType::Type::TAG ? 0 : 1; 53 | if (idx >= (int)node.children[offset]->children.size()) 54 | return Node(); 55 | assert(node.children[offset]->type->type == NodeType::Type::ARGUMENTS); 56 | return renderer.retrieveRenderedNode(*node.children[offset]->children[idx].get(), store); 57 | } 58 | } 59 | 60 | Node DotFilterNodeType::getOperand(Renderer& renderer, const Node& node, Variable store) const { 61 | if (renderer.mode == Renderer::ExecutionMode::INTERPRETER) 62 | return static_cast(renderer).getStack(-1); 63 | return renderer.retrieveRenderedNode(*node.children[0].get(), store); 64 | } 65 | 66 | Node NodeType::getArgument(Renderer& renderer, const Node& node, Variable store, int idx) const { 67 | if (renderer.mode == Renderer::ExecutionMode::INTERPRETER) { 68 | return static_cast(renderer).getStack(-1 - (idx+1)); 69 | } else { 70 | int offset = node.type->type == NodeType::Type::TAG ? 0 : 1; 71 | if (idx >= (int)node.children[offset]->children.size()) 72 | return Node(); 73 | assert(node.children[offset]->type->type == NodeType::Type::ARGUMENTS); 74 | return renderer.retrieveRenderedNode(*node.children[offset]->children[idx].get(), store); 75 | } 76 | } 77 | int NodeType::getArgumentCount(const Node& node) const { 78 | int offset = node.type->type == NodeType::Type::TAG ? 0 : 1; 79 | assert(node.children[offset]->type->type == NodeType::Type::ARGUMENTS); 80 | return node.children[offset]->children.size(); 81 | } 82 | 83 | Node NodeType::getChild(Renderer& renderer, const Node& node, Variable store, int idx) const { 84 | if (renderer.mode == Renderer::ExecutionMode::INTERPRETER) { 85 | return static_cast(renderer).getStack(-1 - (idx+1)); 86 | } else { 87 | if (idx >= (int)node.children.size()) 88 | return Node(); 89 | return renderer.retrieveRenderedNode(*node.children[idx].get(), store); 90 | } 91 | } 92 | int NodeType::getChildCount(const Node& node) const { 93 | return node.children.size(); 94 | } 95 | 96 | 97 | Node Context::ConcatenationNode::render(Renderer& renderer, const Node& node, Variable store) const { 98 | if (++renderer.currentRenderingDepth > renderer.maximumRenderingDepth) { 99 | --renderer.currentRenderingDepth; 100 | renderer.error = LIQUID_RENDERER_ERROR_TYPE_EXCEEDED_DEPTH; 101 | return Node(); 102 | } 103 | if (node.children.size() == 1) { 104 | --renderer.currentRenderingDepth; 105 | return renderer.retrieveRenderedNode(*node.children.front().get(), store); 106 | } 107 | string s; 108 | for (auto& child : node.children) { 109 | s.append(renderer.retrieveRenderedNode(*child.get(), store).getString()); 110 | if (renderer.error != LIQUID_RENDERER_ERROR_TYPE_NONE) 111 | return Node(); 112 | if (renderer.control != Renderer::Control::NONE) { 113 | --renderer.currentRenderingDepth; 114 | return Node(move(s)); 115 | } 116 | } 117 | --renderer.currentRenderingDepth; 118 | return Node(move(s)); 119 | } 120 | 121 | bool Context::ConcatenationNode::optimize(Optimizer& optimizer, Node& node, Variable store) const { 122 | if (++optimizer.renderer.currentRenderingDepth > optimizer.renderer.maximumRenderingDepth) { 123 | --optimizer.renderer.currentRenderingDepth; 124 | return false; 125 | } 126 | string s; 127 | vector> newChildren; 128 | for (auto& child : node.children) { 129 | if (child->type) { 130 | if (!s.empty()) 131 | newChildren.push_back(make_unique(Variant(move(s)))); 132 | newChildren.push_back(move(child)); 133 | } else 134 | s.append(child->getString()); 135 | } 136 | if (newChildren.size() == 0) { 137 | node = Variant(move(s)); 138 | } else { 139 | newChildren.push_back(make_unique(Variant(move(s)))); 140 | node.children = move(newChildren); 141 | } 142 | --optimizer.renderer.currentRenderingDepth; 143 | return true; 144 | } 145 | 146 | Context::Context(int dialects) { 147 | if (dialects & STRICT_STANDARD_DIALECT) 148 | StandardDialect::implementStrict(*this); 149 | else if (dialects & PERMISSIVE_STANDARD_DIALECT) 150 | StandardDialect::implementStrict(*this); 151 | else if (dialects & WEB_DIALECT) { 152 | #ifdef LIQUID_INCLUDE_WEB_DIALECT 153 | WebDialect::implement(*this); 154 | #else 155 | assert(false); 156 | #endif 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/compiler.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDCOMPILER_H 2 | #define LIQUIDCOMPILER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "common.h" 10 | #include "renderer.h" 11 | 12 | namespace Liquid { 13 | 14 | 15 | // Liquid is primarily unary operations on things with filters. So we have a primary register which most things occupy, and then the stack where everything that is not in primary position is affecte dby. 16 | // When referring to an operand, the return register is always 0x0, and anything else is that 4-byte position in the code array. 17 | // Every OP code has 2 operands. 18 | // Register 0, is known as the "return" register. As much work as possible is done in this memory, and basically all operations will assume 19 | // that this register is what's returning from any given thing. 20 | enum OPCode { 21 | OP_MOV, // Copies one register to another. 22 | OP_MOVSTR, // Pushes the operand from memory into the register. 23 | OP_MOVINT, // Pushes the operand into the register. 24 | OP_MOVBOOL, // Pushes the operand into the register. 25 | OP_MOVFLOAT, // Pushes the operand into the register. 26 | OP_MOVNIL, // Pushes the operand into the register. 27 | OP_STACK, // Pulls the nth item of the stack into a register. 28 | OP_PUSH, // Pushes the operand onto the stack, from the specified register. 29 | OP_POP, // Moves the stack point back to the preivous variable. 30 | OP_ADD, // Adds regsiter 0x0 and the target register. 31 | OP_SUB, // Subtracts the target register from 0x0. 32 | OP_EQL, // Checks whether the register is equal to register 0x0. 33 | OP_OUTPUT, // Takes the return register, and appends it to the selected output buffer. 34 | OP_OUTPUTMEM, // Takes the targeted memory address, and appends it to the selected output buffer. Optimized version of OP_OUTPUT to reduce copying. 35 | OP_ASSIGN, // Assigning a variable specified in the target register. Key is at 0x0, and value is at the operand register. 36 | OP_JMP, // Unconditional jump. 37 | OP_JMPFALSE, // Jumps to the instruction if primary register is false. 38 | OP_JMPTRUE, // Jumps to the instruction if the priamry register is true. 39 | OP_CALL, // Calls the function specified with the amount of arugments on the stack. 40 | OP_RESOLVE, // Resovles the named variable in the register and places it into the same register. Operand is either 0x0, for the top-level context, or a register, which contains the context for the next deference., 41 | OP_LENGTH, // Gets the length of the specified variable held in the target register, and puts it into 0x0. 42 | OP_ITERATE, // Iterates through the variable in the specified register, and pops the value into 0x0. If iteration is over, JMPs to the specified instruction. 43 | OP_INVERT, // Coerces to a boolean 44 | OP_PUSHBUFFER, // Pushes a buffer onto to the buffer stack, with the contents of the target register. 45 | OP_POPBUFFER, // Pops a buffer off the buffer stack, flushing the contents of the buffer to the target register. 46 | OP_EXIT // Quits the program. 47 | }; 48 | 49 | bool hasOperand(OPCode opcode); 50 | const char* getSymbolicOpcode(OPCode opcode); 51 | 52 | // Entrypoint is always codeOffset. 53 | // Then comes the data segment, where all strings are located. 54 | // Then comes the actual code segment. 55 | struct Program { 56 | unsigned int codeOffset; 57 | std::vector code; 58 | }; 59 | 60 | struct Compiler { 61 | std::vector data; 62 | std::vector code; 63 | int freeRegister; 64 | std::unordered_map existingStrings; 65 | 66 | const Context& context; 67 | // For forloops, and the forloop vairables, amongst other things. Space should likely be allocated only when actually needed. 68 | // Returns the offset from the current stack frame. 69 | struct DropFrameState { 70 | int stackPoint; 71 | }; 72 | int stackSize; 73 | typedef int (*DropFrameCallback)(Compiler& compiler, DropFrameState& state, const Node& node); 74 | std::unordered_map>> dropFrames; 75 | 76 | void addDropFrame(const std::string& name, DropFrameCallback callback) { 77 | dropFrames[name].emplace_back(callback, DropFrameState { stackSize }); 78 | } 79 | 80 | void clearDropFrame(const std::string& name) { 81 | dropFrames[name].pop_back(); 82 | } 83 | 84 | Compiler(const Context& context); 85 | ~Compiler(); 86 | 87 | int add(const char* str, int length); 88 | int add(OPCode code, int target); 89 | int add(OPCode code, int target, long long operand); 90 | 91 | int addPush(int target); 92 | int addPop(int amount); 93 | 94 | void modify(int offset, OPCode code, int target, long long operand); 95 | int currentOffset() const; 96 | 97 | // Called internally. 98 | int compileBranch(const Node& branch); 99 | Program compile(const Node& tmpl); 100 | 101 | string disassemble(const Program& program); 102 | }; 103 | 104 | struct Interpreter : Renderer { 105 | static constexpr int SHORT_STRING_SIZE = 64; 106 | 107 | struct Register { 108 | enum class Type { 109 | FLOAT, 110 | INT, 111 | BOOL, 112 | NIL, 113 | SHORT_STRING, // Inline, or in a register. 114 | LONG_STRING, // On stack. Up to 65k; short is multiplexed with the type info. 115 | EXTRA_LONG_STRING, // In memory. 116 | VARIABLE // 3rd party variable. 117 | }; 118 | 119 | Type type; 120 | union { 121 | double f; 122 | bool b; 123 | long long i; 124 | void* pointer; 125 | struct { 126 | unsigned char length; 127 | char buffer[SHORT_STRING_SIZE]; 128 | }; 129 | }; 130 | }; 131 | 132 | static constexpr int STACK_SIZE = 100*1024; 133 | static constexpr int TOTAL_REGISTERS = 4; 134 | static constexpr int MAX_FRAMES = 128; 135 | 136 | stack buffers; 137 | 138 | Register registers[TOTAL_REGISTERS]; 139 | const unsigned int* instructionPointer; 140 | char* stackPointer; 141 | 142 | int frames[MAX_FRAMES]; 143 | char stackBlock[STACK_SIZE]; 144 | 145 | Interpreter(const Context& context); 146 | Interpreter(const Context& context, LiquidVariableResolver resolver); 147 | ~Interpreter(); 148 | 149 | // Should be used for conversions only, not for actual optimized code. 150 | Node getStack(int i); 151 | void getStack(Register& reg, int i); 152 | void popStack(int i); 153 | void pushStack(Register& reg); 154 | void pushRegister(Register& reg, const Node& node); 155 | void pushRegister(Register& reg, const string& str); 156 | void pushRegister(Register& reg, string&& str); 157 | 158 | bool run(const unsigned char* code, Variable store, void (*callback)(const char* chunk, size_t len, void* data), void* data, const unsigned char* iteration = nullptr); 159 | 160 | void renderTemplate(const Program& tmpl, Variable store, void (*)(const char* chunk, size_t len, void* data), void* data); 161 | string renderTemplate(const Program& tmpl, Variable store); 162 | }; 163 | } 164 | 165 | #endif 166 | -------------------------------------------------------------------------------- /src/renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDRENDERER_H 2 | #define LIQUIDRENDERER_H 3 | 4 | #include "parser.h" 5 | 6 | namespace Liquid { 7 | struct Context; 8 | struct ContextBoundaryNode; 9 | 10 | // One renderer per thread; though many renderers can be instantiated. 11 | struct Renderer { 12 | const Context& context; 13 | 14 | enum class ExecutionMode { 15 | PARSE_TREE, 16 | INTERPRETER 17 | }; 18 | 19 | enum class Control { 20 | NONE, 21 | BREAK, 22 | CONTINUE, 23 | EXIT 24 | }; 25 | ExecutionMode mode = ExecutionMode::PARSE_TREE; 26 | // The current state of the break. Allows us to have break/continue statements. 27 | Control control = Control::NONE; 28 | // In order to have a more genericized version of forloop drops, that are not affected by assigns. 29 | typedef Node (*DropFunction)(Renderer& renderer, const Node& node, Variable store, void* data); 30 | std::unordered_map>> internalDrops; 31 | std::pair getInternalDrop(const Node& node, Variable store); 32 | std::pair getInternalDrop(const std::string& str); 33 | void pushInternalDrop(const std::string& key, std::pair func); 34 | void popInternalDrop(const std::string& key); 35 | 36 | LiquidRendererErrorType error = LiquidRendererErrorType::LIQUID_RENDERER_ERROR_TYPE_NONE; 37 | 38 | struct Error : LiquidRendererError { 39 | typedef LiquidRendererErrorType Type; 40 | 41 | Error() { 42 | type = Type::LIQUID_RENDERER_ERROR_TYPE_NONE; 43 | details.column = 0; 44 | details.line = 0; 45 | details.args[0][0] = 0; 46 | } 47 | Error(const Error& error) = default; 48 | Error(Error&& error) = default; 49 | 50 | Error(Type type, const Node& node, const std::string& message = "") { 51 | details.column = node.column; 52 | details.line = node.line; 53 | this->type = type; 54 | strncpy(details.args[0], message.c_str(), LIQUID_ERROR_ARG_MAX_LENGTH-1); 55 | details.args[0][LIQUID_ERROR_ARG_MAX_LENGTH-1] = 0; 56 | } 57 | 58 | static string english(const LiquidRendererError& rendererError) { 59 | char buffer[512]; 60 | switch (rendererError.type) { 61 | case Renderer::Error::Type::LIQUID_RENDERER_ERROR_TYPE_NONE: break; 62 | case Renderer::Error::Type::LIQUID_RENDERER_ERROR_TYPE_EXCEEDED_MEMORY: 63 | sprintf(buffer, "Exceeded memory."); 64 | break; 65 | case Renderer::Error::Type::LIQUID_RENDERER_ERROR_TYPE_EXCEEDED_TIME: 66 | sprintf(buffer, "Exceeded rendering time."); 67 | break; 68 | case Renderer::Error::Type::LIQUID_RENDERER_ERROR_TYPE_EXCEEDED_DEPTH: 69 | sprintf(buffer, "Exceeded stack depth."); 70 | break; 71 | case Renderer::Error::Type::LIQUID_RENDERER_ERROR_TYPE_UNKNOWN_VARIABLE: 72 | sprintf(buffer, "Unknown variable '%s'.", rendererError.details.args[0]); 73 | break; 74 | case Renderer::Error::Type::LIQUID_RENDERER_ERROR_TYPE_UNKNOWN_FILTER: 75 | sprintf(buffer, "Unknown filter '%s'.", rendererError.details.args[0]); 76 | break; 77 | } 78 | return string(buffer); 79 | } 80 | }; 81 | 82 | struct Exception : Liquid::Exception { 83 | Renderer::Error rendererError; 84 | std::string message; 85 | Exception(const Renderer::Error& error) : rendererError(error) { 86 | message = Error::english(error); 87 | } 88 | 89 | const char* what() const noexcept override { 90 | return message.data(); 91 | } 92 | }; 93 | 94 | 95 | // If set, this will stop rendering with an error if the limits here, in bytes are breached for this renderer. This is checked any time 96 | // the variable resolver is used. 97 | unsigned int maximumMemoryUsage = 0; 98 | // If set, this will stop rendering with an error if the limits here, in milisecnods, are breached for this renderer. 99 | // This is checked between all concatenations. 100 | unsigned int maximumRenderingTime = 0; 101 | // How many concatenation nodes are allowed at any given time. This roughly corresponds to the amount of nested tags. In non-malicious code 102 | // this will probably rarely exceed 100. 103 | unsigned int maximumRenderingDepth = 100; 104 | 105 | unsigned int currentMemoryUsage; 106 | std::chrono::system_clock::time_point renderStartTime; 107 | unsigned int currentRenderingDepth; 108 | 109 | bool logUnknownFilters = false; 110 | bool logUnknownVariables = false; 111 | 112 | bool internalRender = false; 113 | 114 | const ContextBoundaryNode* nodeContext = nullptr; 115 | 116 | // Done so we don't repeat unknown errors if they're inloops. 117 | unordered_set unknownErrors; 118 | void pushUnknownVariableWarning(const Node& node, int offset, Variable store); 119 | void pushUnknownFilterWarning(const Node& node, Variable store); 120 | 121 | 122 | // Used for the C interface. 123 | Node returnValue; 124 | void* customData = NULL; 125 | LiquidVariableResolver variableResolver; 126 | void* resolverCustomData = NULL; 127 | 128 | Renderer(const Context& context); 129 | Renderer(const Context& context, LiquidVariableResolver variableResolver); 130 | 131 | vector errors; 132 | Variant renderArgument(const Node& ast, Variable store); 133 | LiquidRendererErrorType render(const Node& ast, Variable store, void (*)(const char* chunk, size_t size, void* data), void* data); 134 | string render(const Node& ast, Variable store); 135 | string renderTrimmed(const Node& ast, Variable store); 136 | // Retrieves a rendered node, if possible. If the node in question has a nodetype that is PARTIAL optimized, Has the potential to return node with 137 | // a type still attached; otherwise, will always be a variant node. 138 | Node retrieveRenderedNode(const Node& node, Variable store) { 139 | if (node.type) { 140 | Node value = node.type->render(*this, node, store); 141 | assert(!value.type); 142 | return value; 143 | } 144 | return node; 145 | } 146 | std::chrono::duration getRenderedTime() const; 147 | 148 | operator LiquidRenderer() { return LiquidRenderer {this}; } 149 | 150 | void inject(Variable& variable, const Variant& variant); 151 | Variant parseVariant(Variable variable); 152 | string getString(const Node& node); 153 | 154 | 155 | 156 | std::pair getVariable(const Node& node, Variable store, size_t offset = 0); 157 | bool setVariable(const Node& node, Variable store, Variable value, size_t offset = 0); 158 | 159 | const LiquidVariableResolver& getVariableResolver() const { return variableResolver; } 160 | bool resolveVariableString(string& target, void* variable) { 161 | long long length = variableResolver.getStringLength(LiquidRenderer { this }, variable); 162 | if (length < 0) 163 | return false; 164 | target.resize(length); 165 | if (!variableResolver.getString(LiquidRenderer { this }, variable, const_cast(target.data()))) 166 | return false; 167 | return true; 168 | } 169 | }; 170 | } 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDPARSER_H 2 | #define LIQUIDPARSER_H 3 | 4 | #include "common.h" 5 | #include "lexer.h" 6 | 7 | namespace Liquid { 8 | 9 | struct NodeType; 10 | struct Variable; 11 | struct FilterNodeType; 12 | 13 | struct Parser { 14 | const Context& context; 15 | 16 | struct Error : LiquidParserError { 17 | typedef LiquidParserErrorType Type; 18 | 19 | Error() { 20 | this->type = Type::LIQUID_PARSER_ERROR_TYPE_NONE; 21 | details.column = 0; 22 | details.line = 0; 23 | details.args[0][0] = 0; 24 | } 25 | Error(const Error& error) = default; 26 | Error(Error&& error) = default; 27 | operator bool() const { return type != Type::LIQUID_PARSER_ERROR_TYPE_NONE; } 28 | 29 | template 30 | Error(T& lexer, Type type, const std::string& arg0 = "", const std::string& arg1 = "", const std::string& arg2 = "", const std::string& arg3 = "", const std::string& arg4 = "") { 31 | this->type = type; 32 | details.column = lexer.column; 33 | details.line = lexer.line; 34 | strncpy(details.args[0], arg0.c_str(), LIQUID_ERROR_ARG_MAX_LENGTH-1); 35 | details.args[0][LIQUID_ERROR_ARG_MAX_LENGTH-1] = 0; 36 | strncpy(details.args[1], arg1.c_str(), LIQUID_ERROR_ARG_MAX_LENGTH-1); 37 | details.args[1][LIQUID_ERROR_ARG_MAX_LENGTH-1] = 0; 38 | strncpy(details.args[2], arg2.c_str(), LIQUID_ERROR_ARG_MAX_LENGTH-1); 39 | details.args[2][LIQUID_ERROR_ARG_MAX_LENGTH-1] = 0; 40 | strncpy(details.args[3], arg3.c_str(), LIQUID_ERROR_ARG_MAX_LENGTH-1); 41 | details.args[3][LIQUID_ERROR_ARG_MAX_LENGTH-1] = 0; 42 | strncpy(details.args[4], arg4.c_str(), LIQUID_ERROR_ARG_MAX_LENGTH-1); 43 | details.args[4][LIQUID_ERROR_ARG_MAX_LENGTH-1] = 0; 44 | } 45 | 46 | static string english(LiquidParserError error) { 47 | char buffer[512]; 48 | switch (error.type) { 49 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_NONE: break; 50 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_UNKNOWN_TAG: 51 | sprintf(buffer, "Unknown tag '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 52 | break; 53 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_UNKNOWN_OPERATOR: 54 | sprintf(buffer, "Unknown operator '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 55 | break; 56 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_UNKNOWN_OPERATOR_OR_QUALIFIER: 57 | sprintf(buffer, "Unknown operator, or qualifier '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 58 | break; 59 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_UNEXPECTED_OPERAND: 60 | sprintf(buffer, "Unexpected operand for qualifier '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 61 | break; 62 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_INVALID_ARGUMENTS: 63 | sprintf(buffer, "Invalid arguments for '%s' on line %lu, column %lu; expected %s, got %s.", error.details.args[0], error.details.line, error.details.column, error.details.args[0], error.details.args[1]); 64 | break; 65 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_INVALID_QUALIFIER: 66 | sprintf(buffer, "Invalid qualifier for '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 67 | break; 68 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_UNKNOWN_FILTER: 69 | sprintf(buffer, "Unknown filter '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 70 | break; 71 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_INVALID_SYMBOL: 72 | sprintf(buffer, "Invalid symbol '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 73 | break; 74 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_UNEXPECTED_END: { 75 | if (error.details.args[0][0]) 76 | sprintf(buffer, "Unexpected end to block '%s' on line %lu, column %lu.", error.details.args[0], error.details.line, error.details.column); 77 | else 78 | sprintf(buffer, "Unexpected end to block on line %lu, column %lu.", error.details.line, error.details.column); 79 | } break; 80 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_UNBALANCED_GROUP: 81 | sprintf(buffer, "Unbalanced end to group on line %lu, column %lu.", error.details.line, error.details.column); 82 | break; 83 | case Parser::Error::Type::LIQUID_PARSER_ERROR_TYPE_PARSE_DEPTH_EXCEEDED: 84 | sprintf(buffer, "Parse depth exceeded on line %lu, column %lu.", error.details.line, error.details.column); 85 | break; 86 | } 87 | return string(buffer); 88 | } 89 | }; 90 | 91 | 92 | 93 | enum class State { 94 | NODE, 95 | ARGUMENT, 96 | // An error state that only occurs when we need to basically ignore everything until we get out of argument context. 97 | // Alows us to continue to parse, even if one tag is messed up. 98 | IGNORE_UNTIL_BLOCK_END, 99 | // Specifically for the {% liquid %} *rolls eyes*. 100 | LIQUID_NODE, 101 | LIQUID_ARGUMENT 102 | }; 103 | enum class EFilterState { 104 | UNSET, 105 | COLON, 106 | NAME, 107 | ARGUMENTS, 108 | QUALIFIERS 109 | }; 110 | 111 | 112 | enum class EBlockType { 113 | NONE, 114 | INTERMEDIATE, 115 | END 116 | }; 117 | 118 | State state; 119 | EFilterState filterState; 120 | EBlockType blockType; 121 | vector> nodes; 122 | vector errors; 123 | 124 | // Any more depth than this, and we throw an error. 125 | unsigned int maximumParseDepth = 100; 126 | 127 | void pushError(const Error& error) { 128 | errors.push_back(error); 129 | } 130 | 131 | struct Lexer : Liquid::Lexer { 132 | Parser& parser; 133 | typedef Liquid::Lexer SUPER; 134 | 135 | bool newline(); 136 | bool literal(const char* str, size_t len); 137 | bool dot(); 138 | bool colon(); 139 | bool comma(); 140 | 141 | bool endOutputContext(); 142 | bool endTagContext(); 143 | 144 | bool startOutputBlock(bool suppress); 145 | bool endOutputBlock(bool suppress); 146 | bool endControlBlock(bool suppress); 147 | 148 | bool startVariableDereference(); 149 | bool endVariableDereference(); 150 | bool string(const char* str, size_t len); 151 | bool integer(long long i); 152 | bool floating(double f); 153 | 154 | bool openParenthesis(); 155 | bool closeParenthesis(); 156 | 157 | Lexer(const Context& context, Parser& parser) : Liquid::Lexer(context), parser(parser) { } 158 | }; 159 | 160 | 161 | struct Exception : Liquid::Exception { 162 | vector parserErrors; 163 | Lexer::Error lexerError; 164 | string englishDefault; 165 | 166 | Exception(const vector& errors) : parserErrors(errors) { 167 | englishDefault = Parser::Error::english(parserErrors[errors.size()-1]).c_str(); 168 | } 169 | Exception(const Lexer::Error& error) : lexerError(error) { 170 | englishDefault = Lexer::Error::english(lexerError); 171 | 172 | } 173 | const char* what() const noexcept override { 174 | return englishDefault.c_str(); 175 | } 176 | }; 177 | 178 | 179 | Lexer lexer; 180 | string file; 181 | 182 | Parser(const Context& context) : context(context), lexer(context, *this) { } 183 | 184 | 185 | Error validate(const Node& node) const; 186 | 187 | bool pushNode(unique_ptr node, bool expectingNode = false); 188 | // Pops the last node in the stack, and then applies it as the last child of the node prior to it. 189 | bool popNode(); 190 | // Pops nodes until it reaches a node of the given type. 191 | bool popNodeUntil(NodeType::Type type); 192 | bool hasNode(NodeType::Type type) const; 193 | bool hasNode(const NodeType* type) const; 194 | 195 | Node parseArgument(const char* buffer, size_t len); 196 | Node parseArgument(const std::string& str) { return parseArgument(str.data(), str.size()); } 197 | 198 | // Determines whether or not this is an argument, or a text, and then returns whichever it thinks makes more sense. 199 | Node parseAppropriate(const char* buffer, size_t len, const std::string& file = ""); 200 | Node parseAppropriate(const std::string& str, const std::string& file = "") { return parseAppropriate(str.data(), str.size()); } 201 | 202 | Node parse(const char* buffer, size_t len, const std::string& file = ""); 203 | Node parse(const string& str, const std::string& file = "") { 204 | return parse(str.data(), str.size(), file); 205 | } 206 | 207 | // Unparses the tree into text. Useful when used with optimization. 208 | void unparse(const Node& node, std::string& target, Parser::State state = Parser::State::NODE); 209 | std::string unparse(const Node& node) { std::string target; unparse(node, target); return target; } 210 | 211 | const FilterNodeType* getFilterType(const std::string& opName) const; 212 | }; 213 | } 214 | 215 | #endif 216 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Autotools-style (./configure) wrapper for CMake 4 | # 5 | # 6 | # *** IMPORTANT *** 7 | # 8 | # You must include the GNUInstallDirs module (which comes with 9 | # CMake) in your project. Just put "include (GNUInstallDirs)" in 10 | # you CMakeLists.txt and you should be good. 11 | # 12 | # This script was originally written for Squash 13 | # by Evan Nemerson 14 | # , but has been spun off into a separate 15 | # repository. Please feel free to copy it into your own repository, 16 | # though I would appreciate it if you would post improvements, bugs, 17 | # feature requests, etc. to the issue tracker at 18 | # . 19 | # 20 | # To the extent possible under law, the author(s) hereby waive all 21 | # copyright and related or neighboring rights to this work. For 22 | # details, see 23 | 24 | TOP_SRCDIR="$(dirname $0)" 25 | 26 | if [ "${CMAKE_CMD}" = "" ]; then 27 | CMAKE_CMD="cmake" 28 | fi 29 | 30 | BUILD_TYPE="Debug" 31 | PREFIX=/usr/local 32 | LIBDIR= 33 | CMAKE_ARGS= 34 | 35 | if [ -e "${TOP_SRCDIR}/.configure-custom.sh" ]; then 36 | . "${TOP_SRCDIR}/.configure-custom.sh" 37 | fi 38 | 39 | quote() { 40 | echo "$1" | sed -e "s|'|'\\\\''|g; 1s/^/'/; \$s/\$/'/" 41 | } 42 | 43 | extract_var_string() { 44 | VAR_NAME=$1 45 | VAR_NAME=$(echo $1 | sed -e 's/[ \t]*$//') 46 | if [ "x$2" != "x" ]; then 47 | VAR_VALUE=$2 48 | else 49 | VAR_VALUE=yes 50 | fi 51 | 52 | if [ "x$3" != "x" ]; then 53 | VAR_UC_NAME=$3 54 | else 55 | VAR_UC_NAME=$(echo "$1" | tr '[:lower:]' '[:upper:]' | tr -c '[:alnum:]' '_' | sed 's/_$//g') 56 | fi 57 | } 58 | 59 | set_config_var() { 60 | is_with=n 61 | case "$1" in 62 | "--enable-"*) 63 | name="${1#--enable-}" 64 | cfg="${ENABLE_VARS}" 65 | ;; 66 | "--disable-"*) 67 | name="${1#--disable-}"; 68 | cfg="${DISABLE_VARS}"; 69 | ;; 70 | "--with-"*) 71 | # IFS="=" read -ra WITHARGS <<< "${1}" 72 | name="${1#--with-}" 73 | cfg="${WITH_VARS}" 74 | is_with=y 75 | ;; 76 | esac 77 | 78 | found=n 79 | for varstring in $cfg; do 80 | extract_var_string $(echo "${varstring}" | tr '|' ' ') 81 | if [ "x$VAR_NAME" = "x$name" ]; then 82 | found=y 83 | break; 84 | fi 85 | done 86 | 87 | if [ "$found" = "y" ]; then 88 | if [ "x$is_with" = "xy" ]; then 89 | CMAKE_ARGS="$CMAKE_ARGS -D${VAR_UC_NAME}=$(quote "$2")" 90 | else 91 | CMAKE_ARGS="$CMAKE_ARGS -D${VAR_UC_NAME}=$(quote "${VAR_VALUE}")" 92 | fi 93 | else 94 | echo "Unknown parameter: ${1}" 95 | exit 1 96 | fi 97 | } 98 | 99 | prefix_to_offset() { 100 | expr $(echo "${1}" | awk '{ print length }') + 1 101 | } 102 | 103 | print_help() { 104 | cat <&2 105 | -h, --help display this help and exit 106 | --disable-debug disable debugging mode 107 | --pass-thru pass remaining arguments through to CMake 108 | 109 | --prefix=PREFIX install architecture-independent files in PREFIX 110 | [$PREFIX] 111 | --bindir=DIR user executables [PREFIX/bin] 112 | --sbindir=DIR system admin executables [PREFIX/sbin] 113 | --libexecdir=DIR program executables [PREFIX/libexec] 114 | --sysconfdir=DIR read-only single-machine data [PREFIX/etc] 115 | --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] 116 | --localstatedir=DIR modifiable single-machine data [PREFIX/var] 117 | --libdir=DIR object code libraries [PREFIX/lib] 118 | --includedir=DIR C header files [PREFIX/include] 119 | --oldincludedir=DIR C header files for non-gcc [/usr/include] 120 | --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] 121 | --datadir=DIR read-only architecture-independent data [DATAROOTDIR] 122 | --infodir=DIR info documentation [DATAROOTDIR/info] 123 | --localedir=DIR locale-dependent data [DATAROOTDIR/locale] 124 | --mandir=DIR man documentation [DATAROOTDIR/man] 125 | --docdir=DIR documentation root [DATAROOTDIR/doc/PROJECT_NAME] 126 | EOF 127 | 128 | first=y 129 | for varstring in ${ENABLE_VARS}; do 130 | if [ $first = 'y' ]; then 131 | echo "" 132 | first=n 133 | fi 134 | extract_var_string $(echo "${varstring}" | tr '|' ' ') 135 | var_doc_name="ENABLE_${VAR_UC_NAME}_DOC" 136 | eval "docstring=\$$var_doc_name" 137 | if [ "x${docstring}" = "x" ]; then 138 | printf " --enable-%-14s enable %s support\n" "${VAR_NAME}" "$(echo -n "${VAR_NAME}" | tr '-' ' ')" 139 | else 140 | printf " --enable-%-14s %s\n" "${VAR_NAME}" "$docstring" 141 | fi 142 | done 143 | 144 | first=y 145 | for varstring in ${DISABLE_VARS}; do 146 | if [ $first = 'y' ]; then 147 | echo "" 148 | first=n 149 | fi 150 | extract_var_string $(echo "${varstring}" | tr '|' ' ') 151 | var_doc_name="DISABLE_${VAR_UC_NAME}_DOC" 152 | eval "docstring=\$$var_doc_name" 153 | if [ "x${docstring}" = "x" ]; then 154 | printf " --disable-%-13s disable %s support\n" "${VAR_NAME}" "$(echo -n "${VAR_NAME}" | tr '-' ' ')" 155 | else 156 | printf " --disable-%-13s %s\n" "${VAR_NAME}" "$docstring" 157 | fi 158 | done 159 | 160 | first=y 161 | for varstring in ${WITH_VARS}; do 162 | if [ $first = 'y' ]; then 163 | echo "" 164 | first=n 165 | fi 166 | extract_var_string $(echo "${varstring}" | tr '|' ' ') 167 | var_doc_name="WITH_${VAR_UC_NAME}_DOC" 168 | eval "docstring=\$$var_doc_name" 169 | paraminfo="${VAR_NAME}=${VAR_VALUE}" 170 | if [ "x${docstring}" = "x" ]; then 171 | printf " --with-%-16s enable %s support\n" "$paraminfo" "$(echo -n "${VAR_NAME}" | tr '-' ' ')" 172 | else 173 | printf " --with-%-16s %s\n" "$paraminfo" "$docstring" 174 | fi 175 | done 176 | 177 | exit 0 178 | } 179 | 180 | while [ $# != 0 ]; do 181 | case "$1" in 182 | "--prefix="*) 183 | PREFIX="${1#*=}";; 184 | "--prefix") 185 | PREFIX="${2}"; shift;; 186 | "--bindir="*) 187 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_BINDIR=$(quote "${1#*=}")";; 188 | "--bindir") 189 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_BINDIR=$(quote "$2")"; shift;; 190 | "--sbindir="*) 191 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_SBINDIR=$(quote "${1#*=}")";; 192 | "--sbindir") 193 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_SBINDIR=$(quote "$2")"; shift;; 194 | "--libexecdir="*) 195 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_LIBEXECDIR=$(quote "${1#*=}")";; 196 | "--libexecdir") 197 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_LIBEXECDIR=$(quote "$2")"; shift;; 198 | "--sysconfdir="*) 199 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_SYSCONFDIR=$(quote "${1#*=}")";; 200 | "--sysconfdir") 201 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_SYSCONFDIR=$(quote "$2")"; shift;; 202 | "--sharedstatedir="*) 203 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_SHAREDSTATEDIR=$(quote "${1#*=}")";; 204 | "--sharedstatedir") 205 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_SHAREDSTATEDIR=$(quote "$2")"; shift;; 206 | "--localstatedir="*) 207 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_LOCALSTATEDIR=$(quote "${1#*=}")";; 208 | "--localstatedir") 209 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_LOCALSTATEDIR=$(quote "$2")"; shift;; 210 | "--libdir="*) 211 | LIBDIR="${1#*=}";; 212 | "--libdir") 213 | LIBDIR="${2}"; shift;; 214 | "--includedir="*) 215 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_INCLUDEDIR=$(quote "${1#*=}")";; 216 | "--includedir") 217 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_INCLUDEDIR=$(quote "$2")"; shift;; 218 | "--oldincludedir="*) 219 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_OLDINCLUDEDIR=$(quote "${1#*=}")";; 220 | "--oldincludedir") 221 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_OLDINCLUDEDIR=$(quote "$2")"; shift;; 222 | "--datarootdir="*) 223 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_DATAROOTDIR=$(quote "${1#*=}")";; 224 | "--datarootdir") 225 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_DATAROOTDIR=$(quote "$2")"; shift;; 226 | "--datadir="*) 227 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_DATADIR=$(quote "${1#*=}")";; 228 | "--datadir") 229 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_DATADIR=$(quote "$2")"; shift;; 230 | "--infodir="*) 231 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_INFODIR=$(quote "${1#*=}")";; 232 | "--infodir") 233 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_INFODIR=$(quote "$2")"; shift;; 234 | "--localedir="*) 235 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_LOCALEDIR=$(quote "${1#*=}")";; 236 | "--localedir") 237 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_LOCALEDIR=$(quote "$2")"; shift;; 238 | "--mandir="*) 239 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_MANDIR=$(quote "${1#*=}")";; 240 | "--mandir") 241 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_MANDIR=$(quote "$2")"; shift;; 242 | "--docdir="*) 243 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_DOCDIR=$(quote "${1#*=}")";; 244 | "--docdir") 245 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_DOCDIR=$(quote "$2")"; shift;; 246 | 247 | "CC="*) 248 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_C_COMPILER=$(quote "${1#*=}")";; 249 | "CXX="*) 250 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_CXX_COMPILER=$(quote "${1#*=}")";; 251 | "CFLAGS="*) 252 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_C_FLAGS=$(quote "${1#*=}")";; 253 | "CXXFLAGS="*) 254 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_CXX_FLAGS=$(quote "${1#*=}")";; 255 | "LDFLAGS="*) 256 | LDFLAGS="$LDFLAGS ${1#*=}";; 257 | 258 | "--help") 259 | print_help;; 260 | "-h") 261 | print_help;; 262 | 263 | # This flag is the only one which may be a bit surprising to 264 | # people. Autotools always builds with debugging symbols enabled 265 | # (AFAIK), but for cmake you have to do -DCMAKE_BUILD_TYPE=Debug. 266 | # Unfortunately this can change other things as well, so although 267 | # I realize there is no --disable-debug flag I thought it would be 268 | # prudent to support one here. 269 | "--disable-debug") 270 | BUILD_TYPE="Release";; 271 | 272 | "--pass-thru") 273 | while [ $# != 1 ]; do 274 | shift; 275 | CMAKE_ARGS="$CMAKE_ARGS $(quote "${1}")"; 276 | done;; 277 | 278 | "--enable-"*) 279 | set_config_var "$1" 280 | ;; 281 | 282 | "--disable-"*) 283 | set_config_var "$1" 284 | ;; 285 | 286 | "--with-"*) 287 | name=$(echo "${1#--with-}" | awk '{split($1,v,"="); print v[1]}') 288 | case "${1}" in 289 | "--with-${name}="*) 290 | set_config_var "--with-${name}" "${1#--with-${name}=}";; 291 | "--with-${name}") 292 | set_config_var "$1" "$2"; 293 | shift;; 294 | esac 295 | ;; 296 | 297 | *) 298 | echo "$0: error: unrecognized option: \`$1'" >&2 299 | echo "Try \`$0 --help' for more information" >&2 300 | exit -1 301 | esac; 302 | shift 303 | done 304 | 305 | if [ "x${LIBDIR}" = "x" ]; then 306 | LIBDIR="${PREFIX}/lib" 307 | fi 308 | 309 | # Unlike CFLAGS/CXXFLAGS/CC/CXX, LDFLAGS isn't handled by CMake, so we 310 | # need to parse it here. 311 | if [ "x${LDFLAGS}" != "x" ]; then 312 | for varname in EXE MODULE SHARED STATIC; do 313 | CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_${varname}_LINKER_FLAGS=$(quote "$LDFLAGS")" 314 | done 315 | fi 316 | 317 | eval "${CMAKE_CMD}" "${TOP_SRCDIR}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_INSTALL_LIBDIR="${LIBDIR}" ${CMAKE_ARGS} 318 | -------------------------------------------------------------------------------- /src/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | #include "context.h" 3 | #include "cppvariable.h" 4 | 5 | namespace Liquid { 6 | struct Context; 7 | 8 | Renderer::Renderer(const Context& context) : context(context) { 9 | variableResolver = CPPVariableResolver(); 10 | } 11 | 12 | Renderer::Renderer(const Context& context, LiquidVariableResolver variableResolver) : context(context), variableResolver(variableResolver) { 13 | 14 | } 15 | 16 | Variant Renderer::renderArgument(const Node& ast, Variable store) { 17 | nodeContext = nullptr; 18 | mode = Renderer::ExecutionMode::PARSE_TREE; 19 | errors.clear(); 20 | unknownErrors.clear(); 21 | renderStartTime = std::chrono::system_clock::now(); 22 | currentMemoryUsage = 0; 23 | currentRenderingDepth = 0; 24 | error = Error::Type::LIQUID_RENDERER_ERROR_TYPE_NONE; 25 | internalRender = true; 26 | Node node = retrieveRenderedNode(ast, store); 27 | internalRender = false; 28 | assert(node.type == nullptr); 29 | if (error != LIQUID_RENDERER_ERROR_TYPE_NONE) 30 | throw Error(error, Node()); 31 | return node.variant; 32 | } 33 | 34 | LiquidRendererErrorType Renderer::render(const Node& ast, Variable store, void (*callback)(const char* chunk, size_t size, void* data), void* data) { 35 | if (internalRender) { 36 | Node node = retrieveRenderedNode(ast, store); 37 | assert(node.type == nullptr); 38 | auto s = node.getString(); 39 | callback(s.data(), s.size(), data); 40 | } else { 41 | mode = Renderer::ExecutionMode::PARSE_TREE; 42 | nodeContext = nullptr; 43 | errors.clear(); 44 | unknownErrors.clear(); 45 | renderStartTime = std::chrono::system_clock::now(); 46 | currentMemoryUsage = 0; 47 | currentRenderingDepth = 0; 48 | error = Error::Type::LIQUID_RENDERER_ERROR_TYPE_NONE; 49 | internalRender = true; 50 | Node node = retrieveRenderedNode(ast, store); 51 | internalRender = false; 52 | assert(node.type == nullptr); 53 | auto s = node.getString(); 54 | callback(s.data(), s.size(), data); 55 | } 56 | return error; 57 | } 58 | 59 | string Renderer::render(const Node& ast, Variable store) { 60 | string accumulator; 61 | LiquidRendererErrorType error = render(ast, store, +[](const char* chunk, size_t size, void* data){ 62 | string* accumulator = (string*)data; 63 | accumulator->append(chunk, size); 64 | }, &accumulator); 65 | if (error != LIQUID_RENDERER_ERROR_TYPE_NONE) 66 | throw Error(error, Node()); 67 | return accumulator; 68 | } 69 | 70 | string Renderer::renderTrimmed(const Node& ast, Variable store) { 71 | string result = render(ast, store); 72 | int start,end; 73 | for (start = 0; start < (int)result.size() && isspace(start); ++start); 74 | if (start == (int)result.size()) 75 | return string(); 76 | for (end = (int)result.size()-1; end >= 0 && isspace(end); --end); 77 | return result.substr(start, end - start + 1); 78 | } 79 | 80 | pair Renderer::getInternalDrop(const string& key) { 81 | auto it = internalDrops.find(key); 82 | if (it == internalDrops.end()) 83 | return { nullptr, nullptr }; 84 | return it->second.back(); 85 | } 86 | 87 | pair Renderer::getInternalDrop(const Node& node, Variable store) { 88 | assert(node.type && node.children.size() > 0); 89 | string key = retrieveRenderedNode(*node.children[0].get(), store).getString(); 90 | return getInternalDrop(key); 91 | } 92 | 93 | void Renderer::pushInternalDrop(const std::string& key, std::pair func) { 94 | internalDrops[key].push_back(func); 95 | } 96 | 97 | void Renderer::popInternalDrop(const std::string& key) { 98 | auto it = internalDrops.find(key); 99 | if (it != internalDrops.end()) { 100 | it->second.pop_back(); 101 | if (it->second.size() == 0) 102 | internalDrops.erase(it); 103 | } 104 | } 105 | 106 | 107 | void Renderer::inject(Variable& variable, const Variant& variant) { 108 | switch (variant.type) { 109 | case Variant::Type::STRING: 110 | variable = variableResolver.createString(*this, variant.s.data()); 111 | break; 112 | case Variant::Type::INT: 113 | variable = variableResolver.createInteger(*this, variant.i); 114 | break; 115 | case Variant::Type::FLOAT: 116 | variable = variableResolver.createFloat(*this, variant.f); 117 | break; 118 | case Variant::Type::NIL: 119 | variable = variableResolver.createNil(*this); 120 | break; 121 | case Variant::Type::BOOL: 122 | variable = variableResolver.createBool(*this, variant.b); 123 | break; 124 | case Variant::Type::VARIABLE: 125 | variable = variableResolver.createClone(*this, variant.v.pointer); 126 | break; 127 | case Variant::Type::ARRAY: { 128 | variable = variableResolver.createArray(*this); 129 | for (size_t i = 0; i < variant.a.size(); ++i) { 130 | Variable target; 131 | inject(target, variant.a[i]); 132 | if (!variableResolver.setArrayVariable(*this, variable, i, target)) 133 | break; 134 | variableResolver.freeVariable(*this, target); 135 | } 136 | } break; 137 | default: 138 | variable = variableResolver.createPointer(*this, variant.p); 139 | break; 140 | } 141 | } 142 | 143 | Variant Renderer::parseVariant(Variable variable) { 144 | ELiquidVariableType type = variableResolver.getType(*this, variable); 145 | switch (type) { 146 | case LIQUID_VARIABLE_TYPE_OTHER: 147 | case LIQUID_VARIABLE_TYPE_DICTIONARY: 148 | case LIQUID_VARIABLE_TYPE_ARRAY: 149 | return Variant(Variable({variable})); 150 | break; 151 | case LIQUID_VARIABLE_TYPE_BOOL: { 152 | bool b; 153 | if (variableResolver.getBool(LiquidRenderer { this }, variable, &b)) 154 | return Variant(b); 155 | } break; 156 | case LIQUID_VARIABLE_TYPE_INT: { 157 | long long i; 158 | if (variableResolver.getInteger(*this, variable, &i)) 159 | return Variant(i); 160 | } break; 161 | case LIQUID_VARIABLE_TYPE_FLOAT: { 162 | double f; 163 | if (variableResolver.getFloat(*this, variable, &f)) 164 | return Variant(f); 165 | } break; 166 | case LIQUID_VARIABLE_TYPE_STRING: { 167 | string s; 168 | long long size = variableResolver.getStringLength(*this, variable); 169 | if (size >= 0) { 170 | s.resize(size); 171 | if (variableResolver.getString(*this, variable, const_cast(s.data()))) 172 | return Variant(move(s)); 173 | } 174 | } break; 175 | case LIQUID_VARIABLE_TYPE_NIL: 176 | return Variant(); 177 | } 178 | return Variant(); 179 | } 180 | 181 | 182 | std::string Renderer::getString(const Node& node) { 183 | if (!node.type) { 184 | if (node.variant.type == Variant::Type::VARIABLE) { 185 | string s; 186 | long long size = variableResolver.getStringLength(*this, node.variant.v.pointer); 187 | if (size >= 0) { 188 | s.resize(size); 189 | if (variableResolver.getString(*this, node.variant.v.pointer, const_cast(s.data()))) 190 | return s; 191 | } 192 | } else { 193 | return node.variant.getString(); 194 | } 195 | } 196 | return string(); 197 | } 198 | 199 | void Renderer::pushUnknownFilterWarning(const Node& node, Variable stash) { 200 | assert(node.type); 201 | if (unknownErrors.find(&node) == unknownErrors.end()) { 202 | unknownErrors.insert(&node); 203 | errors.push_back(Error(LIQUID_RENDERER_ERROR_TYPE_UNKNOWN_FILTER, node, node.type->symbol)); 204 | } 205 | } 206 | void Renderer::pushUnknownVariableWarning(const Node& node, int offset, Variable store) { 207 | if (unknownErrors.find(&node) == unknownErrors.end()) { 208 | unknownErrors.insert(&node); 209 | string variableName; 210 | for (size_t i = offset; i < node.children.size(); ++i) { 211 | auto result = retrieveRenderedNode(*node.children[i].get(), store); 212 | if (!variableName.empty()) { 213 | if (result.variant.type == Variant::Type::INT) { 214 | variableName.push_back('['); 215 | variableName.append(std::to_string(result.variant.i)); 216 | variableName.push_back(']'); 217 | } else 218 | variableName.append("."); 219 | variableName.append(result.getString()); 220 | } 221 | } 222 | errors.push_back(Error(LIQUID_RENDERER_ERROR_TYPE_UNKNOWN_VARIABLE, node, variableName)); 223 | } 224 | } 225 | 226 | pair Renderer::getVariable(const Node& node, Variable store, size_t offset) { 227 | Variable storePointer = store; 228 | bool valid = true; 229 | for (size_t i = offset; valid && i < node.children.size(); ++i) { 230 | auto& link = node.children[i]; 231 | auto node = retrieveRenderedNode(*link.get(), store); 232 | if (link.get()->type && link.get()->type->type == NodeType::DOT_FILTER && !node.type) { 233 | if (node.variant.type == Variant::Type::VARIABLE) 234 | storePointer = node.variant.v; 235 | else 236 | inject(storePointer, node.variant); 237 | } else { 238 | switch (node.variant.type) { 239 | case Variant::Type::INT: 240 | if (!variableResolver.getArrayVariable(*this, storePointer, node.variant.i, storePointer)) { 241 | storePointer = Variable({ nullptr }); 242 | valid = false; 243 | } 244 | break; 245 | case Variant::Type::STRING: { 246 | if (!variableResolver.getDictionaryVariable(*this, storePointer, node.variant.s.data(), storePointer)) { 247 | storePointer = Variable({ nullptr }); 248 | valid = false; 249 | } 250 | } break; 251 | default: 252 | storePointer = Variable({ nullptr }); 253 | valid = false; 254 | break; 255 | } 256 | } 257 | } 258 | if (logUnknownVariables && !valid) 259 | pushUnknownVariableWarning(node, offset, store); 260 | return { valid, storePointer }; 261 | } 262 | 263 | bool Renderer::setVariable(const Node& node, Variable store, Variable value, size_t offset) { 264 | Variable storePointer = store; 265 | for (size_t i = offset; i < node.children.size(); ++i) { 266 | auto& link = node.children[i]; 267 | auto part = retrieveRenderedNode(*link.get(), store); 268 | if (i == node.children.size() - 1) { 269 | switch (part.variant.type) { 270 | case Variant::Type::INT: 271 | return variableResolver.setArrayVariable(*this, storePointer, part.variant.i, value); 272 | case Variant::Type::STRING: 273 | return variableResolver.setDictionaryVariable(*this, storePointer, part.variant.s.data(), value); 274 | default: 275 | return false; 276 | } 277 | } else { 278 | switch (part.variant.type) { 279 | case Variant::Type::INT: 280 | if (!variableResolver.getArrayVariable(*this, storePointer, part.variant.i, storePointer)) 281 | return false; 282 | break; 283 | case Variant::Type::STRING: 284 | if (!variableResolver.getDictionaryVariable(*this, storePointer, part.variant.s.data(), storePointer)) 285 | return false; 286 | break; 287 | default: 288 | return false; 289 | } 290 | } 291 | if (!storePointer.pointer) 292 | return false; 293 | } 294 | return false; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/interface.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDINTERFACE_H 2 | #define LIQUIDINTERFACE_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define LIQUID_ERROR_ARG_MAX_LENGTH 32 12 | #define LIQUID_ERROR_FILE_MAX_LENGTH 256 13 | #define LIQUID_ERROR_ARGS_MAX 5 14 | 15 | typedef enum ELiquidLexerErrorType { 16 | LIQUID_LEXER_ERROR_TYPE_NONE, 17 | LIQUID_LEXER_ERROR_TYPE_UNEXPECTED_END 18 | } LiquidLexerErrorType; 19 | 20 | typedef enum ELiquidParserErrorType { 21 | LIQUID_PARSER_ERROR_TYPE_NONE, 22 | LIQUID_PARSER_ERROR_TYPE_UNEXPECTED_END, 23 | // Self-explamatory. 24 | LIQUID_PARSER_ERROR_TYPE_UNKNOWN_TAG, 25 | LIQUID_PARSER_ERROR_TYPE_UNKNOWN_OPERATOR, 26 | LIQUID_PARSER_ERROR_TYPE_UNKNOWN_OPERATOR_OR_QUALIFIER, 27 | LIQUID_PARSER_ERROR_TYPE_UNKNOWN_FILTER, 28 | LIQUID_PARSER_ERROR_TYPE_UNEXPECTED_OPERAND, 29 | LIQUID_PARSER_ERROR_TYPE_INVALID_ARGUMENTS, 30 | LIQUID_PARSER_ERROR_TYPE_INVALID_QUALIFIER, 31 | // Weird symbol in weird place. 32 | LIQUID_PARSER_ERROR_TYPE_INVALID_SYMBOL, 33 | // Was expecting somthing else, i.e. {{ i + }}; was expecting a number there. 34 | LIQUID_PARSER_ERROR_TYPE_UNBALANCED_GROUP, 35 | LIQUID_PARSER_ERROR_TYPE_PARSE_DEPTH_EXCEEDED 36 | } LiquidParserErrorType; 37 | 38 | 39 | typedef struct SLiquidErrorDetails { 40 | size_t line; 41 | size_t column; 42 | char file[LIQUID_ERROR_FILE_MAX_LENGTH]; 43 | char args[LIQUID_ERROR_ARGS_MAX][LIQUID_ERROR_ARG_MAX_LENGTH]; 44 | } LiquidErrorDetails; 45 | 46 | typedef struct SLiquidLexerError { 47 | LiquidLexerErrorType type; 48 | LiquidErrorDetails details; 49 | } LiquidLexerError; 50 | 51 | typedef struct SLiquidParserError { 52 | LiquidParserErrorType type; 53 | LiquidErrorDetails details; 54 | } LiquidParserError, LiquidParserWarning; 55 | 56 | typedef enum ELiquidRendererErrorType { 57 | LIQUID_RENDERER_ERROR_TYPE_NONE, 58 | LIQUID_RENDERER_ERROR_TYPE_EXCEEDED_MEMORY, 59 | LIQUID_RENDERER_ERROR_TYPE_EXCEEDED_TIME, 60 | LIQUID_RENDERER_ERROR_TYPE_EXCEEDED_DEPTH, 61 | LIQUID_RENDERER_ERROR_TYPE_UNKNOWN_VARIABLE, 62 | LIQUID_RENDERER_ERROR_TYPE_UNKNOWN_FILTER 63 | } LiquidRendererErrorType; 64 | 65 | typedef struct SLiquidRendererError { 66 | LiquidRendererErrorType type; 67 | LiquidErrorDetails details; 68 | } LiquidRendererError, LiquidRendererWarning; 69 | 70 | typedef enum ELiquidOptimizationScheme { 71 | LIQUID_OPTIMIZATION_SCHEME_SHIELD, 72 | LIQUID_OPTIMIZATION_SCHEME_NONE, 73 | LIQUID_OPTIMIZATION_SCHEME_PARTIAL, 74 | LIQUID_OPTIMIZATION_SCHEME_FULL 75 | } LiquidOptimizationScheme; 76 | 77 | typedef enum ELiquidTagType { 78 | LIQUID_TAG_TYPE_FREE, 79 | LIQUID_TAG_TYPE_ENCLOSING 80 | } LiquidTagType; 81 | 82 | typedef enum ELiquidOperatorArity { 83 | LIQUID_OPERATOR_ARITY_NONARY, 84 | LIQUID_OPERATOR_ARITY_UNARY, 85 | LIQUID_OPERATOR_ARITY_BINARY, 86 | LIQUID_OPERATOR_ARITY_NARY 87 | } LiquidOperatorArity; 88 | 89 | typedef enum ELiquidOperatorFixness { 90 | LIQUID_OPERATOR_FIXNESS_PREFIX, 91 | LIQUID_OPERATOR_FIXNESS_INFIX, 92 | LIQUID_OPERATOR_FIXNESS_AFFIX 93 | } LiquidOperatorFixness; 94 | 95 | typedef struct SLiquidContext { void* context; } LiquidContext; 96 | typedef struct SLiquidRenderer { void* renderer; } LiquidRenderer; 97 | typedef struct SLiquidParser { void* parser; } LiquidParser; 98 | typedef struct SLiquidTemplate { void* ast; } LiquidTemplate; 99 | typedef struct SLiquidOptimizer { void* optimizer; } LiquidOptimizer; 100 | typedef struct SLiquidCompiler { void* compiler; } LiquidCompiler; 101 | typedef struct SLiquidInterpreter { void* interpreter; } LiquidInterpreter; 102 | typedef struct SLiquidProgram { void* program; } LiquidProgram; 103 | typedef struct SLiquidNode { void* node; } LiquidNode; 104 | typedef struct SLiquidTemplateRender { void* internal; } LiquidTemplateRender; 105 | typedef struct SLiquidProgramRender { char* str; size_t len; } LiquidProgramRender; 106 | 107 | typedef enum ELiquidVariableType { 108 | LIQUID_VARIABLE_TYPE_NIL, 109 | LIQUID_VARIABLE_TYPE_FLOAT, 110 | LIQUID_VARIABLE_TYPE_INT, 111 | LIQUID_VARIABLE_TYPE_STRING, 112 | LIQUID_VARIABLE_TYPE_ARRAY, 113 | LIQUID_VARIABLE_TYPE_BOOL, 114 | LIQUID_VARIABLE_TYPE_DICTIONARY, 115 | LIQUID_VARIABLE_TYPE_OTHER 116 | } LiquidVariableType; 117 | 118 | // Convenience function to register a custom variable type. 119 | // Ownership model looks thusly: 120 | // Calling create creates a newly allocated pointer. In all cases, one of the two things must happen: 121 | // 1. It must be set as an array element, or a hash element. 122 | // 2. It must be freed with freeVariable. 123 | // For languages where the variables are garbage collected, like perl and ruby; freeVariable will be a no-op. 124 | // Whenever getArrayVariable or getDictionaryVariable are called, a pointer is given, but no allocaitons are made. 125 | typedef struct SLiquidVariableResolver { 126 | LiquidVariableType (*getType)(LiquidRenderer renderer, void* variable); 127 | bool (*getBool)(LiquidRenderer renderer, void* variable, bool* target); 128 | bool (*getTruthy)(LiquidRenderer renderer, void* variable); 129 | bool (*getString)(LiquidRenderer renderer, void* variable, char* target); 130 | long long (*getStringLength)(LiquidRenderer renderer, void* variable); 131 | bool (*getInteger)(LiquidRenderer renderer, void* variable, long long* target); 132 | bool (*getFloat)(LiquidRenderer renderer, void* variable, double* target); 133 | bool (*getDictionaryVariable)(LiquidRenderer renderer, void* variable, const char* key, void** target); 134 | bool (*getArrayVariable)(LiquidRenderer renderer, void* variable, long long idx, void** target); 135 | bool (*iterate)(LiquidRenderer renderer, void* variable, bool (*callback)(void* variable, void* data), void* data, int start, int limit, bool reverse); 136 | long long (*getArraySize)(LiquidRenderer renderer, void* variable); 137 | void* (*setDictionaryVariable)(LiquidRenderer renderer, void* variable, const char* key, void* target); 138 | void* (*setArrayVariable)(LiquidRenderer renderer, void* variable, long long idx, void* target); 139 | void* (*createHash)(LiquidRenderer renderer); 140 | void* (*createArray)(LiquidRenderer renderer); 141 | void* (*createFloat)(LiquidRenderer renderer, double value); 142 | void* (*createBool)(LiquidRenderer renderer, bool value); 143 | void* (*createInteger)(LiquidRenderer renderer, long long value); 144 | void* (*createString)(LiquidRenderer renderer, const char* str); 145 | void* (*createPointer)(LiquidRenderer renderer, void* value); 146 | void* (*createNil)(LiquidRenderer renderer); 147 | void* (*createClone)(LiquidRenderer renderer, void* value); 148 | void (*freeVariable)(LiquidRenderer renderer, void* value); 149 | int (*compare)(void* a, void* b); 150 | } LiquidVariableResolver; 151 | 152 | LiquidContext liquidCreateContext(); 153 | const char* liquidGetContextError(LiquidContext context); 154 | void liquidFreeContext(LiquidContext context); 155 | void liquidImplementStrictStandardDialect(LiquidContext context); 156 | void liquidImplementPermissiveStandardDialect(LiquidContext context); 157 | #ifdef LIQUID_INCLUDE_WEB_DIALECT 158 | void liquidImplementWebDialect(LiquidContext context); 159 | #endif 160 | #ifdef LIQUID_INCLUDE_RAPIDJSON_VARIABLE 161 | void liquidImplementJSONStringVariable(); 162 | #endif 163 | 164 | LiquidRenderer liquidCreateRenderer(LiquidContext context); 165 | void liquidRendererSetStrictVariables(LiquidRenderer renderer, bool strict); 166 | void liquidRendererSetStrictFilters(LiquidRenderer renderer, bool strict); 167 | void liquidRendererSetCustomData(LiquidRenderer renderer, void* data); 168 | void* liquidRendererGetCustomData(LiquidRenderer renderer); 169 | void liquidRendererSetReturnValueNil(LiquidRenderer renderer); 170 | void liquidRendererSetReturnValueBool(LiquidRenderer renderer, bool b); 171 | void liquidRendererSetReturnValueString(LiquidRenderer renderer, const char* s, int length); 172 | void liquidRendererSetReturnValueInteger(LiquidRenderer renderer, long long i); 173 | void liquidRendererSetReturnValueFloat(LiquidRenderer renderer, double f); 174 | void liquidRendererSetReturnValueVariable(LiquidRenderer renderer, void* variable); 175 | size_t liquidGetRendererWarningCount(LiquidRenderer renderer); 176 | LiquidRendererWarning liquidGetRendererWarning(LiquidRenderer renderer, size_t index); 177 | void liquidFreeRenderer(LiquidRenderer renderer); 178 | 179 | LiquidParser liquidCreateParser(LiquidContext context); 180 | size_t liquidGetParserWarningCount(LiquidParser parser); 181 | LiquidParserWarning liquidGetParserWarning(LiquidParser parser, size_t index); 182 | void liquidFreeParser(LiquidParser parser); 183 | 184 | LiquidTemplate liquidParserParseTemplate(LiquidParser parser, const char* buffer, size_t size, const char* file, LiquidLexerError* lexer, LiquidParserError* error); 185 | LiquidTemplate liquidParserParseArgument(LiquidParser parser, const char* buffer, size_t size, LiquidLexerError* lexer, LiquidParserError* error); 186 | LiquidTemplate liquidParserParseAppropriate(LiquidParser parser, const char* buffer, size_t size, const char* file, LiquidLexerError* lexer, LiquidParserError* error); 187 | 188 | void liquidFreeTemplate(LiquidTemplate tmpl); 189 | 190 | LiquidOptimizer liquidCreateOptimizer(LiquidRenderer renderer); 191 | void liquidOptimizeTemplate(LiquidOptimizer optimizer, LiquidTemplate tmpl, void* variableStore); 192 | void liquidFreeOptimizer(LiquidOptimizer optimizer); 193 | 194 | LiquidCompiler liquidCreateCompiler(LiquidContext context); 195 | void liquidFreeCompiler(LiquidCompiler compiler); 196 | LiquidProgram liquidCompilerCompileTemplate(LiquidCompiler compiler, LiquidTemplate tmpl); 197 | void liquidFreeProgram(LiquidProgram program); 198 | int liquidCompilerDisassembleProgram(LiquidCompiler compiler, LiquidProgram program, char* buffer, size_t maxSize); 199 | int liquidParserUnparseTemplate(LiquidParser parser, LiquidTemplate tmpl, char* buffer, size_t maxSize); 200 | 201 | LiquidProgramRender liquidRendererRunProgram(LiquidRenderer renderer, void* variableStore, LiquidProgram program, LiquidRendererError* error); 202 | LiquidTemplateRender liquidRendererRenderTemplate(LiquidRenderer renderer, void* variableStore, LiquidTemplate tmpl, LiquidRendererError* error); 203 | void* liquidRendererRenderArgument(LiquidRenderer renderer, void* variableStore, LiquidTemplate argument, LiquidRendererError* error); 204 | typedef void (*LiquidWalkTemplateFunction)(LiquidTemplate tmpl, const LiquidNode node, void* data); 205 | void liquidWalkTemplate(LiquidTemplate tmpl, LiquidWalkTemplateFunction callback, void* data); 206 | void liquidFreeTemplateRender(LiquidTemplateRender render); 207 | 208 | const char* liquidTemplateRenderGetBuffer(LiquidTemplateRender render); 209 | size_t liquidTemplateRenderGetSize(LiquidTemplateRender render); 210 | 211 | void liquidGetLexerErrorMessage(LiquidLexerError error, char* buffer, size_t maxSize); 212 | void liquidGetParserErrorMessage(LiquidParserError error, char* buffer, size_t maxSize); 213 | void liquidGetRendererErrorMessage(LiquidRendererError error, char* buffer, size_t maxSize); 214 | 215 | void liquidFilterGetOperand(void** targetVariable, LiquidRenderer renderer, LiquidNode filter, void* variableStore); 216 | void liquidGetArgument(void** targetVariable, LiquidRenderer renderer, LiquidNode node, void* variableStore, int idx); 217 | void liquidGetChild(void** targetVariable, LiquidRenderer renderer, LiquidNode node, void* variablestore, int idx); 218 | int liquidGetArgumentCount(LiquidNode node); 219 | int liquidGetChildCount(LiquidNode node); 220 | typedef void (*LiquidRenderFunction)(LiquidRenderer renderer, LiquidNode node, void* variableStore, void* data); 221 | typedef void (*LiquidCompileFunction)(LiquidCompiler compiler, LiquidNode node, void* data); 222 | void liquidRegisterVariableResolver(LiquidRenderer renderer, LiquidVariableResolver resolver); 223 | 224 | // Passing -1 to min/maxArguments means no min or max. 225 | void* liquidRegisterTag(LiquidContext context, const char* symbol, enum ELiquidTagType type, int minArguments, int maxArguments, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data); 226 | void* liquidRegisterFilter(LiquidContext context, const char* symbol, int minArguments, int maxArguments, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data); 227 | void* liquidRegisterDotFilter(LiquidContext context, const char* symbol, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data); 228 | void* liquidRegisterOperator(LiquidContext context, const char* symbol, enum ELiquidOperatorArity arity, enum ELiquidOperatorFixness fixness, int priority, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data); 229 | 230 | void* liquidNodeTypeGetUserData(void* nodeType); 231 | #ifdef __cplusplus 232 | } 233 | #endif 234 | 235 | #endif 236 | -------------------------------------------------------------------------------- /src/rapidjsonvariable.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef LIQUIDRAPIDJSONVARAIBLE_H 3 | #define LIQUIDRAPIDJSONVARIABLE_H 4 | 5 | #include "common.h" 6 | #include "context.h" 7 | 8 | #include 9 | // #include 10 | // #include 11 | // #include 12 | // #include 13 | // #include 14 | 15 | namespace Liquid { 16 | 17 | // void dumpVariable(void* variable) { 18 | // rapidjson::StringBuffer sb; 19 | // rapidjson::Writer swriter(sb); 20 | // static_cast(variable)->Accept(swriter); 21 | // fprintf(stderr, "DUMP: %s\n", sb.GetString()); 22 | // } 23 | 24 | struct RapidJSONVariableResolver : LiquidVariableResolver { 25 | RapidJSONVariableResolver() { 26 | getType = +[](LiquidRenderer renderer, void* variable) { 27 | if (!variable) 28 | return LIQUID_VARIABLE_TYPE_NIL; 29 | switch (static_cast(variable)->GetType()) { 30 | case 0: 31 | return LIQUID_VARIABLE_TYPE_NIL; 32 | case 1: 33 | case 2: 34 | return LIQUID_VARIABLE_TYPE_BOOL; 35 | case 3: 36 | return LIQUID_VARIABLE_TYPE_DICTIONARY; 37 | case 4: 38 | return LIQUID_VARIABLE_TYPE_ARRAY; 39 | case 5: 40 | return LIQUID_VARIABLE_TYPE_STRING; 41 | case 6: 42 | return static_cast(variable)->IsDouble() ? LIQUID_VARIABLE_TYPE_FLOAT : LIQUID_VARIABLE_TYPE_INT; 43 | default: 44 | return LIQUID_VARIABLE_TYPE_OTHER; 45 | } 46 | }; 47 | getBool = +[](LiquidRenderer renderer, void* variable, bool* target) { 48 | int type = static_cast(variable)->GetType(); 49 | if (type != 2 && type != 3) 50 | return false; 51 | *target = type == 2; 52 | return true; 53 | }; 54 | getTruthy = +[](LiquidRenderer renderer, void* variable) { 55 | switch (static_cast(variable)->GetType()) { 56 | case 0: 57 | case 1: 58 | return false; 59 | case 5: 60 | return static_cast(variable)->GetStringLength() > 0; 61 | case 6: 62 | return static_cast(variable)->GetInt64() != 0; 63 | default: 64 | return true; 65 | } 66 | }; 67 | getString = +[](LiquidRenderer renderer, void* variable, char* target) { 68 | if (!static_cast(variable)->IsString()) 69 | return false; 70 | strcpy(target, static_cast(variable)->GetString()); 71 | return true; 72 | }; 73 | getStringLength = +[](LiquidRenderer renderer, void* variable) { 74 | if (!static_cast(variable)->IsString()) 75 | return -1LL; 76 | return (long long)static_cast(variable)->GetStringLength(); 77 | }; 78 | getInteger = +[](LiquidRenderer renderer, void* variable, long long* target) { 79 | if (!static_cast(variable)->IsNumber()) 80 | return false; 81 | *target = static_cast(variable)->GetInt64(); 82 | return true; 83 | }; 84 | getFloat = +[](LiquidRenderer renderer, void* variable, double* target) { 85 | if (!static_cast(variable)->IsNumber()) 86 | return false; 87 | *target = static_cast(variable)->GetFloat(); 88 | return true; 89 | }; 90 | getDictionaryVariable = +[](LiquidRenderer renderer, void* variable, const char* key, void** target) { 91 | if (!static_cast(variable)->IsObject() || !static_cast(variable)->HasMember(key)) 92 | return false; 93 | auto& value = (*static_cast(variable))[key]; 94 | *target = &value; 95 | return true; 96 | }; 97 | getArrayVariable = +[](LiquidRenderer renderer, void* variable, long long idx, void** target) { 98 | if (!static_cast(variable)->IsArray()) 99 | return false; 100 | if (idx < 0) 101 | idx += static_cast(variable)->Size(); 102 | if (idx < 0 || idx >= static_cast(variable)->Size()) 103 | return false; 104 | auto& value = (*static_cast(variable))[idx]; 105 | *target = &value; 106 | return true; 107 | }; 108 | 109 | getArraySize = +[](LiquidRenderer renderer, void* variable) { 110 | if (!static_cast(variable)->IsArray()) 111 | return -1LL; 112 | return (long long)static_cast(variable)->Size(); 113 | }; 114 | 115 | iterate = +[](LiquidRenderer renderer, void* variable, bool (*callback)(void* variable, void* data), void* data, int start, int limit, bool reverse) { 116 | rapidjson::Value& value = *static_cast(variable); 117 | if (value.IsArray()) { 118 | if (limit < 0) 119 | limit = (int)value.Size() + limit + 1; 120 | if (start < 0) 121 | start = 0; 122 | int endIndex = std::min(start+limit-1, (int)value.Size()); 123 | if (reverse) { 124 | for (int i = endIndex; i >= start; --i) { 125 | if (!callback(&value[i], data)) 126 | break; 127 | } 128 | } else { 129 | for (int i = start; i <= endIndex; ++i) { 130 | if (!callback(&value[i], data)) 131 | break; 132 | } 133 | } 134 | return true; 135 | } else if (value.IsObject()) { 136 | return false; 137 | } 138 | return false; 139 | }; 140 | 141 | setArrayVariable = +[](LiquidRenderer renderer, void* variable, long long idx, void* target) { 142 | assert(static_cast(renderer.renderer)->resolverCustomData); 143 | rapidjson::Value& value = *static_cast(target); 144 | if (!value.IsArray()) 145 | return (void*)NULL; 146 | if (idx >= value.Size()) 147 | value.Reserve(idx, ((rapidjson::Document*)static_cast(renderer.renderer)->resolverCustomData)->GetAllocator()); 148 | value[idx] = *static_cast(target); 149 | return (void*)&value[idx]; 150 | }; 151 | 152 | setDictionaryVariable = +[](LiquidRenderer renderer, void* variable, const char* key, void* target) { 153 | rapidjson::Document* document = (rapidjson::Document*)static_cast(renderer.renderer)->resolverCustomData; 154 | assert(document); 155 | rapidjson::Value& value = *static_cast(variable); 156 | if (!value.IsObject()) 157 | return (void*)NULL; 158 | rapidjson::Value copy; 159 | if (value.HasMember(key)) { 160 | value[key] = *static_cast(target); 161 | } else { 162 | value.AddMember(rapidjson::Value(key, document->GetAllocator()).Move(), *static_cast(target), ((rapidjson::Document*)static_cast(renderer.renderer)->resolverCustomData)->GetAllocator()); 163 | } 164 | return (void*)&value[key]; 165 | }; 166 | 167 | createHash = +[](LiquidRenderer renderer) { 168 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 169 | assert(document); 170 | return (void*)new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(rapidjson::kObjectType); 171 | }; 172 | createArray = +[](LiquidRenderer renderer) { 173 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 174 | assert(document); 175 | return (void*)new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(rapidjson::kArrayType); 176 | }; 177 | createFloat = +[](LiquidRenderer renderer, double value) { 178 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 179 | assert(document); 180 | rapidjson::Value* jvalue = new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(); 181 | jvalue->SetDouble(value); 182 | return (void*)jvalue; 183 | }; 184 | createBool = +[](LiquidRenderer renderer, bool value) { 185 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 186 | assert(document); 187 | rapidjson::Value* jvalue = new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(); 188 | jvalue->SetBool(value); 189 | return (void*)jvalue; 190 | }; 191 | createInteger = +[](LiquidRenderer renderer, long long value) { 192 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 193 | assert(document); 194 | rapidjson::Value* jvalue = new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(); 195 | jvalue->SetInt64(value); 196 | return (void*)jvalue; 197 | }; 198 | createString = +[](LiquidRenderer renderer, const char* value) { 199 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 200 | assert(static_cast(renderer.renderer)->resolverCustomData); 201 | rapidjson::Value* jvalue = new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(); 202 | jvalue->SetString(value, ((rapidjson::Document*)static_cast(renderer.renderer)->resolverCustomData)->GetAllocator()); 203 | return (void*)jvalue; 204 | }; 205 | createPointer = +[](LiquidRenderer renderer, void* value) { 206 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 207 | assert(document); 208 | rapidjson::Value* jvalue = new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(); 209 | jvalue->SetInt64((long long)value); 210 | return (void*)jvalue; 211 | }; 212 | createNil = +[](LiquidRenderer renderer) { 213 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 214 | assert(document); 215 | rapidjson::Value* jvalue = new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(); 216 | jvalue->SetNull(); 217 | return (void*)jvalue; 218 | }; 219 | createClone = +[](LiquidRenderer renderer, void* variable) { 220 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 221 | assert(document); 222 | assert(static_cast(renderer.renderer)->resolverCustomData); 223 | rapidjson::Value* jvalue = new (document->GetAllocator().Malloc(sizeof(rapidjson::Value))) rapidjson::Value(); 224 | jvalue->CopyFrom(*(rapidjson::Value*)variable, ((rapidjson::Document*)static_cast(renderer.renderer)->resolverCustomData)->GetAllocator()); 225 | return (void*)jvalue; 226 | }; 227 | freeVariable = +[](LiquidRenderer renderer, void* variable) { 228 | rapidjson::Document* document = static_cast(static_cast(renderer.renderer)->resolverCustomData); 229 | assert(document); 230 | rapidjson::Value* val = ((rapidjson::Value*)variable); 231 | val->~GenericValue(); 232 | document->GetAllocator().Free(variable); 233 | }; 234 | 235 | compare = +[](void* a, void* b) { return static_cast(a)->GetInt64() < static_cast(b)->GetInt64() ? -1 : 0; }; 236 | } 237 | }; 238 | 239 | } 240 | 241 | #endif 242 | 243 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Quickstart 3 | 4 | Install with (on Debian/Ubuntu-like systems): 5 | 6 | ```bash 7 | sudo apt-get -y install git build-essential g++ cmake 8 | git clone https://github.com/adamharrison/liquid-cpp.git && cd liquid-cpp && mkdir -p build && cd build && cmake .. && make -j 4 && sudo make install 9 | ``` 10 | 11 | Build with: `g++ program.cpp -lliquid` 12 | 13 | ```c++ 14 | #include 15 | #include 16 | 17 | int main(int argc, char* argv[]) { 18 | Liquid::CPPVariable store; 19 | store["a"] = 10; 20 | std::string tmpl = "{% if a > 1 %}123423{% else %}sdfjkshdfjkhsdf{% endif %}"; 21 | 22 | Liquid::Context context(Liquid::Context::EDialects::PERMISSIVE_STANDARD_DIALECT); 23 | std::cout << Liquid::Renderer(context).render(Liquid::Parser(context).parse(tmpl), store) << std::endl; 24 | return 0; 25 | } 26 | ``` 27 | 28 | ## Introduction 29 | 30 | A fully featured C++17 parser, renderer and optimizer for [Liquid](https://shopify.github.io/liquid/); Shopify's templating language. It's designed to provide official support for using liquid in the following languages: 31 | 32 | * C++ 33 | * C 34 | * Ruby 35 | * Perl 36 | 37 | Other languages (Javascript, Python, etc..) may come later, but any support for them will be unofficial. 38 | 39 | ## Goals 40 | 41 | Here's the overriding philosophy of what I'm aiming for, for this library. 42 | 43 | ### Modular 44 | 45 | All components of the process should be decoupled, and as much control as possible should be transferred to the programmer; nothing should be a monolith. 46 | 47 | ### Extensible 48 | 49 | You should be able to modify almost everything about the way the language behaves by registering new tags, operators, filters, drops, dialects; very little 50 | should be in the core. 51 | 52 | ### General 53 | 54 | There should be no special cases; every instance should be part of a generalizable set of classes, and operators. No direct text parsing, unless you absolutely need to. 55 | 56 | ### Performant 57 | 58 | It should be at least an order of magnitude faster than Ruby liquid, preferably more. It should also use at most an order of magnitude less memory. 59 | 60 | ### Portable 61 | 62 | It should be dead-easy to implement liquid for a new programming language, which means a really robust and easy-to-use C interface that can access most, if not all parts of the system. 63 | 64 | It should also compile on Windows, Mac, and Linux easily. 65 | 66 | ### Independent 67 | 68 | It should have basically no dependencies, other than the C++ standard library itself. There is a small dependency of `libcrypto` if you compile the `Web` dialect, but it is not included 69 | by default. 70 | 71 | ### Conformance 72 | 73 | It should pass all Shopify's test suites, and act exactly as Shopify liquid, if configured to do so. 74 | 75 | ### Insanity at the Edges 76 | 77 | That being said, the deficiencies of the Shopify implementation shouldn't hold it back. It should be extremely easy to toggle extra features; 78 | and a single call should permit `permissive` liquid, which allows for things like expression evaluation in all contexts. All things associated 79 | with the ruby version of Liquid, like global state, and whatnot, should exist purely as a façade pattern in a standalone Ruby module, which 80 | requires an underlying ruby module that implements the more modular, sane version. 81 | 82 | ## Status 83 | 84 | In development. Mostly stable. **VM/Compiler/Interpreter, should not be used in production code, yet, though the rest of the library probably can be, though I make no guarantees.** 85 | 86 | Basic memory audits of the core have been done with valgrind, and leaks have been closed. A more extensive battery of tests is required to determine if there's actually any undefined behaviour or further leaks. 87 | 88 | ### Quick Start 89 | 90 | ### Building 91 | 92 | Building the library is easy; so long as you have the appropriate build-system; currently the library uses cmake to build across platforms. 93 | 94 | It can be built and installed like so, from the main directory: 95 | 96 | ```mkdir -p build && cd build && cmake .. && make && sudo make install``` 97 | 98 | Eventually, I'll have a .deb that can be downloaded from somewhere for Ubuntu distros, but that's not quite up yet. 99 | 100 | #### C++ 101 | 102 | The C++ library, which is built with the normal Makefile can be linked in as a static library. Will eventually be available as a header-only library. 103 | 104 | ```c++ 105 | #include 106 | #include 107 | 108 | int main(int argc, char* argv[]) { 109 | // The liquid context represents all registered tags, operators, filters, and a way to resolve variables. 110 | Liquid::Context context; 111 | // Very few of the bits of liquid are part of the 'core'; instead, they are implemented as dialects. In order to 112 | // stick in most of the default bits of Liquid, you can ask the context to implement the standard dialect, but this 113 | // is not necessary. The only default tag available is {% raw %}, as this is less a tag, and more a lexing hint. No 114 | // filters, or operators, other than the unary - operator, are available by default; they are are all part of the liquid standard dialect. 115 | // In addition to setting all these nodes up, this also sets the 'truthiness' of variables to evaluted in a loose manner; meaning 116 | // that in addition to false and nil, being not true; 0, and empty string are also considered not true. 117 | // In order to implement the stricter Shopify ruby version of things, use `implementStrict` instead. 118 | Liquid::StandardDialect::implementPermissive(context); 119 | // In addition, dialects can be layered. Implementing one dialect does not forgo implementating another; and dialects 120 | // can override one another; whichever dialect was applied last will apply its proper tags, operators, and filters. 121 | // Currently, there is no way to deregsiter a tag, operator, or filter once registered. 122 | 123 | // Initialize a parser. These should be thread-local. One parser can parse many files. 124 | Liquid::Parser parser(context); 125 | 126 | const char exampleFile[] = "{% if a > 1 %}123423{% else %}sdfjkshdfjkhsdf{% endif %}"; 127 | // Throws an exception if there's a fatal parsing error. Liquid accepts quite a lot by default, so normally this'll be fine. 128 | // You can query a vector of errors under `parser.errors`. 129 | Liquid::Node ast = parser.parse(exampleFile, sizeof(exampleFile)-1); 130 | // Initialize a renderer. These should be thread-local. One renderer can render many templates. 131 | // Register the standard, out of the box variable implementation that lets us pass a type union'd variant that can hold either a long long, double, pointer, string, vector, or unordered_map . 132 | // All renders should be thread local. 133 | Liquid::Renderer renderer(context, Liquid::CPPVariableResolver()); 134 | 135 | Liquid::CPPVariable store; 136 | store["a"] = 10; 137 | 138 | std::string result = renderer.render(ast, store); 139 | // Should output `123423` 140 | std::cout << result << std::endl; 141 | return 0; 142 | } 143 | ``` 144 | 145 | This program should be linked with `-lliquid`. 146 | 147 | #### C 148 | 149 | The following program below is analogous to the one above, only using the external C interface. 150 | 151 | ```c 152 | 153 | #include 154 | #include 155 | 156 | int main(int argc, char* argv[]) { 157 | LiquidContext context = liquidCreateContext(); 158 | liquidImplementPermissiveStandardDialect(context); 159 | 160 | LiquidParser parser = liquidCreateParser(context); 161 | const char exampleFile[] = "{% if a > 1 %}123423{% else %}sdfjkshdfjkhsdf{% endif %}"; 162 | LiquidLexerError lexerError; 163 | LiquidParserError parserError; 164 | LiquidTemplate tmpl = liquidParserParseTemplate(parser, exampleFile, sizeof(exampleFile)-1, NULL, &lexerError, &parserError); 165 | if (lexerError.type) { 166 | char buffer[512]; 167 | liquidGetLexerErrorMessage(lexerError, buffer, sizeof(buffer)); 168 | fprintf(stderr, "Lexer Error: %s\n", buffer); 169 | exit(-1); 170 | } 171 | if (parserError.type) { 172 | char buffer[512]; 173 | liquidGetParserErrorMessage(parserError, buffer, sizeof(buffer)); 174 | fprintf(stderr, "Parser Error: %s\n", buffer); 175 | exit(-1); 176 | } 177 | // This object should be thread-local. 178 | LiquidRenderer renderer = liquidCreateRenderer(context); 179 | 180 | 181 | // If no LiquidVariableResolver is specified; an internal default is used that won't read anything you pass in, but will funciton for {% assign %}, {% capture %} and other tags. 182 | /* LiquidVariableResolver resolver = { 183 | ... 184 | }; 185 | 186 | liquidRegisterVariableResolver(renderer, resolver); */ 187 | 188 | // Use something that works with your language here; as resolved by the LiquidVariableResolver above. 189 | void* variableStore = NULL; 190 | LiquidRendererError rendererError; 191 | LiquidTemplateRender result = liquidRendererRenderTemplate(renderer, variableStore, tmpl, &rendererError); 192 | if (rendererError.type) { 193 | char buffer[512]; 194 | liquidGetRendererErrorMessage(rendererError, buffer, sizeof(buffer)); 195 | fprintf(stderr, "Renderer Error: %s\n", buffer); 196 | exit(-1); 197 | } 198 | 199 | // Should output `sdfjkshdfjkhsdf`. 200 | fprintf(stdout, "%s\n", liquidTemplateRenderGetBuffer(result)); 201 | 202 | // All resources, unless otherwise specified, must be free explicitly. 203 | liquidFreeTemplateRender(result); 204 | liquidFreeRenderer(renderer); 205 | liquidFreeTemplate(tmpl); 206 | liquidFreeContext(context); 207 | 208 | return 0; 209 | } 210 | ``` 211 | 212 | This program should be linked with `-lliquid -lstdc++ -lm`. 213 | 214 | #### Ruby 215 | 216 | The ruby module uses the C interface to interface with the liquid library. 217 | 218 | ##### Install 219 | 220 | Currently the package isn't uploaded on rubygems, so it has to be build manually. Luckily; this is easy: 221 | 222 | ```cd ruby/liquidcpp && gem install rake-compiler && rake compile && rake gem && gem install pkg/*.gem && cd - && cd ruby/liquidcpp-dir && rake compile && rake gem && gem install pkg/*.gem && cd -``` 223 | 224 | ##### Usage 225 | 226 | There're two ways to get the ruby library working. You can use the OO way, which mirrors the C++ API. 227 | 228 | ```ruby 229 | require 'liquidcpp' 230 | context = LiquidCPP.new() 231 | parser = LiquidCPP::Parser.new(context) 232 | renderer = LiquidCPP::Renderer.new(context) 233 | template = parser.parseTemplate("{% if a %}asdfghj {{ a }}{% endif %}") 234 | puts renderer.render({ "a" => 1 }, template) 235 | ``` 236 | 237 | Or, alternatively, one can use the "drop in replacement" module, which wraps all this, which will register the exact same constructs as the normal `liquid` gem. 238 | The advantage of this is that by simply replacing your require statements, you should be able to use your existing code; but it'll be close to an order of magnitude 239 | faster. The gem is currently under construction, and may not match exact Shopify gem behaviour in all circumstances. 240 | 241 | ```ruby 242 | require 'liquidcpp-dir' 243 | 244 | template = Liquid::Template.parse("{% if a %}asdfghj {{ a }}{% endif %}") 245 | puts template.render({ "a" => 1 }, template) 246 | ``` 247 | 248 | This is generally discouraged, as you lose object-orientation and modularity; like the ability to have independent liquid contexts with different 249 | tags, filters, operators, and settings vs. some weird mishmash where just shove everything into a global namespace, and "act" like you're using some OO. 250 | But, it's of course, up to you. 251 | 252 | #### Perl 253 | 254 | The perl module uses the C interface to interface with the liquid library, and attempts to mimic `WWW::Shopify::Liquid`. 255 | 256 | ##### Install 257 | 258 | Currently the module hasn't been uploaded to CPAN, but can be built and installed like so; 259 | 260 | ```cd perl && perl Makefile.PL && make && sudo make install && cd -``` 261 | 262 | Will eventually upload this. 263 | 264 | ##### Usage 265 | 266 | Uses the exact same interface as `WWW::Shopify::Liquid`, and is basically almost fully compatible with it as `WWW::Shopify::Liquid::XS`. 267 | 268 | All core constrcuts are overriden, and there is no optimizer at present, but all top-level functions should work correctly; and most dialects that have tags, filters and operators, 269 | that use `operate` instead of `process` or `render` should function without changes. 270 | 271 | ```perl 272 | use WWW::Shopify::Liquid::XS; 273 | 274 | my $liquid = WWW::Shopify::Liquid::XS->new; 275 | my $text = $liquid->render_file({ }, "myfile.liquid"); 276 | print "$text\n"; 277 | ``` 278 | 279 | #### Python 280 | 281 | This will probably come around at some point; but currently, there are no bindings for Python. 282 | 283 | #### Javascript 284 | 285 | This *may* happen. *Maybe*. 286 | 287 | ## Features / Roadmap 288 | 289 | This is what I'm aiming for at any rate. 290 | 291 | ### Done 292 | 293 | * Includes a standard dialect that contains all array, string and math filters by default, as well as all normal operators, and all control flow, iteration and variable tags. 294 | * Significantly less memory usage than ruby-based liquid. 295 | * Contextualized set of filters, operators, and tags per liquid object instantaited; no global state as in the regular Shopify gem, easily allowing for many flavours of liquid in the same process. 296 | * Ability to easily specify additions of filters, operators, and tags called `Dialects`, which can be mixed and matched. 297 | * Small footprint. Aiming for under 5K SLOC, with full standard Liquid as part of the core library. 298 | * Fully featured `extern "C"` interface for easy linking to most scripting languages. OOB bindings for both Ruby and Perl will be provided, that will act as drop-in replacements for `Liquid` and `WWW::Shopify::Liquid`. 299 | * Significant speedup over ruby-based `liquid` and `liquid/c`. (Need to do more comprehensive benchmarks; but at first glance seems like somewhere between a 10-50x speedup over regular `liquid/c` for both rendering and parsing using the VM.) 300 | * Fully compatible with both `Liquid`, Shopify's ruby gem, and `WWW::Shopify::Liquid`, the perl implementation. 301 | * Use a standard build system; like cmake. 302 | * Optional compatibilty with rapidjson to allow for easy JSON reading in C++. 303 | * Line accurate, and helpful error messages. 304 | * Ability to step through and examine the liquid AST. 305 | * In ruby integration, allow for all types of strict/lax modes. 306 | * Allow for tag registration in the drop in replacement module for Ruby. 307 | * Built-in optimizer that will do things like loop unrolling, conditional elimiation, etc.. 308 | * Togglable extra features, such as real operators, parentheses, etc.. 309 | * Ability to set limits on memory consumed, and time spent rendering. 310 | * Ability to partially render content, then spit back out the remaining liquid that genreated it, based on an AST tree. 311 | * UTF-8 aware. 312 | 313 | ### Partial 314 | 315 | * Full test suite that runs all major examples from Shopify's doucmentation. (Test suite runs some examples, but not all). 316 | * Write a register-based bytecode compiler/interpreter, which should be significantly faster than walking the parse tree. (basics in, if statements, for statements, and genrealized non-assembly calling in). 317 | 318 | ### TODO 319 | 320 | * General polish pass to clean up reundant code, and ensure consistency across the C, C++, Perl and Ruby APIs. 321 | 322 | ## License 323 | 324 | ### MIT License 325 | 326 | Copyright 2021 Adam Harrison 327 | 328 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 329 | 330 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 331 | 332 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 333 | 334 | -------------------------------------------------------------------------------- /src/cppvariable.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef LIQUIDCPPVARIABLE_H 3 | #define LIQUIDCPPVARIABLE_H 4 | 5 | #include "common.h" 6 | #include "context.h" 7 | 8 | namespace Liquid { 9 | 10 | struct CPPVariable { 11 | union { 12 | bool b; 13 | string s; 14 | double f; 15 | long long i; 16 | void* p; 17 | vector> a; 18 | unordered_map> d; 19 | }; 20 | ELiquidVariableType type; 21 | 22 | CPPVariable() : type(LIQUID_VARIABLE_TYPE_NIL) { } 23 | CPPVariable(bool b) : b(b), type(LIQUID_VARIABLE_TYPE_BOOL) { } 24 | CPPVariable(long long i) : i(i), type(LIQUID_VARIABLE_TYPE_INT) { } 25 | CPPVariable(int i) : CPPVariable((long long)i) { } 26 | CPPVariable(double f) : f(f), type(LIQUID_VARIABLE_TYPE_FLOAT) { } 27 | CPPVariable(const string& s) : s(s), type(LIQUID_VARIABLE_TYPE_STRING) { } 28 | CPPVariable(const char* s) : CPPVariable(string(s)) { } 29 | CPPVariable(void* nil) : type(LIQUID_VARIABLE_TYPE_NIL) { } 30 | CPPVariable(const CPPVariable& v) :type(LIQUID_VARIABLE_TYPE_NIL) { assign(v); } 31 | CPPVariable(CPPVariable&& v) : type(LIQUID_VARIABLE_TYPE_NIL) { this->move(std::move(v)); } 32 | CPPVariable(std::initializer_list l) : type(LIQUID_VARIABLE_TYPE_NIL) { 33 | assign(vector>()); 34 | a.reserve(l.size()); 35 | for (auto it = l.begin(); it != l.end(); ++it) 36 | a.push_back(make_unique(*it)); 37 | } 38 | CPPVariable(const unordered_map>& d) : type(LIQUID_VARIABLE_TYPE_NIL) { 39 | assign(d); 40 | } 41 | 42 | operator Variable () { return Variable({ this }); } 43 | 44 | 45 | ~CPPVariable() { clear(); } 46 | 47 | void clear() { 48 | switch (type) { 49 | case LIQUID_VARIABLE_TYPE_STRING: 50 | s.~string(); 51 | break; 52 | case LIQUID_VARIABLE_TYPE_DICTIONARY: 53 | d.~unordered_map>(); 54 | break; 55 | case LIQUID_VARIABLE_TYPE_ARRAY: 56 | a.~vector>(); 57 | break; 58 | default: 59 | break; 60 | } 61 | type = LIQUID_VARIABLE_TYPE_NIL; 62 | } 63 | 64 | 65 | void move(CPPVariable&& v) { 66 | clear(); 67 | type = v.type; 68 | switch (type) { 69 | case LIQUID_VARIABLE_TYPE_NIL: 70 | type = v.type; 71 | break; 72 | case LIQUID_VARIABLE_TYPE_BOOL: 73 | b = v.b; 74 | break; 75 | case LIQUID_VARIABLE_TYPE_FLOAT: 76 | f = v.f; 77 | break; 78 | case LIQUID_VARIABLE_TYPE_INT: 79 | i = v.i; 80 | break; 81 | case LIQUID_VARIABLE_TYPE_STRING: 82 | new(&s) string(std::move(v.s)); 83 | break; 84 | case LIQUID_VARIABLE_TYPE_ARRAY: 85 | new(&a) vector>(std::move(v.a)); 86 | break; 87 | case LIQUID_VARIABLE_TYPE_DICTIONARY: 88 | new(&d) unordered_map>(std::move(v.d)); 89 | break; 90 | case LIQUID_VARIABLE_TYPE_OTHER: 91 | assert(false); 92 | break; 93 | } 94 | v.type = LIQUID_VARIABLE_TYPE_NIL; 95 | } 96 | 97 | void assign(long long i) { clear(); this->i = i; type = LIQUID_VARIABLE_TYPE_INT; } 98 | void assign(int i) { assign((long long)i); } 99 | void assign(bool b) { clear(); this->b = b; type = LIQUID_VARIABLE_TYPE_BOOL; } 100 | void assign(double f) { clear(); this->f = f; type = LIQUID_VARIABLE_TYPE_FLOAT; } 101 | void assign(const string& s) { clear(); new(&this->s) string(); this->s = s; type = LIQUID_VARIABLE_TYPE_STRING; } 102 | void assign(const char* s) { assign(string(s)); } 103 | void assign(std::nullptr_t) { clear(); } 104 | void assign(const vector>& a) { 105 | clear(); 106 | new(&this->a) vector>(); 107 | this->a.reserve(a.size()); 108 | type = LIQUID_VARIABLE_TYPE_ARRAY; 109 | for (auto it = a.begin(); it != a.end(); ++it) 110 | this->a.push_back(make_unique(**it)); 111 | } 112 | void assign(const unordered_map>& d) { 113 | clear(); 114 | new(&this->d) unordered_map>(); 115 | type = LIQUID_VARIABLE_TYPE_DICTIONARY; 116 | for (auto it = d.begin(); it != d.end(); ++it) 117 | this->d[it->first] = make_unique(*it->second.get()); 118 | } 119 | void assign(const CPPVariable& v) { 120 | clear(); 121 | switch (v.type) { 122 | case LIQUID_VARIABLE_TYPE_NIL: 123 | type = v.type; 124 | break; 125 | case LIQUID_VARIABLE_TYPE_BOOL: 126 | assign(v.b); 127 | break; 128 | case LIQUID_VARIABLE_TYPE_FLOAT: 129 | assign(v.f); 130 | break; 131 | case LIQUID_VARIABLE_TYPE_INT: 132 | assign(v.i); 133 | break; 134 | case LIQUID_VARIABLE_TYPE_STRING: 135 | assign(v.s); 136 | break; 137 | case LIQUID_VARIABLE_TYPE_ARRAY: 138 | assign(v.a); 139 | break; 140 | case LIQUID_VARIABLE_TYPE_DICTIONARY: 141 | assign(v.d); 142 | break; 143 | case LIQUID_VARIABLE_TYPE_OTHER: 144 | assert(false); 145 | break; 146 | } 147 | } 148 | 149 | CPPVariable& operator[](const string& s) { 150 | if (type == LIQUID_VARIABLE_TYPE_NIL) { 151 | new(&this->d) unordered_map>(); 152 | type = LIQUID_VARIABLE_TYPE_DICTIONARY; 153 | } 154 | assert(type == LIQUID_VARIABLE_TYPE_DICTIONARY); 155 | auto it = d.find(s); 156 | if (it != d.end()) 157 | return *it->second.get(); 158 | return *d.emplace(s, make_unique()).first->second.get(); 159 | } 160 | CPPVariable& operator[](size_t idx) { 161 | if (type == LIQUID_VARIABLE_TYPE_NIL) { 162 | new(&this->a) vector>(); 163 | type = LIQUID_VARIABLE_TYPE_ARRAY; 164 | } 165 | assert(type == LIQUID_VARIABLE_TYPE_ARRAY); 166 | if (a.size() < idx+1) { 167 | size_t oldSize = a.size(); 168 | a.resize(idx+1); 169 | for (size_t i = oldSize; i < idx+1; ++i) 170 | a[i] = make_unique(); 171 | } 172 | return *a[idx].get(); 173 | } 174 | void pushBack(const CPPVariable& variable) { 175 | if (type == LIQUID_VARIABLE_TYPE_NIL) { 176 | new(&this->a) vector>(); 177 | type = LIQUID_VARIABLE_TYPE_ARRAY; 178 | } 179 | a.push_back(make_unique(variable)); 180 | } 181 | void pushBack(unique_ptr variable) { 182 | if (type == LIQUID_VARIABLE_TYPE_NIL) { 183 | new(&this->a) vector>(); 184 | type = LIQUID_VARIABLE_TYPE_ARRAY; 185 | } 186 | a.push_back(std::move(variable)); 187 | } 188 | 189 | CPPVariable& operator = (const std::string& s) { assign(s); return *this; } 190 | CPPVariable& operator = (const char* s) { assign(std::string(s)); return *this; } 191 | CPPVariable& operator = (std::nullptr_t) { assign(nullptr); return *this; } 192 | CPPVariable& operator = (double f) { assign(f); return *this; } 193 | CPPVariable& operator = (bool b) { assign(b); return *this; } 194 | CPPVariable& operator = (long long i) { assign(i); return *this; } 195 | CPPVariable& operator = (int i) { assign(i); return *this; } 196 | CPPVariable& operator = (CPPVariable&& v) { move(std::move(v)); return *this; } 197 | CPPVariable& operator = (const CPPVariable& v) { assign(v); return *this; } 198 | CPPVariable& operator = (const vector>& a) { assign(a); return *this; } 199 | 200 | 201 | ELiquidVariableType getType() const { 202 | return type; 203 | } 204 | 205 | bool getBool(bool& b) const { 206 | if (type != LIQUID_VARIABLE_TYPE_BOOL) 207 | return false; 208 | b = this->b; 209 | return true; 210 | } 211 | 212 | bool getTruthy() const { 213 | return !( 214 | (type == LIQUID_VARIABLE_TYPE_BOOL && !b) || 215 | (type == LIQUID_VARIABLE_TYPE_INT && !i) || 216 | (type == LIQUID_VARIABLE_TYPE_FLOAT && !f) || 217 | (type == LIQUID_VARIABLE_TYPE_OTHER && !p) || 218 | (type == LIQUID_VARIABLE_TYPE_NIL) 219 | ); 220 | } 221 | 222 | bool getString(std::string& s) const { 223 | switch (type) { 224 | case LIQUID_VARIABLE_TYPE_STRING: 225 | s = this->s; 226 | return true; 227 | case LIQUID_VARIABLE_TYPE_FLOAT: 228 | s = std::to_string(f); 229 | return true; 230 | case LIQUID_VARIABLE_TYPE_INT: 231 | s = std::to_string(i); 232 | return true; 233 | case LIQUID_VARIABLE_TYPE_BOOL: 234 | s = b ? "true" : "false"; 235 | return true; 236 | default: 237 | return false; 238 | } 239 | return true; 240 | } 241 | bool getInteger(long long& i) const { 242 | if (type != LIQUID_VARIABLE_TYPE_INT) 243 | return false; 244 | i = this->i; 245 | return true; 246 | } 247 | bool getFloat(double& f) const { 248 | if (type != LIQUID_VARIABLE_TYPE_FLOAT) 249 | return false; 250 | f = this->f; 251 | return true; 252 | } 253 | 254 | bool getDictionaryVariable(const CPPVariable** variable, const std::string& key) const { 255 | if (type != LIQUID_VARIABLE_TYPE_DICTIONARY) 256 | return false; 257 | auto it = d.find(key); 258 | if (it == d.end()) 259 | return false; 260 | *variable = it->second.get(); 261 | return true; 262 | } 263 | 264 | 265 | CPPVariable* setDictionaryVariable(const std::string& key, CPPVariable* target) { 266 | if (type != LIQUID_VARIABLE_TYPE_DICTIONARY) { 267 | if (type != LIQUID_VARIABLE_TYPE_NIL) 268 | return nullptr; 269 | assign(unordered_map>()); 270 | } 271 | auto it = d.find(key); 272 | if (it == d.end()) { 273 | it = d.emplace(key, unique_ptr(target)).first; 274 | } else { 275 | it->second = unique_ptr(target); 276 | } 277 | return it->second.get(); 278 | } 279 | 280 | bool getArrayVariable(const CPPVariable** variable, long long idx) const { 281 | if (type != LIQUID_VARIABLE_TYPE_ARRAY) 282 | return false; 283 | if (idx < 0) 284 | idx += a.size(); 285 | if (idx < 0) 286 | return false; 287 | if (idx >= (long long)a.size()) 288 | return false; 289 | *variable = &((*const_cast(this))[idx]); 290 | return true; 291 | } 292 | 293 | CPPVariable* setArrayVariable(long long idx, CPPVariable* target) { 294 | if (type != LIQUID_VARIABLE_TYPE_ARRAY) 295 | return nullptr; 296 | if (idx < 0) 297 | idx += a.size(); 298 | if (idx < 0 || idx >= (long long)a.size()) 299 | a.resize(idx+1); 300 | a[idx] = make_unique(*target); 301 | return a[idx].get(); 302 | } 303 | 304 | long long getArraySize() const { 305 | if (type != LIQUID_VARIABLE_TYPE_ARRAY) 306 | return -1; 307 | return a.size(); 308 | } 309 | 310 | bool iterate(bool (*callback)(void* variable, void* data), void* data, int start = 0, int limit = -1, bool reverse = false) const { 311 | if (type != LIQUID_VARIABLE_TYPE_ARRAY) 312 | return false; 313 | if (limit < 0) 314 | limit = (int)a.size() + limit + 1; 315 | if (start < 0) 316 | start = 0; 317 | int endIndex = std::min(start+limit-1, (int)a.size()); 318 | if (reverse) { 319 | for (int i = endIndex; i >= start; --i) { 320 | if (!callback(a[i].get(), data)) 321 | break; 322 | } 323 | } else { 324 | for (int i = start; i <= endIndex; ++i) { 325 | if (!callback(a[i].get(), data)) 326 | break; 327 | } 328 | } 329 | return true; 330 | } 331 | 332 | bool operator < (const CPPVariable& v) const { 333 | if (v.type != type) 334 | return false; 335 | switch (type) { 336 | case LIQUID_VARIABLE_TYPE_INT: 337 | return i < v.i; 338 | case LIQUID_VARIABLE_TYPE_FLOAT: 339 | return f < v.f; 340 | case LIQUID_VARIABLE_TYPE_STRING: { 341 | if (v.type == LIQUID_VARIABLE_TYPE_STRING) 342 | return s < v.s; 343 | string str; 344 | v.getString(str); 345 | return s < str; 346 | } 347 | default: 348 | return p < v.p; 349 | } 350 | return false; 351 | } 352 | }; 353 | 354 | 355 | struct CPPVariableResolver : LiquidVariableResolver { 356 | CPPVariableResolver() { 357 | getType = +[](LiquidRenderer renderer, void* variable) { return static_cast(variable)->type; }; 358 | getBool = +[](LiquidRenderer renderer, void* variable, bool* target) { return static_cast(variable)->getBool(*target); }; 359 | getTruthy = +[](LiquidRenderer renderer, void* variable) { return static_cast(variable)->getTruthy(); }; 360 | getString = +[](LiquidRenderer renderer, void* variable, char* target) { 361 | string s; 362 | if (!static_cast(variable)->getString(s)) 363 | return false; 364 | strcpy(target, s.data()); 365 | return true; 366 | }; 367 | getStringLength = +[](LiquidRenderer renderer, void* variable) { 368 | string s; 369 | if (!static_cast(variable)->getString(s)) 370 | return -1LL; 371 | return (long long)s.size(); 372 | }; 373 | getInteger = +[](LiquidRenderer renderer, void* variable, long long* target) { return static_cast(variable)->getInteger(*target); }; 374 | getFloat = +[](LiquidRenderer renderer, void* variable, double* target) { return static_cast(variable)->getFloat(*target); }; 375 | getDictionaryVariable = +[](LiquidRenderer renderer, void* variable, const char* key, void** target) { return static_cast(variable)->getDictionaryVariable((const CPPVariable**)target, key); }; 376 | getArrayVariable = +[](LiquidRenderer renderer, void* variable, long long idx, void** target) { return static_cast(variable)->getArrayVariable((const CPPVariable**)target, idx); }; 377 | setArrayVariable = +[](LiquidRenderer renderer, void* variable, long long idx, void* target) { return (void*)static_cast(variable)->setArrayVariable(idx, static_cast(target)); }; 378 | setDictionaryVariable = +[](LiquidRenderer renderer, void* variable, const char* key, void* target) { return (void*)static_cast(variable)->setDictionaryVariable(key, static_cast(target)); }; 379 | iterate = +[](LiquidRenderer renderer, void* variable, bool (*callback)(void* variable, void* data), void* data, int start, int limit, bool reverse) { return static_cast(variable)->iterate(callback, data, start, limit, reverse); }; 380 | getArraySize = +[](LiquidRenderer renderer, void* variable) { return static_cast(variable)->getArraySize(); }; 381 | 382 | createHash = +[](LiquidRenderer renderer) { return (void*)new CPPVariable(unordered_map>()); }; 383 | createArray = +[](LiquidRenderer renderer) { return (void*)new CPPVariable({ }); }; 384 | createFloat = +[](LiquidRenderer renderer, double value) { return (void*)new CPPVariable(value); }; 385 | createBool = +[](LiquidRenderer renderer, bool value) { return (void*)new CPPVariable(value); }; 386 | createInteger = +[](LiquidRenderer renderer, long long value) { return (void*)new CPPVariable(value); }; 387 | createString = +[](LiquidRenderer renderer, const char* value) { return (void*)new CPPVariable(string(value)); }; 388 | createPointer = +[](LiquidRenderer renderer, void* value) { return (void*)new CPPVariable(value); }; 389 | createNil = +[](LiquidRenderer renderer) { return (void*)new CPPVariable(); }; 390 | createClone = +[](LiquidRenderer renderer, void* variable) { return (void*)new CPPVariable(*static_cast(variable)); }; 391 | freeVariable = +[](LiquidRenderer renderer, void* variable) { delete (CPPVariable*)variable; }; 392 | 393 | compare = +[](void* a, void* b) { return *static_cast(a) < *static_cast(b) ? -1 : 0; }; 394 | } 395 | }; 396 | 397 | } 398 | 399 | #endif 400 | -------------------------------------------------------------------------------- /src/interface.cpp: -------------------------------------------------------------------------------- 1 | #include "interface.h" 2 | #include "dialect.h" 3 | #include "context.h" 4 | #include "optimizer.h" 5 | #include "compiler.h" 6 | #include 7 | 8 | using namespace Liquid; 9 | 10 | LiquidContext liquidCreateContext() { 11 | return LiquidContext({ new Context() }); 12 | } 13 | 14 | void liquidFreeContext(LiquidContext context) { 15 | delete (Context*)context.context; 16 | } 17 | 18 | void liquidImplementStrictStandardDialect(LiquidContext context) { 19 | StandardDialect::implementStrict(*static_cast(context.context)); 20 | } 21 | void liquidImplementPermissiveStandardDialect(LiquidContext context) { 22 | StandardDialect::implementPermissive(*static_cast(context.context)); 23 | } 24 | 25 | #ifdef LIQUID_INCLUDE_WEB_DIALECT 26 | void liquidImplementWebDialect(LiquidContext context) { 27 | WebDialect::implement(*static_cast(context.context)); 28 | } 29 | #endif 30 | 31 | LiquidRenderer liquidCreateRenderer(LiquidContext context) { 32 | // Removed these specified initializers for compatibility with C++17 and MSVC. 33 | Interpreter* interpreter = new Interpreter(*static_cast(context.context), { 34 | /* .getType = */+[](LiquidRenderer renderer, void* variable) { return LIQUID_VARIABLE_TYPE_NIL; }, 35 | /* .getBool = */+[](LiquidRenderer renderer, void* variable, bool* target) { return false; }, 36 | /* .getTruthy = */+[](LiquidRenderer renderer, void* variable) { return false; }, 37 | /* .getString = */+[](LiquidRenderer renderer, void* variable, char* target) { return false; }, 38 | /* .getStringLength = */+[](LiquidRenderer renderer, void* variable) { return 0LL; }, 39 | /* .getInteger = */+[](LiquidRenderer renderer, void* variable, long long* target) { return false; }, 40 | /* .getFloat = */+[](LiquidRenderer renderer, void* variable, double* target) { return false; }, 41 | /* .getDictionaryVariable = */+[](LiquidRenderer renderer, void* variable, const char* key, void** target) { return false; }, 42 | /* .getArrayVariable = */+[](LiquidRenderer renderer, void* variable, long long idx, void** target) { return false; }, 43 | /* .iterate = */+[](LiquidRenderer renderer, void* variable, bool (*callback)(void* variable, void* data), void* data, int start, int limit, bool reverse) { return false; }, 44 | /* .getArraySize = */+[](LiquidRenderer renderer, void* variable) { return 0LL; }, 45 | /* .setDictionaryVariable = */+[](LiquidRenderer renderer, void* variable, const char* key, void* target) { return (void*)NULL; }, 46 | /* .setArrayVariable = */+[](LiquidRenderer renderer, void* variable, long long idx, void* target) { return (void*)NULL; }, 47 | /* .createHash = */+[](LiquidRenderer renderer) { return (void*)NULL; }, 48 | /* .createArray = */+[](LiquidRenderer renderer) { return (void*)NULL; }, 49 | /* .createFloat = */+[](LiquidRenderer renderer, double value) { return (void*)NULL; }, 50 | /* .createBool = */+[](LiquidRenderer renderer, bool value) { return (void*)NULL; }, 51 | /* .createInteger = */+[](LiquidRenderer renderer, long long value) { return (void*)NULL; }, 52 | /* .createString = */+[](LiquidRenderer renderer, const char* str) { return (void*)NULL; }, 53 | /* .createPointer = */+[](LiquidRenderer renderer, void* value) { return (void*)NULL; }, 54 | /* .createNil = */+[](LiquidRenderer renderer) { return (void*)NULL; }, 55 | /* .createClone = */+[](LiquidRenderer renderer, void* value) { return (void*)NULL; }, 56 | /* .freeVariable = */+[](LiquidRenderer renderer, void* value) { }, 57 | /* .compare = */+[](void* a, void* b) { return 0; } 58 | }); 59 | // So that we pre-allocate things. 60 | interpreter->buffers.push(string()); 61 | return LiquidRenderer({ interpreter }); 62 | } 63 | 64 | void liquidFreeRenderer(LiquidRenderer renderer) { 65 | delete (Interpreter*)renderer.renderer; 66 | } 67 | 68 | LiquidParser liquidCreateParser(LiquidContext context) { 69 | return LiquidParser({ new Parser(*static_cast(context.context)) }); 70 | } 71 | void liquidFreeParser(LiquidParser parser) { 72 | delete (Parser*)parser.parser; 73 | } 74 | 75 | LiquidOptimizer liquidCreateOptimizer(LiquidRenderer renderer) { 76 | return LiquidOptimizer({ new Optimizer(*static_cast(renderer.renderer)) }); 77 | } 78 | 79 | void liquidOptimizeTemplate(LiquidOptimizer optimizer, LiquidTemplate tmpl, void* variableStore) { 80 | static_cast(optimizer.optimizer)->optimize(*static_cast(tmpl.ast), Variable({ variableStore })); 81 | } 82 | 83 | void liquidFreeOptimizer(LiquidOptimizer optimizer) { 84 | delete (Optimizer*)optimizer.optimizer; 85 | } 86 | 87 | LiquidCompiler liquidCreateCompiler(LiquidContext context) { 88 | return LiquidCompiler({ new Compiler(*static_cast(context.context)) } ); 89 | } 90 | 91 | void liquidFreeCompiler(LiquidCompiler compiler) { 92 | delete (Compiler*)compiler.compiler; 93 | } 94 | 95 | LiquidProgram liquidCompilerCompileTemplate(LiquidCompiler compiler, LiquidTemplate tmpl) { 96 | return LiquidProgram({ new Program(move(static_cast(compiler.compiler)->compile(*static_cast(tmpl.ast)))) }); 97 | } 98 | 99 | int liquidCompilerDisassembleProgram(LiquidCompiler compiler, LiquidProgram program, char* buffer, size_t maxSize) { 100 | string disassembly = static_cast(compiler.compiler)->disassemble(*static_cast(program.program)); 101 | size_t copied = std::min(maxSize, disassembly.size()); 102 | strncpy(buffer, disassembly.data(), copied); 103 | disassembly[copied-1] = 0; 104 | return copied; 105 | } 106 | 107 | int liquidParserUnparseTemplate(LiquidParser parser, LiquidTemplate tmpl, char* buffer, size_t maxSize) { 108 | string unparse = static_cast(parser.parser)->unparse(*static_cast(tmpl.ast)); 109 | size_t copied = std::min(maxSize, unparse.size()); 110 | strncpy(buffer, unparse.data(), copied); 111 | unparse[copied-1] = 0; 112 | return copied; 113 | } 114 | 115 | void liquidFreeProgram(LiquidProgram program) { 116 | delete static_cast(program.program); 117 | } 118 | 119 | LiquidProgramRender liquidRendererRunProgram(LiquidRenderer renderer, void* variableStore, LiquidProgram program, LiquidRendererError* error) { 120 | if (error) 121 | error->type = LIQUID_RENDERER_ERROR_TYPE_NONE; 122 | 123 | Interpreter* interpreter = static_cast(renderer.renderer); 124 | interpreter->buffers.top().clear(); 125 | try { 126 | interpreter->renderTemplate(*static_cast(program.program), Variable({ variableStore }), +[](const char* chunk, size_t len, void* data) { 127 | }, renderer.renderer); 128 | } catch (Renderer::Exception& exp) { 129 | if (error) 130 | *error = exp.rendererError; 131 | return { nullptr, 0 }; 132 | } 133 | return { interpreter->buffers.top().data(), interpreter->buffers.top().size() }; 134 | } 135 | 136 | 137 | LiquidTemplate liquidParserParseTemplate(LiquidParser parser, const char* buffer, size_t size, const char* file, LiquidLexerError* lexerError, LiquidParserError* parserError) { 138 | Node tmpl; 139 | if (lexerError) 140 | lexerError->type = LiquidLexerErrorType::LIQUID_LEXER_ERROR_TYPE_NONE; 141 | if (parserError) 142 | parserError->type = LiquidParserErrorType::LIQUID_PARSER_ERROR_TYPE_NONE; 143 | try { 144 | tmpl = static_cast(parser.parser)->parse(buffer, size, file ? file : ""); 145 | } catch (Parser::Exception& exp) { 146 | if (lexerError) 147 | *lexerError = exp.lexerError; 148 | if (parserError && exp.parserErrors.size() > 0) 149 | *parserError = exp.parserErrors[0]; 150 | return LiquidTemplate({ NULL }); 151 | } 152 | return LiquidTemplate({ new Node(std::move(tmpl)) }); 153 | } 154 | 155 | 156 | LiquidTemplate liquidParserParseArgument(LiquidParser parser, const char* buffer, size_t size, LiquidLexerError* lexerError, LiquidParserError* parserError) { 157 | Node tmpl; 158 | if (lexerError) 159 | lexerError->type = LiquidLexerErrorType::LIQUID_LEXER_ERROR_TYPE_NONE; 160 | if (parserError) 161 | parserError->type = LiquidParserErrorType::LIQUID_PARSER_ERROR_TYPE_NONE; 162 | try { 163 | tmpl = static_cast(parser.parser)->parseArgument(buffer, size); 164 | } catch (Parser::Exception& exp) { 165 | if (parserError) 166 | *parserError = exp.parserErrors[0]; 167 | return LiquidTemplate({ NULL }); 168 | } 169 | return LiquidTemplate({ new Node(std::move(tmpl)) }); 170 | } 171 | 172 | LiquidTemplate liquidParserParseAppropriate(LiquidParser parser, const char* buffer, size_t size, const char* file, LiquidLexerError* lexerError, LiquidParserError* parserError) { 173 | Node tmpl; 174 | if (lexerError) 175 | lexerError->type = LiquidLexerErrorType::LIQUID_LEXER_ERROR_TYPE_NONE; 176 | if (parserError) 177 | parserError->type = LiquidParserErrorType::LIQUID_PARSER_ERROR_TYPE_NONE; 178 | try { 179 | tmpl = static_cast(parser.parser)->parseAppropriate(buffer, size, file); 180 | } catch (Parser::Exception& exp) { 181 | if (parserError) 182 | *parserError = exp.parserErrors[0]; 183 | return LiquidTemplate({ NULL }); 184 | } 185 | return LiquidTemplate({ new Node(std::move(tmpl)) }); 186 | } 187 | 188 | size_t liquidGetParserWarningCount(LiquidParser parser) { 189 | return static_cast(parser.parser)->errors.size(); 190 | } 191 | 192 | LiquidParserWarning liquidGetParserWarning(LiquidParser parser, size_t index) { 193 | return static_cast(parser.parser)->errors[index]; 194 | } 195 | 196 | void liquidFreeTemplate(LiquidTemplate tmpl) { 197 | delete (Node*)tmpl.ast; 198 | } 199 | 200 | LiquidTemplateRender liquidRendererRenderTemplate(LiquidRenderer renderer, void* variableStore, LiquidTemplate tmpl, LiquidRendererError* error) { 201 | if (error) 202 | error->type = LIQUID_RENDERER_ERROR_TYPE_NONE; 203 | std::string* str; 204 | try { 205 | str = new std::string(std::move(static_cast(renderer.renderer)->render(*static_cast(tmpl.ast), Variable({ variableStore })))); 206 | } catch (Renderer::Exception& exp) { 207 | if (error) 208 | *error = exp.rendererError; 209 | return LiquidTemplateRender({ NULL }); 210 | } 211 | return LiquidTemplateRender({ str }); 212 | } 213 | 214 | 215 | void* liquidRendererRenderArgument(LiquidRenderer renderer, void* variableStore, LiquidTemplate tmpl, LiquidRendererError* error) { 216 | if (error) 217 | error->type = LIQUID_RENDERER_ERROR_TYPE_NONE; 218 | Variable variable; 219 | try { 220 | Variant variant = static_cast(renderer.renderer)->renderArgument(*static_cast(tmpl.ast), Variable({ variableStore })); 221 | static_cast(renderer.renderer)->inject(variable, variant); 222 | 223 | } catch (Renderer::Exception& exp) { 224 | if (error) 225 | *error = exp.rendererError; 226 | return nullptr; 227 | } 228 | return variable; 229 | } 230 | 231 | size_t liquidGetRendererWarningCount(LiquidRenderer renderer) { 232 | return static_cast(renderer.renderer)->errors.size(); 233 | } 234 | 235 | LiquidRendererWarning liquidGetRendererWarning(LiquidRenderer renderer, size_t idx) { 236 | return static_cast(renderer.renderer)->errors[idx]; 237 | } 238 | 239 | void liquidWalkTemplate(LiquidTemplate tmpl, LiquidWalkTemplateFunction callback, void* data) { 240 | static_cast(tmpl.ast)->walk([tmpl, callback, data](const Node& node) { 241 | callback(tmpl, LiquidNode { const_cast(&node) }, data); 242 | }); 243 | } 244 | 245 | void* liquidRegisterTag(LiquidContext context, const char* symbol, enum ELiquidTagType type, int minArguments, int maxArguments, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data) { 246 | Context* ctx = static_cast(context.context); 247 | unique_ptr registeredType = make_unique((TagNodeType::Composition)type, symbol, minArguments, maxArguments, optimization); 248 | registeredType->userRenderFunction = renderFunction; 249 | registeredType->userData = data; 250 | return ctx->registerType(move(registeredType)); 251 | } 252 | void* liquidRegisterFilter(LiquidContext context, const char* symbol, int minArguments, int maxArguments, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data) { 253 | Context* ctx = static_cast(context.context); 254 | unique_ptr registeredType = make_unique(symbol, minArguments, maxArguments, optimization); 255 | registeredType->userRenderFunction = renderFunction; 256 | registeredType->userData = data; 257 | return ctx->registerType(move(registeredType)); 258 | } 259 | 260 | void* liquidRegisterOperator(LiquidContext context, const char* symbol, enum ELiquidOperatorArity arity, enum ELiquidOperatorFixness fixness, int priority, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data) { 261 | Context* ctx = static_cast(context.context); 262 | unique_ptr registeredType = make_unique(symbol, (OperatorNodeType::Arity)arity, priority, (OperatorNodeType::Fixness)fixness, optimization); 263 | registeredType->userRenderFunction = renderFunction; 264 | registeredType->userData = data; 265 | return ctx->registerType(move(registeredType)); 266 | } 267 | 268 | void* liquidRegisterDotFilter(LiquidContext context, const char* symbol, LiquidOptimizationScheme optimization, LiquidRenderFunction renderFunction, void* data) { 269 | Context* ctx = static_cast(context.context); 270 | unique_ptr registeredType = make_unique(symbol, optimization); 271 | registeredType->userRenderFunction = renderFunction; 272 | registeredType->userData = data; 273 | return ctx->registerType(move(registeredType)); 274 | } 275 | 276 | void* liquidNodeTypeGetUserData(void* nodeType) { 277 | return static_cast(nodeType)->userData; 278 | } 279 | 280 | 281 | void liquidFilterGetOperand(void** targetVariable, LiquidRenderer lRenderer, LiquidNode filter, void* variableStore) { 282 | Renderer& renderer = *static_cast(lRenderer.renderer); 283 | const Node& node = *static_cast(filter.node); 284 | Node op = static_cast(node.type)->getOperand(renderer, node, variableStore); 285 | if (!op.type) { 286 | Variable v; 287 | renderer.inject(v, op.variant); 288 | *targetVariable = v; 289 | } 290 | } 291 | 292 | 293 | void liquidGetArgument(void** targetVariable, LiquidRenderer lRenderer, LiquidNode parent, void* variableStore, int idx) { 294 | Renderer& renderer = *static_cast(lRenderer.renderer); 295 | const Node& node = *static_cast(parent.node); 296 | 297 | Node arg = static_cast(node.type)->getArgument(renderer, node, variableStore, idx); 298 | if (!arg.type) { 299 | Variable v; 300 | renderer.inject(v, arg.variant); 301 | *targetVariable = v; 302 | } 303 | } 304 | 305 | int liquidGetArgumentCount(LiquidNode parent) { 306 | const Node& node = *static_cast(parent.node); 307 | return static_cast(node.type)->getArgumentCount(node); 308 | } 309 | 310 | 311 | void liquidGetChild(void** targetVariable, LiquidRenderer lRenderer, LiquidNode parent, void* variableStore, int idx) { 312 | Renderer& renderer = *static_cast(lRenderer.renderer); 313 | const Node& node = *static_cast(parent.node); 314 | 315 | Node child = static_cast(node.type)->getChild(renderer, node, variableStore, idx); 316 | if (!child.type) { 317 | Variable v; 318 | renderer.inject(v, child.variant); 319 | *targetVariable = v; 320 | } 321 | } 322 | 323 | int liquidGetChildCount(LiquidNode parent) { 324 | const Node& node = *static_cast(parent.node); 325 | return static_cast(node.type)->getChildCount(node); 326 | } 327 | 328 | void liquidRendererSetCustomData(LiquidRenderer renderer, void* data) { 329 | static_cast(renderer.renderer)->customData = data; 330 | } 331 | 332 | void* liquidRendererGetCustomData(LiquidRenderer renderer) { 333 | return static_cast(renderer.renderer)->customData; 334 | } 335 | 336 | 337 | void liquidRendererSetStrictVariables(LiquidRenderer renderer, bool strict) { 338 | static_cast(renderer.renderer)->logUnknownVariables = strict; 339 | } 340 | void liquidRendererSetStrictFilters(LiquidRenderer renderer, bool strict) { 341 | static_cast(renderer.renderer)->logUnknownFilters = strict; 342 | } 343 | 344 | void liquidRendererSetReturnValueString(LiquidRenderer renderer, const char* s, int length) { 345 | static_cast(renderer.renderer)->returnValue = move(Variant(string(s, length))); 346 | } 347 | 348 | void liquidRendererSetReturnValueNil(LiquidRenderer renderer) { 349 | static_cast(renderer.renderer)->returnValue = Variant(); 350 | } 351 | 352 | void liquidRendererSetReturnValueBool(LiquidRenderer renderer, bool b) { 353 | static_cast(renderer.renderer)->returnValue = Variant(b); 354 | } 355 | 356 | void liquidRendererSetReturnValueInteger(LiquidRenderer renderer, long long i) { 357 | static_cast(renderer.renderer)->returnValue = Variant(i); 358 | } 359 | 360 | void liquidRendererSetReturnValueFloat(LiquidRenderer renderer, double f) { 361 | static_cast(renderer.renderer)->returnValue = Variant(f); 362 | } 363 | 364 | void liquidRendererSetReturnValueVariable(LiquidRenderer renderer, void* variable) { 365 | static_cast(renderer.renderer)->returnValue = Variant(Variable{variable}); 366 | } 367 | 368 | void liquidRegisterVariableResolver(LiquidRenderer renderer, LiquidVariableResolver resolver) { 369 | Renderer* rdr = static_cast(renderer.renderer); 370 | rdr->variableResolver = resolver; 371 | } 372 | 373 | void liquidFreeTemplateRender(LiquidTemplateRender render) { 374 | delete (std::string*)(render.internal); 375 | } 376 | 377 | const char* liquidTemplateRenderGetBuffer(LiquidTemplateRender render) { 378 | return static_cast(render.internal)->data(); 379 | } 380 | 381 | size_t liquidTemplateRenderGetSize(LiquidTemplateRender render) { 382 | return static_cast(render.internal)->size(); 383 | } 384 | 385 | void liquidGetLexerErrorMessage(LiquidLexerError error, char* buffer, size_t maxSize) { 386 | string s = Lexer::Error::english(error); 387 | strncpy(buffer, s.c_str(), maxSize); 388 | buffer[maxSize] = 0; 389 | } 390 | void liquidGetParserErrorMessage(LiquidParserError error, char* buffer, size_t maxSize) { 391 | string s = Parser::Error::english(error); 392 | strncpy(buffer, s.c_str(), maxSize); 393 | buffer[maxSize] = 0; 394 | } 395 | void liquidGetRendererErrorMessage(LiquidRendererError error, char* buffer, size_t maxSize) { 396 | string s = Renderer::Error::english(error); 397 | strncpy(buffer, s.c_str(), maxSize); 398 | buffer[maxSize] = 0; 399 | } 400 | -------------------------------------------------------------------------------- /src/context.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDCONTEXT_H 2 | #define LIQUIDCONTEXT_H 3 | 4 | #include 5 | #include "common.h" 6 | #include "parser.h" 7 | #include "renderer.h" 8 | 9 | namespace Liquid { 10 | 11 | struct Renderer; 12 | 13 | struct LiteralNodeType : NodeType { 14 | Variant value; 15 | 16 | LiteralNodeType(string symbol, Variant value, ELiquidOptimizationScheme optimization = LIQUID_OPTIMIZATION_SCHEME_NONE) : NodeType(Type::LITERAL, symbol, 0, optimization), value(value) { } 17 | 18 | 19 | Node render(Renderer& renderer, const Node& node, Variable store) const override { 20 | return Node(value); 21 | } 22 | 23 | }; 24 | 25 | struct ContextualNodeType : NodeType { 26 | ContextualNodeType(NodeType::Type type, const std::string& symbol = "", int maxChildren = -1, LiquidOptimizationScheme scheme = LIQUID_OPTIMIZATION_SCHEME_NONE) : NodeType(type, symbol, maxChildren, scheme) { } 27 | 28 | // For operators that are internal to this tag. 29 | unordered_map> operators; 30 | // For filters specific to this tag. 31 | unordered_map> filters; 32 | 33 | // Used for registering intermedaites and qualiifers. 34 | template 35 | void registerType() { 36 | auto nodeType = make_unique(); 37 | switch (nodeType->type) { 38 | case NodeType::Type::FILTER: 39 | filters[nodeType->symbol] = std::move(nodeType); 40 | break; 41 | case NodeType::Type::OPERATOR: 42 | operators[nodeType->symbol] = std::move(nodeType); 43 | break; 44 | default: 45 | assert(false); 46 | break; 47 | } 48 | } 49 | }; 50 | 51 | struct TagNodeType : ContextualNodeType { 52 | enum class Composition { 53 | FREE, 54 | ENCLOSED, 55 | // When a tag needs to be like {% raw %} or {% comment %}, will take the entirety of its contents as a literal. 56 | LEXING_HALT 57 | }; 58 | 59 | // This is insanely stupid. 60 | struct QualifierNodeType : NodeType { 61 | enum class Arity { 62 | NONARY, 63 | UNARY 64 | }; 65 | Arity arity; 66 | 67 | QualifierNodeType(const string& symbol, Arity arity) : NodeType(NodeType::Type::QUALIFIER, symbol, 1, LIQUID_OPTIMIZATION_SCHEME_NONE), arity(arity) { } 68 | Node render(Renderer& renderer, const Node& node, Variable store) const override { return Node(); } 69 | }; 70 | 71 | // For things like if/else, and whatnot. else is a free tag that sits inside the if statement. 72 | unordered_map> intermediates; 73 | // For things for the forloop; like reversed, limit, etc... Super stupid, but Shopify threw them in, and there you are. 74 | unordered_map> qualifiers; 75 | 76 | Composition composition; 77 | int minArguments; 78 | int maxArguments; 79 | 80 | TagNodeType(Composition composition, string symbol, int minArguments = -1, int maxArguments = -1, LiquidOptimizationScheme optimization = LIQUID_OPTIMIZATION_SCHEME_FULL) : ContextualNodeType(NodeType::Type::TAG, symbol, -1, optimization), composition(composition), minArguments(minArguments), maxArguments(maxArguments) { } 81 | 82 | // Used for registering intermedaites and qualiifers. 83 | template 84 | T* registerType() { 85 | auto nodeType = make_unique(); 86 | T* pointer = nodeType.get(); 87 | switch (nodeType->type) { 88 | case NodeType::Type::TAG: 89 | intermediates[nodeType->symbol] = std::move(nodeType); 90 | break; 91 | case NodeType::Type::QUALIFIER: 92 | qualifiers[nodeType->symbol] = std::move(nodeType); 93 | break; 94 | case NodeType::Type::OPERATOR: 95 | operators[nodeType->symbol] = std::move(nodeType); 96 | break; 97 | case NodeType::Type::FILTER: 98 | filters[nodeType->symbol] = std::move(nodeType); 99 | break; 100 | default: 101 | assert(false); 102 | break; 103 | } 104 | return pointer; 105 | } 106 | }; 107 | 108 | struct EnclosedNodeType : TagNodeType { 109 | EnclosedNodeType(string symbol, int minArguments = -1, int maxArguments = -1, LiquidOptimizationScheme optimization = LIQUID_OPTIMIZATION_SCHEME_FULL) : TagNodeType(Composition::ENCLOSED, symbol, minArguments, maxArguments, optimization) { } 110 | }; 111 | 112 | static constexpr int MAX_PRIORITY = INT_MIN; 113 | struct OperatorNodeType : NodeType { 114 | enum class Arity { 115 | NONARY, 116 | UNARY, 117 | BINARY, 118 | NARY 119 | }; 120 | Arity arity; 121 | int priority; 122 | 123 | enum class Fixness { 124 | PREFIX, 125 | INFIX, 126 | AFFIX 127 | }; 128 | Fixness fixness; 129 | 130 | OperatorNodeType(string symbol, Arity arity, int priority = 0, Fixness fixness = Fixness::INFIX, LiquidOptimizationScheme optimization = LIQUID_OPTIMIZATION_SCHEME_FULL) : NodeType(Type::OPERATOR, symbol, -1, optimization), arity(arity), priority(priority), fixness(fixness) { 131 | switch (arity) { 132 | case Arity::NONARY: 133 | maxChildren = 0; 134 | break; 135 | case Arity::UNARY: 136 | maxChildren = 1; 137 | break; 138 | case Arity::BINARY: 139 | maxChildren = 2; 140 | break; 141 | case Arity::NARY: 142 | maxChildren = -1; 143 | break; 144 | } 145 | } 146 | void compile(Compiler& compiler, const Node& node) const override; 147 | 148 | Node getOperand(Renderer& renderer, const Node& node, Variable store, int idx) const; 149 | }; 150 | 151 | struct FilterNodeType : NodeType { 152 | int minArguments; 153 | int maxArguments; 154 | bool allowsWildcardQualifiers; 155 | int priority; 156 | 157 | // Wildcard Qualifier. 158 | struct QualifierNodeType : NodeType { 159 | QualifierNodeType() : NodeType(NodeType::Type::QUALIFIER, "", 1, LIQUID_OPTIMIZATION_SCHEME_NONE) { } 160 | Node render(Renderer& renderer, const Node& node, Variable store) const override { return Node(); } 161 | }; 162 | struct WildcardQualifierNodeType : QualifierNodeType { 163 | WildcardQualifierNodeType() { } 164 | Node render(Renderer& renderer, const Node& node, Variable store) const override { return Node(); } 165 | }; 166 | 167 | FilterNodeType(string symbol, int minArguments = -1, int maxArguments = -1, bool allowsWildcardQualifiers = false, LiquidOptimizationScheme optimization = LIQUID_OPTIMIZATION_SCHEME_FULL) : NodeType(NodeType::Type::FILTER, symbol, -1, optimization), minArguments(minArguments), maxArguments(maxArguments), allowsWildcardQualifiers(allowsWildcardQualifiers), priority(0) { } 168 | 169 | Node getOperand(Renderer& renderer, const Node& node, Variable store) const; 170 | Node getArgument(Renderer& renderer, const Node& node, Variable store, int idx) const; 171 | 172 | void compile(Compiler& compiler, const Node& node) const override; 173 | }; 174 | 175 | 176 | struct DotFilterNodeType : NodeType { 177 | DotFilterNodeType(string symbol, LiquidOptimizationScheme optimization = LIQUID_OPTIMIZATION_SCHEME_FULL) : NodeType(NodeType::Type::DOT_FILTER, symbol, -1, optimization) { } 178 | 179 | Node getOperand(Renderer& renderer, const Node& node, Variable store) const; 180 | }; 181 | 182 | // Represents something a file, or whatnot. Allows the filling in of 183 | struct ContextBoundaryNode : NodeType { 184 | ContextBoundaryNode() : NodeType(NodeType::Type::CONTEXTUAL, "", -1, LIQUID_OPTIMIZATION_SCHEME_NONE) { } 185 | Node render(Renderer& renderer, const Node& node, Variable store) const override { 186 | renderer.nodeContext = this; 187 | return renderer.retrieveRenderedNode(*node.children[1].get(), store); 188 | } 189 | }; 190 | 191 | struct Context { 192 | struct ConcatenationNode : NodeType { 193 | ConcatenationNode() : NodeType(Type::OPERATOR, "", -1, LIQUID_OPTIMIZATION_SCHEME_PARTIAL) { } 194 | 195 | Node render(Renderer& renderer, const Node& node, Variable store) const override; 196 | bool optimize(Optimizer& optimizer, Node& node, Variable store) const override; 197 | void compile(Compiler& compiler, const Node& node) const override; 198 | }; 199 | 200 | struct OutputNode : ContextualNodeType { 201 | OutputNode() : ContextualNodeType(Type::OUTPUT, "echo", -1, LIQUID_OPTIMIZATION_SCHEME_FULL) { } 202 | 203 | Node render(Renderer& renderer, const Node& node, Variable store) const override { 204 | assert(node.children.size() == 1); 205 | auto& argumentNode = node.children.front(); 206 | assert(argumentNode->children.size() == 1); 207 | return Variant(renderer.getString(renderer.retrieveRenderedNode(*argumentNode->children[0].get(), store))); 208 | } 209 | 210 | void compile(Compiler& compiler, const Node& node) const override; 211 | }; 212 | 213 | struct PassthruNode : NodeType { 214 | PassthruNode(NodeType::Type type) :NodeType(type) { } 215 | ~PassthruNode() { } 216 | 217 | Node render(Renderer& renderer, const Node& node, Variable store) const override { 218 | assert(node.children.size() == 1); 219 | auto it = node.children.begin(); 220 | if ((*it)->type) 221 | return (*it)->type->render(renderer, *it->get(), store); 222 | return *it->get(); 223 | } 224 | 225 | 226 | void compile(Compiler& compiler, const Node& node) const override; 227 | }; 228 | 229 | // These are purely for parsing purpose, and should not make their way to the rednerer. 230 | struct GroupNode : PassthruNode { GroupNode() : PassthruNode(Type::GROUP) { } }; 231 | // These are purely for parsing purpose, and should not make their way to the rednerer. 232 | struct GroupDereferenceNode : PassthruNode { GroupDereferenceNode() : PassthruNode(Type::GROUP_DEREFERENCE) { } }; 233 | // Used exclusively for tags. Should be never be rendered by itself. 234 | struct ArgumentNode : NodeType { 235 | ArgumentNode() : NodeType(Type::ARGUMENTS, "", -1, LIQUID_OPTIMIZATION_SCHEME_NONE) { } 236 | void compile(Compiler& compiler, const Node& node) const override; 237 | }; 238 | struct ArrayLiteralNode : NodeType { 239 | ArrayLiteralNode() : NodeType(Type::ARRAY_LITERAL) { } 240 | Node render(Renderer& renderer, const Node& node, Variable store) const override { 241 | Variant var { std::vector() }; 242 | var.a.reserve(node.children.size()); 243 | for (size_t i = 0; i < node.children.size(); ++i) 244 | var.a.push_back(move(renderer.retrieveRenderedNode(*node.children[i].get(), store).variant)); 245 | return Node(move(var)); 246 | } 247 | }; 248 | 249 | struct UnknownFilterNode : FilterNodeType { 250 | UnknownFilterNode() : FilterNodeType("", -1, -1, true, LIQUID_OPTIMIZATION_SCHEME_NONE) { } 251 | 252 | Node render(Renderer& renderer, const Node& node, Variable store) const override { 253 | if (renderer.logUnknownFilters) 254 | renderer.pushUnknownFilterWarning(node, store); 255 | return Node(); 256 | } 257 | }; 258 | 259 | ECoercion coerciveness = COERCE_NONE; 260 | EFalsiness falsiness = FALSY_FALSE; 261 | bool disallowArrayLiterals = false; 262 | bool disallowGroupingOutsideAssign = false; 263 | 264 | struct VariableNode : NodeType { 265 | VariableNode() : NodeType(Type::VARIABLE) { } 266 | 267 | Node render(Renderer& renderer, const Node& node, Variable store) const override { 268 | pair drop = renderer.getInternalDrop(node, store); 269 | if (drop.second) { 270 | Node result = drop.second(renderer, node, store, drop.first); 271 | if (result.type || result.variant.type != Variant::Type::VARIABLE) 272 | return result; 273 | return Node(renderer.parseVariant(result.variant.v)); 274 | } else { 275 | auto variableInfo = renderer.getVariable(node, store); 276 | if (!variableInfo.first) 277 | return Node(); 278 | return Node(renderer.parseVariant(variableInfo.second)); 279 | } 280 | } 281 | 282 | bool optimize(Optimizer& optimizer, Node& node, Variable store) const override; 283 | void compile(Compiler& compiler, const Node& node) const override; 284 | }; 285 | 286 | unordered_map> tagTypes; 287 | unordered_map> unaryOperatorTypes; 288 | unordered_map> binaryOperatorTypes; 289 | unordered_map> filterTypes; 290 | unordered_map> dotFilterTypes; 291 | unordered_map> literalTypes; 292 | 293 | ConcatenationNode concatenationNodeType; 294 | OutputNode outputNodeType; 295 | VariableNode variableNodeType; 296 | GroupNode groupNodeType; 297 | GroupDereferenceNode groupDereferenceNodeType; 298 | ArgumentNode argumentNodeType; 299 | UnknownFilterNode unknownFilterNodeType; 300 | ArrayLiteralNode arrayLiteralNodeType; 301 | ContextBoundaryNode contextBoundaryNodeType; 302 | FilterNodeType::WildcardQualifierNodeType filterWildcardQualifierNodeType; 303 | 304 | const NodeType* getConcatenationNodeType() const { return &concatenationNodeType; } 305 | const NodeType* getOutputNodeType() const { return &outputNodeType; } 306 | const VariableNode* getVariableNodeType() const { return &variableNodeType; } 307 | const NodeType* getGroupNodeType() const { return &groupNodeType; } 308 | const NodeType* getGroupDereferenceNodeType() const { return &groupDereferenceNodeType; } 309 | const NodeType* getArgumentsNodeType() const { return &argumentNodeType; } 310 | const NodeType* getUnknownFilterNodeType() const { return &unknownFilterNodeType; } 311 | const NodeType* getArrayLiteralNodeType() const { return &arrayLiteralNodeType; } 312 | const NodeType* getContextBoundaryNodeType() const { return &contextBoundaryNodeType; } 313 | const NodeType* getFilterWildcardQualifierNodeType() const { return &filterWildcardQualifierNodeType; } 314 | 315 | NodeType* registerType(unique_ptr type) { 316 | NodeType* value = type.get(); 317 | switch (type->type) { 318 | case NodeType::Type::TAG: 319 | tagTypes[type->symbol] = move(type); 320 | break; 321 | case NodeType::Type::OPERATOR: 322 | switch (static_cast(type.get())->arity) { 323 | case OperatorNodeType::Arity::BINARY: 324 | assert(static_cast(type.get())->fixness == OperatorNodeType::Fixness::INFIX); 325 | binaryOperatorTypes[type->symbol] = move(type); 326 | break; 327 | case OperatorNodeType::Arity::UNARY: 328 | assert(static_cast(type.get())->fixness == OperatorNodeType::Fixness::PREFIX); 329 | unaryOperatorTypes[type->symbol] = move(type); 330 | break; 331 | default: 332 | assert(false); 333 | break; 334 | } 335 | break; 336 | case NodeType::Type::FILTER: 337 | filterTypes[type->symbol] = move(type); 338 | break; 339 | case NodeType::Type::DOT_FILTER: 340 | dotFilterTypes[type->symbol] = move(type); 341 | break; 342 | case NodeType::Type::LITERAL: 343 | literalTypes[type->symbol] = move(type); 344 | break; 345 | default: 346 | assert(false); 347 | break; 348 | } 349 | return value; 350 | } 351 | template T* registerType() { return static_cast(registerType(make_unique())); } 352 | 353 | const TagNodeType* getTagType(string symbol) const { 354 | auto it = tagTypes.find(symbol); 355 | if (it == tagTypes.end()) 356 | return nullptr; 357 | return static_cast(it->second.get()); 358 | } 359 | const OperatorNodeType* getBinaryOperatorType(string symbol) const { 360 | auto it = binaryOperatorTypes.find(symbol); 361 | if (it == binaryOperatorTypes.end()) 362 | return nullptr; 363 | return static_cast(it->second.get()); 364 | } 365 | 366 | const OperatorNodeType* getUnaryOperatorType(string symbol) const { 367 | auto it = unaryOperatorTypes.find(symbol); 368 | if (it == unaryOperatorTypes.end()) 369 | return nullptr; 370 | return static_cast(it->second.get()); 371 | } 372 | 373 | const FilterNodeType* getFilterType(string symbol) const { 374 | auto it = filterTypes.find(symbol); 375 | if (it == filterTypes.end()) 376 | return nullptr; 377 | return static_cast(it->second.get()); 378 | } 379 | const DotFilterNodeType* getDotFilterType(string symbol) const { 380 | auto it = dotFilterTypes.find(symbol); 381 | if (it == dotFilterTypes.end()) 382 | return nullptr; 383 | return static_cast(it->second.get()); 384 | } 385 | 386 | const LiteralNodeType* getLiteralType(string symbol) const { 387 | auto it = literalTypes.find(symbol); 388 | if (it == literalTypes.end()) 389 | return nullptr; 390 | return static_cast(it->second.get()); 391 | } 392 | 393 | void optimize(Node& ast, Variable store); 394 | 395 | enum EDialects { 396 | NO_DIALECT = 0, 397 | STRICT_STANDARD_DIALECT = 1, 398 | PERMISSIVE_STANDARD_DIALECT = 2, 399 | WEB_DIALECT = 4 400 | 401 | }; 402 | Context(int dialects = NO_DIALECT); 403 | }; 404 | } 405 | 406 | #endif 407 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUIDCOMMON_H 2 | #define LIQUIDCOMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "interface.h" 15 | 16 | namespace Liquid { 17 | template 18 | using vector = std::vector; 19 | template 20 | using unordered_set = std::unordered_set; 21 | using string = std::string; 22 | template 23 | using unordered_map = std::unordered_map; 24 | template 25 | using unique_ptr = std::unique_ptr; 26 | template 27 | using shared_ptr = std::shared_ptr; 28 | using std::make_unique; 29 | using std::move; 30 | using std::pair; 31 | template 32 | using stack = std::stack; 33 | 34 | 35 | struct Exception : public std::exception { 36 | std::string internal; 37 | 38 | Exception() { } 39 | Exception(const char* format, ...) { 40 | char buffer[512]; 41 | va_list args; 42 | va_start(args, format); 43 | vsnprintf(buffer, sizeof(buffer), format, args); 44 | va_end(args); 45 | internal = buffer; 46 | } 47 | const char * what () const noexcept override { 48 | return internal.c_str(); 49 | } 50 | }; 51 | 52 | // Represents the underlying variable implementation that is passed in to liquid. 53 | struct Variable { 54 | void* pointer; 55 | 56 | Variable() :pointer(nullptr) { } 57 | Variable(void* pointer) : pointer(pointer) { } 58 | 59 | operator bool() const { return pointer != nullptr; } 60 | operator void*() { return pointer; } 61 | operator const void*() const { return pointer; } 62 | operator void** () { return &pointer; } 63 | 64 | Variable& operator = (void* pointer) { this->pointer = pointer; return *this; } 65 | }; 66 | 67 | enum EFalsiness { 68 | FALSY_FALSE = 0, 69 | FALSY_0 = 1, 70 | FALSY_EMPTY_STRING = 2, 71 | FALSY_NIL = 4 72 | }; 73 | enum ECoercion { 74 | COERCE_NONE = 0, 75 | COERCE_STRING_TO_NUMBERS = 1, 76 | COERCE_NUMBERS_TO_STRING = 2, 77 | COERCE_ALL = 3 78 | }; 79 | // Represents everything that can be addressed by textual liquid. A built-in type. 80 | struct Variant { 81 | 82 | enum class Type { 83 | NIL, 84 | BOOL, 85 | FLOAT, 86 | INT, 87 | STRING, 88 | STRING_VIEW, 89 | ARRAY, 90 | VARIABLE, 91 | POINTER 92 | }; 93 | 94 | 95 | union { 96 | bool b; 97 | double f; 98 | long long i; 99 | string s; 100 | void* p; 101 | Variable v; 102 | vector a; 103 | struct { 104 | const char* view; 105 | size_t len; 106 | }; 107 | }; 108 | Type type; 109 | 110 | Variant() : type(Type::NIL) { } 111 | 112 | Variant(const Variant& v) : type(v.type) { 113 | switch (type) { 114 | case Type::STRING: 115 | new(&s) std::string(v.s); 116 | break; 117 | case Type::ARRAY: 118 | new(&a) std::vector(v.a); 119 | break; 120 | case Type::STRING_VIEW: 121 | view = v.view; 122 | len = v.len; 123 | break; 124 | case Type::BOOL: 125 | b = v.b; 126 | break; 127 | case Type::FLOAT: 128 | f = v.f; 129 | break; 130 | case Type::INT: 131 | i = v.i; 132 | break; 133 | case Type::POINTER: 134 | p = v.p; 135 | break; 136 | case Type::VARIABLE: 137 | this->v = v.v; 138 | break; 139 | case Type::NIL: 140 | break; 141 | } 142 | } 143 | Variant(Variant&& v) : type(v.type) { 144 | switch (type) { 145 | case Type::STRING: 146 | new(&s) std::string(std::move(v.s)); 147 | break; 148 | case Type::ARRAY: 149 | new(&a) vector(std::move(v.a)); 150 | break; 151 | case Type::STRING_VIEW: 152 | view = v.view; 153 | len = v.len; 154 | break; 155 | case Type::BOOL: 156 | b = v.b; 157 | break; 158 | case Type::FLOAT: 159 | f = v.f; 160 | break; 161 | case Type::INT: 162 | i = v.i; 163 | break; 164 | case Type::POINTER: 165 | p = v.p; 166 | break; 167 | case Type::VARIABLE: 168 | this->v = v.v; 169 | break; 170 | case Type::NIL: 171 | break; 172 | } 173 | } 174 | Variant(bool b) : b(b), type(Type::BOOL) { } 175 | Variant(double f) : f(f), type(Type::FLOAT) { } 176 | Variant(long long i) : i(i), type(Type::INT) { } 177 | Variant(const std::string& s) : s(s), type(Type::STRING) { } 178 | Variant(std::string&& s) : s(std::move(s)), type(Type::STRING) { } 179 | Variant(const char* s) : s(s), type(Type::STRING) { } 180 | Variant(const char* view, size_t len) : view(view), len(len), type(Type::STRING_VIEW) { } 181 | Variant(Variable v) : v(v), type(Type::VARIABLE) { } 182 | Variant(void* p) : p(p), type(p ? Type::POINTER : Type::NIL) { } 183 | Variant(std::nullptr_t) : p(nullptr), type(Type::NIL) { } 184 | Variant(const std::vector& a) : a(a), type(Type::ARRAY) { } 185 | Variant(vector&& a) : a(std::move(a)), type(Type::ARRAY) { } 186 | 187 | ~Variant() { 188 | switch (type) { 189 | case Type::STRING: 190 | s.~string(); 191 | break; 192 | case Type::ARRAY: 193 | a.~vector(); 194 | break; 195 | default: 196 | break; 197 | } 198 | } 199 | 200 | bool isTruthy(EFalsiness falsiness) const { 201 | switch (type) { 202 | case Variant::Type::BOOL: 203 | return b; 204 | case Variant::Type::INT: 205 | return !((falsiness & FALSY_0) && !i); 206 | case Variant::Type::FLOAT: 207 | return !((falsiness & FALSY_0) && !f); 208 | case Variant::Type::POINTER: 209 | return !((falsiness & FALSY_NIL) && !p); 210 | case Variant::Type::NIL: 211 | return !(falsiness & FALSY_NIL); 212 | case Variant::Type::STRING: 213 | return !((falsiness & FALSY_EMPTY_STRING) && s.size() == 0); 214 | default: 215 | return true; 216 | } 217 | return true; 218 | } 219 | 220 | Variant& operator = (const Variant& v) { 221 | switch (v.type) { 222 | case Type::STRING: 223 | if (type != Type::STRING) { 224 | if (v.type == Type::ARRAY) 225 | a.~vector(); 226 | else 227 | i = 0; 228 | new(&s) string; 229 | } 230 | s = v.s; 231 | break; 232 | case Type::ARRAY: 233 | if (type != Type::ARRAY) { 234 | if (v.type == Type::STRING) 235 | s.~string(); 236 | else 237 | i = 0; 238 | new(&a) vector(); 239 | } 240 | a = v.a; 241 | break; 242 | case Type::STRING_VIEW: 243 | view = v.view; 244 | len = v.len; 245 | break; 246 | default: 247 | f = v.f; 248 | break; 249 | } 250 | return *this; 251 | } 252 | 253 | Variant& operator = (Variant&& v) { 254 | switch (v.type) { 255 | case Type::STRING: 256 | if (type != Type::STRING) { 257 | if (v.type == Type::ARRAY) 258 | a.~vector(); 259 | else 260 | i = 0; 261 | new(&s) string; 262 | } 263 | s = move(v.s); 264 | break; 265 | case Type::ARRAY: 266 | if (type != Type::ARRAY) { 267 | if (v.type == Type::STRING) 268 | s.~string(); 269 | else 270 | i = 0; 271 | new(&a) vector(); 272 | } 273 | a = move(v.a); 274 | break; 275 | case Type::STRING_VIEW: 276 | view = v.view; 277 | len = v.len; 278 | break; 279 | default: 280 | f = v.f; 281 | break; 282 | } 283 | return *this; 284 | } 285 | 286 | bool operator == (const Variant& v) const { 287 | if (type != v.type) 288 | return false; 289 | switch (type) { 290 | case Type::STRING: 291 | return s == v.s; 292 | case Type::INT: 293 | return i == v.i; 294 | case Type::ARRAY: 295 | return a == v.a; 296 | case Type::FLOAT: 297 | return f == v.f; 298 | case Type::NIL: 299 | return true; 300 | case Type::BOOL: 301 | return b == v.b; 302 | default: 303 | return p == v.p; 304 | } 305 | } 306 | bool isNumeric() const { 307 | return type == Type::INT || type == Type::FLOAT; 308 | } 309 | 310 | string getString() const { 311 | switch (type) { 312 | case Type::STRING: 313 | return s; 314 | case Type::STRING_VIEW: 315 | return string(view, len); 316 | case Type::FLOAT: { 317 | // We can't use to_string because that'll add an annoying amount of unecesarry 0s after the decimal point. 318 | char buffer[512]; 319 | int length = snprintf(buffer, sizeof(buffer), "%f", f), i, j; 320 | for (i = 0; i < length && buffer[i] != '.'; ++i); 321 | for (j = length; i < length && j > i && buffer[j-1] == '0'; --j); 322 | return string(buffer, j == i + 1 ? i : j); 323 | } 324 | case Type::INT: 325 | return std::to_string(i); 326 | case Type::BOOL: 327 | return b ? "true" : "false"; 328 | default: 329 | return string(); 330 | } 331 | } 332 | 333 | long long getInt() const { 334 | switch (type) { 335 | case Type::INT: 336 | return i; 337 | case Type::FLOAT: 338 | return (long long)f; 339 | case Type::STRING: 340 | return atoll(s.c_str()); 341 | case Type::STRING_VIEW: 342 | return atoll(view); 343 | default: 344 | return 0; 345 | } 346 | } 347 | 348 | double getFloat() const { 349 | switch (type) { 350 | case Type::INT: 351 | return (double)i; 352 | case Type::FLOAT: 353 | return f; 354 | case Type::STRING: 355 | return atof(s.c_str()); 356 | case Type::STRING_VIEW: 357 | return atof(view); 358 | default: 359 | return 0.0; 360 | } 361 | } 362 | 363 | 364 | size_t hash() const { 365 | switch (type) { 366 | case Type::STRING: 367 | return std::hash{}(s); 368 | case Type::STRING_VIEW: 369 | return std::hash{}(getString()); 370 | case Type::INT: 371 | return std::hash{}(i); 372 | case Type::ARRAY: 373 | return 0; 374 | case Type::FLOAT: 375 | return std::hash{}(f); 376 | case Type::NIL: 377 | return 0; 378 | case Type::BOOL: 379 | return std::hash{}(b); 380 | default: 381 | return std::hash{}(p); 382 | } 383 | } 384 | 385 | bool operator < (const Variant& v) const { 386 | switch (type) { 387 | case Type::INT: 388 | return i < v.getInt(); 389 | break; 390 | case Type::FLOAT: 391 | return f < v.getFloat(); 392 | break; 393 | case Type::STRING: 394 | if (v.type == Type::STRING) 395 | return s < v.s; 396 | return s < v.getString(); 397 | break; 398 | case Type::STRING_VIEW: 399 | return v.getString() < string(view, len); 400 | break; 401 | default: 402 | return p < v.p; 403 | break; 404 | } 405 | return false; 406 | } 407 | }; 408 | 409 | struct NodeType; 410 | 411 | struct Node { 412 | const NodeType* type; 413 | size_t line; 414 | size_t column; 415 | 416 | union { 417 | Variant variant; 418 | vector> children; 419 | }; 420 | 421 | Node() : type(nullptr), line(0), column(0), variant() { } 422 | Node(const NodeType* type) : type(type), line(0), column(0), children() { } 423 | Node(const Node& node) :type(node.type), line(node.line), column(node.column) { 424 | if (type) { 425 | new(&children) vector>(); 426 | children.reserve(node.children.size()); 427 | for (auto it = node.children.begin(); it != node.children.end(); ++it) 428 | children.push_back(make_unique(*it->get())); 429 | } else { 430 | new(&variant) Variant(node.variant); 431 | } 432 | } 433 | Node(const Variant& v) : type(nullptr), line(0), column(0), variant(v) { } 434 | Node(Variant&& v) : type(nullptr), line(0), column(0), variant(std::move(v)) { } 435 | Node(Node&& node) :type(node.type), line(node.line), column(node.column) { 436 | if (type) { 437 | new(&children) vector>(std::move(node.children)); 438 | } else { 439 | new(&variant) Variant(std::move(node.variant)); 440 | } 441 | } 442 | ~Node() { 443 | if (type) 444 | children.~vector>(); 445 | else 446 | variant.~Variant(); 447 | } 448 | 449 | string getString() const { 450 | assert(!type); 451 | return variant.getString(); 452 | } 453 | 454 | Node& operator = (const Node& n) { 455 | if (type) 456 | children.~vector>(); 457 | if (n.type) { 458 | new(&children) vector>(); 459 | children.reserve(n.children.size()); 460 | for (auto it = n.children.begin(); it != n.children.end(); ++it) 461 | children.push_back(make_unique(*it->get())); 462 | } else { 463 | new(&variant) Variant(); 464 | } 465 | type = n.type; 466 | return *this; 467 | } 468 | 469 | // This is more complicated, because of the case where you move one of your children into yourself. 470 | Node& operator = (Node&& n) { 471 | if (type) { 472 | if (!n.type) { 473 | Variant v = move(n.variant); 474 | type = n.type; 475 | children.~vector>(); 476 | new(&variant) Variant(move(v)); 477 | } else { 478 | type = n.type; 479 | children = move(n.children); 480 | } 481 | } else { 482 | if (type) 483 | children.~vector>(); 484 | if (n.type) { 485 | new(&children) vector>(); 486 | children = move(n.children); 487 | } else { 488 | new(&variant) Variant(move(n.variant)); 489 | } 490 | type = n.type; 491 | } 492 | return *this; 493 | } 494 | 495 | template 496 | void walk(T callback) const { 497 | callback(*this); 498 | if (type) { 499 | for (auto it = children.begin(); it != children.end(); ++it) 500 | (*it)->walk(callback); 501 | } 502 | } 503 | }; 504 | 505 | struct Renderer; 506 | struct Parser; 507 | struct Context; 508 | struct Optimizer; 509 | struct Compiler; 510 | struct Program; 511 | 512 | struct NodeType { 513 | enum Type { 514 | VARIABLE, 515 | TAG, 516 | GROUP, 517 | GROUP_DEREFERENCE, 518 | LITERAL, 519 | ARRAY_LITERAL, 520 | OUTPUT, 521 | ARGUMENTS, 522 | QUALIFIER, 523 | OPERATOR, 524 | FILTER, 525 | DOT_FILTER, 526 | CONTEXTUAL 527 | }; 528 | 529 | Type type; 530 | string symbol; 531 | int maxChildren; 532 | LiquidOptimizationScheme optimization; 533 | void* userData = nullptr; 534 | LiquidRenderFunction userRenderFunction = nullptr; 535 | LiquidCompileFunction userCompileFunction = nullptr; 536 | 537 | NodeType(Type type, string symbol = "", int maxChildren = -1, LiquidOptimizationScheme optimization = LIQUID_OPTIMIZATION_SCHEME_FULL) : type(type), symbol(symbol), maxChildren(maxChildren), optimization(optimization) { } 538 | NodeType(const NodeType&) = default; 539 | NodeType(NodeType&&) = default; 540 | virtual ~NodeType() { } 541 | 542 | virtual Node render(Renderer& renderer, const Node& node, Variable store) const; 543 | virtual void compile(Compiler& compiler, const Node& node) const; 544 | virtual bool validate(Parser& parser, const Node& node) const { return true; } 545 | virtual bool optimize(Optimizer& optimizer, Node& node, Variable store) const; 546 | 547 | Node getArgument(Renderer& renderer, const Node& node, Variable store, int idx) const; 548 | Node getChild(Renderer& renderer, const Node& node, Variable store, int idx) const; 549 | int getArgumentCount(const Node& node) const; 550 | int getChildCount(const Node& node) const; 551 | }; 552 | } 553 | 554 | #endif 555 | --------------------------------------------------------------------------------