rpl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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