forthic 0.1.0 → 0.3.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/README.md +314 -14
- data/Rakefile +37 -8
- data/lib/forthic/decorators/docs.rb +69 -0
- data/lib/forthic/decorators/word.rb +331 -0
- data/lib/forthic/errors.rb +270 -0
- data/lib/forthic/grpc/client.rb +223 -0
- data/lib/forthic/grpc/errors.rb +149 -0
- data/lib/forthic/grpc/forthic_runtime_pb.rb +32 -0
- data/lib/forthic/grpc/forthic_runtime_services_pb.rb +31 -0
- data/lib/forthic/grpc/remote_module.rb +120 -0
- data/lib/forthic/grpc/remote_runtime_module.rb +148 -0
- data/lib/forthic/grpc/remote_word.rb +91 -0
- data/lib/forthic/grpc/runtime_manager.rb +60 -0
- data/lib/forthic/grpc/serializer.rb +184 -0
- data/lib/forthic/grpc/server.rb +361 -0
- data/lib/forthic/interpreter.rb +682 -133
- data/lib/forthic/literals.rb +170 -0
- data/lib/forthic/module.rb +383 -0
- data/lib/forthic/modules/standard/array_module.rb +940 -0
- data/lib/forthic/modules/standard/boolean_module.rb +176 -0
- data/lib/forthic/modules/standard/core_module.rb +362 -0
- data/lib/forthic/modules/standard/datetime_module.rb +349 -0
- data/lib/forthic/modules/standard/json_module.rb +55 -0
- data/lib/forthic/modules/standard/math_module.rb +365 -0
- data/lib/forthic/modules/standard/record_module.rb +203 -0
- data/lib/forthic/modules/standard/string_module.rb +170 -0
- data/lib/forthic/tokenizer.rb +225 -78
- data/lib/forthic/utils.rb +35 -0
- data/lib/forthic/websocket/handler.rb +548 -0
- data/lib/forthic/websocket/serializer.rb +160 -0
- data/lib/forthic/word_options.rb +141 -0
- data/lib/forthic.rb +30 -20
- data/protos/README.md +43 -0
- data/protos/v1/forthic_runtime.proto +200 -0
- metadata +76 -39
- data/.standard.yml +0 -3
- data/CHANGELOG.md +0 -5
- data/Guardfile +0 -42
- data/lib/forthic/code_location.rb +0 -20
- data/lib/forthic/forthic_error.rb +0 -51
- data/lib/forthic/forthic_module.rb +0 -145
- data/lib/forthic/global_module.rb +0 -2341
- data/lib/forthic/positioned_string.rb +0 -19
- data/lib/forthic/token.rb +0 -38
- data/lib/forthic/variable.rb +0 -34
- data/lib/forthic/version.rb +0 -5
- data/lib/forthic/words/definition_word.rb +0 -40
- data/lib/forthic/words/end_array_word.rb +0 -28
- data/lib/forthic/words/end_module_word.rb +0 -16
- data/lib/forthic/words/imported_word.rb +0 -27
- data/lib/forthic/words/map_word.rb +0 -169
- data/lib/forthic/words/module_memo_bang_at_word.rb +0 -22
- data/lib/forthic/words/module_memo_bang_word.rb +0 -21
- data/lib/forthic/words/module_memo_word.rb +0 -35
- data/lib/forthic/words/module_word.rb +0 -21
- data/lib/forthic/words/push_value_word.rb +0 -21
- data/lib/forthic/words/start_module_word.rb +0 -31
- data/lib/forthic/words/word.rb +0 -30
- data/sig/forthic.rbs +0 -4
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../decorators/word'
|
|
4
|
+
|
|
5
|
+
module Forthic
|
|
6
|
+
module Modules
|
|
7
|
+
# BooleanModule - Comparison, logic, and membership operations
|
|
8
|
+
#
|
|
9
|
+
# Provides boolean operations including comparisons, logical operators,
|
|
10
|
+
# and membership testing.
|
|
11
|
+
class BooleanModule < Decorators::DecoratedModule
|
|
12
|
+
# Register module documentation
|
|
13
|
+
module_doc <<~DOC
|
|
14
|
+
Comparison, logic, and membership operations for boolean values and conditions.
|
|
15
|
+
|
|
16
|
+
## Categories
|
|
17
|
+
- Comparison: ==, !=, <, <=, >, >=
|
|
18
|
+
- Logic: OR, AND, NOT, XOR, NAND
|
|
19
|
+
- Membership: IN, ANY, ALL
|
|
20
|
+
- Conversion: >BOOL
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
5 3 >
|
|
24
|
+
"hello" "hello" ==
|
|
25
|
+
[1 2 3] [4 5 6] OR
|
|
26
|
+
2 [1 2 3] IN
|
|
27
|
+
DOC
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
super("boolean")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ========================================
|
|
34
|
+
# Comparison Operations
|
|
35
|
+
# ========================================
|
|
36
|
+
|
|
37
|
+
forthic_word :equals, "( a:any b:any -- equal:boolean )", "Test equality", "=="
|
|
38
|
+
def equals(a, b)
|
|
39
|
+
a == b
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
forthic_word :not_equals, "( a:any b:any -- not_equal:boolean )", "Test inequality", "!="
|
|
43
|
+
def not_equals(a, b)
|
|
44
|
+
a != b
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
forthic_word :less_than, "( a:any b:any -- less_than:boolean )", "Less than", "<"
|
|
48
|
+
def less_than(a, b)
|
|
49
|
+
a < b
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
forthic_word :less_than_or_equal, "( a:any b:any -- less_equal:boolean )", "Less than or equal", "<="
|
|
53
|
+
def less_than_or_equal(a, b)
|
|
54
|
+
a <= b
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
forthic_word :greater_than, "( a:any b:any -- greater_than:boolean )", "Greater than", ">"
|
|
58
|
+
def greater_than(a, b)
|
|
59
|
+
a > b
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
forthic_word :greater_than_or_equal, "( a:any b:any -- greater_equal:boolean )", "Greater than or equal", ">="
|
|
63
|
+
def greater_than_or_equal(a, b)
|
|
64
|
+
a >= b
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ========================================
|
|
68
|
+
# Logic Operations
|
|
69
|
+
# ========================================
|
|
70
|
+
|
|
71
|
+
forthic_direct_word :OR, "( a:boolean b:boolean -- result:boolean ) OR ( bools:boolean[] -- result:boolean )", "Logical OR of two values or array"
|
|
72
|
+
def OR(interp)
|
|
73
|
+
b = interp.stack_pop
|
|
74
|
+
|
|
75
|
+
# Case 1: Array on top of stack
|
|
76
|
+
if b.is_a?(Array)
|
|
77
|
+
b.each do |val|
|
|
78
|
+
if val
|
|
79
|
+
interp.stack_push(true)
|
|
80
|
+
return
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
interp.stack_push(false)
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Case 2: Two values
|
|
88
|
+
a = interp.stack_pop
|
|
89
|
+
interp.stack_push(a || b)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
forthic_direct_word :AND, "( a:boolean b:boolean -- result:boolean ) OR ( bools:boolean[] -- result:boolean )", "Logical AND of two values or array"
|
|
93
|
+
def AND(interp)
|
|
94
|
+
b = interp.stack_pop
|
|
95
|
+
|
|
96
|
+
# Case 1: Array on top of stack
|
|
97
|
+
if b.is_a?(Array)
|
|
98
|
+
b.each do |val|
|
|
99
|
+
unless val
|
|
100
|
+
interp.stack_push(false)
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
interp.stack_push(true)
|
|
105
|
+
return
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Case 2: Two values
|
|
109
|
+
a = interp.stack_pop
|
|
110
|
+
interp.stack_push(a && b)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
forthic_word :NOT, "( bool:boolean -- result:boolean )", "Logical NOT"
|
|
114
|
+
def NOT(bool)
|
|
115
|
+
!bool
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
forthic_word :XOR, "( a:boolean b:boolean -- result:boolean )", "Logical XOR (exclusive or)"
|
|
119
|
+
def XOR(a, b)
|
|
120
|
+
(a || b) && !(a && b)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
forthic_word :NAND, "( a:boolean b:boolean -- result:boolean )", "Logical NAND (not and)"
|
|
124
|
+
def NAND(a, b)
|
|
125
|
+
!(a && b)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# ========================================
|
|
129
|
+
# Membership Operations
|
|
130
|
+
# ========================================
|
|
131
|
+
|
|
132
|
+
forthic_word :IN, "( item:any array:any[] -- in:boolean )", "Check if item is in array"
|
|
133
|
+
def IN(item, array)
|
|
134
|
+
return false unless array.is_a?(Array)
|
|
135
|
+
array.include?(item)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
forthic_word :ANY, "( items1:any[] items2:any[] -- any:boolean )", "Check if any item from items1 is in items2"
|
|
139
|
+
def ANY(items1, items2)
|
|
140
|
+
return false unless items1.is_a?(Array) && items2.is_a?(Array)
|
|
141
|
+
|
|
142
|
+
# If items2 is empty, return true (any items from items1 satisfy empty constraint)
|
|
143
|
+
return true if items2.empty?
|
|
144
|
+
|
|
145
|
+
# Check if any item from items1 is in items2
|
|
146
|
+
items1.any? { |item| items2.include?(item) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
forthic_word :ALL, "( items1:any[] items2:any[] -- all:boolean )", "Check if all items from items2 are in items1"
|
|
150
|
+
def ALL(items1, items2)
|
|
151
|
+
return false unless items1.is_a?(Array) && items2.is_a?(Array)
|
|
152
|
+
|
|
153
|
+
# If items2 is empty, return true (all zero items are in items1)
|
|
154
|
+
return true if items2.empty?
|
|
155
|
+
|
|
156
|
+
# Check if all items from items2 are in items1
|
|
157
|
+
items2.all? { |item| items1.include?(item) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# ========================================
|
|
161
|
+
# Conversion Operations
|
|
162
|
+
# ========================================
|
|
163
|
+
|
|
164
|
+
forthic_word :to_BOOL, "( a:any -- bool:boolean )", "Convert to boolean (JavaScript-style truthiness)", ">BOOL"
|
|
165
|
+
def to_BOOL(a)
|
|
166
|
+
# Match JavaScript truthiness: null, undefined, 0, "", false are falsy
|
|
167
|
+
# Everything else (including [], {}) is truthy
|
|
168
|
+
return false if a.nil?
|
|
169
|
+
return false if a == false
|
|
170
|
+
return false if a == 0
|
|
171
|
+
return false if a == ""
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../decorators/word'
|
|
4
|
+
require_relative '../../word_options'
|
|
5
|
+
require_relative '../../errors'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Forthic
|
|
9
|
+
module Modules
|
|
10
|
+
# CoreModule - Essential interpreter operations
|
|
11
|
+
#
|
|
12
|
+
# Provides stack manipulation, variables, control flow, and module system operations.
|
|
13
|
+
class CoreModule < Decorators::DecoratedModule
|
|
14
|
+
# Register module documentation
|
|
15
|
+
module_doc <<~DOC
|
|
16
|
+
Essential interpreter operations for stack manipulation, variables, control flow, and module system.
|
|
17
|
+
|
|
18
|
+
## Categories
|
|
19
|
+
- Stack: POP, DUP, SWAP
|
|
20
|
+
- Variables: VARIABLES, !, @, !@
|
|
21
|
+
- Module: EXPORT, USE-MODULES
|
|
22
|
+
- Execution: INTERPRET
|
|
23
|
+
- Control: IDENTITY, NOP, DEFAULT, *DEFAULT, NULL, ARRAY?
|
|
24
|
+
- Options: ~> (converts array to WordOptions)
|
|
25
|
+
- Profiling: PROFILE-START, PROFILE-TIMESTAMP, PROFILE-END, PROFILE-DATA
|
|
26
|
+
- Logging: START-LOG, END-LOG
|
|
27
|
+
- String: INTERPOLATE, PRINT
|
|
28
|
+
- Debug: PEEK!, STACK!
|
|
29
|
+
|
|
30
|
+
## Options
|
|
31
|
+
INTERPOLATE and PRINT support options via the ~> operator using syntax: [.option_name value ...] ~> WORD
|
|
32
|
+
- separator: String to use when joining array values (default: ", ")
|
|
33
|
+
- null_text: Text to display for null/undefined values (default: "null")
|
|
34
|
+
- json: Use JSON.stringify for all values (default: false)
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
5 .count ! "Count: .count" PRINT
|
|
38
|
+
"Items: .items" [.separator " | "] ~> PRINT
|
|
39
|
+
[1 2 3] PRINT # Direct printing: 1, 2, 3
|
|
40
|
+
[1 2 3] [.separator " | "] ~> PRINT # With options: 1 | 2 | 3
|
|
41
|
+
[ [.name "Alice"] ] REC [.json TRUE] ~> PRINT # JSON format: {"name":"Alice"}
|
|
42
|
+
"Hello .name" INTERPOLATE .greeting !
|
|
43
|
+
[1 2 3] DUP SWAP
|
|
44
|
+
DOC
|
|
45
|
+
|
|
46
|
+
def initialize
|
|
47
|
+
super("core")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Helper method to get or create a variable
|
|
51
|
+
def self.get_or_create_variable(interp, name)
|
|
52
|
+
# Validate variable name - no __ prefix allowed
|
|
53
|
+
if name.match?(/^__/)
|
|
54
|
+
raise InvalidVariableNameError.new(
|
|
55
|
+
interp.get_top_input_string,
|
|
56
|
+
name,
|
|
57
|
+
location: interp.get_string_location
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
cur_module = interp.cur_module
|
|
62
|
+
|
|
63
|
+
# Check if variable already exists
|
|
64
|
+
variable = cur_module.variables[name]
|
|
65
|
+
|
|
66
|
+
# Create it if it doesn't exist
|
|
67
|
+
unless variable
|
|
68
|
+
cur_module.add_variable(name)
|
|
69
|
+
variable = cur_module.variables[name]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
variable
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Stack operations
|
|
76
|
+
|
|
77
|
+
forthic_direct_word :POP, "( a:any -- )", "Removes top item from stack"
|
|
78
|
+
def POP(interp)
|
|
79
|
+
interp.stack_pop
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
forthic_direct_word :DUP, "( a:any -- a:any a:any )", "Duplicates top stack item"
|
|
83
|
+
def DUP(interp)
|
|
84
|
+
a = interp.stack_pop
|
|
85
|
+
interp.stack_push(a)
|
|
86
|
+
interp.stack_push(a)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
forthic_direct_word :SWAP, "( a:any b:any -- b:any a:any )", "Swaps top two stack items"
|
|
90
|
+
def SWAP(interp)
|
|
91
|
+
b = interp.stack_pop
|
|
92
|
+
a = interp.stack_pop
|
|
93
|
+
interp.stack_push(b)
|
|
94
|
+
interp.stack_push(a)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
forthic_word :PEEK_BANG, "( -- )", "Prints top of stack and stops execution", "PEEK!"
|
|
98
|
+
def PEEK_BANG
|
|
99
|
+
stack = interp.get_stack.get_items
|
|
100
|
+
if stack.length > 0
|
|
101
|
+
puts stack.last
|
|
102
|
+
else
|
|
103
|
+
puts "<STACK EMPTY>"
|
|
104
|
+
end
|
|
105
|
+
raise IntentionalStopError.new("PEEK!")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
forthic_word :STACK_BANG, "( -- )", "Prints entire stack (reversed) and stops execution", "STACK!"
|
|
109
|
+
def STACK_BANG
|
|
110
|
+
stack = interp.get_stack.get_items.reverse
|
|
111
|
+
puts JSON.pretty_generate(stack)
|
|
112
|
+
raise IntentionalStopError.new("STACK!")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Variable operations
|
|
116
|
+
|
|
117
|
+
forthic_word :VARIABLES, "( varnames:string[] -- )", "Creates variables in current module"
|
|
118
|
+
def VARIABLES(varnames)
|
|
119
|
+
mod = interp.cur_module
|
|
120
|
+
varnames.each do |v|
|
|
121
|
+
if v.match?(/^__/)
|
|
122
|
+
raise InvalidVariableNameError.new(
|
|
123
|
+
interp.get_top_input_string,
|
|
124
|
+
v,
|
|
125
|
+
location: interp.get_string_location
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
mod.add_variable(v)
|
|
129
|
+
end
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
forthic_direct_word :set_var, "( value:any variable:any -- )", "Sets variable value (auto-creates if string name)", "!"
|
|
134
|
+
def set_var(interp)
|
|
135
|
+
variable = interp.stack_pop
|
|
136
|
+
value = interp.stack_pop
|
|
137
|
+
var_obj = if variable.is_a?(String)
|
|
138
|
+
self.class.get_or_create_variable(interp, variable)
|
|
139
|
+
else
|
|
140
|
+
variable
|
|
141
|
+
end
|
|
142
|
+
var_obj.set_value(value)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
forthic_direct_word :get_var, "( variable:any -- value:any )", "Gets variable value (auto-creates if string name)", "@"
|
|
146
|
+
def get_var(interp)
|
|
147
|
+
variable = interp.stack_pop
|
|
148
|
+
var_obj = if variable.is_a?(String)
|
|
149
|
+
self.class.get_or_create_variable(interp, variable)
|
|
150
|
+
else
|
|
151
|
+
variable
|
|
152
|
+
end
|
|
153
|
+
interp.stack_push(var_obj.get_value)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
forthic_direct_word :set_get_var, "( value:any variable:any -- value:any )", "Sets variable and returns value", "!@"
|
|
157
|
+
def set_get_var(interp)
|
|
158
|
+
variable = interp.stack_pop
|
|
159
|
+
value = interp.stack_pop
|
|
160
|
+
var_obj = if variable.is_a?(String)
|
|
161
|
+
self.class.get_or_create_variable(interp, variable)
|
|
162
|
+
else
|
|
163
|
+
variable
|
|
164
|
+
end
|
|
165
|
+
var_obj.set_value(value)
|
|
166
|
+
interp.stack_push(var_obj.get_value)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Execution
|
|
170
|
+
|
|
171
|
+
forthic_direct_word :INTERPRET, "( string:string -- )", "Interprets Forthic string in current context"
|
|
172
|
+
def INTERPRET(interp)
|
|
173
|
+
string = interp.stack_pop
|
|
174
|
+
string_location = interp.get_string_location
|
|
175
|
+
interp.run(string, string_location) if string
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Module operations
|
|
179
|
+
|
|
180
|
+
forthic_word :EXPORT, "( names:string[] -- )", "Exports words from current module"
|
|
181
|
+
def EXPORT(names)
|
|
182
|
+
interp.cur_module.add_exportable(names)
|
|
183
|
+
nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
forthic_word :USE_MODULES, "( names:string[] -- )", "Imports modules by name", "USE-MODULES"
|
|
187
|
+
def USE_MODULES(names)
|
|
188
|
+
return nil unless names
|
|
189
|
+
interp.use_modules(names)
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Control flow
|
|
194
|
+
|
|
195
|
+
forthic_word :IDENTITY, "( -- )", "Does nothing (identity operation)"
|
|
196
|
+
def IDENTITY
|
|
197
|
+
# No-op
|
|
198
|
+
nil
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
forthic_word :NOP, "( -- )", "Does nothing (no operation)"
|
|
202
|
+
def NOP
|
|
203
|
+
# No-op
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
forthic_direct_word :NULL, "( -- null:null )", "Pushes null onto stack"
|
|
208
|
+
def NULL(interp)
|
|
209
|
+
interp.stack_push(nil)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
forthic_word :ARRAY_Q, "( value:any -- boolean:boolean )", "Returns true if value is an array", "ARRAY?"
|
|
213
|
+
def ARRAY_Q(value)
|
|
214
|
+
value.is_a?(Array)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
forthic_word :DEFAULT, "( value:any default_value:any -- result:any )", "Returns value or default if value is null/undefined/empty string"
|
|
218
|
+
def DEFAULT(value, default_value)
|
|
219
|
+
if value.nil? || value == ""
|
|
220
|
+
default_value
|
|
221
|
+
else
|
|
222
|
+
value
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
forthic_word :star_DEFAULT, "( value:any default_forthic:string -- result:any )", "Returns value or executes Forthic if value is null/undefined/empty string", "*DEFAULT"
|
|
227
|
+
def star_DEFAULT(value, default_forthic)
|
|
228
|
+
if value.nil? || value == ""
|
|
229
|
+
string_location = interp.get_string_location
|
|
230
|
+
interp.run(default_forthic, string_location)
|
|
231
|
+
interp.stack_pop
|
|
232
|
+
else
|
|
233
|
+
value
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Options
|
|
238
|
+
|
|
239
|
+
forthic_word :arrow, "( array:any[] -- options:WordOptions )", "Convert options array to WordOptions. Format: [.key1 val1 .key2 val2]", "~>"
|
|
240
|
+
def arrow(array)
|
|
241
|
+
WordOptions.new(array)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Profiling
|
|
245
|
+
|
|
246
|
+
forthic_word :PROFILE_START, "( -- )", "Starts profiling word execution", "PROFILE-START"
|
|
247
|
+
def PROFILE_START
|
|
248
|
+
interp.start_profiling
|
|
249
|
+
nil
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
forthic_word :PROFILE_END, "( -- )", "Stops profiling word execution", "PROFILE-END"
|
|
253
|
+
def PROFILE_END
|
|
254
|
+
interp.stop_profiling
|
|
255
|
+
nil
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
forthic_word :PROFILE_TIMESTAMP, "( label:string -- )", "Records profiling timestamp with label", "PROFILE-TIMESTAMP"
|
|
259
|
+
def PROFILE_TIMESTAMP(label)
|
|
260
|
+
interp.add_timestamp(label)
|
|
261
|
+
nil
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
forthic_word :PROFILE_DATA, "( -- profile_data:object )", "Returns profiling data (word counts and timestamps)", "PROFILE-DATA"
|
|
265
|
+
def PROFILE_DATA
|
|
266
|
+
histogram = interp.word_histogram
|
|
267
|
+
timestamps = interp.profile_timestamps
|
|
268
|
+
|
|
269
|
+
result = {
|
|
270
|
+
word_counts: [],
|
|
271
|
+
timestamps: []
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
histogram.each do |val|
|
|
275
|
+
result[:word_counts] << { word: val[:word], count: val[:count] }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
prev_time = 0.0
|
|
279
|
+
timestamps.each do |t|
|
|
280
|
+
result[:timestamps] << {
|
|
281
|
+
label: t.label,
|
|
282
|
+
time_ms: t.time_ms,
|
|
283
|
+
delta: t.time_ms - prev_time
|
|
284
|
+
}
|
|
285
|
+
prev_time = t.time_ms
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
result
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Logging
|
|
292
|
+
|
|
293
|
+
forthic_word :START_LOG, "( -- )", "Starts logging interpreter stream", "START-LOG"
|
|
294
|
+
def START_LOG
|
|
295
|
+
interp.start_stream
|
|
296
|
+
nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
forthic_word :END_LOG, "( -- )", "Ends logging interpreter stream", "END-LOG"
|
|
300
|
+
def END_LOG
|
|
301
|
+
interp.end_stream
|
|
302
|
+
nil
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# String operations
|
|
306
|
+
|
|
307
|
+
forthic_word :INTERPOLATE, "( string:string [options:WordOptions] -- result:string )", "Interpolate variables (.name) and return result string. Use \\. to escape literal dots."
|
|
308
|
+
def INTERPOLATE(string, options = {})
|
|
309
|
+
separator = options[:separator] || options['separator'] || ", "
|
|
310
|
+
null_text = options[:null_text] || options['null_text'] || "null"
|
|
311
|
+
use_json = options[:json] || options['json'] || false
|
|
312
|
+
|
|
313
|
+
interpolate_string(string, separator, null_text, use_json)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
forthic_word :PRINT, "( value:any [options:WordOptions] -- )", "Print value to stdout. Strings interpolate variables (.name). Non-strings formatted with options. Use \\. to escape literal dots in strings."
|
|
317
|
+
def PRINT(value, options = {})
|
|
318
|
+
separator = options[:separator] || options['separator'] || ", "
|
|
319
|
+
null_text = options[:null_text] || options['null_text'] || "null"
|
|
320
|
+
use_json = options[:json] || options['json'] || false
|
|
321
|
+
|
|
322
|
+
result = if value.is_a?(String)
|
|
323
|
+
# String: interpolate variables
|
|
324
|
+
interpolate_string(value, separator, null_text, use_json)
|
|
325
|
+
else
|
|
326
|
+
# Non-string: format directly
|
|
327
|
+
value_to_string(value, separator, null_text, use_json)
|
|
328
|
+
end
|
|
329
|
+
puts result
|
|
330
|
+
nil
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
private
|
|
334
|
+
|
|
335
|
+
def interpolate_string(string, separator, null_text, use_json)
|
|
336
|
+
string ||= ""
|
|
337
|
+
|
|
338
|
+
# First, handle escape sequences by replacing \. with a temporary placeholder
|
|
339
|
+
escaped = string.gsub(/\\\./, "\x00ESCAPED_DOT\x00")
|
|
340
|
+
|
|
341
|
+
# Replace whitespace-preceded or start-of-string .variable patterns
|
|
342
|
+
interpolated = escaped.gsub(/(?:^|(?<=\s))\.([a-zA-Z_][a-zA-Z0-9_-]*)/) do
|
|
343
|
+
var_name = ::Regexp.last_match(1)
|
|
344
|
+
variable = self.class.get_or_create_variable(interp, var_name)
|
|
345
|
+
value = variable.get_value
|
|
346
|
+
value_to_string(value, separator, null_text, use_json)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Restore escaped dots
|
|
350
|
+
interpolated.gsub(/\x00ESCAPED_DOT\x00/, '.')
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def value_to_string(value, separator, null_text, use_json)
|
|
354
|
+
return null_text if value.nil?
|
|
355
|
+
return value.to_json if use_json
|
|
356
|
+
return value.join(separator) if value.is_a?(Array)
|
|
357
|
+
return value.to_json if value.is_a?(Hash)
|
|
358
|
+
value.to_s
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|