You want to use the best tool for the job when analyzing, transforming or generating source code, so normally you will end up with many different tools, possibly even written in different languages. Now the problem is to integrate these tools again. Rascal solves this problem by integrating source code analysis, transformation, and generation primitives on the language level. Use it for any kind of metaprogramming task: to construct parsers for programming languages, to analyze and transform source code, or to define new DSLs with full IDE support.
Rascal is a programming language; such that meta programs can be created by, understood by, and debugged by programmers.
Rascal primitives include immutable data, context-free grammars and algebraic data-types, relations, relational calculus operators, advanced patterns matching, generic type-safe traversal, comprehensions, concrete syntax for objects, lexically scoped backtracking, and string templates for code generation. It has libraries for integrating language front-ends, for reusing analysis algorithms, for getting typed meta-data out of version management systems, for interactive visualization, etc.
From A DSL in 36 lines of code:
A grammar in Rascal:
module Syntax
extend lang::std::Layout;
extend lang::std::Id;
start syntax Machine = machine: State+ states;
syntax State = @Foldable state: "state" Id name Trans* out;
syntax Trans = trans: Id event ":" Id to;
A fact extractor and checker in Rascal, using concrete syntax:
module Analyze
import Syntax;
set[Id] unreachable(Machine m) {
r = { <q1,q2> | (State)`state <Id q1> <Trans* ts>` <- m.states,
(Trans)`<Id _>: <Id q2>` <- ts }+;
qs = [ q.name | /State q := m ];
return { q | q <- qs, q notin r[qs[0]] };
}
A code generator:
module Compile
import Syntax;
str compile(Machine m) =
"while (true) {
' event = input.next();
' switch (current) {
' <for (q <- m.states) {>
' case \"<q.name>\":
' <for (t <- q.out) {>
' if (event.equals(\"<t.event>\"))
' current = \"<t.to>\";
' <}>
' break;
' <}>
' }
'}";
We use Cyclomatic Complexity to measure the complexity of a method. The first module implements a Cyclomatic Complexity calculation using Concrete Syntax pattern matching.
module CalculateCC
import lang::java::\syntax::Java15;
int cyclomaticComplexity(MethodDec m) {
result = 1;
visit (m) {
case (Stm)`do <Stm _> while (<Expr _>);`: result += 1;
case (Stm)`while (<Expr _>) <Stm _>`: result += 1;
case (Stm)`if (<Expr _>) <Stm _>`: result +=1;
case (Stm)`if (<Expr _>) <Stm _> else <Stm _>`: result +=1;
case (Stm)`for (<{Expr ","}* _>; <Expr? _>; <{Expr ","}*_>) <Stm _>` : result += 1;
case (Stm)`for (<LocalVarDec _> ; <Expr? e> ; <{Expr ","}* _>) <Stm _>`: result += 1;
case (Stm)`for (<FormalParam _> : <Expr _>) <Stm _>` : result += 1;
case (Stm)`switch (<Expr _> ) <SwitchBlock _>`: result += 1;
case (SwitchLabel)`case <Expr _> :` : result += 1;
case (CatchClause)`catch (<FormalParam _>) <Block _>` : result += 1;
}
return result;
}
Then we iterate over all the files in a directory and it's sub directories and select the top 10 most complex methods
module FindComplexFiles
import List;
import Exception;
import ParseTree;
import util::FileSystem;
import lang::java::\syntax::Disambiguate;
import lang::java::\syntax::Java15;
import CalculateCC;
lrel[int cc, loc method] findComplexFiles(loc project, int limit = 10) {
result = [*maxCC(f) | /file(f) <- crawl(project), f.extension == "java"];
result = sort(result, bool (<int a, loc _>, <int b, loc _>) { return a < b; });
return head(reverse(result), limit);
}
set[MethodDec] allMethods(loc file)
= {m | /MethodDec m := parse(#start[CompilationUnit], file)};
lrel[int cc, loc method] maxCC(loc file)
= [<cyclomaticComplexity(m), [email protected]\loc> | m <- allMethods(file)];
Running findComplex
on the JHotdraw project returns the
following top 10 complex methods
rascal>findComplexFiles(|project://jhotdraw751/|) lrel[int cc,loc method]: [ <83,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/io/StreamPosTokenizer.java|(17762,15910,<499,4>,<946,5>)>, <42,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/draw/liner/SlantedLiner.java|(1396,6337,<53,4>,<208,5>)>, <42,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/geom/BezierPath.java|(14900,4669,<486,4>,<634,5>)>, <31,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/draw/DefaultDrawingViewTransferHandler.java|(2659,12674,<75,4>,<297,5>)>, <30,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/draw/liner/ElbowLiner.java|(1422,5658,<52,4>,<186,5>)>, <28,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/color/HSLPhysiologicColorSpace.java|(1234,3094,<41,4>,<149,5>)>, <28,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/xml/JavaPrimitivesDOMFactory.java|(6035,3378,<175,4>,<263,5>)>, <25,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/net/n3/nanoxml/StdXMLParser.java|(11928,7203,<460,3>,<657,4>)>, <25,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/xml/JavaPrimitivesDOMFactory.java|(9424,2954,<266,4>,<335,5>)>, <24,|project://jhotdraw751/JHotDraw%207.5.1/Source/jhotdraw7/src/main/java/org/jhotdraw/draw/liner/CurvedLiner.java|(1413,5993,<53,4>,<191,5>)> ]
|project...|
are source locations, which is a native concept in
Rascal.
Source locations represent a unique identifier for files (URI) along with a
specific region in a file.
Inside the Rascal IDE you can click on any of these source locations, and an editor will open and show the file and the relevant range
This is a very basic source-to-source Java transformation which changes the style of the code without changing its semantics. The code uses concrete syntax to make sure no syntax errors are introduced and to make sure the rules always match something that really exists in Java.
module Idiomatic
import lang::java::\syntax::Java15;
import IO;
import ParseTree;
CompilationUnit idiomatic(CompilationUnit unit) = innermost visit(unit) {
case (Stm) `if (!<Expr cond>) <Stm a> else <Stm b>` =>
(Stm) `if (<Expr cond>) <Stm b> else <Stm a>`
case (Stm) `if (<Expr cond>) <Stm a>` =>
(Stm) `if (<Expr cond>) { <Stm a> }`
when (Stm) `<Block _>` !:= a
case (Stm) `if (<Expr cond>) <Stm a> else <Stm b>` =>
(Stm) `if (<Expr cond>) { <Stm a> } else { <Stm b> }`
when (Stm) `<Block _>` !:= a
case (Stm) `if (<Expr cond>) { return true; } else { return false; }` =>
(Stm) `return <Expr cond>;`
};
The following code is a small test program written to demonstrate the effect of the idiomatic function. Test functions are integrated in Rascal and will generate random input for parameters and integrate with the IDE to produce test reports.
test bool example() {
code = (CompilationUnit) `class MyClass { int m() { if (!x) println("x"); else println("y"); if (x) return true; else return false; } }`;
return idiomatic(code)
==
(CompilationUnit) `class MyClass { int m() { if (x) { println("y"); } else { println("x"); } return x; } }` ;
}