astrapi 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,79 @@
1
+ require_relative 'indent'
2
+ require_relative 'code'
3
+
4
+ module <%=mm.name%>
5
+
6
+ class DotPrinter
7
+
8
+ include Indent
9
+
10
+ attr_accessor :code,:nodes_decl,:nodes_cnx
11
+
12
+ def print ast #entry method
13
+ @verbose=false
14
+ @nodes_decl=Code.new
15
+ @nodes_cnx=Code.new
16
+ @printed_cnx={} #Cosmetic ! to keep track of already printed cnx source->sink
17
+ @code=Code.new
18
+ code << "digraph G {"
19
+ code.indent=2
20
+ code << "ordering=out;"
21
+ code << "ranksep=.4;"
22
+ code << "bgcolor=\"lightgrey\";"
23
+ code.newline
24
+ code << "node [shape=box, fixedsize=false, fontsize=12, fontname=\"Helvetica-bold\", fontcolor=\"blue\""
25
+ code << " width=.25, height=.25, color=\"black\", fillcolor=\"white\", style=\"filled, solid, bold\"];"
26
+ code << "edge [arrowsize=.5, color=\"black\", style=\"bold\"]"
27
+ process(ast)
28
+ code << @nodes_decl
29
+ code << @nodes_cnx
30
+ code.indent=0
31
+ code << "}"
32
+ return code
33
+ end
34
+
35
+ def process node,level=0
36
+ id=node.object_id
37
+ if node.is_a? Token
38
+ sink="#{id}"
39
+ val=node.val
40
+ nodes_decl << "#{sink} [label=\"#{val}\",color=\"red\"]"
41
+ else
42
+
43
+ kname=node.class.name.split("::")[1]
44
+ id=node.object_id
45
+ nodes_decl << "#{id} [label=\"#{kname}\"]"
46
+
47
+ node.instance_variables.each{|vname|
48
+ ivar=node.instance_variable_get(vname)
49
+ vname=vname.to_s[1..-1]
50
+ case ivar
51
+ when Array
52
+ ivar.each_with_index{|e,idx|
53
+ sink=process(e,level+2)
54
+ @printed_cnx[id]||=[]
55
+ nodes_cnx << "#{id} -> #{sink} [label=\"#{vname}[#{idx}]\"]" if not @printed_cnx[id].include? sink
56
+ @printed_cnx[id] << sink
57
+ }
58
+ when Token
59
+ val=ivar.val
60
+ sink="#{ivar.object_id}"
61
+ nodes_decl << "#{sink} [label=\"#{val}\",color=\"red\"]"
62
+ @printed_cnx[id]||=[]
63
+ nodes_cnx << "#{id} -> #{sink} [label=\"#{vname}\"]" if not @printed_cnx[id].include? sink
64
+ @printed_cnx[id] << sink
65
+ when nil
66
+ else
67
+ sink=process(ivar,level+2)
68
+ @printed_cnx[id]||=[]
69
+ nodes_cnx << "#{id} -> #{sink} [label=\"#{vname}\"]" if not @printed_cnx[id].include? sink
70
+ @printed_cnx[id] << sink
71
+ end
72
+
73
+ }
74
+ end
75
+ return id
76
+ end
77
+
78
+ end #class
79
+ end #module
@@ -0,0 +1,48 @@
1
+ class Code
2
+
3
+ attr_accessor :indent,:code
4
+
5
+ def initialize indent=0
6
+ @code=[]
7
+ @indent=indent
8
+ end
9
+
10
+ def <<(str)
11
+ if str.is_a? Code
12
+ str.code.each do |line|
13
+ @code << " "*@indent+line
14
+ end
15
+ elsif str.is_a? Array
16
+ str.each do |kode|
17
+ @code << kode
18
+ end
19
+ elsif str.nil?
20
+ else
21
+ @code << " "*@indent+str
22
+ end
23
+ end
24
+
25
+ def finalize dot=false
26
+ if dot
27
+ return @code.join('\n')
28
+ end
29
+ @code.join("\n") if @code.any?
30
+ end
31
+
32
+ def newline
33
+ @code << " "
34
+ end
35
+
36
+ def save_as filename,verbose=true,sep="\n"
37
+ str=self.finalize
38
+ File.open(filename,'w'){|f| f.puts(str)}
39
+ puts "saved code in file #{filename}" if verbose
40
+ return filename
41
+ end
42
+
43
+ def size
44
+ @code.size
45
+ end
46
+
47
+
48
+ end
@@ -0,0 +1,59 @@
1
+ require_relative '<%=mm.name.to_s.downcase%>_parser'
2
+ require_relative '<%=mm.name.to_s.downcase%>_pp'
3
+ require_relative '<%=mm.name.to_s.downcase%>_ast_printer'
4
+ require_relative '<%=mm.name.to_s.downcase%>_visitor'
5
+
6
+ module <%=mm.name%>
7
+
8
+ class Compiler
9
+ attr_accessor :ast
10
+
11
+ def initialize
12
+ puts "<%=mm.name%> compiler"
13
+ end
14
+
15
+ def compile filename
16
+ @ast=parse(filename)
17
+ visit
18
+ pretty_print
19
+ dot_print
20
+ end
21
+
22
+ def parse filename
23
+ @filename=filename
24
+ name,@suffix=filename.split("/").last.split(".")
25
+ @basename=File.basename(filename,"."+@suffix)
26
+ puts "==> parsing #{filename}"
27
+ <%=mm.name%>::Parser.new.parse(filename)
28
+ end
29
+
30
+ def visit
31
+ puts "==> dummy visit"
32
+ <%=mm.name%>::Visitor.new.visit(ast)
33
+ end
34
+
35
+ def pretty_print
36
+ target="#{@basename}_pp.#{@suffix}"
37
+ puts "==> pretty print to ............ #{target}"
38
+ code_str=<%=mm.name%>::PrettyPrinter.new.print(ast)
39
+ File.open("#{target}",'w'){|f| f.puts code_str}
40
+ end
41
+
42
+ def dot_print
43
+ target="#{@basename}.dot"
44
+ puts "==> generate dot for AST in .... #{target}"
45
+ code=<%=mm.name%>::DotPrinter.new.print(ast)
46
+ code.save_as(target,verbose=false)
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+ if $PROGRAM_NAME == __FILE__
53
+ filename=ARGV[0]
54
+ raise "need a <%=mm.name%> file !" if filename.nil?
55
+ t1 = Time.now
56
+ <%=mm.name%>::Compiler.new.compile(filename)
57
+ t2 = Time.now
58
+ puts "compiled in : #{t2-t1} s"
59
+ end
@@ -0,0 +1,19 @@
1
+ module Indent
2
+
3
+ INDENT=2
4
+
5
+ def indent str
6
+ @indentation||=-INDENT
7
+ @indentation+=INDENT
8
+ say(str)
9
+ end
10
+
11
+ def dedent
12
+ @indentation-=INDENT
13
+ end
14
+
15
+ def say str
16
+ puts " "*@indentation+str if @verbose
17
+ end
18
+
19
+ end
@@ -0,0 +1,139 @@
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
+ when Class
21
+ return kind==Token
22
+ else
23
+ raise "wrong type for token test"
24
+ end
25
+ end
26
+
27
+ def accept dummy,args=nil
28
+ val
29
+ end
30
+ end
31
+
32
+ module <%=mm.name%>
33
+
34
+ class Lexer
35
+ #................................................
36
+ NEWLINE = /[\n]/
37
+ SPACE = /[ \t]+/
38
+ #.............punctuation........................
39
+ LPAREN = /\A\(/
40
+ RPAREN = /\A\)/
41
+ COMMENT = /\A\#(.*)/
42
+ # .............M3E specific literals.............
43
+ IDENT = /\A[a-zA-Z]+[0-9a-zA-Z_]*/i
44
+ FLOAT = /\A[0-9]+\.[0-9]+/
45
+ FRAC = /\A[0-9]+\/[0-9]+/
46
+ INTEGER = /\A[0-9]+/
47
+ RANGE = /\A#{INTEGER}\.\.#{INTEGER}/
48
+ STRING = /\A\"(.*)\"/ # need to check!
49
+ NIL = /\Anil/
50
+ TRUE = /\A\true/
51
+ FALSE = /\Afalse/
52
+ #..............DSL keywords......................
53
+ <%=keywords_regexp%>
54
+
55
+ def initialize str=''
56
+ init(str)
57
+ end
58
+
59
+ def init str
60
+ @ss=StringScanner.new(str)
61
+ @line=0
62
+ end
63
+
64
+ def tokenize str
65
+ @tokens=[]
66
+ init(str)
67
+ until @ss.eos?
68
+ @tokens << next_token()
69
+ end
70
+ return @tokens[0..-2]
71
+ end
72
+
73
+ #next token can detect spaces length
74
+ def next_token
75
+
76
+ if @ss.bol?
77
+ @line+=1
78
+ @old_pos=@ss.pos
79
+ end
80
+
81
+ position=[@line,@ss.pos-@old_pos+1]
82
+
83
+ return :eos if @ss.eos?
84
+
85
+ case
86
+ when text = @ss.scan(NEWLINE)
87
+ next_token()
88
+ when text = @ss.scan(SPACE)
89
+ next_token()
90
+ when text = @ss.scan(LPAREN)
91
+ return Token.new [:lparen,text,position]
92
+ when text = @ss.scan(COMMENT)
93
+ return Token.new [:m3e_comment,text,position]
94
+ when text = @ss.scan(RPAREN)
95
+ return Token.new [:rparen,text,position]
96
+ when text = @ss.scan(FLOAT)
97
+ return Token.new [:float_lit,text,position]
98
+ when text = @ss.scan(FRAC)
99
+ return Token.new [:frac_lit,text,position]
100
+ when text = @ss.scan(RANGE)
101
+ return Token.new [:range_lit,text,position]
102
+ when text = @ss.scan(INTEGER)
103
+ return Token.new [:integer_lit,text,position]
104
+ when text = @ss.scan(STRING)
105
+ return Token.new [:string_lit,text,position]
106
+ when text = @ss.scan(FRAC)
107
+ return Token.new [:frac_lit,text,position]
108
+ when text = @ss.scan(IDENT)
109
+ case
110
+ <%=apply_regexp%>
111
+ when value = text.match(NIL)
112
+ return Token.new [:nil,text,position]
113
+ when value = text.match(TRUE)
114
+ return Token.new [:true,text,position]
115
+ when value = text.match(FALSE)
116
+ return Token.new [:false,text,position]
117
+ else
118
+ return Token.new [:identifier,text,position]
119
+ end
120
+ else
121
+ x = @ss.getch
122
+ return Token.new [x, x,position]
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ if $PROGRAM_NAME == __FILE__
129
+ str=IO.read(ARGV[0])
130
+ #str.downcase!
131
+ puts str
132
+ t1 = Time.now
133
+ lexer=<%=mm.name%>::Lexer.new
134
+ tokens=lexer.tokenize(str)
135
+ t2 = Time.now
136
+ pp tokens
137
+ puts "number of tokens : #{tokens.size}"
138
+ puts "tokenized in : #{t2-t1} s"
139
+ end
@@ -0,0 +1,75 @@
1
+ require 'pp'
2
+ require_relative 'indent'
3
+ require_relative '<%=mm.name.to_s.downcase%>_lexer'
4
+ require_relative '<%=mm.name.to_s.downcase%>_ast'
5
+
6
+ module <%=mm.name%>
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
+ last_pos = @tokens.last.pos
22
+ @tokens << Token.new([:eos,'',last_pos])
23
+ pp @tokens if @verbose
24
+ parse<%=mm.classes.first.name%>
25
+ end
26
+
27
+ def acceptIt
28
+ tok=tokens.shift
29
+ say "consuming #{tok.val} (#{tok.kind})"
30
+ tok
31
+ end
32
+
33
+ def showNext n=0
34
+ tokens[n]
35
+ end
36
+
37
+ def expect kind
38
+ if (actual=showNext.kind)!=kind
39
+ abort "ERROR at #{showNext.pos}. Expecting #{kind}. Got #{actual}"
40
+ else
41
+ return acceptIt()
42
+ end
43
+ end
44
+
45
+ def nil_maybe?
46
+ if showNext.is_a?(:nil)
47
+ return acceptIt
48
+ else
49
+ return nil
50
+ end
51
+ end
52
+
53
+ def parse_comments
54
+ ret=nil
55
+ while showNext.is_a? :m3e_comment
56
+ str||=""
57
+ str << acceptIt.val
58
+ end
59
+ if str
60
+ ret=Token.new([:comment,str,[0,0]])
61
+ end
62
+ ret
63
+ end
64
+ <%=parsing_methods%>
65
+
66
+ end
67
+ end
68
+ if $PROGRAM_NAME == __FILE__
69
+ filename=ARGV[0]
70
+ raise "need a file !" if filename.nil?
71
+ t1 = Time.now
72
+ <%=mm.name%>::Parser.new.parse(filename)
73
+ t2 = Time.now
74
+ puts "parsed in : #{t2-t1} s"
75
+ end
@@ -0,0 +1,46 @@
1
+ require 'pp'
2
+ require_relative '<%=mm.name.to_s.downcase%>_ast'
3
+ require_relative 'indent'
4
+ require_relative 'code'
5
+
6
+ module <%=mm.name%>
7
+
8
+ class PrettyPrinter
9
+
10
+ include Indent
11
+
12
+ def print ast
13
+ ary=ast.accept(self)
14
+ str=prpr(ary)
15
+ end
16
+
17
+ def prpr ary
18
+ print_rec(ary).lstrip
19
+ end
20
+
21
+ def print_rec ary,indent=0
22
+ str=""
23
+ case e=ary
24
+ when Array
25
+ str << "\n"
26
+ if e.first.to_s.start_with? "#"
27
+ str << " "*indent+ary.shift+"\n"
28
+ end
29
+ str << " "*indent+"("
30
+ ary.each do |e|
31
+ str << print_rec(e,indent+2)
32
+ end
33
+ if ary.last.is_a? Array
34
+ str << "\n"+" "*indent+")"
35
+ else
36
+ str<< "\b)"
37
+ end
38
+ else
39
+ str << e.to_s+ " "
40
+ end
41
+ return str
42
+ end
43
+
44
+ <%=visiting_methods%>
45
+ end #class
46
+ end #module
@@ -0,0 +1,18 @@
1
+ require_relative '<%=mm.name.to_s.downcase%>_ast'
2
+ require_relative 'indent'
3
+ require_relative 'code'
4
+
5
+ module <%=mm.name%>
6
+
7
+ class Visitor
8
+
9
+ include Indent
10
+
11
+ def visit ast
12
+ @verbose=true
13
+ ast.accept(self)
14
+ end
15
+
16
+ <%=visiting_methods%>
17
+ end #class
18
+ end #module
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Astrapi
2
+ VERSION="0.0.5"
3
+ end
data/lib/visitor.rb ADDED
@@ -0,0 +1,53 @@
1
+ require_relative 'ast'
2
+ require_relative 'indent'
3
+ require_relative 'code'
4
+
5
+ module Astrapi
6
+
7
+ class Visitor
8
+
9
+ include Indent
10
+
11
+ def visit ast
12
+ ast.accept(self)
13
+ end
14
+
15
+ def visitModule modul,args=nil
16
+ indent "visitModule"
17
+ name=modul.name.accept(self)
18
+ modul.classes.each{|k| k.accept(self)}
19
+ dedent
20
+ end
21
+
22
+ def visitKlass klass,args=nil
23
+ indent "visitKlass #{klass.name.sym}"
24
+ klass.inheritance.accept(self)
25
+ klass.attrs.each{|attr| attr.accept(self)}
26
+ dedent
27
+ end
28
+
29
+ def visitAttr attr,args=nil
30
+ indent "visitAttr"
31
+ attr.name.accept(self)
32
+ attr.type.accept(self)
33
+ dedent
34
+ end
35
+
36
+ def visitType type,args=nil
37
+ indent "visitType"
38
+ end
39
+
40
+ def visitArrayOf arrayOf,args=nil
41
+ indent "visitArrayOf"
42
+ arrayOf.type.accept(self)
43
+ dedent
44
+ end
45
+
46
+ def visitIdentifier id,args=nil
47
+ indent "visitIdentifier"
48
+ say " - #{id.sym}"
49
+ dedent
50
+ end
51
+
52
+ end
53
+ end
data/tests/geometry.mm ADDED
@@ -0,0 +1,32 @@
1
+ module Geometry
2
+
3
+ class Scene
4
+ attr name => IDENT
5
+ attr elements => Shape[]
6
+ end
7
+
8
+ class Shape
9
+ attr id => IDENT
10
+ attr position => Position
11
+ end
12
+
13
+ class Square < Shape
14
+ attr size => Size
15
+ end
16
+
17
+ class Circle < Shape
18
+ attr radius => Size
19
+ end
20
+
21
+ class Rectangle < Shape
22
+ attr size => Size
23
+ end
24
+
25
+ class Size
26
+ attr dims => INTEGER[]
27
+ end
28
+
29
+ class Position
30
+ attr coord => INTEGER[]
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: astrapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Jean-Christophe Le Lann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Starting from the metamodel of a DSL (abstract classes and their relationship),
14
+ Astrapi generates a compiler front end for this DSL. The model itself is expressed
15
+ in S-expressions
16
+ email: lelannje@ensta-bretagne.fr
17
+ executables:
18
+ - astrapi
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/astrapi
23
+ - doc/astrapi.html
24
+ - lib/ast.rb
25
+ - lib/checker.rb
26
+ - lib/class_diagram_printer.rb
27
+ - lib/code.rb
28
+ - lib/compiler.rb
29
+ - lib/dbg.rb
30
+ - lib/dot_printer.rb
31
+ - lib/indent.rb
32
+ - lib/lexer.rb
33
+ - lib/parser.rb
34
+ - lib/pretty_printer.rb
35
+ - lib/ruby_generator.rb
36
+ - lib/template_ast_printer.rb
37
+ - lib/template_code.rb
38
+ - lib/template_compiler.rb
39
+ - lib/template_indent.rb
40
+ - lib/template_lexer.rb
41
+ - lib/template_parser.rb
42
+ - lib/template_pretty_printer.rb
43
+ - lib/template_visitor.rb
44
+ - lib/version.rb
45
+ - lib/visitor.rb
46
+ - tests/geometry.mm
47
+ homepage: http://www.jcll.fr/astrapi.html
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message: Thanks for installing! A small doc & examples are in astrapi/doc
52
+ and astrapi/tests.
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 2.0.0
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.4.6
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: meta-compiler for S-expression based Domain Specific Languages (DSL)
72
+ test_files: []