Integrated development environments (IDEs) increase programmer productivity, providing rapid, interactive feedback based on the syntax and semantics of a language. A heavy burden lies on developers of new languages to provide adequate IDE support. Code generation techniques provide a viable, efficient approach to semi-automatically produce IDE plugins. Key components for the realization of plugins are the language's grammar and parser. For embedded languages and language extensions, constituent IDE plugin modules and their grammars can be combined. Unlike conventional parsing algorithms, scannerless generalized-LR parsing supports the full set of context-free grammars, which is closed under composition, and hence can parse language embeddings and extensions composed from separate grammar modules. To apply this algorithm in an interactive environment, this paper introduces a novel error recovery mechanism, which allows it to be used with files with syntax errors -- common in interactive editing. Error recovery is vital for providing rapid feedback in case of syntax errors, as most IDE services depend on the parser -- from syntax highlighting to semantic analysis and cross-referencing. We base our approach on the principles of island grammars, and derive permissive grammars with error recovery productions from normal SDF grammars. To cope with the added complexity of these grammars, we adapt the parser to support backtracking. We evaluate the recovery quality and performance of our approach using a set of composed languages, based on Java and Stratego.
Boost PC performance: How more available memory can improve productivity
Providing Rapid Feedback in Generated Modular Language Environments (OOPSLA 2009)
1. Providing Rapid Feedback in
Generated Modular Language
Environments
Lennart Kats (me)
Maartje de Jonge
Emma Nilsson-Nyman
Eelco Visser
OOPSLA 2009 October 29, 2009
Software Engineering Research Group
8. What about
incorrect and incomplete programs?
entity User {
NORMAL PARSING
name : String
password : String
homepage
}
entity BlogPost {
poster : User
body : String
}
9. What about
incorrect and incomplete programs?
entity User {
name : String
NORMAL PARSING
password : String
homepage
ERROR
}
entity BlogPost { Parse failure:
poster : User No abstract syntax tree
body : String
}
NEVER PARSED
10. Mini-Demo
Error Recovery in
a data modeling language
11. Parsing with SDF and SGLR
• Language composition without
shift/reduce conflicts
• Ambiguities can be inspected
• Declarative disambiguation
12. Normal LR Parser
token token token token token x
Recovery:
Backtracking,
skip/insert tokens, etc.
(S)GLR Parser
token x
token token token token token x
token x
Recovery: ?
13. Do we really need to dive into this
intimidating algorithm?
14. Island Grammars [Van Deursen & Kuipers, 1999]
• Parse only interesting bits (Islands)
• Skip the remaining characters (Water)
IDENTIFICATION DIVISION.
PROGRAM-ID. EXAMPLE.
WATER
PROCEDURE DIVISION.
CALL X.
YADA.
WATER
YADA YADA.
CALL Y.
Grammar-based
“error recovery”!
15. Island Grammars [Van Deursen & Kuipers, 1999]
~[ tn]+ → WATER {avoid}
18. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); ERROR
}
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
19. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
NORMAL PARSING
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); ERROR
}
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
20. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); ERROR
NORMAL PARSING
} }
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
21. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); ERROR
NORMAL PARSING
} }
return;
UNEXPECTED KEYWORD
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
22. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling();
}
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
} New production:
WATER → MethodDec {cons(“WATER”)}
{cons(“WATER”),recover}
23. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling();
}
return;
WATER
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
} New production:
WATER → MethodDec {cons(“WATER”),recover}
Stm {cons(“WATER”),recover}
24. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling();
}
WATER
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
} New production:
WATER → Stm {cons(“WATER”),recover}
25. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); LAYOUT
}
WATER
PROBLEMATIC TOKEN
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
26. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); LAYOUT
}
WATER
PROBLEMATIC TOKEN
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
} New production:
WATER → LAYOUT {cons(“WATER”),recover}
27. Mixing Java with Water
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); LAYOUT
}
WATER
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
} New production:
WATER → LAYOUT {cons(“WATER”),recover}
29. Danger of Floods
public class Fridge {
public void startCooling() {
cooling.commence();
// missing }
public void onResetButtonPress() {
log.message(“Reset button pressed”);
power.reboot();
}
}
30. Danger of Floods
public class Fridge {
public void startCooling() {
cooling.commence();
// missing }
public void onResetButtonPress() {
WATER
log.message(“Reset button pressed”);
power.reboot();
}
}
So why not parse methods
with missing closing brackets?
31. Productions for Missing Literals
why not add rules like this:
“if” “(” Expr Stm → Stm {cons(“If”), recover}
and this, and this, and this:
“if” Expr “)” Stm → Stm {cons(“If”), recover}
“if” Expr Stm → Stm {cons(“If”), recover}
“while” “(“ Expr Stm → Stm {cons(“While”), ...}
...
not going to scale.
32. Productions for Missing Literals
“if” “(” Expr “)” Stm → Stm {cons(“If”)}
what it means internally:
IF BOPEN … BCLOSE … → …
[i][f] → IF
[(] → BOPEN
[)] → BCLOSE
so, we can write (using the literal instead of BCLOSE):
→ BCLOSE {recover}
“)”
→ “}” {recover}
33. Applying Insertion Rules
public class Fridge {
public void startCooling() {
cooling.commence();
// missing }
public void onResetButtonPress() {
log.message(“Reset button pressed”);
power.reboot();
}
}
New production:
→ “}” {recover}
34. Applying Insertion Rules
public class Fridge {
public void startCooling() {
cooling.commence();
// missing }
INSERT }
public void onResetButtonPress() {
log.message(“Reset button pressed”);
power.reboot();
}
}
New production:
→ “}” {recover}
35. Recovery Rules
•
• Water
• So who's gonna
• Closing brackets (“}”) write all those
recovery rules?
•
• Opening brackets (“{”)
•
• Separators (“,”)
• We derive
them from the
• Comments
grammar!
•
• String literals
37. Putting Things Together
• Water y = f ( x + 1 ;
• Insertion: “)” y = f ( x + 1 ) ;
y = x ; y = f ( x ) + 1 ;
f ( x + 1 ) ; y = f + 1 ;
f ( 1 ) ; y = f ( 1 ) ;
y ( x + 1 ) ; y = ( x ) + 1 ;
y ( x ) ; y = ( x + 1 ) ;
y () ; y = x + 1 ;
y ( 1 ) ; y = f ;
y = 1 ; y = ( x ) ;
; y = f ( ) ;
40. Recovery Using Backtracking
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); ERROR
}
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
41. Recovery Using Backtracking
public class TemperatureMonitor {
private Fridge fridge;
1. NORMAL PARSING
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); ERROR
}
return;
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
42. Recovery Using Backtracking
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling(); ERROR
1. NORMAL PARSING
}
return;
POINT OF DETECTION
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
43. Recovery Using Backtracking
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) // missing {
fridge.startCooling();
1. NORMAL PARSING
}
return;
2. BACKTRACKING
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
44. Recovery Using Backtracking
public class TemperatureMonitor {
private Fridge fridge;
public void trigger(Temperature temp) {
if (temp.greaterThan(MAX)) { missing {
// 1 RULE
fridge.startCooling(); 2 RULES
1 RULE
} 3 RULES
2 RULES
1 RULE
return;
4 RULES
3 RULES
2 RULES
1 RULE
}
public TemperatureMonitor(Fridge fridge) {
this.fridge = fridge;
}
}
48. Continued Work
“Natural and Flexible Error Recovery
for Generated Parsers” [SLE 2009]
• indentation-based region selection
• fall-back mechanism
• bridge parsing [Nilsson-Nyman et al, 2008]
(www.strategoxt.org/Stratego/PermissiveGrammars)
49. Continued Work
“Natural and Flexible Error Recovery
for Generated Parsers” [SLE 2009]
• comparison with JDT
• performance, quality
• synergy between recovery techniques
(www.strategoxt.org/Stratego/PermissiveGrammars)
50. Conclusion
SGLR/SDF
• modular specifications
• composable languages
Recovery production rules
• transparent
• flexible
• language-independent
www.strategoxt.org/Stratego/PermissiveGrammars
www.strategoxt.org/Stratego/SpoofaxIMP
www.lennartkats.net
Software Engineering Research Group