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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4e71f6368dc2bcc817f0095a389740a6676005ab8ddb862cdef02f0e0910d09d
4
+ data.tar.gz: b408177ce893d5353aa00ce2a73698005c07e22e3d68a8ec6cade6032887a591
5
+ SHA512:
6
+ metadata.gz: 2ffd692800bdf839d25a106c79bea485addaeabb7d2eafd5366a9d034927a642fed7b4ecb498e5f081016f5079077d3a4db4ba96eae113a8ce547024b4164736
7
+ data.tar.gz: b4ab0aec5f7b788873bbbc71761f5382f565864057101125919338c24453bdccdedbcf26f281dc5c46dee4a16fc7824a08516a52c9c318af91ab0f932a329f42
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-12-27
4
+
5
+ - Initial release
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :minitest do
19
+ # with Minitest::Unit
20
+ watch(%r{^test/(.*)\/?test_(.*)\.rb$})
21
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
22
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
23
+
24
+ # with Minitest::Spec
25
+ # watch(%r{^spec/(.*)_spec\.rb$})
26
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
27
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
28
+
29
+ # Rails 4
30
+ # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
31
+ # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
32
+ # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" }
33
+ # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
34
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
35
+ # watch(%r{^test/.+_test\.rb$})
36
+ # watch(%r{^test/test_helper\.rb$}) { 'test' }
37
+
38
+ # Rails < 4
39
+ # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
40
+ # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" }
41
+ # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
42
+ end
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Forthic
2
+
3
+ A Forthic interpreter that runs within Ruby.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ ```bash
10
+ bundle add forthic
11
+ ```
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ ```bash
16
+ gem install forthic
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Here's a basic example of how to use the Forthic interpreter:
22
+
23
+ ```ruby
24
+ require 'forthic'
25
+
26
+ interp = Forthic::Interpreter.new
27
+ interp.run("[1 2 3] '8 *' MAP")
28
+ puts interp.stack_pop
29
+
30
+ # Output:
31
+ #
32
+ # [ 8, 16, 24 ]
33
+ ```
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/linkedin/forthic.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[test standard]
11
+
12
+ task :guard do
13
+ sh "bundle exec guard"
14
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Forthic
4
+ class CodeLocation
5
+ attr_accessor :screen_name, :line, :column, :start_pos, :end_pos
6
+
7
+ # @param [String] screen_name
8
+ # @param [Integer] line
9
+ # @param [Integer] column
10
+ # @param [Integer] start_pos
11
+ # @param [Integer] end_pos
12
+ def initialize(screen_name: "<ad-hoc>", line: 1, column: 1, start_pos: 0, end_pos: 0)
13
+ @screen_name = screen_name
14
+ @line = line
15
+ @column = column
16
+ @start_pos = start_pos
17
+ @end_pos = end_pos
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Forthic
4
+ class ForthicError < StandardError
5
+ attr_accessor :error_key, :title, :description, :location, :caught_error
6
+
7
+ # @param [String] error_key
8
+ # @param [String] title
9
+ # @param [String] description
10
+ # @param [CodeLocation, nil] location
11
+ def initialize(error_key, title, description, location = nil)
12
+ @error_key = error_key
13
+ @title = title
14
+ @description = description
15
+ @location = location
16
+ @caught_error = nil
17
+ puts "ForthicError: #{error_key}, #{title}, #{description}, #{location}"
18
+ end
19
+
20
+ # @param [ForthicError] error
21
+ def set_caught_error(error)
22
+ @caught_error = error
23
+ end
24
+
25
+ # @return [String]
26
+ def get_title
27
+ @title
28
+ end
29
+
30
+ # @return [String]
31
+ def get_description
32
+ @description
33
+ end
34
+
35
+ # @return [Array<ForthicError>]
36
+ def get_error_stack
37
+ max_depth = 100
38
+ cur_error = self
39
+ result = [cur_error]
40
+
41
+ max_depth.times do
42
+ break unless cur_error.caught_error
43
+
44
+ result << cur_error.caught_error
45
+ cur_error = cur_error.caught_error
46
+ end
47
+
48
+ result.reverse
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'words/word'
4
+ require_relative 'words/module_word'
5
+ require_relative 'words/module_memo_word'
6
+ require_relative 'words/module_memo_bang_word'
7
+ require_relative 'words/module_memo_bang_at_word'
8
+ require_relative 'words/imported_word'
9
+ require_relative 'variable'
10
+
11
+ module Forthic
12
+ class ForthicModule
13
+ attr_accessor :words, :exportable, :variables, :modules, :module_prefixes, :required_modules, :name, :forthic_code, :module_id
14
+
15
+ # @param [String] name
16
+ # @param [Interpreter, nil] interp
17
+ # @param [String] forthic_code
18
+ def initialize(name, interp = nil, forthic_code = "")
19
+ @words = []
20
+ @exportable = []
21
+ @variables = {}
22
+ @modules = {}
23
+ @module_prefixes = {}
24
+ @required_modules = []
25
+ @name = name
26
+ @forthic_code = forthic_code
27
+ @module_id = "#{name}-#{rand(1_000_000)}"
28
+ end
29
+
30
+ # @return [ForthicModule]
31
+ def dup
32
+ result = ForthicModule.new(@name, @interp, @forthic_code)
33
+ result.words = @words.dup
34
+ result.exportable = @exportable.dup
35
+ @variables.each { |key, var| result.variables[key] = var.dup }
36
+ @modules.each { |key, mod| result.modules[key] = mod }
37
+ result.required_modules = @required_modules.dup
38
+ result.forthic_code = @forthic_code
39
+ result
40
+ end
41
+
42
+ # @param [String] prefix
43
+ # @param [ForthicModule] mod
44
+ def require_module(prefix, mod)
45
+ @required_modules << { prefix: prefix, module: mod }
46
+ end
47
+
48
+ # @param [String] name
49
+ # @return [ForthicModule, nil]
50
+ def find_module(name)
51
+ @modules[name]
52
+ end
53
+
54
+ # @param [String] word_name
55
+ # @param [Proc] word_func
56
+ def add_module_word(word_name, word_func)
57
+ add_exportable_word(ModuleWord.new(word_name, word_func))
58
+ end
59
+
60
+ # @param [Word] word
61
+ def add_word(word)
62
+ @words << word
63
+ end
64
+
65
+ # @param [Word] word
66
+ # @return [ModuleMemoWord]
67
+ def add_memo_words(word)
68
+ memo_word = ModuleMemoWord.new(word)
69
+ @words << memo_word
70
+ @words << ModuleMemoBangWord.new(memo_word)
71
+ @words << ModuleMemoBangAtWord.new(memo_word)
72
+ memo_word
73
+ end
74
+
75
+ # @param [Array<String>] names
76
+ def add_exportable(names)
77
+ @exportable.concat(names)
78
+ end
79
+
80
+ # @return [Array<Word>]
81
+ def exportable_words
82
+ @words.select { |word| @exportable.include?(word.name) }
83
+ end
84
+
85
+ # @param [Word] word
86
+ def add_exportable_word(word)
87
+ @words << word
88
+ @exportable << word.name
89
+ end
90
+
91
+ # @param [String] name
92
+ # @param [Object, nil] value
93
+ def add_variable(name, value = nil)
94
+ @variables[name] ||= Variable.new(name, value)
95
+ end
96
+
97
+ # @param [Interpreter] interp
98
+ def initialize_modules(interp)
99
+ @required_modules.each do |rec|
100
+ import_module(rec[:prefix], rec[:module], interp)
101
+ end
102
+ end
103
+
104
+ # @param [String] module_name
105
+ # @param [String] prefix
106
+ # @param [ForthicModule] mod
107
+ def register_module(module_name, prefix, mod)
108
+ @modules[module_name] = mod
109
+ @module_prefixes[module_name] ||= Set.new
110
+ @module_prefixes[module_name] << prefix
111
+ end
112
+
113
+ # @param [String] prefix
114
+ # @param [ForthicModule] mod
115
+ # @param [Interpreter] interp
116
+ def import_module(prefix, mod, interp)
117
+ new_module = mod.dup
118
+ new_module.initialize_modules(interp)
119
+
120
+ new_module.exportable_words.each do |word|
121
+ add_word(ImportedWord.new(word, prefix, new_module))
122
+ end
123
+ register_module(mod.name, prefix, new_module)
124
+ end
125
+
126
+ # @param [String] name
127
+ # @return [Word, nil]
128
+ def find_word(name)
129
+ find_dictionary_word(name) || find_variable(name)
130
+ end
131
+
132
+ # @param [String] word_name
133
+ # @return [Word, nil]
134
+ def find_dictionary_word(word_name)
135
+ @words.reverse.find { |w| w.name == word_name }
136
+ end
137
+
138
+ # @param [String] varname
139
+ # @return [PushValueWord, nil]
140
+ def find_variable(varname)
141
+ var_result = @variables[varname]
142
+ var_result ? PushValueWord.new(varname, var_result) : nil
143
+ end
144
+ end
145
+ end