astrapi 0.0.5

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/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