266 lines
5.5 KiB
JavaScript
266 lines
5.5 KiB
JavaScript
export const scan = (source) => {
|
|
const tokens = [];
|
|
let start = 0;
|
|
let current = 0;
|
|
let line = 1;
|
|
|
|
const scan_tokens = () => {
|
|
while (!is_at_end()) {
|
|
start = current;
|
|
scan_token();
|
|
}
|
|
|
|
tokens.push({type: EOF, lexeme: "", line: line});
|
|
return tokens;
|
|
};
|
|
|
|
const is_at_end = () => {
|
|
return current >= source.length;
|
|
}
|
|
|
|
const advance = () => {
|
|
return source.charAt(current++);
|
|
}
|
|
|
|
const add_token = (type, literal) => {
|
|
tokens.push({type: type, lexeme: source.substring(start, current), literal: literal, line: line});
|
|
}
|
|
|
|
const scan_token = () => {
|
|
let c = advance();
|
|
switch (c) {
|
|
case '(':
|
|
add_token(LEFT_PAREN);
|
|
break;
|
|
case ')':
|
|
add_token(RIGHT_PAREN);
|
|
break;
|
|
case '{':
|
|
add_token(LEFT_BRACE);
|
|
break;
|
|
case '}':
|
|
add_token(RIGHT_BRACE);
|
|
break;
|
|
case ',':
|
|
add_token(COMMA);
|
|
break;
|
|
case '.':
|
|
add_token(DOT);
|
|
break;
|
|
case '-':
|
|
add_token(MINUS);
|
|
break;
|
|
case '+':
|
|
add_token(PLUS);
|
|
break;
|
|
case ';':
|
|
add_token(SEMICOLON);
|
|
break;
|
|
case '*':
|
|
add_token(STAR);
|
|
break;
|
|
case '!':
|
|
add_token(match('=') ? BANG_EQUAL : BANG);
|
|
break;
|
|
case '=':
|
|
add_token(match('=') ? EQUAL_EQUAL : EQUAL);
|
|
break;
|
|
case '<':
|
|
add_token(match('=') ? LESS_EQUAL : LESS);
|
|
break;
|
|
case '>':
|
|
add_token(match('=') ? GREATER_EQUAL : GREATER);
|
|
break;
|
|
case '/':
|
|
if (match('/')) {
|
|
while (peek() !== '\n' && !is_at_end()) {
|
|
advance();
|
|
}
|
|
} else {
|
|
add_token(SLASH);
|
|
}
|
|
break;
|
|
case ' ':
|
|
case '\r':
|
|
case '\t':
|
|
break;
|
|
case '\n':
|
|
line++;
|
|
break;
|
|
case '"':
|
|
string();
|
|
break;
|
|
default:
|
|
if (is_digit(c)) {
|
|
number();
|
|
} else if (is_alpha(c)) {
|
|
identifier();
|
|
} else {
|
|
throw error(line, "Unexpected character");
|
|
}
|
|
}
|
|
}
|
|
|
|
const identifier = () => {
|
|
while (is_alphaNumeric(peek())) {
|
|
advance();
|
|
}
|
|
let text = source.substring(start, current);
|
|
let type = keywords.get(text);
|
|
|
|
if (type == null) {
|
|
type = IDENTIFIER;
|
|
}
|
|
add_token(type);
|
|
}
|
|
|
|
const is_alphaNumeric = (c) => {
|
|
return is_alpha(c) || is_digit(c);
|
|
}
|
|
|
|
const is_alpha = (c) => {
|
|
return typeof c === 'string' && (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_';
|
|
}
|
|
|
|
const number = () => {
|
|
while (is_digit(peek())) {
|
|
advance();
|
|
}
|
|
|
|
if (peek() === '.' && is_digit(peekNext())) {
|
|
advance();
|
|
}
|
|
|
|
while (is_digit(peek())) {
|
|
advance();
|
|
}
|
|
|
|
add_token(NUMBER, source.substring(start, current));
|
|
}
|
|
|
|
const string = () => {
|
|
while (peek() !== '"' && !is_at_end()) {
|
|
if (peek() === '\n') {
|
|
line++;
|
|
}
|
|
advance();
|
|
}
|
|
|
|
if (is_at_end()) {
|
|
throw new Error(error(line, "Unterminated string"));
|
|
}
|
|
|
|
advance();
|
|
|
|
let value = source.substring(start + 1, current - 1);
|
|
console.log("string "+value);
|
|
add_token(STRING, value);
|
|
}
|
|
|
|
const peekNext = () => {
|
|
return source.charAt(current + 1);
|
|
}
|
|
|
|
const is_digit = (c) => {
|
|
return typeof c === 'string' && c >= '0' && c <= '9';
|
|
}
|
|
|
|
const peek = () => {
|
|
if (is_at_end()) {
|
|
return '\0';
|
|
}
|
|
return source.charAt(current);
|
|
}
|
|
|
|
const match = (expected) => {
|
|
if (is_at_end()) {
|
|
return false;
|
|
}
|
|
if (source.charAt(current) !== expected) {
|
|
return false;
|
|
}
|
|
current++;
|
|
return true;
|
|
}
|
|
|
|
return scan_tokens();
|
|
}
|
|
|
|
export const error = (token, message) => {
|
|
if (token.type === EOF) {
|
|
return report(token.line, " at end", message);
|
|
} else {
|
|
return report(token.line, "", message);
|
|
}
|
|
}
|
|
|
|
export const report = (line, where, message) => {
|
|
return ("[line " + line + "] Error" + where + ": " + message);
|
|
}
|
|
|
|
export const LEFT_PAREN = 1;
|
|
export const RIGHT_PAREN = 2;
|
|
export const LEFT_BRACE = 3;
|
|
export const RIGHT_BRACE = 4;
|
|
export const COMMA = 5;
|
|
export const DOT = 6;
|
|
export const MINUS = 7;
|
|
export const PLUS = 8;
|
|
export const SEMICOLON = 9;
|
|
export const SLASH = 10;
|
|
export const STAR = 11;
|
|
export const BANG = 12;
|
|
export const BANG_EQUAL = 13;
|
|
export const EQUAL = 14;
|
|
export const EQUAL_EQUAL = 15;
|
|
export const GREATER = 16;
|
|
export const GREATER_EQUAL = 17;
|
|
export const LESS = 18;
|
|
export const LESS_EQUAL = 19;
|
|
export const IDENTIFIER = 20;
|
|
export const STRING = 21;
|
|
export const NUMBER = 22;
|
|
export const AND = 23;
|
|
export const ELSE = 24;
|
|
export const FALSE = 25;
|
|
export const FUN = 26;
|
|
export const FOR = 27;
|
|
export const IF = 28;
|
|
export const OR = 30;
|
|
export const PRINT = 31;
|
|
export const RETURN = 32;
|
|
export const TRUE = 33;
|
|
export const VAR = 34;
|
|
export const WHILE = 35;
|
|
export const EOF = 36;
|
|
export const START = 37;
|
|
export const GO = 38;
|
|
export const TURN = 39;
|
|
export const LEFT = 40;
|
|
export const RIGHT = 41;
|
|
export const PILLARS = 42;
|
|
export const MOVING_PILLARS = 43;
|
|
export const STAIRCASE = 44;
|
|
|
|
export const keywords = new Map([
|
|
["and", AND],
|
|
["else", ELSE],
|
|
["false", FALSE],
|
|
["fun", FUN],
|
|
["for", FOR],
|
|
["if", IF],
|
|
["or", OR],
|
|
["print", PRINT],
|
|
["return", RETURN],
|
|
["true", TRUE],
|
|
["var", VAR],
|
|
["while", WHILE],
|
|
["start", START],
|
|
["go", GO],
|
|
["turn", TURN],
|
|
["left", LEFT],
|
|
["right", RIGHT],
|
|
["pillars", PILLARS],
|
|
["moving_pillars", MOVING_PILLARS],
|
|
["staircase", STAIRCASE]
|
|
])
|