astrapi 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/lexer.rb ADDED
@@ -0,0 +1,152 @@
1
+ require 'pp'
2
+ require 'strscan'
3
+ #require 'benchmark'
4
+
5
+ class Token
6
+ attr_accessor :kind,:val,:pos
7
+ def initialize tab
8
+ @kind,@val,@pos=*tab
9
+ end
10
+
11
+ def is_a? kind
12
+ case kind
13
+ when Symbol
14
+ return @kind==kind
15
+ when Array
16
+ for sym in kind
17
+ return true if @kind==sym
18
+ end
19
+ return false
20
+ else
21
+ raise "wrong type during lookahead"
22
+ end
23
+ end
24
+ end
25
+
26
+ module Astrapi
27
+
28
+ class Lexer
29
+
30
+ # I like explicit things :
31
+
32
+ #................................................
33
+ IDENT = /\AIDENT/
34
+ INT = /\AINT/
35
+ FLOAT = /\AFLOAT/
36
+ STRING = /\ASTRING/
37
+ #................................................
38
+ MODULE = /\Amodule$/
39
+ CLASS = /\Aclass$/
40
+ END_ = /\Aend$/
41
+ ATTR = /\Aattr$/
42
+ #................................................
43
+ NEWLINE = /[\n]/
44
+ SPACE = /[ \t]+/
45
+ #.............punctuation........................
46
+ LPAREN = /\A\(/
47
+ RPAREN = /\A\)/
48
+ COMMENT = /\A\#(.*)/
49
+ #.............operators..........................
50
+
51
+ ARROW = /\A\=\>/
52
+ LBRACK = /\A\[/
53
+ RBRACK = /\A\]/
54
+ LT = /\A</
55
+ # .............literals.........................
56
+ IDENTIFIER = /\A[a-zA-Z]+[a-zA-Z_0-9]*/i
57
+
58
+
59
+ attr_accessor :suppress_comment
60
+
61
+ def initialize str=''
62
+ init(str)
63
+ @suppress_space=true
64
+ @suppress_comment=false
65
+ end
66
+
67
+ def init str
68
+ @ss=StringScanner.new(str)
69
+ @line=0
70
+ end
71
+
72
+ def tokenize str
73
+ @tokens=[]
74
+ init(str)
75
+ until @ss.eos?
76
+ @tokens << next_token()
77
+ end
78
+ return @tokens[0..-2]
79
+ end
80
+
81
+ #next token can detect spaces length
82
+ def next_token
83
+
84
+ if @ss.bol?
85
+ @line+=1
86
+ @old_pos=@ss.pos
87
+ end
88
+
89
+ position=[@line,@ss.pos-@old_pos+1]
90
+
91
+ return :eos if @ss.eos?
92
+
93
+ case
94
+ when text = @ss.scan(NEWLINE)
95
+ next_token()
96
+ when text = @ss.scan(SPACE)
97
+ next_token()
98
+ when text = @ss.scan(COMMENT)
99
+ next_token()
100
+ when text = @ss.scan(ARROW)
101
+ return Token.new [:arrow,text,position]
102
+ when text = @ss.scan(LT)
103
+ return Token.new [:lt,text,position]
104
+ when text = @ss.scan(LBRACK)
105
+ return Token.new [:lbrack,text,position]
106
+ when text = @ss.scan(RBRACK)
107
+ return Token.new [:rbrack,text,position]
108
+ when text = @ss.scan(IDENTIFIER)
109
+ case
110
+ when value = text.match(IDENT)
111
+ return Token.new [:IDENT,text,position]
112
+ when value = text.match(INT)
113
+ return Token.new [:INT,text,position]
114
+ when value = text.match(FLOAT)
115
+ return Token.new [:FLOAT,text,position]
116
+ when value = text.match(STRING)
117
+ return Token.new [:STRING,text,position]
118
+ when value = text.match(MODULE)
119
+ return Token.new [:module,text,position]
120
+ when value = text.match(CLASS)
121
+ return Token.new [:class,text,position]
122
+ when value = text.match(END_)
123
+ return Token.new [:end,text,position]
124
+ when value = text.match(ATTR)
125
+ return Token.new [:attr,text,position]
126
+ when value = text.match(LPAREN)
127
+ return Token.new [:lparen,text,position]
128
+ when value = text.match(RPAREN)
129
+ return Token.new [:rparen,text,position]
130
+ else
131
+ return Token.new [:identifier,text,position]
132
+ end
133
+ else
134
+ x = @ss.getch
135
+ return Token.new [x, x,position]
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ if $PROGRAM_NAME == __FILE__
142
+ str=IO.read(ARGV[0])
143
+ #str.downcase!
144
+ puts str
145
+ t1 = Time.now
146
+ lexer=Astrapi::Lexer.new
147
+ tokens=lexer.tokenize(str)
148
+ t2 = Time.now
149
+ pp tokens
150
+ puts "number of tokens : #{tokens.size}"
151
+ puts "tokenized in : #{t2-t1} s"
152
+ end
data/lib/parser.rb ADDED
@@ -0,0 +1,110 @@
1
+ require 'pp'
2
+ require_relative 'lexer'
3
+ require_relative 'indent'
4
+ require_relative 'ast'
5
+
6
+ module Astrapi
7
+
8
+ class Parser
9
+ include Indent #helper methods mixed in
10
+
11
+ attr_accessor :lexer,:tokens
12
+
13
+ def initialize
14
+ @verbose=false
15
+ @lexer=Lexer.new
16
+ end
17
+
18
+ def parse filename
19
+ str=IO.read(filename)
20
+ @tokens=lexer.tokenize(str)
21
+ @tokens=@tokens.select{|tok| tok.kind!=:comment}
22
+ pp @tokens if @verbose
23
+ parseModule
24
+ end
25
+
26
+ def acceptIt
27
+ tok=tokens.shift
28
+ say "consuming #{tok.val} (#{tok.kind})"
29
+ tok
30
+ end
31
+
32
+ def showNext
33
+ tokens.first
34
+ end
35
+
36
+ def expect kind
37
+ if (actual=showNext.kind)!=kind
38
+ abort "ERROR at #{showNext.pos}. Expecting #{kind}. Got #{actual}"
39
+ else
40
+ return acceptIt()
41
+ end
42
+ end
43
+
44
+ def parseModule
45
+ indent "parseModule"
46
+ expect :module
47
+ name=Identifier.new(expect :identifier)
48
+ classes=[]
49
+ while showNext.is_a? :class
50
+ classes << parseClass
51
+ end
52
+ expect :end
53
+ dedent
54
+ return Astrapi::Module.new(name,classes)
55
+ end
56
+
57
+ def parseClass
58
+ indent "parseClass"
59
+ expect :class
60
+ name=Identifier.new(expect :identifier)
61
+ if showNext.is_a? :lt
62
+ acceptIt
63
+ inheritance= Identifier.new(expect :identifier)
64
+ end
65
+ attrs=[]
66
+ while showNext.is_a? :attr
67
+ attrs << parseAttr
68
+ end
69
+ expect :end
70
+ dedent
71
+ return Astrapi::Klass.new(name,inheritance,attrs)
72
+ end
73
+
74
+ def parseAttr
75
+ indent "parseAttr"
76
+ expect :attr
77
+ name=Identifier.new(expect :identifier)
78
+ expect :arrow
79
+ type=parseAttrType
80
+ dedent
81
+ Attr.new(name,type)
82
+ end
83
+
84
+ def parseAttrType
85
+ indent "parseAttrType"
86
+ if showNext.is_a? [:IDENT,:FLOAT,:INT]
87
+ type=Type.new(Identifier.new(acceptIt))
88
+ else
89
+ type=Type.new(Identifier.new(expect :identifier))
90
+ end
91
+ if showNext.is_a? :lbrack
92
+ acceptIt
93
+ expect :rbrack
94
+ type=ArrayOf.new(type)
95
+ end
96
+ dedent
97
+ return type
98
+ end
99
+
100
+ end
101
+ end
102
+
103
+ if $PROGRAM_NAME == __FILE__
104
+ filename=ARGV[0]
105
+ raise "need a file !" if filename.nil?
106
+ t1 = Time.now
107
+ Astrapi::Parser.new.parse(filename)
108
+ t2 = Time.now
109
+ puts "parsed in : #{t2-t1} s"
110
+ end
@@ -0,0 +1,68 @@
1
+ require_relative 'ast'
2
+ require_relative 'indent'
3
+ require_relative 'code'
4
+
5
+ module Astrapi
6
+
7
+ class PrettyPrinter
8
+
9
+ include Indent
10
+
11
+ def print ast
12
+ ast.accept(self)
13
+ end
14
+
15
+ def visitModule modul,args=nil
16
+ indent "visitModule"
17
+ code=Code.new
18
+ code << "module #{name=modul.name}"
19
+ code.newline
20
+ code.indent=2
21
+ modul.classes.each{|k| code << k.accept(self) ; code.newline}
22
+ code.indent=0
23
+ code << "end"
24
+ dedent
25
+ return code
26
+ end
27
+
28
+ def visitKlass klass,args=nil
29
+ indent "visitKlass"
30
+ code = Code.new
31
+ inherit="< #{klass.inheritance}" if klass.inheritance
32
+ code << "class #{klass.name} #{inherit}"
33
+ code.indent=2
34
+ klass.attrs.each{|attr| code << attr.accept(self)}
35
+ code.indent=0
36
+ code << "end"
37
+ dedent
38
+ code
39
+ end
40
+
41
+ def visitAttr attr,args=nil
42
+ indent "visitAttr"
43
+ type=attr.type.accept(self)
44
+ str="attr #{attr.name} => #{type}"
45
+ dedent
46
+ str
47
+ end
48
+
49
+ def visitType type,args=nil
50
+ indent "visitType"
51
+ dedent
52
+ return type.name.to_s
53
+ end
54
+
55
+ def visitArrayOf arrayOf,args=nil
56
+ indent "visitArrayOf"
57
+ str="#{arrayOf.type.name}[]"
58
+ dedent
59
+ return str
60
+ end
61
+
62
+ def visitIdentifier id,args=nil
63
+ indent "visitIdentifier"
64
+ dedent
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,329 @@
1
+ require 'erb'
2
+ require_relative 'ast'
3
+
4
+ module Astrapi
5
+
6
+ class RubyGenerator
7
+
8
+ attr_accessor :mm
9
+
10
+ #Astrapi basic types :
11
+ BASIC_TYPES = [:integer,:float,:range,:ident,:string]
12
+
13
+ def generate mm
14
+ @mm=mm
15
+ @path=__dir__ #ruby > 2.0
16
+ @path+="/"
17
+ generate_misc
18
+ generate_ast
19
+ generate_dot_ast_printer
20
+ generate_sexp_lexer
21
+ generate_sexp_parser
22
+ generate_sexp_pretty_printer
23
+ generate_visitor
24
+ generate_dsl_compiler
25
+ end
26
+
27
+ def generate_ast
28
+ puts "----> generating #{mm.name} DSL AST classes"
29
+ mm_name=mm.name
30
+ code = Code.new
31
+ code << "# code generated automatically by M3E"
32
+ code << "module #{mm_name}"
33
+ code.newline
34
+ code.indent=2
35
+ code << "class Ast"
36
+ code.indent=4
37
+ code << "attr_accessor :comments_"
38
+ code << "def accept(visitor, arg=nil)"
39
+ code << " name = self.class.name.split(/::/)[1]"
40
+ code << " visitor.send(\"visit\#{name}\".to_sym, self ,arg) # Metaprograming !"
41
+ code << "end"
42
+ code.newline
43
+ code.indent=2
44
+ code << "end"
45
+ code.newline
46
+ code.indent=2
47
+ code << "class Comment_ < Ast"
48
+ code.indent=4
49
+ code << "attr_accessor :list"
50
+ code.indent=2
51
+ code << "end"
52
+ code.newline
53
+ code.indent=2
54
+
55
+ mm.classes.each{|k|
56
+ code << code_for(k)
57
+ code.newline
58
+ }
59
+ code.indent=0
60
+ code << "end"
61
+ code.save_as("#{mm_name.to_s.downcase}_ast.rb",false)
62
+ end
63
+
64
+ def code_for klass
65
+ code=Code.new
66
+ mother = klass.inheritance
67
+ inheritance="< #{klass.inheritance}" if mother
68
+ inheritance ||= "< Ast"
69
+ code << "class #{klass.name} #{inheritance}"
70
+ code.indent=2
71
+ code << "attr_accessor #{klass.attrs.collect{|atr| ':'+atr.name.to_s}.join(',')}"
72
+ code << "def initialize"
73
+ code.indent=4
74
+ klass.attrs.each do |atr|
75
+ init=(atr.type.is_a? ArrayOf) ? "[]" : "nil"
76
+ code << "@#{atr.name} = #{init}"
77
+ end
78
+ code.indent=2
79
+ code << "end"
80
+ code.indent=0
81
+ code << "end"
82
+ code
83
+ end
84
+
85
+ def generate_sexp_lexer
86
+ puts "----> generating #{mm.name} DSL lexer"
87
+ keywords=mm.classes.collect{|k| k.name.to_s}.map{|k| k.downcase.to_sym}
88
+ regexp_code=Code.new #to be inserted in ERB template
89
+ regexp_code.indent=4
90
+ keywords.each do |kw|
91
+ regexp_code << "#{kw.upcase.to_s.ljust(30)} = /\\A#{kw}/"
92
+ end
93
+ keywords_regexp=regexp_code.finalize
94
+ #.......
95
+ regexp_code=Code.new
96
+ regexp_code.indent=8
97
+ keywords.each do |kw|
98
+ regexp_code << "when value = text.match(#{kw.upcase})"
99
+ regexp_code << " return Token.new [:#{kw},text,position]"
100
+ end
101
+ apply_regexp=regexp_code.finalize
102
+ #...... call templating engine
103
+ engine = ERB.new(IO.read(@path+"template_lexer.rb"))
104
+ lexer_rb_code=engine.result(binding)
105
+ File.open("#{mm.name.to_s.downcase}_lexer.rb",'w'){|f| f.puts lexer_rb_code}
106
+ end
107
+
108
+ def generate_sexp_parser
109
+ puts "----> generating #{mm.name} DSL parser"
110
+ code=Code.new
111
+ code.indent=4
112
+ mm.classes.each do |klass|
113
+ kname=klass.name.to_s
114
+ v=kname.downcase
115
+ code.newline
116
+ code << "def parse#{kname}"
117
+ code.indent=6
118
+ code << "indent \"#{klass.name}\""
119
+ code << "if n=nil_maybe?"
120
+ code << " return n"
121
+ code << "end"
122
+ code << "#{v}_ = #{mm.name}::#{kname}.new"
123
+ code << "#{v}_.comments_=parse_comments()"
124
+ code << "expect :lparen"
125
+ code << "expect :#{v}"
126
+ code << core_parsing_for(klass)
127
+ code << "expect :rparen"
128
+ code << "dedent"
129
+ code << "return #{v}_"
130
+ code.indent=4
131
+ code << "end"
132
+ end
133
+ parsing_methods=code.finalize
134
+ #...... call templating engine
135
+ engine = ERB.new(IO.read(@path+"template_parser.rb"))
136
+ parser_rb_code=engine.result(binding)
137
+ File.open("#{mm.name.to_s.downcase}_parser.rb",'w'){|f| f.puts parser_rb_code}
138
+ end
139
+
140
+ def generate_sexp_pretty_printer
141
+ puts "----> generating #{mm.name} DSL pretty printer"
142
+ code=Code.new
143
+ code.indent=4
144
+ mm.classes.each do |klass|
145
+ kname=klass.name.to_s
146
+ v=kname.downcase
147
+ code.newline
148
+ code << "def visit#{kname}(#{v}_,args=nil)"
149
+ code.indent=6
150
+ code << "indent \"#{klass.name}\""
151
+ code << "ary=[]"
152
+ code << "ary << #{v}_.comments_.val if #{v}_.comments_"
153
+ code << "ary << #{v}_.class.to_s.downcase.split('::')[1].to_sym"
154
+ attrs = hierarchical_attrs(klass) + klass.attrs #note the order!
155
+ code_for_attrs=attrs.collect{|atr|
156
+ if atr.type.is_a? ArrayOf
157
+ e=((name=atr.name.to_s).end_with?('s')) ? name[0..-2] : "e"
158
+ code << "#{v}_.#{atr.name}.each do |#{e}|"
159
+ code.indent=8
160
+ code << "ary << #{e}.accept(self,args)"
161
+ code.indent=6
162
+ code << "end"
163
+ else
164
+ code << "ary << #{v}_.#{atr.name}.accept(self)"
165
+ end
166
+ }
167
+ code << "dedent"
168
+ code << "return ary"
169
+ code.indent=4
170
+ code << "end"
171
+ end
172
+ visiting_methods=code.finalize
173
+ #...... call templating engine
174
+ engine = ERB.new(IO.read(@path+"template_pretty_printer.rb"))
175
+ parser_rb_code=engine.result(binding)
176
+ File.open("#{mm.name.to_s.downcase}_pp.rb",'w'){|f|
177
+ f.puts parser_rb_code
178
+ }
179
+ end
180
+
181
+ def generate_visitor
182
+ puts "----> generating #{mm.name} DSL visitor"
183
+ code=Code.new
184
+ code.indent=4
185
+ mm.classes.each do |klass|
186
+ kname=klass.name.to_s
187
+ v=kname.downcase
188
+ code.newline
189
+ code << "def visit#{kname}(#{v}_,args=nil)"
190
+ code.indent=6
191
+ code << "indent \"#{klass.name}\""
192
+ attrs = hierarchical_attrs(klass) + klass.attrs #note the order!
193
+ code_for_attrs=attrs.collect{|atr|
194
+ if atr.type.is_a? ArrayOf
195
+ e=((name=atr.name.to_s).end_with?('s')) ? name[0..-2] : "e"
196
+ code << "#{v}_.#{atr.name}.each do |#{e}|"
197
+ code.indent=8
198
+ code << "#{e}.accept(self,args)"
199
+ code.indent=6
200
+ code << "end"
201
+ else
202
+ code << "#{v}_.#{atr.name}.accept(self)"
203
+ end
204
+ }
205
+ code << "dedent"
206
+ code.indent=4
207
+ code << "end"
208
+ end
209
+ visiting_methods=code.finalize
210
+ #...... call templating engine
211
+ engine = ERB.new(IO.read(@path+"template_visitor.rb"))
212
+ parser_rb_code=engine.result(binding)
213
+ File.open("#{mm.name.to_s.downcase}_visitor.rb",'w'){|f|
214
+ f.puts parser_rb_code
215
+ }
216
+ end
217
+
218
+ def generate_dot_ast_printer
219
+ puts "----> generating #{mm.name} DSL AST printer"
220
+ #...... call templating engine
221
+ engine = ERB.new(IO.read(@path+"template_ast_printer.rb"))
222
+ printer_rb_code=engine.result(binding)
223
+ File.open("#{mm.name.to_s.downcase}_ast_printer.rb",'w'){|f| f.puts printer_rb_code}
224
+ end
225
+
226
+ def generate_dsl_compiler
227
+ puts "----> generating #{mm.name} DSL compiler"
228
+ #...... call templating engine
229
+ engine = ERB.new(IO.read(@path+"template_compiler.rb"))
230
+ code=engine.result(binding)
231
+ File.open("#{mm.name.to_s.downcase}_compiler.rb",'w'){|f| f.puts code}
232
+ end
233
+
234
+ def generate_misc
235
+ engine = ERB.new(IO.read(@path+"template_indent.rb"))
236
+ code=engine.result(binding)
237
+ File.open("indent.rb",'w'){|f| f.puts code}
238
+ engine = ERB.new(IO.read(@path+"template_code.rb"))
239
+ code=engine.result(binding)
240
+ File.open("code.rb",'w'){|f| f.puts code}
241
+ end
242
+
243
+ def core_parsing_for klass
244
+ kname=klass.name.to_s
245
+ #puts "#{kname}".center(80,'=')
246
+ v=kname.downcase
247
+ attrs = hierarchical_attrs(klass) + klass.attrs #note the order!
248
+ code=Code.new
249
+ attrs.each{|attr| code << parsing_code_for(attr,v)}
250
+ return code
251
+ end
252
+
253
+ def hierarchical_attrs klass
254
+ inherited_attrs=[]
255
+ if mother=find_mother_of(klass)
256
+ inherited_attrs << mother.attrs
257
+ inherited_attrs << hierarchical_attrs(mother)
258
+ end
259
+ return inherited_attrs.flatten
260
+ end
261
+
262
+ def find_mother_of klass
263
+ if id=klass.inheritance #identifier
264
+ return mother=mm.classes.find{|k| k.name==id}
265
+ end
266
+ nil
267
+ end
268
+
269
+ def find_sons_of klass
270
+ ret=mm.classes.select{|k| k.inheritance==klass.name}
271
+ ret
272
+ end
273
+
274
+ def parsing_code_for attr,ast_node_var
275
+ code=Code.new
276
+ case type=attr.type
277
+ when ArrayOf #note the position, before 'when Type' !
278
+ possible_starters=compute_possible_starters(type.type)
279
+ code << "comments=parse_comments()"
280
+ lookahead=possible_starters.first.to_s.end_with?("_lit") ? 0 : 1
281
+ starters=possible_starters.collect{|sym| ":#{sym}"}.join(',')
282
+ code << "while showNext(#{lookahead}) && showNext(#{lookahead}).is_a?([#{starters}])"
283
+ code.indent=2
284
+ code << "case showNext(#{lookahead}).kind"
285
+ possible_starters.each do |starter|
286
+ code << "when :#{starter}"
287
+ code.indent=4
288
+ if starter.to_s.end_with?("_lit") #base types
289
+ code << "#{ast_node_var}_.#{attr.name} << acceptIt"
290
+ else
291
+ code << "#{ast_node_var}_.#{attr.name} << parse#{starter.capitalize}()"
292
+ end
293
+ #code << "#{ast_node_var}_.#{attr.name} << parse#{starter.capitalize}()"
294
+ code.indent=2
295
+ end
296
+ code << "else "
297
+ code << " abort \"ERROR when parsing attribute #{attr.name} : expecting one of [#{starters}].\n Got \#{showNext(1).kind}\""
298
+ code << "end"
299
+ code << "if #{ast_node_var}_.#{attr.name}.last.respond_to? :comments_"
300
+ code.indent=4
301
+ code << "#{ast_node_var}_.#{attr.name}.last.comments_=comments"
302
+ code.indent=2
303
+ code << "end"
304
+ code << "comments=parse_comments()"
305
+ code.indent=0
306
+ code << "end"
307
+ when Type
308
+ if is_basic_type(type) #base types
309
+ code << "#{ast_node_var}_.#{attr.name} = acceptIt"
310
+ else
311
+ code << "#{ast_node_var}_.#{attr.name} = parse#{type.name}()"
312
+ end
313
+ end
314
+ return code
315
+ end
316
+
317
+ def is_basic_type type
318
+ type.name.to_s.upcase==type.name.to_s
319
+ end
320
+
321
+ def compute_possible_starters type
322
+ ret=[]
323
+ sons=find_sons_of(type)
324
+ starters= (sons << type).flatten
325
+ ret=starters.collect{|starter| starter.name.to_s.downcase.to_sym}
326
+ ret=ret.collect{|sym| (BASIC_TYPES.include? sym) ? (sym.to_s+"_lit").to_sym : sym}
327
+ end
328
+ end
329
+ end