forthic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []