Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Building DSLs with the Spoofax Language Workbench
1. Building DSLs with
The Spoofax Language Workbench
Eelco Visser
Delft University of Technology
http://eelcovisser.org
Joint work with Lennart Kats, Karl-Trygve Kalleberg, Maartje de Jonge, and many others
2. The Meta-Languages Wars
Internal DSLs vs External DSLs
Textual vs Graphical
Partial vs Complete Code Generation
Project-Specific vs Multi-Project DSLs
Code Generation vs Interpretation
Dynamically vs Statically Typed
3. The Meta-Languages Wars
Internal DSLs vs External DSLs
Textual vs Graphical
Partial vs Complete Code Generation
Project-Specific vs Multi-Project DSLs
Code Generation vs Interpretation
Dynamically vs Statically Typed
4. WebDSL
http://webdsl.org
separation of concerns & linguistic integration
Eelco Visser. WebDSL: A Case Study in Domain-Specific Language Engineering. GTTSE 2007: 291-373
10. Concerns in web applications
- Data model
- User interface templates
- Actions
- Access control rules
- Data validation
WebDSL
- Linguistic integration
- Cross concern, static consistency checking
17. Page Navigation
define tips() {
section{
header{"What Can You Do Here?"}
list{
listitem{ navigate(newauthor()){ "Add an author" } }
listitem{ navigate(newpublication()){ "Add a publication" } }
listitem{ navigate(search()){ "Search" } }
}
}
}
define page search() { ... }
18. HQL Embedding
extend entity Person {
publications -> List<Publication> :=
select p
from Publication as p, Author as a
where (a.person = ~this) and (p = a.publication)
}
20. Access Control
Danny M. Groenewegen, Eelco Visser. Declarative Access Control for WebDSL:
Combining Language Integration and Separation of Concerns. ICWE 2008: 175-188
23. Data Validation
Danny M. Groenewegen, Eelco Visser. Integration of Data Validation and
User Interface Concerns in a DSL for Web Applications. SLE 2009: 164-173
24. Data Validation: Form Input Validation
label("Acronym (without year)"){
input(acronym){
validate(!isEmptyString(acronym),
"Provide acronym")
validate(!(/[ ]/.find(acronym)),
"Acronym should not contain spaces")
validate(!(/[0-9][0-9][0-9][0-9]/.find(acronym)),
"Acronym should not contain year")
}
}
25. Data Validation: Data Invariants
entity Publication {
key :: String (id, index(25),
validate(isUniquePublication(this),
"that key is already in use"),
validate(isValidKeyForURL(key),
"Key should consist of letters, digits, : and -"))
...
}
26. Data Validation: Assertions
extend entity Person {
invitation -> Invitation (inverse=Invitation.invitee)
function invite(email : Email, invitation : WikiText) : Invitation {
validate(findUser(email) == null
&& findInvitationByEmail(email).length == 0,
"That email address is already in use.");
...
}
}
27. Concerns in web applications
- Data model
- User interface templates
- Actions
- Access control rules
- Data validation
WebDSL
- Linguistic integration
- Cross concern, static consistency checking
33. parse
Editor
AST
feedback
core
Integrated
Development
Environment
Code
34. Architectural Requirements for IDEs
Editor services are based on AST
★ services analyze structure, not text of source
Error recovery
★ continue services in presence of syntactic errors
Incremental processing
★ effort of applying analysis, transformation, generation should be
proportional to change in source
Separate compilation (analysis, transformation)
★ keep track of dependencies
35. Holy Grail of Software Language Definition
Automatically derive efficient,
scalable, incremental compiler +
usable IDE from high-level,
declarative language
definition
37. The Spoofax/IMP Language Workbench
Syntax definition: SDF
★ declarative, modular syntax definition
Transformation, analysis, generation: Stratego
★ rewrite rules, strategies, dynamic rules
Editor services
★ domain-specific languages for configuration
Based on IMP framework by IBM Research
46. Lexical Syntax: Identifiers & Literals
follow restriction: symbol may not reserved words
be followed by character from this class:
BlogEntry is one ID
80. Term Rewriting
Term rewrite rules
★ transform term to term
★ pattern matching
★ variable binding
★ substitution
Rewriting strategy
★ algorithm for applying rewrite rules
81. Term Rewrite Rule
left-hand side pattern
label/name
desugar :
Property(x, t) -> Property(x, t, [])
variable
right-hand side pattern
82. Rewrite Strategy
generic strategy
strategy definition
desugar-all = innermost(desugar)
strategy instantiation
apply transformation s exhaustively to all sub-
innermost(s)
terms of subject term in bottom-up order
83. Constant Folding
y := a + (3 * 5 + 2);
Assign(
Var("y")
, Plus(
Var("a")
, Plus(Times(IntLit("3"), IntLit("5")), IntLit("2"))
parse )
)
Assign(
Var("y")
, BinOp(Var("a"), "+", IntLit("17"))
desugar + eval )
y := (a + 17);
pretty-print
89. Consistency Checking
Syntax definition
★ what are well-formed sentences?
Static analysis
★ not all ‘well-formedness’ properties are context-free
★ consistency of compositions
★ consistency of expressions wrt declarations
Error reporting
★ indicate errors in editor
★ use sensible error message
90.
91.
92. Consistency Checking: Ingredients
Editor Interface
★ collecting and displaying errors, warnings
Error checking
★ checking static constraints and reporting errors
Type analysis
★ computing types of expressions
Name resolution
★ disambiguation of names
Reference resolving
★ linking identifiers to declarations
93. Consistency Checking: Generic Approach
Rename
★ make identifiers unique
Map
★ map identifiers to declarations
Project
★ compute properties of declarations, expressions
Check
★ check constraints
96. Error Checking Rules
check :
context -> (target, error)
where assumption
where require(constraint)
require(s) = not(s)
– Context: identifying points in the code to check
– Assumptions: only report an error if certain assumptions hold (validating the context and avoiding spurious errors)
– Constraints: checking for constraints at the context
– Formulating an error message
– Attribution of the error to a particular character range in the source text (usually, only part of the context
97. Error Checking: Binary Operators
check :
e@BinOp(e1, op, e2) ->
(e, $[operator [op] not defined for [<pp>t1] and [<pp>t2]])
where t1 := <type-of> e1
where t2 := <type-of> e2
where require(<type-of> e)
101. Type Analysis: Binary Operators
type-of :
BinOp(e1, op, e2) -> t
where t := <function-type>(op, [<type-of>e1, <type-of>e2])
function-type :
("+", [SimpleType("String"), SimpleType("String")]) -> SimpleType("String")
function-type :
("+", [SimpleType("Int"), SimpleType("Int")]) -> SimpleType("Int")
function-type :
("-", [SimpleType("Int"), SimpleType("Int")]) -> SimpleType("Int")
102. Type Analysis: What is Type of Variable?
define page root(x : Int) {
action exptest() {
for(y : Int in {1,2,x}) {
x := x + y;
}
}
type-of :
}
Var(x) -> t
where t := ???
Assign(
Var("x")
, BinOp(Var("x"), "+", Var("y"))
)
type of variable not part of variable use
103. Variables: Map
declare-all =
alltd(declare)
declare :
Param(x, t) -> Param(x, t)
with rules(
TypeOf : x -> t
)
type-of :
Var(x) -> t
where t := <TypeOf> x
104. Scope
define page root(x : Int) {
action exptest() {
for(x : Int in {1,2,x}) {
print(x);
}
}
}
multiple occurrences of same identifier corresponding to different declarations
105. Variables: Map + Rename
rename-all = alltd(rename)
rename :
Param(x, t) -> Param(y, t)
with y := <rename-var>(x, t)
unique annotation
rename-var :
(x, t) -> y
with y := x{<new>};
map variable to type
rules(
TypeOf : y -> t
RenameId : x -> y
)
rename occurrences
rename :
Var(x) -> Var(y) where y := <RenameId> x
type-of :
Var(x) -> t where t := <TypeOf> x
106. Term Annotations
t{t1,...,tn}
add additional information to term without affecting signature
107. Variables: Check
check :
e@Var(x) -> (e, $[Variable '[x]' not declared])
where require(<type-of>e)
108. Variable Binding Constructs
rename :
For(x, t, e1, stat1*) -> For(y, t, e2, stat2*)
with e2 := <rename-all> e1
with {| RenameId
: y := <rename-var>(x, t)
; stat2* := <rename-all> stat1*
|}
For defines local variable x in body stat1*’
110. Rename, Map, Project, Check
Rename
★ make local variables unique
Map
★ variables to their type
Project
★ compute type of expressions
Check
★ check constraints using types
116. Experience
Bootstrapped
★ SDF, Stratego, ATerm, PP
★ Editor service language
Spoofax
★ WebDSL
★ Mobl: mobile web applications
★ Acoda: migration of data models
★ Aster: attribute grammars
★ PIL: platform independent language
★ Student languages
SDF+Stratego
★ Java, AspectJ, XML, PHP, SQL, Jimple, Octave, Dot, ...
117. Future Work (in progress)
Higher-level definition of scope & type system
★ cover scope systems of real languages
Refactoring
★ layout preservation, generic refactoring strategies
Systematic language design
★ how do we decide that a language design is good?
Interaction design
★ higher-level specifications of interaction
Combining textual and visual
Textual IDE in the browser
118. - the end -
Lennart C. L. Kats, Eelco Visser. The Spoofax Language Workbench. Rules
for Declarative Specification of Languages and IDEs. OOPSLA 2010
http://spoofax.org
http://webdsl.org http://mobl.org
120. define page blog(b : Blog) {
Template Inlining header{output(b.name)}
list{
for(p : Post in b.posts) {
listitem{ outputPost(p) }
}
}
}
define outputPost(pst : Post) {
navigate post(pst) { output(pst.name) }
}
define list() { <ul> elements </ul> }
define listitem() { <li> elements </li> }
define header() { <h1> elements </h1> }
define page blog ( b : Blog ) {
<h1>
output(b.name)
</h1>
<ul>
for ( p : Post in b.posts ) {
<li>
navigate post(p) { output(p.name) }
</li>
}
</ul>
}
121. Context-sensitive Transformation
Local-to-local
★ replace term by another term
Local-to-global
★ local term influence terms in other parts of model
Global-to-local
★ global information influences transformation of local terms
122. define page blog(b : Blog) {
header{output(b.name)}
list{
for(p : Post in b.posts) {
listitem{ outputPost(p) }
}
}
Inlining is Global-to-Local }
define outputPost(pst : Post) {
navigate post(pst) { output(pst.name) }
}
define list() { <ul> elements </ul> }
define listitem() { <li> elements </li> }
define header() { <h1> elements </h1> }
define page blog ( b : Blog ) {
<h1>
output(b.name)
</h1>
<ul>
for ( p : Post in b.posts ) {
<li>
navigate post(p) { output(p.name) }
</li>
}
</ul>
}
123. Inlining as Rewrite Problem
outputPost(p) -> navigate post(p) { output(p.name) }
where define outputPost(pst : Post) {
navigate post(pst) { output(pst.name) }
}
(this is not a valid Stratego rewrite rule)
126. Rewrite in Context
Inline :
[def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*]
where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*
local traversal bound in context
container needed to replace single call with multiple elements
131. Variables
{x, y : s}
★ term variable scope
?t
★ match term pattern t
!t
★ build term pattern t
t1 := t2
★ match term t2 to term (pattern) t1
<s> t
★ apply strategy s to term t
132. Rewrite Rules
l : t1 -> t2 where s
★ named, scoped rewrite rule
★ all variables in t1, t2, s are in scope of the rule
(t1 -> t2 where s)
★ unscoped rewrite rule
★ variables are in scope of enclosing scope
133. Rewrite in Context
Inline :
[def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*]
where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*
bound in context
135. Strategy Definitions
f(x,y|a,b) = s
★ x, y are strategy parameters
★ a, b are term parameters
★ s uses all parameters
f(x) = s f = s
★ term parameters are optional
★ all parameters are optional
Examples
★ try(s) = s <+ id
★ repeat(s) = try(s; repeat(s))
136. Rules with Parameters
Transform all elements of a list
map(s) : [] -> []
map(s) : [x|xs] -> [<s>x | <map(s)> xs]
Invert order of elements of a list
inverse(|ys) : [] -> ys
inverse(|ys) : [x|xs] -> <inverse(|[x|ys])> xs
Pair elements of two lists
zip(s) : ([],[]) -> []
zip(s) : ([x|xs],[y|ys]) -> [<s>(x,y) | <zip(s)>(xs,ys)]
137. Traversal Combinators
all(s)
★ apply s to all direct sub-terms (children)
one(s)
★ apply s to exactly one sub-term
some(s)
★ apply s to at least one sub-term
138. Traversal Strategies
topdown(s) = s; all(topdown(s))
★ apply s to all sub-terms in top-down order
bottomup(s) = all(bottomup(s)); s
★ apply s to all sub-terms in bottom-up order
oncetd(s) = s <+ one(oncetd(s))
★ apply s to one sub-term
alltd(s) = s <+ all(alltd(s))
★ apply s to frontier
139. Rewrite in Context: Local Traversal
Inline :
[def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*]
where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*
local traversal
141. Rewrite in Context: Not Optimal
Inline :
[def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*]
where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*
requires def before use
local traversal for each declaration