@specs-feup/lara
Version:
A js port of the popular framework for building source-to-source compilers
414 lines • 13.4 kB
JavaScript
import { LaraJoinPoint } from "../../LaraJoinPoint.js";
import { object2string } from "../../core/output.js";
import JavaTypes from "../util/JavaTypes.js";
export let LARA_DEBUG = false;
export function setDebug(value = true) {
LARA_DEBUG = value;
}
export function isDebug() {
return LARA_DEBUG;
}
export function notImplemented(functionName = "<unknown>") {
throw ("Function " +
functionName +
" not implemented yet for this weaver implementation");
}
/**
* Returns the value if defined or the provided default value. This useful for optional parameters of functions.
*
* @param value - the original value
* @param defaultValue - the default value
*
* @deprecated Use the ECMAScript 6 default parameter value syntax instead
*/
export function defaultValue(value, defaultValue) {
return value ?? defaultValue;
}
/**
* Temporary method that returns true if the given value is undefined or null,
* to be used while WeaverGenerator does not support returning undefined from Java.
*
* @deprecated Use the javascript `===` operator instead
*/
export function isUndefined(value) {
return value === undefined || value === null;
}
/**
* Throws an exception if the given expression evaluates to false.
*
* @deprecated Use the javascript `throw` statement instead
*/
export function checkTrue(booleanExpr, message = "checkTrue failed", source = "<unknown>") {
if (!booleanExpr) {
throw `${source}: ${message}`;
}
}
/**
* @deprecated Use the javascript '===' operator instead
*/
export function checkDefined(value, varName = "<unknown>", source = "<unknown>") {
if (value !== undefined) {
return;
}
throw `${source}: Value ${varName} is undefined`;
}
// TODO: type should be a JP
/**
* @deprecated Use the javascript `instanceof` operator or JavaTypes.instanceOf instead
*/
export function checkInstance(value, type, source, userTypeName) {
if (isJavaClass(value)) {
throw "Should not receive a Java object here";
}
if (typeof type !== "function") {
throw "LaraCore.checkInstance: parameter 'type' must be a function";
}
if (value instanceof type) {
return;
}
// Try to get name from type
let typeName = type.name;
// If no name, try to use user type name
if (typeName === undefined || typeName.length === 0) {
typeName = userTypeName;
}
// If still undefined, add placeholder
if (typeName === undefined) {
typeName = "<could not determine>";
}
let valueName = value.constructor.name;
if (valueName.length === 0) {
valueName = undefined;
}
let message = "Expected value to be of type '" + typeName + "'";
if (valueName !== undefined) {
message += ", but is of type '" + valueName + "'";
}
else {
message +=
", but is of another type. The code of the constructor function is:\n" +
value.constructor;
}
if (source !== undefined) {
message = source + ": " + message;
}
throw message;
}
export function checkType(value, type, source) {
if (typeof value === type) {
return;
}
// Special case: array
if (type === "array" && value instanceof Array) {
return;
}
// Special case: regex
if (type === "regex" && value instanceof RegExp) {
return;
}
let message = "Expected value to be of type '" +
type +
"', but is of type '" +
typeof value +
"'";
if (source !== undefined) {
message = source + ": " + message;
}
throw message;
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function checkBoolean(variable, source) {
checkType(variable, "boolean", source);
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function checkString(variable, source) {
checkType(variable, "string", source);
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function checkNumber(variable, source) {
checkType(variable, "number", source);
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function checkArray(variable, source) {
checkType(variable, "array", source);
}
/**
* @returns true if the given value is an array, false otherwise
*
* @deprecated Use the javascript `instanceof` operator instead
*/
export function isArray(value) {
return value instanceof Array;
}
/**
* @deprecated Use the javascript built-in methods instead
*/
export function toArray(objectWithLength) {
//return Array.prototype.slice.call(objectWithLength);
const newArray = [];
for (const index in objectWithLength) {
newArray.push(objectWithLength[index]);
}
return newArray;
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function isString(variable) {
return typeof variable === "string" || variable instanceof String;
}
/**
* @returns true if the given object is an instance of the given Java class name
*
* @deprecated Use JavaTypes.isJavaObject or JavaTypes.instanceOf instead
*/
export function isJavaClass(variable, javaClassname) {
if (javaClassname === undefined) {
return JavaTypes.isJavaObject(variable);
}
return JavaTypes.instanceOf(variable, javaClassname);
}
/**
* Converts an arguments object to a JavaScript array (Array).
*
* If there is a single argument after the start index and that argument is an array, that array will be returned.
* This helper is kept for Lara code using directly the `arguments` object. If you are using Javascript, consider using [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters](rest parameters) to extract the variadic arguments of your function, and using the `flattenArgsArray` function to perform the single-element flattening, if needed.
*
* @param args - The original arguments object.
* @param start - The index where we begin the conversion (inclusive).
* */
export function arrayFromArgs(args, start = 0) {
checkDefined(args, "args", "LaraCore arrayFromArgs");
if (start === undefined || start < 0) {
start = 0;
}
// If only one element and is already an array, just return the array
if (args.length === start + 1 && isArray(args[start])) {
return args[start];
}
if (args.length === start + 1 && isJavaList(args[start])) {
return toArray(args[start]);
}
return Array.prototype.slice.call(args, start);
}
function isJavaList(list) {
return JavaTypes.instanceOf(list, "java.util.List");
}
export function info(message, origin) {
if (origin !== undefined) {
console.log(`${origin}: ${message}`);
return;
}
console.log(message);
}
////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function isJoinPoint($jp, type) {
if (!($jp instanceof LaraJoinPoint) && $jp.getClass === undefined) {
return false;
}
if (type) {
return $jp.instanceOf(type);
}
return true;
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function checkJoinPoint($jp, source) {
if (isJoinPoint($jp)) {
return;
}
let message = "Expected variable to be of type join point, but it's of type '" +
typeof $jp +
"'";
if (source !== undefined) {
message = source + ": " + message;
}
throw message;
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function checkJoinPointType($jp, type, source) {
checkJoinPoint($jp, source);
if (isJoinPoint($jp, type)) {
return;
}
let message = "Expected variable to be a join point of type '" +
type +
"', but it's of type '" +
$jp.joinPointType +
"'";
if (source !== undefined) {
message = source + ": " + message;
}
throw message;
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function isObject(variable) {
return typeof variable === "object";
}
/**
* @deprecated Use the javascript `instanceof` operator instead
*/
export function isFunction(variable) {
return typeof variable === "function";
}
/**
* @param message - The message to print. Accepts functions, that are only evaluated if debug information is enabled. Use functions if the debug message can include expensive processing.
*/
export function debug(message, origin) {
if (LARA_DEBUG) {
if (message instanceof Function) {
message = message();
}
info("[DEBUG] " + message, origin);
}
}
export function debugObject(object, origin) {
if (LARA_DEBUG) {
const lines = object2string(object).split("\n");
info("[DEBUG] " + lines.join("\n[DEBUG] "), origin);
}
}
/**
* Flatten an Arguments array. In this context, it means that if the array only contains one element,
* and that element is an Array, it will be returned instead of the outer Array.
*
* @param args - Arguments array. Must be some array-like object.
* @returns Flattened argument array
*
* This method had been deprecated with the following justification, however, Node.JS is not handling
* well calling Java functions with variadic arguments, so this is still necessary.
*
* "This is implemented for compatibility reasons. As the Lara language used ES5 as its
* base, there was no spread operator to pass argument arrays to variadic functions, so there
* is calling code expecting to be able to pass a single array as the variadic argument."
*/
export function flattenArgsArray(args) {
if (args.length === 1) {
const singleArgument = args[0];
if (Array.isArray(singleArgument)) {
return singleArgument;
}
if (isJavaList(singleArgument)) {
return toArray(singleArgument);
}
}
// use Array.from to ensure it is an array, not some other Array-like variable
return Array.from(args);
}
/*
* Custom getter that is used as a compatibility layer between JS properties and Java methods.
*
* The name of this functions must be the same as the value of the field org.lara.interpreter.weaver.interf.JoinPoint.LARA_GETTER .
*/
export function laraGetter(object, property) {
if (isJavaClass(object)) {
const value = object[property];
// If type is function, assume it should be called without arguments
if (typeof value === "function") {
// Java object
if (isJavaClass(object) && !isUndefined(object.class)) {
// Special case: property 'class'
if (property === "class") {
return object.class;
}
return JavaTypes.SpecsSystem.invokeAsGetter(object, property);
}
// JS object
return value;
}
return value;
}
else {
for (let obj = object; obj !== null; obj = Object.getPrototypeOf(obj)) {
const descriptor = Object.getOwnPropertyDescriptor(obj, property);
if (descriptor !== undefined) {
if (Object.getOwnPropertyDescriptor(descriptor, "get")) {
return descriptor.get?.call?.(object);
}
else if (Object.getOwnPropertyDescriptor(descriptor, "value")) {
return descriptor.value;
}
else {
continue;
}
}
}
}
}
/**
* @returns an array with the keys of an object
*
* @deprecated Use the javascript built-in methods instead
*/
export function getKeys(object) {
const keys = [];
for (const key in object) {
keys.push(key);
}
return keys;
}
/**
* @returns an array with the values of an object
*
* @deprecated Use the javascript built-in methods instead
*/
export function getValues(object) {
const values = [];
for (const key in object) {
values.push(object[key]);
}
return values;
}
/**
* Acts as a constructor where you can pass the arguments object.
*
* @returns the constructed object, of the constructor if it could not be built.
*
* @deprecated Use the javascript built-in methods instead
*/
export function newObject(aClass, args) {
const obj = Object.create(aClass.prototype);
return aClass.apply(obj, args) || obj;
}
const _LARA_IMPORT_LOADED = new Set();
export function laraImport(importName) {
// Return if already loaded
if (_LARA_IMPORT_LOADED.has(importName)) {
debug(() => "laraImport: import " + importName + " already processed, ignoring");
return;
}
// Import
_LARA_IMPORT_LOADED.add(importName);
debug(() => "laraImport: importing " + importName);
// Check if Kleene Start
if (importName.endsWith(".*")) {
_laraImportKleeneStar(importName.substring(0, importName.length - 2));
}
// Simple import
else {
JavaTypes.LaraI.loadLaraImport(importName);
}
}
function _laraImportKleeneStar(packageName) {
const laraImports = JavaTypes.LaraI.getLaraImportInPackage(packageName);
for (const singleLaraImport of laraImports) {
laraImport(singleLaraImport);
}
}
//# sourceMappingURL=LaraCore.js.map