about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-01-07 12:57:39 -0500
committerelioat <elioat@tilde.institute>2024-01-07 12:57:39 -0500
commitb07f98097974c471b62414fd1ebc3de16879ed1d (patch)
tree613397c4874b31744a5409df3a5f23a02d12417a
parent58b9ff47aa3a4ce56cb22a4dbb7f7d2e14c872e9 (diff)
downloadtour-b07f98097974c471b62414fd1ebc3de16879ed1d.tar.gz
*
-rw-r--r--js/scripting-lang/lang.js318
1 files changed, 318 insertions, 0 deletions
diff --git a/js/scripting-lang/lang.js b/js/scripting-lang/lang.js
new file mode 100644
index 0000000..11a7ee5
--- /dev/null
+++ b/js/scripting-lang/lang.js
@@ -0,0 +1,318 @@
+// 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',
+};
+
+// 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;
+        }
+
+        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;
+        }
+
+        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
+
+/* 
+// Define a variable
+let a = 10;
+
+// Assign a new value to a variable
+a = 20;
+
+// Define a function
+function add(x, y) {
+  return x + y;
+}
+
+// Call a function
+let b = add(a, 5);
+
+// If statement
+if (b == 25) {
+  b = b + 5;
+} else {
+  b = b - 5;
+}
+
+// Print the result
+console.log(b);
+*/
\ No newline at end of file