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.
- checksums.yaml +4 -4
- data/README.md +314 -14
- data/Rakefile +36 -7
- 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 +694 -245
- 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 +224 -77
- 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 +72 -39
- data/.standard.yml +0 -3
- data/CHANGELOG.md +0 -11
- data/CLAUDE.md +0 -74
- data/Guardfile +0 -42
- data/lib/forthic/code_location.rb +0 -20
- data/lib/forthic/forthic_error.rb +0 -50
- data/lib/forthic/forthic_module.rb +0 -146
- data/lib/forthic/global_module.rb +0 -2328
- data/lib/forthic/positioned_string.rb +0 -19
- data/lib/forthic/token.rb +0 -37
- data/lib/forthic/variable.rb +0 -34
- data/lib/forthic/version.rb +0 -5
- data/lib/forthic/words/definition_word.rb +0 -38
- 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,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'runtime_manager'
|
|
4
|
+
require_relative 'remote_module'
|
|
5
|
+
|
|
6
|
+
module Forthic
|
|
7
|
+
module Grpc
|
|
8
|
+
# RemoteRuntimeModule provides words for connecting to and using remote Forthic runtimes
|
|
9
|
+
class RemoteRuntimeModule < Forthic::Decorators::DecoratedModule
|
|
10
|
+
module_doc <<~DOC
|
|
11
|
+
Module for connecting to and using remote Forthic runtimes (TypeScript, Python).
|
|
12
|
+
|
|
13
|
+
Enables cross-runtime execution by connecting to remote gRPC servers and importing
|
|
14
|
+
their modules into the current interpreter.
|
|
15
|
+
|
|
16
|
+
Example usage:
|
|
17
|
+
"python" "localhost:50051" CONNECT-RUNTIME
|
|
18
|
+
["pandas"] USE-PY-MODULES
|
|
19
|
+
|
|
20
|
+
"typescript" "localhost:50052" CONNECT-RUNTIME
|
|
21
|
+
["fs"] "ts" USE-TS-MODULES-AS
|
|
22
|
+
DOC
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
super('remote_runtime')
|
|
26
|
+
@runtime_manager = RuntimeManager.instance
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
forthic_direct_word :CONNECT_RUNTIME, '( name:str address:str -- )', 'Connect to a remote runtime', 'CONNECT-RUNTIME'
|
|
30
|
+
def CONNECT_RUNTIME(interp)
|
|
31
|
+
address = interp.stack_pop
|
|
32
|
+
name = interp.stack_pop
|
|
33
|
+
@runtime_manager.connect_runtime(name, address)
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
forthic_direct_word :DISCONNECT_RUNTIME, '( name:str -- )', 'Disconnect from a remote runtime', 'DISCONNECT-RUNTIME'
|
|
38
|
+
def DISCONNECT_RUNTIME(interp)
|
|
39
|
+
name = interp.stack_pop
|
|
40
|
+
@runtime_manager.disconnect_runtime(name)
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
forthic_direct_word :DISCONNECT_ALL, '( -- )', 'Disconnect from all remote runtimes', 'DISCONNECT-ALL'
|
|
45
|
+
def DISCONNECT_ALL(_interp)
|
|
46
|
+
@runtime_manager.disconnect_all
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
forthic_direct_word :CONNECTED_RUNTIMES, '( -- runtimes:array )', 'Get list of connected runtime names', 'CONNECTED-RUNTIMES'
|
|
51
|
+
def CONNECTED_RUNTIMES(interp)
|
|
52
|
+
interp.stack_push(@runtime_manager.connected_runtimes)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
forthic_direct_word :USE_TS_MODULES, '( modules:array -- )', 'Import TypeScript modules', 'USE-TS-MODULES'
|
|
56
|
+
def USE_TS_MODULES(interp)
|
|
57
|
+
modules = interp.stack_pop
|
|
58
|
+
load_modules('typescript', modules, nil, interp)
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
forthic_direct_word :USE_TS_MODULES_AS, '( modules:array prefix:str -- )', 'Import TypeScript modules with prefix', 'USE-TS-MODULES-AS'
|
|
63
|
+
def USE_TS_MODULES_AS(interp)
|
|
64
|
+
prefix = interp.stack_pop
|
|
65
|
+
modules = interp.stack_pop
|
|
66
|
+
load_modules('typescript', modules, prefix, interp)
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
forthic_direct_word :USE_PY_MODULES, '( modules:array -- )', 'Import Python modules', 'USE-PY-MODULES'
|
|
71
|
+
def USE_PY_MODULES(interp)
|
|
72
|
+
modules = interp.stack_pop
|
|
73
|
+
load_modules('python', modules, nil, interp)
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
forthic_direct_word :USE_PY_MODULES_AS, '( modules:array prefix:str -- )', 'Import Python modules with prefix', 'USE-PY-MODULES-AS'
|
|
78
|
+
def USE_PY_MODULES_AS(interp)
|
|
79
|
+
prefix = interp.stack_pop
|
|
80
|
+
modules = interp.stack_pop
|
|
81
|
+
load_modules('python', modules, prefix, interp)
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
forthic_direct_word :USE_RB_MODULES, '( modules:array -- )', 'Import Ruby modules from another runtime', 'USE-RB-MODULES'
|
|
86
|
+
def USE_RB_MODULES(interp)
|
|
87
|
+
modules = interp.stack_pop
|
|
88
|
+
load_modules('ruby', modules, nil, interp)
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
forthic_direct_word :USE_RB_MODULES_AS, '( modules:array prefix:str -- )', 'Import Ruby modules with prefix', 'USE-RB-MODULES-AS'
|
|
93
|
+
def USE_RB_MODULES_AS(interp)
|
|
94
|
+
prefix = interp.stack_pop
|
|
95
|
+
modules = interp.stack_pop
|
|
96
|
+
load_modules('ruby', modules, prefix, interp)
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
forthic_direct_word :LIST_REMOTE_MODULES, '( runtime:str -- modules:array )', 'List available modules in a remote runtime', 'LIST-REMOTE-MODULES'
|
|
101
|
+
def LIST_REMOTE_MODULES(interp)
|
|
102
|
+
runtime_name = interp.stack_pop
|
|
103
|
+
client = @runtime_manager.get_runtime(runtime_name)
|
|
104
|
+
raise "Runtime '#{runtime_name}' not connected" unless client
|
|
105
|
+
|
|
106
|
+
modules = client.list_modules
|
|
107
|
+
module_summaries = modules.map do |mod|
|
|
108
|
+
{
|
|
109
|
+
'name' => mod[:name],
|
|
110
|
+
'description' => mod[:description],
|
|
111
|
+
'word_count' => mod[:word_count]
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
interp.stack_push(module_summaries)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# Load modules from a remote runtime
|
|
120
|
+
# @param runtime_name [String] The name of the runtime (e.g., 'python', 'typescript')
|
|
121
|
+
# @param module_names [Array<String>] List of module names to load
|
|
122
|
+
# @param prefix [String, nil] Optional prefix for module names
|
|
123
|
+
# @param interp [Interpreter] The interpreter instance
|
|
124
|
+
def load_modules(runtime_name, module_names, prefix, interp)
|
|
125
|
+
client = @runtime_manager.get_runtime(runtime_name)
|
|
126
|
+
raise "Runtime '#{runtime_name}' not connected. Use CONNECT-RUNTIME first." unless client
|
|
127
|
+
|
|
128
|
+
module_names.each do |module_name|
|
|
129
|
+
# Create remote module
|
|
130
|
+
remote_module = RemoteModule.new(module_name, client, runtime_name)
|
|
131
|
+
remote_module.initialize!
|
|
132
|
+
|
|
133
|
+
# Register module (always with original name)
|
|
134
|
+
interp.register_module(remote_module)
|
|
135
|
+
|
|
136
|
+
# Import with optional prefix
|
|
137
|
+
if prefix
|
|
138
|
+
# Use array format: [module_name, prefix]
|
|
139
|
+
interp.use_modules([[remote_module.name, prefix]])
|
|
140
|
+
else
|
|
141
|
+
# Use array format with empty prefix
|
|
142
|
+
interp.use_modules([remote_module.name])
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../module'
|
|
4
|
+
require_relative 'client'
|
|
5
|
+
|
|
6
|
+
module Forthic
|
|
7
|
+
module Grpc
|
|
8
|
+
# RemoteWord - Proxy word that delegates execution to a remote runtime via gRPC
|
|
9
|
+
#
|
|
10
|
+
# When executed:
|
|
11
|
+
# 1. Captures current interpreter stack
|
|
12
|
+
# 2. Sends word name + stack to remote runtime via gRPC
|
|
13
|
+
# 3. Replaces local stack with result stack from remote execution
|
|
14
|
+
#
|
|
15
|
+
# This allows seamless integration of remote runtime words (like TypeScript's FILE-EXISTS?
|
|
16
|
+
# or Python's DF-FROM-RECORDS) into the local Ruby interpreter.
|
|
17
|
+
#
|
|
18
|
+
# Example usage:
|
|
19
|
+
# client = GrpcClient.new('localhost:50052')
|
|
20
|
+
# remote_word = RemoteWord.new('REVERSE', client, 'typescript', 'array',
|
|
21
|
+
# '( array -- reversed )', 'Reverse array')
|
|
22
|
+
# remote_word.execute(interp) # Executes in TypeScript runtime!
|
|
23
|
+
class RemoteWord < Forthic::Word
|
|
24
|
+
attr_reader :client, :runtime_name, :module_name, :stack_effect, :description
|
|
25
|
+
|
|
26
|
+
# @param name [String] Word name (e.g., "DF_FROM_RECORDS")
|
|
27
|
+
# @param client [GrpcClient] gRPC client connected to remote runtime
|
|
28
|
+
# @param runtime_name [String] Name of remote runtime (e.g., "python", "typescript")
|
|
29
|
+
# @param module_name [String] Module name (e.g., "pandas", "fs")
|
|
30
|
+
# @param stack_effect [String] Stack notation (e.g., "( records:array -- df:DataFrame )")
|
|
31
|
+
# @param description [String] Human-readable description
|
|
32
|
+
def initialize(name, client, runtime_name, module_name, stack_effect = '( -- )', description = '')
|
|
33
|
+
super(name)
|
|
34
|
+
@client = client
|
|
35
|
+
@runtime_name = runtime_name
|
|
36
|
+
@module_name = module_name
|
|
37
|
+
@stack_effect = stack_effect
|
|
38
|
+
@description = description
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Execute word in remote runtime
|
|
42
|
+
#
|
|
43
|
+
# Captures entire stack, sends to remote runtime, and replaces stack with result.
|
|
44
|
+
# This is inefficient but correct.
|
|
45
|
+
#
|
|
46
|
+
# @param interp [Interpreter] The Forthic interpreter
|
|
47
|
+
def execute(interp)
|
|
48
|
+
# Capture current stack state
|
|
49
|
+
stack = interp.get_stack
|
|
50
|
+
stack_items = stack.get_items
|
|
51
|
+
|
|
52
|
+
# Execute word in remote runtime
|
|
53
|
+
# The server has already imported the module, so just send the word name
|
|
54
|
+
result_stack = @client.execute_word(@name, stack_items)
|
|
55
|
+
|
|
56
|
+
# Clear local stack and replace with result
|
|
57
|
+
stack.set_raw_items([])
|
|
58
|
+
|
|
59
|
+
# Push all result items
|
|
60
|
+
result_stack.each { |item| interp.stack_push(item) }
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
raise "Error executing remote word #{@module_name}.#{@name} in #{@runtime_name} runtime: #{e.message}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get runtime name for debugging/introspection
|
|
66
|
+
# @return [String] Runtime name
|
|
67
|
+
def get_runtime_name
|
|
68
|
+
@runtime_name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get module name for debugging/introspection
|
|
72
|
+
# @return [String] Module name
|
|
73
|
+
def get_module_name
|
|
74
|
+
@module_name
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get runtime execution information
|
|
78
|
+
# RemoteWords are runtime-specific and can only execute in their designated runtime
|
|
79
|
+
#
|
|
80
|
+
# @return [Hash] Runtime info with keys: runtime, is_remote, is_standard, available_in
|
|
81
|
+
def get_runtime_info
|
|
82
|
+
{
|
|
83
|
+
runtime: @runtime_name,
|
|
84
|
+
is_remote: true,
|
|
85
|
+
is_standard: false,
|
|
86
|
+
available_in: [@runtime_name]
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
|
|
5
|
+
module Forthic
|
|
6
|
+
module Grpc
|
|
7
|
+
# RuntimeManager manages connections to remote Forthic runtimes
|
|
8
|
+
# Uses Singleton pattern to ensure a single registry of runtime connections
|
|
9
|
+
class RuntimeManager
|
|
10
|
+
include Singleton
|
|
11
|
+
|
|
12
|
+
attr_reader :connections
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@connections = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Connect to a remote runtime
|
|
19
|
+
# @param name [String] The name of the runtime (e.g., 'python', 'typescript')
|
|
20
|
+
# @param address [String] The gRPC address (e.g., 'localhost:50051')
|
|
21
|
+
# @return [GrpcClient] The client connection
|
|
22
|
+
def connect_runtime(name, address)
|
|
23
|
+
@connections[name] ||= GrpcClient.new(address)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Get an existing runtime connection
|
|
27
|
+
# @param name [String] The name of the runtime
|
|
28
|
+
# @return [GrpcClient, nil] The client connection or nil if not found
|
|
29
|
+
def get_runtime(name)
|
|
30
|
+
@connections[name]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Disconnect from a specific runtime
|
|
34
|
+
# @param name [String] The name of the runtime
|
|
35
|
+
def disconnect_runtime(name)
|
|
36
|
+
@connections[name]&.close
|
|
37
|
+
@connections.delete(name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Disconnect from all runtimes
|
|
41
|
+
def disconnect_all
|
|
42
|
+
@connections.each_value(&:close)
|
|
43
|
+
@connections.clear
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Check if a runtime is connected
|
|
47
|
+
# @param name [String] The name of the runtime
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def connected?(name)
|
|
50
|
+
@connections.key?(name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Get list of connected runtime names
|
|
54
|
+
# @return [Array<String>]
|
|
55
|
+
def connected_runtimes
|
|
56
|
+
@connections.keys
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'forthic_runtime_pb'
|
|
4
|
+
require 'date'
|
|
5
|
+
require 'time'
|
|
6
|
+
|
|
7
|
+
module Forthic
|
|
8
|
+
module Grpc
|
|
9
|
+
# Serializer for converting Ruby values to/from gRPC StackValue protobuf messages
|
|
10
|
+
#
|
|
11
|
+
# Handles all Forthic types:
|
|
12
|
+
# - Primitives: Integer, Float, String, Boolean, Nil
|
|
13
|
+
# - Collections: Array, Hash
|
|
14
|
+
# - Temporal: Date, Time, DateTime
|
|
15
|
+
#
|
|
16
|
+
# Pattern: Mirrors Python serializer.py and TypeScript serializer.ts
|
|
17
|
+
module Serializer
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Serialize a Ruby value to a StackValue protobuf message
|
|
21
|
+
#
|
|
22
|
+
# @param value [Object] Ruby value to serialize
|
|
23
|
+
# @return [Forthic::StackValue] Protobuf StackValue message
|
|
24
|
+
def serialize_value(value)
|
|
25
|
+
stack_value = Forthic::StackValue.new
|
|
26
|
+
|
|
27
|
+
case value
|
|
28
|
+
when Integer
|
|
29
|
+
stack_value.int_value = value
|
|
30
|
+
when Float
|
|
31
|
+
stack_value.float_value = value
|
|
32
|
+
when String
|
|
33
|
+
stack_value.string_value = value
|
|
34
|
+
when TrueClass, FalseClass
|
|
35
|
+
stack_value.bool_value = value
|
|
36
|
+
when NilClass
|
|
37
|
+
stack_value.null_value = Forthic::NullValue.new
|
|
38
|
+
when Array
|
|
39
|
+
stack_value.array_value = serialize_array(value)
|
|
40
|
+
when Hash
|
|
41
|
+
stack_value.record_value = serialize_record(value)
|
|
42
|
+
when Time, DateTime
|
|
43
|
+
# Handle Time and DateTime before Date (DateTime is a subclass of Date)
|
|
44
|
+
stack_value.instant_value = serialize_instant(value)
|
|
45
|
+
when Date
|
|
46
|
+
stack_value.plain_date_value = serialize_date(value)
|
|
47
|
+
else
|
|
48
|
+
raise ArgumentError, "Cannot serialize type #{value.class}: #{value.inspect}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
stack_value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Deserialize a StackValue protobuf message to a Ruby value
|
|
55
|
+
#
|
|
56
|
+
# @param stack_value [Forthic::StackValue] Protobuf message
|
|
57
|
+
# @return [Object] Ruby value
|
|
58
|
+
def deserialize_value(stack_value)
|
|
59
|
+
case stack_value.value
|
|
60
|
+
when :int_value
|
|
61
|
+
stack_value.int_value
|
|
62
|
+
when :float_value
|
|
63
|
+
stack_value.float_value
|
|
64
|
+
when :string_value
|
|
65
|
+
stack_value.string_value
|
|
66
|
+
when :bool_value
|
|
67
|
+
stack_value.bool_value
|
|
68
|
+
when :null_value
|
|
69
|
+
nil
|
|
70
|
+
when :array_value
|
|
71
|
+
deserialize_array(stack_value.array_value)
|
|
72
|
+
when :record_value
|
|
73
|
+
deserialize_record(stack_value.record_value)
|
|
74
|
+
when :plain_date_value
|
|
75
|
+
deserialize_date(stack_value.plain_date_value)
|
|
76
|
+
when :instant_value
|
|
77
|
+
deserialize_instant(stack_value.instant_value)
|
|
78
|
+
when :zoned_datetime_value
|
|
79
|
+
deserialize_zoned_datetime(stack_value.zoned_datetime_value)
|
|
80
|
+
else
|
|
81
|
+
raise ArgumentError, "Unknown StackValue type: #{stack_value.value}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Serialize a Ruby array to ArrayValue
|
|
86
|
+
#
|
|
87
|
+
# @param array [Array] Ruby array
|
|
88
|
+
# @return [Forthic::ArrayValue] Protobuf ArrayValue
|
|
89
|
+
def serialize_array(array)
|
|
90
|
+
array_value = Forthic::ArrayValue.new
|
|
91
|
+
# Use push to add items to repeated field (protobuf convention)
|
|
92
|
+
array.each do |item|
|
|
93
|
+
array_value.items.push(serialize_value(item))
|
|
94
|
+
end
|
|
95
|
+
array_value
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Deserialize an ArrayValue to Ruby array
|
|
99
|
+
#
|
|
100
|
+
# @param array_value [Forthic::ArrayValue] Protobuf ArrayValue
|
|
101
|
+
# @return [Array] Ruby array
|
|
102
|
+
def deserialize_array(array_value)
|
|
103
|
+
array_value.items.map { |item| deserialize_value(item) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Serialize a Ruby hash to RecordValue
|
|
107
|
+
#
|
|
108
|
+
# @param hash [Hash] Ruby hash with string keys
|
|
109
|
+
# @return [Forthic::RecordValue] Protobuf RecordValue
|
|
110
|
+
def serialize_record(hash)
|
|
111
|
+
record_value = Forthic::RecordValue.new
|
|
112
|
+
hash.each do |key, value|
|
|
113
|
+
record_value.fields[key.to_s] = serialize_value(value)
|
|
114
|
+
end
|
|
115
|
+
record_value
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Deserialize a RecordValue to Ruby hash
|
|
119
|
+
#
|
|
120
|
+
# @param record_value [Forthic::RecordValue] Protobuf RecordValue
|
|
121
|
+
# @return [Hash] Ruby hash with string keys
|
|
122
|
+
def deserialize_record(record_value)
|
|
123
|
+
result = {}
|
|
124
|
+
record_value.fields.each do |key, value|
|
|
125
|
+
result[key] = deserialize_value(value)
|
|
126
|
+
end
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Serialize a Ruby Date to PlainDateValue
|
|
131
|
+
#
|
|
132
|
+
# @param date [Date] Ruby Date
|
|
133
|
+
# @return [Forthic::PlainDateValue] Protobuf PlainDateValue
|
|
134
|
+
def serialize_date(date)
|
|
135
|
+
plain_date_value = Forthic::PlainDateValue.new
|
|
136
|
+
plain_date_value.iso8601_date = date.iso8601
|
|
137
|
+
plain_date_value
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Deserialize a PlainDateValue to Ruby Date
|
|
141
|
+
#
|
|
142
|
+
# @param plain_date_value [Forthic::PlainDateValue] Protobuf PlainDateValue
|
|
143
|
+
# @return [Date] Ruby Date
|
|
144
|
+
def deserialize_date(plain_date_value)
|
|
145
|
+
Date.iso8601(plain_date_value.iso8601_date)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Serialize a Ruby Time/DateTime to InstantValue
|
|
149
|
+
#
|
|
150
|
+
# @param time [Time, DateTime] Ruby Time or DateTime
|
|
151
|
+
# @return [Forthic::InstantValue] Protobuf InstantValue
|
|
152
|
+
def serialize_instant(time)
|
|
153
|
+
instant_value = Forthic::InstantValue.new
|
|
154
|
+
# Convert to UTC and format as ISO 8601 with timezone
|
|
155
|
+
if time.is_a?(DateTime)
|
|
156
|
+
# DateTime: convert to Time first
|
|
157
|
+
utc_time = time.to_time.utc
|
|
158
|
+
else
|
|
159
|
+
# Time: call utc directly
|
|
160
|
+
utc_time = time.utc
|
|
161
|
+
end
|
|
162
|
+
instant_value.iso8601 = utc_time.iso8601
|
|
163
|
+
instant_value
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Deserialize an InstantValue to Ruby Time
|
|
167
|
+
#
|
|
168
|
+
# @param instant_value [Forthic::InstantValue] Protobuf InstantValue
|
|
169
|
+
# @return [Time] Ruby Time (UTC)
|
|
170
|
+
def deserialize_instant(instant_value)
|
|
171
|
+
Time.iso8601(instant_value.iso8601)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Deserialize a ZonedDateTimeValue to Ruby Time with timezone
|
|
175
|
+
#
|
|
176
|
+
# @param zoned_value [Forthic::ZonedDateTimeValue] Protobuf ZonedDateTimeValue
|
|
177
|
+
# @return [Time] Ruby Time with timezone info
|
|
178
|
+
def deserialize_zoned_datetime(zoned_value)
|
|
179
|
+
# Parse ISO 8601 string, Ruby Time will preserve timezone
|
|
180
|
+
Time.iso8601(zoned_value.iso8601)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|