209 lines
No EOL
5.7 KiB
JavaScript
209 lines
No EOL
5.7 KiB
JavaScript
import {scan, token_types} from './scanner';
|
|
|
|
export const parse = function (tokens) {
|
|
let token_index = 0;
|
|
|
|
return statement();
|
|
|
|
function statement() {
|
|
if (check(token_types.IDENTIFIER, token_index) && check(token_types.EQUALS, token_index + 1)) {
|
|
let var_name = current_token();
|
|
advance();
|
|
advance();
|
|
return {type: 'declaration', var_name: var_name, initializer: expression()};
|
|
} else {
|
|
return expression();
|
|
}
|
|
}
|
|
|
|
function expression() {
|
|
return equality();
|
|
}
|
|
|
|
function equality() {
|
|
let expr = comparison()
|
|
|
|
while (match([token_types.EQUALS_EQUALS, token_types.NOT_EQUALS])) {
|
|
let operator = previous_token();
|
|
let right = unary();
|
|
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
function comparison() {
|
|
let expr = addition();
|
|
|
|
while (match([token_types.LESS, token_types.LESS_OR_EQUAL, token_types.GREATER, token_types.GREATER_OR_EQUAL])) {
|
|
let operator = previous_token();
|
|
let right = addition();
|
|
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
function addition() {
|
|
let expr = multiplication();
|
|
|
|
while (match([token_types.MINUS, token_types.PLUS])) {
|
|
let operator = previous_token();
|
|
let right = multiplication();
|
|
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
function multiplication() {
|
|
let expr = unary();
|
|
|
|
while (match([token_types.SLASH, token_types.STAR, token_types.DOT])) {
|
|
let operator = previous_token();
|
|
let right = unary();
|
|
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
function unary() {
|
|
if (match([token_types.NOT, token_types.MINUS])) {
|
|
let operator = previous_token();
|
|
let right = unary();
|
|
return {type: 'unary', operator: operator, right: right};
|
|
} else {
|
|
return call();
|
|
}
|
|
}
|
|
|
|
function call() {
|
|
let expr = primary();
|
|
|
|
for (; ;) {
|
|
if (match([token_types.LEFT_PAREN])) {
|
|
expr = finish_call(expr.name);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
function finish_call(callee) {
|
|
let arguments_list = [];
|
|
if (!check(token_types.RIGHT_PAREN, token_index)) {
|
|
do {
|
|
arguments_list.push(expression());
|
|
} while (match([token_types.COMMA]));
|
|
}
|
|
if (!match([token_types.RIGHT_PAREN])) {
|
|
throw {message: "Expect ')' after arguments."};
|
|
}
|
|
|
|
return {type: 'call', name: callee, arguments: arguments_list};
|
|
}
|
|
|
|
|
|
function primary() {
|
|
if (match([token_types.NUMERIC, token_types.STRING])) {
|
|
return {type: 'literal', value: previous_token().value, value_type: previous_token().type};
|
|
} else if (match([token_types.LAZY])) {
|
|
let expression = previous_token().expression;
|
|
let tokens = scan(expression);
|
|
let parsed_expression = parse(tokens);
|
|
return {
|
|
type: 'lazy',
|
|
expression: expression,
|
|
value: parsed_expression
|
|
};
|
|
} else if (match([token_types.LEFT_PAREN])) {
|
|
let expr = expression();
|
|
if (expr && match([token_types.RIGHT_PAREN])) {
|
|
return {
|
|
type: 'group',
|
|
expression: expr
|
|
};
|
|
} else {
|
|
throw {message: 'expected expression or )'};
|
|
}
|
|
} else if (check(token_types.IDENTIFIER, token_index)) {
|
|
let identifier = {
|
|
type: 'identifier',
|
|
name: current_token().value
|
|
};
|
|
advance();
|
|
return identifier;
|
|
} else if (check(token_types.AT, token_index)) {
|
|
let result = {
|
|
type: 'reference',
|
|
name: current_token().value
|
|
};
|
|
advance();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* matches token against array of tokens to check for equality (matching type)
|
|
* @param tokens_to_match array of tokens
|
|
* @returns {boolean}
|
|
*/
|
|
function match(tokens_to_match) {
|
|
for (let i = 0; i < tokens_to_match.length; i++) {
|
|
if (are_same(tokens_to_match[i], current_token())) {
|
|
advance()
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if token at position index matches the given
|
|
* @param token_to_check expected token type
|
|
* @param index of token to check
|
|
* @returns {boolean}
|
|
*/
|
|
function check(token_to_check, index) {
|
|
let token = tokens[index];
|
|
if (!token) {
|
|
return false;
|
|
}
|
|
return are_same(token_to_check, token);
|
|
|
|
}
|
|
|
|
/**
|
|
* checks if 2 tokens have same type
|
|
* @param token_1
|
|
* @param token_2
|
|
* @returns {boolean}
|
|
*/
|
|
function are_same(token_1, token_2) {
|
|
if (is_at_end()) {
|
|
return false;
|
|
} else {
|
|
return token_1.type === token_2.type;
|
|
}
|
|
|
|
}
|
|
|
|
function is_at_end() {
|
|
return token_index >= tokens.length;
|
|
}
|
|
|
|
function advance() {
|
|
token_index += 1;
|
|
}
|
|
|
|
function previous_token() {
|
|
return tokens[token_index - 1];
|
|
}
|
|
|
|
function current_token() {
|
|
return tokens[token_index];
|
|
}
|
|
} |