This project implements a transpiler that converts calculator expressions into executable JavaScript code. It's an extension of the previous arithmetic calculator that now supports complex numbers, min/max operators, and scope analysis features including variable declarations, assignments, and print operations.
The calculator has been extended to support:
-
Variable Declarations and Assignments
- Variables are automatically declared on first assignment
- Variables must be used only after declaration
- Variable names are prefixed with $ to avoid conflicts with JavaScript keywords
-
Comma-Separated Expressions
- Support for multiple expressions separated by commas
- Evaluates each expression in sequence
-
Print Function
- Added
print()
functionality to output results - Properly handles complex number output formatting
- Added
-
Scope Analysis
- Detects and reports usage of undeclared variables
- Provides line and column information for errors
- Ensures variables are declared before use
The major bug fixed in this version involved the handling of complex number operations. The issue was that the code was directly calling complex number methods (like sub
, mul
, div
) on JavaScript primitive numbers, which don't have these methods.
For example, code was being generated like this:
(4 - 2).sub(Complex(1)) // Error: 2.sub is not a function
The fix involved modifying the buildBinaryExpression
function in ast-build.js
to ensure that all operands are properly wrapped in Complex
objects before calling these methods:
// Fixed code generation for complex operations
Complex(4 - 2).sub(Complex(1)) // Correct: Wraps the result in a Complex object
Key changes included:
-
Updated operator handling condition:
// Only use regular binary expressions if both operands are simple numbers // AND operation is not arithmetic if (isSimpleNumericNode(left) && isSimpleNumericNode(right) && ['+', '-', '*', '/'].indexOf(op) === -1) { // Use regular binary expression }
-
Added special handling for binary expressions:
else if (left.type === "BinaryExpression") { // If left is a binary expression, wrap it in Complex() leftNode = { type: "CallExpression", callee: { type: "Identifier", name: "Complex" }, arguments: [{ /* binary expression */ }], }; }
-
Enhanced unary expression handling: The negation operator also needed similar fixes to properly wrap arguments in
Complex
objects.
These changes ensure that complex number methods are always called on Complex
objects, not on primitive JavaScript numbers.
In this implementation, variables use reference semantics when assigned complex numbers:
- When assigning a complex number to a variable, the variable holds a reference to the object
- This approach is faster and consumes less memory
- It's consistent with JavaScript's default behavior for objects
The transpiler treats variable assignments as references to objects, following JavaScript's native behavior for complex types:
// Example of reference semantics in the generated code
$a = Complex("2").add(Complex("3i"));
$b = $a; // $b now references the same object as $a
$b = Complex("9"); // $b now references a completely different object
When executed, each variable maintains its own identity:
print($b); // 9
print($a); // 2 + 3i
The scope analyzer is implemented in scope.js
using the ast-types
library for AST traversal and manipulation. It performs the following key functions:
Three key data structures are built during AST traversal:
let assigned = new Map(); // Maps variable names to their first assignment
let used = new Map(); // Tracks all variable usages
let usedBeforeDeclaration = new Map(); // Tracks variables used before declaration
The analyzer traverses the AST using the visitor pattern:
-
Assignment Expression Visitor: Detects variable declarations by tracking the first assignment to each variable
visitAssignmentExpression(path) { if (node.left.type === "Identifier" && name.startsWith("$")) { // Register the first assignment as a declaration assigned.set(name, node); } }
-
Identifier Visitor: Monitors variable usages and validates declarations
visitIdentifier(path) { // Skip if not a variable usage if (!name.startsWith("$")) return; // Check if the variable has been declared if (!assigned.has(name)) { // Record usage before declaration error usedBeforeDeclaration.set(name, [...]); } }
-
Program Visitor: Injects let declarations at the program start
visitProgram(path) { // Generate variable declarations let declared = Array.from(assigned.keys()) .map(name => variableDeclarator(identifier(name))); // Insert at program start path.get('body').unshift(variableDeclaration("let", declared)); }
When variables are used before declaration, the system generates clear error messages with precise location information:
Bad usage of variable "b" at line 2 column 3
This implementation enables proper variable scoping, ensuring that variables are declared before use while maintaining JavaScript's reference semantics for objects.
$ node bin/calc2js.mjs --run "a = 4+i, b = 2-2i, print(a*b)"
{ re: 10, im: -6 }
$ node bin/calc2js.mjs "a = 4+d+i, b = 2-2i, print(c)"
Not declared variable 'd' at line 1 column 7
Not declared variable 'c' at line 3 column 7
$ node bin/calc2js.mjs "a = 2, a-b, b = 3"
Bad usage of variable "b" at line 2 column 3
The transpiler follows a standard compiler architecture with these components:
-
Lexer (
lexer.l
): Defines tokens (numbers, identifiers, operators) -
Parser (
grammar.jison
): Defines grammar rules for expressions -
AST Building (
ast-build.js
): Constructs JavaScript AST from parsed expressions -
Scope Analysis (
scope.js
): Analyzes variable declarations and usages -
Support Library (
support-lib.js
): Provides utilities for complex numbers -
Transpiler (
transpile.js
): Coordinates the transpilation process -
CLI (
bin/calc2js.mjs
): Provides user interface to the transpiler
- Read the input file
- Parse the input to generate an AST
- Analyze scope to detect variable declarations and usages
- Check for undeclared or improperly used variables
- Identify support library dependencies
- Generate JavaScript code from the AST
- Run or write the generated code to an output file
Usage: calc2js [options] [command] <filename>
Transpile a Calc program to JavaScript
Options:
-V, --version output the version number
-r, --run run the generated code
-o, --output <filename> file in which to write the output
-v, --verbose show the generated code
-a, --ast show the AST
-s, --scope shows the scope map
-D, --definitions shows the variable definitions
-h, --help display help for command
Commands:
transpile [options] <filename> Transpiles the input to JavaScript
run [options] <filename> Transpiles and run the resulting JS code
npm install arith2js-calculator
git clone https://github.com/ULL-ESIT-PL-2425/arith2js-adrian-grassin-luis-alu0101349480.git
cd arith2js-adrian-grassin-luis-alu0101349480
npm install
Run tests:
npm test
Generate documentation and coverage:
npm run docs
View documentation:
npm run dev
To publish this package to npm:
- Update the package name and version in package.json
- Create an npm account if you don't have one
- Login to npm:
npm login
- Publish the package:
npm publish
ISC