//-------------------------------------------------------------------------------------------------- // // @ CopyRight Roberti & Parau Enterprises, Inc. 2021-2023 // // This work is licensed under the Creative Commons Attribution-NoDerivatives 4.0 International License. // To view a copy of this license, visit http://creativecommons.org/licenses/by-nd/4.0/ // or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. // //-------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------- // // Macro class - used in a hash table for quick access // //-------------------------------------------------------------------------------------------------- package framework; import java.io.*; import java.math.BigInteger; import java.net.URISyntaxException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.*; import java.util.regex.*; import javax.script.ScriptException; import java.lang.NoSuchMethodException; import org.graalvm.polyglot.*; //import org.graalvm.polyglot.proxy.*; import java.util.concurrent.locks.*; public class DVMacros { // // Initialize - Get list of all macros // protected DVMacros(ArrayList dirList) throws IOException, URISyntaxException { // // Set JS engine // this.jsEngine= Engine.newBuilder().build(); // // Get all available macros // for (String dirName : dirList) { File dir= new File(dirName); File[] macroFileList= dir.listFiles(); for (File macroFile : macroFileList) this.addMacro(macroFile.getName(), macroFile, null, macroFile.getPath(), (int) macroFile.length(), this.userMacroSet); } // // Handle File case // URL location= DVUtil.getClassLocation(this.getClass()); if (! DVUtil.isClassInJar(this.getClass())) { File macroDir= null; try { macroDir= new File(Paths.get(Paths.get(location.toURI()).toString(), "framework/macros").toString()); } catch(Exception e) { System.err.println(DVUtil.formatException( "\n*** Software error - unable to read framework macro names from Java resource path [" + location.toString() + "]", e) + "\n"); throw e; } for (File macroFile : macroDir.listFiles()) { String macroName= macroFile.getName(); this.addMacro(macroName, macroFile, null, macroFile.getPath(), (int) macroFile.length(), this.frameworkMacroSet); } } // // Handle the JAR case // else { try { InputStream indexStream= this.getClass().getResourceAsStream("/framework/macros/index"); BufferedReader indexReader= new BufferedReader(new InputStreamReader(indexStream, StandardCharsets.UTF_8)); for (String macroName= indexReader.readLine(); macroName != null && ! macroName.equals(""); macroName= indexReader.readLine()) { String fullMacroName= "/framework/macros/" + macroName; InputStream macroStream= this.getClass().getResourceAsStream(fullMacroName); this.addMacro(macroName, null, macroStream, (macroStream == null ? "*** Unavailable ***" : "JAR: " + fullMacroName), 0, this.frameworkMacroSet); } } catch(Exception e) { System.err.println(DVUtil.formatException( "\n*** Software error - unable to read framework macro index from main JAR file", e) + "\n"); throw e; } } } // // Add architecture macros // protected boolean addArchitectureMacro(DVStatements.Statement owner, DVMacroJSApi jsApi, String architecture, String extension, String options, String abi, String os, String abiEnv, DVOpCodes archOpCodes, String archDirectory, DVOpCodes extOpCodes, String extDirectory) { // // Upper case all SETENV parm // this.archOpCodes= archOpCodes; this.archDirectory= archDirectory; this.extOpCodes= extOpCodes; this.extDirectory= extDirectory; // // Get list of macros // boolean okFlag= true; try { this.architectureLock.lock(); if (this.archMacroSet == null) { this.getArchMacros(owner, architecture, extension, true); if (this.archMacroSet == null) return false; if (this.extOpCodes != null) { this.getArchMacros(owner, architecture, extension, false); if (this.extMacroSet == null) return false; } } else if (! architecture.equals(this.architecture) || ! options.equals(this.options) || ! abi.equals(this.abi) || ! os.equals(this.os)) return (okFlag= owner.putErrorFalse("Multiple architectures error - macros already loaded for architecture [%s], " + "options [%s], abi [%s], os [%s]", this.architecture, this.options, this.abi, this.os)); } finally { if (okFlag && this.archMacroSet != null) { this.architecture= architecture; this.extension= extension; this.options= options; this.abi= abi; this.os= os; this.abiEnv= abiEnv; } this.architectureLock.unlock(); } return true; } // // Get architecture macros // protected void getArchMacros(DVStatements.Statement owner, String architecture, String extension, boolean arch) { // // Set up variables // String dirName= (arch ? "/arch/" + architecture + "/macros" : "/arch/" + architecture + "/ext/" + extension + "/macros"); DVOpCodes opCodes= (arch ? this.archOpCodes : this.extOpCodes); String type= (arch ? "architectural" : "architectural extension"); HashMap macroSet= new HashMap(1024); // // Handle File case // URL location= DVUtil.getClassLocation(opCodes.getClass()); if (! DVUtil.isClassInJar(opCodes.getClass())) { File macroDir= null; try { macroDir= new File(Paths.get(Paths.get(location.toURI()).toString(), dirName.substring(1)).toString()); } catch(Exception e) { owner.putError(DVUtil.formatException( "\n*** Software error - unable to read framework macro names from Java resource path [" + location.toString() + "]\nDirectory [" + dirName + "]", e) + "\n"); return; } for (File macroFile : macroDir.listFiles()) { String macroName= macroFile.getName(); this.addMacro(macroName, macroFile, null, macroFile.getPath(), (int) macroFile.length(), macroSet); } } // // Handle architecture macros in JAR // else { try { InputStream indexStream= opCodes.getClass().getResourceAsStream(dirName + "/index"); BufferedReader indexReader= new BufferedReader(new InputStreamReader(indexStream, StandardCharsets.UTF_8)); for (String macroName= indexReader.readLine(); macroName != null; macroName= indexReader.readLine()) { String fullMacroName= dirName + "/" + macroName; InputStream macroStream= opCodes.getClass().getResourceAsStream(fullMacroName); this.addMacro(macroName, null, macroStream, (macroStream == null ? "*** Unavailable ***" : "JAR: " + fullMacroName), 0, macroSet); } } catch(Exception e) { owner.putError(DVUtil.formatException( "\nUnable to read " + type + " macro index from JAR file [" + dirName + "]\n", e) + "\n"); return; } } if (arch) this.archMacroSet= macroSet; else this.extMacroSet= macroSet; } // // Add user/framework macro // private void addMacro(String macroName, File macroFile, InputStream macroInputStream, String macroLocation, int codeLength, HashMap macroSet) { macroName= macroName.toUpperCase(); if (! macroName.endsWith(".MAC")) return; macroName= macroName.substring(0, macroName.length()-4).toUpperCase(); if (! this.userMacroSet.containsKey(macroName)) macroSet.put(macroName, new Macro(macroName, macroFile, macroInputStream, macroLocation, codeLength)); } // // Add inline macro // protected void addInlineMacro(DVStatements.Statement owner, DVMacroJSApi jSApi, String macroLocation, String inlineCode) { Matcher matchHeader= this.matchHeader.matcher(inlineCode); if (! matchHeader.lookingAt()) { owner.putError("In line macro has an invalid header"); return; } Matcher matchMacroName= this.matchMacroName.matcher(inlineCode); matchMacroName.region(matchHeader.end(), inlineCode.length()); if (! matchMacroName.lookingAt()) { owner.putError("In line macro name is either missing or invalid"); return; } String macroName= matchMacroName.group("macroName").toUpperCase(); jSApi.addInlineMacro(owner, new Macro(macroName, macroLocation, inlineCode)); } // // Check if macro exists // protected Macro get(DVMacroJSApi jSApi, String macroName) { macroName= macroName.toUpperCase(); Macro macro; if ((macro= jSApi.getMacro(macroName)) != null) return macro; if ((macro= this.userMacroSet.get(macroName)) != null) return macro; if (this.extMacroSet != null && (macro= this.userMacroSet.get(macroName)) != null) return macro; if (this.archMacroSet != null && (macro= this.archMacroSet.get(macroName)) != null) return macro; return this.frameworkMacroSet.get(macroName); } // // Macro subclass - one instance for each macro // class Macro { // // Standard new macro interface // Macro(String macroName, File file, InputStream inputStream, String sourceLocation, int macroLength) { this.macroName= macroName; this.file= file; this.inputStream= inputStream; this.codeLength= macroLength; this.sourceLocation= sourceLocation; this.inlineCode= null; } // // Inline macro interface // Macro(String macroName, String sourceLocation, String inlineCode) { this.macroName= macroName; this.file= null; this.inputStream= null; this.sourceLocation= sourceLocation; this.inlineCode= inlineCode; } // // Execute macro - if unloaded read it from file and parse parm template // protected void execute(DVStatements.Statement owner, DVMacroJSApi jSApi, String evalParm) throws ScriptException { owner.genCode= new DVCode(null, "Macro [" + this.macroName + "] source location is [" + this.sourceLocation + "]"); // // Execute macro class // jSApi.startMacro(owner, this.macroName, evalParm, this.jsCode); try { jSApi.getContext().eval("js", this.jsCode); } catch (Exception e) { StackTraceElement[] stackTrace= e.getStackTrace(); String msg= e.getMessage() + "\nTrace back:"; for (int i= 0; i < stackTrace.length; i ++) { msg+= String.format("\n Line: %5d in method: %s", stackTrace[i].getLineNumber(), stackTrace[i].getMethodName()); if (stackTrace[i].getMethodName().startsWith("$_DVASM_$")) break; } throw new ScriptException(msg); } finally { jSApi.endMacro(); } } // // Read code and create parameter template // protected void read() { boolean parseCodeFlag= false; try { this.readLock.lock(); if (this.jsCode != null || this.permError != null) return; String tcode; if (this.inlineCode == null) { byte[] tbyte; if (this.file != null) { tbyte= new byte[this.codeLength]; InputStream sourceStream= new FileInputStream(this.file); for (int i= 0; i < this.codeLength; i= sourceStream.read(tbyte, i, this.codeLength-i)); sourceStream.close(); } else { tbyte= this.inputStream.readAllBytes(); this.inputStream.close(); } tcode= new String(tbyte); } else tcode= this.inlineCode; if (! tcode.endsWith("\n")) tcode+= "\n"; // // Scan for JS code delimiters // DVMacroHeaderParser headerParser= new DVMacroHeaderParser(new DVInputString(tcode)); headerParser.start(this); parseCodeFlag= true; tcode= convertEmbeddedCode(tcode); String funcName= "$_DVASM_$_" + this.macroName.replace(".", "_") + "_$$_"; this.jsCode= "function " + funcName + "() { const DVASM= __dvasm; " + "if (DVASM.getMacroName() != \"" + this.macroName + "\") {" + "DVASM.putError(\"Code of macro [" + this.macroName + "] invoked by code of macro [\" + DVASM.getMacroName() + \"]\"); " + "return; } " + "const BigInteger= Java.type(\"java.math.BigInteger\"); " + "const BigDecimal= Java.type(\"java.math.BigDecimal\"); " + "const Pattern= Java.type(\"java.util.regex.Pattern\"); " + "const Matcher= Java.type(\"java.util.regex.Matcher\"); " + "eval(DVASM.getEvalParm());\n" + tcode + "} " + funcName + "();\n"; } catch (ParseException | ScriptException e) { if (parseCodeFlag) this.permError= String.format("Error while parsing embedded code of macro [%s]\n%s", this.macroName, e.getMessage()); else this.permError= String.format("Error while parsing header of macro [%s]\n%s", this.macroName, e.getMessage().replace("", "")); } catch (IOException e) { this.permError= String.format( "I/O Error while reading macro [%s]\n%s", this.macroName, e.getMessage()); } finally { this.readLock.unlock(); } } // // Convert embedded code to a sequence of DVASM.formatLine JS statements // String convertEmbeddedCode(String code) throws ParseException, ScriptException { String retCode = ""; int start = code.indexOf('\n') + 1; int end; int n = 2; while (start < code.length()) { String newLine; end = code.indexOf('\n', start); if (end < 0) break; String line = code.substring(start, end); if (line.strip().equals("") || line.charAt(0) != '\\') newLine = line; else { DVMacroInlineParser parser = new DVMacroInlineParser(new DVInputString(line.substring(1))); parser.start(); if (!parser.error.equals("")) throw new ScriptException(String.format("Error found while parsing line[%d]\n%s", n, parser.error)); if ((parser.retString[0] + parser.retString[1] + parser.retString[2]).strip().equals("") && ! parser.retString[3].strip().equals("")) { int xs= 0; int pl= 0; String ln= line.substring(0, line.indexOf('/')); int xn; while ((xn= ln.indexOf('\t', xs)) >= 0) { pl= ((pl+xn-xs+4) >> 2) << 2; xs= xn+1; } pl+= ln.length()-xs; newLine= "DVASM.formatComm(\"" + parser.retString[3] + "\", " + Integer.toString(pl) + ");"; } else { if (parser.retString[1].startsWith("<")) { newLine = "DVASM.formatLeft(\""; parser.retString[1] = parser.retString[1].substring(1); } else newLine = "DVASM.formatLine(\""; newLine += parser.retString[0].strip() + "\", \"" + parser.retString[1].strip() + "\", \"" + parser.retString[2].strip() + "\", \"" + parser.retString[3].strip() + "\");"; } } retCode += newLine + "\n"; start = end + 1; n++; } return retCode; } protected void setPermError(String permError) { this.readLock.lock(); if (this.permError != null) this.permError= permError; this.readLock.unlock(); } // // Macro class fields // protected String macroName; protected final File file; protected final InputStream inputStream; protected int codeLength; protected final String sourceLocation; protected final String inlineCode; protected String jsCode= null; protected ArrayList posParm= new ArrayList(); protected Integer minPosParmSize= null; protected HashMap keyParm= new HashMap(); protected String permError= null; private final ReentrantLock readLock= new ReentrantLock(); } // // Return environment // protected String[] getEnv() { return new String[] { this.architecture, this.options, this.abi, this.os, this.abiEnv }; } // // Return engine // protected Engine getJSEngine() { return this.jsEngine; } // // Macros fields // private String architecture= null; private String options= null; private String abi= null; private String os= null; private String extension= null; private DVOpCodes archOpCodes= null; private String archDirectory= null; private DVOpCodes extOpCodes= null; private String extDirectory= null; private final HashMap userMacroSet= new HashMap(1024); private HashMap extMacroSet= null; private HashMap archMacroSet= null; private final HashMap frameworkMacroSet= new HashMap(1024); private String abiEnv= null; private final ReentrantLock architectureLock= new ReentrantLock(); private final Engine jsEngine; private final Pattern matchHeader= Pattern.compile( "(?:^//macro[\\s\\t]+)", Pattern.CASE_INSENSITIVE); private final Pattern matchMacroName= Pattern.compile( "(?:(?(([a-z_][0-9a-z_]*)([\\.][a-z_][0-9a-z_]*)*)" + ")[\\s\\t]+)", Pattern.CASE_INSENSITIVE); private static final BigInteger jsMaxInt= new BigInteger("+9007199254740991"); private static final BigInteger jsMinInt= new BigInteger("-9007199254740991"); }