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