Transpiler for calculator expressions with complex numbers, scope analysis and variable support
npm install arith2js-calculator
print() functionality to output results
sub, mul, div) on JavaScript primitive numbers, which don't have these methods.
javascript
(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:
`javascript
// Fixed code generation for complex operations
Complex(4 - 2).sub(Complex(1)) // Correct: Wraps the result in a Complex object
`
Key changes included:
1. Updated operator handling condition:
`javascript
// 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
}
`
2. Added special handling for binary expressions:
`javascript
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 / }],
};
}
`
3. 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.
Variable Assignment Semantics
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
$3
The transpiler treats variable assignments as references to objects, following JavaScript's native behavior for complex types:
`javascript
// 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
`
Scope Analysis Implementation
The scope analyzer is implemented in scope.js using the ast-types library for AST traversal and manipulation. It performs the following key functions:
$3
Three key data structures are built during AST traversal:
`javascript
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
`
$3
The analyzer traverses the AST using the visitor pattern:
- Assignment Expression Visitor: Detects variable declarations by tracking the first assignment to each variable
`javascript
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
`javascript
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
`javascript
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));
}
`
$3
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.
Usage Examples
$3
`
$ node bin/calc2js.mjs --run "a = 4+i, b = 2-2i, print(a*b)"
{ re: 10, im: -6 }
`
$3
`
$ 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
`
$3
`
$ node bin/calc2js.mjs "a = 2, a-b, b = 3"
Bad usage of variable "b" at line 2 column 3
`
Architecture
The transpiler follows a standard compiler architecture with these components:
1. Lexer (lexer.l): Defines tokens (numbers, identifiers, operators)
2. Parser (grammar.jison): Defines grammar rules for expressions
3. AST Building (ast-build.js): Constructs JavaScript AST from parsed expressions
4. Scope Analysis (scope.js): Analyzes variable declarations and usages
5. Support Library (support-lib.js): Provides utilities for complex numbers
6. Transpiler (transpile.js): Coordinates the transpilation process
7. CLI (bin/calc2js.mjs): Provides user interface to the transpiler
Transpilation Process
1. Read the input file
2. Parse the input to generate an AST
3. Analyze scope to detect variable declarations and usages
4. Check for undeclared or improperly used variables
5. Identify support library dependencies
6. Generate JavaScript code from the AST
7. Run or write the generated code to an output file
Command Line Options
`
Usage: calc2js [options] [command]
Transpile a Calc program to JavaScript
Options:
-V, --version output the version number
-r, --run run the generated code
-o, --output 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] Transpiles the input to JavaScript
run [options] Transpiles and run the resulting JS code
`
Installation
$3
`
npm install arith2js-calculator
`
$3
`
git clone https://github.com/ULL-ESIT-PL-2425/arith2js-adrian-grassin-luis-alu0101349480.git
cd arith2js-adrian-grassin-luis-alu0101349480
npm install
`
Testing and Documentation
Run tests:
`
npm test
`
Generate documentation and coverage:
`
npm run docs
`
View documentation:
`
npm run dev
`
Publishing the Package
To publish this package to npm:
1. Update the package name and version in package.json
2. Create an npm account if you don't have one
3. Login to npm:
`
npm login
`
4. Publish the package:
`
npm publish
``