ReDuxml 0.1.0
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 +7 -0
- data/lib/ast_ext/node.rb +51 -0
- data/lib/re_dux.rb +119 -0
- data/lib/re_dux/element.rb +8 -0
- data/lib/re_dux/element/parameterization.rb +17 -0
- data/lib/re_dux/evaluate.rb +74 -0
- data/lib/re_dux/evaluate/lexer.rb +106 -0
- data/lib/re_dux/evaluate/operator.rb +96 -0
- data/lib/re_dux/evaluate/parser.rb +143 -0
- data/lib/re_dux/parameters.rb +28 -0
- data/lib/ruby_ext/boolean.rb +37 -0
- data/lib/ruby_ext/fixnum.rb +5 -0
- data/lib/ruby_ext/macro.rb +50 -0
- data/lib/ruby_ext/regexp.rb +8 -0
- data/lib/ruby_ext/string.rb +12 -0
- data/lib/symbolic_ext/coerced.rb +15 -0
- data/lib/symbolic_ext/symbolic.rb +25 -0
- data/lib/symbolic_ext/variable.rb +60 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3c46a9c2cb2e9736c7e650a7ac4a709adcbafaec
|
4
|
+
data.tar.gz: ff9641aec41459ce78fa9290b392762f0c5a0f6d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 993f3702741994531bb37a9a453576eec93002d0054ebbfd23be4569b6dc89e52abddbf2bc8cece85e0544cfc501ab3442d4d7579c15de21559b96e1600cd045
|
7
|
+
data.tar.gz: 6f8ac1fa8e955a2a15c4b3c621131f9556e1a4c49458144f2079fc77447a72f5f5d30106123b6b122ce615dc149af84e71d4f87652f2fc443820a2b3a59c369f
|
data/lib/ast_ext/node.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'ast'
|
2
|
+
|
3
|
+
module AST
|
4
|
+
# redefining in order to allow type itself to be any type e.g. String, Symbol, Fixnum, etc.
|
5
|
+
class Node
|
6
|
+
include AST
|
7
|
+
|
8
|
+
# The `properties` hash is passed to {#assign_properties}.
|
9
|
+
def initialize(type, children=[], properties={})
|
10
|
+
@type, @children = type, children.to_a.freeze
|
11
|
+
|
12
|
+
assign_properties(properties)
|
13
|
+
|
14
|
+
@hash = [@type.object_id, @children, self.class].hash
|
15
|
+
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param logic [Hash] hash of operators allowed in this AST containing each operator's print properties
|
20
|
+
# @return [String] string reconstituted from polish-notation into notation normally required by each operator
|
21
|
+
def print(logic=nil)
|
22
|
+
return type.to_s if children.empty?
|
23
|
+
str = ''
|
24
|
+
op = type.respond_to?(:text) ? type : logic[type.to_s]
|
25
|
+
case op.position
|
26
|
+
when :prefix
|
27
|
+
str << op.symbol
|
28
|
+
children.each do |c| str << c.print(logic) end
|
29
|
+
when :postfix
|
30
|
+
children.each do |c| str << c.print(logic) end
|
31
|
+
str << op.symbol
|
32
|
+
when :infix
|
33
|
+
if op.arity > 2
|
34
|
+
str << children.first.print(logic) << op.symbol << children[1].print(logic) << op.pair.to_s << children.last.print
|
35
|
+
else
|
36
|
+
str << (children.first.respond_to?(:print) ? children.first.print(logic) : children.first.to_s) << op.symbol << children.last.print
|
37
|
+
end
|
38
|
+
else # should not happen
|
39
|
+
end
|
40
|
+
str
|
41
|
+
end # to_s
|
42
|
+
end # class Node
|
43
|
+
|
44
|
+
def new_ast(op, *obj)
|
45
|
+
args = obj.collect do |o| o.is_a?(Node) ? o : Node.new(o) end
|
46
|
+
args.unshift self if is_a?(Node)
|
47
|
+
args.unshift Node.new(self) if is_a?(Fixnum)
|
48
|
+
args.unshift Node.new(self) if self.is_a?(Symbolic::Variable)
|
49
|
+
Node.new(op, args)
|
50
|
+
end
|
51
|
+
end
|
data/lib/re_dux.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/ruby_ext/macro')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/re_dux/evaluate')
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/re_dux/element')
|
4
|
+
|
5
|
+
module ReDuxml
|
6
|
+
include Duxml
|
7
|
+
|
8
|
+
class ResolverClass < ::Ox::Sax
|
9
|
+
COND_ATTR_NAME = 'if'
|
10
|
+
REF_ATTR_NAME = 'ref'
|
11
|
+
|
12
|
+
@dead = false
|
13
|
+
@cursor_stack
|
14
|
+
|
15
|
+
attr_reader :e, :dead, :cursor_stack
|
16
|
+
|
17
|
+
# @param doc [Ox::Document] document that is being constructed as XML is parsed
|
18
|
+
# @param _observer [Object] object that will observe this document's content
|
19
|
+
def initialize(logic)
|
20
|
+
@cursor_stack = []
|
21
|
+
@e = ReDuxml::Evaluator.new(logic)
|
22
|
+
end
|
23
|
+
|
24
|
+
def cursor
|
25
|
+
@cursor_stack.last
|
26
|
+
end
|
27
|
+
|
28
|
+
def start_element(name)
|
29
|
+
new_el = Duxml::Element.new(name, line, column)
|
30
|
+
cursor << new_el unless cursor.nil?
|
31
|
+
@cursor_stack << new_el
|
32
|
+
end
|
33
|
+
|
34
|
+
def attr(name, val)
|
35
|
+
cursor[name] = val
|
36
|
+
end
|
37
|
+
|
38
|
+
def text(str)
|
39
|
+
cursor << str
|
40
|
+
end
|
41
|
+
|
42
|
+
def end_element(name)
|
43
|
+
case
|
44
|
+
when cursor.if?
|
45
|
+
cursor.remove_attribute(COND_ATTR_NAME)
|
46
|
+
when cursor.respond_to?(:instantiate)
|
47
|
+
# target = cursor.instantiate # target likely plural
|
48
|
+
@cursor_stack[-2].replace(cursor, target)
|
49
|
+
when cursor.ref?
|
50
|
+
# target = resolve_ref
|
51
|
+
@cursor_stack[-2].replace(cursor, target)
|
52
|
+
cursor.remove_attribute(REF_ATTR_NAME)
|
53
|
+
else
|
54
|
+
@cursor_stack[-2].remove(cursor)
|
55
|
+
return
|
56
|
+
end
|
57
|
+
@cursor_stack.pop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# generates new doc from current doc, resolving parameter values and instantiating Instance objects, and pruning filtered objonents.
|
62
|
+
def resolve(path=nil)
|
63
|
+
io = File.open path
|
64
|
+
saxer = ResolverClass.new(Duxml::Doc.new)
|
65
|
+
Ox.sax_parse(saxer, io, {convert_special: true, symbolize: false})
|
66
|
+
saxer.cursor
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# finds index of close parentheses corresponding to first open parentheses found in given str
|
72
|
+
def find_close_parens_index(str)
|
73
|
+
levels = 0
|
74
|
+
index = 0
|
75
|
+
str.each_char do |char|
|
76
|
+
case char
|
77
|
+
when '(' then
|
78
|
+
levels += 1
|
79
|
+
when ')' then
|
80
|
+
levels -= 1
|
81
|
+
else
|
82
|
+
end
|
83
|
+
return index if levels == 0
|
84
|
+
index += 1
|
85
|
+
end
|
86
|
+
raise Exception, "cannot find end of parameter expression!"
|
87
|
+
end
|
88
|
+
|
89
|
+
# takes given potentially parameterized string, applies given param_hash's values, then resolves parameter expressions
|
90
|
+
# returning resolved result
|
91
|
+
def resolve_str(content_str, param_hash)
|
92
|
+
question = find_expr content_str
|
93
|
+
return content_str if question.nil?
|
94
|
+
reply = Macro.new e.evaluate(question, param_hash)
|
95
|
+
replacement_str = reply.parameterized? ? reply : reply.demacro
|
96
|
+
macro_string = Macro.new(question)
|
97
|
+
content_str.gsub(macro_string, replacement_str)
|
98
|
+
end
|
99
|
+
|
100
|
+
# finds macro expression within given string
|
101
|
+
# e.g. find_expr 'asdf @(param) asdf' => 'param'
|
102
|
+
def find_expr(str)
|
103
|
+
expr_start_index = str.index('@(')
|
104
|
+
return nil if expr_start_index.nil?
|
105
|
+
expr_end_index = find_close_parens_index str[expr_start_index+1..-1]
|
106
|
+
str[expr_start_index+2, expr_end_index-1]
|
107
|
+
end
|
108
|
+
|
109
|
+
# returns first ancestor that is NOT an Instance
|
110
|
+
def find_non_inst_ancestor(node)
|
111
|
+
cur = node.parent
|
112
|
+
return if cur.nil?
|
113
|
+
if cur.respond_to?(:params) && cur.simple_class != 'design'
|
114
|
+
find_non_inst_ancestor cur
|
115
|
+
else
|
116
|
+
cur
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end # module Dux
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../ruby_ext/string')
|
2
|
+
|
3
|
+
# methods to extend Dux::Object with methods needed to process parameterized XML content
|
4
|
+
module Parameterization
|
5
|
+
# returns true if self[:if] is true or indeterminate (because condition is currently parameterized)
|
6
|
+
# returns false otherwise i.e. this node does not exist in this design build
|
7
|
+
def if?
|
8
|
+
return true unless (if_str = xml[:if])
|
9
|
+
if_str.parameterized? || if_str == 'true' ? true : false
|
10
|
+
end
|
11
|
+
|
12
|
+
# changes condition of this object's existence
|
13
|
+
def if=(condition)
|
14
|
+
# check for valid conditional
|
15
|
+
change_attr_value :if, condition
|
16
|
+
end
|
17
|
+
end # module Parameterization
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require_relative 'evaluate/parser'
|
2
|
+
|
3
|
+
module ReDuxml
|
4
|
+
class Evaluator
|
5
|
+
include Math
|
6
|
+
include ReDuxml
|
7
|
+
include Symbolic
|
8
|
+
|
9
|
+
COMPARATORS = %w()
|
10
|
+
|
11
|
+
@param_hash
|
12
|
+
@parser
|
13
|
+
|
14
|
+
attr_reader :param_hash, :parser
|
15
|
+
|
16
|
+
def initialize(logic=nil)
|
17
|
+
@parser = Parser.new(logic || '../../xml/logic.xml')
|
18
|
+
@param_hash = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#<Evaluator: param_hash: '#{param_hash.to_s}' parser_logic: '#{parser}'>"
|
23
|
+
end
|
24
|
+
|
25
|
+
def evaluate(_expr, _param_hash={})
|
26
|
+
@param_hash = _param_hash
|
27
|
+
expr = resolve_params _expr
|
28
|
+
result = reduce parser.parse expr
|
29
|
+
case
|
30
|
+
when result.respond_to?(:imaginary), result.class == TrueClass, result.class == FalseClass then result
|
31
|
+
when result.respond_to?(:print) then result.print(parser.logic)
|
32
|
+
else result.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def resolve_params(_expr)
|
39
|
+
expr = _expr.dup
|
40
|
+
param_hash.each do |param, val| expr.gsub!(param, val) end
|
41
|
+
expr
|
42
|
+
end
|
43
|
+
|
44
|
+
include Symbolic
|
45
|
+
|
46
|
+
def reduce(_ast)
|
47
|
+
ast = _ast.type.respond_to?(:symbol) ? _ast : new_ast(parser.logic[_ast.type.to_s], _ast.children.dup)
|
48
|
+
if ast.children.any?
|
49
|
+
operator = ast.type
|
50
|
+
args = ast.children.collect do |c|
|
51
|
+
new_child = c.children.any? ? reduce(c) : c.type
|
52
|
+
if new_child.is_a?(Node) && new_child.type.is_a?(Symbol)
|
53
|
+
new_ast(parser.logic[new_child.type.to_s], *new_child.children.dup)
|
54
|
+
else
|
55
|
+
new_child
|
56
|
+
end
|
57
|
+
end.flatten
|
58
|
+
begin
|
59
|
+
result = case operator.position
|
60
|
+
when :prefix
|
61
|
+
method(operator.ruby).call(*args)
|
62
|
+
else
|
63
|
+
args.first.send(operator.ruby, *args[1..-1])
|
64
|
+
end
|
65
|
+
result.nil? ? ast : result
|
66
|
+
rescue
|
67
|
+
ast
|
68
|
+
end
|
69
|
+
else
|
70
|
+
ast
|
71
|
+
end
|
72
|
+
end # def reduce(ast)
|
73
|
+
end # class Evaluator
|
74
|
+
end # module ReDux
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require_relative '../../ruby_ext/regexp'
|
2
|
+
require_relative '../../../lib/symbolic_ext/symbolic'
|
3
|
+
|
4
|
+
module Lexer
|
5
|
+
@string_hash
|
6
|
+
@input
|
7
|
+
@tokens
|
8
|
+
attr_reader :string_hash
|
9
|
+
attr_accessor :input, :tokens
|
10
|
+
|
11
|
+
TOKEN_TYPES = {
|
12
|
+
string: /STRING[\d]+/,
|
13
|
+
function: /log|exp|sqrt/,
|
14
|
+
bool: /true|false/,
|
15
|
+
param: Regexp.identifier,
|
16
|
+
num: /\d/,
|
17
|
+
grouping: /[\(\):,]/
|
18
|
+
}
|
19
|
+
|
20
|
+
Struct.new('Token', :type, :value)
|
21
|
+
|
22
|
+
def lex(expr)
|
23
|
+
@input = tag_strings(expr).split(/\b/).reverse.collect do |s| s.strip end
|
24
|
+
@tokens = []
|
25
|
+
while (sub_str = input.pop) do
|
26
|
+
type = get_type sub_str
|
27
|
+
value = formatted_value(sub_str, type)
|
28
|
+
tokens << (@last_token = Struct::Token.new(type, value)) unless value.nil?
|
29
|
+
end
|
30
|
+
@last_token = nil
|
31
|
+
tokens
|
32
|
+
end # def lex(expr)
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :last_token
|
37
|
+
|
38
|
+
def formatted_value(sub_str, type)
|
39
|
+
formatted_str = untag_strings subst_minus(sub_str)
|
40
|
+
case type
|
41
|
+
when :operator, :grouping
|
42
|
+
split_or_keep formatted_str
|
43
|
+
when :param then get_var(formatted_str)
|
44
|
+
when :num then formatted_str.to_i
|
45
|
+
when :bool then formatted_str == 'true'
|
46
|
+
else
|
47
|
+
formatted_str
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_var(str)
|
52
|
+
if(var_token = tokens.find do |t| t.value.to_s == str end)
|
53
|
+
var_token.value
|
54
|
+
else
|
55
|
+
Symbolic.send(:var, name: str)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def split_or_keep(str)
|
60
|
+
if str.size > 1 && logic[str].nil?
|
61
|
+
str.split(//).reverse.each do |c| input << c unless c.strip.empty? end
|
62
|
+
nil
|
63
|
+
else
|
64
|
+
logic[str]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_type(sub_str)
|
69
|
+
TOKEN_TYPES.each do |type, regexp|
|
70
|
+
return type if regexp.match(sub_str).to_s == sub_str
|
71
|
+
end
|
72
|
+
:operator
|
73
|
+
end
|
74
|
+
|
75
|
+
def subst_minus(_str)
|
76
|
+
str = _str.dup
|
77
|
+
if str == '-'
|
78
|
+
unless last_token.nil? || last_token.type == 'operator' || %w(\( , :).include?(last_token.value)
|
79
|
+
str = "\u2013"
|
80
|
+
str.encode('utf-8')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
str
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
def untag_strings(_expr)
|
88
|
+
expr = _expr.dup
|
89
|
+
string_hash.each do |k, v| expr.gsub!(k, v) end
|
90
|
+
expr
|
91
|
+
end
|
92
|
+
|
93
|
+
#strings can contain whitespaces and characters the parser may miscategorize as operators, etc.
|
94
|
+
# so they are replaced with unique keys in a module attribute hash for retrieval when doing string operations
|
95
|
+
# and returning final result
|
96
|
+
def tag_strings(expr)
|
97
|
+
tagged_str = expr.dup
|
98
|
+
@string_hash = {}
|
99
|
+
expr.scan(Regexp.string) do |s|
|
100
|
+
k = "STRING#{s.object_id}"
|
101
|
+
tagged_str[s] = k
|
102
|
+
@string_hash[k] = s
|
103
|
+
end
|
104
|
+
tagged_str
|
105
|
+
end
|
106
|
+
end # module Lexer
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Operator
|
2
|
+
include Reportable
|
3
|
+
# @return [Boolean]
|
4
|
+
def grouping?
|
5
|
+
nodes.find do |n|
|
6
|
+
return true if n.name == 'pair'
|
7
|
+
end
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def parent=(logic)
|
12
|
+
@logic = logic
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Boolean]
|
16
|
+
def right_associative?
|
17
|
+
nodes.find do |n|
|
18
|
+
return n.text == 'true' if n.name == 'right_associative'
|
19
|
+
end
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] name of ruby method corresponding to this operator
|
24
|
+
def ruby
|
25
|
+
nodes.find do |n|
|
26
|
+
return n.text if n.name == 'ruby'
|
27
|
+
end
|
28
|
+
symbol
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] literal for operator e.g. '+'
|
32
|
+
def symbol
|
33
|
+
return nil unless self.respond_to?(:nodes)
|
34
|
+
nodes.find do |n|
|
35
|
+
return n.text if n.name == 'symbol'
|
36
|
+
end
|
37
|
+
raise Exception
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Symbol] :prefix, :infix (default), or :postfix
|
41
|
+
def position
|
42
|
+
nodes.find do |n|
|
43
|
+
return n.text.to_sym if n.name == 'position'
|
44
|
+
end
|
45
|
+
:infix
|
46
|
+
end
|
47
|
+
|
48
|
+
def reverse
|
49
|
+
nodes.find do |n|
|
50
|
+
return @logic[n.text] if n.name == 'reverse'
|
51
|
+
end
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def pair
|
56
|
+
return nil unless grouping?
|
57
|
+
nodes.find do |n|
|
58
|
+
return @logic[n.text] if n.name == 'pair'
|
59
|
+
end
|
60
|
+
raise Exception
|
61
|
+
end
|
62
|
+
|
63
|
+
def inverse
|
64
|
+
nodes.find do |n|
|
65
|
+
return @logic[n.text] if n.name == 'inverse'
|
66
|
+
end
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
symbol
|
72
|
+
end
|
73
|
+
|
74
|
+
def print
|
75
|
+
nodes.find do |n|
|
76
|
+
return n.text if n.name == 'print'
|
77
|
+
end
|
78
|
+
symbol
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Regexp] expression to find operator in string
|
82
|
+
def regexp
|
83
|
+
nodes.find do |n|
|
84
|
+
return Regexp.new(n.text) if %w(regexp symbol ).include?(n.name)
|
85
|
+
end
|
86
|
+
# TODO exception here?
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Fixnum] number of arguments required
|
90
|
+
def arity
|
91
|
+
nodes.find do |n|
|
92
|
+
return n.text.to_i if n.name == 'arity'
|
93
|
+
end
|
94
|
+
2
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'duxml'
|
2
|
+
require_relative 'operator'
|
3
|
+
require_relative 'lexer'
|
4
|
+
|
5
|
+
module ReDuxml
|
6
|
+
class Parser
|
7
|
+
# hash of unique strings found in the parsed expression for substitution before (@see Lexer#lex) and
|
8
|
+
# after parsing to allow parser to handle strings that may contain white spaces, operators, etc.
|
9
|
+
@string_hash
|
10
|
+
|
11
|
+
# array of Struct::Tokens from lexer
|
12
|
+
@input
|
13
|
+
|
14
|
+
# array of AST nodes produced by parser; can be popped to subordinate to higher precedence operation nodes
|
15
|
+
@output
|
16
|
+
|
17
|
+
# array of operator definitions (not classes!) in XML form, extended by module Operator
|
18
|
+
@op_stack
|
19
|
+
|
20
|
+
# hash of operator symbol strings to Operator object that contains properties and methods to access them
|
21
|
+
@logic
|
22
|
+
|
23
|
+
# stack to track argument count for operators requiring more than 2
|
24
|
+
@arities
|
25
|
+
|
26
|
+
attr_reader :string_hash, :logic
|
27
|
+
attr_accessor :input, :output, :op_stack, :arities
|
28
|
+
|
29
|
+
include Duxml
|
30
|
+
include Lexer
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
doc.logic.name
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(_logic)
|
37
|
+
if _logic
|
38
|
+
load _logic
|
39
|
+
@logic = {}
|
40
|
+
doc.logic.Operator.each do |op|
|
41
|
+
op.parent = @logic
|
42
|
+
@logic[op.symbol] = op
|
43
|
+
end
|
44
|
+
end
|
45
|
+
raise Exception if logic.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO attribute code to Dentaku
|
49
|
+
def parse(expr)
|
50
|
+
@input = lex(expr)
|
51
|
+
@output = []
|
52
|
+
@op_stack = []
|
53
|
+
@arities = []
|
54
|
+
|
55
|
+
return nil if input.empty?
|
56
|
+
|
57
|
+
while(token = input.shift)
|
58
|
+
case token.type
|
59
|
+
when :num, :bool, :string, :param
|
60
|
+
output.push AST::Node.new(token.value)
|
61
|
+
when :operator
|
62
|
+
op_prop = token.value
|
63
|
+
arities << op_prop.arity-1 if op_prop.symbol == '?'
|
64
|
+
if op_prop.right_associative?
|
65
|
+
while op_stack.last && op_prop.precedence < op_stack.last.precedence
|
66
|
+
if !op_stack.last.grouping?
|
67
|
+
consume
|
68
|
+
else
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
else
|
73
|
+
while op_stack.last && op_prop.precedence <= op_stack.last.precedence && !op_stack.last.grouping?
|
74
|
+
consume
|
75
|
+
end
|
76
|
+
end
|
77
|
+
op_stack << op_prop
|
78
|
+
when :function
|
79
|
+
arities << 0
|
80
|
+
op_stack << token.value
|
81
|
+
when :grouping
|
82
|
+
op_prop = token.value
|
83
|
+
case op_prop.to_s
|
84
|
+
when '('
|
85
|
+
if input.any? && input.first.type == :grouping && input.first.value.to_s == '('
|
86
|
+
input.shift
|
87
|
+
consume(0)
|
88
|
+
else
|
89
|
+
op_stack << op_prop
|
90
|
+
end
|
91
|
+
when ')'
|
92
|
+
while op_stack.any? && op_stack.last.symbol != op_prop.pair.to_s
|
93
|
+
consume(arities.pop || op_stack.last.arity)
|
94
|
+
end
|
95
|
+
lparen = op_stack.pop
|
96
|
+
fail ParseError, "Unbalanced parenthesis" unless lparen && lparen.grouping?
|
97
|
+
|
98
|
+
if op_stack.last && op_stack.last.position == 'prefix'
|
99
|
+
consume(arities.pop)
|
100
|
+
end
|
101
|
+
when ','
|
102
|
+
arities[-1] += 1
|
103
|
+
while op_stack.any? && op_stack.last.symbol != '('
|
104
|
+
consume
|
105
|
+
end
|
106
|
+
when ':'
|
107
|
+
while op_stack.any? && op_stack.last.symbol != '?'
|
108
|
+
consume(arities.pop)
|
109
|
+
end
|
110
|
+
arities[-1] += 1
|
111
|
+
op_stack << op_prop
|
112
|
+
else
|
113
|
+
fail ParseError, "Unknown grouping token #{ token.value }"
|
114
|
+
end # case token.value ... when :grouping
|
115
|
+
else
|
116
|
+
fail ParseError, "Not implemented for tokens of type #{ token.type }"
|
117
|
+
end # case token.type
|
118
|
+
end # while (token = input.shift)
|
119
|
+
|
120
|
+
while op_stack.any?
|
121
|
+
consume
|
122
|
+
end
|
123
|
+
|
124
|
+
unless output.count == 1
|
125
|
+
fail ParseError, "Invalid statement"
|
126
|
+
end
|
127
|
+
|
128
|
+
output.first
|
129
|
+
end # def parse(expr)
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def consume(count=2)
|
134
|
+
op_stack.pop if op_stack.last.symbol == ':'
|
135
|
+
operator = op_stack.pop
|
136
|
+
output.push AST::Node.new(operator, get_args(operator.arity || count))
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_args(count)
|
140
|
+
Array.new(count) { output.pop }.reverse.compact
|
141
|
+
end
|
142
|
+
end # class Parser
|
143
|
+
end # module ReDuxml
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../symbolic_ext/variable')
|
2
|
+
|
3
|
+
module Duxml
|
4
|
+
module Parameter; end
|
5
|
+
|
6
|
+
# represents a parameter that can be used in any element's attribute values or text content
|
7
|
+
# and is replaced with its value when validating an XML design
|
8
|
+
class ParameterClass < ::Symbolic::Variable
|
9
|
+
# Parameter can be initialized from XML Element or Ruby args
|
10
|
+
# @param args[0] [String] must be name of Parameter
|
11
|
+
# @param args[1] [String|Fixnum|Float|Boolean] can be starting value of Parameter
|
12
|
+
# @param args[2] [String] can be description text of Parameter
|
13
|
+
def initialize(name, value=nil)
|
14
|
+
@name, @value = name, value
|
15
|
+
end
|
16
|
+
end # class Parameter
|
17
|
+
|
18
|
+
module Parameter
|
19
|
+
# @param val [String|Fixnum|Float|Boolean] changes value of Parameter and reports change
|
20
|
+
def value=(val)
|
21
|
+
if val != self[:value]
|
22
|
+
old_val = self[:value]
|
23
|
+
self[:value] = val
|
24
|
+
report :change_attribute, {old_value: old_val, new_value: val, attr_name: 'value'}
|
25
|
+
end
|
26
|
+
end # def value=
|
27
|
+
end
|
28
|
+
end # module Dux
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative '../../lib/ast_ext/node'
|
2
|
+
|
3
|
+
class TrueClass
|
4
|
+
def ternary(a, b)
|
5
|
+
a
|
6
|
+
end
|
7
|
+
|
8
|
+
def and(obj)
|
9
|
+
obj
|
10
|
+
end
|
11
|
+
|
12
|
+
def or(obj)
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def not
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class FalseClass
|
22
|
+
def ternary(a, b)
|
23
|
+
b
|
24
|
+
end
|
25
|
+
|
26
|
+
def and(obj)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def or(obj)
|
31
|
+
obj
|
32
|
+
end
|
33
|
+
|
34
|
+
def not
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative 'string'
|
2
|
+
|
3
|
+
# string wrapped in parameter expression macro symbol and delimiters,
|
4
|
+
# indicating content is to be parsed and resolved when building and validating XML design
|
5
|
+
class Macro
|
6
|
+
include Enumerable
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
# is '@' by default
|
10
|
+
MACRO_SYMBOL = '@'
|
11
|
+
# are parentheses by default e.g. '()'
|
12
|
+
DELIMITERS = %w{( )}
|
13
|
+
|
14
|
+
@macro_string
|
15
|
+
|
16
|
+
# string including MACRO_SYMBOL and DELIMITERS
|
17
|
+
attr_accessor :macro_string
|
18
|
+
|
19
|
+
# takes given string and wraps in MACRO_SYMBOL and DELIMITERS if not already wrapped
|
20
|
+
# e.g. str => 'asdf'
|
21
|
+
# Macro.new str => '@(asdf)'
|
22
|
+
def initialize(str)
|
23
|
+
@macro_string = is_macro?(str) ? str : "#{MACRO_SYMBOL}#{DELIMITERS.first}#{str}#{DELIMITERS.last}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# checks a string to see if it's a valid macro expression without leading or trailing non-expression or delimiter text
|
27
|
+
def is_macro?(str)
|
28
|
+
str[0,2] == MACRO_SYMBOL+DELIMITERS.first && str[-1] == DELIMITERS.last && str.balanced_parens?
|
29
|
+
end
|
30
|
+
|
31
|
+
# compares #demacro'd @macro_string to obj
|
32
|
+
def <=>(obj)
|
33
|
+
demacro <=> obj
|
34
|
+
end
|
35
|
+
|
36
|
+
# just yields each character of #demacro'd @macro_string
|
37
|
+
def each(&block)
|
38
|
+
demacro.split(//).each do |char| yield char end
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns string without MACRO_SYMBOL and DELIMITERS
|
42
|
+
def demacro
|
43
|
+
macro_string[2..-2]
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns nil if not, and match data for any parameter names found
|
47
|
+
def parameterized?
|
48
|
+
macro_string.match Regexp.identifier
|
49
|
+
end
|
50
|
+
end # class Macro
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# extending String with #parameterized? and #balanced_parens? only to assist macro.rb
|
2
|
+
class String
|
3
|
+
# returns whether or not contents include any Macro strings i.e. Parameter expressions
|
4
|
+
def parameterized?
|
5
|
+
self.include?('@(')
|
6
|
+
end
|
7
|
+
|
8
|
+
# returns whether number of open parentheses and close parentheses match
|
9
|
+
def balanced_parens?
|
10
|
+
self.match('(').size == self.match(')').size
|
11
|
+
end
|
12
|
+
end # class String
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'coerced'
|
2
|
+
|
3
|
+
module Symbolic
|
4
|
+
include AST
|
5
|
+
|
6
|
+
def %(var)
|
7
|
+
return 0 if self.object_id == var.object_id
|
8
|
+
return self % var if self.is_a?(Numeric) && var.is_a?(Numeric)
|
9
|
+
new_ast :%, var
|
10
|
+
end
|
11
|
+
|
12
|
+
def -@(var)
|
13
|
+
return -var unless var.is_a?(Node)
|
14
|
+
return -var.type if var.type.is_a?(Numeric)
|
15
|
+
reversed = var.type.reverse
|
16
|
+
reversed ? new_ast(reversed, *var.children.dup) : new_ast(:-@, [var])
|
17
|
+
end
|
18
|
+
|
19
|
+
def not(var)
|
20
|
+
return nil if var.is_a?(Symbolic::Variable)
|
21
|
+
return !var unless var.is_a?(Node)
|
22
|
+
inverted = var.type.inverse
|
23
|
+
inverted ? new_ast(inverted, *var.children.dup) : new_ast(:!, [var])
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'symbolic'
|
2
|
+
require_relative '../ruby_ext/fixnum'
|
3
|
+
require_relative '../ruby_ext/boolean'
|
4
|
+
|
5
|
+
module Symbolic
|
6
|
+
class Variable
|
7
|
+
include AST
|
8
|
+
|
9
|
+
def and(obj)
|
10
|
+
return self if obj.equal?(true) || obj.equal?(self)
|
11
|
+
return false if obj.equal?(false)
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def or(obj)
|
16
|
+
return self if obj.equal?(false) || obj.equal?(self)
|
17
|
+
return true if obj.equal?(true)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def <(obj)
|
22
|
+
eql?(obj) ? false : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def >(obj)
|
26
|
+
eql?(obj) ? false : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def !=(obj)
|
30
|
+
if obj.is_a?(Variable) || obj.is_a?(Numeric)
|
31
|
+
return object_id == obj.object_id ? false : nil
|
32
|
+
end
|
33
|
+
|
34
|
+
return !self if obj.equal?(true)
|
35
|
+
return self if obj.equal?(false)
|
36
|
+
return false if obj.equal?(self)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(obj)
|
41
|
+
if obj.is_a?(Variable) || obj.is_a?(Numeric)
|
42
|
+
result = object_id == obj.object_id ? true : nil
|
43
|
+
return result
|
44
|
+
end
|
45
|
+
|
46
|
+
return !self if obj.equal?(false)
|
47
|
+
return self if obj.equal?(true)
|
48
|
+
return true if obj.equal?(self)
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def >=(obj)
|
53
|
+
object_id == obj.object_id ? true : nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def <=(obj)
|
57
|
+
object_id == obj.object_id ? true : nil
|
58
|
+
end
|
59
|
+
end # class Variable
|
60
|
+
end # module Symbolic
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ReDuxml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Kong
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: duxml
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: symbolic
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.3'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- peter.kong@nxp.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/ast_ext/node.rb
|
49
|
+
- lib/re_dux/element/parameterization.rb
|
50
|
+
- lib/re_dux/element.rb
|
51
|
+
- lib/re_dux/evaluate/lexer.rb
|
52
|
+
- lib/re_dux/evaluate/operator.rb
|
53
|
+
- lib/re_dux/evaluate/parser.rb
|
54
|
+
- lib/re_dux/evaluate.rb
|
55
|
+
- lib/re_dux/parameters.rb
|
56
|
+
- lib/re_dux.rb
|
57
|
+
- lib/ruby_ext/boolean.rb
|
58
|
+
- lib/ruby_ext/fixnum.rb
|
59
|
+
- lib/ruby_ext/macro.rb
|
60
|
+
- lib/ruby_ext/regexp.rb
|
61
|
+
- lib/ruby_ext/string.rb
|
62
|
+
- lib/symbolic_ext/coerced.rb
|
63
|
+
- lib/symbolic_ext/symbolic.rb
|
64
|
+
- lib/symbolic_ext/variable.rb
|
65
|
+
homepage: http://www.github.com/re_duxml
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.9.3
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.8.11
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.0.14.1
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Reusable Dynamic Universal XML
|
89
|
+
test_files: []
|