special-giggle 1.0.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/.gitignore +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +22 -0
- data/README.md +51 -0
- data/Rakefile +27 -0
- data/SPECIFICATION.md +3 -0
- data/bin/console +14 -0
- data/bin/setup +6 -0
- data/lib/special-giggle.rb +27 -0
- data/lib/special-giggle/generator.rb +143 -0
- data/lib/special-giggle/lexer.rb +62 -0
- data/lib/special-giggle/main.rb +72 -0
- data/lib/special-giggle/parser.rb +85 -0
- data/lib/special-giggle/version.rb +3 -0
- data/special-giggle.gemspec +27 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 51b322e0b69121f5f57d8b7a514783d86dc33d06c7e81b59b33c36129f06741b
|
4
|
+
data.tar.gz: f74f42bb454c31cf959493f856904eb969fcc58aa19ab3fb0bb231c7d1883c3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7064c01e544f59f15b46dd552ead4eba0a1b36b2a9dc854f1f57af40e98bbb20abc824d261411aa308bee06bf6ea740387141b61cbc6a130ca627e676a5e40fb
|
7
|
+
data.tar.gz: a08e189d09bf5d5f4b7f003fff659cc4229a7ca6f2461f668e9b0332a4b2e22b07971fb879e7607abf1b6dfbafe9bf66812da393f4530436826a3b89739d8e21
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
special-giggle (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
irb (1.0.0)
|
10
|
+
rake (10.5.0)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
ruby
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
bundler (~> 2.0)
|
17
|
+
irb (~> 1.0)
|
18
|
+
rake (~> 10.0)
|
19
|
+
special-giggle!
|
20
|
+
|
21
|
+
BUNDLED WITH
|
22
|
+
2.0.2
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# SpecialGiggle (Under Development)
|
2
|
+
|
3
|
+
Compiler for BX language, written fully in Ruby
|
4
|
+
|
5
|
+
## Changelog
|
6
|
+
|
7
|
+
See [CHANGELOG.md](CHANGELOG.md) for more details
|
8
|
+
|
9
|
+
## Specification
|
10
|
+
|
11
|
+
See [SPECIFICATION.md](SPECIFICATION.md) for details
|
12
|
+
|
13
|
+
## Version Convention
|
14
|
+
|
15
|
+
- Patch 0.0.x: Implementation level changes, such as bugfixes
|
16
|
+
- Minor 0.x.0: Backwards compatible API changes, such as new functionality and feature
|
17
|
+
- Major x.0.0: Backwards incompatible API changes, such as changes that will break existing codes
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'special-giggle'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install special-giggle
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'special-giggle'
|
39
|
+
|
40
|
+
SpecialGiggle.compile("file.bx", "file.bin")
|
41
|
+
```
|
42
|
+
|
43
|
+
## Development
|
44
|
+
|
45
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
46
|
+
|
47
|
+
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).
|
48
|
+
|
49
|
+
## Contributing
|
50
|
+
|
51
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kalari499/special-giggle.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
# Default
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
# Build
|
8
|
+
desc "Build special-giggle Gem"
|
9
|
+
task :build do
|
10
|
+
sh %{gem build special-giggle.gemspec}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Unit test
|
14
|
+
Rake::TestTask.new(:test => [:test_good, :test_bad]) do |t|
|
15
|
+
t.description = "Unit testing: All tests"
|
16
|
+
end
|
17
|
+
|
18
|
+
Rake::TestTask.new(:test_good) do |t|
|
19
|
+
t.description = "Unit testing: Good programs"
|
20
|
+
t.test_files = ['test/good/unit.rb']
|
21
|
+
end
|
22
|
+
|
23
|
+
Rake::TestTask.new(:test_bad) do |t|
|
24
|
+
t.description = "Unit testing: Bad programs - Not implemented"
|
25
|
+
t.test_files = []
|
26
|
+
end
|
27
|
+
|
data/SPECIFICATION.md
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "special-giggle"
|
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__)
|
data/bin/setup
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'special-giggle/lexer'
|
2
|
+
require 'special-giggle/parser'
|
3
|
+
require 'special-giggle/generator'
|
4
|
+
|
5
|
+
module SpecialGiggle
|
6
|
+
include Lexer
|
7
|
+
include Parser
|
8
|
+
include Generator
|
9
|
+
|
10
|
+
class GiggleError < StandardError; end
|
11
|
+
|
12
|
+
def self.to_asm(string)
|
13
|
+
tokens = Lexer.tokenize(string)
|
14
|
+
ast = Parser.parse(tokens)
|
15
|
+
code = Generator.generate(ast)
|
16
|
+
code
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.compile(infile, outfile, tmpdir = "/tmp")
|
20
|
+
raise GiggleError, "Input file #{infile} does not found." unless File.exist?(infile)
|
21
|
+
file = File.basename(infile, ".*")
|
22
|
+
sfile, ofile = File.expand_path("#{file}.s", tmpdir), File.expand_path("#{file}.o", tmpdir)
|
23
|
+
File.write(sfile, to_asm(File.read(infile)))
|
24
|
+
system("as --64 -o #{ofile} #{sfile}") || (raise GiggleError, "Can't compile with as")
|
25
|
+
system("gcc -no-pie -o #{outfile} #{ofile}") || (raise GiggleError, "Can't link with gcc")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Generator
|
2
|
+
class GeneratorError < StandardError; end
|
3
|
+
|
4
|
+
class Token
|
5
|
+
def initialize(type, src = nil, dst = nil)
|
6
|
+
@type, @src, @dst = type, src, dst
|
7
|
+
end
|
8
|
+
|
9
|
+
def need_imm?
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_code
|
13
|
+
case @type
|
14
|
+
when :Print
|
15
|
+
[ "movq #{@src}, %rsi", "movq $__print_fmt, %rdi", "movq $0, %rax", "call printf" ]
|
16
|
+
when :Move
|
17
|
+
[ @src.is_a?(Integer) ? "movq $#{@src}, %r11" : "movq #{@src}, %r11", "movq %r11, #{@dst}" ]
|
18
|
+
when :Tilde
|
19
|
+
[ "movq #{@src}, %r11", "notq %r11", "movq %r11, #{@dst}" ]
|
20
|
+
when :Negative
|
21
|
+
[ "movq #{@src}, %r11", "negq %r11", "movq %r11, #{@dst}" ]
|
22
|
+
when :Plus
|
23
|
+
[ "movq #{@src[0]}, %r11", "movq %r11, #{@dst}", "movq #{@src[1]}, %r11", "addq %r11, #{@dst}"]
|
24
|
+
when :Minus
|
25
|
+
[ "movq #{@src[0]}, %r11", "movq %r11, #{@dst}", "movq #{@src[1]}, %r11", "subq %r11, #{@dst}"]
|
26
|
+
when :Asterisk
|
27
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "imulq %rcx", "movq %rax, #{@dst}" ]
|
28
|
+
when :Slash
|
29
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "cqo", "idivq %rcx", "movq %rax, #{@dst}" ]
|
30
|
+
when :Percent
|
31
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "cqo", "idivq %rcx", "movq %rdx, #{@dst}" ]
|
32
|
+
when :Bar
|
33
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "orq %rcx, %rax", "movq %rax, #{@dst}" ]
|
34
|
+
when :Ampersand
|
35
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "andq %rcx, %rax", "movq %rax, #{@dst}" ]
|
36
|
+
when :Caret
|
37
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "xorq %rcx, %rax", "movq %rax, #{@dst}" ]
|
38
|
+
when :LeftShift
|
39
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "salq %cl, %rax", "movq %rax, #{@dst}" ]
|
40
|
+
when :RightShift
|
41
|
+
[ "movq #{@src[0]}, %rax", "movq #{@src[1]}, %rcx", "sarq %cl, %rax", "movq %rax, #{@dst}" ]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
BinaryOps = {
|
47
|
+
:Plus => 50,
|
48
|
+
:Minus => 50,
|
49
|
+
:Asterisk => 60,
|
50
|
+
:Slash => 60,
|
51
|
+
:Percent => 60,
|
52
|
+
:Bar => 10,
|
53
|
+
:Ampersand => 30,
|
54
|
+
:Caret => 20,
|
55
|
+
:LeftShift => 40,
|
56
|
+
:RightShift => 40,
|
57
|
+
}
|
58
|
+
|
59
|
+
UnaryOps = {
|
60
|
+
:Tilde => 70,
|
61
|
+
:Negative => 80,
|
62
|
+
}
|
63
|
+
|
64
|
+
Vars = Array.new
|
65
|
+
|
66
|
+
def self.var_temp
|
67
|
+
#var = "#{8 * Vars.length}(%rsp)"
|
68
|
+
var = "tmp_#{Vars.length}"
|
69
|
+
Vars << var unless Vars.include?(var)
|
70
|
+
var
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.generate_expression(token)
|
74
|
+
if token.type == :Number
|
75
|
+
t = var_temp
|
76
|
+
[t, [Token.new(:Move, token.child[0], t)]]
|
77
|
+
elsif token.type == :Variable
|
78
|
+
[token.child[0], []]
|
79
|
+
elsif token.type == :Bracket
|
80
|
+
var, array = generate_expression(token.child[0])
|
81
|
+
t = var_temp
|
82
|
+
[t, [array, Token.new(:Move, var, t)]]
|
83
|
+
elsif UnaryOps.include?(token.type)
|
84
|
+
var, array = generate_expression(token.child[0])
|
85
|
+
t = var_temp
|
86
|
+
[t, [array, Token.new(token.type, var, t)]]
|
87
|
+
else
|
88
|
+
var1, array1 = generate_expression(token.child[0])
|
89
|
+
var2, array2 = generate_expression(token.child[1])
|
90
|
+
t = var_temp
|
91
|
+
[t, [array1, array2, Token.new(token.type, [var1, var2], t)]]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.generate_statement(token)
|
96
|
+
array = Array.new
|
97
|
+
if token.type == :Print
|
98
|
+
var, garray = generate_expression(token.child[0])
|
99
|
+
array << garray unless garray.empty?
|
100
|
+
array << Token.new(:Print, var)
|
101
|
+
elsif token.type == :Move
|
102
|
+
Vars << token.child[0].child[0] unless Vars.include?(token.child[0].child[0])
|
103
|
+
var, garray = generate_expression(token.child[1])
|
104
|
+
array << garray unless garray.empty?
|
105
|
+
array << Token.new(:Move, var, token.child[0].child[0])
|
106
|
+
end
|
107
|
+
array
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.generate(token)
|
111
|
+
array = Array.new
|
112
|
+
token.child.each do |statement|
|
113
|
+
array << generate_statement(statement)
|
114
|
+
end
|
115
|
+
|
116
|
+
output = %{
|
117
|
+
.section .data
|
118
|
+
__print_fmt:
|
119
|
+
.string "%ld\\n"
|
120
|
+
|
121
|
+
.section .text
|
122
|
+
.globl main
|
123
|
+
main:
|
124
|
+
pushq %rbp
|
125
|
+
movq %rsp, %rbp
|
126
|
+
subq <VARIABLE>, %rsp
|
127
|
+
|
128
|
+
<CODE>
|
129
|
+
movq %rbp, %rsp
|
130
|
+
popq %rbp
|
131
|
+
movq $0, %rax
|
132
|
+
retq
|
133
|
+
}
|
134
|
+
text = ""
|
135
|
+
array.flatten.each { |ops| ops.to_code.each { |line| text += " " * 2 + line + "\n" } }
|
136
|
+
|
137
|
+
Vars.length.times do |i|
|
138
|
+
regex = "(?<![\\w])#{Vars[i]}(?![\\w])"
|
139
|
+
text.gsub!(/#{regex}/, i > 0 ? "#{8*i}(%rsp)" : "(%rsp)")
|
140
|
+
end
|
141
|
+
output.gsub('<VARIABLE>', "$#{Vars.length * 8}").gsub('<CODE>', text)
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Lexer
|
2
|
+
class LexerError < StandardError; end
|
3
|
+
|
4
|
+
class Token
|
5
|
+
attr_reader :string, :type
|
6
|
+
|
7
|
+
def initialize(string, type)
|
8
|
+
@string, @type = string, type
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Patterns = {
|
13
|
+
:Blank => '[ \t\r]+',
|
14
|
+
:NewLine => '\n+',
|
15
|
+
:Print => 'print',
|
16
|
+
:Variable => '[A-Za-z_][A-Za-z0-9_]*',
|
17
|
+
:Number => '-?[0-9]+',
|
18
|
+
:Plus => '\+',
|
19
|
+
:Minus => '\-',
|
20
|
+
:Asterisk => '\*',
|
21
|
+
:Slash => '\/',
|
22
|
+
:Percent => '\%',
|
23
|
+
:Bar => '\|',
|
24
|
+
:Ampersand => '\&',
|
25
|
+
:Caret => '\^',
|
26
|
+
:Tilde => '\~',
|
27
|
+
:LeftShift => '<<',
|
28
|
+
:RightShift => '>>',
|
29
|
+
:Equal => '\=',
|
30
|
+
:LeftBracket => '\(',
|
31
|
+
:RightBracket => '\)',
|
32
|
+
:SemiColon => '\;',
|
33
|
+
}
|
34
|
+
|
35
|
+
def self.next_token(string, index)
|
36
|
+
Patterns.each do |type, pattern|
|
37
|
+
res = string.match(pattern, index)
|
38
|
+
next if res.nil?
|
39
|
+
return [res[0], type, res.end(0)] if res.begin(0) == index
|
40
|
+
end
|
41
|
+
[nil, nil, nil]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.tokenize(string)
|
45
|
+
tokens = Array.new
|
46
|
+
index, line, column = 0, 1, 1
|
47
|
+
while index < string.length
|
48
|
+
rstring, rtype, index = next_token(string, index)
|
49
|
+
case rtype
|
50
|
+
when nil
|
51
|
+
raise LexerError, "Unrecognized character in line #{line}, column #{column}"
|
52
|
+
when :Blank
|
53
|
+
next
|
54
|
+
when :NewLine
|
55
|
+
line, column = line + 1, 1
|
56
|
+
else
|
57
|
+
tokens << Token.new(rstring, rtype)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
tokens
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative "lexer"
|
2
|
+
require_relative "parser"
|
3
|
+
require_relative "generator"
|
4
|
+
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
def main
|
8
|
+
ARGV << "-h" if ARGV.empty?
|
9
|
+
|
10
|
+
options = Hash.new
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: ruby main.rb [options]"
|
13
|
+
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "Specific options:"
|
16
|
+
|
17
|
+
opts.on("-f FILE", "--file", String, "Input from file (required)") do |f|
|
18
|
+
options[:file] = f
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-t LANG", "--target", String, "Target language - C, Assembly (default C)") do |t|
|
22
|
+
options[:lang] = t
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-o FILE", "--output", String, "Output to file (default: stdout)") do |o|
|
26
|
+
options[:output] = o
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on_tail("-h", "--help","Show this message") do
|
30
|
+
puts opts
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end.parse!
|
34
|
+
|
35
|
+
abort("Missing input file. Abort.") if options[:file].nil?
|
36
|
+
abort("File not found. Abort.") unless File.file?(options[:file])
|
37
|
+
|
38
|
+
options[:lang] ||= "C"
|
39
|
+
lang = case options[:lang].downcase
|
40
|
+
when "c"
|
41
|
+
:C
|
42
|
+
when "assembly"
|
43
|
+
:S
|
44
|
+
else
|
45
|
+
abort("Language not supported. Abort.")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Read program
|
49
|
+
program = File.read(options[:file])
|
50
|
+
|
51
|
+
# Lexing
|
52
|
+
lexer = Lexer.new
|
53
|
+
tokens = lexer.tokenize(program)
|
54
|
+
|
55
|
+
# Parsing
|
56
|
+
parser = Parser.new
|
57
|
+
ast = parser.parse(tokens)
|
58
|
+
|
59
|
+
# Code Generator
|
60
|
+
generator = Generator.new
|
61
|
+
code = generator.generate(ast, lang)
|
62
|
+
|
63
|
+
if options[:output]
|
64
|
+
File.write(options[:output], code)
|
65
|
+
else
|
66
|
+
puts code
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if __FILE__ == $0
|
71
|
+
main
|
72
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Parser
|
2
|
+
class ParserError < StandardError; end
|
3
|
+
|
4
|
+
class Token
|
5
|
+
attr_reader :type, :child
|
6
|
+
|
7
|
+
def initialize(type, *args)
|
8
|
+
@type, @child = type, args
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
BinaryOps = {
|
13
|
+
:Plus => 50,
|
14
|
+
:Minus => 50,
|
15
|
+
:Asterisk => 60,
|
16
|
+
:Slash => 60,
|
17
|
+
:Percent => 60,
|
18
|
+
:Bar => 10,
|
19
|
+
:Ampersand => 30,
|
20
|
+
:Caret => 20,
|
21
|
+
:LeftShift => 40,
|
22
|
+
:RightShift => 40,
|
23
|
+
}
|
24
|
+
|
25
|
+
UnaryOps = {
|
26
|
+
:Tilde => 70,
|
27
|
+
:Minus => 80,
|
28
|
+
}
|
29
|
+
|
30
|
+
def self.parse_expression(tokens)
|
31
|
+
if tokens.length == 1 && tokens[0].type == :Variable
|
32
|
+
Token.new(:Variable, tokens[0].string)
|
33
|
+
elsif tokens.length == 1 && tokens[0].type == :Number
|
34
|
+
Token.new(:Number, tokens[0].string.to_i)
|
35
|
+
elsif tokens.length >= 3 && tokens[0].type == :LeftBracket && tokens[-1].type == :RightBracket
|
36
|
+
Token.new(:Bracket, parse_expression(tokens[1...-1]))
|
37
|
+
elsif tokens.length >= 2 && UnaryOps.include?(tokens[0].type)
|
38
|
+
if tokens[0].type == :Minus
|
39
|
+
Token.new(:Negative, parse_expression(tokens[1..-1]))
|
40
|
+
else
|
41
|
+
Token.new(tokens[0].type, parse_expression(tokens[1..-1]))
|
42
|
+
end
|
43
|
+
elsif tokens.length >= 3
|
44
|
+
head, precedence = nil, 100
|
45
|
+
(tokens.length-1).downto(0) do |index|
|
46
|
+
token = tokens[index]
|
47
|
+
next if index == 0 || index == tokens.length - 1 || !BinaryOps.include?(token.type)
|
48
|
+
next unless precedence.nil? || precedence > BinaryOps[token.type]
|
49
|
+
begin
|
50
|
+
head = Token.new(token.type, parse_expression(tokens[0..index-1]), parse_expression(tokens[index+1..-1]))
|
51
|
+
precedence = BinaryOps[token.type]
|
52
|
+
rescue ParserError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
raise ParserError, "Syntax error in statement: #{tokens.map(&:string).join(' ')}" if head.nil?
|
56
|
+
head
|
57
|
+
else
|
58
|
+
raise ParserError, "Syntax error in statement: #{tokens.map(&:string).join(' ')}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.parse_statement(tokens)
|
63
|
+
if tokens.length >= 1 && tokens[0].type == :Print
|
64
|
+
Token.new(:Print, parse_expression(tokens[1..-1]))
|
65
|
+
elsif tokens.length >= 2 && tokens[0].type == :Variable && tokens[1].type == :Equal
|
66
|
+
Token.new(:Move, Token.new(:Variable, tokens[0].string), parse_expression(tokens[2..-1]))
|
67
|
+
else
|
68
|
+
raise ParserError, "Syntax error in statement: #{tokens.map(&:string).join(' ')}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.parse(tokens)
|
73
|
+
head = Token.new(:Program)
|
74
|
+
statement = Array.new
|
75
|
+
tokens.each do |token|
|
76
|
+
if token.type == :SemiColon
|
77
|
+
head.child << parse_statement(statement)
|
78
|
+
statement = Array.new
|
79
|
+
else
|
80
|
+
statement << token
|
81
|
+
end
|
82
|
+
end
|
83
|
+
head
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "special-giggle/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "special-giggle"
|
7
|
+
spec.version = SpecialGiggle::VERSION
|
8
|
+
spec.authors = ["kalari499"]
|
9
|
+
spec.email = ["kalari499@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "BX language compiler written in Ruby"
|
12
|
+
spec.homepage = "https://github.com/kalari499/special-giggle"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
16
|
+
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| 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", "~> 2.0"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "irb", "~> 1.0"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: special-giggle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- kalari499
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-09-30 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: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
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: irb
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- kalari499@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- CHANGELOG.md
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- SPECIFICATION.md
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- lib/special-giggle.rb
|
72
|
+
- lib/special-giggle/generator.rb
|
73
|
+
- lib/special-giggle/lexer.rb
|
74
|
+
- lib/special-giggle/main.rb
|
75
|
+
- lib/special-giggle/parser.rb
|
76
|
+
- lib/special-giggle/version.rb
|
77
|
+
- special-giggle.gemspec
|
78
|
+
homepage: https://github.com/kalari499/special-giggle
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata:
|
82
|
+
changelog_uri: https://github.com/kalari499/special-giggle/blob/master/CHANGELOG.md
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.0.6
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: BX language compiler written in Ruby
|
102
|
+
test_files: []
|