diff options
author | elioat <elioat@tilde.institute> | 2024-01-07 12:57:39 -0500 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2024-01-07 12:57:39 -0500 |
commit | b07f98097974c471b62414fd1ebc3de16879ed1d (patch) | |
tree | 613397c4874b31744a5409df3a5f23a02d12417a | |
parent | 58b9ff47aa3a4ce56cb22a4dbb7f7d2e14c872e9 (diff) | |
download | tour-b07f98097974c471b62414fd1ebc3de16879ed1d.tar.gz |
*
-rw-r--r-- | js/scripting-lang/lang.js | 318 |
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 |