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] ])