arith2js-calculator

1.0.0 • Public • Published

Open in Codespaces

arith2js Calculator with Scope Analysis

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.

New Features: Scope Introduction

The calculator has been extended to support:

  1. 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
  2. Comma-Separated Expressions

    • Support for multiple expressions separated by commas
    • Evaluates each expression in sequence
  3. Print Function

    • Added print() functionality to output results
    • Properly handles complex number output formatting
  4. Scope Analysis

    • Detects and reports usage of undeclared variables
    • Provides line and column information for errors
    • Ensures variables are declared before use

Bug Fixes in This Version

Complex Number Operation Handling

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:

  1. 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
    }
  2. 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 */ }],
      };
    }
  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

Implementation Details

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

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:

1. Variable Declaration and Usage Tracking

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

2. AST Traversal

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));
    }

3. Error Reporting

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

Variable Assignment and Print

$ node bin/calc2js.mjs --run "a = 4+i, b = 2-2i, print(a*b)"
{ re: 10, im: -6 }

Error Detection (Undeclared Variables)

$ 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

Usage Before Declaration

$ 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] <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

Installation

From npm (as a package)

npm install arith2js-calculator

From Source

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
    

License

ISC

Package Sidebar

Install

npm i arith2js-calculator

Weekly Downloads

1

Version

1.0.0

License

ISC

Unpacked Size

62.5 kB

Total Files

13

Last publish

Collaborators

  • adriangrassin