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,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
require 'time'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Forthic
|
|
8
|
+
module WebSocket
|
|
9
|
+
# Serializer for converting Ruby values to/from JSON StackValue format
|
|
10
|
+
#
|
|
11
|
+
# Mirrors the gRPC serializer but outputs JSON instead of protobuf.
|
|
12
|
+
# Maintains the same structure as the gRPC StackValue messages for consistency.
|
|
13
|
+
#
|
|
14
|
+
# Handles all Forthic types:
|
|
15
|
+
# - Primitives: Integer, Float, String, Boolean, Nil
|
|
16
|
+
# - Collections: Array, Hash
|
|
17
|
+
# - Temporal: Date, Time, DateTime
|
|
18
|
+
#
|
|
19
|
+
# JSON format matches the WebSocket protocol specification in
|
|
20
|
+
# BROWSER_SERVER_WEBSOCKET_PLAN.md
|
|
21
|
+
module Serializer
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Serialize a Ruby value to a JSON StackValue hash
|
|
25
|
+
#
|
|
26
|
+
# @param value [Object] Ruby value to serialize
|
|
27
|
+
# @return [Hash] JSON-serializable hash with {type:, value:} structure
|
|
28
|
+
def serialize_value(value)
|
|
29
|
+
case value
|
|
30
|
+
when Integer
|
|
31
|
+
{ 'type' => 'int', 'value' => value }
|
|
32
|
+
when Float
|
|
33
|
+
{ 'type' => 'float', 'value' => value }
|
|
34
|
+
when String
|
|
35
|
+
{ 'type' => 'string', 'value' => value }
|
|
36
|
+
when TrueClass, FalseClass
|
|
37
|
+
{ 'type' => 'bool', 'value' => value }
|
|
38
|
+
when NilClass
|
|
39
|
+
{ 'type' => 'null', 'value' => nil }
|
|
40
|
+
when Array
|
|
41
|
+
{ 'type' => 'array', 'value' => value.map { |item| serialize_value(item) } }
|
|
42
|
+
when Hash
|
|
43
|
+
{ 'type' => 'record', 'value' => serialize_record(value) }
|
|
44
|
+
when Time, DateTime
|
|
45
|
+
# Handle Time and DateTime before Date (DateTime is a subclass of Date)
|
|
46
|
+
{ 'type' => 'instant', 'value' => serialize_instant(value) }
|
|
47
|
+
when Date
|
|
48
|
+
{ 'type' => 'plain_date', 'value' => value.iso8601 }
|
|
49
|
+
else
|
|
50
|
+
raise ArgumentError, "Cannot serialize type #{value.class}: #{value.inspect}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Deserialize a JSON StackValue hash to a Ruby value
|
|
55
|
+
#
|
|
56
|
+
# @param stack_value [Hash] JSON hash with {type:, value:} structure
|
|
57
|
+
# @return [Object] Ruby value
|
|
58
|
+
def deserialize_value(stack_value)
|
|
59
|
+
type = stack_value['type']
|
|
60
|
+
value = stack_value['value']
|
|
61
|
+
|
|
62
|
+
case type
|
|
63
|
+
when 'int'
|
|
64
|
+
value.to_i
|
|
65
|
+
when 'float'
|
|
66
|
+
value.to_f
|
|
67
|
+
when 'string'
|
|
68
|
+
value.to_s
|
|
69
|
+
when 'bool'
|
|
70
|
+
value
|
|
71
|
+
when 'null'
|
|
72
|
+
nil
|
|
73
|
+
when 'array'
|
|
74
|
+
value.map { |item| deserialize_value(item) }
|
|
75
|
+
when 'record'
|
|
76
|
+
deserialize_record(value)
|
|
77
|
+
when 'plain_date'
|
|
78
|
+
Date.iso8601(value)
|
|
79
|
+
when 'instant'
|
|
80
|
+
Time.iso8601(value)
|
|
81
|
+
when 'zoned_datetime'
|
|
82
|
+
Time.iso8601(value)
|
|
83
|
+
else
|
|
84
|
+
raise ArgumentError, "Unknown StackValue type: #{type}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Serialize a Ruby array to JSON array
|
|
89
|
+
#
|
|
90
|
+
# @param array [Array] Ruby array
|
|
91
|
+
# @return [Array<Hash>] Array of serialized StackValues
|
|
92
|
+
def serialize_array(array)
|
|
93
|
+
array.map { |item| serialize_value(item) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Deserialize a JSON array to Ruby array
|
|
97
|
+
#
|
|
98
|
+
# @param array_value [Array<Hash>] Array of JSON StackValues
|
|
99
|
+
# @return [Array] Ruby array
|
|
100
|
+
def deserialize_array(array_value)
|
|
101
|
+
array_value.map { |item| deserialize_value(item) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Serialize a Ruby hash to JSON record
|
|
105
|
+
#
|
|
106
|
+
# @param hash [Hash] Ruby hash with string keys
|
|
107
|
+
# @return [Hash] Hash with serialized StackValue values
|
|
108
|
+
def serialize_record(hash)
|
|
109
|
+
result = {}
|
|
110
|
+
hash.each do |key, value|
|
|
111
|
+
result[key.to_s] = serialize_value(value)
|
|
112
|
+
end
|
|
113
|
+
result
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Deserialize a JSON record to Ruby hash
|
|
117
|
+
#
|
|
118
|
+
# @param record_value [Hash] JSON record with StackValue values
|
|
119
|
+
# @return [Hash] Ruby hash with string keys
|
|
120
|
+
def deserialize_record(record_value)
|
|
121
|
+
result = {}
|
|
122
|
+
record_value.each do |key, value|
|
|
123
|
+
result[key] = deserialize_value(value)
|
|
124
|
+
end
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Serialize a Ruby Time/DateTime to ISO 8601 UTC string
|
|
129
|
+
#
|
|
130
|
+
# @param time [Time, DateTime] Ruby Time or DateTime
|
|
131
|
+
# @return [String] ISO 8601 UTC string
|
|
132
|
+
def serialize_instant(time)
|
|
133
|
+
if time.is_a?(DateTime)
|
|
134
|
+
# DateTime: convert to Time first
|
|
135
|
+
utc_time = time.to_time.utc
|
|
136
|
+
else
|
|
137
|
+
# Time: call utc directly
|
|
138
|
+
utc_time = time.utc
|
|
139
|
+
end
|
|
140
|
+
utc_time.iso8601
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Serialize a complete stack to JSON
|
|
144
|
+
#
|
|
145
|
+
# @param stack [Array] Array of Ruby values
|
|
146
|
+
# @return [Array<Hash>] Array of serialized StackValues
|
|
147
|
+
def serialize_stack(stack)
|
|
148
|
+
stack.map { |value| serialize_value(value) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Deserialize a JSON stack to Ruby array
|
|
152
|
+
#
|
|
153
|
+
# @param json_stack [Array<Hash>] Array of JSON StackValues
|
|
154
|
+
# @return [Array] Array of Ruby values
|
|
155
|
+
def deserialize_stack(json_stack)
|
|
156
|
+
json_stack.map { |stack_value| deserialize_value(stack_value) }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Forthic
|
|
6
|
+
# WordOptions - Type-safe options container for module words
|
|
7
|
+
#
|
|
8
|
+
# Overview:
|
|
9
|
+
# WordOptions provides a structured way for Forthic words to accept optional
|
|
10
|
+
# configuration parameters without requiring fixed parameter positions. This
|
|
11
|
+
# enables flexible, extensible APIs similar to keyword arguments in other languages.
|
|
12
|
+
#
|
|
13
|
+
# Usage in Forthic:
|
|
14
|
+
# [.option_name value ...] ~> WORD
|
|
15
|
+
#
|
|
16
|
+
# The ~> operator takes an options array and a word, passing the options as
|
|
17
|
+
# an additional parameter to words that support them.
|
|
18
|
+
#
|
|
19
|
+
# Example in Forthic code:
|
|
20
|
+
# [1 2 3] '2 *' [.with_key TRUE] ~> MAP
|
|
21
|
+
# [10 20 30] [.comparator "-1 *"] ~> SORT
|
|
22
|
+
# [[[1 2]]] [.depth 1] ~> FLATTEN
|
|
23
|
+
#
|
|
24
|
+
# Implementation in Module Words:
|
|
25
|
+
# Words declare options support by adding an options parameter:
|
|
26
|
+
#
|
|
27
|
+
# def MAP(items, forthic, options = {})
|
|
28
|
+
# with_key = options[:with_key] || options['with_key']
|
|
29
|
+
# push_error = options[:push_error] || options['push_error']
|
|
30
|
+
# # ... use options to modify behavior
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# The word decorator/registration automatically:
|
|
34
|
+
# 1. Checks if the top stack item is a WordOptions instance
|
|
35
|
+
# 2. Converts it to a plain Hash if present
|
|
36
|
+
# 3. Passes an empty {} if no options provided
|
|
37
|
+
#
|
|
38
|
+
# Common Patterns:
|
|
39
|
+
# - Boolean flags: options[:with_key]
|
|
40
|
+
# - Numeric values: options[:depth]
|
|
41
|
+
# - String values: options[:comparator]
|
|
42
|
+
# - Multiple options: All accessed from same options hash
|
|
43
|
+
#
|
|
44
|
+
# Internal Representation:
|
|
45
|
+
# Created from flat array: [.key1 val1 .key2 val2]
|
|
46
|
+
# Stored as Hash internally for efficient lookup
|
|
47
|
+
# Converted to Hash when passed to words
|
|
48
|
+
#
|
|
49
|
+
# Note: Dot-symbols in Forthic have the leading '.' already stripped,
|
|
50
|
+
# so keys come in as "key1", "key2", etc.
|
|
51
|
+
class WordOptions
|
|
52
|
+
# Create a new WordOptions from a flat array
|
|
53
|
+
#
|
|
54
|
+
# @param flat_array [Array] Array of alternating keys and values
|
|
55
|
+
# @raise [ArgumentError] if array is not even length or keys are not strings
|
|
56
|
+
def initialize(flat_array)
|
|
57
|
+
unless flat_array.is_a?(Array)
|
|
58
|
+
raise ArgumentError, "Options must be an array"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless flat_array.length.even?
|
|
62
|
+
raise ArgumentError,
|
|
63
|
+
"Options must be key-value pairs (even length). Got #{flat_array.length} elements"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@options = {}
|
|
67
|
+
|
|
68
|
+
flat_array.each_slice(2) do |key, value|
|
|
69
|
+
# Key should be a string (dot-symbol with . already stripped)
|
|
70
|
+
unless key.is_a?(String)
|
|
71
|
+
raise ArgumentError,
|
|
72
|
+
"Option key must be a string (dot-symbol). Got: #{key.class}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@options[key] = value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get option value with optional default
|
|
80
|
+
#
|
|
81
|
+
# @param key [String] The option key
|
|
82
|
+
# @param default_value [Object, nil] The default value if key not found
|
|
83
|
+
# @return [Object, nil] The option value or default
|
|
84
|
+
def get(key, default_value = nil)
|
|
85
|
+
@options.key?(key) ? @options[key] : default_value
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if option exists
|
|
89
|
+
#
|
|
90
|
+
# @param key [String] The option key
|
|
91
|
+
# @return [Boolean] true if option exists
|
|
92
|
+
def has?(key)
|
|
93
|
+
@options.key?(key)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get all options as plain hash
|
|
97
|
+
#
|
|
98
|
+
# @return [Hash] All options as a hash
|
|
99
|
+
def to_hash
|
|
100
|
+
@options.dup
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Alias for compatibility
|
|
104
|
+
alias to_record to_hash
|
|
105
|
+
|
|
106
|
+
# Get all option keys
|
|
107
|
+
#
|
|
108
|
+
# @return [Array<String>] All option keys
|
|
109
|
+
def keys
|
|
110
|
+
@options.keys
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# For debugging/display
|
|
114
|
+
#
|
|
115
|
+
# @return [String] String representation of options
|
|
116
|
+
def to_s
|
|
117
|
+
pairs = @options.map { |k, v| ".#{k} #{v.to_json}" }.join(" ")
|
|
118
|
+
"<WordOptions: #{pairs}>"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Inspect method for better debugging
|
|
122
|
+
alias inspect to_s
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Helper for words to check if top of stack is WordOptions
|
|
126
|
+
# and pop it if present. Returns empty hash if not present.
|
|
127
|
+
#
|
|
128
|
+
# @param interp [Interpreter] The interpreter instance
|
|
129
|
+
# @return [Hash] The options hash or empty hash
|
|
130
|
+
def self.pop_options_if_present(interp)
|
|
131
|
+
return {} if interp.get_stack.length.zero?
|
|
132
|
+
|
|
133
|
+
top = interp.stack_peek
|
|
134
|
+
if top.is_a?(WordOptions)
|
|
135
|
+
opts = interp.stack_pop
|
|
136
|
+
return opts.to_hash
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
{}
|
|
140
|
+
end
|
|
141
|
+
end
|
data/lib/forthic.rb
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Forthic - A stack-based programming language
|
|
4
|
+
#
|
|
5
|
+
# Main entry point for the Forthic Ruby runtime.
|
|
6
|
+
# Requires all core components and standard library modules.
|
|
4
7
|
|
|
8
|
+
# Core components
|
|
9
|
+
require_relative 'forthic/errors'
|
|
10
|
+
require_relative 'forthic/utils'
|
|
11
|
+
require_relative 'forthic/literals'
|
|
12
|
+
require_relative 'forthic/tokenizer'
|
|
13
|
+
require_relative 'forthic/word_options'
|
|
14
|
+
require_relative 'forthic/module'
|
|
15
|
+
require_relative 'forthic/interpreter'
|
|
16
|
+
|
|
17
|
+
# Decorators
|
|
18
|
+
require_relative 'forthic/decorators/word'
|
|
19
|
+
require_relative 'forthic/decorators/docs'
|
|
20
|
+
|
|
21
|
+
# Standard library modules
|
|
22
|
+
require_relative 'forthic/modules/standard/core_module'
|
|
23
|
+
require_relative 'forthic/modules/standard/array_module'
|
|
24
|
+
require_relative 'forthic/modules/standard/record_module'
|
|
25
|
+
require_relative 'forthic/modules/standard/string_module'
|
|
26
|
+
require_relative 'forthic/modules/standard/math_module'
|
|
27
|
+
require_relative 'forthic/modules/standard/boolean_module'
|
|
28
|
+
require_relative 'forthic/modules/standard/json_module'
|
|
29
|
+
require_relative 'forthic/modules/standard/datetime_module'
|
|
30
|
+
|
|
31
|
+
# Main module namespace
|
|
5
32
|
module Forthic
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
autoload :Token, "forthic/token"
|
|
9
|
-
autoload :PositionedString, "forthic/positioned_string"
|
|
10
|
-
autoload :ForthicError, "forthic/forthic_error"
|
|
11
|
-
autoload :Word, "forthic/words/word"
|
|
12
|
-
autoload :PushValueWord, "forthic/words/push_value_word"
|
|
13
|
-
autoload :DefinitionWord, "forthic/words/definition_word"
|
|
14
|
-
autoload :ModuleMemoWord, "forthic/words/module_memo_word"
|
|
15
|
-
autoload :ModuleMemoBangAtWord, "forthic/words/module_memo_bang_at_word"
|
|
16
|
-
autoload :ModuleMemoBangWord, "forthic/words/module_memo_bang_word"
|
|
17
|
-
autoload :ModuleMemoBangAtWord, "forthic/words/module_memo_bang_at_word"
|
|
18
|
-
autoload :EndArrayWord, "forthic/words/end_array_word"
|
|
19
|
-
autoload :StartModuleWord, "forthic/words/start_module_word"
|
|
20
|
-
autoload :EndModuleWord, "forthic/words/end_module_word"
|
|
21
|
-
autoload :MapWord, "forthic/words/map_word"
|
|
22
|
-
autoload :ForthicModule, "forthic/forthic_module"
|
|
23
|
-
autoload :GlobalModule, "forthic/global_module"
|
|
24
|
-
autoload :Interpreter, "forthic/interpreter"
|
|
33
|
+
# Version of the Forthic Ruby runtime
|
|
34
|
+
VERSION = '0.3.0'
|
|
25
35
|
end
|
data/protos/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Forthic gRPC Protocol Definitions
|
|
2
|
+
|
|
3
|
+
This directory contains versioned Protocol Buffer definitions for Forthic's multi-runtime communication.
|
|
4
|
+
|
|
5
|
+
## Versioning
|
|
6
|
+
|
|
7
|
+
Protocol definitions are organized by version to ensure compatibility:
|
|
8
|
+
|
|
9
|
+
- **v1/** - Initial gRPC protocol version
|
|
10
|
+
- Basic types: int, string, bool, float, null, array, record
|
|
11
|
+
- Temporal types: instant, plain_date, zoned_datetime
|
|
12
|
+
- Operations: ExecuteWord, ExecuteSequence, ListModules, GetModuleInfo
|
|
13
|
+
- Error handling with rich context
|
|
14
|
+
|
|
15
|
+
## Version History
|
|
16
|
+
|
|
17
|
+
### v1 (Current)
|
|
18
|
+
- Initial release supporting TypeScript ↔ Python ↔ Ruby ↔ Rust cross-runtime execution
|
|
19
|
+
- Full type serialization for all Forthic standard types
|
|
20
|
+
- Module discovery and introspection
|
|
21
|
+
- Enhanced error reporting with stack traces and context
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
The proto files are used to generate Ruby gRPC code:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Generate protobuf code
|
|
29
|
+
bundle exec rake generate_grpc
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The generated files are in `lib/forthic/grpc/`:
|
|
33
|
+
- `forthic_runtime_pb.rb` - Message definitions
|
|
34
|
+
- `forthic_runtime_services_pb.rb` - Service definitions
|
|
35
|
+
|
|
36
|
+
## Future Versions
|
|
37
|
+
|
|
38
|
+
When protocol changes are needed:
|
|
39
|
+
1. Copy current version to new directory (e.g., `v2/`)
|
|
40
|
+
2. Make changes in new version
|
|
41
|
+
3. Regenerate Ruby code from new version
|
|
42
|
+
4. Update client/server to support multiple versions if needed
|
|
43
|
+
5. Deprecate old versions with migration timeline
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package forthic;
|
|
4
|
+
|
|
5
|
+
// Service for executing Forthic words across runtime boundaries
|
|
6
|
+
service ForthicRuntime {
|
|
7
|
+
// Execute a single word in the remote runtime
|
|
8
|
+
rpc ExecuteWord(ExecuteWordRequest) returns (ExecuteWordResponse);
|
|
9
|
+
|
|
10
|
+
// Execute a sequence of words in one remote call (batched execution optimization)
|
|
11
|
+
rpc ExecuteSequence(ExecuteSequenceRequest) returns (ExecuteSequenceResponse);
|
|
12
|
+
|
|
13
|
+
// Phase 3: Module discovery
|
|
14
|
+
// List available runtime-specific modules (excludes standard library)
|
|
15
|
+
rpc ListModules(ListModulesRequest) returns (ListModulesResponse);
|
|
16
|
+
|
|
17
|
+
// Get detailed information about a specific module
|
|
18
|
+
rpc GetModuleInfo(GetModuleInfoRequest) returns (GetModuleInfoResponse);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Request to execute a word in a remote runtime
|
|
22
|
+
message ExecuteWordRequest {
|
|
23
|
+
// Name of the word to execute
|
|
24
|
+
string word_name = 1;
|
|
25
|
+
|
|
26
|
+
// Stack state before execution (simple types only for Phase 1)
|
|
27
|
+
repeated StackValue stack = 2;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Response from executing a word
|
|
31
|
+
message ExecuteWordResponse {
|
|
32
|
+
// Stack state after execution
|
|
33
|
+
repeated StackValue result_stack = 1;
|
|
34
|
+
|
|
35
|
+
// Error information if execution failed
|
|
36
|
+
optional ErrorInfo error = 2;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Request to execute a sequence of words in one batch
|
|
40
|
+
// Optimization: Reduces RPC round-trips for consecutive remote words
|
|
41
|
+
message ExecuteSequenceRequest {
|
|
42
|
+
// Names of words to execute in order
|
|
43
|
+
repeated string word_names = 1;
|
|
44
|
+
|
|
45
|
+
// Initial stack state before execution
|
|
46
|
+
repeated StackValue stack = 2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Response from executing a word sequence
|
|
50
|
+
message ExecuteSequenceResponse {
|
|
51
|
+
// Stack state after executing all words
|
|
52
|
+
repeated StackValue result_stack = 1;
|
|
53
|
+
|
|
54
|
+
// Error information if any word failed
|
|
55
|
+
optional ErrorInfo error = 2;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Represents a value on the Forthic stack
|
|
59
|
+
// Phase 2: Supports all basic Forthic types including arrays and records
|
|
60
|
+
// Phase 8: Added temporal types (instant, plain_date, zoned_datetime)
|
|
61
|
+
message StackValue {
|
|
62
|
+
oneof value {
|
|
63
|
+
int64 int_value = 1;
|
|
64
|
+
string string_value = 2;
|
|
65
|
+
bool bool_value = 3;
|
|
66
|
+
double float_value = 4;
|
|
67
|
+
NullValue null_value = 5;
|
|
68
|
+
ArrayValue array_value = 6;
|
|
69
|
+
RecordValue record_value = 7;
|
|
70
|
+
InstantValue instant_value = 8;
|
|
71
|
+
PlainDateValue plain_date_value = 9;
|
|
72
|
+
ZonedDateTimeValue zoned_datetime_value = 10;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Represents null/None value
|
|
77
|
+
message NullValue {
|
|
78
|
+
// Empty message, presence indicates null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Represents an array (list) of values
|
|
82
|
+
message ArrayValue {
|
|
83
|
+
repeated StackValue items = 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Represents a record (object/dict) with string keys
|
|
87
|
+
message RecordValue {
|
|
88
|
+
map<string, StackValue> fields = 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Phase 8: Temporal Types
|
|
92
|
+
|
|
93
|
+
// Represents an instant in time (UTC timestamp)
|
|
94
|
+
// Serialized as ISO 8601 string with timezone (e.g., "2025-01-15T10:30:00Z")
|
|
95
|
+
// Maps to:
|
|
96
|
+
// - TypeScript: Temporal.Instant
|
|
97
|
+
// - Python: datetime.datetime (timezone-aware)
|
|
98
|
+
message InstantValue {
|
|
99
|
+
string iso8601 = 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Represents a calendar date without time or timezone
|
|
103
|
+
// Serialized as ISO 8601 date string (e.g., "2025-01-15")
|
|
104
|
+
// Maps to:
|
|
105
|
+
// - TypeScript: Temporal.PlainDate
|
|
106
|
+
// - Python: datetime.date
|
|
107
|
+
message PlainDateValue {
|
|
108
|
+
string iso8601_date = 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Represents a date-time with timezone
|
|
112
|
+
// Serialized as ISO 8601 string with timezone (e.g., "2025-01-15T10:30:00-05:00[America/New_York]")
|
|
113
|
+
// Maps to:
|
|
114
|
+
// - TypeScript: Temporal.ZonedDateTime
|
|
115
|
+
// - Python: datetime.datetime (timezone-aware with tzinfo)
|
|
116
|
+
message ZonedDateTimeValue {
|
|
117
|
+
string iso8601 = 1;
|
|
118
|
+
string timezone = 2; // IANA timezone name (e.g., "America/New_York")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Phase 9: Enhanced error information from remote execution
|
|
122
|
+
message ErrorInfo {
|
|
123
|
+
// Error message
|
|
124
|
+
string message = 1;
|
|
125
|
+
|
|
126
|
+
// Runtime where error occurred (e.g., "python", "ruby", "typescript")
|
|
127
|
+
string runtime = 2;
|
|
128
|
+
|
|
129
|
+
// Stack trace from the remote runtime
|
|
130
|
+
repeated string stack_trace = 3;
|
|
131
|
+
|
|
132
|
+
// Error type/class name (e.g., "ValueError", "TypeError")
|
|
133
|
+
string error_type = 4;
|
|
134
|
+
|
|
135
|
+
// Word location where error occurred (if available)
|
|
136
|
+
optional string word_location = 5;
|
|
137
|
+
|
|
138
|
+
// Module name where error occurred (if available)
|
|
139
|
+
optional string module_name = 6;
|
|
140
|
+
|
|
141
|
+
// Additional context information
|
|
142
|
+
map<string, string> context = 7;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Phase 3: Module Discovery Messages
|
|
146
|
+
|
|
147
|
+
// Request to list available runtime-specific modules
|
|
148
|
+
message ListModulesRequest {
|
|
149
|
+
// Empty for now - future: could add filters
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Response with list of available modules
|
|
153
|
+
message ListModulesResponse {
|
|
154
|
+
repeated ModuleSummary modules = 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Summary information about a module
|
|
158
|
+
message ModuleSummary {
|
|
159
|
+
// Module name
|
|
160
|
+
string name = 1;
|
|
161
|
+
|
|
162
|
+
// Brief description
|
|
163
|
+
string description = 2;
|
|
164
|
+
|
|
165
|
+
// Number of words in the module
|
|
166
|
+
int32 word_count = 3;
|
|
167
|
+
|
|
168
|
+
// Whether this is a runtime-specific module (e.g., pandas)
|
|
169
|
+
bool runtime_specific = 4;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Request detailed information about a specific module
|
|
173
|
+
message GetModuleInfoRequest {
|
|
174
|
+
// Name of the module
|
|
175
|
+
string module_name = 1;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Response with detailed module information
|
|
179
|
+
message GetModuleInfoResponse {
|
|
180
|
+
// Module name
|
|
181
|
+
string name = 1;
|
|
182
|
+
|
|
183
|
+
// Module description
|
|
184
|
+
string description = 2;
|
|
185
|
+
|
|
186
|
+
// List of words in the module
|
|
187
|
+
repeated WordInfo words = 3;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Information about a single word
|
|
191
|
+
message WordInfo {
|
|
192
|
+
// Word name
|
|
193
|
+
string name = 1;
|
|
194
|
+
|
|
195
|
+
// Stack effect notation (e.g., "( a b -- c )")
|
|
196
|
+
string stack_effect = 2;
|
|
197
|
+
|
|
198
|
+
// Description of what the word does
|
|
199
|
+
string description = 3;
|
|
200
|
+
}
|