package com.sixtyfour.runner; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; import java.io.PrintWriter; import java.util.List; import java.util.Locale; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.UIManager; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.PlainDocument; import com.sixtyfour.Assembler; import com.sixtyfour.Basic; import com.sixtyfour.Loader; import com.sixtyfour.Logger; import com.sixtyfour.cbmnative.NativeCompiler; import com.sixtyfour.cbmnative.PlatformProvider; import com.sixtyfour.cbmnative.ProgressListener; import com.sixtyfour.cbmnative.Transformer; import com.sixtyfour.cbmnative.javascript.PlatformJs; import com.sixtyfour.cbmnative.mos6502.c64.Platform64; import com.sixtyfour.cbmnative.mos6502.vic20.Platform20; import com.sixtyfour.cbmnative.mos6502.x16.PlatformX16; import com.sixtyfour.cbmnative.powerscript.PlatformPs; import com.sixtyfour.config.CompilerConfig; import com.sixtyfour.config.MemoryConfig; import com.sixtyfour.extensions.x16.X16Extensions; import com.sixtyfour.parser.Preprocessor; import com.sixtyfour.parser.cbmnative.UnTokenizer; import com.sixtyfour.system.FileWriter; import com.sixtyfour.system.ProgramPart; /** * A simple UI that allows for compiling BASIC programs from the desktop. * * @author EgonOlsen * */ public class VisualMospeed { private JFrame frame; private JPanel panel; private JButton load; private JButton compile; private int selectedTarget = 0; private JComboBox<String> target; private JPanel box; private JLabel targetLabel; private PlatformProvider platform = null; private String appendix = ".prg"; private File file; private String[] code; private File lastDir; private long fileDate; /** * The main method. Just run this without any parameters. * * @param args */ public static void main(String[] args) { new VisualMospeed(); } /** * Creates a new visual compiler. */ public VisualMospeed() { setup(); } private void setup() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { // } frame = new JFrame("MOSpeed - Visual Compiler"); frame.setLayout(new BorderLayout()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); panel = new JPanel(); panel.setLayout(new GridLayout(1, 2, 15, 15)); load = new JButton(); load.setText("LOAD"); load.setActionCommand("load"); load.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if ("load".equals(e.getActionCommand())) { loadProgram(); } } }); compile = new JButton(); compile.setText("COMPILE"); compile.setEnabled(false); compile.setActionCommand("compile"); compile.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if ("compile".equals(e.getActionCommand())) { compile(); } } }); target = new JComboBox<String>(new String[] { "C64", "VIC20", "X16", "Javascript", "Powershell" }); target.setToolTipText("Select target platform!"); target.addActionListener((e) -> { selectedTarget = target.getSelectedIndex(); }); targetLabel = new JLabel("Select target platform:"); box = new JPanel(); box.setLayout(new BoxLayout(box, BoxLayout.PAGE_AXIS)); box.add(targetLabel); box.add(target); panel.add(box); panel.add(load); panel.add(compile); frame.add(panel); frame.pack(); frame.setSize(640, 80); frame.setLocationRelativeTo(null); frame.setVisible(true); } private void compile() { if (file != null && fileDate != 0) { if (file.lastModified() > fileDate) { Logger.log("File has changed, reloading..."); load(file); } } compile.setEnabled(false); load.setEnabled(false); target.setEnabled(false); new Thread() { public void run() { compileInternal(); } }.start(); } private void compileInternal() { try { JFrame outFrame; outFrame = new JFrame("MOSpeed - console output"); outFrame.setLayout(new BorderLayout()); outFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); Document doc = new PlainDocument(); JTextArea textArea = new JTextArea(40, 80); JScrollPane scrollPane = new JScrollPane(textArea); outFrame.setPreferredSize(new Dimension(800, 600)); outFrame.setSize(new Dimension(800, 600)); outFrame.add(scrollPane, BorderLayout.CENTER); textArea.setDocument(doc); outFrame.setVisible(true); ByteArrayOutputStream logStream = new ByteArrayOutputStream(); PrintStream consolePs = new PrintStream(logStream) { @Override public void println(String x) { try { doc.insertString(doc.getLength(), x + "\n", null); scrollDown(textArea); } catch (BadLocationException e) { // } } @Override public void print(String x) { try { doc.insertString(doc.getLength(), x, null); scrollDown(textArea); } catch (BadLocationException e) { // } } @Override public void println() { try { doc.insertString(doc.getLength(), "\n", null); scrollDown(textArea); } catch (BadLocationException e) { // } } private void scrollDown(JTextArea textArea) { try { textArea.setCaretPosition(textArea.getDocument().getLength()); } catch (Exception e) { // Pffft... } } }; DotPrintingProgressListener dpl = new DotPrintingProgressListener(consolePs); Logger.setPrintStream(consolePs); CompilerConfig conf = new CompilerConfig(); MemoryConfig memConfig = new MemoryConfig(); setPlatform(conf); Basic basic = new Basic(code); try { Logger.log("Checking source file..."); basic.compile(conf); } catch (Exception e) { Logger.log("\n!!! Error compiling BASIC program: " + e.getMessage()); return; } conf.setProgressListener(dpl); List<String> nCode = null; NativeCompiler nComp = NativeCompiler.getCompiler(); try { nCode = nComp.compile(conf, basic, memConfig, platform); } catch (Exception e) { Logger.log("\n!!! Error compiling: " + e.getMessage()); Logger.log("Error in line: " + nComp.getLastProcessedLine()); return; } Assembler assy = null; if (is6502Platform(platform)) { assy = new Assembler(nCode); try { assy.compile(conf); } catch (Exception e) { Logger.log("\n!!! Error running assembler: " + e.getMessage()); return; } } Logger.log("\nREADY!"); String targetFile = "++" + file.getName().replace(".BAS", "").replace(".bas", "").replace(".prg", "").replace(".PRG", "") + appendix; File file = saveProgram(targetFile); if (file != null) { writeTargetFiles(memConfig, file, nCode, assy, platform); } else { Logger.log("No file selected, nothing saved!"); } } catch (Exception e) { Logger.log(e); } finally { compile.setEnabled(true); load.setEnabled(true); target.setEnabled(true); } } private void writeTargetFiles(MemoryConfig memConfig, File targetFile, List<String> ncode, Assembler assy, PlatformProvider platform) { if (is6502Platform(platform)) { write6502(memConfig, targetFile, assy, platform); // Check out of memory on write time int se = memConfig.getStringEnd(); if (se <= 0) { se = platform.getBasicMemoryEndAddress(); } if (se >= 0) { ProgramPart part0 = assy.getProgram().getParts().get(0); if (part0.getAddress() <= se && part0.getEndAddress() > se) { Logger.log("\nWARNING: Compiled program's length exceeds memory limit: " + (part0.getEndAddress() + ">" + se)); } } } else if (platform instanceof PlatformJs) { writeJavascript(targetFile, ncode); } else if (platform instanceof PlatformPs) { writePowershell(targetFile, ncode); } else { Logger.log("\n!!! Unsupported platform: " + platform); return; } } private void writePowershell(File targetFile, List<String> ncode) { try (PrintWriter pw = new PrintWriter(targetFile)) { Logger.log("Writing target file: " + targetFile); for (String line : ncode) { pw.println(line); } } catch (Exception e) { Logger.log("Failed to write target file '" + targetFile + "': " + e.getMessage()); return; } } private void writeJavascript(File targetFile, List<String> ncode) { Transformer trsn = new PlatformJs().getTransformer(); try (PrintWriter pw = new PrintWriter(targetFile); PrintWriter cpw = new PrintWriter(targetFile.getPath().replace(".js", ".html"))) { Logger.log("Writing target files: " + targetFile); for (String line : ncode) { pw.println(line); } String[] parts = targetFile.getPath().replace("\\", "/").split("/"); for (String line : trsn.createCaller(parts[parts.length - 1])) { cpw.println(line); } } catch (Exception e) { Logger.log("Failed to write target file '" + targetFile + "': " + e.getMessage()); return; } } private void write6502(MemoryConfig memConfig, File targetFile, Assembler assy, PlatformProvider platform) { try { Logger.log("Writing target file: " + targetFile); FileWriter.writeAsPrg(assy.getProgram(), targetFile.getPath(), memConfig.getProgramStart() == -1 || (memConfig.getProgramStart() < platform.getMaxHeaderAddress() && memConfig.getProgramStart() >= platform.getBaseAddress() + 23), platform.getBaseAddress(), true); } catch (Exception e) { Logger.log("Failed to write target file '" + targetFile + "': " + e.getMessage()); return; } } private void setPlatform(CompilerConfig conf) { switch (selectedTarget) { case 0: platform = new Platform64(); appendix = ".prg"; break; case 1: platform = new Platform20(); appendix = ".prg"; break; case 2: platform = new PlatformX16(); Basic.registerExtension(new X16Extensions()); conf.setNonDecimalNumbersAware(true); conf.setConvertStringToLower(true); appendix = ".prg"; break; case 3: platform = new PlatformJs(); appendix = ".js"; break; case 4: platform = new PlatformPs(); appendix = ".ps1"; } } private boolean is6502Platform(PlatformProvider platform) { return platform instanceof Platform64 || platform instanceof Platform20 || platform instanceof PlatformX16; } private void loadProgram() { JFileChooser fc = new JFileChooser(); if (lastDir != null) { fc.setCurrentDirectory(lastDir); } int ret = fc.showOpenDialog(frame); if (ret == JFileChooser.CANCEL_OPTION || ret == JFileChooser.ERROR_OPTION) { compile.setEnabled(code != null && code.length > 0); return; } load(fc.getSelectedFile()); } private void load(File file) { this.file = file; fileDate = file.lastModified(); String srcFile = file.toString(); code = null; if (srcFile.toLowerCase(Locale.ENGLISH).endsWith(".prg")) { try { Logger.log("Looks like a PRG file, trying to convert it..."); byte[] data = Loader.loadBlob(srcFile); UnTokenizer unto = new UnTokenizer(); code = unto.getText(data, true).toArray(new String[0]); Logger.log("PRG file converted into ASCII, proceeding!"); srcFile = srcFile.replace(".prg", ".bas"); } catch (Exception e) { Logger.log("Failed to convert PRG file: " + e.getMessage()); Logger.log("Proceeding as if it was ASCII instead!"); } } if (code == null) { code = Loader.loadProgram(srcFile); } lastDir = file.getParentFile(); for (String line : code) { line = line.trim(); if (!line.isEmpty()) { if (!Character.isDigit(line.charAt(0))) { code = Preprocessor.convertToLineNumbers(code); JOptionPane.showMessageDialog(frame, "Program converted from labels to line numbers!"); } } break; } compile.setEnabled(true); frame.setTitle(file.getName()); } private File saveProgram(String targetFile) { Logger.log("Select file to save!"); JFileChooser fc = new JFileChooser(); if (lastDir != null) { fc.setCurrentDirectory(lastDir); } File f = new File(lastDir, targetFile); fc.setSelectedFile(f); int ret = fc.showSaveDialog(frame); if (ret == JFileChooser.CANCEL_OPTION || ret == JFileChooser.ERROR_OPTION) { compile.setEnabled(code != null && code.length > 0); return null; } File file = fc.getSelectedFile(); if (file.isFile()) { boolean ok = file.delete(); if (!ok) { Logger.log("Failed to delete file: " + file); } } return file; } private static class DotPrintingProgressListener implements ProgressListener { private PrintStream ps = null; private int cnt = 0; public DotPrintingProgressListener(PrintStream ps) { this.ps = ps; } @Override public void nextStep() { ps.print("*"); cnt++; if (cnt >= 80) { cnt = 0; ps.println(); } } @Override public void start() { // } @Override public void done() { ps.println(); } } }