Parse the ALF language source file and generate a JSON abstract syntax tree (AST).
The homework will take one or two parameters from the command line. The first paremeter is the filename with the Alf script, the second one the output file. If the output file is missing, the output is the same as the script file with the extension ”.json”.
node main.js script.alf script.alf.json
Here are some tips for solving the homework:
Try running small scripts to test every feature.
All the nodes in the AST have the following format:
{ id:"node_id", line: "the line where the instruction is in the file, starting at 1" }
The list of the node ids is:
If you have any questions related to the homework, please ask them by posting an issue on the github repository with the title format [alf] <your question title>. You will need a github account for that.
If you want to receive an email when issues are posted or when there are new messages, got to the github repository and click Watch.
The Alf language is a typed language defined as follows:
a=10; b=20; s=55;
Values are
7
7.5
"a"
"this is a text"
false
none
Comments are a part of the source file that is ignored. They are enclosed between ”{” and ”}” and are not imbricated.
{this is a text to describe the script} def v:integer=55;
An identifier can be any value that starts with a letter, $ or _ and may have also numbers in its name.
identifier $identifier _identifier
{ id:"identifier", title:"identifier_name" }
A value is any integer number, float number, logic or string.
3534 45634.423 false "s" "string"
{ id:"value", type:"value_type", // integer, real, character, logic, string value:actual_value }
def variable_name [:variable_type] [= value or expression], ...; def variable_name, variable_name, variable_name, ... :variable_type, ....;
def a:integer = 3; def a = 3; def a1:integer = 3, a2:string = "text", a3:integer; def a,b : integer;
{ id:"def", variables:[ // array of variables { type:"data type", title: "variable name", value: "variable value" }, ] }
struct struct_name property_name:data_type [= initial_value] ... ... end;
{ id:"struct", title:"struct_name", properties:[ // array of properties { type:"property data type", title: "property name", value: "property value" // if it exists }, ] }
object_name.property
{ id:"property", object: { expression for object }, title: { expression for property } }
list_name:elements_data_type[integer_number to integer_number] <note> [] are actual brackets and are mandatory </note>
{ id:"array", title: array_name, element_type:elements_data_type, from: from_integer_number, to: to_integer_number }
array_name[index]
{ id:"element_of_array", array:{ expression for array }, index: { expression for index } }
Operator | Precedence |
---|---|
if | Low |
== != | |
> >= < ⇐ | |
and or xor | |
not | |
+ - | |
* / mod | High |
2+5; 2*4+5; variable+5; x>y; x=y; 2+(x+y)*(5+6);
{ id:"exp", op:"operator", left:left_operand, right:right_operand }
{ id:"exp", op:"negative", value:50 }
variable = expression;
x = 1000; s = "text" + "s"; y = f(); l[i] = 900; q.e = "this is a text";
{ id:"set", to: { ... id, property, element_of_array }, from: { ... exp } }
fn function_name:return_type (parameter1:[parameter1_type][=value], ...) => expression;
fn function_name:return_type (parameter1:[parameter1_type][:=value], ...) begin ... end;
fn f1:none () begin end;
fn sum:integer (a:integer, b:integer) => a+b;
fn power:integer (a:integer, n:integer) begin def p:integer; def i:integer; if n == 0 then begin power=1; end; for i in [1,n] p=p * a; return p; end;
{ id:"fn", title:"function_name", parameters:[ // array of parameters { type:"data type", name: "parameter name" }, ], return_type:"type of the value that the function returns", statements:[ // array of statements (even if it is only one) ... ] }
This is the statement that sets the return value of the function.
return x+y;
{ id:"return", value: { ... } }
The is a call for a function
function_name (parameter_name1:value1, parameter_name2:value, ...);
function_name (parameter1_value, parameter2_value, ...)
write (text:"Text"); s = sum (3, 5); getdir ();
{ id:"function_call", function:"function_name", parameters:{ // dictionary of parameter name or index and value "parameter1": parameter_value, "2":parameter value, ... } }
if expression then ... end;
if expression then ... else ... end;
{ id:"if", exp:expression, then: [ // list of statements ] }
{ id:"if", exp:expression, then: [ // list of statements ], else: [ // list of statements ] }
There are three types of loops: while, repeat and for.
loop expression run ... end;
{ id:"loop_run", exp :expression, statements: [ // array of statements ] }
loop ... when expression;
{ id:"loop_when", exp :expression, statements: [ // array of statements ] }
for variable_name in expression run ... end;
or
for variable_name from number to number run ... end;
for variable_name from number downto number run ... end;
{ id:"for", variable:variable_name, exp:{ expression ... } statements: [ // array of statements ] }
or
{ id:"for", variable:variable_name, from: { expression }, to/downto: { expression }, statements: [ // array of statements ] }
If the source file has any error, you will have to output a JSON with the error. There are two kinds of errors:
For any lexical error, the output will be:
{ error:"lexical", line: line_number, text: "text that is not recognised" }
For any syntactical error, the output will be:
{ error:"syntax", line: line_number, token: "token that the parser saw", // token name will not be checked by checker expected: [list of expected tokens] // token name will not be checked by checker }
For an additional 0.5p, implement the following:
Add preprocessing to the source. You have
Registers an identifier to a value.
#register identifier value
The identifier will be replaced in the program with the value (textually).
Verifies if an identifier is registered and has a value and adds the source code up to else or endif. Else is optional.
#register platform ... ... #if platform = windows p := "Windows" {this source is ignored if platform is not windows} #else p := "Linux" {this source code is ignored if platform is not linux} #endif
Unregister an identifier from a value.
#register N value ... #unregister N {N is undefined}
The homework will be tested automatically using a set of public and private tests.
You can download the public tests from the GitHub repository.
To run the tests, download the contents of the repository in the folder with the homework. Enter the verify folder and run ./run_all.sh.
cd verify ./run_all.sh
You will need bash for that. You can use either Linux or Windows Linux Subsystem.
wget https://nodejs.org/dist/v6.10.0/node-v6.10.0-linux-x64.tar.xz tar xvfJ node-v6.10.0-linux-x64.tar.xz cd node-v6.10.0-linux-x64 sudo cp -R * /usr
When uploading the homework, we might have some private tests that it needs to pass. vmchecker will run them.
The homework needs to be uploaded to vmchecker. Login with your moodle user name, select the Automates et Langages Formelles (FILS) course and upload the homework archive.
The readme file has the following format:
Your full name Group An explanation how you wrote your homework, what did you use, what are the main ideas.
To upload your homework, please follow the following:
DO NO include node_modules.
When the archive is uploaded, vmchecker will run:
unzip archive.zip homework cd homework npm install echo '{ "node":true, "loopfunc": true, "esnext":true }' > .jshintrc jshint *.js jison grammar.y grammar.l -o grammar.js