rpl 0.4.0 → 0.7.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 +4 -4
- data/bin/rpl +11 -9
- data/lib/rpl/interpreter.rb +25 -204
- data/lib/rpl/parser.rb +87 -0
- data/lib/rpl/types/boolean.rb +32 -0
- data/lib/rpl/types/list.rb +34 -0
- data/lib/rpl/types/name.rb +36 -0
- data/lib/rpl/types/numeric.rb +113 -0
- data/lib/rpl/types/program.rb +27 -0
- data/lib/rpl/types/string.rb +27 -0
- data/lib/rpl/types.rb +19 -0
- data/lib/rpl/{core → words}/branch.rb +14 -16
- data/lib/rpl/{core → words}/filesystem.rb +10 -11
- data/lib/rpl/words/general.rb +112 -0
- data/lib/rpl/{core → words}/logarithm.rb +4 -4
- data/lib/rpl/{core → words}/mode.rb +7 -6
- data/lib/rpl/{core → words}/operations.rb +144 -134
- data/lib/rpl/{core → words}/program.rb +5 -3
- data/lib/rpl/{core → words}/stack.rb +22 -28
- data/lib/rpl/{core → words}/store.rb +18 -45
- data/lib/rpl/words/string-list.rb +133 -0
- data/lib/rpl/{core → words}/test.rb +19 -29
- data/lib/rpl/{core → words}/time-date.rb +6 -8
- data/lib/rpl/{core → words}/trig.rb +20 -36
- data/lib/rpl/words.rb +15 -0
- data/lib/rpl.rb +17 -29
- metadata +24 -16
- data/lib/rpl/core/general.rb +0 -57
- data/lib/rpl/core/list.rb +0 -33
- data/lib/rpl/core/string.rb +0 -114
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cde98587c0d2ec81f1a6a6d9918b8f7b3ffa40c26959a58d97e7f6e6aef6ef3
|
4
|
+
data.tar.gz: a8aef5c02a2956265a862b3a2530cd972639c60055387adb2dd74c3785a71dc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fc702e52d8c6d8089bb45cc251964056be1758aea579f3a798afcf404468a7ac754de90f2bb5eacc67c5d1f0489c65b3eb6ca0b3d38c3c0f0f8be54961ad44f
|
7
|
+
data.tar.gz: 83346cce2d900ca261932ceed751e268abc4cab1c5d37da2b198709a1823647d8fefd375f55adbc67b92ee2b3ed3d7c4e28c4fde30c9b8e1f213f275bfdffb7f
|
data/bin/rpl
CHANGED
@@ -10,6 +10,7 @@ require 'rpl'
|
|
10
10
|
class RplRepl
|
11
11
|
def initialize( interpreter )
|
12
12
|
interpreter ||= Rpl.new
|
13
|
+
|
13
14
|
@interpreter = interpreter
|
14
15
|
end
|
15
16
|
|
@@ -33,28 +34,22 @@ class RplRepl
|
|
33
34
|
begin
|
34
35
|
@interpreter.run( input )
|
35
36
|
rescue ArgumentError => e
|
36
|
-
|
37
|
+
pp e
|
37
38
|
end
|
38
39
|
|
39
40
|
print_stack
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
|
-
def format_element( elt )
|
44
|
-
@interpreter.stringify( elt )
|
45
|
-
end
|
46
|
-
|
47
44
|
def print_stack
|
48
45
|
stack_size = @interpreter.stack.size
|
49
46
|
|
50
47
|
@interpreter.stack.each_with_index do |elt, i|
|
51
|
-
puts "#{stack_size - i}: #{
|
48
|
+
puts "#{stack_size - i}: #{elt}"
|
52
49
|
end
|
53
50
|
end
|
54
51
|
end
|
55
52
|
|
56
|
-
interpreter = Rpl.new
|
57
|
-
|
58
53
|
options = { run_REPL: ARGV.empty?,
|
59
54
|
files: [],
|
60
55
|
programs: [] }
|
@@ -73,14 +68,21 @@ OptionParser.new do |opts|
|
|
73
68
|
end
|
74
69
|
end.parse!
|
75
70
|
|
71
|
+
# Instantiate interpreter
|
72
|
+
interpreter = Rpl.new
|
73
|
+
|
74
|
+
# first run provided files if any
|
76
75
|
options[:files].each do |filename|
|
77
76
|
interpreter.run "\"#{filename}\" feval"
|
78
77
|
end
|
79
78
|
|
79
|
+
# second run provided code if any
|
80
80
|
options[:programs].each do |program|
|
81
81
|
interpreter.run program
|
82
82
|
end
|
83
83
|
|
84
|
+
# third launch REPL if (explicitely or implicitely) asked
|
84
85
|
RplRepl.new( interpreter ).run if options[:run_REPL]
|
85
86
|
|
86
|
-
|
87
|
+
# last print resulting stack on exit (formatted so that it can be fed back later to interpreter)
|
88
|
+
pp interpreter.export_stack
|
data/lib/rpl/interpreter.rb
CHANGED
@@ -5,9 +5,11 @@ require 'bigdecimal/math'
|
|
5
5
|
require 'bigdecimal/util'
|
6
6
|
|
7
7
|
require 'rpl/dictionary'
|
8
|
+
require 'rpl/types'
|
8
9
|
|
9
10
|
class Interpreter
|
10
11
|
include BigMath
|
12
|
+
include Types
|
11
13
|
|
12
14
|
attr_reader :stack,
|
13
15
|
:dictionary,
|
@@ -16,173 +18,37 @@ class Interpreter
|
|
16
18
|
attr_accessor :precision
|
17
19
|
|
18
20
|
def initialize( stack = [], dictionary = Rpl::Lang::Dictionary.new )
|
19
|
-
@version = 0.
|
20
|
-
|
21
|
-
@precision = default_precision
|
21
|
+
@version = 0.7
|
22
22
|
|
23
23
|
@dictionary = dictionary
|
24
24
|
@stack = stack
|
25
|
-
|
26
|
-
populate_dictionary if @dictionary.words.empty?
|
27
|
-
end
|
28
|
-
|
29
|
-
def default_precision
|
30
|
-
12
|
31
|
-
end
|
32
|
-
|
33
|
-
def parse( input )
|
34
|
-
is_numeric = lambda do |elt|
|
35
|
-
begin
|
36
|
-
!Float(elt).nil?
|
37
|
-
rescue ArgumentError
|
38
|
-
begin
|
39
|
-
!Integer(elt).nil?
|
40
|
-
rescue ArgumentError
|
41
|
-
false
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
unless input.index("\n").nil?
|
47
|
-
input = input
|
48
|
-
.split("\n")
|
49
|
-
.map do |line|
|
50
|
-
comment_begin_index = line.index('#')
|
51
|
-
|
52
|
-
if comment_begin_index.nil?
|
53
|
-
line
|
54
|
-
elsif comment_begin_index == 0
|
55
|
-
''
|
56
|
-
else
|
57
|
-
line[0..(comment_begin_index - 1)]
|
58
|
-
end
|
59
|
-
end
|
60
|
-
.join(' ')
|
61
|
-
end
|
62
|
-
|
63
|
-
splitted_input = input.split(' ')
|
64
|
-
|
65
|
-
# 2-passes:
|
66
|
-
# 1. regroup strings and programs
|
67
|
-
opened_programs = 0
|
68
|
-
closed_programs = 0
|
69
|
-
opened_lists = 0
|
70
|
-
closed_lists = 0
|
71
|
-
string_delimiters = 0
|
72
|
-
name_delimiters = 0
|
73
|
-
regrouping = false
|
74
|
-
|
75
|
-
regrouped_input = []
|
76
|
-
splitted_input.each do |elt|
|
77
|
-
if elt[0] == '«'
|
78
|
-
opened_programs += 1
|
79
|
-
elt.gsub!( '«', '« ') if elt.length > 1 && elt[1] != ' '
|
80
|
-
elsif elt[0] == '{'
|
81
|
-
opened_lists += 1
|
82
|
-
elt.gsub!( '{', '{ ') if elt.length > 1 && elt[1] != ' '
|
83
|
-
elsif elt[0] == '"' && elt.length > 1
|
84
|
-
string_delimiters += 1
|
85
|
-
elsif elt[0] == "'" && elt.length > 1
|
86
|
-
name_delimiters += 1
|
87
|
-
end
|
88
|
-
|
89
|
-
elt = "#{regrouped_input.pop} #{elt}".strip if regrouping
|
90
|
-
|
91
|
-
regrouped_input << elt
|
92
|
-
|
93
|
-
case elt[-1]
|
94
|
-
when '»'
|
95
|
-
closed_programs += 1
|
96
|
-
elt.gsub!( '»', ' »') if elt.length > 1 && elt[-2] != ' '
|
97
|
-
when '}'
|
98
|
-
closed_lists += 1
|
99
|
-
elt.gsub!( '}', ' }') if elt.length > 1 && elt[-2] != ' '
|
100
|
-
when '"'
|
101
|
-
string_delimiters += 1
|
102
|
-
when "'"
|
103
|
-
name_delimiters += 1
|
104
|
-
end
|
105
|
-
|
106
|
-
regrouping = string_delimiters.odd? || name_delimiters.odd? || (opened_programs > closed_programs ) || (opened_lists > closed_lists )
|
107
|
-
end
|
108
|
-
|
109
|
-
# 2. parse
|
110
|
-
# TODO: parse ∞, <NaN> as numerics
|
111
|
-
parsed_tree = []
|
112
|
-
regrouped_input.each do |elt|
|
113
|
-
parsed_entry = { value: elt }
|
114
|
-
|
115
|
-
parsed_entry[:type] = case elt[0]
|
116
|
-
when '«'
|
117
|
-
:program
|
118
|
-
when '{'
|
119
|
-
:list
|
120
|
-
when '"'
|
121
|
-
:string
|
122
|
-
when "'"
|
123
|
-
:name # TODO: check for forbidden space
|
124
|
-
else
|
125
|
-
if is_numeric.call( elt )
|
126
|
-
:numeric
|
127
|
-
else
|
128
|
-
:word
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
if %I[string name].include?( parsed_entry[:type] )
|
133
|
-
parsed_entry[:value] = parsed_entry[:value][1..-2]
|
134
|
-
elsif %I[program].include?( parsed_entry[:type] )
|
135
|
-
parsed_entry[:value] = parsed_entry[:value][2..-3]
|
136
|
-
elsif %I[list].include?( parsed_entry[:type] )
|
137
|
-
parsed_entry[:value] = parse( parsed_entry[:value][2..-3] )
|
138
|
-
elsif %I[numeric].include?( parsed_entry[:type] )
|
139
|
-
underscore_position = parsed_entry[:value].index('_')
|
140
|
-
|
141
|
-
if parsed_entry[:value][0] == '0' && ( ['b', 'o', 'x'].include?( parsed_entry[:value][1] ) || !underscore_position.nil? )
|
142
|
-
if parsed_entry[:value][1] == 'x'
|
143
|
-
parsed_entry[:base] = 16
|
144
|
-
elsif parsed_entry[:value][1] == 'b'
|
145
|
-
parsed_entry[:base] = 2
|
146
|
-
elsif parsed_entry[:value][1] == 'o'
|
147
|
-
parsed_entry[:base] = 8
|
148
|
-
parsed_entry[:value] = parsed_entry[:value][2..]
|
149
|
-
elsif !underscore_position.nil?
|
150
|
-
parsed_entry[:base] = parsed_entry[:value][1..(underscore_position - 1)].to_i
|
151
|
-
parsed_entry[:value] = parsed_entry[:value][(underscore_position + 1)..]
|
152
|
-
end
|
153
|
-
else
|
154
|
-
parsed_entry[:base] = 10
|
155
|
-
end
|
156
|
-
|
157
|
-
parsed_entry[:value] = parsed_entry[:value].to_i( parsed_entry[:base] ) unless parsed_entry[:base] == 10
|
158
|
-
parsed_entry[:value] = BigDecimal( parsed_entry[:value], @precision )
|
159
|
-
end
|
160
|
-
|
161
|
-
parsed_tree << parsed_entry
|
162
|
-
end
|
163
|
-
|
164
|
-
parsed_tree
|
165
25
|
end
|
166
26
|
|
167
27
|
def run( input )
|
168
28
|
@dictionary.add_local_vars_layer
|
169
29
|
|
170
|
-
parse( input.to_s ).each do |elt|
|
171
|
-
|
172
|
-
|
173
|
-
break if %w[break quit exit].include?( elt[:value] )
|
174
|
-
|
175
|
-
command = @dictionary.lookup( elt[:value] )
|
176
|
-
|
177
|
-
if command.nil?
|
178
|
-
# if there isn't a command by that name then it's a name
|
179
|
-
elt[:type] = :name
|
30
|
+
Parser.parse( input.to_s ).each do |elt|
|
31
|
+
if elt.instance_of?( RplName )
|
32
|
+
break if %w[break quit exit].include?( elt.value )
|
180
33
|
|
34
|
+
if elt.not_to_evaluate
|
181
35
|
@stack << elt
|
182
|
-
elsif command.is_a?( Proc )
|
183
|
-
command.call
|
184
36
|
else
|
185
|
-
|
37
|
+
command = @dictionary.lookup( elt.value )
|
38
|
+
if command.nil?
|
39
|
+
# if there isn't a command by that name then it's a name
|
40
|
+
# elt[:type] = :name
|
41
|
+
|
42
|
+
@stack << elt
|
43
|
+
elsif command.is_a?( Proc )
|
44
|
+
command.call
|
45
|
+
else
|
46
|
+
if command.instance_of?( RplProgram )
|
47
|
+
run( command.value )
|
48
|
+
else
|
49
|
+
@stack << command
|
50
|
+
end
|
51
|
+
end
|
186
52
|
end
|
187
53
|
else
|
188
54
|
@stack << elt
|
@@ -201,7 +67,7 @@ class Interpreter
|
|
201
67
|
needs.each_with_index do |need, index|
|
202
68
|
stack_index = (index + 1) * -1
|
203
69
|
|
204
|
-
raise ArgumentError, "Type Error, needed #{need} got #{@stack[stack_index]}" unless need == :any || need.include?( @stack[stack_index]
|
70
|
+
raise ArgumentError, "Type Error, needed #{need} got #{@stack[stack_index]}" unless need == :any || need.include?( @stack[stack_index].class )
|
205
71
|
end
|
206
72
|
|
207
73
|
args = []
|
@@ -212,52 +78,7 @@ class Interpreter
|
|
212
78
|
args
|
213
79
|
end
|
214
80
|
|
215
|
-
def
|
216
|
-
|
217
|
-
when :numeric
|
218
|
-
prefix = case elt[:base]
|
219
|
-
when 2
|
220
|
-
'0b'
|
221
|
-
when 8
|
222
|
-
'0o'
|
223
|
-
when 10
|
224
|
-
''
|
225
|
-
when 16
|
226
|
-
'0x'
|
227
|
-
else
|
228
|
-
"0#{elt[:base]}_"
|
229
|
-
end
|
230
|
-
|
231
|
-
if elt[:value].infinite?
|
232
|
-
suffix = elt[:value].infinite?.positive? ? '∞' : '-∞'
|
233
|
-
elsif elt[:value].nan?
|
234
|
-
suffix = '<NaN>'
|
235
|
-
else
|
236
|
-
suffix = if elt[:value].to_i == elt[:value]
|
237
|
-
elt[:value].to_i
|
238
|
-
else
|
239
|
-
elt[:value].to_s('F')
|
240
|
-
end
|
241
|
-
suffix = elt[:value].to_s( elt[:base] ) unless elt[:base] == 10
|
242
|
-
end
|
243
|
-
|
244
|
-
"#{prefix}#{suffix}"
|
245
|
-
when :list
|
246
|
-
"{ #{elt[:value].map { |e| stringify( e ) }.join(' ')} }"
|
247
|
-
when :program
|
248
|
-
"« #{elt[:value]} »"
|
249
|
-
when :string
|
250
|
-
"\"#{elt[:value]}\""
|
251
|
-
when :name
|
252
|
-
"'#{elt[:value]}'"
|
253
|
-
else
|
254
|
-
elt[:value]
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
def infer_resulting_base( numerics )
|
259
|
-
10 if numerics.length.zero?
|
260
|
-
|
261
|
-
numerics.last[:base]
|
81
|
+
def export_stack
|
82
|
+
@stack.map(&:to_s).join(' ')
|
262
83
|
end
|
263
84
|
end
|
data/lib/rpl/parser.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
include Types
|
5
|
+
|
6
|
+
def self.parse( input )
|
7
|
+
unless input.index("\n").nil?
|
8
|
+
input = input.split("\n")
|
9
|
+
.map do |line|
|
10
|
+
comment_begin_index = line.index('#')
|
11
|
+
|
12
|
+
case comment_begin_index
|
13
|
+
when nil
|
14
|
+
line
|
15
|
+
when 0
|
16
|
+
''
|
17
|
+
else
|
18
|
+
line[0..(comment_begin_index - 1)]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
.join(' ')
|
22
|
+
end
|
23
|
+
|
24
|
+
splitted_input = input.split(' ')
|
25
|
+
|
26
|
+
# 2-passes:
|
27
|
+
# 1. regroup strings and programs
|
28
|
+
opened_programs = 0
|
29
|
+
closed_programs = 0
|
30
|
+
opened_lists = 0
|
31
|
+
closed_lists = 0
|
32
|
+
string_delimiters = 0
|
33
|
+
name_delimiters = 0
|
34
|
+
regrouping = false
|
35
|
+
|
36
|
+
regrouped_input = []
|
37
|
+
splitted_input.each do |elt|
|
38
|
+
if elt[0] == '«'
|
39
|
+
opened_programs += 1
|
40
|
+
elt.gsub!( '«', '« ') if elt.length > 1 && elt[1] != ' '
|
41
|
+
elsif elt[0] == '{'
|
42
|
+
opened_lists += 1
|
43
|
+
elt.gsub!( '{', '{ ') if elt.length > 1 && elt[1] != ' '
|
44
|
+
elsif elt[0] == '"' && elt.length > 1
|
45
|
+
string_delimiters += 1
|
46
|
+
elsif elt[0] == "'" && elt.length > 1
|
47
|
+
name_delimiters += 1
|
48
|
+
end
|
49
|
+
|
50
|
+
elt = "#{regrouped_input.pop} #{elt}".strip if regrouping
|
51
|
+
|
52
|
+
regrouped_input << elt
|
53
|
+
|
54
|
+
case elt[-1]
|
55
|
+
when '»'
|
56
|
+
closed_programs += 1
|
57
|
+
elt.gsub!( '»', ' »') if elt.length > 1 && elt[-2] != ' '
|
58
|
+
when '}'
|
59
|
+
closed_lists += 1
|
60
|
+
elt.gsub!( '}', ' }') if elt.length > 1 && elt[-2] != ' '
|
61
|
+
when '"'
|
62
|
+
string_delimiters += 1
|
63
|
+
when "'"
|
64
|
+
name_delimiters += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
regrouping = string_delimiters.odd? || name_delimiters.odd? || (opened_programs > closed_programs ) || (opened_lists > closed_lists )
|
68
|
+
end
|
69
|
+
|
70
|
+
# 2. parse
|
71
|
+
regrouped_input.map do |element|
|
72
|
+
if RplBoolean.can_parse?( element )
|
73
|
+
Types.new_object( RplBoolean, element )
|
74
|
+
elsif RplNumeric.can_parse?( element )
|
75
|
+
Types.new_object( RplNumeric, element )
|
76
|
+
elsif RplList.can_parse?( element )
|
77
|
+
Types.new_object( RplList, element )
|
78
|
+
elsif RplString.can_parse?( element )
|
79
|
+
Types.new_object( RplString, element )
|
80
|
+
elsif RplProgram.can_parse?( element )
|
81
|
+
Types.new_object( RplProgram, element )
|
82
|
+
elsif RplName.can_parse?( element )
|
83
|
+
Types.new_object( RplName, element )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Types
|
4
|
+
class RplBoolean
|
5
|
+
attr_accessor :value
|
6
|
+
|
7
|
+
def initialize( value )
|
8
|
+
raise RplTypeError unless self.class.can_parse?( value )
|
9
|
+
|
10
|
+
@value = if value.is_a?( String )
|
11
|
+
value.downcase == 'true'
|
12
|
+
else
|
13
|
+
value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@value.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.can_parse?( value )
|
22
|
+
return %w[true false].include?( value.downcase ) if value.is_a?( String )
|
23
|
+
|
24
|
+
%w[TrueClass FalseClass].include?( value.class.to_s )
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==( other )
|
28
|
+
other.class == RplBoolean and
|
29
|
+
other.value == value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rpl/parser'
|
4
|
+
|
5
|
+
module Types
|
6
|
+
class RplList
|
7
|
+
attr_accessor :value
|
8
|
+
|
9
|
+
def initialize( value )
|
10
|
+
raise RplTypeError unless self.class.can_parse?( value )
|
11
|
+
|
12
|
+
@value = if value.instance_of?( Array )
|
13
|
+
value
|
14
|
+
else
|
15
|
+
# we systematicalyl trim enclosing { }
|
16
|
+
Parser.parse( value[2..-3] )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"{ #{@value.map(&:to_s).join(' ')} }"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.can_parse?( value )
|
25
|
+
value.instance_of?( Array ) or
|
26
|
+
value[0..1] == '{ ' && value[-2..-1] == ' }'
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==( other )
|
30
|
+
other.class == RplList and
|
31
|
+
other.value == value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Types
|
4
|
+
class RplName
|
5
|
+
attr_accessor :value,
|
6
|
+
:not_to_evaluate
|
7
|
+
|
8
|
+
def initialize( value )
|
9
|
+
raise RplTypeError unless self.class.can_parse?( value )
|
10
|
+
|
11
|
+
# we systematicalyl trim enclosing '
|
12
|
+
@not_to_evaluate = value[0] == "'"
|
13
|
+
@value = if value[0] == "'" && value[-1] == "'"
|
14
|
+
value[1..-2]
|
15
|
+
else
|
16
|
+
value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"'#{@value}'"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.can_parse?( value )
|
25
|
+
( value.length > 2 and value[0] == "'" and value[-1] == "'" ) or
|
26
|
+
( value != "''" and !value.match?(/^[0-9']+$/) and
|
27
|
+
# it's not any other type
|
28
|
+
[RplBoolean, RplList, RplProgram, RplString, RplNumeric].reduce( true ) { |memo, type_class| memo && !type_class.can_parse?( value ) } )
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==( other )
|
32
|
+
other.class == RplName and
|
33
|
+
other.value == value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module Types
|
6
|
+
class RplNumeric
|
7
|
+
attr_accessor :value,
|
8
|
+
:base
|
9
|
+
|
10
|
+
@@precision = 12 # rubocop:disable Style/ClassVars
|
11
|
+
|
12
|
+
def self.default_precision
|
13
|
+
@@precision = 12 # rubocop:disable Style/ClassVars
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.precision
|
17
|
+
@@precision
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.precision=( precision )
|
21
|
+
@@precision = precision.to_i # rubocop:disable Style/ClassVars
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize( value, base = 10 )
|
25
|
+
raise RplTypeError unless self.class.can_parse?( value )
|
26
|
+
|
27
|
+
@base = base
|
28
|
+
|
29
|
+
case value
|
30
|
+
when RplNumeric
|
31
|
+
@value = value.value
|
32
|
+
@base = value.base
|
33
|
+
when BigDecimal
|
34
|
+
@value = value
|
35
|
+
when Integer
|
36
|
+
@value = BigDecimal( value, @@precision )
|
37
|
+
when Float
|
38
|
+
@value = BigDecimal( value, @@precision )
|
39
|
+
when String
|
40
|
+
begin
|
41
|
+
@value = BigDecimal( value )
|
42
|
+
rescue ArgumentError
|
43
|
+
case value
|
44
|
+
when /^0x[0-9a-f]+$/
|
45
|
+
@base = 16
|
46
|
+
@value = /^0x(?<value>[0-9a-f]+)$/.match( value )['value'].to_i( @base )
|
47
|
+
when /^0o[0-7]+$/
|
48
|
+
@base = 8
|
49
|
+
@value = /^0o(?<value>[0-7]+)$/.match( value )['value'].to_i( @base )
|
50
|
+
when /^0b[0-1]+$/
|
51
|
+
@base = 2
|
52
|
+
@value = /^0b(?<value>[0-1]+)$/.match( value )['value'].to_i( @base )
|
53
|
+
when '∞'
|
54
|
+
@value = BigDecimal('+Infinity')
|
55
|
+
when '-∞'
|
56
|
+
@value = BigDecimal('-Infinity')
|
57
|
+
else
|
58
|
+
matches = /(?<base>[0-9]+)b(?<value>[0-9a-z]+)/.match( value )
|
59
|
+
@base = matches['base'].to_i
|
60
|
+
@value = matches['value'].to_i( @base )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
prefix = case @base
|
68
|
+
when 2
|
69
|
+
'0b'
|
70
|
+
when 8
|
71
|
+
'0o'
|
72
|
+
when 10
|
73
|
+
''
|
74
|
+
when 16
|
75
|
+
'0x'
|
76
|
+
else
|
77
|
+
"0#{@base}_"
|
78
|
+
end
|
79
|
+
|
80
|
+
if @value.infinite?
|
81
|
+
suffix = @value.infinite?.positive? ? '∞' : '-∞'
|
82
|
+
elsif @value.nan?
|
83
|
+
suffix = '<NaN>'
|
84
|
+
else
|
85
|
+
suffix = if @value.to_i == @value
|
86
|
+
@value.to_i
|
87
|
+
else
|
88
|
+
@value.to_s('F')
|
89
|
+
end
|
90
|
+
suffix = @value.to_s( @base ) unless @base == 10
|
91
|
+
end
|
92
|
+
|
93
|
+
"#{prefix}#{suffix}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.can_parse?( value )
|
97
|
+
[RplNumeric, BigDecimal, Integer, Float].include?( value.class ) or
|
98
|
+
( value.is_a?( String ) and ( value.match?(/^-?[0-9]*\.?[0-9]+$/) or
|
99
|
+
value.match?(/^-?∞$/) or
|
100
|
+
value.match?(/^0b[0-1]+$/) or
|
101
|
+
value.match?(/^0o[0-7]+$/) or
|
102
|
+
value.match?(/^0x[0-9a-f]+$/) or
|
103
|
+
( value.match?(/^[0-9]+b[0-9a-z]+$/) and
|
104
|
+
value.split('_').first.to_i <= 36 ) ) )
|
105
|
+
end
|
106
|
+
|
107
|
+
def ==( other )
|
108
|
+
other.class == RplNumeric and
|
109
|
+
other.base == base and
|
110
|
+
other.value == value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Types
|
4
|
+
class RplProgram
|
5
|
+
attr_accessor :value
|
6
|
+
|
7
|
+
def initialize( value )
|
8
|
+
raise RplTypeError unless self.class.can_parse?( value )
|
9
|
+
|
10
|
+
# we systematicalyl trim enclosing « »
|
11
|
+
@value = value[2..-3] # TODO: parse each element ?
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"« #{@value} »"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.can_parse?( value )
|
19
|
+
value.length > 4 && value[0..1] == '« ' && value[-2..-1] == ' »' && !value[2..-3].strip.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==( other )
|
23
|
+
other.class == RplProgram and
|
24
|
+
other.value == value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Types
|
4
|
+
class RplString
|
5
|
+
attr_accessor :value
|
6
|
+
|
7
|
+
def initialize( value )
|
8
|
+
raise RplTypeError unless self.class.can_parse?( value )
|
9
|
+
|
10
|
+
# we systematicalyl trim enclosing "
|
11
|
+
@value = value[1..-2]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"\"#{@value}\""
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.can_parse?( value )
|
19
|
+
value[0] == '"' && value[-1] == '"'
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==( other )
|
23
|
+
other.class == RplString and
|
24
|
+
other.value == value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|