Library for building abstract semantic graph of library specification written in LibSL.
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.vpa-research:libsl-parser:Tag'
}
repositories {
...
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.vpa-research:libsl-parser:Tag")
}
Attention: do not forget to replace Tag to the needed version from releases(also, you can replace Tag with
some commit hash from the master branch).
val libSL = LibSL("some/path/to/working/dir")
val library = libSL.loadFromFile("path/to/lsl/file")The constructor of LibSL() sets the path to the working dir. This dir can contain .lsl files to be imported
via import keyword of LibSL. If this feature isn't needed, an empty string should be passed.
After execution of this code, variable library will contain the LSL Tree nodes representing the description structure.
For example, let's get library's name:
library.metadata.nameAlso, field metadata contains field such as lslVersion and some optional fields (see sources of the
meta.kt)
Let's get the list of all library's automata:
val automata = library.automataNow, let's get the automaton with name MyAutomaton and add the new state to it:
val automaton = automata.first { automaton -> automaton.name == "MyAutomaton" }
val newState = State("MyState", kind = StateKind.SIMPLE)
automaton.states.add(newState)The argument called kind sets state's kind. The value could be SIMPLE (means 'just a state'),
INIT (initial state) or FINISH (finish state). Read LibSL docs for more info.
This way to get an automaton (also functions, types and global variables) has a limitation: it can't find an automaton
if it located in import-ed file. There is the true-way name resolution (finding the node), see the next paragraph.
Contexts represent scopes of entities. They are used to describe visibility levels of such things like variables, automata, functions and types. For example:
types {
// global context is available here
int(int32);
T(int32);
}
automaton A : T {
// global context and the A automaton context are available here
fun foo(argA: int) {
// global context, the A automaton context and the foo function's context are available here
argA = 1; // arga is in context of the foo function
}
}
fun A.bar(argB: int) {
// global context, the A automaton context and the bar function's context are available here
// error: argA = 1 as far as argA is declared in context of the function foo that isn't available here
argB = 2; // argB is in context of the bar function
}
So, these contexts are pretty much like visibility scopes from other languages.
Important note: contexts are represent like linked structures:
val globalScope = LslGlobalContext()
val automatonAContext = AutomatonContext(globalScope)
val functionFooContext = FunctionContext(automatonContext)
val functionBarContext = FunctionContext(automatonContext)
// NB: functionFooContext is independent of functionBarContextThen, if you try to resolve the reference in context, the context will try to resolve it in itself, then in parent one, then in parent's parent, ...
Getting the context of library:
val context = libSL.contextTip: The context can be also set to LibSL object via the constructor.
References are used to describe entities like automata, functions, types and variables. Each type of object has the reference builder, so references could be got like this:
val intTypeRef = TypeReferenceBuilder.build("int")
val functionFooReference = FunctionReferenceBuilder.build("foo", listOf(intTypeRef), automatonAContext)
// functionFooReference describes function with name foo and one argument of type int
val intType = intType.resolve() // returns the int type or null (if it can't be resolved)
// val intType = intType.resolveOrError() returns the int type or throws an exception (if it can't be resolved)
val functionFoo = functionFooReference.resolve()These movements also could be used to get an automaton or variable. So, you must use reference builders and .resolve()
or resolveOrError() functions to get nodes if it possible.
There are some other functions for name resolution, see sources of references/.
IMPORTANT: when the new node is being added to the ASG and the node can be resolved via context, you must add it to the corresponding context too:
context.storeAutomaton(myAutomaton)LibSL has expressions. They are used in contracts and as function's arguments. Under the hood they are represented by ASG nodes.
There are some utilities to improve an expression experience:
4.1. ExpressionVisitor
This class could be used to visit expressions.
4.2 TypeInferer
This class could be used to simple type resolution. Example:
context.typeInferer.getExpressionTypeOrNull(myExpression)IMPORTANT: this class does simple type resolution. So, type of 1 + (2*4) is IntType, but type of 1 + 1.0 can't be
determined. The interer also can determine types of functions, arrays and others (1 + arr[0])
Create the new .lsl file in ./src/test/testdata/lsl/. Then run the main() function in
generateTests.kt or run the Generate tests configuration preset in IntelliJ Idea.
New test runners can be found in the file GeneratedTests.kt
Each ASG (abstract semantic graph) received by the parser (abstract semantic graph) is being compared with the
result of the previous run. These results are located in ./src/test/testdata/expected/.