This is an old revision of the document!
The LARA language is JavaScript (plain JavaScript is valid LARA code) extended with custom syntax oriented for source code analysis and transformation.
Recently LARA has been moving away from language-specific constructs (e.g., select, apply) in favor of JavaScript-based APIs (e.g., weaver.Query).
Aspects are regions of code where LARA syntax can be used, and are similar to functions. They are also the entry point of a LARA script, the first aspect of the script is executed by default.
Aspectdef definition
// The main aspect is the first declared in the file aspectdef MyFirstAspect // aspect code end aspectdef ASecondAspect // An aspect that can be called in the main aspect // aspect code end
Input parameter definition:
input funcs, opt end
Input parameter definition with default values:
input funcs = ['kernel'], opt = ['unroll', 'interchange', 'tile'] end
Output definition. Output cannot be initialized inside the output block:
output optFuncs, code end optFuncs = []; code = '';
Simple aspect call:
call OptimizeProgram();
Calling an aspect with arguments:
call OptimizeFunctions(functions, optimizations);
Calling an aspect with named arguments:
call OptimizeFunctions(opt: optimizations, funcs: ['gridIterate']);
Calling an aspect and retrieving the outputs:
// Current syntax var optimizer = call OptimizeFunctions(functions, optimizations); // Previous syntax call optimizer : OptimizeFunctions(functions, optimizations); var changedFuncs = optimizer.optFuncs; var finalCode = optimizer.code;
Assigning an aspect call to a variable for later use. Can still use input arguments and outputs as shown before:
var optimizer = new OptimizeFunctions(functions, optimizations); call optimizer(); // or optimizer.call() var changedFuncs = optimizer.optFuncs; var finalCode = optimizer.code;
Aspects can also be called inside Javascript files (.js). You need to build the aspect object and reference the complete path, separated by dollar signs ($):
// Calling aspect MeasureEnergy from inside a .js file (new lara$profiling$Energy$EnergyTest()).call();
Actions are used inside apply statements. There are two default actions, insert and def. Then, it is possible to use weaver-specific actions, which are called with the exec keyword:
select function{'kernel'} end apply insert before '/* Creating a clone. */'; // add a comment before the function def name = 'original_kernel'; // redefine the name of the kernel function exec clone('cloned_kernel'); // use a MANET-specific action to create a 'kernel' clone named 'cloned_kernel' end
When calling an action we can specify any join point in the chain to be the action target:
select function.loop end apply $loop.exec tile(8); $function.insert before '/* This function was transformed. */'; end
It is possible to omit the target, the last join point in the chain is used:
select function.loop end apply exec unroll(2); // performed on the same loop as below $loop.exec tile(8); // performed on the same loop as above end
If the action returns a result, we can use the following syntax:
var result = $jp.exec <action_name>;
If 'result' is a variable that already exists, 'var' can be omitted:
result = $jp.exec <action_name>;
The keyword '.exec' can be omitted if you prefix the name of the action with the target join point, and add parenthesis:
result = $jp.<action_name>();
You can invoke shell commands from LARA using the function cmd. The first argument is the command, and the second an array with the arguments for the command:
cmd("ls", ["-l", "-v"]);
To add include folders, use the flag -i. To add more than one folder, use double quotes and separate the folders with the file separator character of your current system
larai -i "libs1;libs2"
To pass arguments to the top-level aspect use the flag -av. The input is a JSON representation of the input:
larai -av "{inputFile:'data.json',execute:true,iterations:10}"
To insert sections of code that span several lines, you can define codedef sections, which act as templates. Example:
codedef CodeTemplate(param1, param2) %{ // This code is inserted as-is, without escaping // To apply the values of parameters, use [[]] var [[param1]] = [[param2]]; }% end
Declared codedefs can then be used in the code as functions:
code = CodeTemplate("varName", 2);
Lara supports JavaScript regular expressions, for instance:
var regex = /(return\s+)(10)(\s*;)/; regex.test("return 10;"); // Returns 'true'
Since exec is a LARA keyword, calling regex.exec is not permitted. However, you can access object properties using strings to circumvent this limitation:
(regex['exec']("return 10;"))[2]; // Returns '10'
Alternatively, you can also use the match function in strings:
String('aaaa').match(new RegExp('a*')) // Returns 'aaaa'
Or you can also use the match boolean operator ( ~= ):
'aaaa' ~= /a*/ // Returns true
LARA supports importing LARA files (with extension .lara) that are present in the include path (flag -i) using the keyword import. To import a file, you have to use the path to the file from the include folder, using '.' as separator and omitting the extension of the file. For instance, if you add as include the folder ~/foo and you want to import the file ~/foo/bar/Aspect.lara, you can write the following code:
import bar.Aspect;
Import statements must be the first statements in a LARA file. LARA weavers come bundled with support for a set of imports, which are part of their API (e.g., Clava API).
All JS files (with extension .js) that are present in folders of the include path are automatically evaluated before any LARA file. However, there is a mechanism for importing specific JS files at the same time as LARA files.
When the name of a folder of the include path ends with '-lara', the JS files are not automatically loaded, but instead, must be imported using the keyword import. The same rules of importing of LARA files apply. For instance, if you add as include the folder ~/src-lara and you want to import the file ~/src-lara/bar/Foo.js, you can write the following code:
import bar.Foo;
If the folder contains both a Foo.lara and a Foo.js, both files are imported, first the LARA file, and then the JS file. This can be useful if the JS file needs specific imports (the keyword import is LARA-specific and is not supported by native JS), in this case the imports can be declared in a LARA file and the rest of the code in a JS file. If both files are connected, we recommend using the same name for both files, with corresponding extensions (.lara and .js).
Furthermore, since importing will evaluate all imported LARA and JS scripts in the same context, to help keep things organized, we recommend that LARA/JS scripts declare a single variable/class with the same name as the import (e.g., importing weaver.Query declares a new class Query).
Since LARA is based on an older version of JS (i.e. EcmaScript 5), many recent JavaScript features are not available in LARA code. We recommend using JS files in order to have access to more recent features of JavaScript. Currently the LARA framework supports EcmaScript 2021 for JS files.
LARA supports reading from and writing to JSON objects with the object Io:
import lara.Io; ... Io.writeJson("file.json", anObject); var loadedObject = Io.readJson("file.json");
To test if a join point is of a certain type, use the attribute .instanceOf(<join_point_name>)
. This respects the join point hierarchy (e.g., “call” and “expr” can both return true) and is more robust than directly checking the name of the join point, i.e. $jp.joinPointType === <join_point_name>
.
This section contains LARA constructs that are still valid, but for which newer (and recommended) alternatives exist.
(Alternative: Query.search())
Select with full join point chain:
select program.file.function.body.loop end
Select with only last join point. The chain is induced, produces the same result as above:
select loop end
Assign the selected join points to a variable for later use:
LOOPS: select loop end
Assign an alias for specific join points:
select ($p=loop).($c=loop) end
(Alternative: for…of on return of Query.search() - i.e. weaver.Selector instances)
Apply after a select:
select loop end apply println($loop.rank); end
Apply on a previously selected set of join points:
apply to LOOPS println($loop.rank); end
It is possible to perform a natural join on two sets of join points:
LOOP_START: select function.body.loop.($loop_start = first) end FUNCTION_FIRST: select function.body.first end apply to LOOP_START::FUNCTION_FIRST // Init counters at the beginning of the function $first.insert before%{counter_[[$loop.uid]] = 0;}%; // Increment counters when entering the loop $loop_start.insert before%{counter_[[$loop.uid]] = counter_[[$loop.uid]] +1;}%; // ... end
Use of join points with aliases inside the apply statement:
select ($p=loop).($c=loop) end apply $p.exec Interchange($c); end
(Alternative: Query.search() filters)
Using a condition block:
select function end apply // ... end condition $function.name == 'kernel' end
Combining conditions using && (and) and || (or) operators:
select loop end apply $loop.exec Unroll(2); end condition $loop.is_innermost && $loop.type=="for" end
Using “filter” conditions on the join point chain:
select function{name=='kernel'} end apply // ... end // OR select function{'kernel'} end // uses the default attribute of the join point apply // ... end