syntax_tree-bf 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 +7 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/main.yml +34 -0
- data/.gitignore +10 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +36 -0
- data/LICENSE +21 -0
- data/README.md +76 -0
- data/Rakefile +17 -0
- data/exe/bf +6 -0
- data/lib/syntax_tree/bf/evaluate.rb +113 -0
- data/lib/syntax_tree/bf/format.rb +54 -0
- data/lib/syntax_tree/bf/nodes.rb +201 -0
- data/lib/syntax_tree/bf/parser.rb +83 -0
- data/lib/syntax_tree/bf/pretty_print.rb +65 -0
- data/lib/syntax_tree/bf/version.rb +7 -0
- data/lib/syntax_tree/bf/visitor.rb +43 -0
- data/lib/syntax_tree/bf.rb +30 -0
- data/syntax_tree-bf.gemspec +33 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ddb9a72bfa7b2fd5f6960214c364bf2dea81ecd90f4512187280fca93a0e086c
|
4
|
+
data.tar.gz: b6c92797a50f0a94b160aa5d4e2ccccd81472a0b770a79c2c7e7bccad47c166c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 30f416c5e87cdafd9f088655ab4f0eac4bdaa99ce8bc0a10cc492435fb7b03fbca87c0e461147c94d7a9004bface66c21d0927441cb8bf1e98b8be8775428ed5
|
7
|
+
data.tar.gz: e3f09df44d161a2212ab2e6075e0f2b77457fdf4e2b3988886b126287a93c9ea76b82133659a87a32439bd038e52c89b4a8b978a6d5672b92cbf20a9fddb203f
|
@@ -0,0 +1,34 @@
|
|
1
|
+
name: Main
|
2
|
+
on:
|
3
|
+
- push
|
4
|
+
- pull_request_target
|
5
|
+
jobs:
|
6
|
+
ci:
|
7
|
+
name: CI
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
env:
|
10
|
+
CI: true
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@master
|
13
|
+
- uses: ruby/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
bundler-cache: true
|
16
|
+
ruby-version: '3.1'
|
17
|
+
- name: Test
|
18
|
+
run: |
|
19
|
+
bundle exec rake test
|
20
|
+
bundle exec rake stree:check
|
21
|
+
automerge:
|
22
|
+
name: AutoMerge
|
23
|
+
needs: ci
|
24
|
+
runs-on: ubuntu-latest
|
25
|
+
if: github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]'
|
26
|
+
steps:
|
27
|
+
- uses: actions/github-script@v3
|
28
|
+
with:
|
29
|
+
script: |
|
30
|
+
github.pulls.merge({
|
31
|
+
owner: context.payload.repository.owner.login,
|
32
|
+
repo: context.payload.repository.name,
|
33
|
+
pull_number: context.payload.pull_request.number
|
34
|
+
})
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## [0.1.0] - 2022-05-26
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- 🎉 Initial release! 🎉
|
14
|
+
|
15
|
+
[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree-bf/compare/v0.1.0...HEAD
|
16
|
+
[0.1.0]: https://github.com/ruby-syntax-tree/syntax_tree-bf/compare/30c324...v0.1.0
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
syntax_tree-bf (0.1.0)
|
5
|
+
prettier_print
|
6
|
+
syntax_tree (>= 2.0.1)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
docile (1.4.0)
|
12
|
+
minitest (5.15.0)
|
13
|
+
prettier_print (0.1.0)
|
14
|
+
rake (13.0.6)
|
15
|
+
simplecov (0.21.2)
|
16
|
+
docile (~> 1.1)
|
17
|
+
simplecov-html (~> 0.11)
|
18
|
+
simplecov_json_formatter (~> 0.1)
|
19
|
+
simplecov-html (0.12.3)
|
20
|
+
simplecov_json_formatter (0.1.4)
|
21
|
+
syntax_tree (2.7.1)
|
22
|
+
prettier_print
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
x86_64-darwin-21
|
26
|
+
x86_64-linux
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler
|
30
|
+
minitest
|
31
|
+
rake
|
32
|
+
simplecov
|
33
|
+
syntax_tree-bf!
|
34
|
+
|
35
|
+
BUNDLED WITH
|
36
|
+
2.3.6
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022-present Kevin Newton
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# SyntaxTree::Bf
|
2
|
+
|
3
|
+
[](https://github.com/ruby-syntax-tree/syntax_tree-bf/actions/workflows/main.yml)
|
4
|
+
[](https://rubygems.org/gems/syntax_tree-bf)
|
5
|
+
|
6
|
+
[Syntax Tree](https://github.com/ruby-syntax-tree/syntax_tree) support for Brainf***.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem "syntax_tree-bf"
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle install
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install syntax_tree-bf
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
From code:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require "syntax_tree/bf"
|
30
|
+
|
31
|
+
pp SyntaxTree::Bf.parse(source) # print out the AST
|
32
|
+
puts SyntaxTree::Bf.format(source) # format the AST
|
33
|
+
```
|
34
|
+
|
35
|
+
From the CLI:
|
36
|
+
|
37
|
+
```sh
|
38
|
+
$ stree ast --plugins=bf file.bf
|
39
|
+
(root
|
40
|
+
(increment),
|
41
|
+
...
|
42
|
+
(increment),
|
43
|
+
(loop
|
44
|
+
(shift_right),
|
45
|
+
...
|
46
|
+
(decrement)
|
47
|
+
),
|
48
|
+
(shift_right),
|
49
|
+
...
|
50
|
+
(output)
|
51
|
+
)
|
52
|
+
```
|
53
|
+
|
54
|
+
or
|
55
|
+
|
56
|
+
```sh
|
57
|
+
$ stree format --plugins=bf file.bf
|
58
|
+
++++++++[
|
59
|
+
>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-
|
60
|
+
]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
|
61
|
+
```
|
62
|
+
|
63
|
+
or
|
64
|
+
|
65
|
+
```sh
|
66
|
+
$ stree write --plugins=bf file.bf
|
67
|
+
file.bf 1ms
|
68
|
+
```
|
69
|
+
|
70
|
+
## Contributing
|
71
|
+
|
72
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-syntax-tree/syntax_tree-bf.
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
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,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "syntax_tree/rake_tasks"
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << "test"
|
9
|
+
t.libs << "lib"
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
11
|
+
end
|
12
|
+
|
13
|
+
source_files = FileList["{lib,test}/**/*.rb"]
|
14
|
+
SyntaxTree::Rake::CheckTask.new { |t| t.source_files = source_files }
|
15
|
+
SyntaxTree::Rake::WriteTask.new { |t| t.source_files = source_files }
|
16
|
+
|
17
|
+
task default: :test
|
data/exe/bf
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Bf
|
5
|
+
module Evaluate
|
6
|
+
# Takes a bf syntax tree and compiles it down to a simple bytecode. The
|
7
|
+
# instructions almost entirely map to the tree nodes except:
|
8
|
+
#
|
9
|
+
# * Root - replaced by its contents
|
10
|
+
# * Loop - replaced by a jmz (jump if zero) and a jmp (unconditional jump)
|
11
|
+
#
|
12
|
+
class Compiler < Visitor
|
13
|
+
attr_reader :insns
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@insns = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit_root(node)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit_loop(node)
|
24
|
+
index = insns.length
|
25
|
+
insns << [:jmz, -1]
|
26
|
+
|
27
|
+
super
|
28
|
+
|
29
|
+
insns << [:jmp, index]
|
30
|
+
insns[index][1] = insns.length
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_increment(node)
|
34
|
+
insns << [:inc]
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_decrement(node)
|
38
|
+
insns << [:dec]
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit_shift_right(node)
|
42
|
+
insns << [:shr]
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_shift_left(node)
|
46
|
+
insns << [:shl]
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_input(node)
|
50
|
+
insns << [:inp]
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit_output(node)
|
54
|
+
insns << [:out]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# This is the virtual machine that runs the set of instructions that the
|
59
|
+
# compiler outputs. Its state is kept on an infinite tape of memory, which
|
60
|
+
# is paired with a cursor.
|
61
|
+
class Machine
|
62
|
+
attr_reader :insns, :stdin, :stdout
|
63
|
+
|
64
|
+
def initialize(insns, stdin: STDIN, stdout: STDOUT)
|
65
|
+
@insns = insns
|
66
|
+
@stdin = stdin
|
67
|
+
@stdout = stdout
|
68
|
+
end
|
69
|
+
|
70
|
+
def run
|
71
|
+
pc = 0
|
72
|
+
|
73
|
+
tape = Hash.new { 0 }
|
74
|
+
cursor = 0
|
75
|
+
|
76
|
+
while pc < insns.length
|
77
|
+
insn = insns[pc]
|
78
|
+
pc += 1
|
79
|
+
|
80
|
+
case insn
|
81
|
+
in [:inc]
|
82
|
+
tape[cursor] += 1
|
83
|
+
in [:dec]
|
84
|
+
tape[cursor] -= 1
|
85
|
+
in [:shr]
|
86
|
+
cursor += 1
|
87
|
+
in [:shl]
|
88
|
+
cursor -= 1
|
89
|
+
in [:inp]
|
90
|
+
tape[cursor] = stdin.getc.ord
|
91
|
+
in [:out]
|
92
|
+
stdout.putc(tape[cursor].chr)
|
93
|
+
in [:jmz, offset]
|
94
|
+
pc = offset if tape[cursor] == 0
|
95
|
+
in [:jmp, offset]
|
96
|
+
pc = offset
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
tape
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.run(source, stdin: STDIN, stdout: STDOUT)
|
105
|
+
node = Parser.new(source).parse
|
106
|
+
compiler = Compiler.new
|
107
|
+
|
108
|
+
compiler.visit(node)
|
109
|
+
Machine.new(compiler.insns, stdin: stdin, stdout: stdout).run
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Bf
|
5
|
+
class Format < Visitor
|
6
|
+
attr_reader :q
|
7
|
+
|
8
|
+
def initialize(q)
|
9
|
+
@q = q
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit_root(node)
|
13
|
+
super
|
14
|
+
q.breakable(force: true)
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_loop(node)
|
18
|
+
q.group do
|
19
|
+
q.text("[")
|
20
|
+
q.nest(2) do
|
21
|
+
q.breakable("")
|
22
|
+
super
|
23
|
+
end
|
24
|
+
q.breakable("")
|
25
|
+
q.text("]")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_increment(node)
|
30
|
+
q.text("+")
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_decrement(node)
|
34
|
+
q.text("-")
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_shift_right(node)
|
38
|
+
q.text(">")
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit_shift_left(node)
|
42
|
+
q.text("<")
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_input(node)
|
46
|
+
q.text(",")
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_output(node)
|
50
|
+
q.text(".")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Bf
|
5
|
+
class Node
|
6
|
+
def format(q)
|
7
|
+
Format.new(q).visit(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def pretty_print(q)
|
11
|
+
PrettyPrint.new(q).visit(self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# The root node of the syntax tree.
|
16
|
+
class Root < Node
|
17
|
+
attr_reader :nodes, :location
|
18
|
+
|
19
|
+
def initialize(nodes:, location:)
|
20
|
+
@nodes = nodes
|
21
|
+
@location = location
|
22
|
+
end
|
23
|
+
|
24
|
+
def accept(visitor)
|
25
|
+
visitor.visit_root(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def child_nodes
|
29
|
+
nodes
|
30
|
+
end
|
31
|
+
|
32
|
+
alias deconstruct child_nodes
|
33
|
+
|
34
|
+
def deconstruct_keys(keys)
|
35
|
+
{ nodes: nodes, location: location }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# [ ... ]
|
40
|
+
class Loop < Node
|
41
|
+
attr_reader :nodes, :location
|
42
|
+
|
43
|
+
def initialize(nodes:, location:)
|
44
|
+
@nodes = nodes
|
45
|
+
@location = location
|
46
|
+
end
|
47
|
+
|
48
|
+
def accept(visitor)
|
49
|
+
visitor.visit_loop(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def child_nodes
|
53
|
+
nodes
|
54
|
+
end
|
55
|
+
|
56
|
+
alias deconstruct child_nodes
|
57
|
+
|
58
|
+
def deconstruct_keys(keys)
|
59
|
+
{ nodes: nodes, location: location }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# +
|
64
|
+
class Increment < Node
|
65
|
+
attr_reader :location
|
66
|
+
|
67
|
+
def initialize(location:)
|
68
|
+
@location = location
|
69
|
+
end
|
70
|
+
|
71
|
+
def accept(visitor)
|
72
|
+
visitor.visit_increment(self)
|
73
|
+
end
|
74
|
+
|
75
|
+
def child_nodes
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
|
79
|
+
alias deconstruct child_nodes
|
80
|
+
|
81
|
+
def deconstruct_keys(keys)
|
82
|
+
{ value: "+", location: location }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# -
|
87
|
+
class Decrement < Node
|
88
|
+
attr_reader :location
|
89
|
+
|
90
|
+
def initialize(location:)
|
91
|
+
@location = location
|
92
|
+
end
|
93
|
+
|
94
|
+
def accept(visitor)
|
95
|
+
visitor.visit_decrement(self)
|
96
|
+
end
|
97
|
+
|
98
|
+
def child_nodes
|
99
|
+
[]
|
100
|
+
end
|
101
|
+
|
102
|
+
alias deconstruct child_nodes
|
103
|
+
|
104
|
+
def deconstruct_keys(keys)
|
105
|
+
{ value: "-", location: location }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# >
|
110
|
+
class ShiftRight < Node
|
111
|
+
attr_reader :location
|
112
|
+
|
113
|
+
def initialize(location:)
|
114
|
+
@location = location
|
115
|
+
end
|
116
|
+
|
117
|
+
def accept(visitor)
|
118
|
+
visitor.visit_shift_right(self)
|
119
|
+
end
|
120
|
+
|
121
|
+
def child_nodes
|
122
|
+
[]
|
123
|
+
end
|
124
|
+
|
125
|
+
alias deconstruct child_nodes
|
126
|
+
|
127
|
+
def deconstruct_keys(keys)
|
128
|
+
{ value: ">", location: location }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# <
|
133
|
+
class ShiftLeft < Node
|
134
|
+
attr_reader :location
|
135
|
+
|
136
|
+
def initialize(location:)
|
137
|
+
@location = location
|
138
|
+
end
|
139
|
+
|
140
|
+
def accept(visitor)
|
141
|
+
visitor.visit_shift_left(self)
|
142
|
+
end
|
143
|
+
|
144
|
+
def child_nodes
|
145
|
+
[]
|
146
|
+
end
|
147
|
+
|
148
|
+
alias deconstruct child_nodes
|
149
|
+
|
150
|
+
def deconstruct_keys(keys)
|
151
|
+
{ value: "<", location: location }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# ,
|
156
|
+
class Input < Node
|
157
|
+
attr_reader :location
|
158
|
+
|
159
|
+
def initialize(location:)
|
160
|
+
@location = location
|
161
|
+
end
|
162
|
+
|
163
|
+
def accept(visitor)
|
164
|
+
visitor.visit_input(self)
|
165
|
+
end
|
166
|
+
|
167
|
+
def child_nodes
|
168
|
+
[]
|
169
|
+
end
|
170
|
+
|
171
|
+
alias deconstruct child_nodes
|
172
|
+
|
173
|
+
def deconstruct_keys(keys)
|
174
|
+
{ value: ",", location: location }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# .
|
179
|
+
class Output < Node
|
180
|
+
attr_reader :location
|
181
|
+
|
182
|
+
def initialize(location:)
|
183
|
+
@location = location
|
184
|
+
end
|
185
|
+
|
186
|
+
def accept(visitor)
|
187
|
+
visitor.visit_output(self)
|
188
|
+
end
|
189
|
+
|
190
|
+
def child_nodes
|
191
|
+
[]
|
192
|
+
end
|
193
|
+
|
194
|
+
alias deconstruct child_nodes
|
195
|
+
|
196
|
+
def deconstruct_keys(keys)
|
197
|
+
{ value: ".", location: location }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Bf
|
5
|
+
# Parses the given source code into a syntax tree.
|
6
|
+
class Parser
|
7
|
+
class Error < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :source
|
11
|
+
|
12
|
+
def initialize(source)
|
13
|
+
@source = source
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
Root.new(nodes: parse_segment(source, 0), location: 0...source.length)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_segment(segment, offset)
|
23
|
+
index = 0
|
24
|
+
nodes = []
|
25
|
+
|
26
|
+
while index < segment.length
|
27
|
+
location = offset + index
|
28
|
+
|
29
|
+
case segment[index]
|
30
|
+
when "+"
|
31
|
+
nodes << Increment.new(location: location...(location + 1))
|
32
|
+
index += 1
|
33
|
+
when "-"
|
34
|
+
nodes << Decrement.new(location: location...(location + 1))
|
35
|
+
index += 1
|
36
|
+
when ">"
|
37
|
+
nodes << ShiftRight.new(location: location...(location + 1))
|
38
|
+
index += 1
|
39
|
+
when "<"
|
40
|
+
nodes << ShiftLeft.new(location: location...(location + 1))
|
41
|
+
index += 1
|
42
|
+
when "."
|
43
|
+
nodes << Output.new(location: location...(location + 1))
|
44
|
+
index += 1
|
45
|
+
when ","
|
46
|
+
nodes << Input.new(location: location...(location + 1))
|
47
|
+
index += 1
|
48
|
+
when "["
|
49
|
+
matched = 1
|
50
|
+
end_index = index + 1
|
51
|
+
|
52
|
+
while matched != 0 && end_index < segment.length
|
53
|
+
case segment[end_index]
|
54
|
+
when "["
|
55
|
+
matched += 1
|
56
|
+
when "]"
|
57
|
+
matched -= 1
|
58
|
+
end
|
59
|
+
|
60
|
+
end_index += 1
|
61
|
+
end
|
62
|
+
|
63
|
+
raise Error, "Unmatched start loop" if matched != 0
|
64
|
+
|
65
|
+
content = segment[(index + 1)...(end_index - 1)]
|
66
|
+
nodes << Loop.new(
|
67
|
+
nodes: parse_segment(content, offset + index + 1),
|
68
|
+
location: location...(offset + end_index)
|
69
|
+
)
|
70
|
+
|
71
|
+
index = end_index
|
72
|
+
when "]"
|
73
|
+
raise Error, "Unmatched end loop"
|
74
|
+
else
|
75
|
+
index += 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
nodes
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Bf
|
5
|
+
class PrettyPrint < Visitor
|
6
|
+
attr_reader :q
|
7
|
+
|
8
|
+
def initialize(q)
|
9
|
+
@q = q
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit_root(node)
|
13
|
+
visit_node("root", node)
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_loop(node)
|
17
|
+
visit_node("loop", node)
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_increment(node)
|
21
|
+
visit_node("increment", node)
|
22
|
+
end
|
23
|
+
|
24
|
+
def visit_decrement(node)
|
25
|
+
visit_node("decrement", node)
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_shift_right(node)
|
29
|
+
visit_node("shift_right", node)
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_shift_left(node)
|
33
|
+
visit_node("shift_left", node)
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_input(node)
|
37
|
+
visit_node("input", node)
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit_output(node)
|
41
|
+
visit_node("output", node)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def visit_node(name, node)
|
47
|
+
q.group do
|
48
|
+
q.text("(")
|
49
|
+
q.text(name)
|
50
|
+
|
51
|
+
if node.child_nodes.any?
|
52
|
+
q.nest(2) do
|
53
|
+
q.breakable
|
54
|
+
q.seplist(node.child_nodes) { |child_node| visit(child_node) }
|
55
|
+
end
|
56
|
+
|
57
|
+
q.breakable("")
|
58
|
+
end
|
59
|
+
|
60
|
+
q.text(")")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Bf
|
5
|
+
class Visitor
|
6
|
+
def visit(node)
|
7
|
+
node.accept(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_all(nodes)
|
11
|
+
nodes.map { |node| visit(node) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_child_nodes(node)
|
15
|
+
visit_all(node.child_nodes)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Visit a Root node.
|
19
|
+
alias visit_root visit_child_nodes
|
20
|
+
|
21
|
+
# Visit a Loop node.
|
22
|
+
alias visit_loop visit_child_nodes
|
23
|
+
|
24
|
+
# Visit an Increment node.
|
25
|
+
alias visit_increment visit_child_nodes
|
26
|
+
|
27
|
+
# Visit a Decrement node.
|
28
|
+
alias visit_decrement visit_child_nodes
|
29
|
+
|
30
|
+
# Visit a ShiftRight node.
|
31
|
+
alias visit_shift_right visit_child_nodes
|
32
|
+
|
33
|
+
# Visit a ShiftLeft node.
|
34
|
+
alias visit_shift_left visit_child_nodes
|
35
|
+
|
36
|
+
# Visit an Input node.
|
37
|
+
alias visit_input visit_child_nodes
|
38
|
+
|
39
|
+
# Visit an Output node.
|
40
|
+
alias visit_output visit_child_nodes
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "prettier_print"
|
4
|
+
require "syntax_tree"
|
5
|
+
|
6
|
+
require_relative "bf/nodes"
|
7
|
+
require_relative "bf/parser"
|
8
|
+
require_relative "bf/visitor"
|
9
|
+
|
10
|
+
require_relative "bf/evaluate"
|
11
|
+
require_relative "bf/format"
|
12
|
+
require_relative "bf/pretty_print"
|
13
|
+
|
14
|
+
module SyntaxTree
|
15
|
+
module Bf
|
16
|
+
def self.format(source, maxwidth = 80)
|
17
|
+
PrettierPrint.format(+"", maxwidth) { |q| parse(source).format(q) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse(source)
|
21
|
+
Parser.new(source).parse
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.read(source)
|
25
|
+
File.read(source)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
register_handler(".bf", Bf)
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/syntax_tree/bf/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "syntax_tree-bf"
|
7
|
+
spec.version = SyntaxTree::Bf::VERSION
|
8
|
+
spec.authors = ["Kevin Newton"]
|
9
|
+
spec.email = ["kddnewton@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Syntax Tree support for Brainf***"
|
12
|
+
spec.homepage = "https://github.com/ruby-syntax-tree/syntax_tree-bf"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.metadata = { "rubygems_mfa_required" => "true" }
|
15
|
+
|
16
|
+
spec.files = Dir.chdir(__dir__) do
|
17
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
spec.bindir = "exe"
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = %w[lib]
|
25
|
+
|
26
|
+
spec.add_dependency "prettier_print"
|
27
|
+
spec.add_dependency "syntax_tree", ">= 2.0.1"
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler"
|
30
|
+
spec.add_development_dependency "minitest"
|
31
|
+
spec.add_development_dependency "rake"
|
32
|
+
spec.add_development_dependency "simplecov"
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: syntax_tree-bf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Newton
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-05-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: prettier_print
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: syntax_tree
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.0.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- kddnewton@gmail.com
|
100
|
+
executables:
|
101
|
+
- bf
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".github/dependabot.yml"
|
106
|
+
- ".github/workflows/main.yml"
|
107
|
+
- ".gitignore"
|
108
|
+
- CHANGELOG.md
|
109
|
+
- Gemfile
|
110
|
+
- Gemfile.lock
|
111
|
+
- LICENSE
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- exe/bf
|
115
|
+
- lib/syntax_tree/bf.rb
|
116
|
+
- lib/syntax_tree/bf/evaluate.rb
|
117
|
+
- lib/syntax_tree/bf/format.rb
|
118
|
+
- lib/syntax_tree/bf/nodes.rb
|
119
|
+
- lib/syntax_tree/bf/parser.rb
|
120
|
+
- lib/syntax_tree/bf/pretty_print.rb
|
121
|
+
- lib/syntax_tree/bf/version.rb
|
122
|
+
- lib/syntax_tree/bf/visitor.rb
|
123
|
+
- syntax_tree-bf.gemspec
|
124
|
+
homepage: https://github.com/ruby-syntax-tree/syntax_tree-bf
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata:
|
128
|
+
rubygems_mfa_required: 'true'
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubygems_version: 3.3.3
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: Syntax Tree support for Brainf***
|
148
|
+
test_files: []
|