astrapi 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/astrapi +9 -0
- data/doc/astrapi.html +149 -0
- data/lib/ast.rb +68 -0
- data/lib/checker.rb +61 -0
- data/lib/class_diagram_printer.rb +83 -0
- data/lib/code.rb +48 -0
- data/lib/compiler.rb +149 -0
- data/lib/dbg.rb +7 -0
- data/lib/dot_printer.rb +72 -0
- data/lib/indent.rb +19 -0
- data/lib/lexer.rb +152 -0
- data/lib/parser.rb +110 -0
- data/lib/pretty_printer.rb +68 -0
- data/lib/ruby_generator.rb +329 -0
- data/lib/template_ast_printer.rb +79 -0
- data/lib/template_code.rb +48 -0
- data/lib/template_compiler.rb +59 -0
- data/lib/template_indent.rb +19 -0
- data/lib/template_lexer.rb +139 -0
- data/lib/template_parser.rb +75 -0
- data/lib/template_pretty_printer.rb +46 -0
- data/lib/template_visitor.rb +18 -0
- data/lib/version.rb +3 -0
- data/lib/visitor.rb +53 -0
- data/tests/geometry.mm +32 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 10ee1c3dea18bdd7755a762060ccc1a1aa0692ca
|
4
|
+
data.tar.gz: cb41eab2377dafbac4d3b79c90e88873ec83973e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d327cb9aa4133ef33a4c47b191fbea985b2adc7e746d952077b1ec5caee7820fcfc9382b980a5e4966fd1b21490cecca79beeb5f7cb436eaf0321a1af08fcd70
|
7
|
+
data.tar.gz: 342378734808d44a6dd3993d593d6036669f7e3dd6be18aed39b966798d7416a309b2a8337d8850c9590591af093f4d0dbb80985229af075cb87929811267857
|
data/bin/astrapi
ADDED
data/doc/astrapi.html
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
<h1 id="astrapi-meta-compiler">Astrapi meta-compiler</h1>
|
2
|
+
|
3
|
+
<h2 id="what-is-astrapi-">What is Astrapi ?</h2>
|
4
|
+
|
5
|
+
<p>Astrapi is meta-compiler for Sexp-based Domain Specific Languages : once you have described your DSL concepts (abstract syntax) thanks to Astrapi language, the compiler generates several files for you:</p>
|
6
|
+
|
7
|
+
<ul>
|
8
|
+
<li>the corresponding classes</li>
|
9
|
+
<li>lexer and parser</li>
|
10
|
+
<li>generic visitor</li>
|
11
|
+
<li>pretty printer</li>
|
12
|
+
<li>graphical AST viewer</li>
|
13
|
+
</ul>
|
14
|
+
|
15
|
+
<p>Finally a driver for you own DSL compiler is also generated. Version 0.0.2 provides Ruby generation, but Python, Java and C++ will be available soon.</p>
|
16
|
+
|
17
|
+
<p>Astrapi-generated DSL parser will assume your DSL models are written in plain <strong>s-expressions</strong>.</p>
|
18
|
+
|
19
|
+
<h2 id="what-are-s-expressions-">What are s-expressions ?</h2>
|
20
|
+
<p>S-expressions, abreviated as "sexps", actually mean "symbolic expressions". They originated from the famous LISP language. Compiler designers resort to <em>sexp</em> as the most direct mean to capture Abstract Syntax Trees (AST) in a textual format.</p>
|
21
|
+
|
22
|
+
<p>Sexps are convenient to serialize both data <em>and</em> code, which offers a superiority over other serialization formats like XML, YAML or JSON.</p>
|
23
|
+
|
24
|
+
<p>It may be noticed that several S-expressions parsers exist around. In the Ruby ecosystems, SXP and Sexpistols can be recommanded. However, these parsers will just turn the parenthesized expressions into a Ruby native data structure : namely arrays of arrays, etc. If your intent is to consider each s-expression as an instance of a <em>custom</em> class, then Astrapi is for you !</p>
|
25
|
+
|
26
|
+
<h2 id="how-to-install-">How to install ?</h2>
|
27
|
+
<p>In your terminal, simply type : <strong>gem install astrapi</strong></p>
|
28
|
+
|
29
|
+
<h2 id="quick-start">Quick start</h2>
|
30
|
+
<p>In this example, we invent a toy language (DSL) that aims at describing simple geometry. Let us begin with examples programs written in our expected syntax :</p>
|
31
|
+
|
32
|
+
<!-- HTML generated using hilite.me -->
|
33
|
+
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%">(<span style="color: #0066BB; font-weight: bold">scene</span> <span style="color: #996633">test</span>
|
34
|
+
(<span style="color: #0066BB; font-weight: bold">square</span> <span style="color: #996633">s1</span>
|
35
|
+
(<span style="color: #0066BB; font-weight: bold">position</span> <span style="color: #0000DD; font-weight: bold">123</span> <span style="color: #0000DD; font-weight: bold">345</span>)
|
36
|
+
(<span style="color: #0066BB; font-weight: bold">size</span> <span style="color: #0000DD; font-weight: bold">12</span>)
|
37
|
+
)
|
38
|
+
(<span style="color: #0066BB; font-weight: bold">circle</span> <span style="color: #996633">c1</span>
|
39
|
+
(<span style="color: #0066BB; font-weight: bold">position</span> <span style="color: #0000DD; font-weight: bold">123</span> <span style="color: #0000DD; font-weight: bold">345</span>)
|
40
|
+
(<span style="color: #0066BB; font-weight: bold">size</span> <span style="color: #0000DD; font-weight: bold">12</span> <span style="color: #0000DD; font-weight: bold">23</span>)
|
41
|
+
)
|
42
|
+
(<span style="color: #0066BB; font-weight: bold">rectangle</span> <span style="color: #996633">s2</span>
|
43
|
+
(<span style="color: #0066BB; font-weight: bold">position</span> <span style="color: #0000DD; font-weight: bold">323</span> <span style="color: #0000DD; font-weight: bold">445</span>)
|
44
|
+
(<span style="color: #0066BB; font-weight: bold">size</span> <span style="color: #0000DD; font-weight: bold">12</span> <span style="color: #0000DD; font-weight: bold">34</span>)
|
45
|
+
)
|
46
|
+
)
|
47
|
+
</pre></div>
|
48
|
+
|
49
|
+
<p>Now let's express the concepts of this model : let's name this a <em>metamodel</em>. I suffix this file with '.mm'. It ressembles <em>Ruby modules and class</em>, but it is not.</p>
|
50
|
+
|
51
|
+
<!-- HTML generated using hilite.me -->
|
52
|
+
<div style="background: #f0f3f3; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #006699; font-weight: bold">module</span> <span style="color: #00CCFF; font-weight: bold">Geometry</span>
|
53
|
+
|
54
|
+
<span style="color: #006699; font-weight: bold">class</span> <span style="color: #00AA88; font-weight: bold">Scene</span>
|
55
|
+
<span style="color: #006699">attr</span> elements <span style="color: #555555">=></span> <span style="color: #336600">Shape</span><span style="color: #555555">[]</span>
|
56
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
57
|
+
|
58
|
+
<span style="color: #006699; font-weight: bold">class</span> <span style="color: #00AA88; font-weight: bold">Shape</span>
|
59
|
+
<span style="color: #006699">attr</span> <span style="color: #336666">id</span> <span style="color: #555555">=></span> <span style="color: #336600">IDENT</span>
|
60
|
+
<span style="color: #006699">attr</span> position <span style="color: #555555">=></span> <span style="color: #336600">Position</span>
|
61
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
62
|
+
|
63
|
+
<span style="color: #006699; font-weight: bold">class</span> <span style="color: #00AA88; font-weight: bold">Square</span> <span style="color: #555555"><</span> <span style="color: #336600">Shape</span>
|
64
|
+
<span style="color: #006699">attr</span> size <span style="color: #555555">=></span> <span style="color: #336600">INTEGER</span>
|
65
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
66
|
+
|
67
|
+
<span style="color: #006699; font-weight: bold">class</span> <span style="color: #00AA88; font-weight: bold">Circle</span> <span style="color: #555555"><</span> <span style="color: #336600">Shape</span>
|
68
|
+
<span style="color: #006699">attr</span> radius <span style="color: #555555">=></span> <span style="color: #336600">INTEGER</span>
|
69
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
70
|
+
|
71
|
+
<span style="color: #006699; font-weight: bold">class</span> <span style="color: #00AA88; font-weight: bold">Rectangle</span> <span style="color: #555555"><</span> <span style="color: #336600">Shape</span>
|
72
|
+
<span style="color: #006699">attr</span> size <span style="color: #555555">=></span> <span style="color: #336600">Size</span>
|
73
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
74
|
+
|
75
|
+
<span style="color: #006699; font-weight: bold">class</span> <span style="color: #00AA88; font-weight: bold">Size</span>
|
76
|
+
<span style="color: #006699">attr</span> dims <span style="color: #555555">=></span> <span style="color: #336600">INTEGER</span><span style="color: #555555">[]</span>
|
77
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
78
|
+
|
79
|
+
<span style="color: #006699; font-weight: bold">class</span> <span style="color: #00AA88; font-weight: bold">Position</span>
|
80
|
+
<span style="color: #006699">attr</span> coord <span style="color: #555555">=></span> <span style="color: #336600">INTEGER</span><span style="color: #555555">[]</span>
|
81
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
82
|
+
<span style="color: #006699; font-weight: bold">end</span>
|
83
|
+
</pre></div>
|
84
|
+
|
85
|
+
<p>Then compile this metamodel using Astrapi :</p>
|
86
|
+
|
87
|
+
<!-- HTML generated using hilite.me -->
|
88
|
+
<div style="background: #f0f3f3; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%">
|
89
|
+
jcll$ > astrapi geometry.mm
|
90
|
+
ASTRAPI meta-compiler for Sexp-based DSLs (c) J-C Le Lann 2016
|
91
|
+
==> parsing metamodel.................... geometry.mm
|
92
|
+
==> pretty print metamodel............... geometry_pp.mm
|
93
|
+
==> generate dot for metamodel........... geometry_ast.dot
|
94
|
+
==> checking metamodel
|
95
|
+
==> generating class diagram............. geometry_class_diagram.dot
|
96
|
+
==> generate software stack for DSL 'Geometry'. Ruby version
|
97
|
+
----> generating Geometry DSL AST classes
|
98
|
+
----> generating Geometry DSL AST printer
|
99
|
+
----> generating Geometry DSL lexer
|
100
|
+
----> generating Geometry DSL parser
|
101
|
+
----> generating Geometry DSL pretty printer
|
102
|
+
----> generating Geometry DSL compiler
|
103
|
+
</pre></div>
|
104
|
+
|
105
|
+
<p>Now we can play with our brand new Geometry compiler !</p>
|
106
|
+
|
107
|
+
<!-- HTML generated using hilite.me -->
|
108
|
+
<div style="background: #f8f8f8; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%">$> ruby geometry_compiler.rb ex1.geo
|
109
|
+
Geometry compiler
|
110
|
+
==> parsing ex1.geo
|
111
|
+
==> pretty print to ........ ex1.geo_pp.geometry
|
112
|
+
==> generate dot for AST in ex1.geo.dot
|
113
|
+
compiled in : 0.001463371 s
|
114
|
+
</pre></div>
|
115
|
+
|
116
|
+
<p>In particular, we can have a look at the graphical AST (plain graphiz dot format). If you have xdot installed, simply type <strong>xdot ex1.geo.dot</strong> and the AST of our example will show up like this.</p>
|
117
|
+
|
118
|
+
<p><img src="./img/ex1.geo.png" alt="Image of AST model" /></p>
|
119
|
+
|
120
|
+
<h2 id="about-the-metamodel">About the Metamodel</h2>
|
121
|
+
|
122
|
+
<h3 id="basic-syntax">Basic syntax</h3>
|
123
|
+
<p>Astrapi metamodel syntax remains very basic. A concept is captured as a <strong>class</strong>, followed by the list of its attributes <strong>attr</strong>. The type of the attributes appears after <strong>=></strong>. Attributes can be simple or multiple. The order of declaration of classes has no importance, but the <strong>order of <em>attr</em> declarations is fundamental</strong>, as it drives the generated DSL parser. This may be enhanced in a future version.</p>
|
124
|
+
|
125
|
+
<h3 id="basic-data-types">Basic data types</h3>
|
126
|
+
<p>There also exist some basic types understood by Astrapi and parsed as <em>terminals</em> :</p>
|
127
|
+
|
128
|
+
<ul>
|
129
|
+
<li><strong>IDENT</strong> will make the lexer recognize tokens matching the regexp /[a-zA-Z]+[a-zA-Z_0-9]*/i.</li>
|
130
|
+
<li><strong>INTEGER</strong> : regexp is /[0-9]+/</li>
|
131
|
+
<li><strong>FLOAT</strong> : regexp is /[0-9]+.[0-9]+/</li>
|
132
|
+
<li><strong>RANGE</strong> : regexp is /[0-9]+..[0-9+/</li>
|
133
|
+
<li><strong>STRING</strong> : regexp is /"(.*)"/</li>
|
134
|
+
<li><strong>NIL</strong> : regexp is /nil/</li>
|
135
|
+
</ul>
|
136
|
+
|
137
|
+
<p>Note that you can use <strong>nil</strong> in your DSL model, either for expected concepts or terminals.</p>
|
138
|
+
|
139
|
+
<h3 id="graphical-view-of-the-metamodel">Graphical view of the Metamodel</h3>
|
140
|
+
<p>A graphical view of the metamodel structure is also generated by Astrapi. This is a basic <em>class-diagram</em> of the AST classes. The name of the dot file appears during the metamodel compilation. It is suffix a ** xxx_ast.dot <strong>.
|
141
|
+
To view it, type **xdot xxx_ast.dot</strong>. For our Geometry example :</p>
|
142
|
+
|
143
|
+
<p><img src="./img/geometry_class_diagram.png" alt="Image of the metamodel" /></p>
|
144
|
+
|
145
|
+
<h2 id="comments-in-astrapi-s-expressions">Comments in Astrapi s-expressions</h2>
|
146
|
+
<p>Comments are accepted in Astrapi s-expressions. Comments starts with <strong>#</strong> symbol and ends at the end of line. A comment will be attached (attribute <strong>.comments_</strong>) to the AST node following this comment. As such, comments can be reused (code generation etc).</p>
|
147
|
+
|
148
|
+
<h2 id="credits">Credits</h2>
|
149
|
+
<p>Please <a href="mailto:lelannje@ensta-bretagne.fr">drop me an email</a> if you use Astrapi, or want some additional features or bug fixes.</p>
|
data/lib/ast.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Astrapi
|
2
|
+
|
3
|
+
class Ast
|
4
|
+
def accept(visitor, arg=nil)
|
5
|
+
name = self.class.name.split(/::/)[1]
|
6
|
+
visitor.send("visit#{name}".to_sym, self ,arg) # Metaprograming !
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Module < Ast
|
11
|
+
attr_accessor :name,:classes
|
12
|
+
def initialize name,classes=[]
|
13
|
+
@name,@classes=name,classes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Klass < Ast
|
18
|
+
attr_accessor :name,:inheritance,:attrs
|
19
|
+
def initialize name,inheritance=nil,attrs=[]
|
20
|
+
@name,@inheritance,@attrs=name,inheritance,attrs
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Attr < Ast
|
25
|
+
attr_accessor :name,:type
|
26
|
+
def initialize name,type
|
27
|
+
@name,@type=name,type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Type < Ast
|
32
|
+
attr_accessor :name
|
33
|
+
def initialize name
|
34
|
+
@name=name
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(other)
|
38
|
+
name==other.name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ArrayOf < Type
|
43
|
+
attr_accessor :type
|
44
|
+
def initialize type
|
45
|
+
@type=type
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#..............................
|
50
|
+
class Identifier < Ast
|
51
|
+
def initialize tok
|
52
|
+
@tok=tok
|
53
|
+
end
|
54
|
+
|
55
|
+
def str
|
56
|
+
@tok.val.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
str
|
61
|
+
end
|
62
|
+
|
63
|
+
def ==(other)
|
64
|
+
str==other.str
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/checker.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'ast'
|
2
|
+
require_relative 'visitor'
|
3
|
+
require_relative 'code'
|
4
|
+
|
5
|
+
module Astrapi
|
6
|
+
|
7
|
+
class Checker < Visitor
|
8
|
+
|
9
|
+
# the checker checks :
|
10
|
+
# - that classes are uniquely defined
|
11
|
+
# - that attrs are uniquely defined withing a given class
|
12
|
+
# - that attrs types are defined in the metamodel
|
13
|
+
attr_accessor :mm
|
14
|
+
|
15
|
+
def check ast
|
16
|
+
@verbose=false
|
17
|
+
@mm=ast
|
18
|
+
ast.accept(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def visitModule modul,args=nil
|
22
|
+
indent "visitModule"
|
23
|
+
name=modul.name.accept(self)
|
24
|
+
modul.classes.each{|k| k.accept(self)}
|
25
|
+
dedent
|
26
|
+
end
|
27
|
+
|
28
|
+
def visitKlass klass,args=nil
|
29
|
+
indent "visitKlass #{klass.name}"
|
30
|
+
if klass.inheritance
|
31
|
+
|
32
|
+
end
|
33
|
+
klass.attrs.each{|attr| attr.accept(self)}
|
34
|
+
dedent
|
35
|
+
end
|
36
|
+
|
37
|
+
def visitAttr attr,args=nil
|
38
|
+
indent "visitAttr"
|
39
|
+
attr.name.accept(self)
|
40
|
+
attr.type.accept(self)
|
41
|
+
dedent
|
42
|
+
end
|
43
|
+
|
44
|
+
def visitType type,args=nil
|
45
|
+
indent "visitType"
|
46
|
+
dedent
|
47
|
+
end
|
48
|
+
|
49
|
+
def visitArrayOf arrayOf,args=nil
|
50
|
+
indent "visitArrayOf"
|
51
|
+
arrayOf.type.accept(self)
|
52
|
+
dedent
|
53
|
+
end
|
54
|
+
|
55
|
+
def visitIdentifier id,args=nil
|
56
|
+
indent "visitIdentifier"
|
57
|
+
say " - #{id}"
|
58
|
+
dedent
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative 'ast'
|
2
|
+
require_relative 'indent'
|
3
|
+
require_relative 'code'
|
4
|
+
|
5
|
+
module Astrapi
|
6
|
+
|
7
|
+
class ClassDiagramPrinter
|
8
|
+
|
9
|
+
include Indent
|
10
|
+
|
11
|
+
DECL_FOR_INSTRINSICS={
|
12
|
+
:IDENT => "IDENT[label=\"{IDENT|+kind\n+val\n+pos\n\}\"]",
|
13
|
+
:FLOAT => "FLOAT[label=\"{FLOAT|+kind\n+val\n+pos\n\}\"]",
|
14
|
+
:INTEGER => "INTEGER[label=\"{INTEGER|+kind\n+val\n+pos\n\}\"]",
|
15
|
+
:STRING => "STRING[label=\"{STRING|+kind\n+val\n+pos\n\}\"]",
|
16
|
+
:FRAC => "FRAC[label=\"{FRAC|+kind\n+val\n+pos\n\}\"]",
|
17
|
+
:NIL => "NIL[label=\"{NIL|+kind\n+val\n+pos\n\}\"]"
|
18
|
+
}
|
19
|
+
|
20
|
+
def print ast
|
21
|
+
ast.accept(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def visitModule modul,args=nil
|
25
|
+
indent "visitModule"
|
26
|
+
|
27
|
+
decl=Code.new # declarations of nodes
|
28
|
+
modul.classes.collect do |k|
|
29
|
+
attrs_str=k.attrs.collect{|attr| "+ #{attr.name}\\n"}
|
30
|
+
decl << "#{k.name}[label = \"{#{k.name}|#{attrs_str.join()}|...}\"]"
|
31
|
+
end
|
32
|
+
cnx=Code.new #connexions
|
33
|
+
modul.classes.each{|k| cnx << k.accept(self)}
|
34
|
+
#................................................................
|
35
|
+
code=Code.new
|
36
|
+
code << "digraph hierarchy {"
|
37
|
+
code << " size=\"5,5\""
|
38
|
+
#code << " splines=ortho"
|
39
|
+
code << " node[shape=record,style=filled,fillcolor=gray95]"
|
40
|
+
code << " edge[dir=back, arrowtail=empty]"
|
41
|
+
code.newline
|
42
|
+
code.indent=2
|
43
|
+
code << decl
|
44
|
+
code << cnx
|
45
|
+
code.indent=0
|
46
|
+
code << "}"
|
47
|
+
dedent
|
48
|
+
return code
|
49
|
+
end
|
50
|
+
|
51
|
+
def visitKlass klass,args=nil
|
52
|
+
indent "visitKlass"
|
53
|
+
code = Code.new
|
54
|
+
klass.attrs.each do |attr|
|
55
|
+
case type=attr.type
|
56
|
+
when ArrayOf
|
57
|
+
sink=type.type.name.to_s
|
58
|
+
head="label=\"*\""
|
59
|
+
when Type
|
60
|
+
sink=type.name.to_s
|
61
|
+
head="label=1"
|
62
|
+
code << DECL_FOR_INSTRINSICS[sink.to_sym] if is_instrinsic(type)
|
63
|
+
end
|
64
|
+
code << "#{klass.name} -> #{sink}[#{head},arrowtail=diamond]"
|
65
|
+
end
|
66
|
+
if klass.inheritance
|
67
|
+
code << "#{klass.inheritance} -> #{klass.name}"
|
68
|
+
end
|
69
|
+
dedent
|
70
|
+
code
|
71
|
+
end
|
72
|
+
|
73
|
+
def is_instrinsic type
|
74
|
+
if type.respond_to? :type
|
75
|
+
str=type.type.name.to_s
|
76
|
+
return str==str.upcase
|
77
|
+
else
|
78
|
+
str=type.name.to_s
|
79
|
+
return str==str.upcase
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/code.rb
ADDED
@@ -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
|
data/lib/compiler.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
require_relative 'dbg'
|
5
|
+
require_relative 'version'
|
6
|
+
require_relative 'parser'
|
7
|
+
require_relative 'checker'
|
8
|
+
require_relative 'pretty_printer'
|
9
|
+
require_relative 'dot_printer'
|
10
|
+
require_relative 'class_diagram_printer'
|
11
|
+
require_relative 'ruby_generator'
|
12
|
+
|
13
|
+
module Astrapi
|
14
|
+
|
15
|
+
$options={
|
16
|
+
:verbosity => 0
|
17
|
+
}
|
18
|
+
|
19
|
+
class Compiler
|
20
|
+
|
21
|
+
include Dbg
|
22
|
+
|
23
|
+
attr_accessor :mm
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
puts "ASTRAPI meta-compiler for Sexp-based DSLs [version #{VERSION}] (c) J-C Le Lann 2016"
|
27
|
+
end
|
28
|
+
|
29
|
+
def analyze_options args
|
30
|
+
args << "-h" if args.empty?
|
31
|
+
opt_parser = OptionParser.new do |opts|
|
32
|
+
opts.banner = "Usage: astrapi <file.mm> [options]"
|
33
|
+
|
34
|
+
opts.on("-p", "--parse", "[P]arsing only. AST generated internally") do |n|
|
35
|
+
$options[:parse_only]=true
|
36
|
+
$options[:verbosity]=2
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-c", "--check", "[C]hecking only. Semantic/contextual analysis ") do |n|
|
40
|
+
$options[:check_only]=true
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("--pp", "Pretty-print back the Astrapi code") do |n|
|
44
|
+
$options[:pp_only]=true
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("--version", "Prints version") do |n|
|
48
|
+
puts VERSION
|
49
|
+
abort
|
50
|
+
end
|
51
|
+
|
52
|
+
targets=[:ruby,:java,:python,:cpp]
|
53
|
+
opts.on("--lang TARGET", "Target LANGUAGE (ruby by default,java,python,cpp)",targets) do |lang|
|
54
|
+
case lang
|
55
|
+
when :ruby
|
56
|
+
$options[:target_language]=lang
|
57
|
+
when :java,:python,:cpp
|
58
|
+
raise "#{lang} version not implemented yet. Sorry"
|
59
|
+
$options[:target_language]=lang
|
60
|
+
else
|
61
|
+
raise "illegal target language #{lang}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on("-v LEVEL", "--verbose level", "allow verbose levels (0,1)") do |level|
|
66
|
+
$options[:verbosity]=level.to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on("-h", "--help", "Prints this help") do
|
70
|
+
puts opts
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
begin
|
76
|
+
opt_parser.parse!(args)
|
77
|
+
rescue Exception => e
|
78
|
+
puts e
|
79
|
+
dbg 0,e.backtrace if $options[:verbosity]>2
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
# remaining ARGV (remind that parse! has removed everything else)
|
83
|
+
filename = ARGV.pop
|
84
|
+
unless filename
|
85
|
+
dbg 0,"Need to specify a filename to process"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
return filename
|
89
|
+
end
|
90
|
+
|
91
|
+
def compile mm_filename
|
92
|
+
@mm=parse(mm_filename)
|
93
|
+
pretty_print
|
94
|
+
dot_print
|
95
|
+
check
|
96
|
+
generate_class_diagram
|
97
|
+
generate_ruby
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse mm_filename
|
101
|
+
puts "==> parsing metamodel.................... #{mm_filename}"
|
102
|
+
ast=Astrapi::Parser.new.parse(mm_filename)
|
103
|
+
exit if $options[:parse_only]
|
104
|
+
ast
|
105
|
+
end
|
106
|
+
|
107
|
+
def pretty_print
|
108
|
+
target="#{mm.name.to_s.downcase}_pp.mm"
|
109
|
+
puts "==> pretty print metamodel............... #{target}"
|
110
|
+
code=PrettyPrinter.new.print(mm)
|
111
|
+
code.save_as(target,verbose=false)
|
112
|
+
exit if $options[:pp_only]
|
113
|
+
end
|
114
|
+
|
115
|
+
def check
|
116
|
+
puts "==> checking metamodel"
|
117
|
+
Checker.new.check(mm)
|
118
|
+
end
|
119
|
+
|
120
|
+
def generate_class_diagram
|
121
|
+
target="#{mm.name.to_s.downcase}_class_diagram.dot"
|
122
|
+
puts "==> generating class diagram............. #{target}"
|
123
|
+
code=ClassDiagramPrinter.new.print(mm)
|
124
|
+
code.save_as(target,verbose=false)
|
125
|
+
end
|
126
|
+
|
127
|
+
def dot_print
|
128
|
+
target="#{mm.name.to_s.downcase}_ast.dot"
|
129
|
+
puts "==> generate dot for metamodel........... #{target}"
|
130
|
+
code=DotPrinter.new.print(mm)
|
131
|
+
code.save_as(target,verbose=false)
|
132
|
+
end
|
133
|
+
|
134
|
+
def generate_ruby
|
135
|
+
puts "==> generate software stack for DSL '#{@mm.name}'. Ruby version"
|
136
|
+
RubyGenerator.new.generate(mm)
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
if $PROGRAM_NAME == __FILE__
|
143
|
+
filename=ARGV[0]
|
144
|
+
raise "need a file !" if filename.nil?
|
145
|
+
t1 = Time.now
|
146
|
+
Astrapi::Compiler.new.compile(filename)
|
147
|
+
t2 = Time.now
|
148
|
+
puts "compiled in : #{t2-t1} s"
|
149
|
+
end
|
data/lib/dbg.rb
ADDED
data/lib/dot_printer.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative 'ast'
|
2
|
+
require_relative 'indent'
|
3
|
+
require_relative 'code'
|
4
|
+
|
5
|
+
module Astrapi
|
6
|
+
|
7
|
+
class DotPrinter
|
8
|
+
|
9
|
+
include Indent
|
10
|
+
|
11
|
+
attr_accessor :code,:nodes_decl,:nodes_cnx
|
12
|
+
|
13
|
+
def print ast #entry method
|
14
|
+
@verbose=false
|
15
|
+
@nodes_decl=Code.new
|
16
|
+
@nodes_cnx=Code.new
|
17
|
+
@printed_cnx={} #Cosmetic ! to keep track of already printed cnx source->sink
|
18
|
+
@code=Code.new
|
19
|
+
code << "digraph G {"
|
20
|
+
code.indent=2
|
21
|
+
code << "ordering=out;"
|
22
|
+
code << "ranksep=.4;"
|
23
|
+
code << "bgcolor=\"lightgrey\";"
|
24
|
+
code.newline
|
25
|
+
code << "node [shape=box, fixedsize=false, fontsize=12, fontname=\"Helvetica-bold\", fontcolor=\"blue\""
|
26
|
+
code << " width=.25, height=.25, color=\"black\", fillcolor=\"white\", style=\"filled, solid, bold\"];"
|
27
|
+
code << "edge [arrowsize=.5, color=\"black\", style=\"bold\"]"
|
28
|
+
process(ast)
|
29
|
+
code << @nodes_decl
|
30
|
+
code << @nodes_cnx
|
31
|
+
code.indent=0
|
32
|
+
code << "}"
|
33
|
+
return code
|
34
|
+
end
|
35
|
+
|
36
|
+
def process node,level=0
|
37
|
+
|
38
|
+
kname=node.class.name.split("::")[1]
|
39
|
+
id=node.object_id
|
40
|
+
nodes_decl << "#{id} [label=\"#{kname}\"]"
|
41
|
+
|
42
|
+
node.instance_variables.each{|vname|
|
43
|
+
ivar=node.instance_variable_get(vname)
|
44
|
+
vname=vname.to_s[1..-1]
|
45
|
+
case ivar
|
46
|
+
when Array
|
47
|
+
ivar.each_with_index{|e,idx|
|
48
|
+
sink=process(e,level+2)
|
49
|
+
@printed_cnx[id]||=[]
|
50
|
+
nodes_cnx << "#{id} -> #{sink} [label=\"#{vname}[#{idx}]\"]" if not @printed_cnx[id].include? sink
|
51
|
+
@printed_cnx[id] << sink
|
52
|
+
}
|
53
|
+
when Token
|
54
|
+
val=ivar.val
|
55
|
+
sink="#{ivar.object_id}"
|
56
|
+
nodes_decl << "#{sink} [label=\"#{val}\",color=\"red\"]"
|
57
|
+
@printed_cnx[id]||=[]
|
58
|
+
nodes_cnx << "#{id} -> #{sink} [label=\"#{vname}\"]" if not @printed_cnx[id].include? sink
|
59
|
+
@printed_cnx[id] << sink
|
60
|
+
else
|
61
|
+
sink=process(ivar,level+2)
|
62
|
+
@printed_cnx[id]||=[]
|
63
|
+
nodes_cnx << "#{id} -> #{sink} [label=\"#{vname}\"]" if not @printed_cnx[id].include? sink
|
64
|
+
@printed_cnx[id] << sink
|
65
|
+
end
|
66
|
+
|
67
|
+
}
|
68
|
+
return id
|
69
|
+
end
|
70
|
+
|
71
|
+
end #class
|
72
|
+
end #module
|
data/lib/indent.rb
ADDED