From 6401deccc40fc1b8ca80fd9228463c45ccb52dbd Mon Sep 17 00:00:00 2001 From: Wirlaburla Date: Sat, 20 Jul 2024 00:58:21 -0500 Subject: [PATCH] add fscript parser implementation (unused and unfinished) --- .../main/java/FirescriptFormatException.java | 20 ++ .../src/main/java/ReplacingInputStream.java | 137 +++++++++ firestar/src/main/java/Rizzo.java | 290 ++++++++++++++++-- 3 files changed, 414 insertions(+), 33 deletions(-) create mode 100644 firestar/src/main/java/FirescriptFormatException.java create mode 100644 firestar/src/main/java/ReplacingInputStream.java diff --git a/firestar/src/main/java/FirescriptFormatException.java b/firestar/src/main/java/FirescriptFormatException.java new file mode 100644 index 0000000..265c2ec --- /dev/null +++ b/firestar/src/main/java/FirescriptFormatException.java @@ -0,0 +1,20 @@ +/* + * Firestar Mod Manager + * Copyright (C) 2024 bonkmaykr + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + + +public class FirescriptFormatException extends Exception { } diff --git a/firestar/src/main/java/ReplacingInputStream.java b/firestar/src/main/java/ReplacingInputStream.java new file mode 100644 index 0000000..8771d03 --- /dev/null +++ b/firestar/src/main/java/ReplacingInputStream.java @@ -0,0 +1,137 @@ +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Created by simon on 8/29/17. + * Copyright 2017 Simon Haoran Liang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +public class ReplacingInputStream extends FilterInputStream { + + private Queue inQueue, outQueue; + private final byte[] search, replacement; + private boolean caseSensitive = true; + + public ReplacingInputStream(InputStream in, String search, String replacement) { + super(in); + + this.inQueue = new LinkedList<>(); + this.outQueue = new LinkedList<>(); + + this.search = search.getBytes(); + this.replacement = replacement.getBytes(); + } + + public ReplacingInputStream(InputStream in, String search, String replacement, boolean caseSensitive) { + super(in); + + this.inQueue = new LinkedList<>(); + this.outQueue = new LinkedList<>(); + + this.search = search.getBytes(); + this.replacement = replacement.getBytes(); + this.caseSensitive = caseSensitive; + } + + private boolean isMatchFound() { + Iterator iterator = inQueue.iterator(); + + for (byte b : search) { + if (!iterator.hasNext()) return false; + Integer i = iterator.next(); + if (caseSensitive && b != i) return false; + else if (Character.toLowerCase(b) != Character.toLowerCase(i.byteValue())) return false; + } + + return true; + } + + private void readAhead() throws IOException { + // Work up some look-ahead. + while (inQueue.size() < search.length) { + int next = super.read(); + inQueue.offer(next); + + if (next == -1) { + break; + } + } + } + + @Override + public int read() throws IOException { + // Next byte already determined. + + while (outQueue.isEmpty()) { + readAhead(); + + if (isMatchFound()) { + for (byte a : search) { + inQueue.remove(); + } + + for (byte b : replacement) { + outQueue.offer((int) b); + } + } else { + outQueue.add(inQueue.remove()); + } + } + + return outQueue.remove(); + } + + @Override + public int read(byte b[]) throws IOException { + return read(b, 0, b.length); + } + + // copied straight from InputStream inplementation, just needed to to use `read()` from this class + @Override + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int c = read(); + if (c == -1) { + return -1; + } + b[off] = (byte)c; + + int i = 1; + try { + for (; i < len ; i++) { + c = read(); + if (c == -1) { + break; + } + b[off + i] = (byte)c; + } + } catch (IOException ee) { + } + return i; + } +} \ No newline at end of file diff --git a/firestar/src/main/java/Rizzo.java b/firestar/src/main/java/Rizzo.java index 9bc4f47..18ea6c7 100644 --- a/firestar/src/main/java/Rizzo.java +++ b/firestar/src/main/java/Rizzo.java @@ -16,18 +16,212 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; +import java.util.Scanner; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.StringWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; public class Rizzo { + private Scanner scanner; - public void Rizzo(File infile) { - + public Rizzo(File infile) throws FileNotFoundException, FirescriptFormatException { + scanner = new Scanner(infile); + parseScript(null); } - public static String[] translateCommandline(String toProcess) { - if (toProcess == null || toProcess.length() == 0) { + private void parseScript(Object context) throws FirescriptFormatException { + while (scanner.hasNextLine()) { + String line = scanner.nextLine().trim(); + if (line.startsWith("#") || line.length() < 1) continue; + if (!parseArgs(translateCommandline(line), context)) break; + } + } + + private boolean parseArgs(String[] args, Object context) throws FirescriptFormatException { + if (args.length > 0) { + if (context == null) { + System.out.println("Parsing Command: " + Arrays.toString(args)); + if (args[0].equalsIgnoreCase("file")) { + File newCtx = new File(Main.inpath + "temp/" + args[1]); + System.out.println("Calling new parse: " + Arrays.toString(Arrays.copyOfRange(args, 2, args.length))); + parseArgs(Arrays.copyOfRange(args, 2, args.length), newCtx); + } + } else { + System.out.println("Parsing Command: " + Arrays.toString(args) + " with context: " + context.getClass().getName() + "@" + context.hashCode()); + if (args[0].equals("{")) { + System.out.println("New context parse: " + context.getClass().getName() + "@" + context.hashCode()); + parseScript(context); + } else if (args[0].equals("}")) { + System.out.println("Ending context block: " + context.getClass().getName() + "@" + context.hashCode()); + return false; + } else if (context instanceof File file) { + if (args[0].equalsIgnoreCase("delete")) { + System.out.println("Deleting: " + file.getPath()); + if (file.getAbsolutePath().startsWith(Main.inpath + "temp/")) + file.delete(); + } else if (args[0].equalsIgnoreCase("xml")) { + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + // Mmmm, love me some INVALID XML corrections + ReplacingInputStream ris = new ReplacingInputStream(new FileInputStream(file), "&", "&"); + Document doc = docBuilder.parse(ris); + parseArgs(Arrays.copyOfRange(args, 1, args.length), doc); + try { + FileOutputStream output = new FileOutputStream(file); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + + StreamResult result = new StreamResult(new StringWriter()); + DOMSource source = new DOMSource(doc); + + transformer.transform(source, result); + // Look ma, I'm breaking XML standards! + String xmlString = result.getWriter().toString() + .replace("&", "&") + .replace(" ", "\t") + .replace("", "\b") + .replace(" ", "\n") + .replace(" ", "\r") + .replace(" ", "\f"); + PrintStream ps = new PrintStream(output); + ps.print(xmlString); + ps.close(); + } catch (TransformerException ex) { + Logger.getLogger(Rizzo.class.getName()).log(Level.SEVERE, null, ex); + } + } catch (SAXException | IOException | ParserConfigurationException ex) { + Logger.getLogger(Rizzo.class.getName()).log(Level.SEVERE, null, ex); + } + } else if (args[0].equalsIgnoreCase("str") || args[0].equalsIgnoreCase("xstr")) { + try { + FileInputStream fis = new FileInputStream(file); + ByteArrayInputStream bais = new ByteArrayInputStream(fis.readAllBytes()); + fis.close(); + FileOutputStream fos = new FileOutputStream(file); + if (args[1].equalsIgnoreCase("append")) { + bais.transferTo(fos); + for (int s = 0; s < args[2].length(); s++) { + char c = args[2].charAt(s); + fos.write(c); + } + } else if (args[1].equalsIgnoreCase("replace") || args[1].equalsIgnoreCase("delete")) { + String replacement = ""; + if (args[1].equalsIgnoreCase("replace")) replacement = args[3]; + ReplacingInputStream ris; + if (args[0].equalsIgnoreCase("xstr")) ris = new ReplacingInputStream(bais, args[2], replacement, false); + else ris = new ReplacingInputStream(bais, args[2], replacement); + ris.transferTo(fos); + ris.close(); + } + fos.flush(); + fos.close(); + } catch (IOException ex) { + Logger.getLogger(Rizzo.class.getName()).log(Level.SEVERE, null, ex); + } + } + } else if (context instanceof Document document) { + if (args[0].equalsIgnoreCase("modify")) { + Element elem = traverse(document, args[1]); + parseArgs(Arrays.copyOfRange(args, 2, args.length), elem); + } else if (args[0].equalsIgnoreCase("create")) { + String newTag = args[1].substring(args[1].lastIndexOf(".")+1); + String newID = ""; + if (newTag.contains("#")) { + newID = newTag.substring(newTag.indexOf("#")+1); + newTag = newTag.substring(0, newTag.indexOf("#")); + } + Element newElem = document.createElement(newTag); + if (newID != null && newID.length() > 0) newElem.setAttribute("id", newID); + traverse(document, args[1].substring(0, args[1].lastIndexOf("."))).appendChild(newElem); + parseArgs(Arrays.copyOfRange(args, 2, args.length), newElem); + } else if (args[0].equalsIgnoreCase("delete")) { + Element elem = traverse(document, args[1]); + elem.getParentNode().removeChild(elem); + } + } else if (context instanceof Element element) { + if (args[0].equalsIgnoreCase("set")) { + if (args[1].equalsIgnoreCase("attribute")) + element.setAttribute(args[2], args[3]); + else if (args[1].equalsIgnoreCase("value")) + element.setNodeValue(args[2]); + } + } + } + } + return true; + } + + private Element traverse(Document doc, String selector) throws FirescriptFormatException { + if (selector == null || selector.length() == 0 || doc == null) { + return null; + } + String[] elems = selector.split("\\."); + Element parent = null; + for (String tag : elems) { + Element newParent = null; + int index = 0; + if (tag.contains("[")) { + index = Integer.parseInt(tag.substring(tag.indexOf("[")+1, tag.lastIndexOf("]"))); + tag = tag.substring(0, tag.indexOf("[")); + } + + String id = ""; + if (tag.contains("#")) { + id = tag.substring(tag.indexOf("#")+1); + tag = tag.substring(0, tag.indexOf("#")); + } + if (id.length() > 0) { + NodeList ns; + if (parent != null) ns = parent.getElementsByTagName(tag); + else ns = doc.getElementsByTagName(tag); + for (int i = 0; i < ns.getLength(); i++) { + Node n = ns.item(i); + if (((Element)n).getAttribute("id").equals(id)) { + newParent = (Element)n; + break; + } + } + } else { + if (parent != null) newParent = (Element)parent.getElementsByTagName(tag).item(index); + else newParent = (Element)doc.getElementsByTagName(tag).item(index); + } + if (newParent == null) throw new FirescriptFormatException(); + else parent = newParent; + } + return parent; + } + + private static String[] translateCommandline(String line) { + if (line == null || line.length() == 0) { return new String[0]; } @@ -35,53 +229,83 @@ public class Rizzo { final int inQuote = 1; final int inDoubleQuote = 2; int state = normal; - final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true); final ArrayList result = new ArrayList(); final StringBuilder current = new StringBuilder(); boolean lastTokenHasBeenQuoted = false; + boolean lastTokenWasEscaped = false; - while (tok.hasMoreTokens()) { - String nextTok = tok.nextToken(); + for (int i = 0; i < line.length(); i++) { + char nextTok = line.charAt(i); switch (state) { - case inQuote: - if ("\'".equals(nextTok)) { + case inQuote -> { + if (nextTok == '\\') { + lastTokenWasEscaped = true; + } else if (nextTok == '\'' && !lastTokenWasEscaped) { lastTokenHasBeenQuoted = true; state = normal; } else { - current.append(nextTok); - } - break; - case inDoubleQuote: - if ("\"".equals(nextTok)) { - lastTokenHasBeenQuoted = true; - state = normal; - } else { - current.append(nextTok); - } - break; - default: - if ("\'".equals(nextTok)) { - state = inQuote; - } else if ("\"".equals(nextTok)) { - state = inDoubleQuote; - } else if (" ".equals(nextTok)) { - if (lastTokenHasBeenQuoted || current.length() != 0) { - result.add(current.toString()); - current.setLength(0); + if (lastTokenWasEscaped) { + if (nextTok == 't') nextTok = '\t'; + if (nextTok == 'b') nextTok = '\b'; + if (nextTok == 'n') nextTok = '\n'; + if (nextTok == 'r') nextTok = '\r'; + if (nextTok == 'f') nextTok = '\f'; } - } else { current.append(nextTok); + lastTokenWasEscaped = false; + } + } + case inDoubleQuote -> { + if (nextTok == '\\') { + lastTokenWasEscaped = true; + } else if (nextTok == '\"' && !lastTokenWasEscaped) { + lastTokenHasBeenQuoted = true; + state = normal; + } else { + if (lastTokenWasEscaped) { + if (nextTok == 't') nextTok = '\t'; + if (nextTok == 'b') nextTok = '\b'; + if (nextTok == 'n') nextTok = '\n'; + if (nextTok == 'r') nextTok = '\r'; + if (nextTok == 'f') nextTok = '\f'; + } + current.append(nextTok); + lastTokenWasEscaped = false; + } + } + default -> { + switch (nextTok) { + case '\\' -> lastTokenWasEscaped = true; + case '\'' -> state = inQuote; + case '\"' -> state = inDoubleQuote; + case ' ' -> { + if (!lastTokenWasEscaped && (lastTokenHasBeenQuoted || current.length() != 0)) { + result.add(current.toString()); + current.setLength(0); + } + } + default -> { + if (lastTokenWasEscaped) { + if (nextTok == 't') nextTok = '\t'; + if (nextTok == 'b') nextTok = '\b'; + if (nextTok == 'n') nextTok = '\n'; + if (nextTok == 'r') nextTok = '\r'; + if (nextTok == 'f') nextTok = '\f'; + lastTokenWasEscaped = false; + } + current.append(nextTok); + } } lastTokenHasBeenQuoted = false; - break; + } } } if (lastTokenHasBeenQuoted || current.length() != 0) { result.add(current.toString()); } if (state == inQuote || state == inDoubleQuote) { - throw new RuntimeException("unbalanced quotes in " + toProcess); + throw new RuntimeException("unbalanced quotes in " + line); } return result.toArray(new String[result.size()]); } -} +} \ No newline at end of file