rupkl 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.
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ module OperationCommon
6
+ def to_ruby(scopes)
7
+ evaluate(scopes).to_ruby(nil)
8
+ end
9
+
10
+ def to_string(scopes)
11
+ evaluate(scopes).to_string(nil)
12
+ end
13
+
14
+ def to_pkl_string(scopes)
15
+ evaluate(scopes).to_pkl_string(nil)
16
+ end
17
+
18
+ private
19
+
20
+ def s_op(scopes)
21
+ r = receiver.evaluate(scopes)
22
+ check_operator(r)
23
+
24
+ k = key.evaluate(scopes)
25
+ check_key_operand(r, k)
26
+
27
+ r.find_by_key(k) ||
28
+ begin
29
+ message = "cannot find key '#{k.to_pkl_string(scopes)}'"
30
+ raise EvaluationError.new(message, position)
31
+ end
32
+ end
33
+
34
+ def u_op(scopes)
35
+ o = operand.evaluate(scopes)
36
+ check_operator(o)
37
+
38
+ result =
39
+ if operator == :-
40
+ -o.value
41
+ else
42
+ !o.value
43
+ end
44
+ create_op_result(result)
45
+ end
46
+
47
+ def b_op(scopes)
48
+ l = l_operand.evaluate(scopes)
49
+ check_operator(l)
50
+ return l if short_circuit?(l)
51
+
52
+ r = r_operand.evaluate(scopes)
53
+ check_r_operand(l, r)
54
+
55
+ l, r = l.coerce(operator, r)
56
+ create_op_result(l.__send__(ruby_op, r))
57
+ end
58
+
59
+ def check_operator(operand)
60
+ undefined_operator?(operand) &&
61
+ begin
62
+ message =
63
+ "operator '#{operator}' is not defined for " \
64
+ "#{operand.class.basename} type"
65
+ raise EvaluationError.new(message, position)
66
+ end
67
+ end
68
+
69
+ def undefined_operator?(operand)
70
+ !operand.respond_to?(:undefined_operator?) ||
71
+ operand.undefined_operator?(operator)
72
+ end
73
+
74
+ def check_key_operand(receiver, key)
75
+ invalid_key_operand?(receiver, key) &&
76
+ begin
77
+ message =
78
+ "invalid key operand type #{key.class.basename} is given " \
79
+ "for operator '#{operator}'"
80
+ raise EvaluationError.new(message, position)
81
+ end
82
+ end
83
+
84
+ def invalid_key_operand?(receiver, key)
85
+ receiver.respond_to?(:invalid_key_operand?) &&
86
+ receiver.invalid_key_operand?(key)
87
+ end
88
+
89
+ def check_r_operand(l_operand, r_operand)
90
+ invalid_r_operand?(l_operand, r_operand) &&
91
+ begin
92
+ message =
93
+ "invalid operand type #{r_operand.class.basename} is given " \
94
+ "for operator '#{operator}'"
95
+ raise EvaluationError.new(message, position)
96
+ end
97
+ end
98
+
99
+ def invalid_r_operand?(l_operand, r_operand)
100
+ if l_operand.respond_to?(:invalid_r_operand?)
101
+ l_operand.invalid_r_operand?(r_operand)
102
+ else
103
+ !r_operand.is_a?(l_operand.class)
104
+ end
105
+ end
106
+
107
+ def short_circuit?(l_operand)
108
+ l_operand.respond_to?(:short_circuit?) &&
109
+ l_operand.short_circuit?(operator)
110
+ end
111
+
112
+ def ruby_op
113
+ { '&&': :&, '||': :|, '~/': :/ }.fetch(operator, operator)
114
+ end
115
+
116
+ def create_op_result(result)
117
+ case result
118
+ when ::Integer then Integer.new(result, position)
119
+ when ::Float then Float.new(result, position)
120
+ when ::String then String.new(result, nil, position)
121
+ when ::TrueClass, ::FalseClass then Boolean.new(result, position)
122
+ end
123
+ end
124
+ end
125
+
126
+ class SubscriptOperation
127
+ include OperationCommon
128
+
129
+ def initialize(receiver, key, position)
130
+ @receiver = receiver
131
+ @key = key
132
+ @position = position
133
+ end
134
+
135
+ attr_reader :receiver
136
+ attr_reader :key
137
+ attr_reader :position
138
+
139
+ def operator
140
+ :[]
141
+ end
142
+
143
+ def evaluate(scopes)
144
+ s_op(scopes)
145
+ end
146
+ end
147
+
148
+ class UnaryOperation
149
+ include OperationCommon
150
+
151
+ def initialize(operator, operand, position)
152
+ @operator = operator
153
+ @operand = operand
154
+ @position = position
155
+ end
156
+
157
+ attr_reader :operator
158
+ attr_reader :operand
159
+ attr_reader :position
160
+
161
+ def evaluate(scopes)
162
+ u_op(scopes)
163
+ end
164
+ end
165
+
166
+ class BinaryOperation
167
+ include OperationCommon
168
+
169
+ def initialize(operator, l_operand, r_operand, position)
170
+ @operator = operator
171
+ @l_operand = l_operand
172
+ @r_operand = r_operand
173
+ @position = position
174
+ end
175
+
176
+ attr_reader :operator
177
+ attr_reader :l_operand
178
+ attr_reader :r_operand
179
+ attr_reader :position
180
+
181
+ def evaluate(scopes)
182
+ b_op(scopes)
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class PklClassProperty
6
+ include PropertyEvaluator
7
+
8
+ def initialize(name, value, objects, position)
9
+ @name = name
10
+ @value = value
11
+ @objects = objects
12
+ @position = position
13
+ end
14
+
15
+ attr_reader :name
16
+ attr_reader :value
17
+ attr_reader :objects
18
+ attr_reader :position
19
+
20
+ def evaluate(scopes)
21
+ v = evaluate_property(value, objects, scopes)
22
+ self.class.new(name, v, nil, position)
23
+ end
24
+
25
+ def to_ruby(scopes)
26
+ [name.id, property_to_ruby(value, objects, scopes)]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class PklModule
6
+ include StructCommon
7
+
8
+ def initialize(items, position)
9
+ @position = position
10
+ items&.each do |item|
11
+ case item
12
+ when PklClassProperty then add_property(item)
13
+ end
14
+ end
15
+ end
16
+
17
+ attr_reader :properties
18
+ attr_reader :position
19
+
20
+ def evaluate(scopes)
21
+ push_scope(scopes) do |s|
22
+ self.class.new(evaluate_properties(s), position)
23
+ end
24
+ end
25
+
26
+ def to_ruby(scopes)
27
+ push_scope(scopes) do |s|
28
+ create_pkl_object(s, properties, nil, nil)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def add_property(property)
35
+ (@properties ||= []) << property
36
+ end
37
+
38
+ def evaluate_properties(scopes)
39
+ properties&.each_with_object([]) do |property, result|
40
+ property
41
+ .evaluate(scopes)
42
+ .then { add_hash_member(result, _1, :name) }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class String
6
+ include ValueCommon
7
+
8
+ def initialize(value, portions, position)
9
+ super(value, position)
10
+ @portions = portions
11
+ end
12
+
13
+ attr_reader :portions
14
+
15
+ def evaluate(scopes)
16
+ s = value || evaluate_portions(scopes) || ''
17
+ self.class.new(s, nil, position)
18
+ end
19
+
20
+ def to_pkl_string(scopes)
21
+ super(scopes)
22
+ .then { |s| escape(s) }
23
+ .then { |s| "\"#{s}\"" }
24
+ end
25
+
26
+ def undefined_operator?(operator)
27
+ [:[], :==, :'!=', :+].none?(operator)
28
+ end
29
+
30
+ def invalid_key_operand?(key)
31
+ !key.is_a?(Integer)
32
+ end
33
+
34
+ def find_by_key(key)
35
+ index = key.value
36
+ return nil unless (0...value.length).include?(index)
37
+
38
+ self.class.new(value[index], nil, portions)
39
+ end
40
+
41
+ private
42
+
43
+ def evaluate_portions(scopes)
44
+ portions
45
+ &.map { evaluate_portion(scopes, _1) }
46
+ &.join
47
+ end
48
+
49
+ def evaluate_portion(scopes, portion)
50
+ if portion.respond_to?(:to_string)
51
+ portion.to_string(scopes)
52
+ else
53
+ portion
54
+ end
55
+ end
56
+
57
+ def escape(string)
58
+ replace = {
59
+ "\t" => '\t', "\n" => '\n', "\r" => '\r',
60
+ '"' => '\"', '\\' => '\\\\'
61
+ }
62
+ string.gsub(/([\t\n\r"\\])/) { replace[_1] }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ module StructCommon
6
+ def to_string(_scopes)
7
+ "new #{self.class.basename} #{to_pkl_string(nil)}"
8
+ end
9
+
10
+ def coerce(_operator, r_operand)
11
+ [self, r_operand]
12
+ end
13
+
14
+ private
15
+
16
+ def push_scope(scopes)
17
+ yield([*scopes, self])
18
+ end
19
+
20
+ def add_hash_member(members, member, accessor)
21
+ duplicate_member?(members, member, accessor) &&
22
+ begin
23
+ message = 'duplicate definition of member'
24
+ raise EvaluationError.new(message, member.position)
25
+ end
26
+ members << member
27
+ end
28
+
29
+ def duplicate_member?(members, member, accessor)
30
+ members
31
+ .any? { _1.__send__(accessor) == member.__send__(accessor) }
32
+ end
33
+
34
+ def add_array_member(members, member)
35
+ members << member
36
+ end
37
+
38
+ def match_members?(lhs, rhs, match_order)
39
+ if !match_order && [lhs, rhs].all?(Array)
40
+ lhs.size == rhs.size &&
41
+ lhs.all? { rhs.include?(_1) } && rhs.all? { lhs.include?(_1) }
42
+ else
43
+ lhs == rhs
44
+ end
45
+ end
46
+
47
+ def merge_hash_members(lhs, rhs, accessor)
48
+ return nil unless lhs || rhs
49
+ return rhs unless lhs
50
+
51
+ rhs&.each do |r|
52
+ if (index = find_index(lhs, r, accessor))
53
+ lhs[index] = r
54
+ else
55
+ lhs << r
56
+ end
57
+ end
58
+
59
+ lhs
60
+ end
61
+
62
+ def find_index(lhs, rhs, accessor)
63
+ lhs.find_index { _1.__send__(accessor) == rhs.__send__(accessor) }
64
+ end
65
+
66
+ def merge_array_members(lhs, rhs)
67
+ return nil unless lhs || rhs
68
+ return rhs unless lhs
69
+ return lhs unless rhs
70
+
71
+ lhs.concat(rhs)
72
+ end
73
+
74
+ def create_pkl_object(scopes, properties, entries, elements)
75
+ RuPkl::PklObject.new(
76
+ to_ruby_hash_members(scopes, properties),
77
+ to_ruby_hash_members(scopes, entries),
78
+ to_ruby_array_members(scopes, elements)
79
+ )
80
+ end
81
+
82
+ def to_ruby_hash_members(scopes, members)
83
+ members
84
+ &.to_h { _1.to_ruby(scopes) }
85
+ end
86
+
87
+ def to_ruby_array_members(scopes, members)
88
+ members
89
+ &.map { _1.to_ruby(scopes) }
90
+ end
91
+
92
+ def to_pkl_string_object(*members)
93
+ return '{}' if members.empty?
94
+
95
+ members
96
+ .map { _1.to_pkl_string(nil) }
97
+ .join('; ')
98
+ .then { "{ #{_1} }" }
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ module ValueCommon
6
+ def initialize(value, position)
7
+ @value = value
8
+ @position = position
9
+ end
10
+
11
+ attr_reader :value
12
+ attr_reader :position
13
+
14
+ def to_ruby(scopes)
15
+ evaluate(scopes).value
16
+ end
17
+
18
+ def to_string(scopes)
19
+ to_ruby(scopes).to_s
20
+ end
21
+
22
+ def to_pkl_string(scopes)
23
+ to_string(scopes)
24
+ end
25
+
26
+ def ==(other)
27
+ other.instance_of?(self.class) && value == other.value
28
+ end
29
+
30
+ def coerce(_operator, r_operand)
31
+ [value, r_operand.value]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ define_parser do
6
+ rule(:unqualified_member_ref) do
7
+ id.as(:unqualified_member_ref)
8
+ end
9
+
10
+ rule(:primary) do
11
+ [
12
+ float_literal, integer_literal, boolean_literal, string_literal,
13
+ unqualified_member_ref, bracketed(expression)
14
+ ].inject(:|)
15
+ end
16
+
17
+ rule(:qualified_member_ref) do
18
+ (
19
+ primary.as(:receiver) >>
20
+ (ws? >> str('.') >> ws? >> id).repeat(1).as(:member)
21
+ ).as(:qualified_member_ref) | primary
22
+ end
23
+
24
+ rule(:subscript_operation) do
25
+ (
26
+ qualified_member_ref.as(:receiver) >>
27
+ (
28
+ pure_ws? >> bracketed(expression, '[', ']') >>
29
+ (ws? >> str('=')).absent?
30
+ ).repeat(1).as(:key)
31
+ ).as(:subscript_operation) | qualified_member_ref
32
+ end
33
+
34
+ rule(:unary_operation) do
35
+ (
36
+ (str(:-) | str(:!)).as(:unary_operator) >>
37
+ ws? >> subscript_operation.as(:operand)
38
+ ) | subscript_operation
39
+ end
40
+
41
+ rule(:binary_operation) do
42
+ expression = unary_operation
43
+ operators = binary_operators
44
+ reducer = proc { |l, o, r| { binary_operator: o, l_operand: l, r_operand: r } }
45
+ infix_expression(expression, *operators, &reducer) | unary_operation
46
+ end
47
+
48
+ rule(:expression) do
49
+ binary_operation
50
+ end
51
+
52
+ private
53
+
54
+ def binary_operators
55
+ operators = {
56
+ '||': 1, '&&': 2,
57
+ '==': 3, '!=': 3,
58
+ '<': 4, '>': 4, '<=': 4, '>=': 4,
59
+ '+': 5, '-': 5,
60
+ '*': 6, '/': 6, '~/': 6, '%': 6,
61
+ '**': 7
62
+ }
63
+ operators
64
+ .sort_by { |op, _| op.length }.reverse
65
+ .map { |op, priority| binary_operator_element(op, priority) }
66
+ end
67
+
68
+ def binary_operator_element(operator, priority)
69
+ atom =
70
+ if operator == :-
71
+ pure_ws? >> str(operator) >> ws?
72
+ else
73
+ ws? >> str(operator) >> ws?
74
+ end
75
+ if operator == :**
76
+ [atom, priority, :right]
77
+ else
78
+ [atom, priority, :left]
79
+ end
80
+ end
81
+ end
82
+
83
+ define_transform do
84
+ rule(unqualified_member_ref: simple(:member)) do
85
+ Node::MemberReference.new(nil, member, member.position)
86
+ end
87
+
88
+ rule(
89
+ qualified_member_ref:
90
+ { receiver: simple(:receiver), member: sequence(:member) }
91
+ ) do
92
+ member.inject(receiver) do |r, m|
93
+ Node::MemberReference.new(r, m, r.position)
94
+ end
95
+ end
96
+
97
+ rule(
98
+ subscript_operation:
99
+ { receiver: simple(:receiver), key: sequence(:key) }
100
+ ) do
101
+ key.inject(receiver) do |r, k|
102
+ Node::SubscriptOperation.new(r, k, r.position)
103
+ end
104
+ end
105
+
106
+ rule(unary_operator: simple(:operator), operand: simple(:operand)) do
107
+ Node::UnaryOperation.new(operator.to_sym, operand, node_position(operator))
108
+ end
109
+
110
+ rule(
111
+ binary_operator: simple(:operator),
112
+ l_operand: simple(:l_operand), r_operand: simple(:r_operand)
113
+ ) do
114
+ Node::BinaryOperation.new(
115
+ operator.to_sym, l_operand, r_operand, l_operand.position
116
+ )
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ KEYWORDS = [
6
+ 'abstract', 'amends', 'as', 'class', 'const', 'else', 'extends',
7
+ 'external', 'false', 'fixed', 'for', 'function', 'hidden', 'if',
8
+ 'import', 'import*', 'in', 'is', 'let', 'local', 'module', 'new',
9
+ 'nothing', 'null', 'open', 'out', 'outer', 'read', 'read*', 'read?',
10
+ 'super', 'this', 'throw', 'trace', 'true', 'typealias', 'unknown', 'when'
11
+ ].freeze
12
+
13
+ RESERVED_KEYWORDS = [
14
+ 'protected', 'override', 'record', 'delete', 'case', 'switch', 'vararg'
15
+ ].freeze
16
+
17
+ define_parser do
18
+ [*KEYWORDS, *RESERVED_KEYWORDS].each do |kw|
19
+ rule(:"kw_#{kw}") do
20
+ str(kw) >> match('\\w').absent?
21
+ end
22
+ end
23
+
24
+ rule(:regular_identifier) do
25
+ (str('_') | str('$') | match('\p{XID_Start}')) >> match('\p{XID_Continue}').repeat
26
+ end
27
+
28
+ rule(:quoted_identifier) do
29
+ str('`') >> (str('`').absent? >> any).repeat(1) >> str('`')
30
+ end
31
+
32
+ rule(:id) do
33
+ regular_identifier.as(:regular_id) | quoted_identifier.as(:quoted_id)
34
+ end
35
+ end
36
+
37
+ define_transform do
38
+ rule(regular_id: simple(:id)) do
39
+ if keyword?(id)
40
+ message = "keyword '#{id}' is not allowed for identifier"
41
+ parse_error(message, node_position(id))
42
+ end
43
+
44
+ Node::Identifier.new(id.to_sym, node_position(id))
45
+ end
46
+
47
+ rule(quoted_id: simple(:id)) do
48
+ Node::Identifier.new(id.to_s[1..-2].to_sym, node_position(id))
49
+ end
50
+
51
+ private
52
+
53
+ def keyword?(id)
54
+ KEYWORDS.any?(id) || RESERVED_KEYWORDS.any?(id)
55
+ end
56
+ end
57
+ end
58
+ end