config: packages,json,shared (5 files)

This commit is contained in:
Richard Ward
2025-12-31 13:07:54 +00:00
parent 2a0e104fbd
commit 52775236cc
5 changed files with 714 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
{
"name": "Expression Tests",
"description": "Test all expression types in JSON script",
"tests": [
{
"name": "Binary expressions - addition",
"function": "all_expressions",
"args": [10, 5],
"expected": {
"sum": 15,
"diff": 5,
"product": 50,
"max": 10,
"bothPositive": true,
"message": "Sum: 15, Product: 50, Max: 10"
}
},
{
"name": "Binary expressions - negative numbers",
"function": "all_expressions",
"args": [-10, 5],
"expected": {
"sum": -5,
"diff": -15,
"product": -50,
"max": 5,
"bothPositive": false,
"message": "Sum: -5, Product: -50, Max: 5"
}
},
{
"name": "Binary expressions - equal values",
"function": "all_expressions",
"args": [7, 7],
"expected": {
"sum": 14,
"diff": 0,
"product": 49,
"max": 7,
"bothPositive": true,
"message": "Sum: 14, Product: 49, Max: 7"
}
},
{
"name": "All operators - basic arithmetic",
"function": "all_operators",
"args": [10, 3],
"expected": {
"arithmetic": {
"add": 13,
"subtract": 7,
"multiply": 30,
"divide": 3.3333333333333335,
"modulo": 1
},
"comparison": {
"equal": false,
"notEqual": true,
"lessThan": false,
"greaterThan": true,
"lessOrEqual": false,
"greaterOrEqual": true
},
"logical": {
"and": 3,
"or": 10,
"not": false
},
"unary": {
"negate": -10,
"plus": 10
}
}
},
{
"name": "All operators - equal values",
"function": "all_operators",
"args": [5, 5],
"expected": {
"arithmetic": {
"add": 10,
"subtract": 0,
"multiply": 25,
"divide": 1,
"modulo": 0
},
"comparison": {
"equal": true,
"notEqual": false,
"lessThan": false,
"greaterThan": false,
"lessOrEqual": true,
"greaterOrEqual": true
},
"logical": {
"and": 5,
"or": 5,
"not": false
},
"unary": {
"negate": -5,
"plus": 5
}
}
}
]
}

View File

@@ -0,0 +1,104 @@
{
"name": "Statement Tests",
"description": "Test all statement types in JSON script",
"tests": [
{
"name": "For-each loop with array",
"function": "all_statements",
"args": [[10, 20, 30, 40, 50]],
"expected": {
"count": 5,
"sum": 150,
"average": 30,
"error": null
}
},
{
"name": "For-each loop with empty array",
"function": "all_statements",
"args": [[]],
"expected": {
"count": 0,
"sum": 0,
"average": 0,
"error": null
}
},
{
"name": "For-each loop with single item",
"function": "all_statements",
"args": [[100]],
"expected": {
"count": 1,
"sum": 100,
"average": 100,
"error": null
}
},
{
"name": "Control flow - negative",
"function": "control_flow",
"args": [-5],
"expected": "negative"
},
{
"name": "Control flow - zero",
"function": "control_flow",
"args": [0],
"expected": "zero"
},
{
"name": "Control flow - small",
"function": "control_flow",
"args": [5],
"expected": "small"
},
{
"name": "Control flow - medium",
"function": "control_flow",
"args": [50],
"expected": "medium"
},
{
"name": "Control flow - large",
"function": "control_flow",
"args": [150],
"expected": "large"
},
{
"name": "Control flow - boundary (10)",
"function": "control_flow",
"args": [10],
"expected": "medium"
},
{
"name": "Control flow - boundary (100)",
"function": "control_flow",
"args": [100],
"expected": "large"
},
{
"name": "Data structures - no parameters",
"function": "data_structures",
"args": [],
"expected": {
"numbers": [1, 2, 3, 4, 5],
"person": {
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"active": true
},
"config": {
"server": {
"host": "localhost",
"port": 3000,
"protocol": "http"
},
"features": ["auth", "api", "dashboard"]
},
"extractedName": "John Doe"
}
}
]
}

View File

@@ -0,0 +1,241 @@
/**
* TypeScript type definitions for JSON Script Runtime Executor
*/
// Expression Types
export type Expression =
| StringLiteral
| NumberLiteral
| BooleanLiteral
| NullLiteral
| TemplateLiteral
| BinaryExpression
| LogicalExpression
| UnaryExpression
| ConditionalExpression
| MemberAccess
| CallExpression
| ObjectLiteral
| ArrayLiteral
| Reference;
export interface StringLiteral {
type: 'string_literal';
value: string;
}
export interface NumberLiteral {
type: 'number_literal';
value: number;
}
export interface BooleanLiteral {
type: 'boolean_literal';
value: boolean;
}
export interface NullLiteral {
type: 'null_literal';
}
export interface TemplateLiteral {
type: 'template_literal';
template: string;
}
export interface BinaryExpression {
type: 'binary_expression';
left: Expression | any;
operator: '+' | '-' | '*' | '/' | '%' | '==' | '===' | '!=' | '!==' | '<' | '>' | '<=' | '>=';
right: Expression | any;
}
export interface LogicalExpression {
type: 'logical_expression';
left: Expression | any;
operator: '&&' | '||' | '??' | 'and' | 'or';
right: Expression | any;
}
export interface UnaryExpression {
type: 'unary_expression';
operator: '!' | '-' | '+' | '~' | 'not' | 'typeof';
argument: Expression | any;
}
export interface ConditionalExpression {
type: 'conditional_expression';
test: Expression | any;
consequent: Expression | any;
alternate: Expression | any;
}
export interface MemberAccess {
type: 'member_access';
object: Expression | any;
property: string | Expression | any;
computed?: boolean;
optional?: boolean;
}
export interface CallExpression {
type: 'call_expression';
callee: string | Expression;
args?: Array<Expression | any>;
}
export interface ObjectLiteral {
type: 'object_literal';
properties: Record<string, Expression | any>;
}
export interface ArrayLiteral {
type: 'array_literal';
elements: Array<Expression | any>;
}
export type Reference = string; // $ref:path.to.value
// Statement Types
export type Statement =
| ConstDeclaration
| LetDeclaration
| Assignment
| IfStatement
| ReturnStatement
| TryCatch
| CallExpressionStatement
| ForEachLoop
| Comment;
export interface ConstDeclaration {
type: 'const_declaration';
name: string;
value: Expression | any;
}
export interface LetDeclaration {
type: 'let_declaration';
name: string;
value: Expression | any;
}
export interface Assignment {
type: 'assignment';
target: string | Reference;
value: Expression | any;
}
export interface IfStatement {
type: 'if_statement';
condition: Expression | any;
then?: Statement[];
else?: Statement[];
}
export interface ReturnStatement {
type: 'return';
value: Expression | any;
}
export interface TryCatch {
type: 'try_catch';
try: Statement[];
catch?: {
param?: string;
body: Statement[];
};
finally?: Statement[];
}
export interface CallExpressionStatement {
type: 'call_expression';
callee: string | Expression;
args?: Array<Expression | any>;
}
export interface ForEachLoop {
type: 'for_each_loop';
iterator: string;
iterable: Expression | any;
body?: Statement[];
}
export interface Comment {
type: 'comment';
text: string;
}
// Function Definition
export interface FunctionParameter {
name: string;
type: string;
description?: string;
optional?: boolean;
default?: any;
}
export interface FunctionDefinition {
id: string;
name: string;
description?: string;
async?: boolean;
exported?: boolean;
params?: FunctionParameter[];
returnType?: string;
body: Statement[];
}
// Constant Definition
export interface ConstantDefinition {
id: string;
name: string;
type: string;
value: any;
description?: string;
exported?: boolean;
}
// Script JSON Structure
export interface ScriptJSON {
schema_version: string;
package: string;
description?: string;
constants?: ConstantDefinition[];
functions: FunctionDefinition[];
}
// Execution Context
export interface ExecutionContext {
params: Record<string, any>;
local_vars: Record<string, any>;
constants: Record<string, any>;
imports: Record<string, any>;
functions: Record<string, any>;
catch?: Record<string, any>;
}
// Return Value
export interface ReturnValue {
type: 'return';
value: any;
}
// Main API
export function resolveRef(ref: any, context: ExecutionContext): any;
export function evalExpression(expr: Expression | any, context: ExecutionContext): any;
export function executeStatement(stmt: Statement, context: ExecutionContext): ReturnValue | null;
export function executeFunction(scriptJson: ScriptJSON, functionName: string, args?: any[]): any;
export function loadAndExecute(jsonPath: string, functionName: string, args?: any[]): Promise<any>;
export function loadAndExecuteSync(jsonPath: string, functionName: string, args?: any[]): any;
declare const _default: {
executeFunction: typeof executeFunction;
loadAndExecute: typeof loadAndExecute;
loadAndExecuteSync: typeof loadAndExecuteSync;
resolveRef: typeof resolveRef;
evalExpression: typeof evalExpression;
executeStatement: typeof executeStatement;
};
export default _default;

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env node
/**
* CLI Test Runner for JSON Script Tests
* Usage: node test_cli.js <script.json> <test-suite.json>
*/
const { runTestSuiteSync, formatResults } = require('./test_runner.js');
const path = require('path');
function main() {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: node test_cli.js <script.json> <test-suite.json>');
console.error('');
console.error('Example:');
console.error(' node test_cli.js ../../script.json ../../../json_script_example/tests/expressions.cases.json');
process.exit(1);
}
const scriptPath = path.resolve(args[0]);
const testPath = path.resolve(args[1]);
console.log('Running tests...');
console.log(`Script: ${scriptPath}`);
console.log(`Test Suite: ${testPath}`);
try {
const results = runTestSuiteSync(scriptPath, testPath);
const output = formatResults(results);
console.log(output);
// Exit with error code if tests failed
process.exit(results.failed > 0 ? 1 : 0);
} catch (error) {
console.error('\n❌ Test execution failed:');
console.error(error.message);
console.error(error.stack);
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,219 @@
/**
* JSON-based Test Runner for Script Executor
* Executes test cases defined in JSON format
*/
import { executeFunction } from './script_executor.js';
/**
* Deep equality comparison
*/
function deepEqual(a, b) {
if (a === b) return true;
if (a === null || b === null) return false;
if (typeof a !== typeof b) return false;
if (typeof a !== 'object') return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!keysB.includes(key)) return false;
if (!deepEqual(a[key], b[key])) return false;
}
return true;
}
/**
* Execute a single test case
* @param {Object} testCase - Test case definition
* @param {Object} scriptJson - Script JSON to test against
* @returns {Object} Test result
*/
function executeTestCase(testCase, scriptJson) {
const startTime = performance.now();
let passed = false;
let actualResult = null;
let error = null;
try {
actualResult = executeFunction(scriptJson, testCase.function, testCase.args || []);
// Check result
if (testCase.expected !== undefined) {
passed = deepEqual(actualResult, testCase.expected);
} else if (testCase.expectedError) {
passed = false; // Should have thrown
} else {
passed = true; // No assertion
}
} catch (err) {
error = err;
if (testCase.expectedError) {
// Check if error matches expected
passed = err.message.includes(testCase.expectedError);
} else {
passed = false;
}
}
const duration = performance.now() - startTime;
return {
name: testCase.name,
function: testCase.function,
passed,
duration,
actualResult,
expectedResult: testCase.expected,
error: error ? error.message : null
};
}
/**
* Execute test suite from JSON
* @param {Object} testSuite - Test suite definition
* @param {Object} scriptJson - Script JSON to test
* @returns {Object} Test results
*/
function executeTestSuite(testSuite, scriptJson) {
const results = {
suite: testSuite.name || 'Unnamed Suite',
description: testSuite.description,
total: 0,
passed: 0,
failed: 0,
duration: 0,
tests: []
};
const startTime = performance.now();
for (const testCase of (testSuite.tests || [])) {
const result = executeTestCase(testCase, scriptJson);
results.tests.push(result);
results.total++;
if (result.passed) {
results.passed++;
} else {
results.failed++;
}
}
results.duration = performance.now() - startTime;
return results;
}
/**
* Format test results as human-readable string
* @param {Object} results - Test results
* @returns {string} Formatted output
*/
function formatResults(results) {
const lines = [];
lines.push(`\n${'='.repeat(60)}`);
lines.push(`Test Suite: ${results.suite}`);
if (results.description) {
lines.push(`Description: ${results.description}`);
}
lines.push(`${'='.repeat(60)}\n`);
for (const test of results.tests) {
const icon = test.passed ? '✅' : '❌';
lines.push(`${icon} ${test.name}`);
lines.push(` Function: ${test.function}`);
lines.push(` Duration: ${test.duration.toFixed(2)}ms`);
if (!test.passed) {
lines.push(` Expected: ${JSON.stringify(test.expectedResult)}`);
lines.push(` Actual: ${JSON.stringify(test.actualResult)}`);
if (test.error) {
lines.push(` Error: ${test.error}`);
}
}
lines.push('');
}
lines.push(`${'='.repeat(60)}`);
lines.push(`Results: ${results.passed}/${results.total} passed (${results.failed} failed)`);
lines.push(`Total Duration: ${results.duration.toFixed(2)}ms`);
lines.push(`${'='.repeat(60)}\n`);
if (results.failed === 0) {
lines.push('🎉 All tests passed!');
} else {
lines.push(`${results.failed} test(s) failed.`);
}
return lines.join('\n');
}
/**
* Load and run test suite from files
* @param {string} scriptPath - Path to script.json
* @param {string} testPath - Path to test suite JSON
* @returns {Promise<Object>} Test results
*/
async function runTestSuite(scriptPath, testPath) {
const fs = await import('fs/promises');
const scriptContent = await fs.readFile(scriptPath, 'utf-8');
const scriptJson = JSON.parse(scriptContent);
const testContent = await fs.readFile(testPath, 'utf-8');
const testSuite = JSON.parse(testContent);
return executeTestSuite(testSuite, scriptJson);
}
/**
* Synchronous version for Node.js
*/
function runTestSuiteSync(scriptPath, testPath) {
if (typeof require !== 'undefined') {
const fs = require('fs');
const scriptContent = fs.readFileSync(scriptPath, 'utf-8');
const scriptJson = JSON.parse(scriptContent);
const testContent = fs.readFileSync(testPath, 'utf-8');
const testSuite = JSON.parse(testContent);
return executeTestSuite(testSuite, scriptJson);
}
throw new Error('Synchronous file loading not available in browser environment');
}
// Export for different module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
executeTestCase,
executeTestSuite,
formatResults,
runTestSuite,
runTestSuiteSync,
deepEqual
};
}
export {
executeTestCase,
executeTestSuite,
formatResults,
runTestSuite,
runTestSuiteSync,
deepEqual
};
export default {
executeTestCase,
executeTestSuite,
formatResults,
runTestSuite,
runTestSuiteSync,
deepEqual
};