✨ start working on parser
This commit is contained in:
parent
06a9d4a122
commit
d52cdc4874
460
src/parser.js
460
src/parser.js
@ -15,9 +15,461 @@ class Juicescript_parser {
|
|||||||
MAIN: Do parsing
|
MAIN: Do parsing
|
||||||
*/
|
*/
|
||||||
parse(){
|
parse(){
|
||||||
/**/for(let one_token of this.token_list){
|
// RESET //
|
||||||
/**/one_token.type = Juicescript.token_type.name(one_token.type);
|
// counters
|
||||||
/**/console.log(one_token);
|
this.position = 0;
|
||||||
/**/}
|
|
||||||
|
// current scope
|
||||||
|
this.scope = null;
|
||||||
|
|
||||||
|
// token list
|
||||||
|
this.tree = this.program_tree_template();
|
||||||
|
|
||||||
|
// warning and error counter
|
||||||
|
this.warning_count = 0;
|
||||||
|
this.error_count = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// PARSE ALL TOKENS //
|
||||||
|
while(!this.is_at_end()){
|
||||||
|
// consume next token
|
||||||
|
this.next();
|
||||||
|
|
||||||
|
// parse
|
||||||
|
this.parse_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RETURN PROGRAM TREE //
|
||||||
|
return this.tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Parse one token at current position
|
||||||
|
*/
|
||||||
|
parse_one(){
|
||||||
|
switch(this.token.type){
|
||||||
|
// DELIMITER //
|
||||||
|
case Juicescript.token_type.DELIMITER:
|
||||||
|
// ignore
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// FUNCTION DEFINITION //
|
||||||
|
case Juicescript.token_type.DEF:
|
||||||
|
this.parse_def();
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// END //
|
||||||
|
case Juicescript.token_type.END:
|
||||||
|
this.parse_end();
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// COMMAND //
|
||||||
|
case Juicescript.token_type.IDENTIFIER:
|
||||||
|
this.parse_command();
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// EVERYTHING ELSE //
|
||||||
|
default:
|
||||||
|
// unexpected (ignore with error)
|
||||||
|
this.error_token("unexpected token");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PARSER: Handle function definition
|
||||||
|
*/
|
||||||
|
parse_def(){
|
||||||
|
let parameter_list = [];
|
||||||
|
while(!this.is_at_end()){
|
||||||
|
// IS NEXT TOKEN A DELIMITER? //
|
||||||
|
if(this.match_type(Juicescript.token_type.DELIMITER)){
|
||||||
|
// end of parameter list
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GET NEXT PARAMETER //
|
||||||
|
// consume next token
|
||||||
|
this.next();
|
||||||
|
/**/continue;
|
||||||
|
|
||||||
|
// make sure next has valid type for parameter
|
||||||
|
if(!this.is_parameter(this.token)){
|
||||||
|
// ignore with error
|
||||||
|
this.error_token("invalid parameter");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// PARSE PARAMETER //
|
||||||
|
// get parsed object
|
||||||
|
let parameter = this.parse_parameter();
|
||||||
|
|
||||||
|
// add to list
|
||||||
|
parameter_list.push(parameter);
|
||||||
|
}
|
||||||
|
/**/console.log(parameter_list);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**/this.debug("enter scope " + this.peek().value);
|
||||||
|
/**/this.scope = this.peek().value;
|
||||||
|
/**/this.tree.scope[this.scope] = this.scope_tree_template();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PARSER: Handle function definition
|
||||||
|
*/
|
||||||
|
parse_end(){
|
||||||
|
/**/this.debug("enter scope root");
|
||||||
|
/**/this.scope = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PARSER: Handle command
|
||||||
|
*/
|
||||||
|
parse_command(){
|
||||||
|
let argument_list = [];
|
||||||
|
while(!this.is_at_end()){
|
||||||
|
// IS NEXT TOKEN A DELIMITER? //
|
||||||
|
if(this.match_type(Juicescript.token_type.DELIMITER)){
|
||||||
|
// end of argument list
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GET NEXT ARGUMENT //
|
||||||
|
// consume next token
|
||||||
|
this.next();
|
||||||
|
|
||||||
|
// make sure next has valid type for an argument
|
||||||
|
if(!this.is_argument(this.token)){
|
||||||
|
// ignore with error
|
||||||
|
this.error_token("invalid argument");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// PARSE ARGUMENT //
|
||||||
|
// get parsed object
|
||||||
|
let argument = this.parse_argument();
|
||||||
|
|
||||||
|
// add to list
|
||||||
|
argument_list.push(argument);
|
||||||
|
}
|
||||||
|
/**/console.log(argument_list);
|
||||||
|
|
||||||
|
|
||||||
|
///**/this.command_add({name: this.token.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PARSER: Handle argument at current position and return resulting object
|
||||||
|
*/
|
||||||
|
parse_argument(){
|
||||||
|
// NEW OJECT //
|
||||||
|
let argument = {};
|
||||||
|
|
||||||
|
|
||||||
|
// VARIABLE OR LITERAL? //
|
||||||
|
if(this.is_variable(this.token)){
|
||||||
|
// parse variable
|
||||||
|
let variable = this.parse_variable();
|
||||||
|
|
||||||
|
// add to list
|
||||||
|
argument = {type: Juicescript.argument_type.VARIABLE, variable: variable};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// add literal
|
||||||
|
argument = {type: Juicescript.argument_type.LITERAL, value: this.token.value};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RETURN //
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PARSER: Handle variable at current position and return resulting object
|
||||||
|
*/
|
||||||
|
parse_variable(){
|
||||||
|
// NEW OJECT //
|
||||||
|
let variable = {};
|
||||||
|
|
||||||
|
// clear index list
|
||||||
|
variable.index = [];
|
||||||
|
|
||||||
|
|
||||||
|
// GET VARIABLE NAME //
|
||||||
|
if(this.token.value !== null){
|
||||||
|
// literal
|
||||||
|
variable.name = {type: Juicescript.argument_type.LITERAL, value: this.token.value};
|
||||||
|
|
||||||
|
} else if(this.match_type(Juicescript.token_type.BRACKET_CURLY_OPEN)){
|
||||||
|
// consume argument
|
||||||
|
this.next();
|
||||||
|
|
||||||
|
// make sure token has valid type for an argument
|
||||||
|
if(this.is_argument(this.token)){
|
||||||
|
// value of another variable
|
||||||
|
let argument = this.parse_argument();
|
||||||
|
variable.name = argument;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ignore with error
|
||||||
|
this.error_token("expected variable name, but got");
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume curly closing bracket
|
||||||
|
if(!this.match_type(Juicescript.token_type.BRACKET_CURLY_CLOSE)){
|
||||||
|
// ignore with error
|
||||||
|
this.error_token("expected '}', but got", this.peek());
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ignore with error
|
||||||
|
this.error("unable to determine variable name");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HANDLE OPTIONAL LIST KEYS //
|
||||||
|
while(this.match_type(Juicescript.token_type.BRACKET_SQUARE_OPEN)){
|
||||||
|
// consume argument
|
||||||
|
this.next();
|
||||||
|
|
||||||
|
// make sure token has valid type for an argument
|
||||||
|
if(this.is_argument(this.token)){
|
||||||
|
// parse argument
|
||||||
|
let argument = this.parse_argument();
|
||||||
|
|
||||||
|
// add index to list
|
||||||
|
variable.index.push(argument);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ignore with error
|
||||||
|
this.error_token("expected list index, but got");
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume square closing bracket
|
||||||
|
if(!this.match_type(Juicescript.token_type.BRACKET_SQUARE_CLOSE)){
|
||||||
|
// ignore with error
|
||||||
|
this.error_token("expected ']', but got", this.peek());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RETURN //
|
||||||
|
return variable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Consume next token from list
|
||||||
|
*/
|
||||||
|
next(){
|
||||||
|
this.token = this.token_list[this.position++];
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Return OFFSET next token from list
|
||||||
|
*/
|
||||||
|
peek(offset = 0){
|
||||||
|
return this.token_list[(this.position + offset)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Consume (and return true) if OFFSET next token's type matches TYPE
|
||||||
|
*/
|
||||||
|
match_type(type, offset = 0){
|
||||||
|
// ignore if it doesn't match
|
||||||
|
if(this.peek(offset).type != type) return false;
|
||||||
|
|
||||||
|
// consume if it matches
|
||||||
|
this.position += offset;
|
||||||
|
this.next();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match_type_list(type_list, offset = 0){
|
||||||
|
// check all types
|
||||||
|
for(var one_type of type_list){
|
||||||
|
// check this type
|
||||||
|
if(this.match_type(one_type, offset)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing found
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Return if we are at end of token list
|
||||||
|
*/
|
||||||
|
is_at_end(){
|
||||||
|
// check if next token is of type eof
|
||||||
|
let eof = (this.peek().type === Juicescript.token_type.EOF);
|
||||||
|
|
||||||
|
// check if next token exists
|
||||||
|
let out_of_tokens = (this.token_list.length <= this.position);
|
||||||
|
|
||||||
|
// if one of those is true, we are at the end
|
||||||
|
return (eof || out_of_tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Check if type of TOKEN is valid for arguments
|
||||||
|
*/
|
||||||
|
is_argument(token){
|
||||||
|
// check lookup table
|
||||||
|
return ([
|
||||||
|
Juicescript.token_type.VARIABLE,
|
||||||
|
Juicescript.token_type.STRING,
|
||||||
|
Juicescript.token_type.NUMBER,
|
||||||
|
Juicescript.token_type.TRUE,
|
||||||
|
Juicescript.token_type.FALSE,
|
||||||
|
Juicescript.token_type.NULL
|
||||||
|
]).includes(token.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Check if TOKEN is a variable
|
||||||
|
*/
|
||||||
|
is_variable(token){
|
||||||
|
return (token.type === Juicescript.token_type.VARIABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Add new command object with OPTIONS to current scope
|
||||||
|
*/
|
||||||
|
command_add(options){
|
||||||
|
// NEW OJECT //
|
||||||
|
let command = {};
|
||||||
|
|
||||||
|
|
||||||
|
// COLLECT REQUIRED ATTRIBUTES //
|
||||||
|
// name
|
||||||
|
command.name = options.name ?? null;
|
||||||
|
if(command.name === null){
|
||||||
|
throw "missing command name";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// COLLECT ATTRIBUTES WITH POSSIBLE FALLBACK VALUES //
|
||||||
|
// line
|
||||||
|
command.line = options.line ?? this.token.line;
|
||||||
|
|
||||||
|
|
||||||
|
// OPTIONAL ATTRIBUTES //
|
||||||
|
command.argument = options.argument ?? [];
|
||||||
|
|
||||||
|
|
||||||
|
// ADD TO SCOPE'S COMMAND LIST //
|
||||||
|
/**/this.debug("add command " + command.name + " to scope " + (this.scope ?? "root"));
|
||||||
|
/**/console.log(this.scope, command);
|
||||||
|
/**/let scope_command_list = (this.scope === null ? this.tree.root.command : this.tree.scope[this.scope].command);
|
||||||
|
/**/scope_command_list ??= [];
|
||||||
|
/**/scope_command_list.push(command);
|
||||||
|
//*/ TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Return empty program tree structure
|
||||||
|
*/
|
||||||
|
program_tree_template(){
|
||||||
|
return {
|
||||||
|
root: {
|
||||||
|
command: [],
|
||||||
|
flag: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
scope: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Return empty scope tree structure
|
||||||
|
*/
|
||||||
|
scope_tree_template(){
|
||||||
|
return {
|
||||||
|
command: [],
|
||||||
|
flag: {},
|
||||||
|
global: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Automagically produce error messages
|
||||||
|
*/
|
||||||
|
error_token(text, token = null){
|
||||||
|
// GET TOKEN //
|
||||||
|
token ??= this.token;
|
||||||
|
|
||||||
|
|
||||||
|
// HANDLE TOKEN //
|
||||||
|
// get token type name
|
||||||
|
let type_name = Juicescript.token_type.name(token.type).toLowerCase();
|
||||||
|
|
||||||
|
// try to get lexeme
|
||||||
|
let lexeme = null;
|
||||||
|
if(token.lexeme.trim().length > 0){
|
||||||
|
lexeme = token.lexeme.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct error message
|
||||||
|
this.error(text + " " + (lexeme === null ? type_name : ("'" + lexeme + "'")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELPER: Automagically keep track of problems and add additional info to stderr
|
||||||
|
*/
|
||||||
|
debug(text, additional){
|
||||||
|
// add defaults
|
||||||
|
additional ??= {};
|
||||||
|
additional.line ??= this.token.line;
|
||||||
|
|
||||||
|
// forward
|
||||||
|
this.io.stderr.debug(text, additional);
|
||||||
|
}
|
||||||
|
info(text, additional){
|
||||||
|
// add defaults
|
||||||
|
additional ??= {};
|
||||||
|
additional.line ??= this.token.line;
|
||||||
|
|
||||||
|
// forward
|
||||||
|
this.io.stderr.info(text, additional);
|
||||||
|
}
|
||||||
|
warning(text, additional){
|
||||||
|
// KEEP TRACK OF PROBLEM //
|
||||||
|
this.warning_count++;
|
||||||
|
|
||||||
|
|
||||||
|
// PRINT MESSAGE //
|
||||||
|
// add defaults
|
||||||
|
additional ??= {};
|
||||||
|
additional.line ??= this.token.line;
|
||||||
|
|
||||||
|
// forward
|
||||||
|
this.io.stderr.warning(text, additional);
|
||||||
|
}
|
||||||
|
error(text, additional){
|
||||||
|
// KEEP TRACK OF PROBLEM //
|
||||||
|
this.error_count++;
|
||||||
|
|
||||||
|
|
||||||
|
// PRINT MESSAGE //
|
||||||
|
// add defaults
|
||||||
|
additional ??= {};
|
||||||
|
additional.line ??= this.token.line;
|
||||||
|
|
||||||
|
// forward
|
||||||
|
this.io.stderr.error(text, additional);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user