kaguya 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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e73ebd3accde57e170c68259136fa3024de8c673
4
+ data.tar.gz: 29ce8f9a8441b5b7c433fe160b9426cd1d501b53
5
+ SHA512:
6
+ metadata.gz: '0589b9960cb92fe547e3ed2cd48c81df78f87578e78657dc8aed9dcab1db2c33b0dff53d43826f4aab11bfaf271c272dec37dc719f7faef6d5d44b70068d6f5f'
7
+ data.tar.gz: 3d24c6dec0660cce7048edff1b934c5836e88db4ffed576f62a47729c4ee269c53666f593133a82ceff0b0b3109e6967d08203981b4c64c56e401d7580fa19be
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.14.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kaguya.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 mozamimy (Moza USANE)
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.
@@ -0,0 +1,132 @@
1
+ # Kaguya
2
+
3
+ Kaguya is an implementation of Brainf\*\*k by Ruby.
4
+
5
+ This implementation consits of following elements,
6
+
7
+ - Tiny parser
8
+ - Tiny compiler
9
+ - Tiny virtual stack machine
10
+
11
+ ## Installation
12
+
13
+ ```
14
+ $ gem install kaguya
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```
20
+ $ kaguya helloworld.bf
21
+ ```
22
+
23
+ An option `--debug` helps you to see AST and instruction sequence.
24
+
25
+ ```
26
+ $ kaguya --debug helloworld.bf
27
+ ```
28
+
29
+ ## Technical components
30
+
31
+ ### Parser
32
+
33
+ https://github.com/mozamimy/kaguya/blob/master/lib/kaguya/parser.rb
34
+
35
+ The parser parses a BF script one charactor at a time. Errors can be raised by the parser if the script contains invalid corresponding of `[` and `]` or invalid charactors.
36
+
37
+ Only the root node and `while` nodes have children.
38
+
39
+ ### Virtual machine
40
+
41
+ https://github.com/mozamimy/kaguya/blob/master/lib/kaguya/vm.rb
42
+
43
+ Kaguya VM has a two stacks (left\_stack, right\_stack) and has following 9 instructions,
44
+
45
+ - `forward`: Forward stack, if right\_stack is empty then push 0 to left\_stack else move top of right\_stack to left\_stack.
46
+ - `backward`: Backward stack, top data of left\_stack is poped and pushed to right\_stack.
47
+ - `increment`: Increment top of left\_stack.
48
+ - `decrement`: Decrement top of left\_stack.
49
+ - `output`: Output top of left\_stack to standard I/O.
50
+ - `input`: Input a value from standard I/O and push it to left\_stack.
51
+ - `branch_ifzero N`: Jump to an instruction at `PC - N` if top of left\_stack is 0.
52
+ - `branch_unlesszero N`: Jump to an instruction at `PC - N` unless top of left\_stack is 0.
53
+ - `leave`: Exit
54
+
55
+ ### Compiler
56
+
57
+ https://github.com/mozamimy/kaguya/blob/master/lib/kaguya/compiler.rb
58
+
59
+ Kaguya compiler compiles an AST generated by the parser to an instruction sequence for Kaguya VM.
60
+
61
+ ### Example
62
+
63
+ ```
64
+ [20:17:51]mozamimy@queen:kaguya (master) (-'x'-).oO(
65
+ > cat example/useless.bf
66
+ >>+[<>[+-]]
67
+ [20:17:53]mozamimy@queen:kaguya (master) (-'x'-).oO(
68
+ > be exe/kaguya --debug example/useless.bf
69
+ === AST ===
70
+ #<Kaguya::AST::Node:0x007fd755a3f8c0
71
+ @children=
72
+ [#<Kaguya::AST::Node:0x007fd755a3f780
73
+ @children=[],
74
+ @parent=#<Kaguya::AST::Node:0x007fd755a3f8c0 ...>,
75
+ @type=:forward>,
76
+ #<Kaguya::AST::Node:0x007fd755a3f500
77
+ @children=[],
78
+ @parent=#<Kaguya::AST::Node:0x007fd755a3f8c0 ...>,
79
+ @type=:forward>,
80
+ #<Kaguya::AST::Node:0x007fd755a3f230
81
+ @children=[],
82
+ @parent=#<Kaguya::AST::Node:0x007fd755a3f8c0 ...>,
83
+ @type=:increment>,
84
+ #<Kaguya::AST::Node:0x007fd755a3f028
85
+ @children=
86
+ [#<Kaguya::AST::Node:0x007fd755a3edd0
87
+ @children=[],
88
+ @parent=#<Kaguya::AST::Node:0x007fd755a3f028 ...>,
89
+ @type=:backward>,
90
+ #<Kaguya::AST::Node:0x007fd755a3e7e0
91
+ @children=[],
92
+ @parent=#<Kaguya::AST::Node:0x007fd755a3f028 ...>,
93
+ @type=:forward>,
94
+ #<Kaguya::AST::Node:0x007fd755a3e5b0
95
+ @children=
96
+ [#<Kaguya::AST::Node:0x007fd755a3e3f8
97
+ @children=[],
98
+ @parent=#<Kaguya::AST::Node:0x007fd755a3e5b0 ...>,
99
+ @type=:increment>,
100
+ #<Kaguya::AST::Node:0x007fd755a3e1f0
101
+ @children=[],
102
+ @parent=#<Kaguya::AST::Node:0x007fd755a3e5b0 ...>,
103
+ @type=:decrement>],
104
+ @parent=#<Kaguya::AST::Node:0x007fd755a3f028 ...>,
105
+ @type=:while>],
106
+ @parent=#<Kaguya::AST::Node:0x007fd755a3f8c0 ...>,
107
+ @type=:while>],
108
+ @parent=nil,
109
+ @type=:root>
110
+ === ISEQ ===
111
+ [[:forward, nil],
112
+ [:forward, nil],
113
+ [:increment, nil],
114
+ [:branch_ifzero, 8],
115
+ [:backward, nil],
116
+ [:forward, nil],
117
+ [:branch_ifzero, 4],
118
+ [:increment, nil],
119
+ [:decrement, nil],
120
+ [:branch_unlesszero, -2],
121
+ [:branch_unlesszero, -6],
122
+ [:leave, nil]]
123
+ === RUN ===
124
+ ```
125
+
126
+ ## Contributing
127
+
128
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mozamimy/kaguya.
129
+
130
+ ## License
131
+
132
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "kaguya"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -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,2 @@
1
+ +++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.
2
+ ------------.<++++++++.--------.+++.------.--------.>+.
@@ -0,0 +1,5 @@
1
+ >++++[<++++++++>-]>++++++++[<++++++>-]<++.<.>+.<.>++.<.>++.<.>>++[<--->-]
2
+ <..<.>.++.<.>--.>++[<+++>-]<.<.>>++[<--->-]<.>++[<++++>-]<.<.>>++[<--->-]
3
+ <-.+.<.>-.>+++[<++>-]<+.<.>>++[<--->-]<.--.<.>++.++++.<.>---.---.<.>++.-
4
+ .<.>+.+++.<.>--.--.<.>++.++++.<.>---.-----.<.>+++++.+.<.>.>++[<--->-]<.<.
5
+ >>++[<+++>-]<.----.<.>++++.++.<.>-.-----.<.>+++++.+.<.>.--.
@@ -0,0 +1 @@
1
+ >>+[<>[+-]]
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'kaguya/cli'
4
+
5
+ Kaguya::CLI.start(ARGV)
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kaguya/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'kaguya'
8
+ spec.version = Kaguya::VERSION
9
+ spec.authors = ['mozamimy (Moza USANE)']
10
+ spec.email = ['alice@mozami.me']
11
+
12
+ spec.summary = %q{An implementation of brankf**k.}
13
+ spec.description = %q{An implementation of brankf**k.}
14
+ spec.homepage = 'https://github.com/mozamimy/kaguya'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.14'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ end
@@ -0,0 +1,8 @@
1
+ require 'kaguya/version'
2
+ require 'kaguya/cli'
3
+ require 'kaguya/parser'
4
+ require 'kaguya/compiler'
5
+ require 'kaguya/vm'
6
+ require 'kaguya/ast/node'
7
+
8
+ module Kaguya; end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaguya
4
+ module AST
5
+ class Node
6
+ attr_reader :parent
7
+ attr_reader :children
8
+ attr_reader :type
9
+
10
+ # @param [Symbol] type
11
+ # @param [Node] parent
12
+ def initialize(type:, parent:)
13
+ @type = type
14
+ @parent = parent
15
+ @children = []
16
+
17
+ @parent.children << self if @parent
18
+ end
19
+
20
+ # @param [Compiler] compiler
21
+ # @return [Array]
22
+ def accept(compiler)
23
+ compiler.visit(self)
24
+ end
25
+
26
+ # @return [String]
27
+ def to_s
28
+ @type.to_s
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kaguya'
4
+ require 'optparse'
5
+ require 'pp'
6
+
7
+ module Kaguya
8
+ class CLI
9
+ def self.start(argv)
10
+ new(argv).run
11
+ end
12
+
13
+ def initialize(argv)
14
+ @argv = argv.dup
15
+ parser.parse!(@argv)
16
+ end
17
+
18
+ def run
19
+ source_file = File.open(@argv[0], 'r')
20
+ parser = Parser.new(source_file)
21
+ ast = parser.parse
22
+
23
+ if @debug
24
+ puts '=== AST ==='
25
+ pp ast
26
+ end
27
+
28
+ compiler = Compiler.new(ast)
29
+ iseq = compiler.compile
30
+
31
+ if @debug
32
+ puts '=== ISEQ ==='
33
+ pp iseq
34
+ puts '=== RUN ==='
35
+ end
36
+
37
+ vm = VM.new(iseq)
38
+ vm.run
39
+ end
40
+
41
+ private
42
+
43
+ def parser
44
+ @parser ||= OptionParser.new do |opts|
45
+ opts.banner = 'kaguya'
46
+ opts.version = Kaguya::VERSION
47
+ opts.on('-d', '--debug') { |d| @debug = d }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaguya
4
+ class Compiler
5
+ # @param [AST::Node] ast
6
+ def initialize(ast)
7
+ @ast = ast
8
+ end
9
+
10
+ # @return [Array]
11
+ def compile
12
+ iseq = @ast.accept(self)
13
+ iseq << [:leave, nil]
14
+ iseq
15
+ end
16
+
17
+ # @param [AST::Node] node
18
+ # @return [Array]
19
+ def visit(node)
20
+ iseq = []
21
+
22
+ case node.type
23
+ when :forward
24
+ iseq << [:forward, nil]
25
+ when :backward
26
+ iseq << [:backward, nil]
27
+ when :increment
28
+ iseq << [:increment, nil]
29
+ when :decrement
30
+ iseq << [:decrement, nil]
31
+ when :output
32
+ iseq << [:output, nil]
33
+ when :input
34
+ iseq << [:input, nil]
35
+ when :while
36
+ sub_iseq = []
37
+
38
+ node.children.each do |child|
39
+ sub_iseq.concat(child.accept(self))
40
+ end
41
+
42
+ iseq << [:branch_ifzero, sub_iseq.size + 2]
43
+ iseq.concat(sub_iseq)
44
+ iseq << [:branch_unlesszero, -sub_iseq.size]
45
+ when :root
46
+ node.children.each do |child|
47
+ iseq.concat(child.accept(self))
48
+ end
49
+ else
50
+ raise 'Invalid node!'
51
+ end
52
+
53
+ iseq
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaguya
4
+ class Parser
5
+ # @param [IO] input
6
+ def initialize(input)
7
+ @input = input
8
+ end
9
+
10
+ # @return [AST::Node]
11
+ def parse
12
+ root = AST::Node.new(type: :root, parent: nil)
13
+ current_node = root
14
+ context_level = 0
15
+
16
+ @input.each_char do |ch|
17
+ case ch
18
+ when '>'
19
+ AST::Node.new(type: :forward, parent: current_node)
20
+ when '<'
21
+ AST::Node.new(type: :backward, parent: current_node)
22
+ when '+'
23
+ AST::Node.new(type: :increment, parent: current_node)
24
+ when '-'
25
+ AST::Node.new(type: :decrement, parent: current_node)
26
+ when '.'
27
+ AST::Node.new(type: :output, parent: current_node)
28
+ when ','
29
+ AST::Node.new(type: :input, parent: current_node)
30
+ when '['
31
+ context_level += 1
32
+ current_node = AST::Node.new(type: :while, parent: current_node)
33
+ when ']'
34
+ context_level -= 1
35
+ current_node = current_node.parent
36
+ when ' ', "\n", "\r"
37
+ # read next charactor
38
+ else
39
+ raise "Invalid charactor `#{ch}`."
40
+ end
41
+ end
42
+
43
+ if context_level != 0
44
+ raise 'Invalid brace correspondence.'
45
+ end
46
+
47
+ root
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaguya
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pp'
4
+
5
+ module Kaguya
6
+ class VM
7
+ # @param [Array] iseq
8
+ def initialize(iseq)
9
+ @iseq = iseq
10
+ @pc = 0
11
+ @left_stack = [0]
12
+ @right_stack = []
13
+ end
14
+
15
+ def run
16
+ loop do
17
+ instruction = fetch(@pc)
18
+ execute(instruction)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # @param [Integer] pc
25
+ # @return [Integer]
26
+ def fetch(pc)
27
+ @iseq[pc]
28
+ end
29
+
30
+ # @param [Array] instruction
31
+ def execute(instruction)
32
+ case instruction[0]
33
+ when :forward
34
+ if @right_stack.size < 1
35
+ @left_stack.push(0)
36
+ else
37
+ @left_stack.push(@right_stack.pop)
38
+ end
39
+ @pc += 1
40
+ when :backward
41
+ @right_stack.push(@left_stack.pop)
42
+ @pc += 1
43
+ when :increment
44
+ @left_stack.push(@left_stack.pop + 1)
45
+ @pc += 1
46
+ when :decrement
47
+ @left_stack.push(@left_stack.pop - 1)
48
+ @pc += 1
49
+ when :output
50
+ value = @left_stack.pop
51
+ STDOUT.print(value.chr)
52
+ @left_stack.push(value)
53
+ @pc += 1
54
+ when :input
55
+ @left_stack.pop
56
+ @left_stack.push(STDIN.getc.ord)
57
+ @pc += 1
58
+ when :branch_ifzero
59
+ value = @left_stack.pop
60
+ @left_stack.push(value)
61
+ if value.zero?
62
+ @pc += instruction[1]
63
+ else
64
+ @pc += 1
65
+ end
66
+ when :branch_unlesszero
67
+ value = @left_stack.pop
68
+ @left_stack.push(value)
69
+ if !value.zero?
70
+ @pc += instruction[1]
71
+ else
72
+ @pc += 1
73
+ end
74
+ when :leave
75
+ exit 0
76
+ else
77
+ raise "Unknown instruction #{instruction[0]}"
78
+ end
79
+ rescue => e
80
+ pp @left_stack
81
+ pp @right_stack
82
+ raise(e)
83
+ end
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kaguya
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - mozamimy (Moza USANE)
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: An implementation of brankf**k.
56
+ email:
57
+ - alice@mozami.me
58
+ executables:
59
+ - kaguya
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
66
+ - Gemfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bin/console
71
+ - bin/setup
72
+ - example/helloworkd.bf
73
+ - example/prime.bf
74
+ - example/useless.bf
75
+ - exe/kaguya
76
+ - kaguya.gemspec
77
+ - lib/kaguya.rb
78
+ - lib/kaguya/ast/node.rb
79
+ - lib/kaguya/cli.rb
80
+ - lib/kaguya/compiler.rb
81
+ - lib/kaguya/parser.rb
82
+ - lib/kaguya/version.rb
83
+ - lib/kaguya/vm.rb
84
+ homepage: https://github.com/mozamimy/kaguya
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.6.8
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: An implementation of brankf**k.
108
+ test_files: []