forthic 0.2.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +314 -14
  3. data/Rakefile +36 -7
  4. data/lib/forthic/decorators/docs.rb +69 -0
  5. data/lib/forthic/decorators/word.rb +331 -0
  6. data/lib/forthic/errors.rb +270 -0
  7. data/lib/forthic/grpc/client.rb +223 -0
  8. data/lib/forthic/grpc/errors.rb +149 -0
  9. data/lib/forthic/grpc/forthic_runtime_pb.rb +32 -0
  10. data/lib/forthic/grpc/forthic_runtime_services_pb.rb +31 -0
  11. data/lib/forthic/grpc/remote_module.rb +120 -0
  12. data/lib/forthic/grpc/remote_runtime_module.rb +148 -0
  13. data/lib/forthic/grpc/remote_word.rb +91 -0
  14. data/lib/forthic/grpc/runtime_manager.rb +60 -0
  15. data/lib/forthic/grpc/serializer.rb +184 -0
  16. data/lib/forthic/grpc/server.rb +361 -0
  17. data/lib/forthic/interpreter.rb +694 -245
  18. data/lib/forthic/literals.rb +170 -0
  19. data/lib/forthic/module.rb +383 -0
  20. data/lib/forthic/modules/standard/array_module.rb +940 -0
  21. data/lib/forthic/modules/standard/boolean_module.rb +176 -0
  22. data/lib/forthic/modules/standard/core_module.rb +362 -0
  23. data/lib/forthic/modules/standard/datetime_module.rb +349 -0
  24. data/lib/forthic/modules/standard/json_module.rb +55 -0
  25. data/lib/forthic/modules/standard/math_module.rb +365 -0
  26. data/lib/forthic/modules/standard/record_module.rb +203 -0
  27. data/lib/forthic/modules/standard/string_module.rb +170 -0
  28. data/lib/forthic/tokenizer.rb +224 -77
  29. data/lib/forthic/utils.rb +35 -0
  30. data/lib/forthic/websocket/handler.rb +548 -0
  31. data/lib/forthic/websocket/serializer.rb +160 -0
  32. data/lib/forthic/word_options.rb +141 -0
  33. data/lib/forthic.rb +30 -20
  34. data/protos/README.md +43 -0
  35. data/protos/v1/forthic_runtime.proto +200 -0
  36. metadata +72 -39
  37. data/.standard.yml +0 -3
  38. data/CHANGELOG.md +0 -11
  39. data/CLAUDE.md +0 -74
  40. data/Guardfile +0 -42
  41. data/lib/forthic/code_location.rb +0 -20
  42. data/lib/forthic/forthic_error.rb +0 -50
  43. data/lib/forthic/forthic_module.rb +0 -146
  44. data/lib/forthic/global_module.rb +0 -2328
  45. data/lib/forthic/positioned_string.rb +0 -19
  46. data/lib/forthic/token.rb +0 -37
  47. data/lib/forthic/variable.rb +0 -34
  48. data/lib/forthic/version.rb +0 -5
  49. data/lib/forthic/words/definition_word.rb +0 -38
  50. data/lib/forthic/words/end_array_word.rb +0 -28
  51. data/lib/forthic/words/end_module_word.rb +0 -16
  52. data/lib/forthic/words/imported_word.rb +0 -27
  53. data/lib/forthic/words/map_word.rb +0 -169
  54. data/lib/forthic/words/module_memo_bang_at_word.rb +0 -22
  55. data/lib/forthic/words/module_memo_bang_word.rb +0 -21
  56. data/lib/forthic/words/module_memo_word.rb +0 -35
  57. data/lib/forthic/words/module_word.rb +0 -21
  58. data/lib/forthic/words/push_value_word.rb +0 -21
  59. data/lib/forthic/words/start_module_word.rb +0 -31
  60. data/lib/forthic/words/word.rb +0 -30
  61. data/sig/forthic.rbs +0 -4
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../decorators/word'
4
+ require_relative '../../literals'
5
+ require 'time'
6
+ require 'date'
7
+ require 'tzinfo'
8
+
9
+ module Forthic
10
+ module Modules
11
+ # DateTimeModule - Date and time operations
12
+ #
13
+ # Provides timezone-aware datetime manipulation using Ruby's Time and Date classes.
14
+ class DateTimeModule < Decorators::DecoratedModule
15
+ # Register module documentation
16
+ module_doc <<~DOC
17
+ Date and time operations using Ruby Time and Date classes for timezone-aware datetime manipulation.
18
+
19
+ ## Categories
20
+ - Current: TODAY, NOW
21
+ - Time adjustment: AM, PM
22
+ - Conversion to: >TIME, >DATE, >DATETIME, AT
23
+ - Conversion from: TIME>STR, DATE>STR, DATE>INT
24
+ - Timestamps: >TIMESTAMP, TIMESTAMP>DATETIME
25
+ - Date math: ADD-DAYS, SUBTRACT-DATES
26
+
27
+ ## Examples
28
+ TODAY
29
+ NOW
30
+ "14:30" >TIME
31
+ "2024-01-15" >DATE
32
+ TODAY "14:30" >TIME AT
33
+ TODAY 7 ADD-DAYS
34
+ DOC
35
+
36
+ def initialize
37
+ super("datetime")
38
+ end
39
+
40
+ # Helper method to get Time in specific timezone
41
+ def get_time_in_timezone(timezone_name)
42
+ tz = TZInfo::Timezone.get(timezone_name)
43
+ tz.now
44
+ end
45
+
46
+ # Helper method to convert Unix timestamp to Time in specific timezone
47
+ def timestamp_to_time(timestamp, timezone_name)
48
+ tz = TZInfo::Timezone.get(timezone_name)
49
+ tz.to_local(Time.at(timestamp).utc)
50
+ end
51
+
52
+ # Helper method to parse time string in specific timezone
53
+ def parse_time_in_timezone(str, timezone_name)
54
+ tz = TZInfo::Timezone.get(timezone_name)
55
+ utc_time = Time.parse(str).utc
56
+ tz.to_local(utc_time)
57
+ end
58
+
59
+ # ========================================
60
+ # Current Time/Date
61
+ # ========================================
62
+
63
+ forthic_direct_word :TODAY, "( -- date:Date )", "Get current date"
64
+ def TODAY(interp)
65
+ timezone = interp.get_timezone
66
+ # Get current time in timezone, then convert to Date
67
+ today = get_time_in_timezone(timezone)
68
+ date = Date.new(today.year, today.month, today.day)
69
+ interp.stack_push(date)
70
+ end
71
+
72
+ forthic_direct_word :NOW, "( -- datetime:Time )", "Get current datetime"
73
+ def NOW(interp)
74
+ timezone = interp.get_timezone
75
+ now = get_time_in_timezone(timezone)
76
+ interp.stack_push(now)
77
+ end
78
+
79
+ # ========================================
80
+ # Time Adjustment
81
+ # ========================================
82
+
83
+ forthic_direct_word :AM, "( time:SimpleTime -- time:SimpleTime )", "Convert time to AM (subtract 12 from hour if >= 12)"
84
+ def AM(interp)
85
+ time = interp.stack_pop
86
+ result = if time && time.respond_to?(:hour)
87
+ if time.hour >= 12
88
+ Forthic::SimpleTime.new(hour: time.hour - 12, minute: time.minute)
89
+ else
90
+ time
91
+ end
92
+ else
93
+ time
94
+ end
95
+ interp.stack_push(result)
96
+ end
97
+
98
+ forthic_direct_word :PM, "( time:SimpleTime -- time:SimpleTime )", "Convert time to PM (add 12 to hour if < 12)"
99
+ def PM(interp)
100
+ time = interp.stack_pop
101
+ result = if time && time.respond_to?(:hour)
102
+ if time.hour < 12
103
+ Forthic::SimpleTime.new(hour: time.hour + 12, minute: time.minute)
104
+ else
105
+ time
106
+ end
107
+ else
108
+ time
109
+ end
110
+ interp.stack_push(result)
111
+ end
112
+
113
+ # ========================================
114
+ # Conversion To Date/Time
115
+ # ========================================
116
+
117
+ forthic_direct_word :to_TIME, "( item:any -- time:SimpleTime )", "Convert string or datetime to SimpleTime", ">TIME"
118
+ def to_TIME(interp)
119
+ item = interp.stack_pop
120
+ result = nil
121
+
122
+ if item
123
+ # If already a SimpleTime, return it
124
+ if item.is_a?(Forthic::SimpleTime)
125
+ result = item
126
+ # If it's a Time, extract the hour and minute
127
+ elsif item.is_a?(Time)
128
+ result = Forthic::SimpleTime.new(hour: item.hour, minute: item.min)
129
+ else
130
+ # Otherwise, parse as string
131
+ str = item.to_s.strip
132
+
133
+ # Handle "HH:MM AM/PM" format
134
+ ampm_match = str.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i)
135
+ if ampm_match
136
+ hour = ampm_match[1].to_i
137
+ minute = ampm_match[2].to_i
138
+ meridiem = ampm_match[3].upcase
139
+
140
+ if meridiem == "PM" && hour < 12
141
+ hour += 12
142
+ elsif meridiem == "AM" && hour == 12
143
+ hour = 0
144
+ end
145
+
146
+ result = Forthic::SimpleTime.new(hour: hour, minute: minute)
147
+ else
148
+ # Try standard parsing (HH:MM or HH:MM:SS)
149
+ begin
150
+ match = str.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/)
151
+ if match
152
+ hour = match[1].to_i
153
+ minute = match[2].to_i
154
+ result = Forthic::SimpleTime.new(hour: hour, minute: minute)
155
+ end
156
+ rescue StandardError
157
+ # Fall through
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ interp.stack_push(result)
164
+ end
165
+
166
+ forthic_direct_word :to_DATE, "( item:any -- date:Date )", "Convert string or datetime to Date", ">DATE"
167
+ def to_DATE(interp)
168
+ item = interp.stack_pop
169
+ result = nil
170
+
171
+ if item
172
+ # If already a Date, return it
173
+ if item.is_a?(Date)
174
+ result = item
175
+ # If it's a Time, extract the date
176
+ elsif item.is_a?(Time)
177
+ result = Date.new(item.year, item.month, item.day)
178
+ else
179
+ # Otherwise, parse as string
180
+ str = item.to_s.strip
181
+
182
+ # Try standard ISO format (YYYY-MM-DD)
183
+ begin
184
+ result = Date.parse(str)
185
+ rescue StandardError
186
+ # Fall through
187
+ end
188
+ end
189
+ end
190
+
191
+ interp.stack_push(result)
192
+ end
193
+
194
+ forthic_direct_word :to_DATETIME, "( str_or_timestamp:any -- datetime:Time )", "Convert string or timestamp to Time", ">DATETIME"
195
+ def to_DATETIME(interp)
196
+ item = interp.stack_pop
197
+
198
+ unless item
199
+ interp.stack_push(nil)
200
+ return
201
+ end
202
+
203
+ # If already a Time, return it
204
+ if item.is_a?(Time)
205
+ interp.stack_push(item)
206
+ return
207
+ end
208
+
209
+ # If it's a number, treat as Unix timestamp (seconds)
210
+ if item.is_a?(Numeric)
211
+ timezone = interp.get_timezone
212
+ time = timestamp_to_time(item, timezone)
213
+ interp.stack_push(time)
214
+ return
215
+ end
216
+
217
+ # Otherwise, parse as string
218
+ str = item.to_s.strip
219
+
220
+ begin
221
+ # Try parsing as ISO datetime string
222
+ timezone = interp.get_timezone
223
+ time = parse_time_in_timezone(str, timezone)
224
+ interp.stack_push(time)
225
+ rescue StandardError
226
+ interp.stack_push(nil)
227
+ end
228
+ end
229
+
230
+ forthic_direct_word :AT, "( date:Date time:SimpleTime -- datetime:Time )", "Combine date and time into datetime"
231
+ def AT(interp)
232
+ time = interp.stack_pop
233
+ date = interp.stack_pop
234
+
235
+ unless date && time
236
+ interp.stack_push(nil)
237
+ return
238
+ end
239
+
240
+ # Create Time from date and time components in the specified timezone
241
+ timezone_name = interp.get_timezone
242
+ hour = time.respond_to?(:hour) ? (time.hour || 0) : 0
243
+ minute = time.respond_to?(:minute) ? (time.minute || 0) : 0
244
+
245
+ # Create local time in the specified timezone
246
+ tz = TZInfo::Timezone.get(timezone_name)
247
+ local_time = Time.new(date.year, date.month, date.day, hour, minute, 0)
248
+ datetime = tz.to_local(tz.local_to_utc(local_time))
249
+
250
+ interp.stack_push(datetime)
251
+ end
252
+
253
+ # ========================================
254
+ # Conversion From Date/Time
255
+ # ========================================
256
+
257
+ forthic_word :TIME_to_STR, "( time:SimpleTime -- str:string )", "Convert time to HH:MM string", "TIME>STR"
258
+ def TIME_to_STR(time)
259
+ return "" unless time && time.respond_to?(:hour)
260
+
261
+ hour = time.hour.to_s.rjust(2, '0')
262
+ minute = time.minute.to_s.rjust(2, '0')
263
+ "#{hour}:#{minute}"
264
+ end
265
+
266
+ forthic_word :DATE_to_STR, "( date:Date -- str:string )", "Convert date to YYYY-MM-DD string", "DATE>STR"
267
+ def DATE_to_STR(date)
268
+ return "" unless date && date.respond_to?(:year)
269
+
270
+ date.to_s
271
+ end
272
+
273
+ forthic_word :DATE_to_INT, "( date:Date -- int:number )", "Convert date to integer (YYYYMMDD)", "DATE>INT"
274
+ def DATE_to_INT(date)
275
+ return nil unless date && date.respond_to?(:year)
276
+
277
+ year = date.year
278
+ month = date.month.to_s.rjust(2, '0')
279
+ day = date.day.to_s.rjust(2, '0')
280
+ "#{year}#{month}#{day}".to_i
281
+ end
282
+
283
+ # ========================================
284
+ # Timestamps
285
+ # ========================================
286
+
287
+ forthic_direct_word :to_TIMESTAMP, "( datetime:Time -- timestamp:number )", "Convert datetime to Unix timestamp (seconds)", ">TIMESTAMP"
288
+ def to_TIMESTAMP(interp)
289
+ datetime = interp.stack_pop
290
+
291
+ unless datetime
292
+ interp.stack_push(nil)
293
+ return
294
+ end
295
+
296
+ # Convert to Unix timestamp (seconds)
297
+ if datetime.is_a?(Time)
298
+ interp.stack_push(datetime.to_i)
299
+ else
300
+ interp.stack_push(nil)
301
+ end
302
+ end
303
+
304
+ forthic_direct_word :TIMESTAMP_to_DATETIME, "( timestamp:number -- datetime:Time )", "Convert Unix timestamp (seconds) to datetime", "TIMESTAMP>DATETIME"
305
+ def TIMESTAMP_to_DATETIME(interp)
306
+ timestamp = interp.stack_pop
307
+
308
+ unless timestamp.is_a?(Numeric)
309
+ interp.stack_push(nil)
310
+ return
311
+ end
312
+
313
+ # Convert timestamp (seconds) to Time
314
+ timezone = interp.get_timezone
315
+ datetime = timestamp_to_time(timestamp, timezone)
316
+ interp.stack_push(datetime)
317
+ end
318
+
319
+ # ========================================
320
+ # Date Math
321
+ # ========================================
322
+
323
+ forthic_direct_word :ADD_DAYS, "( date:Date num_days:number -- date:Date )", "Add days to a date", "ADD-DAYS"
324
+ def ADD_DAYS(interp)
325
+ num_days = interp.stack_pop
326
+ date = interp.stack_pop
327
+ result = if date && date.respond_to?(:year) && !num_days.nil?
328
+ date + num_days
329
+ else
330
+ nil
331
+ end
332
+ interp.stack_push(result)
333
+ end
334
+
335
+ forthic_direct_word :SUBTRACT_DATES, "( date1:Date date2:Date -- num_days:number )", "Get difference in days between dates (date1 - date2)", "SUBTRACT-DATES"
336
+ def SUBTRACT_DATES(interp)
337
+ date2 = interp.stack_pop
338
+ date1 = interp.stack_pop
339
+ result = if date1 && date2 && date1.respond_to?(:year) && date2.respond_to?(:year)
340
+ # Get the difference in days: date1 - date2
341
+ (date1 - date2).to_i
342
+ else
343
+ nil
344
+ end
345
+ interp.stack_push(result)
346
+ end
347
+ end
348
+ end
349
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../decorators/word'
4
+ require 'json'
5
+
6
+ module Forthic
7
+ module Modules
8
+ # JsonModule - JSON serialization, parsing, and formatting operations
9
+ #
10
+ # Provides operations for converting between Ruby objects and JSON strings.
11
+ class JsonModule < Decorators::DecoratedModule
12
+ # Register module documentation
13
+ module_doc <<~DOC
14
+ JSON serialization, parsing, and formatting operations.
15
+
16
+ ## Categories
17
+ - Conversion: >JSON, JSON>
18
+ - Formatting: JSON-PRETTIFY
19
+
20
+ ## Examples
21
+ {name: "Alice", age: 30} >JSON
22
+ '{"name":"Alice"}' JSON>
23
+ '{"a":1}' JSON-PRETTIFY
24
+ DOC
25
+
26
+ def initialize
27
+ super("json")
28
+ end
29
+
30
+ forthic_word :to_JSON, "( object:any -- json:string )", "Convert object to JSON string", ">JSON"
31
+ def to_JSON(object)
32
+ return "null" if object.nil?
33
+ JSON.generate(object)
34
+ end
35
+
36
+ forthic_direct_word :from_JSON, "( json:string -- object:any )", "Parse JSON string to object", "JSON>"
37
+ def from_JSON(interp)
38
+ json = interp.stack_pop
39
+ result = if json.nil? || json.strip.empty?
40
+ nil
41
+ else
42
+ JSON.parse(json)
43
+ end
44
+ interp.stack_push(result)
45
+ end
46
+
47
+ forthic_word :JSON_PRETTIFY, "( json:string -- pretty:string )", "Format JSON with 2-space indentation", "JSON-PRETTIFY"
48
+ def JSON_PRETTIFY(json)
49
+ return "" if json.nil? || json.strip.empty?
50
+ obj = JSON.parse(json)
51
+ JSON.pretty_generate(obj)
52
+ end
53
+ end
54
+ end
55
+ end