Top Banner
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
149

Building DSLs with the Spoofax Language Workbench

Dec 05, 2014

Download

Technology

Eelco Visser

Slides for my keynote talk at Code Generation 2010
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Building DSLs with the Spoofax Language Workbench

Building DSLs with The Spoofax Language Workbench

Eelco VisserDelft University of Technology

http://eelcovisser.org

Joint work with Lennart Kats, Karl-Trygve Kalleberg, Maartje de Jonge, and many others

Page 2: Building DSLs with the Spoofax Language Workbench

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

Page 3: Building DSLs with the Spoofax Language Workbench

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

Page 5: Building DSLs with the Spoofax Language Workbench

1,190,303 publications

http://researchr.org

Page 6: Building DSLs with the Spoofax Language Workbench

correct & extend

publication records

Page 7: Building DSLs with the Spoofax Language Workbench

authorprofiles

Page 8: Building DSLs with the Spoofax Language Workbench

bibliographies

tagging

reputation system

access control rules

user groups

conference calendar

community engineering

etc.

Page 9: Building DSLs with the Spoofax Language Workbench

18,000 lines of WebDSL code

138 (generated) tables in mysql

Page 10: Building DSLs with the Spoofax Language Workbench

Concerns in web applications

- Data model

- User interface templates

- Actions

- Access control rules

- Data validation

WebDSL

- Linguistic integration

- Cross concern, static consistency checking

Page 11: Building DSLs with the Spoofax Language Workbench

WebLib: a model of researchr

Page 12: Building DSLs with the Spoofax Language Workbench

Data Model

entity Publication { key :: String (id) title :: String (name,searchable) authors -> List<Author> journal :: String (searchable) volume :: Int issue :: Int year :: Int (searchable) month :: String (searchable) firstpage :: Int lastpage :: Int cites -> Set<Publication> citedBy -> Set<Publication> (inverse=Publication.cites) }

entity Author { key :: String (id) name :: String publications -> Set<Publication> (inverse=Publication.authors) created :: DateTime modified :: DateTime}

Page 13: Building DSLs with the Spoofax Language Workbench

User Interface

Page 14: Building DSLs with the Spoofax Language Workbench

define page search() { var query : String action show() { var pubs : List<Publication> := searchPublication(query); if(pubs.length > 0) { replace(results, results(query,pubs)); } else { replace(results, noresults()); } } main{ section{ header{"Search"} form{ input(query)[onkeyup:=show()] } placeholder results{} } }}

Full Text Search

entity Publication { title :: String (name,searchable) journal :: String (searchable) year :: Int (searchable) month :: String (searchable) ... }

Page 15: Building DSLs with the Spoofax Language Workbench

define page root() { main{ section{ header{"Welcome to WebLib: Your Digital Library"} aboutWeblib() recent() tips() } }}

define aboutWeblib() { par{ "WebLib is a digital library ..." }}

Page & Template Definitions

Page 16: Building DSLs with the Spoofax Language Workbench

XML Embeddingdefine no-span main() { <div class="page-wrap"> top() <div class="body"> messages() elements() </div> <div class="clear"></div> </div> footer()}

Page 17: Building DSLs with the Spoofax Language Workbench

Page Navigationdefine 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() { ... }

Page 18: Building DSLs with the Spoofax Language Workbench

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)}

Page 19: Building DSLs with the Spoofax Language Workbench

Forms & Data Bindingdefine page editpublication(pub : Publication) { main{ section{ header{"Edit Publication '" output(pub.title) "'"} form{ group{ label("Key:" ){ input(pub.key) } label("Title:" ){ input(pub.title) } label("Authors:"){ input(pub.authors) } ... } submit action{ pub.modified := now(); return publication(pub); } { "Save" } } } }}

Page 21: Building DSLs with the Spoofax Language Workbench

Access Control Rules

define showReview(review : Review) { action delete() { ... } section{ ... navigate(editReview(review)){"[Edit]"} submitlink delete() {"[X]"} }}

Page 22: Building DSLs with the Spoofax Language Workbench

Access Control Rules

access control rules

principal is User with credentials username, password

predicate isReviewer(review : Review) { review.reviewer == securityContext.principal } rule template showReview(review : Review, pubLink : Bool) { review.public || isReviewer(review) rule action delete() { loggedIn() && isReviewer(review) } } rule page editReview(review : Review) { loggedIn() && isReviewer(review) }

define showReview(review : Review) { action delete() { ... } section{ ... navigate(editReview(review)){"[Edit]"} submitlink delete() {"[X]"} }}

Page 24: Building DSLs with the Spoofax Language Workbench

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")

}}

Page 25: Building DSLs with the Spoofax Language Workbench

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 -")) ... }

Page 26: Building DSLs with the Spoofax Language Workbench

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.");

... }}

Page 27: Building DSLs with the Spoofax Language Workbench

Concerns in web applications

- Data model

- User interface templates

- Actions

- Access control rules

- Data validation

WebDSL

- Linguistic integration

- Cross concern, static consistency checking

Page 28: Building DSLs with the Spoofax Language Workbench

IDEs for DSLs

Page 29: Building DSLs with the Spoofax Language Workbench

Model

Code

AST

core

Errorsparse check

desugar

generate

Traditional Compiler Architecture

Page 30: Building DSLs with the Spoofax Language Workbench

Compiler Ingredients

Syntax definition

★ concrete syntax

★ abstract syntax

Static semantics

★ error checking

★ name resolution

★ type analysis

Model-to-model transformation

★ express constructs in core language

Code generation

★ translate core language models to implementation

Page 31: Building DSLs with the Spoofax Language Workbench

IDEs Required

Page 32: Building DSLs with the Spoofax Language Workbench

Editor Services for Modern IDEs

Syntactic Editor Services

★ syntax checking

★ bracket matching

★ syntax highlighting

★ code folding

★ outline view

Semantic Editor Services

★ error checking

★ reference resolving

★ hover help

★ code completion

★ refactoring

Page 33: Building DSLs with the Spoofax Language Workbench

Editor

Code

AST

core

IntegratedDevelopmentEnvironment

feedback

parse

Page 34: Building DSLs with the Spoofax Language Workbench

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

Page 35: Building DSLs with the Spoofax Language Workbench

Holy Grail of Software Language Definition

Automatically derive efficient, scalable, incremental compiler + usable IDE from high-level, declarative language

definition

Page 36: Building DSLs with the Spoofax Language Workbench

The Spoofax Language Workbench

Page 37: Building DSLs with the Spoofax Language Workbench

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

Page 38: Building DSLs with the Spoofax Language Workbench

Syntax Definition

Page 39: Building DSLs with the Spoofax Language Workbench

Syntax Definition

Parse Table Signature Pretty-Print Table

TransformParse Pretty-Print

entity User { name :: String pw :: Secret}def output(u : User) {

@Entityclass User { String _user; public User getUser() { return _user; }syntax definition is basis of language definition

Page 40: Building DSLs with the Spoofax Language Workbench

Lexical & Context-Free Syntax

Page 41: Building DSLs with the Spoofax Language Workbench

composed from

Structure of Sentences

Page 42: Building DSLs with the Spoofax Language Workbench

Context-Free Grammar

grammar production

symbol

DefinitionProperty*

{Stat “;”}*“entity”

regular expressionsort

Page 43: Building DSLs with the Spoofax Language Workbench

Lexical Syntax

character class

characters in lexical syntax not separated by layout

regular expression

Page 44: Building DSLs with the Spoofax Language Workbench

Syntax of Data Models

Page 45: Building DSLs with the Spoofax Language Workbench

RecognizingWell-Formed

Sentences

Page 46: Building DSLs with the Spoofax Language Workbench

Lexical Syntax: Identifiers & Literals

follow restriction: symbol may not be followed by character from this class: BlogEntry is one ID

reserved words

Page 47: Building DSLs with the Spoofax Language Workbench

Lexical Syntax: Strings

complement

escape

escape escape

Page 48: Building DSLs with the Spoofax Language Workbench

Lexical Syntax: Whitespace

LAYOUT? is inserted between symbols in CF productions

normalize

Page 49: Building DSLs with the Spoofax Language Workbench

Modular Syntax Definition

Page 50: Building DSLs with the Spoofax Language Workbench

Parsing & Abstract Syntax

building structured representation of sentence

Page 51: Building DSLs with the Spoofax Language Workbench

Linking Concrete & Abstract Syntax

Page 52: Building DSLs with the Spoofax Language Workbench

Terms to Represent Abstract Syntax

Entity(“Blog”, [Property(“name”,SimpleType(“String”)), Property(“author”, SimpleType(“User”))])

Page 53: Building DSLs with the Spoofax Language Workbench

Parsing in Spoofax

Page 54: Building DSLs with the Spoofax Language Workbench

Signatures

defining the structure of abstract syntax terms

Page 55: Building DSLs with the Spoofax Language Workbench

Syntax Definition

Parse Table Signature Pretty-Print Table

TransformParse Pretty-Print

entity User { name :: String pw :: Secret}def output(u : User) {

@Entityclass User { String _user; public User getUser() { return _user; }syntax definition is basis of language definition

Page 56: Building DSLs with the Spoofax Language Workbench

Signatures

declare arguments and types of constructorssignature is generated from grammar

Page 57: Building DSLs with the Spoofax Language Workbench

Disambiguation

Page 58: Building DSLs with the Spoofax Language Workbench

Arithmetic Expressions

Plus(Times(Var(“x”),Int(2)),Var(“z”))note: this style produces good abstract syntax

Page 59: Building DSLs with the Spoofax Language Workbench
Page 60: Building DSLs with the Spoofax Language Workbench

Associativity

Page 61: Building DSLs with the Spoofax Language Workbench

Priority

Page 62: Building DSLs with the Spoofax Language Workbench

Parsing after Disambiguation

Page 63: Building DSLs with the Spoofax Language Workbench

Brackets

Page 64: Building DSLs with the Spoofax Language Workbench

Language Composition

Page 65: Building DSLs with the Spoofax Language Workbench

define page index() { list{ for(p : Post in select p from Post as p order by (p.title)) { listitem{ output(p) } } }}

Embedding HQL

ForElem("p", SimpleType("Post"), QueryRule( SelectFrom( Some(Select(None(), [AliasedExpression(Path(["p"]), None())])) , FromClause([FromRangeJoin(FromClass(Path(["Post"]), Some(AsAlias(Alias("p"))), ) , Some(OrderByClause([OrderElement(Paren([Path(["p", "title"])]), None())])) ), [CallElems("listitem", [CallArgs("output", [Var("p")])])])

module expressionsimports Commonimports HQLexports context-free syntax SelectStatement -> Exp

Page 66: Building DSLs with the Spoofax Language Workbench

Presentational

Editor Services

Page 67: Building DSLs with the Spoofax Language Workbench

Editor Services

Syntax highlighting

★ token coloring

Code folding

★ folding & unfolding code fragments

Outline view

★ table of contents

Syntax properties

★ bracket matching, indentation

Syntax completion

★ match on syntactic triggers & replace with template

Page 68: Building DSLs with the Spoofax Language Workbench

module nwl.main

imports nwl-Builders nwl-Colorer nwl-Folding nwl-Outliner nwl-References nwl-Syntax nwl-Completions

language General properties name : nwl id : nwl extends : Root description : "Spoofax/IMP-generated editor for the nwl language" url : http://strategoxt.org extensions : nwl table : include/nwl.tbl start symbols : Start

Editor Services Composition

Page 69: Building DSLs with the Spoofax Language Workbench

Syntax Definition

Default Syntax Highlighting

Default Code Folding

DefaultOutline View

Editor

Custom Syntax Highlighting

CustomCode Folding

CustomOutline View+ + +

Page 70: Building DSLs with the Spoofax Language Workbench

module nwl-Foldingimports nwl-Folding.generatedfolding Element.CallElems Element.Call Element.ForElem Element.ForAllElem

module nwl-Folding.generatedfolding Default folding Start.Module Definition.Entity Property.Property Definition.TemplateDef Element.CallArgs PageRef.PageRef Element.Action

Code Folding

Page 71: Building DSLs with the Spoofax Language Workbench

Outline View

module nwl-Outliner.generatedoutliner Default outliner Start.Module Definition.Entity Property.Property Definition.TemplateDef Element.CallArgs Exp.MethodCall Element.CallElems Element.Call Element.ForElem Element.ForAllElem PageRef.PageRef Element.Action Statement.For Statement.While Statement.If Element.Submit Element.XmlElem

Page 72: Building DSLs with the Spoofax Language Workbench

module nwl-Colorer.generated colorer Default, token-based highlighting keyword : 127 0 85 bold identifier : default string : blue number : darkgreen var : 255 0 100 italic operator : 0 0 128 layout : 100 100 0 italic colorer System colors darkred = 128 0 0 red = 255 0 0 darkgreen = 0 128 0 green = 0 255 0 darkblue = 0 0 128 blue = 0 0 255 cyan = 0 255 255 magenta = 255 0 255 yellow = 255 255 0 white = 255 255 255 black = 0 0 0 gray = 128 128 128 grey = gray orange = 255 165 0 pink = 255 105 180 brown = 139 69 19 default = _

Default Syntax Highlighting

Page 73: Building DSLs with the Spoofax Language Workbench

Custom Syntax Highlighting

module nwl-Colorerimports nwl-Colorer.generatedcolorer Element : darkgreen

Page 74: Building DSLs with the Spoofax Language Workbench

Syntax Properties

module nwl-Syntax.generatedlanguage Syntax properties (static defaults) // Comment constructs: line comment : "//" block comment : "/*" * "*/" // Fences (used for matching, // inserting, indenting brackets): fences : [ ] ( ) { } // Automatic indent hints // (indent after these tokens): indent after : "=" ":" // Regular expression for identifiers: identifier lexical : "[A-Za-z0-9_]+"

Page 75: Building DSLs with the Spoofax Language Workbench

Parse Error Recovery

Page 76: Building DSLs with the Spoofax Language Workbench

Parse Error Recovery

also for composite languages

Page 77: Building DSLs with the Spoofax Language Workbench

BuildersSemantic Editor Services

Page 78: Building DSLs with the Spoofax Language Workbench

Builders: Analysis & Transformation Services

module nwl-Buildersbuilders provider: include/nwl.ctree

observer: editor-analyze

builder : "Generate Java code" = generate-java (openeditor) (realtime)

builder : "Show ATerm (selection)" = generate-aterm (openeditor) (realtime) (meta) builder : "Normalize (selection)" = show-normalized (openeditor) (realtime) (meta)

builder : "Normalize Pretty (selection)" = show-normalized-pp (openeditor) (realtime) (meta)

Page 79: Building DSLs with the Spoofax Language Workbench

Term Rewriting

Page 80: Building DSLs with the Spoofax Language Workbench

Term Rewriting

Term rewrite rules

★ transform term to term

★ pattern matching

★ variable binding

★ substitution

Rewriting strategy

★ algorithm for applying rewrite rules

Page 81: Building DSLs with the Spoofax Language Workbench

Term Rewrite Rule

desugar : Property(x, t) -> Property(x, t, [])

label/name

left-hand side pattern

right-hand side patternvariable

Page 82: Building DSLs with the Spoofax Language Workbench

Rewrite Strategy

desugar-all = innermost(desugar)

strategy definitiongeneric strategy

strategy instantiation

innermost(s) apply transformation s exhaustively to all sub-terms of subject term in bottom-up order

Page 83: Building DSLs with the Spoofax Language Workbench

Constant Folding

y := a + (3 * 5 + 2);

Assign( Var("y"), BinOp(Var("a"), "+", IntLit("17")))

Assign( Var("y"), Plus( Var("a") , Plus(Times(IntLit("3"), IntLit("5")), IntLit("2")) ))

y := (a + 17);

desugar + eval

parse

pretty-print

Page 84: Building DSLs with the Spoofax Language Workbench

Constant Folding Rules

strategies

eval-all = innermost(desugar + eval)

rules eval : BinOp(IntLit(x), "+", IntLit(y)) -> IntLit(<addS>(x, y)) eval : BinOp(IntLit(x), "-", IntLit(y)) -> IntLit(<subtS>(x, y)) eval : BinOp(IntLit(x), "*", IntLit(y)) -> IntLit(<mulS>(x, y)) eval : BinOp(IntLit(x), "/", IntLit(y)) -> IntLit(<divS>(x, y))

Page 85: Building DSLs with the Spoofax Language Workbench

Conditional Rewrite Rules

eval : BinOp(IntLit(x), "+", IntLit(y)) -> IntLit(<addS>(x, y))

eval : BinOp(IntLit(x), "+", IntLit(y)) -> IntLit(z) where z := <addS>(x, y)condition

match apply transformation

bound in condition

Page 86: Building DSLs with the Spoofax Language Workbench

Code Generation by Model Transformation

Page 87: Building DSLs with the Spoofax Language Workbench

elem-to-java-servlet : tc@elem|[ x(e)[passign*]{elem*} ]| -> <wrap-input-render-java-code> bstm* |[ String x_temp = ident+"~inputident"+uniqueid; ~*<render-error-messages-with-error-template(|java:expr|[ x_temp ]|, <ErrorTemplateInput>)> bstm*|[ bstm_call* ]| ]| where <is-input-template> tc with inputident := <get-inputnumber> tc ; x_temp := <newname> "temp" ; bstm_call* := <control-flow-tcall-helper(|"render",expr|[ x_temp ]|)> tc

Rewriting with Concrete Object Syntax

Stratego + embedded Java

Page 88: Building DSLs with the Spoofax Language Workbench

Consistency Checking

Page 89: Building DSLs with the Spoofax Language Workbench

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

Page 90: Building DSLs with the Spoofax Language Workbench
Page 91: Building DSLs with the Spoofax Language Workbench
Page 92: Building DSLs with the Spoofax Language Workbench

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

Page 93: Building DSLs with the Spoofax Language Workbench

Consistency Checking: Generic Approach

Rename

★ make identifiers unique

Map

★ map identifiers to declarations

Project

★ compute properties of declarations, expressions

Check

★ check constraints

Page 94: Building DSLs with the Spoofax Language Workbench

Editor Interface

module nwl-Builders

imports nwl-Builders.generated

builders

provider: include/nwl.ctree

observer: editor-analyze

module static-analysis

imports include/nwlimports entitiesimports utils

rules // static analysis

editor-analyze: (ast, path, fullpath) -> (errors, warnings, notes) with ...

editor/nwl-Builders.esvtrans/static-analysis.str

Page 95: Building DSLs with the Spoofax Language Workbench

Editor Interface

editor-analyze: (ast, path, fullpath) -> (errors, warnings, notes) with errors := <collect-all(check, conc)> ast; warnings := <collect-all(constraint-warning, conc)> ast; notes := <collect-all(constraint-note, conc)> ast

Page 96: Building DSLs with the Spoofax Language Workbench

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

Page 97: Building DSLs with the Spoofax Language Workbench

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)

Page 98: Building DSLs with the Spoofax Language Workbench

Pretty-Printing with String Interpolation

pp : Entity(x, prop*) -> $[entity [x] { [<map(pp)>prop*] }] pp : Property(x,t) -> $[[x] : [<pp>t] ] pp : SimpleType(x) -> x pp : SetType(t) -> $[Set<[<pp> t]>] pp : [] -> $[] pp : [t] -> <pp>t pp : [t1,t2|ts] -> $[[<pp>t1],[<pp>[t2|ts]]]

Page 99: Building DSLs with the Spoofax Language Workbench

Type Analysis

type-of : e -> t

compute type of expression

Page 100: Building DSLs with the Spoofax Language Workbench

Type Analysis: Literals

type-of : StringLit(x) -> SimpleType("String") type-of : IntLit(x) -> SimpleType("Int")

Page 101: Building DSLs with the Spoofax Language Workbench

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")

Page 102: Building DSLs with the Spoofax Language Workbench

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

Page 103: Building DSLs with the Spoofax Language Workbench

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

Page 104: Building DSLs with the Spoofax Language Workbench

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

Page 105: Building DSLs with the Spoofax Language Workbench

Variables: Map + Renamerename-all = alltd(rename)

rename : Param(x, t) -> Param(y, t) with y := <rename-var>(x, t)

rename-var : (x, t) -> y with y := x{<new>}; rules( TypeOf : y -> t RenameId : x -> y )

rename : Var(x) -> Var(y) where y := <RenameId> x type-of : Var(x) -> t where t := <TypeOf> x

unique annotation

rename occurrences

map variable to type

Page 106: Building DSLs with the Spoofax Language Workbench

Term Annotations

t{t1,...,tn}

add additional information to term without affecting signature

Page 107: Building DSLs with the Spoofax Language Workbench

Variables: Check

check : e@Var(x) -> (e, $[Variable '[x]' not declared]) where require(<type-of>e)

Page 108: Building DSLs with the Spoofax Language Workbench

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*’

Page 109: Building DSLs with the Spoofax Language Workbench

Editor Interface with Analysis

editor-analyze: (ast, path, fullpath) -> (errors, warnings, notes) with ast2 := <analyze> ast; errors := <collect-all(check, conc)> ast2; warnings := <collect-all(constraint-warning, conc)> ast2; notes := <collect-all(constraint-note, conc)> ast2

analyze = rename-all

Page 110: Building DSLs with the Spoofax Language Workbench

Rename, Map, Project, Check

Rename

★ make local variables unique

Map

★ variables to their type

Project

★ compute type of expressions

Check

★ check constraints using types

Page 111: Building DSLs with the Spoofax Language Workbench

Reference Resolution

Page 112: Building DSLs with the Spoofax Language Workbench

Reference Resolution

Aiding program navigation

★ Hover-click on identifier to jump to declaration

Reuse name resolution infrastructure

Page 113: Building DSLs with the Spoofax Language Workbench

editor-resolve: (source, position, ast, path, fullpath) -> target where target := <compute-target> source

Reference Resolution

module nwl-References

imports nwl-References.generated

references

reference _ : editor-resolve

Page 114: Building DSLs with the Spoofax Language Workbench

editor-resolve: (SimpleType(type), position, ast, path, fullpath) -> target where Entity(target,_) := <declaration-of> type editor-resolve: (ref@PageRef(x,e*), position, ast, path, fullpath) -> target where TemplateDef(_,target,_,_) := <declaration-of> ref

From Use to Declaration

Page 115: Building DSLs with the Spoofax Language Workbench

Experience

Page 116: Building DSLs with the Spoofax Language Workbench

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

Page 117: Building DSLs with the Spoofax Language Workbench

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

Page 119: Building DSLs with the Spoofax Language Workbench

Context-sensitiveTransformation

Page 120: Building DSLs with the Spoofax Language Workbench

Template Inliningdefine page blog(b : Blog) { 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>}

Page 121: Building DSLs with the Spoofax Language Workbench

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

Page 122: Building DSLs with the Spoofax Language Workbench

define page blog(b : Blog) { 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>}

Inlining is Global-to-Local

Page 123: Building DSLs with the Spoofax Language Workbench

outputPost(p) -> navigate post(p) { output(p.name) }where define outputPost(pst : Post) { navigate post(pst) { output(pst.name) } }

Inlining as Rewrite Problem

(this is not a valid Stratego rewrite rule)

Page 124: Building DSLs with the Spoofax Language Workbench

Rewrite Rules are Context-free

desugar : Property(x, t) -> Property(x, t, [])

Page 125: Building DSLs with the Spoofax Language Workbench

Simplification: Parameterless Templates

define page blog(b : Blog) { ... footer()}define footer() { navigate url(http://webdsl.org) { “WebDSL” }}

define page blog ( b : Blog ) { ... container() { navigate url(http://webdsl.org) { “WebDSL” } }}

Page 126: Building DSLs with the Spoofax Language Workbench

Rewrite in Context

Inline : [def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*] where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*

bound in contextlocal traversal

container needed to replace single call with multiple elements

Page 127: Building DSLs with the Spoofax Language Workbench

Intermezzo:Rewriting Strategies

Page 128: Building DSLs with the Spoofax Language Workbench

transformation

is

partial* function from terms to terms

* transformation may fail to apply to a term

Page 129: Building DSLs with the Spoofax Language Workbench

Defining Transformations: Rules & Strategies

Rewrite rules are basic transformations

★ transform term matching lhs to instantiation of rhs

★ evaluate condition (where clause)

Strategies combine rules into complex transformations

★ select rules to apply

★ select algorithm to apply rules

Strategy combinators

★ composition of custom transformations

Page 130: Building DSLs with the Spoofax Language Workbench

Strategy Combinators

id★ Identity

fail★ failure

s1 ; s2★ sequential composition

s1 <+ s2★ choice

Page 131: Building DSLs with the Spoofax Language Workbench

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

Page 132: Building DSLs with the Spoofax Language Workbench

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

Page 133: Building DSLs with the Spoofax Language Workbench

Rewrite in Context

Inline : [def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*] where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*

bound in context

Page 134: Building DSLs with the Spoofax Language Workbench

[ TemplateDef( "footer" , [] , [Navigate(PageRef("webdsl", []), [String("WebDSL")])] ), TemplateDef( "blog" , [Param("b", SimpleType("Blog"))] , [Call("footer")] )]

Inline : [def@TemplateDef(f,[],elem*) | def1*] -> [def | def2*] where def2* := <alltd((Call(f) -> Call("container", elem*)))> def1*

f elem*

def1*Call(f)

Page 135: Building DSLs with the Spoofax Language Workbench

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

Page 136: Building DSLs with the Spoofax Language Workbench

Rules with Parameters

Transform all elements of a list

Invert order of elements of a list

Pair elements of two lists

inverse(|ys) : [] -> ys

inverse(|ys) : [x|xs] -> <inverse(|[x|ys])> xs

map(s) : [] -> []

map(s) : [x|xs] -> [<s>x | <map(s)> xs]

zip(s) : ([],[]) -> []

zip(s) : ([x|xs],[y|ys]) -> [<s>(x,y) | <zip(s)>(xs,ys)]

Page 137: Building DSLs with the Spoofax Language Workbench

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

Page 138: Building DSLs with the Spoofax Language Workbench

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

Page 139: Building DSLs with the Spoofax Language Workbench

Rewrite in Context: Local Traversal

Inline : [def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*] where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*

local traversal

Page 140: Building DSLs with the Spoofax Language Workbench

Dynamic Rewrite Rules

Page 141: Building DSLs with the Spoofax Language Workbench

Rewrite in Context: Not Optimal

requires def before use local traversal for each declaration

Inline : [def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*] where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*

Page 142: Building DSLs with the Spoofax Language Workbench

Dynamic Rewrite Rules

separate traversal from rule definition (binding closures)

declare-inline : TemplateDef(f,[],elem1*) -> TemplateDef(f,[],elem1*) where rules( InlineTemplate : Call(f) -> Call("container", elem1*) )

inline = alltd(declare-inline); topdown(try(InlineTemplate))

Inline : [def@TemplateDef(f,[],elem1*) | def1*] -> [def | def2*] where def2* := <alltd((Call(f) -> Call("container", elem1*)))> def1*

Page 143: Building DSLs with the Spoofax Language Workbench

outputPost(p) -> navigate post(p) { output(p.name) }where define outputPost(pst : Post) { navigate post(pst) { output(pst.name) } }

Inlining as Rewrite Problem (Revisited)

(informal)

declare-inline : TemplateDef(f,[],elem1*) -> TemplateDef(f,[],elem1*) where rules( InlineTemplate : Call(f) -> Call("container", elem1*) )

(formal; but not yet complete)

Page 144: Building DSLs with the Spoofax Language Workbench

Template Parameterdefine page blog(b : Blog) { 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>}

Page 145: Building DSLs with the Spoofax Language Workbench

declare-inline : def@TemplateDef(mod*,f,param*,elem1*) -> def where rules( InlineTemplate : Call(f, e*, []) -> Call("container", [], elem3*) where elem3* := <substitute> (param*, e*, elem1*) ) substitute : (param*, e*, elem1*) -> elem2* where {| Subst : <zip(bind-arg)> (param*, e*) ; elem2* := <alltd(Subst)> elem1* |} bind-arg : (Param(x, t), e) -> (Param(x, t), e) where rules( Subst : Var(x) -> e )

Inlining Templates with Parameters

Page 146: Building DSLs with the Spoofax Language Workbench

declare-inline : def@TemplateDef(mod*,f,param*,elem1*) -> def where rules( InlineTemplate : Call(f, e*, elem2*) -> Call("container", [], elem3*) where {| Subst : rules( Subst : Elements() -> Call("container",[],elem2*) ) ; elem3* := <substitute> (param*, e*, elem1*) |} )

define list() { <ul> elements </ul> }

Element Parameters

Page 147: Building DSLs with the Spoofax Language Workbench

Removing Intermediate Structures

rules // remove containers desugar-container : [Call("container",[], elem1*) | elem2*] -> [elem1*, elem2*] desugar : elem1* -> elem2* where elem2* := <at-suffix(desugar-container)> elem1*

container needed to replace single call with multiple elements

Page 148: Building DSLs with the Spoofax Language Workbench

Inlining Strategy

module template-inlining

imports libstratego-libimports include/nwlimports desugar

strategies inline-all = desugar-all; alltd(declare-inline); innermost(desugar <+ InlineTemplate)

rules declare-inline : ...

Page 149: Building DSLs with the Spoofax Language Workbench

module template-inlining

imports libstratego-libimports include/nwlimports desugar

strategies inline-all = desugar-all; alltd(declare-inline); innermost(desugar <+ InlineTemplate)

rules declare-inline : TemplateDef(mod*,f,param*,elem1*) -> TemplateDef(mod*,f,param*,elem1*) where rules( InlineTemplate : Call(f, e*, elem2*) -> Call("container", [], elem3*) where {| Subst : rules( Subst : Elements() -> Call("container", [], elem2*) ) ; elem3* := <substitute> (param*, e*, elem1*) |} ) substitute : (param*, e*, elem1*) -> elem2* where {| Subst : <zip(bind-arg)> (param*, e*) ; elem2* := <alltd(Subst)> elem1* |} bind-arg : (Param(x, t), e) -> (Param(x, t), e) where rules( Subst : Var(x) -> e )

rules // remove containers desugar-container : [Call("container",[], elem1*) | elem2*] -> [elem1*, elem2*] desugar : elem1* -> elem2* where elem2* := <at-suffix(desugar-container)> elem1*

Template Inlining Transformation