rubasteme 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7318849fdcbcfd2daa359998f303bba5c27429bce2a36f06e9beddf05d26ba8a
4
+ data.tar.gz: 0037b93486d56d45c125ec47aa5417de6f61dbb6519679154974ac039206a738
5
+ SHA512:
6
+ metadata.gz: 10e44958db29f64d4b17e0db107756e8952393941b7305f9b32adf79b5f71ca18faa234d99cf24ceb5d6da6023e4855260998d16c50d105d002645d80231aa15
7
+ data.tar.gz: 7abdcb827afb2cbbe428e1ac59167d98f21c07ab83046e212cd48af734cc4245f32de39f7b36741f46a740f8787e1b08c981f91dce62154070704140ff0f463c
@@ -0,0 +1,18 @@
1
+ name: Build
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.0.1
14
+ - name: Run the default task
15
+ run: |
16
+ gem install bundler -v 2.2.15
17
+ bundle install
18
+ bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,57 @@
1
+ Gemfile.lock
2
+ *.gem
3
+ *.rbc
4
+ /.config
5
+ /coverage/
6
+ /InstalledFiles
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /test/tmp/
11
+ /test/version_tmp/
12
+ /tmp/
13
+
14
+ # Used by dotenv library to load environment variables.
15
+ # .env
16
+
17
+ # Ignore Byebug command history file.
18
+ .byebug_history
19
+
20
+ ## Specific to RubyMotion:
21
+ .dat*
22
+ .repl_history
23
+ build/
24
+ *.bridgesupport
25
+ build-iPhoneOS/
26
+ build-iPhoneSimulator/
27
+
28
+ ## Specific to RubyMotion (use of CocoaPods):
29
+ #
30
+ # We recommend against adding the Pods directory to your .gitignore. However
31
+ # you should judge for yourself, the pros and cons are mentioned at:
32
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
33
+ #
34
+ # vendor/Pods/
35
+
36
+ ## Documentation cache and generated files:
37
+ /.yardoc/
38
+ /_yardoc/
39
+ /doc/
40
+ /rdoc/
41
+
42
+ ## Environment normalization:
43
+ /.bundle/
44
+ /vendor/bundle
45
+ /lib/bundler/man/
46
+
47
+ # for a library or gem, you might want to ignore these files since the code is
48
+ # intended to run in multiple environments; otherwise, check them in:
49
+ # Gemfile.lock
50
+ # .ruby-version
51
+ # .ruby-gemset
52
+
53
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
54
+ .rvmrc
55
+
56
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
57
+ # .rubocop-https?--*
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+ - (nothing to record here)
3
+
4
+ ## [0.1.0] - 2021-05-20
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in rubasteme.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 mnbi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Rubasteme
2
+
3
+ [![Build Status](https://github.com/mnbi/rubasteme/workflows/Build/badge.svg)](https://github.com/mnbi/rubasteme/actions?query=workflow%3A"Build")
4
+
5
+ Simple Abstract Syntax Tree and a parser for Scheme written in Ruby.
6
+
7
+ It is intended to be used as a part of a Scheme implementation written
8
+ in Ruby.
9
+
10
+ - Rubasteme is a set of classes to represent an AST for Scheme language.
11
+ - Currently (in 0.1.0), it does support partially the language
12
+ specification of Scheme.
13
+ - Rubasteme does not provide any features for lexical analysis.
14
+ - Instead, it uses `rbscmlex` to execute lexical analysis of Scheme
15
+ program.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'rubasteme'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle install
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install rubasteme
32
+
33
+ ## Usage
34
+
35
+ See the directory, `examples`. There are 2 small implementations of
36
+ subset for Scheme language. Though both have very limited
37
+ capabilities as Scheme interpreter, they can execute the following
38
+ code:
39
+
40
+ ``` scheme
41
+ (define (fact n)
42
+ (define (fact-iter n r c)
43
+ (if (< n c)
44
+ r
45
+ (fact-iter n (* r c) (+ c 1))))
46
+ (fact-iter n 1 1))
47
+
48
+ (display (fact 10))
49
+ (display (fact 100))
50
+ (display (fact 1000))
51
+ ```
52
+
53
+ ``` scheme
54
+ (define (iota-iter result count start step)
55
+ (if (zero? count)
56
+ result
57
+ (iota-iter (append result (list start))
58
+ (- count 1)
59
+ (+ start step)
60
+ step)))
61
+
62
+ (define (iota count start step)
63
+ (iota-iter () count start step))
64
+
65
+ (display (iota 10 1 1))
66
+ (display (iota 10 1/9 11/99))
67
+ ```
68
+
69
+ ### How to execute
70
+
71
+ 1. Save the above code in a file.
72
+ 2. Run `examples/mini_rus3` or `examples/mini_sicp_scheme`
73
+ 3. Input `(load "some_file.scm")` after the prompt, then hit return key.
74
+
75
+ Or, input any arbitrary expressions after the prompt. Note that the
76
+ current REPL does not support to input expression in multi-lines.
77
+ Each expression must be input entirely in a single line.
78
+
79
+ ### mini Rus3 (`mini_rus3`)
80
+
81
+ It is intended to show how to use Ruby classes of AST nodes.
82
+
83
+ ### SICP Scheme (`mini_sicp_scheme`)
84
+
85
+ It is intended to show how to use AST nodes those are represented as
86
+ "tagged Array."
87
+
88
+ ## Abstract Syntax Tree
89
+
90
+ ### Supported Types
91
+
92
+ - empty list
93
+ - boolean
94
+ - identifier
95
+ - character
96
+ - string
97
+ - number
98
+ - program
99
+ - list
100
+ - quotation
101
+ - procedure call
102
+ - lambda expression
103
+ - conditional
104
+ - assignment
105
+ - identifier definition
106
+ - cond
107
+ - and
108
+ - or
109
+ - when
110
+ - unless
111
+ - let
112
+ - let*
113
+ - letrec
114
+ - letrec*
115
+ - do
116
+
117
+ ## TODO
118
+
119
+ - Add more documentation.
120
+ - Implement more types.
121
+ - Re-factor code.
122
+
123
+ ## Development
124
+
125
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
126
+
127
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
128
+
129
+ ## Contributing
130
+
131
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/mnbi/rubasteme](https://github.com/mnbi/rubasteme).
132
+
133
+
134
+ ## License
135
+
136
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
13
+
14
+ require "rdoc/task"
15
+
16
+ RDoc::Task.new do |rdoc|
17
+ rdoc.generator = "ri"
18
+ rdoc.rdoc_dir = "rdoc"
19
+ rdoc.rdoc_files.include("lib/**/*.rb")
20
+ end
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "rubasteme"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubasteme"
4
+
5
+ def usage
6
+ puts <<HELP
7
+ usage:
8
+ mini_rus3 [option]
9
+ option:
10
+ -v, --version : print version
11
+ -h, --help : show this message
12
+ HELP
13
+ end
14
+
15
+ opts = {}
16
+ while ARGV.size > 0
17
+ arg = ARGV.shift
18
+ case arg
19
+ when "-v", "--version"
20
+ puts "mini_rus3 (Rubasteme :version #{Rubasteme::VERSION} :release #{Rubasteme::RELEASE})"
21
+ exit 0
22
+ when "-h", "--help"
23
+ usage
24
+ exit 0
25
+ end
26
+ end
27
+
28
+ class Evaluator
29
+ def version
30
+ "(sample-evaluator :version 0.1.0 :release 2021-05-20)"
31
+ end
32
+
33
+ def initialize
34
+ @env = Kernel.binding
35
+ {
36
+ "+" => "add",
37
+ "-" => "subtract",
38
+ "*" => "mul",
39
+ "/" => "div",
40
+ "%" => "mod",
41
+ "<" => "lt?",
42
+ "<=" => "le?",
43
+ ">" => "gt?",
44
+ ">=" => "ge?",
45
+ "==" => "eqv?",
46
+ }.each { |op, proc_name|
47
+ @env.receiver.instance_eval("def #{proc_name}(op1, op2); op1 #{op} op2; end")
48
+ }
49
+ @env.receiver.instance_eval {
50
+ def display(obj)
51
+ pp obj
52
+ end
53
+ }
54
+ end
55
+
56
+ def eval(ast_node)
57
+ rb_src =
58
+ case ast_node.type
59
+ when :ast_program
60
+ ast_node.map{|node| translate(node)}.join("; ")
61
+ else
62
+ translate(ast_node)
63
+ end
64
+
65
+ rb_eval(rb_src)
66
+ end
67
+
68
+ private
69
+
70
+ def rb_eval(rb_src)
71
+ @env.receiver.instance_eval(rb_src)
72
+ end
73
+
74
+ def translate(ast_node)
75
+ case ast_node.type
76
+ when :ast_empty_list
77
+ ast_node.literal
78
+ when :ast_boolean
79
+ (ast_node.literal[1] == "f") ? "false" : "true"
80
+ when :ast_identifier
81
+ translate_identifier(ast_node)
82
+ when :ast_string
83
+ ast_node.literal
84
+ when :ast_number
85
+ ast_node.literal
86
+ when :ast_procedure_call
87
+ translate_procedure_call(ast_node)
88
+ when :ast_lambda_expression
89
+ translate_lambda_expression(ast_node)
90
+ when :ast_conditional
91
+ translate_conditional(ast_node)
92
+ when :ast_identifier_definition
93
+ translate_identifier_definition(ast_node)
94
+ else
95
+ puts "not implemented yet to evaluate AST node type(%s)" % ast_node.type
96
+ end
97
+ end
98
+
99
+ CHAR_MAP = {
100
+ "-" => "_",
101
+ ">" => "to_",
102
+ }
103
+
104
+ OPERATOR_MAP = {
105
+ "+" => "add",
106
+ "-" => "subtract",
107
+ "*" => "mul",
108
+ "/" => "div",
109
+ "%" => "mod",
110
+ "=" => "eqv?",
111
+ "<" => "lt?",
112
+ ">" => "gt?",
113
+ "<=" => "le?",
114
+ ">=" => "ge?",
115
+ }
116
+
117
+ def translate_identifier(ast_node)
118
+ id = ast_node.literal
119
+ return OPERATOR_MAP[id] if OPERATOR_MAP.key?(id)
120
+ id.gsub!(/[\->]/, CHAR_MAP) if /[\->]/ === id
121
+ id
122
+ end
123
+
124
+ def translate_procedure_call(ast_node)
125
+ operands = ast_node.operands.map{|e| translate(e)}.join(", ")
126
+ operator = translate(ast_node.operator)
127
+ if ast_node.operator.type == :ast_lambda_expression
128
+ "#{operator}.call(#{operands})"
129
+ else
130
+ "#{operator}(#{operands})"
131
+ end
132
+ end
133
+
134
+ def translate_lambda_expression(ast_node)
135
+ formals = ast_node.formals.map{|e| translate(e)}.join(",")
136
+ body = ast_node.body.map{|e| translate(e)}.join("; ")
137
+ "lambda{|#{formals}|#{body}}"
138
+ end
139
+
140
+ def translate_conditional(ast_node)
141
+ test = translate(ast_node.test)
142
+ consequent = translate(ast_node.consequent)
143
+ alternate = translate(ast_node.alternate) if ast_node.alternate?
144
+ rb_src = "if #{test}; #{consequent}"
145
+ rb_src += "; else; #{alternate}" if alternate
146
+ rb_src += "; end"
147
+ rb_src
148
+ end
149
+
150
+ def translate_identifier_definition(ast_node)
151
+ name = translate(ast_node.identifier)
152
+ if ast_node.expression.type == :ast_lambda_expression
153
+ node = ast_node.expression
154
+ formals = node.formals.map{|e| translate(e)}.join(", ")
155
+ body = node.body.map{|e| translate(e)}.join("; ")
156
+ "def #{name}(#{formals}); #{body}; end"
157
+ else
158
+ value = translate(ast_node.expression)
159
+ "#{name} = #{value}"
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+ evaluator = Evaluator.new
166
+
167
+ require "readline"
168
+
169
+ prompt = "mini_Rus3> "
170
+
171
+ msg = loop {
172
+ source = Readline::readline(prompt, true)
173
+ break "Bye!" if source.nil?
174
+
175
+ case source
176
+ when /\(load-scm\s+"(.*)"\)/
177
+ file = Regexp.last_match[1]
178
+ source = File.readlines(file, chomp: true).join(" ")
179
+ when /\(version\)/
180
+ puts "(Rubasteme :version #{Rubasteme::VERSION} :release #{Rubasteme::RELEASE})"
181
+ puts "(Rbscmlex :version #{Rbscmlex::VERSION} :release #{Rbscmlex::RELEASE})"
182
+ next
183
+ end
184
+
185
+ lexer = Rbscmlex::Lexer.new(source, form: :token)
186
+ parser = Rubasteme.parser(lexer)
187
+ ast_program = parser.parse
188
+
189
+ result = evaluator.eval(ast_program)
190
+
191
+ pp result
192
+ }
193
+ puts msg unless msg.nil?