├── .gitignore
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── console
├── emerald
└── setup
├── circle.yml
├── config
└── pre_commit.yml
├── cpp
├── .gitignore
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── Makefile
├── lib
│ ├── catch.hpp
│ ├── json.hpp
│ ├── peglib.h
│ └── utf8.h
├── requirements.txt
├── samples
│ └── sample.emr
├── setup.py
├── src
│ ├── grammar.cpp
│ ├── grammar.hpp
│ ├── grammar.peg
│ ├── htmlencode.cpp
│ ├── htmlencode.hpp
│ ├── main.cpp
│ ├── nodes
│ │ ├── attribute.cpp
│ │ ├── attribute.hpp
│ │ ├── attributes.cpp
│ │ ├── attributes.hpp
│ │ ├── binary_expr.cpp
│ │ ├── binary_expr.hpp
│ │ ├── boolean.hpp
│ │ ├── comment.cpp
│ │ ├── comment.hpp
│ │ ├── conditional.cpp
│ │ ├── conditional.hpp
│ │ ├── each.cpp
│ │ ├── each.hpp
│ │ ├── escaped.cpp
│ │ ├── escaped.hpp
│ │ ├── key_value_pair.cpp
│ │ ├── key_value_pair.hpp
│ │ ├── line.cpp
│ │ ├── line.hpp
│ │ ├── literal_new_line.cpp
│ │ ├── literal_new_line.hpp
│ │ ├── node.cpp
│ │ ├── node.hpp
│ │ ├── node_list.cpp
│ │ ├── node_list.hpp
│ │ ├── scope.cpp
│ │ ├── scope.hpp
│ │ ├── scope_fn.hpp
│ │ ├── scoped_key_value_pairs.cpp
│ │ ├── scoped_key_value_pairs.hpp
│ │ ├── tag_statement.cpp
│ │ ├── tag_statement.hpp
│ │ ├── text_literal_content.cpp
│ │ ├── text_literal_content.hpp
│ │ ├── unary_expr.cpp
│ │ ├── unary_expr.hpp
│ │ ├── value_list.cpp
│ │ ├── value_list.hpp
│ │ ├── variable.cpp
│ │ ├── variable.hpp
│ │ ├── variable_name.cpp
│ │ ├── variable_name.hpp
│ │ ├── with.cpp
│ │ └── with.hpp
│ ├── preprocessor.cpp
│ ├── preprocessor.hpp
│ ├── scopes.peg
│ ├── tokens.peg
│ └── variables.peg
├── test
│ ├── emerald_tests.cpp
│ ├── general_tests.cpp
│ ├── preprocessor_tests.cpp
│ ├── scope_tests.cpp
│ ├── templating_tests.cpp
│ ├── test_helper.cpp
│ └── test_helper.hpp
└── util
│ ├── .rubocop.yml
│ ├── file_helper.rb
│ ├── generate
│ └── generator.rb
├── emerald-logo.png
├── emerald.gemspec
├── lib
├── emerald.rb
└── emerald
│ ├── grammar.rb
│ ├── grammar
│ ├── emerald.tt
│ ├── scopes.tt
│ ├── tokens.tt
│ └── variables.tt
│ ├── index.emr
│ ├── nodes
│ ├── attribute_list.rb
│ ├── attributes.rb
│ ├── base_scope_fn.rb
│ ├── binary_expr.rb
│ ├── boolean_expr.rb
│ ├── comment.rb
│ ├── each.rb
│ ├── given.rb
│ ├── line.rb
│ ├── nested.rb
│ ├── node.rb
│ ├── pair_list.rb
│ ├── root.rb
│ ├── scope.rb
│ ├── scope_fn.rb
│ ├── tag_statement.rb
│ ├── text_literal.rb
│ ├── unary_expr.rb
│ ├── unless.rb
│ ├── value_list.rb
│ ├── variable.rb
│ ├── variable_name.rb
│ └── with.rb
│ ├── preprocessor.rb
│ └── version.rb
├── sample.emr
└── spec
├── emerald_spec.rb
├── functional
├── general_spec.rb
├── scope_spec.rb
└── templating_spec.rb
├── preprocessor
├── emerald
│ ├── events
│ │ ├── events.emr
│ │ └── form.emr
│ └── general
│ │ ├── attr.emr
│ │ ├── html.emr
│ │ ├── metas.emr
│ │ ├── nested.emr
│ │ ├── sample.emr
│ │ └── scopes.emr
├── intermediate
│ ├── events
│ │ ├── events.txt
│ │ └── form.txt
│ └── general
│ │ ├── attr.txt
│ │ ├── html.txt
│ │ ├── metas.txt
│ │ ├── nested.txt
│ │ ├── sample.txt
│ │ └── scopes.txt
└── preprocessor_spec.rb
├── spec_helper.rb
└── treetop
└── treetop_suite.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.bundle/
3 | /.yardoc
4 | /Gemfile.lock
5 | /_yardoc/
6 | /coverage/
7 | /doc/
8 | /pkg/
9 | /spec/reports/
10 | /tmp/
11 | bin/pre-commit
12 | tags
13 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | Metrics/LineLength:
2 | Max: 120
3 |
4 | Style/SpaceInsideHashLiteralBraces:
5 | EnforcedStyle: no_space
6 |
7 | Metrics/AbcSize:
8 | Enabled: false
9 |
10 | Metrics/MethodLength:
11 | Max: 30
12 |
13 | Style/DoubleNegation:
14 | Enabled: false
15 |
16 | Style/WordArray:
17 | Enabled: false
18 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.3.2
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in emerald.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2017 Andrew McBurney, Dave Pagurek, Yu Chen Hu, Google Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | One templating language, for any stack.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 
16 |
17 | # Usage
18 | ```
19 | gem install emerald-lang
20 | emerald process some_html_file --beautify
21 | ```
22 |
23 | # Contributing
24 | ## Setup
25 | ```
26 | bundle install
27 | bundle exec rake setup
28 | ```
29 |
30 | ## Running tests
31 | ```
32 | bundle exec rake test
33 | ```
34 | ## Pushing to Rubygems
35 | Update the `EMERALD_VERSION` constant, then:
36 |
37 | ```
38 | rake release
39 | ```
40 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rspec/core/rake_task'
3 | require 'pre-commit'
4 |
5 | RSpec::Core::RakeTask.new(:test)
6 |
7 | task :setup do
8 | sh 'pre-commit install'
9 | sh 'git config pre-commit.checks "[rubocop]"'
10 | end
11 |
12 | task :default => :test
13 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "emerald"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start
15 |
--------------------------------------------------------------------------------
/bin/emerald:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5 | require 'emerald'
6 |
7 | Emerald::CLI.start(ARGV)
8 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 |
5 | bundle install
6 |
7 | # Do any other automated setup that you need to do here
8 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | ruby:
3 | version:
4 | 2.3.2
5 |
6 | dependencies:
7 | pre:
8 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test && sudo apt-get update
9 | - sudo apt-get update; sudo apt-get install gcc-4.9; sudo apt-get install g++-4.9
10 | - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 100
11 | - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 100
12 | - sudo apt-get install libboost-all-dev
13 | override:
14 | - bundle install:
15 | timeout: 240
16 |
17 | test:
18 | override:
19 | - bundle exec rake test
20 | - cd cpp && make test
21 |
--------------------------------------------------------------------------------
/config/pre_commit.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :checks_add:
3 | - :rubocop
4 | :checks_remove:
5 | - :rails
6 |
--------------------------------------------------------------------------------
/cpp/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files
2 | *.slo
3 | *.lo
4 | *.o
5 | *.d
6 | *.obj
7 |
8 | # Precompiled Headers
9 | *.gch
10 | *.pch
11 |
12 | # Compiled Dynamic libraries
13 | *.so
14 | *.dylib
15 | *.dll
16 | *.dSYM
17 |
18 | # Fortran module files
19 | *.mod
20 | *.smod
21 |
22 | # Compiled Static libraries
23 | *.lai
24 | *.la
25 | *.a
26 | *.lib
27 |
28 | # Executables
29 | *.exe
30 | *.out
31 | *.app
32 |
33 | # personal notes
34 | todo.md
35 |
36 | # from g++
37 | emerald
38 | emerald_test
39 | emerald.DSYM
40 |
41 | # From pip
42 | dist/
43 | Emerald.egg-info/
44 |
--------------------------------------------------------------------------------
/cpp/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.4.1
2 |
--------------------------------------------------------------------------------
/cpp/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | ruby "2.4.1"
6 |
7 | gem "colorize"
8 | gem "rubocop"
9 | gem "thor"
10 |
--------------------------------------------------------------------------------
/cpp/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | ast (2.3.0)
5 | colorize (0.8.1)
6 | parallel (1.11.2)
7 | parser (2.4.0.0)
8 | ast (~> 2.2)
9 | powerpack (0.1.1)
10 | rainbow (2.2.2)
11 | rake
12 | rake (12.0.0)
13 | rubocop (0.49.1)
14 | parallel (~> 1.10)
15 | parser (>= 2.3.3.1, < 3.0)
16 | powerpack (~> 0.1)
17 | rainbow (>= 1.99.1, < 3.0)
18 | ruby-progressbar (~> 1.7)
19 | unicode-display_width (~> 1.0, >= 1.0.1)
20 | ruby-progressbar (1.8.1)
21 | thor (0.19.4)
22 | unicode-display_width (1.3.0)
23 |
24 | PLATFORMS
25 | ruby
26 |
27 | DEPENDENCIES
28 | colorize
29 | rubocop
30 | thor
31 |
32 | RUBY VERSION
33 | ruby 2.4.1p111
34 |
35 | BUNDLED WITH
36 | 1.15.1
37 |
--------------------------------------------------------------------------------
/cpp/Makefile:
--------------------------------------------------------------------------------
1 | CXX = g++ -std=c++11
2 | CXXFLAGS = -Wall -O -g -MMD
3 |
4 | # src/main.cpp
5 | CLI_OBJECT = src/main.o
6 | CLI_DEPEND = src/main.d
7 |
8 | # src/
9 | SOURCE = $(filter-out src/main.cpp, $(wildcard src/*.cpp)) $(wildcard src/nodes/*.cpp)
10 | OBJECTS = ${SOURCE:.cpp=.o}
11 | DEPENDS = ${OBJECTS:.o=.d}
12 |
13 | # test/
14 | TEST_SOURCE = $(wildcard test/*.cpp)
15 | TEST_OBJECTS = ${TEST_SOURCE:.cpp=.o}
16 | TEST_DEPENDS = ${TEST_OBJECTS:.o=.d}
17 |
18 | EXEC = emerald
19 | TEST_EXEC = emerald_test
20 | LIBS = -lboost_regex
21 |
22 | ${EXEC} : ${CLI_OBJECT} ${OBJECTS}
23 | ${CXX} ${CXXFLAGS} ${CLI_OBJECT} ${OBJECTS} -o ${EXEC} ${LIBS}
24 |
25 | ${TEST_EXEC}: ${OBJECTS} ${TEST_OBJECTS}
26 | ${CXX} ${CXXFLAGS} ${OBJECTS} ${TEST_OBJECTS} -o ${TEST_EXEC} ${LIBS}
27 |
28 | test: ${TEST_EXEC}
29 | ./${TEST_EXEC}
30 |
31 | clean:
32 | rm ${CLI_DEPEND} ${DEPENDS} ${TEST_DEPENDS} ${CLI_OBJECT} ${OBJECTS} ${TEST_OBJECTS} $(EXEC) ${TEST_EXEC}
33 |
34 | -include {DEPENDS} {TEST_DEPENDS}
35 |
--------------------------------------------------------------------------------
/cpp/lib/utf8.h:
--------------------------------------------------------------------------------
1 | /* THIS FILE IS PUBLIC DOMAIN. NO WARRANTY OF ANY KIND. NO RIGHTS RESERVED. */
2 | #ifndef UTF8_H_HEADERFILE
3 | #define UTF8_H_HEADERFILE
4 |
5 | #ifndef UTF32_INT_TYPE
6 | #define UTF32_INT_TYPE unsigned long
7 | #endif
8 |
9 | #ifndef true
10 | #define true 1
11 | #endif
12 | #ifndef false
13 | #define false 0
14 | #endif
15 | #ifndef bool
16 | #define bool int
17 | #endif
18 |
19 | static inline bool
20 | utf32_is_surrogate(UTF32_INT_TYPE cp)
21 | {
22 | return cp > 0xd7ff && cp < 0xe000;
23 | }
24 |
25 | static inline bool
26 | utf32_is_non_character(UTF32_INT_TYPE cp)
27 | {
28 | return ((0xfffeU == (0xffeffffeU & cp) || (cp >= 0xfdd0U && cp <= 0xfdefU)));
29 | }
30 |
31 | static inline bool
32 | utf32_is_valid(UTF32_INT_TYPE cp)
33 | {
34 | return cp < 0x10ffffU && !utf32_is_non_character(cp);
35 | }
36 |
37 | static inline unsigned
38 | utf8_encoded_len(UTF32_INT_TYPE cp)
39 | {
40 | if (cp < 0x80U) {
41 | return 1;
42 | } else if (cp < 0x800U) {
43 | return 2;
44 | } else if (!utf32_is_valid(cp) || utf32_is_surrogate(cp)) {
45 | return 0;
46 | } else if (cp < 0x10000U) {
47 | return 3;
48 | } else {
49 | return 4;
50 | }
51 | }
52 |
53 | static inline unsigned
54 | utf8_first_byte_length_hint(unsigned char ch)
55 | {
56 | switch (ch & ~0x0fU) {
57 | case 0x00:
58 | case 0x10:
59 | case 0x20:
60 | case 0x30:
61 | case 0x40:
62 | case 0x50:
63 | case 0x60:
64 | case 0x70: return 1;
65 | case 0xc0: return ch >= 0xc2 ? 2 : 0;
66 | case 0xd0: return 2;
67 | case 0xe0: return 3;
68 | case 0xf0: return ch <= 0xf4 ? 4 : 0;
69 | default: return 0;
70 | }
71 | }
72 |
73 | static inline bool
74 | utf8_first_byte_valid(unsigned char ch)
75 | {
76 | return 0 != utf8_first_byte_length_hint(ch);
77 | }
78 |
79 | static inline bool
80 | utf8_first_bytes_valid(unsigned char ch1, unsigned char ch2)
81 | {
82 | if (ch1 < 0x80) {
83 | return true;
84 | } else if (0x80 == (ch2 & 0xc0)) {
85 | /* 0x80..0xbf */
86 | switch (ch1) {
87 | case 0xe0: return ch2 >= 0xa0;
88 | case 0xed: return ch2 <= 0x9f;
89 | case 0xf0: return ch2 >= 0x90;
90 | case 0xf4: return ch2 <= 0x8f;
91 | }
92 | return true;
93 | }
94 | return false;
95 | }
96 |
97 | /**
98 | * @return (UTF32_INT_TYPE-1 on failure. On success the decoded
99 | * Unicode codepoint is returned.
100 | */
101 | static inline UTF32_INT_TYPE
102 | utf8_decode(const char *src, size_t size)
103 | {
104 | UTF32_INT_TYPE cp;
105 | unsigned n;
106 |
107 | if (0 == size)
108 | goto failure;
109 |
110 | cp = (unsigned char) *src;
111 | n = utf8_first_byte_length_hint(cp);
112 | if (1 != n) {
113 | unsigned char x;
114 |
115 | if (0 == n || n > size)
116 | goto failure;
117 |
118 | x = *++src;
119 | if (!utf8_first_bytes_valid(cp, x))
120 | goto failure;
121 |
122 | n--;
123 | cp &= 0x3f >> n;
124 |
125 | for (;;) {
126 | cp = (cp << 6) | (x & 0x3f);
127 | if (--n == 0)
128 | break;
129 | x = *++src;
130 | if (0x80 != (x & 0xc0))
131 | goto failure;
132 | }
133 | if (utf32_is_non_character(cp))
134 | goto failure;
135 | }
136 | return cp;
137 |
138 | failure:
139 | return (UTF32_INT_TYPE) -1;
140 | }
141 |
142 | static inline unsigned
143 | utf8_encode(UTF32_INT_TYPE cp, char *buf)
144 | {
145 | unsigned n = utf8_encoded_len(cp);
146 |
147 | if (n > 0) {
148 | static const unsigned char first_byte[] = {
149 | 0xff, 0x00, 0xc0, 0xe0, 0xf0
150 | };
151 | unsigned i = n;
152 |
153 | while (--i > 0) {
154 | buf[i] = (cp & 0x3f) | 0x80;
155 | cp >>= 6;
156 | }
157 | buf[0] = cp | first_byte[n];
158 | }
159 | return n;
160 | }
161 |
162 | #endif /* UTF8_H_HEADERFILE */
163 | /* vi: set ai et ts=2 sts=2 sw=2 cindent: */
164 |
--------------------------------------------------------------------------------
/cpp/requirements.txt:
--------------------------------------------------------------------------------
1 | cpplint
2 |
--------------------------------------------------------------------------------
/cpp/samples/sample.emr:
--------------------------------------------------------------------------------
1 | * Emerald Language
2 |
3 | doctype html
4 |
5 | html
6 | head
7 | styles
8 | "css/main.css"
9 | "css/vendor/bootstrap.min.css"
10 |
11 | scripts
12 | "js/script.js"
13 | "js/other_script.js"
14 |
15 | style
16 | var black = #333
17 | var blue = #0066ff
18 |
19 | body
20 | header
21 | h1 Emerald
22 | h2 An html5 markup language designed with event driven
23 | applications in mind.
24 |
25 | main
26 | section
27 | h1 Why use Emerald?
28 | p Emerald allows you to scope events and styles to html
29 | elements in an elegant, clean way.
30 |
31 | figure
32 | figcaption Here's an example of elements scoped in a
33 | button here.
34 |
35 | button Click me. (
36 | click -> console.log("I was clicked!")
37 | hover -> console.log("I was hovered!")
38 | )
39 |
40 | footer (
41 | hover ->
42 | this.border = 1px solid @blue
43 | this.text-shadow = 0px 0px 8px 2px rgba(0,0,0,0.3)
44 | )
45 | p Like what you see? Check out the docs for more samples.
46 |
--------------------------------------------------------------------------------
/cpp/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | Emerald, the language agnostic templating engine.
3 | Copyright 2016-2017, Emerald Language (MIT)
4 | """
5 |
6 | from setuptools import setup
7 |
8 | setup(
9 | name='Emerald',
10 |
11 | version='1.0.0',
12 |
13 | description='Emerald, the language agnostic templating engine.',
14 |
15 | url='https://github.com/emerald-lang/emerald',
16 |
17 | author='Andrew Robert McBurney, Dave Pagurek van Mossel, Yu Chen Hou',
18 |
19 | author_email='andrewrobertmcburney@gmail.com, davepagurek@gmail.com, me@yuchenhou.com',
20 |
21 | license='MIT',
22 |
23 | classifiers=[
24 | 'Development Status :: 3 - Alpha',
25 | 'Intended Audience :: Developers',
26 | 'Topic :: Software Development :: Template Engines',
27 | 'License :: OSI Approved :: MIT License',
28 | 'Programming Language :: Python :: 2'
29 | ],
30 |
31 | keywords='sample setuptools development',
32 |
33 | extras_require={
34 | 'test': ['cpplint']
35 | }
36 | )
37 |
--------------------------------------------------------------------------------
/cpp/src/grammar.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "grammar.hpp"
6 |
7 | // [START] Include nodes
8 | #include "nodes/node.hpp"
9 | #include "nodes/node_list.hpp"
10 | #include "nodes/boolean.hpp"
11 | #include "nodes/scope_fn.hpp"
12 | #include "nodes/scope.hpp"
13 | #include "nodes/line.hpp"
14 | #include "nodes/value_list.hpp"
15 | #include "nodes/literal_new_line.hpp"
16 | #include "nodes/attribute.hpp"
17 | #include "nodes/attributes.hpp"
18 | #include "nodes/tag_statement.hpp"
19 | #include "nodes/text_literal_content.hpp"
20 | #include "nodes/escaped.hpp"
21 | #include "nodes/comment.hpp"
22 | #include "nodes/scoped_key_value_pairs.hpp"
23 | #include "nodes/key_value_pair.hpp"
24 | #include "nodes/unary_expr.hpp"
25 | #include "nodes/binary_expr.hpp"
26 | #include "nodes/each.hpp"
27 | #include "nodes/with.hpp"
28 | #include "nodes/conditional.hpp"
29 | #include "nodes/variable_name.hpp"
30 | #include "nodes/variable.hpp"
31 | #include "nodes/scope.hpp"
32 | // [END] Include nodes
33 |
34 | namespace {
35 | // Helper function to turn a maybe rule (one element, made optional with a ?) into its value or a default
36 | template
37 | std::function optional(T default_value) {
38 | return [=](const peg::SemanticValues &sv) -> T {
39 | if (sv.size() > 0) {
40 | return sv[0].get();
41 | } else {
42 | return default_value;
43 | }
44 | };
45 | }
46 |
47 | // Helper to turn plural rules (one element, repeated with + or *) into a vector of a given type
48 | template
49 | std::function(const peg::SemanticValues&)> repeated() {
50 | return [](const peg::SemanticValues& sv) -> std::vector {
51 | std::vector contents;
52 |
53 | for (unsigned int n = 0; n < sv.size(); n++) {
54 | contents.push_back(sv[n].get());
55 | }
56 |
57 | return contents;
58 | };
59 | }
60 | }
61 |
62 | Grammar::Grammar() : emerald_parser(syntax) {
63 | emerald_parser["ROOT"] =
64 | [](const peg::SemanticValues& sv) -> NodePtr {
65 | NodePtrs nodes = sv[0].get();
66 | return NodePtr(new NodeList(nodes, "\n"));
67 | };
68 |
69 | emerald_parser["line"] =
70 | [](const peg::SemanticValues& sv) -> NodePtr {
71 | NodePtr line = sv[0].get();
72 |
73 | return NodePtr(new Line(line));
74 | };
75 |
76 | emerald_parser["value_list"] =
77 | [](const peg::SemanticValues& sv) -> NodePtr {
78 | NodePtr keyword = sv[0].get();
79 | NodePtrs literals = sv[1].get();
80 |
81 | return NodePtr(new ValueList(keyword, literals));
82 | };
83 |
84 | emerald_parser["literal_new_line"] =
85 | [](const peg::SemanticValues& sv) -> NodePtr {
86 | NodePtr inline_lit_str = sv[0].get();
87 |
88 | return NodePtr(new LiteralNewLine(inline_lit_str));
89 | };
90 |
91 | emerald_parser["pair_list"] =
92 | [](const peg::SemanticValues& sv) -> NodePtr {
93 | NodePtrs nodes;
94 | std::string base_keyword = sv[0].get();
95 | std::vector semantic_values = sv[1].get>();
96 |
97 | std::transform(semantic_values.begin(), semantic_values.end(), nodes.begin(),
98 | [=](const peg::SemanticValues& svs) {
99 | NodePtrs pairs = svs[0].get();
100 | return NodePtr(new ScopedKeyValuePairs(base_keyword, pairs));
101 | });
102 |
103 | return NodePtr(new NodeList(nodes, "\n"));
104 | };
105 |
106 | emerald_parser["scoped_key_value_pairs"] = repeated();
107 |
108 | emerald_parser["scoped_key_value_pair"] =
109 | [](const peg::SemanticValues& sv) -> const peg::SemanticValues {
110 | return sv;
111 | };
112 |
113 | emerald_parser["key_value_pair"] =
114 | [](const peg::SemanticValues& sv) -> NodePtr {
115 | std::string key = sv[0].get();
116 | NodePtr value = sv[1].get();
117 |
118 | return NodePtr(new KeyValuePair(key, value));
119 | };
120 |
121 | emerald_parser["comment"] =
122 | [](const peg::SemanticValues& sv) -> NodePtr {
123 | NodePtr text_content = sv[0].get();
124 |
125 | return NodePtr(new Comment(text_content));
126 | };
127 |
128 | emerald_parser["maybe_id_name"] = optional("");
129 |
130 | emerald_parser["class_names"] = repeated();
131 |
132 | emerald_parser["tag_statement"] =
133 | [](const peg::SemanticValues& sv) -> NodePtr {
134 | std::string tag_name = sv[0].get();
135 | std::string id_name = sv[1].get();
136 | std::vector class_names = sv[2].get>();
137 | NodePtr body = sv[3].get();
138 | NodePtr attributes = sv[4].get();
139 | NodePtr nested = sv[5].get();
140 |
141 | return NodePtr(
142 | new TagStatement(tag_name, id_name, class_names, body, attributes, nested));
143 | };
144 |
145 | emerald_parser["attr_list"] =
146 | [](const peg::SemanticValues& sv) -> NodePtr {
147 | NodePtrs nodes = sv[0].get();
148 |
149 | return NodePtr(new Attributes(nodes));
150 | };
151 |
152 | emerald_parser["attribute"] =
153 | [](const peg::SemanticValues& sv) -> NodePtr {
154 | std::string key = sv[0].get();
155 | NodePtr value = sv[1].get();
156 | return NodePtr(new Attribute(key, value));
157 | };
158 |
159 | emerald_parser["escaped"] =
160 | [](const peg::SemanticValues& sv) -> NodePtr {
161 | return NodePtr(new Escaped(sv.str()));
162 | };
163 |
164 | emerald_parser["maybe_negation"] = optional(false);
165 |
166 | emerald_parser["negation"] =
167 | [](const peg::SemanticValues& sv) -> bool {
168 | return true;
169 | };
170 |
171 | emerald_parser["unary_expr"] =
172 | [](const peg::SemanticValues& sv) -> BooleanPtr {
173 | bool negated = sv[0].get();
174 | BooleanPtr expr = sv[1].get();
175 |
176 | return BooleanPtr(new UnaryExpr(negated, expr));
177 | };
178 |
179 | emerald_parser["binary_expr"] =
180 | [](const peg::SemanticValues& sv) -> BooleanPtr {
181 | BooleanPtr lhs = sv[0].get();
182 | std::string op_str = sv[1].get().str();
183 | BooleanPtr rhs = sv[2].get();
184 | BinaryExpr::Operator op;
185 | if (op_str == BinaryExpr::OR_STR) {
186 | op = BinaryExpr::Operator::OR;
187 | } else if (op_str == BinaryExpr::AND_STR) {
188 | op = BinaryExpr::Operator::AND;
189 | } else {
190 | throw "Invalid operator: " + op_str;
191 | }
192 |
193 | return BooleanPtr(new BinaryExpr(lhs, op, rhs));
194 | };
195 |
196 | emerald_parser["boolean_expr"] =
197 | [](const peg::SemanticValues& sv) -> BooleanPtr {
198 | return sv[0].get();
199 | };
200 |
201 | emerald_parser["maybe_key_name"] = optional("");
202 |
203 | emerald_parser["each"] =
204 | [](const peg::SemanticValues& sv) -> ScopeFnPtr {
205 | std::string collection_name = sv[0].get();
206 | std::string val_name = sv[1].get();
207 | std::string key_name = sv[2].get();
208 |
209 | return ScopeFnPtr(new Each(collection_name, val_name, key_name));
210 | };
211 |
212 | emerald_parser["with"] =
213 | [](const peg::SemanticValues& sv) -> ScopeFnPtr {
214 | std::string var_name = sv[0].get();
215 |
216 | return ScopeFnPtr(new With(var_name));
217 | };
218 |
219 | emerald_parser["given"] =
220 | [](const peg::SemanticValues& sv) -> ScopeFnPtr {
221 | BooleanPtr condition = sv[0].get();
222 |
223 | return ScopeFnPtr(new Conditional(true, condition));
224 | };
225 |
226 | emerald_parser["unless"] =
227 | [](const peg::SemanticValues& sv) -> ScopeFnPtr {
228 | BooleanPtr condition = sv[0].get();
229 |
230 | return ScopeFnPtr(new Conditional(false, condition));
231 | };
232 |
233 | emerald_parser["scope_fn"] =
234 | [](const peg::SemanticValues& sv) -> ScopeFnPtr {
235 | return sv[0].get();
236 | };
237 |
238 | emerald_parser["variable_name"] =
239 | [](const peg::SemanticValues& sv) -> BooleanPtr {
240 | std::string name = sv.str();
241 |
242 | return BooleanPtr(new VariableName(name));
243 | };
244 |
245 | emerald_parser["variable"] =
246 | [](const peg::SemanticValues& sv) -> NodePtr {
247 | std::string name = sv.token(0);
248 |
249 | return NodePtr(new Variable(name));
250 | };
251 |
252 | emerald_parser["scope"] =
253 | [](const peg::SemanticValues& sv) -> NodePtr {
254 | ScopeFnPtr scope_fn = sv[0].get();
255 | NodePtr body = sv[1].get();
256 |
257 | return NodePtr(new Scope(scope_fn, body));
258 | };
259 |
260 | // Repeated Nodes
261 | const std::vector repeated_nodes = {
262 | "statements", "literal_new_lines", "key_value_pairs", "ml_lit_str_quoteds",
263 | "ml_templess_lit_str_qs", "inline_literals", "il_lit_str_quoteds", "attributes"
264 | };
265 | for (std::string repeated_node : repeated_nodes) {
266 | emerald_parser[repeated_node.c_str()] = repeated();
267 | }
268 |
269 | // Wrapper Nodes
270 | const std::vector wrapper_nodes = {
271 | "statement", "text_content", "ml_lit_str_quoted", "ml_templess_lit_str_q",
272 | "inline_literal", "il_lit_str_quoted", "nested_tag"
273 | };
274 | for (std::string wrapper_node : wrapper_nodes) {
275 | emerald_parser[wrapper_node.c_str()] =
276 | [](const peg::SemanticValues& sv) -> NodePtr {
277 | return sv[0].get();
278 | };
279 | }
280 |
281 | // Literals
282 | const std::vector literals = {
283 | "multiline_literal", "inline_lit_str", "inline_literals_node"
284 | };
285 | for (std::string string_rule : literals) {
286 | emerald_parser[string_rule.c_str()] =
287 | [](const peg::SemanticValues& sv) -> NodePtr {
288 | NodePtrs body = sv[0].get();
289 |
290 | return NodePtr(new NodeList(body, ""));
291 | };
292 | }
293 |
294 | // Literal Contents
295 | const std::vector literal_contents = {
296 | "ml_lit_content", "il_lit_content", "il_lit_str_content"
297 | };
298 | for (std::string string_rule_content : literal_contents) {
299 | emerald_parser[string_rule_content.c_str()] =
300 | [](const peg::SemanticValues& sv) -> NodePtr {
301 | return NodePtr(new TextLiteralContent(sv.str()));
302 | };
303 | }
304 |
305 | const std::vector optional_nodes = {
306 | "maybe_text_content", "maybe_nested_tag", "maybe_attr_list"
307 | };
308 | for (std::string rule_name : optional_nodes) {
309 | emerald_parser[rule_name.c_str()] = optional(NodePtr());
310 | }
311 |
312 | // Terminals
313 | const std::vector terminals = {
314 | "attr", "tag", "class_name", "id_name", "key_name"
315 | };
316 | for (std::string rule_name : terminals) {
317 | emerald_parser[rule_name.c_str()] =
318 | [](const peg::SemanticValues& sv) -> std::string {
319 | return sv.str();
320 | };
321 | }
322 |
323 | emerald_parser.enable_packrat_parsing();
324 | }
325 |
326 | Grammar& Grammar::get_instance() {
327 | static Grammar instance;
328 | return instance;
329 | }
330 |
331 | peg::parser Grammar::get_parser() {
332 | return emerald_parser;
333 | }
334 |
335 | bool Grammar::valid(const std::string &input) {
336 | std::string output;
337 | emerald_parser.parse(input.c_str(), output);
338 | return output.length() == input.length();
339 | }
340 |
--------------------------------------------------------------------------------
/cpp/src/grammar.hpp:
--------------------------------------------------------------------------------
1 | #ifndef GRAMMAR_H
2 | #define GRAMMAR_H
3 |
4 | #include
5 |
6 | #include "../lib/peglib.h"
7 |
8 | /**
9 | * Singleton class for transforming Emerald code into intermediate
10 | * representation to be parsed by the PEG grammar
11 | */
12 | class Grammar {
13 |
14 | public:
15 | Grammar(Grammar const&) = delete; // Copy constructor
16 | Grammar& operator=(Grammar const&) = delete; // Copy assignment
17 | Grammar(Grammar&&) = delete; // Move constructor
18 | Grammar& operator=(Grammar&&) = delete; // Move assignment
19 |
20 | static Grammar& get_instance();
21 | peg::parser get_parser();
22 | bool valid(const std::string &input);
23 |
24 | protected:
25 | Grammar();
26 |
27 | private:
28 | std::string get_whitespace(int);
29 |
30 | // PEG parser
31 | peg::parser emerald_parser;
32 |
33 | // Grammar rules
34 | static constexpr const auto syntax =
35 | #include "grammar.peg"
36 | "\n"
37 | #include "tokens.peg"
38 | "\n"
39 | #include "scopes.peg"
40 | "\n"
41 | #include "variables.peg"
42 | "\n"
43 | R"(%whitespace <- [ \t]*)"
44 | ;
45 |
46 | };
47 |
48 | #endif // GRAMMAR_H
49 |
--------------------------------------------------------------------------------
/cpp/src/grammar.peg:
--------------------------------------------------------------------------------
1 | R"(
2 | ROOT <- statements
3 |
4 | statements <- (scope / pair_list / value_list / line / comment)+
5 |
6 | scope <- scope_fn ~lbrace ~nl ROOT ~rbrace ~nl
7 |
8 | line <- (tag_statement / comment) ~nl
9 |
10 | value_list <- special_keyword ~nl ~lbrace ~nl literal_new_lines ~rbrace ~nl
11 |
12 | literal_new_lines <- literal_newline+
13 |
14 | literal_new_line <- inline_lit_str ~nl
15 |
16 | pair_list <- base_keyword ~nl ~lbrace ~nl scoped_key_value_pairs ~rbrace ~nl
17 |
18 | scoped_key_value_pairs <- scoped_key_value_pair+
19 |
20 | scoped_key_value_pair <- key_value_pairs ~nl
21 |
22 | key_value_pairs <- key_value_pair+
23 |
24 | key_value_pair <- attr ~space+ inline_lit_str space*
25 |
26 | comment <- ~space* ~'*' ~space* text_content
27 |
28 | maybe_text_content <- text_content?
29 |
30 | text_content <- multiline_literal / ml_templess_lit / inline_literals_node
31 |
32 | multiline_literal <- ~'->' ~space* ~nl ml_lit_str_quoteds ~'$'
33 |
34 | ml_templess_lit <- ~('=>' / '~>') ~space* ~nl ml_templess_lit_str_qs ~'$'
35 |
36 | ml_lit_str_quoteds <- ml_lit_str_quoted*
37 |
38 | ml_lit_str_quoted <- variable / escaped / ml_lit_content
39 |
40 | # Jesus forgive me.
41 | ml_templess_lit_str_qs <- ml_templess_lit_str_q*
42 |
43 | ml_templess_lit_str_q <- escaped / ml_lit_content
44 |
45 | ################################
46 | # Inline literals strings
47 | ################################
48 |
49 | inline_literals_node <- inline_literals
50 |
51 | inline_literals <- inline_literal*
52 |
53 | inline_literal <- variable / escaped / il_lit_content
54 |
55 | inline_lit_str <- ~'"' il_lit_str_quoteds ~'"'
56 |
57 | il_lit_str_quoteds <- il_lit_str_quoted*
58 |
59 | il_lit_str_quoted <- variable / escaped / il_lit_content
60 |
61 | ml_lit_content <- !'$' .
62 |
63 | il_lit_content <- !lparen !nl .
64 |
65 | il_lit_str_content <- !'"' .
66 |
67 | escaped <- '\\' .
68 |
69 | tag_statement <- tag maybe_id_name class_names ~space* maybe_text_content maybe_attr_list maybe_nested_tag
70 |
71 | maybe_nested_tag <- nested_tag?
72 |
73 | nested_tag <- ~nl ~lbrace ~nl ROOT ~rbrace ~nl
74 |
75 | maybe_id_name <- id_name?
76 |
77 | id_name <- '#' $name< ([a-zA-Z_] / '-')+ >
78 |
79 | class_names <- class_name*
80 |
81 | class_name <- '.' $name< ([a-zA-Z_] / '-')+ >
82 |
83 | maybe_attr_list <- attr_list?
84 |
85 | attr_list <- ~lparen ~nl ~lbrace ~nl attributes ~rbrace ~nl ~rparen
86 |
87 | attributes <- attribute*
88 |
89 | attribute <- attr ~space* inline_lit_str ~nl
90 | )"
91 |
--------------------------------------------------------------------------------
/cpp/src/htmlencode.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include