fruitloop 0.3
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.
- data/MIT-LICENSE +22 -0
- data/README.md +33 -0
- data/Rakefile +30 -0
- data/lib/add.loop +6 -0
- data/lib/fruitloop.rb +8 -0
- data/lib/gen/jsvisitor.rb +53 -0
- data/lib/gen/symboltable.rb +20 -0
- data/lib/lexer.rb +105 -0
- data/lib/mult.loop +9 -0
- data/lib/nodes/node_assignment.rb +11 -0
- data/lib/nodes/node_loop.rb +11 -0
- data/lib/nodes/node_parent.rb +12 -0
- data/lib/nodes/node_start.rb +8 -0
- data/lib/parser.rb +176 -0
- data/lib/sub.loop +6 -0
- data/lib/token/all.rb +32 -0
- metadata +64 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
== FRUITLOOP
|
2
|
+
|
3
|
+
Copyright (c) 2012-2012 Thomas Hoefer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
FRUITLOOP
|
2
|
+
=========
|
3
|
+
Implementation of the LOOP programming language which compiles to JavaScript. See: http://de.wikipedia.org/wiki/LOOP-Programm.
|
4
|
+
|
5
|
+
|
6
|
+
DEPENDENCIES
|
7
|
+
============
|
8
|
+
- Ruby >= 1.9.2p290, not tested with other versions
|
9
|
+
- Node.js >= 0.5.6; the binary is expected to be located at /usr/local/bin/node
|
10
|
+
|
11
|
+
|
12
|
+
HOWTO
|
13
|
+
=====
|
14
|
+
- Run "ruby fruitloop.rb add.loop" to compile and execute "add.loop". The same goes for the other examples.
|
15
|
+
- The result of a computation will always be located in variable "xa".
|
16
|
+
|
17
|
+
|
18
|
+
GRAMMAR
|
19
|
+
=======
|
20
|
+
- S ::= P$
|
21
|
+
- P ::= id A X | loop id do P end X
|
22
|
+
- A ::= : B
|
23
|
+
- B ::= = C
|
24
|
+
- C ::= id D | number
|
25
|
+
- D ::= + number | - number
|
26
|
+
- X ::= ; P X | Epsilon
|
27
|
+
|
28
|
+
|
29
|
+
License
|
30
|
+
=======
|
31
|
+
FRUITLOOP is released under the MIT license: [www.opensource.org/licenses/MIT](www.opensource.org/licenses/MIT)
|
32
|
+
|
33
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#
|
2
|
+
# To change this template, choose Tools | Templates
|
3
|
+
# and open the template in the editor.
|
4
|
+
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rake'
|
8
|
+
require 'rake/clean'
|
9
|
+
require 'rake/gempackagetask'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
require 'rake/testtask'
|
12
|
+
|
13
|
+
Rake::GemPackageTask.new(spec) do |p|
|
14
|
+
p.gem_spec = spec
|
15
|
+
p.need_tar = true
|
16
|
+
p.need_zip = true
|
17
|
+
end
|
18
|
+
|
19
|
+
Rake::RDocTask.new do |rdoc|
|
20
|
+
files =['README', 'LICENSE', 'lib/**/*.rb']
|
21
|
+
rdoc.rdoc_files.add(files)
|
22
|
+
rdoc.main = "README" # page to start on
|
23
|
+
rdoc.title = "LoopLang Docs"
|
24
|
+
rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
|
25
|
+
rdoc.options << '--line-numbers'
|
26
|
+
end
|
27
|
+
|
28
|
+
Rake::TestTask.new do |t|
|
29
|
+
t.test_files = FileList['test/**/*.rb']
|
30
|
+
end
|
data/lib/add.loop
ADDED
data/lib/fruitloop.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'gen/symboltable'
|
2
|
+
|
3
|
+
class JSVisitor
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@target = ""
|
7
|
+
@loop_counter = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_start(node)
|
11
|
+
compile "sys = require('sys');"
|
12
|
+
compile "xa = 0;"
|
13
|
+
compile "/* adding null initializers */"
|
14
|
+
SymbolTable.null_initializers.each do |token|
|
15
|
+
compile "#{token.name} = 0;"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit_assignment(node)
|
20
|
+
if node.rvalue
|
21
|
+
compile "#{node.lvalue.name} = #{node.rvalue.number};"
|
22
|
+
else
|
23
|
+
compile "#{node.lvalue.name} = #{node.op1.name} #{node.op.op} #{node.op2.number}; "
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_loop_start(node)
|
28
|
+
compile "_loopTerminate#{@loop_counter} = #{node.to.name};"
|
29
|
+
compile "for(var _loopVar#{@loop_counter} = 0; _loopVar#{@loop_counter} < _loopTerminate#{@loop_counter}; _loopVar#{@loop_counter}++) {"
|
30
|
+
@loop_counter += 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_loop_end(node)
|
34
|
+
compile "}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
compile "sys.puts(\"xa: \" + xa);"
|
39
|
+
nodejs = IO.popen("/usr/local/bin/node", "r+")
|
40
|
+
nodejs.puts @target
|
41
|
+
nodejs.close_write
|
42
|
+
puts nodejs.gets
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def compile(source)
|
49
|
+
#p source
|
50
|
+
@target += "#{source}"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class SymbolTable
|
2
|
+
|
3
|
+
@@symbols = {}
|
4
|
+
@@identifiers = []
|
5
|
+
|
6
|
+
def self.get_id(sym_or_tk)
|
7
|
+
@@symbols[sym_or_tk.is_a?(Token) ? sym_or_tk.name : sym_or_tk]
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.add_id token
|
11
|
+
@@symbols[token.name] = token
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.add_null_initializer token
|
15
|
+
@@identifiers << token
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.null_initializers; @@identifiers; end;
|
19
|
+
|
20
|
+
end
|
data/lib/lexer.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "token/all"
|
2
|
+
|
3
|
+
|
4
|
+
# Scanner-Komponente
|
5
|
+
# Liefert bei einem Aufruf von "input_token" immer das nächste Token im Quellprogramm.
|
6
|
+
class Lexer
|
7
|
+
|
8
|
+
attr_accessor :lexem, :char, :state
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@source = ""
|
12
|
+
read_input(ARGV.first)
|
13
|
+
@pos = -1
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# Gibt das nächste Token der Eingabe zurück
|
18
|
+
def input_token
|
19
|
+
|
20
|
+
self.state = :start
|
21
|
+
next_char
|
22
|
+
|
23
|
+
while true
|
24
|
+
|
25
|
+
# puts "state: #{state}, char: #{char}, lexem: #{lexem}"
|
26
|
+
|
27
|
+
case state
|
28
|
+
when :start then
|
29
|
+
case char
|
30
|
+
when ":" then self.state = :colon
|
31
|
+
when "=" then self.state = :eq
|
32
|
+
when /[+|-]/ then self.state = :binop
|
33
|
+
when ";" then self.state = :semicolon
|
34
|
+
when "@" then self.state = :terminate
|
35
|
+
when /[a-z]/ then
|
36
|
+
self.lexem = char
|
37
|
+
self.state = :identifier_start
|
38
|
+
when /[0-9]/ then
|
39
|
+
self.lexem = char
|
40
|
+
self.state = :number_start
|
41
|
+
else next_char
|
42
|
+
end
|
43
|
+
when :colon then return TColon.new
|
44
|
+
when :eq then return TEq.new
|
45
|
+
when :binop then return TBinOp.new(char)
|
46
|
+
when :semicolon then return TSemicolon.new
|
47
|
+
when :identifier_start then
|
48
|
+
if next_char =~ /[a-z]/
|
49
|
+
self.lexem += char
|
50
|
+
else
|
51
|
+
self.state = :identifier
|
52
|
+
@pos -= 1
|
53
|
+
end
|
54
|
+
when :identifier then
|
55
|
+
l = get_and_clear_lexem
|
56
|
+
return case l
|
57
|
+
when "loop" then TLoop.new
|
58
|
+
when "do" then TDo.new
|
59
|
+
when "end" then TEnd.new
|
60
|
+
else TIdentifier.new(l)
|
61
|
+
end
|
62
|
+
when :number_start then
|
63
|
+
if next_char =~ /[0-9]/ then
|
64
|
+
self.lexem += char
|
65
|
+
else
|
66
|
+
@pos -= 1
|
67
|
+
self.state = :number
|
68
|
+
end
|
69
|
+
when :number then
|
70
|
+
l = get_and_clear_lexem
|
71
|
+
return TNumber.new(l)
|
72
|
+
when :terminate then return TTerminate.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Gibt das aktuelle Lexem zurück. Löscht zusätzlich die Instanzvariable.
|
81
|
+
def get_and_clear_lexem
|
82
|
+
lex = self.lexem
|
83
|
+
self.lexem = ""
|
84
|
+
lex
|
85
|
+
end
|
86
|
+
|
87
|
+
# Reader-Methode die das aktuelle Zeichen liefert
|
88
|
+
def char; @c; end;
|
89
|
+
|
90
|
+
# Liest den Quellcode
|
91
|
+
def read_input(file); @source = open(file).read; end;
|
92
|
+
|
93
|
+
# Liefert das nächste Zeichen der Eingabe
|
94
|
+
def next_char
|
95
|
+
if @pos <= @source.length-1
|
96
|
+
@pos += 1
|
97
|
+
@c = @source.slice(@pos, 1)
|
98
|
+
else
|
99
|
+
@c = "@"
|
100
|
+
end
|
101
|
+
@c
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
data/lib/mult.loop
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
class NodeAssignment
|
3
|
+
|
4
|
+
attr_accessor :lvalue # will always be present with either...
|
5
|
+
attr_accessor :op1, :op, :op2 # ... formal correct version or...
|
6
|
+
attr_accessor :rvalue # ... the normal version :)
|
7
|
+
|
8
|
+
def accept(visitor)
|
9
|
+
visitor.visit_assignment self
|
10
|
+
end
|
11
|
+
end
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require "nodes/node_parent"
|
2
|
+
require "nodes/node_assignment"
|
3
|
+
require "nodes/node_loop"
|
4
|
+
require "nodes/node_start"
|
5
|
+
require "gen/jsvisitor"
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
class Parser
|
10
|
+
|
11
|
+
attr_reader :lookahead
|
12
|
+
|
13
|
+
def initialize(lexer)
|
14
|
+
@lexer = lexer
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_tokens
|
18
|
+
while @token = @lexer.input_token
|
19
|
+
p @token
|
20
|
+
break if @token.is_a?TTerminate
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Parsing method for the start production
|
25
|
+
def parse_start
|
26
|
+
consume_token
|
27
|
+
case lookahead
|
28
|
+
when TIdentifier, TLoop then
|
29
|
+
@node_start = NodeStart.new # The AST´s root node
|
30
|
+
parse_p @node_start
|
31
|
+
match TTerminate
|
32
|
+
else
|
33
|
+
parse_error
|
34
|
+
end
|
35
|
+
|
36
|
+
# Evaluate AST after the syntax is good
|
37
|
+
traverse_ast
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def parse_p parent_node
|
42
|
+
case lookahead
|
43
|
+
when TIdentifier then
|
44
|
+
@node_assignment = NodeAssignment.new
|
45
|
+
@node_assignment.lvalue = lookahead
|
46
|
+
SymbolTable.add_id lookahead
|
47
|
+
parent_node << @node_assignment # Assignment node
|
48
|
+
match TIdentifier
|
49
|
+
parse_a
|
50
|
+
parse_x parent_node
|
51
|
+
when TLoop then
|
52
|
+
@node_loop = NodeLoop.new
|
53
|
+
parent_node << @node_loop # Loop node
|
54
|
+
match TLoop
|
55
|
+
to = lookahead
|
56
|
+
semantic_error "loop variable must be defined" unless SymbolTable.get_id(to)
|
57
|
+
@node_loop.to = to
|
58
|
+
match TIdentifier
|
59
|
+
match TDo
|
60
|
+
parse_p @node_loop
|
61
|
+
match TEnd
|
62
|
+
parse_x parent_node
|
63
|
+
else
|
64
|
+
parse_error
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# As x can evaluate to Epsilon...
|
70
|
+
def parse_x parent_node
|
71
|
+
case lookahead
|
72
|
+
when TSemicolon then # ... besides the case that it is not Epsilon ...
|
73
|
+
match TSemicolon
|
74
|
+
parse_p parent_node
|
75
|
+
parse_x parent_node
|
76
|
+
when TTerminate, TEnd, TSemicolon then # ... predict for the follow set.
|
77
|
+
# Epsilon production
|
78
|
+
else
|
79
|
+
# Error - the current lookahead is neither in..
|
80
|
+
# 1. FIRST(x)
|
81
|
+
# 2. FOLLOW(x) if Epsilon in FIRST(x)
|
82
|
+
parse_error
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def parse_a
|
88
|
+
case lookahead
|
89
|
+
when TColon then
|
90
|
+
match TColon
|
91
|
+
parse_b
|
92
|
+
else
|
93
|
+
parse_error
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def parse_b
|
99
|
+
case lookahead
|
100
|
+
when TEq then
|
101
|
+
match TEq
|
102
|
+
parse_c
|
103
|
+
else
|
104
|
+
parse_error
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def parse_c
|
110
|
+
case lookahead
|
111
|
+
when TIdentifier
|
112
|
+
SymbolTable.add_null_initializer lookahead
|
113
|
+
@node_assignment.op1 = lookahead # An assignment´s first operand
|
114
|
+
match TIdentifier
|
115
|
+
parse_d
|
116
|
+
when TNumber
|
117
|
+
@node_assignment.rvalue = lookahead # Short assignment
|
118
|
+
match TNumber
|
119
|
+
else
|
120
|
+
parse_error
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def parse_d
|
126
|
+
case lookahead
|
127
|
+
when TBinOp then
|
128
|
+
@node_assignment.op = lookahead # Operand
|
129
|
+
match TBinOp
|
130
|
+
@node_assignment.op2 = lookahead # An assignment´s second operand
|
131
|
+
match TNumber
|
132
|
+
else
|
133
|
+
parse_error
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# Checks if the current token is an instance of <which_class>. If this is not the case
|
141
|
+
# the programm terminates as an syntacial error occured. Reads the next token after the
|
142
|
+
# check was successful.
|
143
|
+
def match(which_class)
|
144
|
+
if lookahead.is_a?(which_class)
|
145
|
+
#p "Is: #{lookahead.class}, Expected: #{which_class}"
|
146
|
+
else
|
147
|
+
p "Syntaxfehler! Is: #{lookahead.class} Expected: #{which_class}"
|
148
|
+
exit
|
149
|
+
end
|
150
|
+
consume_token
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns the next token from the scanner
|
154
|
+
def consume_token
|
155
|
+
@lookahead = @lexer.input_token
|
156
|
+
@lookahead
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_error
|
160
|
+
p "Syntax error: #{lookahead.class}"
|
161
|
+
exit
|
162
|
+
end
|
163
|
+
|
164
|
+
def semantic_error(message)
|
165
|
+
p "Semantic error: #{message}"
|
166
|
+
exit
|
167
|
+
end
|
168
|
+
|
169
|
+
def traverse_ast
|
170
|
+
visitor = JSVisitor.new
|
171
|
+
@node_start.accept visitor
|
172
|
+
visitor.run
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
end
|
data/lib/sub.loop
ADDED
data/lib/token/all.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Token
|
2
|
+
attr_accessor :line, :row
|
3
|
+
end
|
4
|
+
|
5
|
+
class TIdentifier < Token
|
6
|
+
attr_accessor :name
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TNumber < Token
|
13
|
+
attr_accessor :number
|
14
|
+
def initialize(number)
|
15
|
+
@number = number
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TBinOp < Token
|
20
|
+
attr_accessor :op
|
21
|
+
def initialize(op)
|
22
|
+
@op = op
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class TColon < Token; end;
|
27
|
+
class TEq < Token; end;
|
28
|
+
class TTerminate < Token; end;
|
29
|
+
class TEnd < Token; end;
|
30
|
+
class TLoop < Token; end;
|
31
|
+
class TDo < Token; end;
|
32
|
+
class TSemicolon < Token; end;
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fruitloop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.3'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas Hoefer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-12 00:00:00.000000000 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
description: FRUITLOOP is an implementation of the LOOP programming language.
|
16
|
+
email: mail@tomhoefer.de
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.md
|
21
|
+
- MIT-LICENSE
|
22
|
+
files:
|
23
|
+
- MIT-LICENSE
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- lib/add.loop
|
27
|
+
- lib/fruitloop.rb
|
28
|
+
- lib/gen/jsvisitor.rb
|
29
|
+
- lib/gen/symboltable.rb
|
30
|
+
- lib/lexer.rb
|
31
|
+
- lib/mult.loop
|
32
|
+
- lib/nodes/node_assignment.rb
|
33
|
+
- lib/nodes/node_loop.rb
|
34
|
+
- lib/nodes/node_parent.rb
|
35
|
+
- lib/nodes/node_start.rb
|
36
|
+
- lib/parser.rb
|
37
|
+
- lib/sub.loop
|
38
|
+
- lib/token/all.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://www.github.com/thoefer/fruitloop
|
41
|
+
licenses: []
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.6.2
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: FRUITLOOP is an implementation of the LOOP programming language.
|
64
|
+
test_files: []
|