gisele 0.1.0 → 0.2.0
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/CHANGELOG.md +16 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/gisele.gemspec +1 -0
- data/gisele.noespec +2 -1
- data/lib/gisele/command.rb +21 -6
- data/lib/gisele/language/ast/bool_and.rb +14 -0
- data/lib/gisele/language/ast/bool_expr.rb +14 -0
- data/lib/gisele/language/ast/bool_not.rb +14 -0
- data/lib/gisele/language/ast/bool_or.rb +14 -0
- data/lib/gisele/language/ast/elsif_clause.rb +14 -0
- data/lib/gisele/language/ast/helpers.rb +15 -9
- data/lib/gisele/language/ast/if_st.rb +14 -0
- data/lib/gisele/language/ast/node.rb +39 -3
- data/lib/gisele/language/ast/task_call_st.rb +14 -0
- data/lib/gisele/language/ast/var_ref.rb +14 -0
- data/lib/gisele/language/ast/when_clause.rb +14 -0
- data/lib/gisele/language/ast/while_st.rb +14 -0
- data/lib/gisele/language/ast.rb +2 -2
- data/lib/gisele/language/dot.yml +19 -0
- data/lib/gisele/language/sugar_removal.rb +30 -9
- data/lib/gisele/language/syntax/bool_expr.rb +14 -0
- data/lib/gisele/language/syntax/bool_lit.rb +1 -1
- data/lib/gisele/language/syntax/grammar.citrus +4 -3
- data/lib/gisele/language/syntax/node.rb +2 -1
- data/lib/gisele/language/to_graph.rb +119 -0
- data/lib/gisele/language.rb +4 -1
- data/lib/gisele/loader.rb +1 -0
- data/lib/gisele/version.rb +1 -1
- data/spec/command/main/gisele_ast_ruby.stdout +6 -3
- data/spec/command/main/gisele_graph.cmd +1 -0
- data/spec/command/main/gisele_graph.stdout +30 -0
- data/spec/command/main/gisele_help.stdout +10 -5
- data/spec/command/main/gisele_no_sugar.stdout +11 -5
- data/spec/command/main/gisele_version.stdout +1 -1
- data/spec/fixtures/tasks/complete.gis +18 -0
- data/spec/fixtures/tasks/simple.ast +57 -19
- data/spec/spec_helper.rb +8 -0
- data/spec/unit/language/ast/test_node.rb +22 -0
- data/spec/unit/language/sugar_removal/test_if_to_guarded_commands.rb +32 -12
- data/spec/unit/language/syntax/test_to_ast.rb +23 -11
- data/spec/unit/language/test_syntax.rb +5 -0
- data/spec/unit/language/test_to_graph.rb +10 -0
- metadata +50 -18
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,19 @@
|
|
1
|
-
# 0.
|
1
|
+
# 0.2.0 / 2012-02-17
|
2
|
+
|
3
|
+
## Enhancements
|
4
|
+
|
5
|
+
* A --graph option has been added to the main `gisele` shell command. It outputs a graph in
|
6
|
+
the graphviz/dot format representing a process as a box-and-arrow workflow.
|
7
|
+
|
8
|
+
## Breaking changes
|
9
|
+
|
10
|
+
* Boolean literals (true, false) are now explicitly represented in boolean expressions,
|
11
|
+
under a :bool_lit AST node.
|
12
|
+
* All statements and clauses relying on boolean conditions (if_st, while_st, elsif_clause,
|
13
|
+
when_clause) have now an explicit :bool_expr node as first child. Previously, a subnode
|
14
|
+
of the boolean grammar was used.
|
15
|
+
|
16
|
+
# 0.1.0 / 2012-02-17
|
2
17
|
|
3
18
|
## Enhancements
|
4
19
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/gisele.gemspec
CHANGED
@@ -131,6 +131,7 @@ Gem::Specification.new do |s|
|
|
131
131
|
s.add_dependency("epath", "~> 0.0.1")
|
132
132
|
s.add_dependency("quickl", "~> 0.4.3")
|
133
133
|
s.add_dependency("awesome_print", "~> 1.0")
|
134
|
+
s.add_dependency("yargi", "~> 0.2.0")
|
134
135
|
|
135
136
|
# The version of ruby required by this gem
|
136
137
|
#
|
data/gisele.noespec
CHANGED
@@ -12,7 +12,7 @@ variables:
|
|
12
12
|
upper:
|
13
13
|
Gisele
|
14
14
|
version:
|
15
|
-
0.
|
15
|
+
0.2.0
|
16
16
|
summary: |-
|
17
17
|
Gisele is a Process Analyzer Toolset
|
18
18
|
description: |-
|
@@ -29,6 +29,7 @@ variables:
|
|
29
29
|
- {name: epath, version: "~> 0.0.1", groups: [runtime]}
|
30
30
|
- {name: quickl, version: "~> 0.4.3", groups: [runtime]}
|
31
31
|
- {name: awesome_print, version: "~> 1.0", groups: [runtime]}
|
32
|
+
- {name: yargi, version: "~> 0.2.0", groups: [runtime]}
|
32
33
|
#
|
33
34
|
- {name: rake, version: "~> 0.9.2", groups: [development]}
|
34
35
|
- {name: bundler, version: "~> 1.0", groups: [development]}
|
data/lib/gisele/command.rb
CHANGED
@@ -5,7 +5,7 @@ module Gisele
|
|
5
5
|
#
|
6
6
|
# SYNOPSIS
|
7
7
|
# gisele [--version] [--help]
|
8
|
-
# gisele [--ast] PROCESS_FILE
|
8
|
+
# gisele [--ast | --graph] PROCESS_FILE
|
9
9
|
#
|
10
10
|
# OPTIONS
|
11
11
|
# #{summarized_options}
|
@@ -14,14 +14,18 @@ module Gisele
|
|
14
14
|
# The Gisele process analyzer toolset provides tools and technique to model and analyze
|
15
15
|
# complex process models such as care processes.
|
16
16
|
#
|
17
|
-
# When --
|
17
|
+
# When --no-sugar is specified, syntactic sugar is first removed before making any other
|
18
|
+
# transformation. For now, this rewrites all `if` statements as explicit `case` guarded
|
19
|
+
# commands.
|
20
|
+
#
|
21
|
+
# When --ast is used, the command parses the process file and prints its Abstract Syntax
|
18
22
|
# Tree (AST) on standard output. By default, this option prints the AST for manual
|
19
23
|
# debugging, that is with colors and extra information. Use --ast=ruby to get a ruby
|
20
24
|
# array for automatic processing.
|
21
25
|
#
|
22
|
-
# When --
|
23
|
-
#
|
24
|
-
#
|
26
|
+
# When --graph is used, the command parses the process file. It then converts the AST into
|
27
|
+
# a directed graph representing the process as a box-and-arrow workflow and outputs it on
|
28
|
+
# standard output. For now, the only output format available is dot (from graphviz).
|
25
29
|
#
|
26
30
|
class Gisele::Command < Quickl::Command(__FILE__, __LINE__)
|
27
31
|
|
@@ -35,6 +39,10 @@ module Gisele
|
|
35
39
|
opt.on('--no-sugar', 'Apply syntactic sugar removal') do
|
36
40
|
@sugar = false
|
37
41
|
end
|
42
|
+
@print_graph = nil
|
43
|
+
opt.on('--graph=[MODE]', 'Converts and print a graph (dot)') do |value|
|
44
|
+
@print_graph = (value || "dot").to_sym
|
45
|
+
end
|
38
46
|
opt.on_tail('--help', "Show this help message") do
|
39
47
|
raise Quickl::Help
|
40
48
|
end
|
@@ -52,7 +60,9 @@ module Gisele
|
|
52
60
|
|
53
61
|
ast = Gisele.ast(file)
|
54
62
|
ast = Gisele::Language::SugarRemoval.new.call(ast) unless @sugar
|
63
|
+
|
55
64
|
print_ast(ast, @print_ast) if @print_ast
|
65
|
+
print_graph(ast, @print_graph) if @print_graph
|
56
66
|
end
|
57
67
|
|
58
68
|
private
|
@@ -68,5 +78,10 @@ module Gisele
|
|
68
78
|
ap ast, options
|
69
79
|
end
|
70
80
|
|
71
|
-
|
81
|
+
def print_graph(ast, option)
|
82
|
+
graph = Gisele::Language::ToGraph.new.call(ast)
|
83
|
+
puts graph.to_dot
|
84
|
+
end
|
85
|
+
|
86
|
+
end # class Command
|
72
87
|
end # module Gisele
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Gisele
|
2
|
+
module Language
|
3
|
+
module AST
|
4
|
+
module BoolAnd
|
5
|
+
include Node
|
6
|
+
|
7
|
+
def label
|
8
|
+
markers[:match] ? markers[:match].to_s : "(#{self[1].label} and #{self[2].label})"
|
9
|
+
end
|
10
|
+
|
11
|
+
end # module BoolAnd
|
12
|
+
end # module AST
|
13
|
+
end # module Language
|
14
|
+
end # module Gisele
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Gisele
|
2
|
+
module Language
|
3
|
+
module AST
|
4
|
+
module BoolNot
|
5
|
+
include Node
|
6
|
+
|
7
|
+
def label
|
8
|
+
markers[:match] ? markers[:match].to_s : "not(#{last.label})"
|
9
|
+
end
|
10
|
+
|
11
|
+
end # module BoolNot
|
12
|
+
end # module AST
|
13
|
+
end # module Language
|
14
|
+
end # module Gisele
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Gisele
|
2
|
+
module Language
|
3
|
+
module AST
|
4
|
+
module BoolOr
|
5
|
+
include Node
|
6
|
+
|
7
|
+
def label
|
8
|
+
markers[:match] ? markers[:match].to_s : "(#{self[1].label} or #{self[2].label})"
|
9
|
+
end
|
10
|
+
|
11
|
+
end # module BoolOr
|
12
|
+
end # module AST
|
13
|
+
end # module Language
|
14
|
+
end # module Gisele
|
@@ -3,17 +3,17 @@ module Gisele
|
|
3
3
|
module AST
|
4
4
|
module Helpers
|
5
5
|
|
6
|
-
def ast(arg)
|
7
|
-
return node(arg) if looks_a_node?(arg)
|
8
|
-
Syntax.ast(arg)
|
6
|
+
def ast(arg, markers = {})
|
7
|
+
return node(arg, markers) if looks_a_node?(arg)
|
8
|
+
ast(Syntax.ast(arg), markers)
|
9
9
|
end
|
10
10
|
|
11
|
-
def node(arg)
|
11
|
+
def node(arg, markers = {})
|
12
12
|
return arg if arg.is_a?(Node)
|
13
13
|
unless looks_a_node?(arg)
|
14
14
|
raise ArgumentError, "Array expected, #{arg.inspect} found."
|
15
15
|
end
|
16
|
-
extend_node(arg).tap do |node|
|
16
|
+
extend_node(arg, markers).tap do |node|
|
17
17
|
node.children.each{|c| node(c) if looks_a_node?(c)}
|
18
18
|
end
|
19
19
|
end
|
@@ -24,10 +24,16 @@ module Gisele
|
|
24
24
|
arg.is_a?(Node) or (arg.is_a?(Array) and arg.first.is_a?(Symbol))
|
25
25
|
end
|
26
26
|
|
27
|
-
def extend_node(arg)
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def extend_node(arg, markers)
|
28
|
+
mod = ast_module(arg)
|
29
|
+
arg.extend(mod).tap do |node|
|
30
|
+
node.markers = markers
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def ast_module(node)
|
35
|
+
modname = Language.rule2mod(node.first)
|
36
|
+
AST.const_get(modname) rescue Node
|
31
37
|
end
|
32
38
|
|
33
39
|
extend(self)
|
@@ -3,6 +3,16 @@ module Gisele
|
|
3
3
|
module AST
|
4
4
|
module Node
|
5
5
|
|
6
|
+
# Returns the node markers
|
7
|
+
def markers
|
8
|
+
@markers ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Sets node markers
|
12
|
+
def markers=(markers)
|
13
|
+
@markers = markers
|
14
|
+
end
|
15
|
+
|
6
16
|
# Returns the rule name, that is, the first Symbol element
|
7
17
|
# of the node array.
|
8
18
|
#
|
@@ -24,6 +34,11 @@ module Gisele
|
|
24
34
|
self[1..-1]
|
25
35
|
end
|
26
36
|
|
37
|
+
# Returns the associated ast_module
|
38
|
+
def ast_module
|
39
|
+
AST::Helpers.send(:ast_module, self)
|
40
|
+
end
|
41
|
+
|
27
42
|
# Applies copy-and-transform to this node.
|
28
43
|
#
|
29
44
|
# Example:
|
@@ -34,7 +49,7 @@ module Gisele
|
|
34
49
|
# # => [:something, ...]
|
35
50
|
#
|
36
51
|
def copy(&block)
|
37
|
-
base = AST.node([rule_name])
|
52
|
+
base = AST.node([rule_name], markers.dup)
|
38
53
|
children.inject(base, &block)
|
39
54
|
end
|
40
55
|
|
@@ -44,11 +59,32 @@ module Gisele
|
|
44
59
|
# will correctly be applied to the duplicated array.
|
45
60
|
#
|
46
61
|
def dup
|
47
|
-
AST.node(super)
|
62
|
+
AST.node(super, markers.dup)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a label for this AST node
|
66
|
+
def label
|
67
|
+
""
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns attributes to use for dot printing
|
71
|
+
def dot_attributes
|
72
|
+
attrs = Language::DOT_ATTRIBUTES[rule_name.to_s] || {}
|
73
|
+
attrs.merge(:label => label)
|
48
74
|
end
|
49
75
|
|
50
76
|
end # module Node
|
51
77
|
end # module AST
|
52
78
|
end # module Language
|
53
79
|
end # module Gisele
|
54
|
-
require_relative 'unit'
|
80
|
+
require_relative 'unit'
|
81
|
+
require_relative 'task_call_st'
|
82
|
+
require_relative 'while_st'
|
83
|
+
require_relative 'if_st'
|
84
|
+
require_relative 'elsif_clause'
|
85
|
+
require_relative 'when_clause'
|
86
|
+
require_relative 'bool_expr'
|
87
|
+
require_relative 'bool_and'
|
88
|
+
require_relative 'bool_or'
|
89
|
+
require_relative 'bool_not'
|
90
|
+
require_relative 'var_ref'
|
data/lib/gisele/language/ast.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
task_refinement:
|
2
|
+
shape: circle
|
3
|
+
style: filled
|
4
|
+
fillcolor: black
|
5
|
+
fixed: true
|
6
|
+
width: 0.1
|
7
|
+
task_call_st:
|
8
|
+
shape: box
|
9
|
+
while_st:
|
10
|
+
shape: diamond
|
11
|
+
if_st:
|
12
|
+
shape: diamond
|
13
|
+
case_st:
|
14
|
+
shape: diamond
|
15
|
+
par_st:
|
16
|
+
shape: box
|
17
|
+
height: 0.1
|
18
|
+
style: filled
|
19
|
+
fillcolor: black
|
@@ -15,8 +15,17 @@ module Gisele
|
|
15
15
|
|
16
16
|
def on_if_st(node)
|
17
17
|
condition, dost, *clauses = node.children
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
# create case_st with same markers as the if_st
|
20
|
+
when_clause = [:when_clause, condition, @main.call(dost)]
|
21
|
+
when_clause = node(when_clause, node.markers.dup)
|
22
|
+
base = [:case_st, when_clause]
|
23
|
+
base = node(base, node.markers.dup)
|
24
|
+
|
25
|
+
# this is the condition for elsif clauses
|
26
|
+
@condition = negate(condition.last)
|
27
|
+
|
28
|
+
# make injection now
|
20
29
|
clauses.inject base do |memo,clause|
|
21
30
|
memo << call(clause)
|
22
31
|
end
|
@@ -24,17 +33,29 @@ module Gisele
|
|
24
33
|
|
25
34
|
def on_elsif_clause(node)
|
26
35
|
condition, dost, = node.children
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
36
|
+
|
37
|
+
# install new conditions for me and next elsif clauses
|
38
|
+
condition = condition.last
|
39
|
+
previous = @condition
|
40
|
+
@condition = [:bool_and, negate(condition), @condition]
|
41
|
+
|
42
|
+
# convert elsif to when and keep the markers
|
43
|
+
base = \
|
44
|
+
[:when_clause,
|
45
|
+
[:bool_expr, [:bool_and, condition, previous]],
|
46
|
+
@main.call(dost) ]
|
47
|
+
node(base, node.markers.dup)
|
31
48
|
end
|
32
49
|
|
33
50
|
def on_else_clause(node)
|
34
51
|
dost, = node.children
|
35
|
-
|
36
|
-
|
37
|
-
|
52
|
+
|
53
|
+
# convert else to when and keep the markers
|
54
|
+
base = \
|
55
|
+
[:when_clause,
|
56
|
+
[:bool_expr, @condition],
|
57
|
+
@main.call(dost)]
|
58
|
+
node(base, node.markers.dup)
|
38
59
|
end
|
39
60
|
|
40
61
|
private
|
@@ -90,7 +90,8 @@ grammar Gisele::Language::Syntax::Grammar
|
|
90
90
|
### Boolean expressions
|
91
91
|
|
92
92
|
rule bool_expr
|
93
|
-
bool_or
|
93
|
+
(spacing theexpr:bool_or)
|
94
|
+
<Gisele::Language::Syntax::BoolExpr>
|
94
95
|
end
|
95
96
|
|
96
97
|
rule bool_or
|
@@ -112,11 +113,11 @@ grammar Gisele::Language::Syntax::Grammar
|
|
112
113
|
end
|
113
114
|
|
114
115
|
rule bool_term
|
115
|
-
bool_paren |
|
116
|
+
bool_paren | bool_lit | bool_varref
|
116
117
|
end
|
117
118
|
|
118
119
|
rule bool_paren
|
119
|
-
('(' spacing expr:
|
120
|
+
('(' spacing expr:bool_or spacing ')')
|
120
121
|
<Gisele::Language::Syntax::BoolParen>
|
121
122
|
end
|
122
123
|
|
@@ -4,7 +4,7 @@ module Gisele
|
|
4
4
|
module Node
|
5
5
|
|
6
6
|
def to_ast
|
7
|
-
Language::AST.node(_to_ast)
|
7
|
+
Language::AST.node(_to_ast, {:match => self})
|
8
8
|
end
|
9
9
|
|
10
10
|
end # module Node
|
@@ -20,6 +20,7 @@ require_relative 'bool_paren'
|
|
20
20
|
require_relative 'bool_not'
|
21
21
|
require_relative 'bool_and'
|
22
22
|
require_relative 'bool_or'
|
23
|
+
require_relative 'bool_expr'
|
23
24
|
require_relative 'st_list'
|
24
25
|
require_relative 'implicit_seq_st'
|
25
26
|
require_relative 'task_call_st'
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Gisele
|
2
|
+
module Language
|
3
|
+
class ToGraph < Transformer
|
4
|
+
module Connector; end
|
5
|
+
|
6
|
+
def recurse_on_last(node)
|
7
|
+
call(node.last)
|
8
|
+
end
|
9
|
+
alias :on_unit :recurse_on_last
|
10
|
+
|
11
|
+
def on_task_def(node)
|
12
|
+
@graph = Yargi::Digraph.new
|
13
|
+
call(SugarRemoval.new.call(node.last))
|
14
|
+
@graph.vertices(Connector).each do |vertex|
|
15
|
+
next unless vertex.out_edges.size == 1
|
16
|
+
target = vertex.out_edges.first.target
|
17
|
+
@graph.reconnect(vertex.in_edges, nil, target)
|
18
|
+
@graph.remove_vertex(vertex)
|
19
|
+
end
|
20
|
+
@graph
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_task_refinement(node)
|
24
|
+
entry, exit = add_vertex(node), add_vertex(node)
|
25
|
+
c_entry, c_exit = call(node.last)
|
26
|
+
connect(entry, c_entry)
|
27
|
+
connect(c_exit, exit)
|
28
|
+
[entry, exit]
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_seq_st(node)
|
32
|
+
mine = entry_and_exit(node)
|
33
|
+
current = mine.first
|
34
|
+
node.children.each do |child|
|
35
|
+
c_entry, c_exit = call(child)
|
36
|
+
connect(current, c_entry)
|
37
|
+
current = c_exit
|
38
|
+
end
|
39
|
+
connect(current, mine.last)
|
40
|
+
mine
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_par_st(node)
|
44
|
+
entry, exit = add_vertex(node), add_vertex(node)
|
45
|
+
node.children.each do |child|
|
46
|
+
c_entry, c_exit = call(child)
|
47
|
+
connect(entry, c_entry)
|
48
|
+
connect(c_exit, exit)
|
49
|
+
end
|
50
|
+
[entry, exit]
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_case_st(node)
|
54
|
+
entry, exit = entry_and_exit(node)
|
55
|
+
|
56
|
+
diamond = add_vertex(node)
|
57
|
+
connect(entry, diamond)
|
58
|
+
|
59
|
+
node.children.each do |when_clause|
|
60
|
+
c_entry, c_exit = call(when_clause.last)
|
61
|
+
connect(diamond, c_entry, when_clause)
|
62
|
+
connect(c_exit, exit)
|
63
|
+
end
|
64
|
+
|
65
|
+
[entry, exit]
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def on_while_st(node)
|
70
|
+
cond, dost, = node.children
|
71
|
+
|
72
|
+
entry, exit = entry_and_exit(node)
|
73
|
+
|
74
|
+
diamond = add_vertex(node)
|
75
|
+
connect(entry, diamond)
|
76
|
+
|
77
|
+
c_entry, c_exit = call(node.last)
|
78
|
+
|
79
|
+
connect(diamond, exit, false_ast_node)
|
80
|
+
connect(diamond, c_entry, true_ast_node)
|
81
|
+
connect(c_exit, diamond)
|
82
|
+
|
83
|
+
[entry, exit]
|
84
|
+
end
|
85
|
+
|
86
|
+
def on_task_call_st(node)
|
87
|
+
entry, exit = entry_and_exit(node)
|
88
|
+
task = add_vertex(node)
|
89
|
+
connect(entry, task)
|
90
|
+
connect(task, exit)
|
91
|
+
[entry, exit]
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def add_vertex(node)
|
97
|
+
@graph.add_vertex(node.dot_attributes)
|
98
|
+
end
|
99
|
+
|
100
|
+
def entry_and_exit(node, tag = Connector)
|
101
|
+
@graph.add_n_vertices(2, tag)
|
102
|
+
end
|
103
|
+
|
104
|
+
def connect(source, target, node = nil)
|
105
|
+
marks = node.nil? ? {} : node.dot_attributes
|
106
|
+
@graph.connect(source, target, marks)
|
107
|
+
end
|
108
|
+
|
109
|
+
def false_ast_node
|
110
|
+
Syntax.ast("false", :root => :bool_expr)
|
111
|
+
end
|
112
|
+
|
113
|
+
def true_ast_node
|
114
|
+
Syntax.ast("true", :root => :bool_expr)
|
115
|
+
end
|
116
|
+
|
117
|
+
end # class SugarRemoval
|
118
|
+
end # module Language
|
119
|
+
end # module Gisele
|