about summary refs log blame commit diff stats
path: root/js/scripting-lang/lang.js
blob: f91f842d76a855d6579d687d7fa458d5411319e0 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                                                                                                                            
                           



















































































































                                                               





                                                       































































































                                                                                                       




                                                 






























































                                                                                                                               



                                   
 



                                      
 
                         
 

                                                    
 




                                
// The goal here is less to make anything useful...or even something that works, but to learn what parts an interpreted languages needs to have to function.

// Define the types of tokens
const TokenType = {
    NUMBER: 'NUMBER',
    PLUS: 'PLUS',
    IDENTIFIER: 'IDENTIFIER',
    ASSIGNMENT: 'ASSIGNMENT',
    FUNCTION: 'FUNCTION',
    LEFT_PAREN: 'LEFT_PAREN',
    RIGHT_PAREN: 'RIGHT_PAREN',
    LEFT_BRACE: 'LEFT_BRACE',
    RIGHT_BRACE: 'RIGHT_BRACE',
    SEMICOLON: 'SEMICOLON',
};

// Lexer
function lexer(input) {
    const tokens = [];
    let current = 0;

    while (current < input.length) {
        let char = input[current];

        if (/\d/.test(char)) {
            let value = '';
            while (/\d/.test(char)) {
                value += char;
                char = input[++current];
            }
            tokens.push({
                type: TokenType.NUMBER,
                value
            });
            continue;
        }

        if (char === '+') {
            tokens.push({
                type: TokenType.PLUS
            });
            current++;
            continue;
        }

        if (/[a-z]/i.test(char)) {
            let value = '';
            while (/[a-z]/i.test(char)) {
                value += char;
                char = input[++current];
            }
            tokens.push({
                type: TokenType.IDENTIFIER,
                value
            });
            continue;
        }

        if (char === ':') {
            tokens.push({
                type: TokenType.ASSIGNMENT
            });
            current++;
            continue;
        }

        if (char === '=') {
            tokens.push({
                type: TokenType.EQUAL
            });
            current++;
            continue;
        }

        if (input.slice(current, current + 2) === 'if') {
            tokens.push({
                type: TokenType.IF
            });
            current += 2;
            continue;
        }

        if (input.slice(current, current + 4) === 'else') {
            tokens.push({
                type: TokenType.ELSE
            });
            current += 4;
            continue;
        }

        if (char === '(') {
            tokens.push({
                type: TokenType.LEFT_PAREN
            });
            current++;
            continue;
        }

        if (char === ')') {
            tokens.push({
                type: TokenType.RIGHT_PAREN
            });
            current++;
            continue;
        }

        if (char === '{') {
            tokens.push({
                type: TokenType.LEFT_BRACE
            });
            current++;
            continue;
        }

        if (char === '}') {
            tokens.push({
                type: TokenType.RIGHT_BRACE
            });
            current++;
            continue;
        }

        if (input.slice(current, current + 8) === 'function') {
            tokens.push({
                type: TokenType.FUNCTION
            });
            current += 8;
            continue;
        }

        if (char === ';') {
            tokens.push({ type: TokenType.SEMICOLON });
            current++;
            continue;
        }

        current++;
    }

    return tokens;
}

// Parser
function parser(tokens) {
    let current = 0;

    function walk() {
        if (current >= tokens.length) {
            return null; // Return null when there are no more tokens
        }

        let token = tokens[current];

        if (token.type === TokenType.NUMBER) {
            current++;
            return {
                type: 'NumberLiteral',
                value: token.value,
            };
        }

        if (token.type === TokenType.PLUS) {
            current++;
            return {
                type: 'PlusExpression',
                left: walk(),
                right: walk(),
            };
        }

        if (token.type === TokenType.IDENTIFIER) {
            current++;
            return {
                type: 'Identifier',
                value: token.value,
            };
        }

        if (token.type === TokenType.ASSIGNMENT) {
            current++;
            return {
                type: 'AssignmentExpression',
                name: tokens[current - 2].value,
                value: walk(),
            };
        }

        if (token.type === TokenType.IF) {
            current++;
            let node = {
                type: 'IfExpression',
                test: walk(),
                consequent: walk(),
                alternate: tokens[current].type === TokenType.ELSE ? (current++, walk()) : null,
            };
            return node;
        }

        if (token.type === TokenType.FUNCTION) {
            current++;
            let node = {
                type: 'FunctionDeclaration',
                name: tokens[current++].value,
                params: [],
                body: [],
            };
            while (tokens[current].type !== TokenType.RIGHT_PAREN) {
                node.params.push(tokens[current++].value);
            }
            current++; // Skip right paren
            while (tokens[current].type !== TokenType.RIGHT_BRACE) {
                node.body.push(walk());
            }
            current++; // Skip right brace
            return node;
        }

        if (token.type === TokenType.IDENTIFIER && tokens[current + 1].type === TokenType.LEFT_PAREN) {
            current++;
            let node = {
                type: 'FunctionCall',
                name: token.value,
                args: [],
            };
            current++; // Skip left paren
            while (tokens[current].type !== TokenType.RIGHT_PAREN) {
                node.args.push(walk());
            }
            current++; // Skip right paren
            return node;
        }

        if (token.type === TokenType.SEMICOLON) {
            current++;
            return;
        }

        throw new TypeError(token.type);
    }

    let ast = {
        type: 'Program',
        body: [],
    };

    while (current < tokens.length) {
        const node = walk();
        if (node !== null) {
            ast.body.push(node);
        }
    }

    return ast;
}

// Interpreter
function interpreter(ast) {
    let globalScope = {};

    function evalNode(node) {
        switch (node.type) {
            case 'NumberLiteral':
                return parseInt(node.value);
            case 'PlusExpression':
                return evalNode(node.left) + evalNode(node.right);
            case 'AssignmentExpression':
                globalScope[node.name] = evalNode(node.value);
                return;
            case 'Identifier':
                return globalScope[node.value];
            case 'IfExpression':
                return evalNode(node.test) ? evalNode(node.consequent) : node.alternate ? evalNode(node.alternate) : undefined;
            case 'FunctionDeclaration':
                globalScope[node.name] = function() {
                    let localScope = Object.create(globalScope);
                    for (let i = 0; i < node.params.length; i++) {
                        localScope[node.params[i]] = arguments[i];
                    }
                    let lastResult;
                    for (let bodyNode of node.body) {
                        lastResult = evalNode(bodyNode);
                    }
                    return lastResult;
                };
                return;
            case 'FunctionCall':
                if (globalScope[node.name] instanceof Function) {
                    let args = node.args.map(evalNode);
                    return globalScope[node.name].apply(null, args);
                }
                throw new Error(`Function ${node.name} is not defined`);
            default:
                throw new Error(`Unknown node type: ${node.type}`);
        }
    }

    return evalNode(ast.body[0]);
}

// Usage
// const tokens = lexer('2 + 2');
// const ast = parser(tokens);
// const result = interpreter(ast);
// console.log(result); // 4

// const tokens2 = lexer('x : 2 + 2');
// const ast2 = parser(tokens2);
// const result2 = interpreter(ast2);
// console.log(result2); 

const fs = require('fs');

// Read the input from a file
const input = fs.readFileSync('input.txt', 'utf-8');

// Usage
const tokens = lexer(input);
const ast = parser(tokens);
const result = interpreter(ast);
console.log(result);