opulent 0.0.9 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.libold/opulent.rb +96 -0
- data/.libold/opulent/context.rb +80 -0
- data/.libold/opulent/engine.rb +88 -0
- data/.libold/opulent/filter.rb +101 -0
- data/.libold/opulent/logger.rb +67 -0
- data/.libold/opulent/nodes.rb +13 -0
- data/.libold/opulent/nodes/block.rb +29 -0
- data/.libold/opulent/nodes/comment.rb +35 -0
- data/.libold/opulent/nodes/control.rb +230 -0
- data/.libold/opulent/nodes/define.rb +42 -0
- data/.libold/opulent/nodes/eval.rb +41 -0
- data/.libold/opulent/nodes/expression.rb +28 -0
- data/.libold/opulent/nodes/filter.rb +70 -0
- data/.libold/opulent/nodes/helper.rb +69 -0
- data/.libold/opulent/nodes/node.rb +101 -0
- data/.libold/opulent/nodes/root.rb +62 -0
- data/.libold/opulent/nodes/text.rb +54 -0
- data/.libold/opulent/nodes/theme.rb +36 -0
- data/.libold/opulent/parser.rb +252 -0
- data/.libold/opulent/parser/block.rb +70 -0
- data/.libold/opulent/parser/comment.rb +32 -0
- data/.libold/opulent/parser/control.rb +83 -0
- data/.libold/opulent/parser/define.rb +39 -0
- data/.libold/opulent/parser/eval.rb +33 -0
- data/.libold/opulent/parser/expression.rb +350 -0
- data/.libold/opulent/parser/filter.rb +41 -0
- data/.libold/opulent/parser/node.rb +232 -0
- data/.libold/opulent/parser/root.rb +96 -0
- data/.libold/opulent/parser/text.rb +114 -0
- data/.libold/opulent/parser/theme.rb +36 -0
- data/.libold/opulent/preprocessor.rb +102 -0
- data/.libold/opulent/runtime.rb +144 -0
- data/.libold/opulent/template.rb +43 -0
- data/.libold/opulent/tokens.rb +276 -0
- data/.libold/opulent/version.rb +5 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +102 -0
- data/Rakefile +6 -0
- data/benchmark/benchmark.rb +46 -0
- data/benchmark/cases/node/node.haml +17 -0
- data/benchmark/cases/node/node.op +14 -0
- data/benchmark/cases/node/node.slim +19 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/docs/syntax.md +3 -0
- data/docs/usage.md +3 -0
- data/lib/opulent.rb +11 -64
- data/lib/opulent/compiler.rb +132 -0
- data/lib/opulent/compiler/block.rb +31 -0
- data/lib/opulent/compiler/comment.rb +22 -0
- data/lib/opulent/compiler/control.rb +152 -0
- data/lib/opulent/compiler/define.rb +88 -0
- data/lib/opulent/compiler/eval.rb +15 -0
- data/lib/opulent/compiler/filter.rb +54 -0
- data/lib/opulent/compiler/node.rb +232 -0
- data/lib/opulent/compiler/root.rb +19 -0
- data/lib/opulent/compiler/text.rb +60 -0
- data/lib/opulent/context.rb +88 -0
- data/lib/opulent/engine.rb +60 -0
- data/lib/opulent/filters.rb +222 -0
- data/lib/opulent/logger.rb +47 -0
- data/lib/opulent/parser.rb +196 -0
- data/lib/opulent/parser/block.rb +56 -0
- data/lib/opulent/parser/comment.rb +27 -0
- data/lib/opulent/parser/control.rb +112 -0
- data/lib/opulent/parser/define.rb +30 -0
- data/lib/opulent/parser/eval.rb +21 -0
- data/lib/opulent/parser/expression.rb +344 -0
- data/lib/opulent/parser/filter.rb +25 -0
- data/lib/opulent/parser/node.rb +246 -0
- data/lib/opulent/parser/root.rb +48 -0
- data/lib/opulent/parser/text.rb +127 -0
- data/lib/opulent/settings.rb +79 -0
- data/lib/opulent/template.rb +67 -0
- data/lib/opulent/tokens.rb +166 -0
- data/lib/opulent/version.rb +4 -0
- data/opulent.gemspec +36 -0
- metadata +160 -7
@@ -0,0 +1,69 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Nodes
|
4
|
+
module Nodes
|
5
|
+
# @NodeFactory
|
6
|
+
class Helper
|
7
|
+
def root
|
8
|
+
Root.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(name = '', atts = {}, parent = nil, indent = 0, children = [])
|
12
|
+
Node.new name, atts, parent, indent, children
|
13
|
+
end
|
14
|
+
|
15
|
+
def block_yield(name = '', parent = nil, indent = 0, children = [])
|
16
|
+
Yield.new name, {}, parent, indent, children
|
17
|
+
end
|
18
|
+
|
19
|
+
def block(name = '', parent = nil, indent = 0, children = [])
|
20
|
+
Block.new name, {}, parent, indent, children
|
21
|
+
end
|
22
|
+
|
23
|
+
def theme(name = '', parent = nil, indent = 0, children = [])
|
24
|
+
Theme.new name, parent, indent, children
|
25
|
+
end
|
26
|
+
|
27
|
+
def filter(name = '', atts = {}, parent = nil, indent = 0, value = '')
|
28
|
+
Filter.new name, atts, parent, indent, value
|
29
|
+
end
|
30
|
+
|
31
|
+
def evaluate(value, parent = nil, indent = 0, children = [])
|
32
|
+
Evaluate.new value, parent, indent, children
|
33
|
+
end
|
34
|
+
|
35
|
+
def control(name, value = '', parent = nil, indent = 0, children = [[]])
|
36
|
+
case name
|
37
|
+
when :if, :unless
|
38
|
+
CnditionalControl.new name, value, parent, indent, children
|
39
|
+
when :case
|
40
|
+
CaseControl.new name, value, parent, indent, children.first
|
41
|
+
when :while, :until
|
42
|
+
LoopControl.new name, value, parent, indent, children.first
|
43
|
+
when :each
|
44
|
+
EachControl.new name, value, parent, indent, children.first
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def expression(value = [])
|
49
|
+
Expression.new value
|
50
|
+
end
|
51
|
+
|
52
|
+
def definition(name, params = [], parent = nil, indent = 0)
|
53
|
+
Define.new name, params, parent, indent
|
54
|
+
end
|
55
|
+
|
56
|
+
def text(value = nil, escaped = true, parent = nil, indent = 0)
|
57
|
+
Text.new value, escaped, parent, indent
|
58
|
+
end
|
59
|
+
|
60
|
+
def print(value = nil, escaped = true, parent = nil, indent = 0)
|
61
|
+
Print.new value, escaped, parent, indent
|
62
|
+
end
|
63
|
+
|
64
|
+
def comment(value = nil, parent = nil, indent = 0)
|
65
|
+
Comment.new value, parent, indent
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Nodes
|
4
|
+
module Nodes
|
5
|
+
# @Node
|
6
|
+
#
|
7
|
+
# Node class used to describe a HTML Element used for building a
|
8
|
+
# page model during the parsing process
|
9
|
+
#
|
10
|
+
class Node
|
11
|
+
# Allow direct access to node variables
|
12
|
+
attr_accessor :name, :attributes, :children, :whitespace, :parent, :indent, :extension, :theme, :blocks, :yields, :self_enclosing
|
13
|
+
|
14
|
+
# Initialize node instance variables
|
15
|
+
#
|
16
|
+
# @param name [String] name of the html node
|
17
|
+
# @param indentation [Fixnum] node indentation for restructuring
|
18
|
+
# @param attributes [Hash] stores key="value" attributes
|
19
|
+
# @param children [Array] collection of the node's child elements
|
20
|
+
#
|
21
|
+
def initialize(name = '', attributes = {}, parent = nil, indent = 0, children = [])
|
22
|
+
@name = name
|
23
|
+
@parent = parent
|
24
|
+
@indent = indent
|
25
|
+
@attributes = attributes
|
26
|
+
@children = children
|
27
|
+
@extension = nil
|
28
|
+
@theme = Engine::DEFAULT_THEME
|
29
|
+
@self_enclosing = Engine::SELF_ENCLOSING.include? name
|
30
|
+
@whitespace = [nil, nil]
|
31
|
+
@blocks = {
|
32
|
+
Engine::DEFAULT_YIELD => @children
|
33
|
+
}
|
34
|
+
@yields = []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a new node to the nodes array
|
38
|
+
#
|
39
|
+
def push(node)
|
40
|
+
@children << node
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return extended attributes if an extension was set using the [extend]
|
45
|
+
# identifier of the node
|
46
|
+
#
|
47
|
+
def extend_attributes(attributes, extension)
|
48
|
+
return attributes if extension.nil?
|
49
|
+
|
50
|
+
extension.each do |key, value|
|
51
|
+
case attributes[key]
|
52
|
+
when Array
|
53
|
+
attributes[key] = (attributes[key] << value).flatten
|
54
|
+
when Hash
|
55
|
+
attributes[key] = value.merge attributes[key]
|
56
|
+
when nil
|
57
|
+
attributes[key] = value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
attributes
|
62
|
+
end
|
63
|
+
|
64
|
+
# Node evaluation method which goes through all the child nodes and evaluates
|
65
|
+
# them using their own eval method
|
66
|
+
#
|
67
|
+
def evaluate(context)
|
68
|
+
# Set attributes for current context
|
69
|
+
attributes = Runtime.attributes @attributes, @extension, context
|
70
|
+
|
71
|
+
# Evaluate all provided blocks
|
72
|
+
blocks = Hash[@blocks.map{ |key, value|
|
73
|
+
children = value.map do |child|
|
74
|
+
child.evaluate context
|
75
|
+
end.flatten.compact
|
76
|
+
|
77
|
+
[key, children]
|
78
|
+
}]
|
79
|
+
|
80
|
+
# Map self to a different node and set evaluated children nodes and
|
81
|
+
# evaluated attributes and add a pointer to the children block
|
82
|
+
mapped_node = self.dup
|
83
|
+
mapped_node.attributes = attributes
|
84
|
+
mapped_node.blocks = blocks
|
85
|
+
mapped_node.children = mapped_node.blocks[Engine::DEFAULT_YIELD]
|
86
|
+
|
87
|
+
# Replace node with its definition if it has one
|
88
|
+
if Runtime[@theme]
|
89
|
+
if Runtime[@theme][@name] && @name != context.name
|
90
|
+
return Runtime.define mapped_node, attributes, context
|
91
|
+
else
|
92
|
+
return mapped_node
|
93
|
+
end
|
94
|
+
else
|
95
|
+
# Theme does not exist
|
96
|
+
Runtime.error :theme, @theme
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Nodes
|
4
|
+
module Nodes
|
5
|
+
# @Root
|
6
|
+
#
|
7
|
+
# HTML Root from which the construction of the final page starts. The root
|
8
|
+
# stores all the node definitions that will be replaced in the components.
|
9
|
+
#
|
10
|
+
class Root
|
11
|
+
# Allow direct access to node variables
|
12
|
+
attr_accessor :themes, :children, :indent, :blocks, :yields
|
13
|
+
|
14
|
+
# Initialize node instance variables
|
15
|
+
#
|
16
|
+
# @param definitions [Hash] node definitions to be replaced in children
|
17
|
+
# @param children [Array] collection of the node's child elements
|
18
|
+
#
|
19
|
+
def initialize
|
20
|
+
@themes = {
|
21
|
+
Engine::DEFAULT_THEME => {}
|
22
|
+
}
|
23
|
+
@children = []
|
24
|
+
@blocks = {}
|
25
|
+
@yields = {}
|
26
|
+
@indent = -1
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add a new node to the nodes array
|
30
|
+
#
|
31
|
+
# @param node [Node] Node to be added to the parent
|
32
|
+
#
|
33
|
+
def push(node)
|
34
|
+
@children << node
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Shorthand theme access for root definitions
|
39
|
+
#
|
40
|
+
# @param key [Symbol] definition or theme name
|
41
|
+
#
|
42
|
+
def [](key)
|
43
|
+
if @themes.has_key? key
|
44
|
+
@themes[key]
|
45
|
+
else
|
46
|
+
@themes[Engine::DEFAULT_THEME][key]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Evaluate all child nodes using the given context and the
|
51
|
+
# node definitions from the root knowledgebase
|
52
|
+
#
|
53
|
+
# @param context [Context] Call environment binding object
|
54
|
+
#
|
55
|
+
def evaluate(context)
|
56
|
+
@children.map do |node|
|
57
|
+
node.evaluate(context)
|
58
|
+
end.flatten.compact
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Nodes
|
4
|
+
module Nodes
|
5
|
+
# @Text
|
6
|
+
#
|
7
|
+
# The text class will output raw or escaped HTML text
|
8
|
+
#
|
9
|
+
class Text
|
10
|
+
# Allow direct access to literal value and type
|
11
|
+
attr_accessor :value, :escaped, :parent, :indent, :name
|
12
|
+
|
13
|
+
# Initialize literal instance variables
|
14
|
+
#
|
15
|
+
# @param value stores the literal's explicit value
|
16
|
+
#
|
17
|
+
def initialize(value = nil, escaped = true, parent = nil, indent = 0)
|
18
|
+
@value = value
|
19
|
+
@escaped = escaped
|
20
|
+
@parent = parent
|
21
|
+
@indent = indent
|
22
|
+
@name = :text
|
23
|
+
end
|
24
|
+
|
25
|
+
# Value evaluation method which returns the processed value of the
|
26
|
+
# literal
|
27
|
+
#
|
28
|
+
def evaluate(context)
|
29
|
+
value = context.evaluate "\"#{@value}\""
|
30
|
+
|
31
|
+
evaluated_text = self.dup
|
32
|
+
evaluated_text.value = @escaped ? Runtime.escape(value) : value
|
33
|
+
return evaluated_text
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @Print
|
38
|
+
#
|
39
|
+
# The print class will evaluate the ruby code and return a new text
|
40
|
+
# node containing the escaped or unescaped eval sequence
|
41
|
+
#
|
42
|
+
class Print < Text
|
43
|
+
# Value evaluation method which returns the processed value of the literal
|
44
|
+
#
|
45
|
+
def evaluate(context)
|
46
|
+
value = context.evaluate @value
|
47
|
+
|
48
|
+
evaluated_text = self.dup
|
49
|
+
evaluated_text.value = @escaped ? Runtime.escape(value) : value
|
50
|
+
return evaluated_text
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Nodes
|
4
|
+
module Nodes
|
5
|
+
# @Theme
|
6
|
+
#
|
7
|
+
# Node class used to describe a HTML Element used for building a
|
8
|
+
# page model during the parsing process
|
9
|
+
#
|
10
|
+
class Theme
|
11
|
+
# Allow direct access to node variables
|
12
|
+
attr_accessor :name, :indent, :parent, :children
|
13
|
+
|
14
|
+
# Initialize node instance variables
|
15
|
+
#
|
16
|
+
# @param name [String] name of the html node
|
17
|
+
# @param indentation [Fixnum] node indentation for restructuring
|
18
|
+
# @param attributes [Hash] stores key="value" attributes
|
19
|
+
# @param children [Array] collection of the node's child elements
|
20
|
+
#
|
21
|
+
def initialize(name = '', parent = nil, indent = 0, children = [])
|
22
|
+
@name = name
|
23
|
+
@parent = parent
|
24
|
+
@indent = indent
|
25
|
+
@children = children
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add a new node to the nodes array
|
29
|
+
#
|
30
|
+
def push(node)
|
31
|
+
@children << node
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require_relative 'parser/root'
|
2
|
+
require_relative 'parser/node'
|
3
|
+
require_relative 'parser/eval'
|
4
|
+
require_relative 'parser/expression'
|
5
|
+
require_relative 'parser/define'
|
6
|
+
require_relative 'parser/text'
|
7
|
+
require_relative 'parser/filter'
|
8
|
+
require_relative 'parser/control'
|
9
|
+
require_relative 'parser/theme'
|
10
|
+
require_relative 'parser/block'
|
11
|
+
require_relative 'parser/comment'
|
12
|
+
|
13
|
+
# @Opulent
|
14
|
+
module Opulent
|
15
|
+
# @Lexer
|
16
|
+
module Parser
|
17
|
+
# @Singleton
|
18
|
+
class << self
|
19
|
+
# Include Opulent tokens regular expressions to be checked using the
|
20
|
+
# accept (or expect) method together with lookahead expressions
|
21
|
+
include Root
|
22
|
+
include Node
|
23
|
+
include Expression
|
24
|
+
include Evaluate
|
25
|
+
include Define
|
26
|
+
include Text
|
27
|
+
include Filter
|
28
|
+
include Control
|
29
|
+
include Theme
|
30
|
+
include Block
|
31
|
+
include Comment
|
32
|
+
|
33
|
+
# Analyze the input code and check for matching tokens.
|
34
|
+
# In case no match was found, throw an exception.
|
35
|
+
# In special cases, modify the token hash.
|
36
|
+
#
|
37
|
+
# @param code [String] Opulent code that needs to be analyzed
|
38
|
+
# @return Nodes array
|
39
|
+
#
|
40
|
+
def parse(code)
|
41
|
+
# Code to be parsed
|
42
|
+
@code = code
|
43
|
+
|
44
|
+
# Keeps track of consumed code
|
45
|
+
@consumed = ""
|
46
|
+
|
47
|
+
# Current line being parsed, used for error reporting
|
48
|
+
@current_line = 1
|
49
|
+
|
50
|
+
# Node creation wrapper for comprehensible node creation
|
51
|
+
@create = Nodes::Helper.new
|
52
|
+
|
53
|
+
# Create root node, our wrapper for all the HTML elements we use
|
54
|
+
# which knows how to evaluate each of them to output the final code
|
55
|
+
# The root also stores custom node definitions collection from which we
|
56
|
+
# replace the nodes which use them
|
57
|
+
@root = @create.root
|
58
|
+
|
59
|
+
# Loop until we have no tokens left to parse or we find an error
|
60
|
+
root @root
|
61
|
+
|
62
|
+
# We still have code left to parse
|
63
|
+
error :root unless @code.strip.empty?
|
64
|
+
|
65
|
+
return @root
|
66
|
+
end
|
67
|
+
|
68
|
+
# Accept and consume or reject a given token as long as we have tokens
|
69
|
+
# remaining. Shift the code with the match length plus any extra character
|
70
|
+
# count around the capture group
|
71
|
+
#
|
72
|
+
# @param identifier [RegEx] Token syntax to be accepted by the parser
|
73
|
+
# @param required [Boolean] Expect the given token syntax
|
74
|
+
# @param strip [Boolean] Left strip the current code to remove whitespace
|
75
|
+
#
|
76
|
+
def accept(identifier, required = false, strip = true)
|
77
|
+
# Get token from tokens knowledgebase
|
78
|
+
token = Tokens[identifier]
|
79
|
+
|
80
|
+
# If the token's capture group is smaller than the whole match,
|
81
|
+
# advance the code chunk with more spaces
|
82
|
+
extra = token[:extra] || 0
|
83
|
+
|
84
|
+
# Remove leading whitespace between expressions
|
85
|
+
if @code && strip
|
86
|
+
if (stripped = @code[/\A +/]) then @consumed += stripped end
|
87
|
+
@code.lstrip!
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check to see if
|
91
|
+
if @code =~ token[:regex]
|
92
|
+
@current_line += 1 if identifier == :newline
|
93
|
+
|
94
|
+
shift = $1.size + extra
|
95
|
+
|
96
|
+
@consumed += @code[0..shift - 1] if shift > 0
|
97
|
+
@code = @code[shift..-1]
|
98
|
+
|
99
|
+
return $1
|
100
|
+
elsif required
|
101
|
+
error :expect, identifier
|
102
|
+
else
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Accept and consume or reject a given token as long as we have tokens
|
108
|
+
# remaining on the current line only. Shift the code with the match length
|
109
|
+
# plus any extra character count around the capture group
|
110
|
+
#
|
111
|
+
# @param identifier [RegEx] Token syntax to be accepted by the parser
|
112
|
+
# @param required [Boolean] Expect the given token syntax
|
113
|
+
# @param strip [Boolean] Left strip the current code to remove whitespace
|
114
|
+
#
|
115
|
+
def accept_line(identifier, required = false, strip = true)
|
116
|
+
# Get token from tokens knowledgebase
|
117
|
+
token = Tokens[identifier]
|
118
|
+
|
119
|
+
# If the token's capture group is smaller than the whole match,
|
120
|
+
# advance the code chunk with more spaces
|
121
|
+
extra = token[:extra] || 0
|
122
|
+
|
123
|
+
# Remove leading whitespace between expressions
|
124
|
+
if @code.lines.first && strip
|
125
|
+
if (stripped = @code.lines.first[/\A +/]) then @consumed += stripped end
|
126
|
+
@code.gsub! /\A +/, ''
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check to see if
|
130
|
+
if @code.lines.first =~ token[:regex]
|
131
|
+
@current_line += 1 if identifier == :newline
|
132
|
+
|
133
|
+
shift = $1.size + extra
|
134
|
+
|
135
|
+
@consumed += @code[0..shift - 1] if shift > 0
|
136
|
+
@code = @code[shift..-1]
|
137
|
+
|
138
|
+
return $1
|
139
|
+
elsif required
|
140
|
+
error :expect, identifier
|
141
|
+
else
|
142
|
+
return nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Wrapper method for accepting unstripped tokens such as whitespace or a
|
147
|
+
# certain required sequence directly
|
148
|
+
#
|
149
|
+
# @param identifier [RegEx] Token syntax to be accepted by the parser
|
150
|
+
# @param required [Boolean] Expect the given token syntax
|
151
|
+
#
|
152
|
+
def accept_unstripped(identifier, required = false)
|
153
|
+
accept(identifier, required, false)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Wrapper method for accepting unstripped tokens such as whitespace or a
|
157
|
+
# certain required sequence directly on the current line only
|
158
|
+
#
|
159
|
+
# @param identifier [RegEx] Token syntax to be accepted by the parser
|
160
|
+
# @param required [Boolean] Expect the given token syntax
|
161
|
+
#
|
162
|
+
def accept_line_unstripped(identifier, required = false)
|
163
|
+
accept_line(identifier, required, false)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Look ahead in the current code to see if we can match an input token.
|
167
|
+
# No modifications will be done to the code.
|
168
|
+
#
|
169
|
+
# @param token [RegEx] Token syntax to be accepted by the parser
|
170
|
+
# @param strip [Boolean] Left strip the current code to remove whitespace
|
171
|
+
#
|
172
|
+
def lookahead(token, strip = true)
|
173
|
+
# Get token from tokens knowledgebase
|
174
|
+
token = Tokens[token]
|
175
|
+
|
176
|
+
# We don't want to modify anything in the code directly, so we use a
|
177
|
+
# local code variable
|
178
|
+
code = @code
|
179
|
+
|
180
|
+
# Remove leading whitespace between expressions
|
181
|
+
code = code.lstrip if code && strip
|
182
|
+
|
183
|
+
# Check to see if
|
184
|
+
if code =~ token[:regex]
|
185
|
+
return $~[:capture]
|
186
|
+
else
|
187
|
+
return nil
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Undo a found match by removing the token from the consumed code and
|
192
|
+
# adding it back to the code chunk
|
193
|
+
#
|
194
|
+
# @param match [String] Matched string to be undone
|
195
|
+
#
|
196
|
+
def undo(match)
|
197
|
+
unless match.empty?
|
198
|
+
@consumed = @consumed[0..-match.length]
|
199
|
+
@code = match + @code
|
200
|
+
return nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Give an explicit error report where an unexpected sequence of tokens
|
205
|
+
# appears and give indications on how to solve it
|
206
|
+
#
|
207
|
+
# @param context [Symbol] Context name in which the error happens
|
208
|
+
# @param data [Array] Additional error information
|
209
|
+
#
|
210
|
+
def error(context, *data)
|
211
|
+
consumed = @consumed.lines.last.strip if @consumed.lines.last
|
212
|
+
code = @code.empty? ? ' END' : @code.lines.first.strip
|
213
|
+
|
214
|
+
message = case context
|
215
|
+
when :root
|
216
|
+
"Unknown node type encountered on line #{@current_line} of input at:\n\n" +
|
217
|
+
"#{consumed}" + Logger.red(code)
|
218
|
+
when :expect
|
219
|
+
"Expected to find token :#{data[0]} on line #{@current_line} of input at:\n\n" +
|
220
|
+
"#{consumed}" + Logger.red(code)
|
221
|
+
when :assignments_colon
|
222
|
+
"Unexpected end of element attributes reached on line #{@current_line} of input.\n\n" +
|
223
|
+
"Expected to find an attribute at:\n\n" +
|
224
|
+
"#{consumed}" + Logger.red(code)
|
225
|
+
when :assignments_comma
|
226
|
+
"Unexpected end of element attributes reached on line #{@current_line} of input.\n\n" +
|
227
|
+
"Expected to find an attribute value at:\n\n" +
|
228
|
+
"#{consumed}" + Logger.red(code)
|
229
|
+
when :expression
|
230
|
+
"Unexpected end of expression reached on line #{@current_line} of input.\n\n" +
|
231
|
+
"Expected to find another expression term at:\n\n" +
|
232
|
+
"#{consumed}" + Logger.red(code)
|
233
|
+
when :whitespace_expression
|
234
|
+
"Unexpected end of expression reached on line #{@current_line} of input.\n\n" +
|
235
|
+
"Please use paranthesis for method parameters at:\n\n" +
|
236
|
+
"#{consumed}" + Logger.red(code)
|
237
|
+
when :definition
|
238
|
+
"Unexpected start of definition on line #{@current_line - 1} of input.\n\n" +
|
239
|
+
"Found a definition inside another definition or element at:\n\n" +
|
240
|
+
(@consumed.lines[-2] || "") + Logger.red(consumed + "\n " + code)
|
241
|
+
else
|
242
|
+
"#{consumed}" + Logger.red(code)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Reconstruct lines to display where errors occur
|
246
|
+
fail "\n\nOpulent " + Logger.red("[Parser Error]") + "\n---\n" +
|
247
|
+
"A parsing error has been encountered in the \"#{context}\" context.\n" +
|
248
|
+
"#{message}\n\n\n"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|