rpl 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,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RplLang
4
+ module Core
5
+ module String
6
+ def populate_dictionary
7
+ super
8
+
9
+ @dictionary.add_word( ['→str', '->str'],
10
+ 'String',
11
+ '( a -- s ) convert element to string',
12
+ proc do
13
+ args = stack_extract( [:any] )
14
+
15
+ @stack << { type: :string,
16
+ value: stringify( args[0] ) }
17
+ end )
18
+
19
+ @dictionary.add_word( ['str→', 'str->'],
20
+ 'String',
21
+ '( s -- a ) convert string to element',
22
+ proc do
23
+ args = stack_extract( [%i[string]] )
24
+
25
+ @stack += parse( args[0][:value] )
26
+ end )
27
+
28
+ @dictionary.add_word( ['chr'],
29
+ 'String',
30
+ '( n -- c ) convert ASCII character code in stack level 1 into a string',
31
+ proc do
32
+ args = stack_extract( [%i[numeric]] )
33
+
34
+ @stack << { type: :string,
35
+ value: args[0][:value].to_i.chr }
36
+ end )
37
+
38
+ @dictionary.add_word( ['num'],
39
+ 'String',
40
+ '( s -- n ) return ASCII code of the first character of the string in stack level 1 as a real number',
41
+ proc do
42
+ args = stack_extract( [%i[string]] )
43
+
44
+ @stack << { type: :numeric,
45
+ base: 10,
46
+ value: args[0][:value].ord }
47
+ end )
48
+
49
+ @dictionary.add_word( ['size'],
50
+ 'String',
51
+ '( s -- n ) return the length of the string',
52
+ proc do
53
+ args = stack_extract( [%i[string]] )
54
+
55
+ @stack << { type: :numeric,
56
+ base: 10,
57
+ value: args[0][:value].length }
58
+ end )
59
+
60
+ @dictionary.add_word( ['pos'],
61
+ 'String',
62
+ '( s s -- n ) search for the string in level 1 within the string in level 2',
63
+ proc do
64
+ args = stack_extract( [%i[string], %i[string]] )
65
+
66
+ @stack << { type: :numeric,
67
+ base: 10,
68
+ value: args[1][:value].index( args[0][:value] ) }
69
+ end )
70
+
71
+ @dictionary.add_word( ['sub'],
72
+ 'String',
73
+ '( s n n -- s ) return a substring of the string in level 3',
74
+ proc do
75
+ args = stack_extract( [%i[numeric], %i[numeric], %i[string]] )
76
+
77
+ @stack << { type: :string,
78
+ value: args[2][:value][ (args[1][:value] - 1)..(args[0][:value] - 1) ] }
79
+ end )
80
+
81
+ @dictionary.add_word( ['rev'],
82
+ 'String',
83
+ '( s -- s ) reverse string',
84
+ proc do
85
+ args = stack_extract( [%i[string list]] )
86
+
87
+ result = args[0]
88
+
89
+ case args[0][:type]
90
+ when :string
91
+ result = { type: :string,
92
+ value: args[0][:value].reverse }
93
+ when :list
94
+ result[:value].reverse!
95
+ end
96
+
97
+ @stack << result
98
+ end )
99
+
100
+ @dictionary.add_word( ['split'],
101
+ 'String',
102
+ '( s c -- … ) split string s on character c',
103
+ proc do
104
+ args = stack_extract( [%i[string], %i[string]] )
105
+
106
+ args[1][:value].split( args[0][:value] ).each do |elt|
107
+ @stack << { type: :string,
108
+ value: elt }
109
+ end
110
+ end )
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RplLang
4
+ module Core
5
+ module Test
6
+ def populate_dictionary
7
+ super
8
+
9
+ @dictionary.add_word( ['>'],
10
+ 'Test',
11
+ '( a b -- t ) is a greater than b?',
12
+ proc do
13
+ args = stack_extract( %i[any any] )
14
+
15
+ @stack << { type: :boolean,
16
+ value: args[1][:value] > args[0][:value] }
17
+ end )
18
+
19
+ @dictionary.add_word( ['≥', '>='],
20
+ 'Test',
21
+ '( a b -- t ) is a greater than or equal to b?',
22
+ proc do
23
+ args = stack_extract( %i[any any] )
24
+
25
+ @stack << { type: :boolean,
26
+ value: args[1][:value] >= args[0][:value] }
27
+ end )
28
+
29
+ @dictionary.add_word( ['<'],
30
+ 'Test',
31
+ '( a b -- t ) is a less than b?',
32
+ proc do
33
+ args = stack_extract( %i[any any] )
34
+
35
+ @stack << { type: :boolean,
36
+ value: args[1][:value] < args[0][:value] }
37
+ end )
38
+
39
+ @dictionary.add_word( ['≤', '<='],
40
+ 'Test',
41
+ '( a b -- t ) is a less than or equal to b?',
42
+ proc do
43
+ args = stack_extract( %i[any any] )
44
+
45
+ @stack << { type: :boolean,
46
+ value: args[1][:value] <= args[0][:value] }
47
+ end )
48
+
49
+ @dictionary.add_word( ['≠', '!='],
50
+ 'Test',
51
+ '( a b -- t ) is a not equal to b',
52
+ proc do
53
+ args = stack_extract( %i[any any] )
54
+
55
+ @stack << { type: :boolean,
56
+ value: args[1][:value] != args[0][:value] }
57
+ end )
58
+
59
+ @dictionary.add_word( ['==', 'same'],
60
+ 'Test',
61
+ '( a b -- t ) is a equal to b',
62
+ proc do
63
+ args = stack_extract( %i[any any] )
64
+
65
+ @stack << { type: :boolean,
66
+ value: args[1][:value] == args[0][:value] }
67
+ end )
68
+
69
+ @dictionary.add_word( ['and'],
70
+ 'Test',
71
+ '( a b -- t ) boolean and',
72
+ proc do
73
+ args = stack_extract( [%i[boolean], %i[boolean]] )
74
+
75
+ @stack << { type: :boolean,
76
+ value: args[1][:value] && args[0][:value] }
77
+ end )
78
+
79
+ @dictionary.add_word( ['or'],
80
+ 'Test',
81
+ '( a b -- t ) boolean or',
82
+ proc do
83
+ args = stack_extract( [%i[boolean], %i[boolean]] )
84
+
85
+ @stack << { type: :boolean,
86
+ value: args[1][:value] || args[0][:value] }
87
+ end )
88
+
89
+ @dictionary.add_word( ['xor'],
90
+ 'Test',
91
+ '( a b -- t ) boolean xor',
92
+ proc do
93
+ args = stack_extract( [%i[boolean], %i[boolean]] )
94
+
95
+ @stack << { type: :boolean,
96
+ value: args[1][:value] ^ args[0][:value] }
97
+ end )
98
+
99
+ @dictionary.add_word( ['not'],
100
+ 'Test',
101
+ '( a -- t ) invert boolean value',
102
+ proc do
103
+ args = stack_extract( [%i[boolean]] )
104
+
105
+ @stack << { type: :boolean,
106
+ value: !args[0][:value] }
107
+ end )
108
+
109
+ @dictionary.add_word( ['true'],
110
+ 'Test',
111
+ '( -- t ) push true onto stack',
112
+ proc do
113
+ @stack << { type: :boolean,
114
+ value: true }
115
+ end )
116
+
117
+ @dictionary.add_word( ['false'],
118
+ 'Test',
119
+ '( -- t ) push false onto stack',
120
+ proc do
121
+ @stack << { type: :boolean,
122
+ value: false }
123
+ end )
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module RplLang
6
+ module Core
7
+ module TimeAndDate
8
+ def populate_dictionary
9
+ super
10
+
11
+ @dictionary.add_word( ['time'],
12
+ 'Time and date',
13
+ '( -- t ) push current time',
14
+ proc do
15
+ @stack << { type: :string,
16
+ value: Time.now.to_s }
17
+ end )
18
+ @dictionary.add_word( ['date'],
19
+ 'Time and date',
20
+ '( -- d ) push current date',
21
+ proc do
22
+ @stack << { type: :string,
23
+ value: Date.today.to_s }
24
+ end )
25
+ @dictionary.add_word( ['ticks'],
26
+ 'Time and date',
27
+ '( -- t ) push datetime as ticks',
28
+ proc do
29
+ ticks_since_epoch = Time.utc( 1, 1, 1 ).to_i * 10_000_000
30
+ now = Time.now
31
+ @stack << { type: :numeric,
32
+ base: 10,
33
+ value: now.to_i * 10_000_000 + now.nsec / 100 - ticks_since_epoch }
34
+ end )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://rosettacode.org/wiki/Trigonometric_functions#Ruby
4
+
5
+ module RplLang
6
+ module Core
7
+ module Trig
8
+ def populate_dictionary
9
+ super
10
+
11
+ @dictionary.add_word( ['𝛑', 'pi'],
12
+ 'Trig on reals and complexes',
13
+ '( … -- 𝛑 ) push 𝛑',
14
+ proc do
15
+ @stack << { type: :numeric,
16
+ base: 10,
17
+ value: BigMath.PI( precision ) }
18
+ end )
19
+
20
+ @dictionary.add_word( ['sin'],
21
+ 'Trig on reals and complexes',
22
+ '( n -- m ) compute sinus of n',
23
+ proc do
24
+ args = stack_extract( [%i[numeric]] )
25
+
26
+ @stack << { type: :numeric,
27
+ base: infer_resulting_base( args ),
28
+ value: BigMath.sin( BigDecimal( args[0][:value], precision ), precision ) }
29
+ end )
30
+
31
+ @dictionary.add_word( ['asin'],
32
+ 'Trig on reals and complexes',
33
+ '( n -- m ) compute arg-sinus of n',
34
+ proc do
35
+ run( '
36
+ dup abs 1 ==
37
+ « 𝛑 2 / * »
38
+ « dup sq 1 swap - sqrt / atan »
39
+ ifte' )
40
+ end )
41
+
42
+ @dictionary.add_word( ['cos'],
43
+ 'Trig on reals and complexes',
44
+ '( n -- m ) compute cosinus of n',
45
+ proc do
46
+ args = stack_extract( [%i[numeric]] )
47
+
48
+ @stack << { type: :numeric,
49
+ base: infer_resulting_base( args ),
50
+ value: BigMath.cos( BigDecimal( args[0][:value], precision ), precision ) }
51
+ end )
52
+ @dictionary.add_word( ['acos'],
53
+ 'Trig on reals and complexes',
54
+ '( n -- m ) compute arg-cosinus of n',
55
+ proc do
56
+ run( '
57
+ dup 0 ==
58
+ « drop 𝛑 2 / »
59
+ «
60
+ dup sq 1 swap - sqrt / atan
61
+ dup 0 <
62
+ « 𝛑 + »
63
+ ift
64
+ »
65
+ ifte' )
66
+ end )
67
+
68
+ @dictionary.add_word( ['tan'],
69
+ 'Trig on reals and complexes',
70
+ '( n -- m ) compute tangent of n',
71
+ proc do
72
+ run( 'dup sin swap cos /' )
73
+ end )
74
+
75
+ @dictionary.add_word( ['atan'],
76
+ 'Trig on reals and complexes',
77
+ '( n -- m ) compute arc-tangent of n',
78
+ proc do
79
+ args = stack_extract( [%i[numeric]] )
80
+
81
+ @stack << { type: :numeric,
82
+ base: infer_resulting_base( args ),
83
+ value: BigMath.atan( BigDecimal( args[0][:value], precision ), precision ) }
84
+ end)
85
+ @dictionary.add_word( ['d→r', 'd->r'],
86
+ 'Trig on reals and complexes',
87
+ '( n -- m ) convert degree to radian',
88
+ proc do
89
+ run( '180 / 𝛑 *' )
90
+ end )
91
+ @dictionary.add_word( ['r→d', 'r->d'],
92
+ 'Trig on reals and complexes',
93
+ '( n -- m ) convert radian to degree',
94
+ proc do
95
+ run( '𝛑 180 / /' )
96
+ end)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Dictionary
4
+ attr_reader :words,
5
+ :vars,
6
+ :local_vars_layers
7
+
8
+ def initialize
9
+ @words = {}
10
+ @vars = {}
11
+ @local_vars_layers = []
12
+ end
13
+
14
+ def add_word( names, category, help, implementation )
15
+ names.each do |name|
16
+ @words[ name ] = { category: category,
17
+ help: help,
18
+ implementation: implementation }
19
+ end
20
+ end
21
+
22
+ def add_var( name, implementation )
23
+ @vars[ name ] = implementation
24
+ end
25
+
26
+ def remove_vars( names )
27
+ names.each do |name|
28
+ @vars.delete( name )
29
+ end
30
+ end
31
+
32
+ def remove_var( name )
33
+ remove_vars( [name] )
34
+ end
35
+
36
+ def remove_all_vars
37
+ @vars = {}
38
+ end
39
+
40
+ def add_local_vars_layer
41
+ @local_vars_layers << {}
42
+ end
43
+
44
+ def add_local_var( name, implementation )
45
+ @local_vars_layers.last[ name ] = implementation
46
+ end
47
+
48
+ def remove_local_vars( names )
49
+ names.each do |name|
50
+ @local_vars_layers.last.delete( name )
51
+ end
52
+ end
53
+
54
+ def remove_local_var( name )
55
+ remove_local_vars( [name] )
56
+ end
57
+
58
+ def remove_local_vars_layer
59
+ @local_vars_layers.pop
60
+ end
61
+
62
+ def lookup( name )
63
+ # look in local variables from the deepest layer up
64
+ local_vars_layer = @local_vars_layers.reverse.find { |layer| layer[ name ] }
65
+ word = local_vars_layer.nil? ? nil : local_vars_layer[ name ]
66
+
67
+ # otherwise look in (global) variables
68
+ word ||= @vars[ name ]
69
+
70
+ # or is it a core word
71
+ word ||= @words[ name ].nil? ? nil : @words[ name ][:implementation]
72
+
73
+ word
74
+ end
75
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal/math'
4
+
5
+ require 'rpl/dictionary'
6
+
7
+ class Interpreter
8
+ include BigMath
9
+
10
+ attr_reader :stack,
11
+ :dictionary,
12
+ :version
13
+
14
+ attr_accessor :precision
15
+
16
+ def initialize( stack = [], dictionary = Rpl::Lang::Dictionary.new )
17
+ @version = 0.1
18
+
19
+ @precision = default_precision
20
+
21
+ @dictionary = dictionary
22
+ @stack = stack
23
+
24
+ populate_dictionary if @dictionary.words.empty?
25
+ end
26
+
27
+ def default_precision
28
+ 12
29
+ end
30
+
31
+ def parse( input )
32
+ is_numeric = lambda do |elt|
33
+ begin
34
+ !Float(elt).nil?
35
+ rescue ArgumentError
36
+ begin
37
+ !Integer(elt).nil?
38
+ rescue ArgumentError
39
+ false
40
+ end
41
+ end
42
+ end
43
+
44
+ splitted_input = input.split(' ')
45
+
46
+ # 2-passes:
47
+ # 1. regroup strings and programs
48
+ opened_programs = 0
49
+ closed_programs = 0
50
+ string_delimiters = 0
51
+ name_delimiters = 0
52
+ regrouping = false
53
+
54
+ regrouped_input = []
55
+ splitted_input.each do |elt|
56
+ if elt[0] == '«'
57
+ opened_programs += 1
58
+ elt.gsub!( '«', '« ') if elt.length > 1 && elt[1] != ' '
59
+ end
60
+ string_delimiters += 1 if elt[0] == '"' && elt.length > 1
61
+ name_delimiters += 1 if elt[0] == "'" && elt.length > 1
62
+
63
+ elt = "#{regrouped_input.pop} #{elt}".strip if regrouping
64
+
65
+ regrouped_input << elt
66
+
67
+ if elt[-1] == '»'
68
+ closed_programs += 1
69
+ elt.gsub!( '»', ' »') if elt.length > 1 && elt[-2] != ' '
70
+ end
71
+ string_delimiters += 1 if elt[-1] == '"'
72
+ name_delimiters += 1 if elt[-1] == "'"
73
+
74
+ regrouping = string_delimiters.odd? || name_delimiters.odd? || (opened_programs > closed_programs )
75
+ end
76
+
77
+ # 2. parse
78
+ # TODO: parse ∞, <NaN> as numerics
79
+ parsed_tree = []
80
+ regrouped_input.each do |elt|
81
+ parsed_entry = { value: elt }
82
+
83
+ parsed_entry[:type] = case elt[0]
84
+ when '«'
85
+ :program
86
+ when '"'
87
+ :string
88
+ when "'"
89
+ :name # TODO: check for forbidden space
90
+ else
91
+ if is_numeric.call( elt )
92
+ :numeric
93
+ else
94
+ :word
95
+ end
96
+ end
97
+
98
+ if %I[string name].include?( parsed_entry[:type] )
99
+ parsed_entry[:value] = parsed_entry[:value][1..-2]
100
+ elsif parsed_entry[:type] == :program
101
+ parsed_entry[:value] = parsed_entry[:value][2..-3]
102
+ elsif parsed_entry[:type] == :numeric
103
+ parsed_entry[:base] = 10 # TODO: parse others possible bases 0x...
104
+
105
+ begin
106
+ parsed_entry[:value] = Float( parsed_entry[:value] )
107
+ parsed_entry[:value] = parsed_entry[:value].to_i if (parsed_entry[:value] % 1).zero? && elt.index('.').nil?
108
+ rescue ArgumentError
109
+ parsed_entry[:value] = Integer( parsed_entry[:value] )
110
+ end
111
+
112
+ parsed_entry[:value] = BigDecimal( parsed_entry[:value], @precision )
113
+ end
114
+
115
+ parsed_tree << parsed_entry
116
+ end
117
+
118
+ parsed_tree
119
+ end
120
+
121
+ def run( input )
122
+ @dictionary.add_local_vars_layer
123
+
124
+ parse( input.to_s ).each do |elt|
125
+ case elt[:type]
126
+ when :word
127
+ break if %w[break quit exit].include?( elt[:value] )
128
+
129
+ command = @dictionary.lookup( elt[:value] )
130
+
131
+ if command.nil?
132
+ # if there isn't a command by that name then it's a name
133
+ elt[:type] = :name
134
+
135
+ @stack << elt
136
+ elsif command.is_a?( Proc )
137
+ command.call
138
+ else
139
+ run( command[:value] )
140
+ end
141
+ else
142
+ @stack << elt
143
+ end
144
+ end
145
+
146
+ @dictionary.remove_local_vars_layer
147
+
148
+ # superfluous but feels nice
149
+ @stack
150
+ end
151
+
152
+ def stack_extract( needs )
153
+ raise ArgumentError, 'Not enough elements' if @stack.size < needs.size
154
+
155
+ needs.each_with_index do |need, index|
156
+ stack_index = (index + 1) * -1
157
+
158
+ raise ArgumentError, "Type Error, needed #{need} got #{@stack[stack_index]}" unless need == :any || need.include?( @stack[stack_index][:type] )
159
+ end
160
+
161
+ args = []
162
+ needs.size.times do
163
+ args << @stack.pop
164
+ end
165
+
166
+ args
167
+ end
168
+
169
+ def stringify( elt )
170
+ case elt[:type]
171
+ when :numeric
172
+ prefix = case elt[:base]
173
+ when 2
174
+ '0b'
175
+ when 8
176
+ '0o'
177
+ when 10
178
+ ''
179
+ when 16
180
+ '0x'
181
+ else
182
+ "0#{elt[:base]}_"
183
+ end
184
+
185
+ if elt[:value].infinite?
186
+ suffix = elt[:value].infinite?.positive? ? '∞' : '-∞'
187
+ elsif elt[:value].nan?
188
+ suffix = '<NaN>'
189
+ else
190
+ suffix = if elt[:value].to_i == elt[:value]
191
+ elt[:value].to_i
192
+ else
193
+ elt[:value].to_s('F')
194
+ end
195
+ suffix = elt[:value].to_s( elt[:base] ) unless elt[:base] == 10
196
+ end
197
+
198
+ "#{prefix}#{suffix}"
199
+ when :list
200
+ "[#{elt[:value].map { |e| stringify( e ) }.join(', ')}]"
201
+ when :program
202
+ "« #{elt[:value]} »"
203
+ when :string
204
+ "\"#{elt[:value]}\""
205
+ when :name
206
+ "'#{elt[:value]}'"
207
+ else
208
+ elt[:value]
209
+ end
210
+ end
211
+
212
+ def infer_resulting_base( numerics )
213
+ 10 if numerics.length.zero?
214
+
215
+ numerics.last[:base]
216
+ end
217
+ end