✨ 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
|
||||
*/
|
||||
parse(){
|
||||
/**/for(let one_token of this.token_list){
|
||||
/**/one_token.type = Juicescript.token_type.name(one_token.type);
|
||||
/**/console.log(one_token);
|
||||
/**/}
|
||||
// RESET //
|
||||
// counters
|
||||
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