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 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
@@ -0,0 +1,6 @@
1
+ xa := 7;
2
+ xb := 3;
3
+
4
+ loop xb do
5
+ xa := xa + 1
6
+ end
data/lib/fruitloop.rb ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__)))
2
+
3
+ require "lexer"
4
+ require "parser"
5
+
6
+ lexer = Lexer.new
7
+ parser = Parser.new(lexer)
8
+ parser.parse_start
@@ -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,9 @@
1
+ a := a + 5;
2
+ b := b + 8;
3
+
4
+ loop a do
5
+ loop b do
6
+ xa := xa + 1
7
+ end
8
+ end
9
+
@@ -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
@@ -0,0 +1,11 @@
1
+ class NodeLoop < ParentNode
2
+ attr_accessor :to
3
+
4
+ def accept(visitor)
5
+ visitor.visit_loop_start self
6
+ stmts.each do |stmt|
7
+ stmt.accept visitor
8
+ end
9
+ visitor.visit_loop_end self
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class ParentNode
2
+
3
+ attr_accessor :stmts
4
+
5
+ def initialize
6
+ @stmts = []
7
+ end
8
+
9
+ def <<(it)
10
+ @stmts << it
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ class NodeStart < ParentNode
2
+ def accept(visitor)
3
+ visitor.visit_start self
4
+ stmts.each do |stmt|
5
+ stmt.accept visitor
6
+ end
7
+ end
8
+ 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
@@ -0,0 +1,6 @@
1
+ xa := 7;
2
+ xb := 1;
3
+
4
+ loop xb do
5
+ xa := xa - 1
6
+ end
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: []