forthic 0.1.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.
@@ -0,0 +1,34 @@
1
+ # # frozen_string_literal: true
2
+
3
+ module Forthic
4
+ class Variable
5
+ attr_accessor :name, :value
6
+
7
+ # @param [String] name
8
+ # @param [Object] value
9
+ def initialize(name, value = nil)
10
+ @name = name
11
+ @value = value
12
+ end
13
+
14
+ # @return [String]
15
+ def get_name
16
+ @name
17
+ end
18
+
19
+ # @param [Object] val
20
+ def set_value(val)
21
+ @value = val
22
+ end
23
+
24
+ # @return [Object]
25
+ def get_value
26
+ @value
27
+ end
28
+
29
+ # @return [Variable]
30
+ def dup
31
+ Variable.new(@name, @value)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Forthic
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,40 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+ require_relative '../forthic_error'
5
+
6
+ module Forthic
7
+ class DefinitionWord < Word
8
+ attr_accessor :words, :cur_index
9
+
10
+ # @param [String] name
11
+ def initialize(name)
12
+ super(name)
13
+ @words = []
14
+ @cur_index = 0
15
+ end
16
+
17
+ # @param [Word] word
18
+ def add_word(word)
19
+ @words.push(word)
20
+ end
21
+
22
+ # @param [Interpreter] interp
23
+ def execute(interp)
24
+ @words.each do |word|
25
+ begin
26
+ word.execute(interp)
27
+ rescue => e
28
+ error = ForthicError.new(
29
+ "definition_word-29",
30
+ "Error executing word #{word.name}",
31
+ "Error in #{self.name} definition",
32
+ interp.get_string_location
33
+ )
34
+ error.set_caught_error(e)
35
+ raise error
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+ require_relative '../token'
5
+
6
+ module Forthic
7
+ class EndArrayWord < Word
8
+ def initialize
9
+ super("]")
10
+ end
11
+
12
+ # @param [Interpreter] interp
13
+ def execute(interp)
14
+ items = []
15
+ item = interp.stack_pop
16
+
17
+ # NOTE: This won't infinite loop because interp.stack_pop() will eventually fail
18
+ loop do
19
+ break if item.is_a?(Token) && item.type == TokenType::START_ARRAY
20
+ items.push(item)
21
+ item = interp.stack_pop
22
+ end
23
+
24
+ items.reverse!
25
+ interp.stack_push(items)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+
5
+ module Forthic
6
+ class EndModuleWord < Word
7
+ def initialize
8
+ super("}")
9
+ end
10
+
11
+ # @param [Interpreter] interp
12
+ def execute(interp)
13
+ interp.module_stack_pop
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+ require_relative 'module_word'
5
+
6
+ module Forthic
7
+ class ImportedWord < Word
8
+ attr_accessor :module_word, :imported_module
9
+
10
+ # @param [Word] module_word
11
+ # @param [String] prefix
12
+ # @param [ModuleWord] imported_module
13
+ def initialize(module_word, prefix, imported_module)
14
+ prefix = prefix.empty? ? "" : "#{prefix}."
15
+ super("#{prefix}#{module_word.name}")
16
+ @module_word = module_word
17
+ @imported_module = imported_module
18
+ end
19
+
20
+ # @param [Interpreter] interp
21
+ def execute(interp)
22
+ interp.module_stack_push(@imported_module)
23
+ @module_word.execute(interp)
24
+ interp.module_stack_pop
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Forthic
4
+ class MapWord
5
+ attr_accessor :forthic, :forthic_location, :items, :flags, :depth, :num_interps, :push_error, :with_key, :cur_index, :result, :errors, :is_debugging, :processing_item, :is_done
6
+
7
+ def initialize(items, forthic, forthic_location, flags)
8
+ @forthic = forthic
9
+ @forthic_location = forthic_location
10
+ @items = items
11
+ @flags = flags
12
+
13
+ # MAP flags
14
+ @depth = flags[:depth] || 0
15
+ @num_interps = flags[:interps] || 1
16
+ @push_error = flags[:push_error]
17
+ @with_key = flags[:with_key]
18
+
19
+ @cur_index = 0
20
+ @result = []
21
+ @errors = []
22
+ @is_debugging = false
23
+ @processing_item = false
24
+ @is_done = false
25
+ end
26
+
27
+ def execute(interp)
28
+ normal_execute(interp)
29
+ end
30
+
31
+ def normal_execute(interp)
32
+ @is_debugging = false
33
+ items = @items
34
+ if !items || items.empty?
35
+ interp.stack_push(items)
36
+ return
37
+ end
38
+
39
+ @result = []
40
+ @errors = []
41
+ if @num_interps > 1
42
+ interp.stack_push(items)
43
+ interp.run("LENGTH")
44
+ num_items = interp.stack_pop
45
+ group_size = (num_items.to_f / @num_interps).ceil
46
+ interp.stack_push(items)
47
+ interp.stack_push(group_size)
48
+ interp.run("GROUPS-OF")
49
+ groups = interp.stack_pop
50
+
51
+ # Clone and load up interpreters
52
+ interp_runs = []
53
+
54
+ groups.each do |group|
55
+ new_interp = interp.dup
56
+ interp_run = -> { map(new_interp, group) }
57
+ interp_runs.push(interp_run)
58
+ end
59
+
60
+ # Run in parallel using threads
61
+ threads = interp_runs.map do |interp_run|
62
+ Thread.new { interp_run.call }
63
+ end
64
+ run_results = threads.map(&:value)
65
+
66
+ # Gather results
67
+ is_array = items.is_a?(Array)
68
+ array_result = []
69
+ object_result = {}
70
+ errors = []
71
+ run_results.each do |res|
72
+ if is_array
73
+ array_result.concat(res[0])
74
+ else
75
+ object_result.merge!(res[0])
76
+ end
77
+ errors.concat(res[1])
78
+ end
79
+ @result = is_array ? array_result : object_result
80
+ @errors = errors
81
+ else
82
+ map(interp, items)
83
+ end
84
+
85
+ # Return results
86
+ interp.stack_push(@result)
87
+ interp.stack_push(@errors) if @push_error
88
+ end
89
+
90
+ def map(interp, items)
91
+ forthic = @forthic
92
+ forthic_location = @forthic_location
93
+ self_ref = self
94
+
95
+ if !items
96
+ interp.stack_push(items)
97
+ return
98
+ end
99
+
100
+ # This maps the forthic over an item, storing errors if needed
101
+ map_value = lambda do |key, value, errors|
102
+ interp.stack_push(key) if self_ref.with_key
103
+ interp.stack_push(value)
104
+
105
+ if self_ref.push_error
106
+ error = nil
107
+ begin
108
+ # If this runs successfully, it would have pushed the result onto the stack
109
+ interp.run(forthic, forthic_location)
110
+ rescue => e
111
+ # Since this didn't run successfully, push nil onto the stack
112
+ interp.stack_push(nil)
113
+ error = e
114
+ end
115
+ errors.push(error)
116
+ else
117
+ interp.run(forthic, forthic_location)
118
+ end
119
+ interp.stack_pop
120
+ end
121
+
122
+ # This recursively descends a record structure
123
+ descend_record = lambda do |record, depth, accum, errors|
124
+ record.each do |k, item|
125
+ if depth > 0
126
+ if item.is_a?(Array)
127
+ accum[k] = []
128
+ descend_list.call(item, depth - 1, accum[k], errors)
129
+ else
130
+ accum[k] = {}
131
+ descend_record.call(item, depth - 1, accum[k], errors)
132
+ end
133
+ else
134
+ accum[k] = map_value.call(k, item, errors)
135
+ end
136
+ end
137
+ accum
138
+ end
139
+
140
+ # This recursively descends a list
141
+ descend_list = lambda do |items, depth, accum, errors|
142
+ items.each_with_index do |item, i|
143
+ if depth > 0
144
+ if item.is_a?(Array)
145
+ accum.push([])
146
+ descend_list.call(item, depth - 1, accum.last, errors)
147
+ else
148
+ accum.push({})
149
+ descend_record.call(item, depth - 1, accum.last, errors)
150
+ end
151
+ else
152
+ accum.push(map_value.call(i, item, errors))
153
+ end
154
+ end
155
+ accum
156
+ end
157
+
158
+ errors = []
159
+ result = if items.is_a?(Array)
160
+ descend_list.call(items, @depth, [], errors)
161
+ else
162
+ descend_record.call(items, @depth, {}, errors)
163
+ end
164
+ @result = result
165
+ @errors = errors
166
+ [result, errors]
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,22 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+ require_relative 'module_memo_word'
5
+
6
+ module Forthic
7
+ class ModuleMemoBangAtWord < Word
8
+ attr_accessor :memo_word
9
+
10
+ # @param [ModuleMemoWord] memo_word
11
+ def initialize(memo_word)
12
+ super("#{memo_word.name}!@")
13
+ @memo_word = memo_word
14
+ end
15
+
16
+ # @param [Interpreter] interp
17
+ def execute(interp)
18
+ @memo_word.refresh(interp)
19
+ interp.stack_push(@memo_word.value)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+ require_relative 'module_memo_word'
5
+
6
+ module Forthic
7
+ class ModuleMemoBangWord < Word
8
+ attr_accessor :memo_word
9
+
10
+ # @param [ModuleMemoWord] memo_word
11
+ def initialize(memo_word)
12
+ super("#{memo_word.name}!")
13
+ @memo_word = memo_word
14
+ end
15
+
16
+ # @param [Interpreter] interp
17
+ def execute(interp)
18
+ @memo_word.refresh(interp)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+
5
+ module Forthic
6
+ class ModuleMemoWord < Word
7
+ attr_accessor :word, :has_value, :value
8
+
9
+ # @param [Word] word
10
+ def initialize(word)
11
+ super(word.name)
12
+ @word = word
13
+ @has_value = false
14
+ @value = nil
15
+ end
16
+
17
+ # @param [Interpreter] interp
18
+ def refresh(interp)
19
+ @word.execute(interp)
20
+ @value = interp.stack_pop
21
+ @has_value = true
22
+ end
23
+
24
+ # @param [Interpreter] interp
25
+ def execute(interp)
26
+ refresh(interp) unless @has_value
27
+ interp.stack_push(@value)
28
+ end
29
+
30
+ def reset
31
+ @has_value = false
32
+ @value = nil
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+
5
+ module Forthic
6
+ class ModuleWord < Word
7
+ attr_accessor :handler
8
+
9
+ # @param [String] name
10
+ # @param [Proc] handler
11
+ def initialize(name, handler)
12
+ super(name)
13
+ @handler = handler
14
+ end
15
+
16
+ # @param [Interpreter] interp
17
+ def execute(interp)
18
+ @handler.call(interp)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+
5
+ module Forthic
6
+ class PushValueWord < Word
7
+ attr_accessor :value
8
+
9
+ # @param [String] name
10
+ # @param [Object] value
11
+ def initialize(name, value)
12
+ super(name)
13
+ @value = value
14
+ end
15
+
16
+ # @param [Interpreter] interp
17
+ def execute(interp, _options = {})
18
+ interp.stack_push(@value)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # # frozen_string_literal: true
2
+
3
+ require_relative 'word'
4
+ require_relative '../forthic_module'
5
+
6
+ module Forthic
7
+ class StartModuleWord < Word
8
+ # @param [Interpreter] interp
9
+ def execute(interp)
10
+ # The app module is the only module with a blank name
11
+ if self.name == ""
12
+ interp.module_stack_push(interp.get_app_module)
13
+ return
14
+ end
15
+
16
+ # If the module is used by the current module, push it onto the stack, otherwise
17
+ # create a new module.
18
+ mod = interp.cur_module.find_module(self.name)
19
+ unless mod
20
+ mod = ForthicModule.new(self.name)
21
+ interp.cur_module.register_module(mod.name, mod.name, mod)
22
+
23
+ # If we're at the app module, also register with interpreter
24
+ if interp.cur_module.name == ""
25
+ interp.register_module(mod)
26
+ end
27
+ end
28
+ interp.module_stack_push(mod)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Forthic
4
+ class Word
5
+ attr_accessor :name, :string, :location
6
+
7
+ # @param [String] name
8
+ def initialize(name)
9
+ @name = name
10
+ @string = name
11
+ @location = nil
12
+ end
13
+
14
+ # @param [CodeLocation] location
15
+ def set_location(location)
16
+ @location = location
17
+ end
18
+
19
+ # @return [CodeLocation, nil]
20
+ def get_location
21
+ @location
22
+ end
23
+
24
+ # @param [Interpreter] _interp
25
+ # @param [Hash] _options
26
+ def execute(_interp, _options = {})
27
+ raise "Must override Word.execute"
28
+ end
29
+ end
30
+ end
data/lib/forthic.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "forthic/version"
4
+
5
+ module Forthic
6
+ autoload :Tokenizer, 'forthic/tokenizer'
7
+ autoload :CodeLocation, 'forthic/code_location'
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'
25
+ end
data/sig/forthic.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Forthic
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forthic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rino Jose
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: This package provides a Forthic interpreter that allows you to execute
13
+ Forthic code within your Ruby projects. Forthic is a stack-based programming language
14
+ inspired by Forth.
15
+ email:
16
+ - rjose@forthix.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".standard.yml"
22
+ - CHANGELOG.md
23
+ - Guardfile
24
+ - README.md
25
+ - Rakefile
26
+ - lib/forthic.rb
27
+ - lib/forthic/code_location.rb
28
+ - lib/forthic/forthic_error.rb
29
+ - lib/forthic/forthic_module.rb
30
+ - lib/forthic/global_module.rb
31
+ - lib/forthic/interpreter.rb
32
+ - lib/forthic/positioned_string.rb
33
+ - lib/forthic/token.rb
34
+ - lib/forthic/tokenizer.rb
35
+ - lib/forthic/variable.rb
36
+ - lib/forthic/version.rb
37
+ - lib/forthic/words/definition_word.rb
38
+ - lib/forthic/words/end_array_word.rb
39
+ - lib/forthic/words/end_module_word.rb
40
+ - lib/forthic/words/imported_word.rb
41
+ - lib/forthic/words/map_word.rb
42
+ - lib/forthic/words/module_memo_bang_at_word.rb
43
+ - lib/forthic/words/module_memo_bang_word.rb
44
+ - lib/forthic/words/module_memo_word.rb
45
+ - lib/forthic/words/module_word.rb
46
+ - lib/forthic/words/push_value_word.rb
47
+ - lib/forthic/words/start_module_word.rb
48
+ - lib/forthic/words/word.rb
49
+ - sig/forthic.rbs
50
+ homepage: https://github.com/linkedin/forthic
51
+ licenses: []
52
+ metadata:
53
+ homepage_uri: https://github.com/linkedin/forthic
54
+ source_code_uri: https://github.com/linkedin/forthic
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 3.1.0
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 3.6.2
70
+ specification_version: 4
71
+ summary: A Forthic interpreter that runs within Ruby.
72
+ test_files: []