From 36ab1b7c0570049475b678b0c027219dc1a34dcf Mon Sep 17 00:00:00 2001 From: Victor Date: Sun, 16 Mar 2014 23:33:45 +0200 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.xml | 95 ++ nbproject/build-impl.xml | 1413 +++++++++++++++++ nbproject/genfiles.properties | 8 + nbproject/project.properties | 73 + nbproject/project.xml | 15 + src/com/annimon/asm/AnalyzerPanel.java | 141 ++ src/com/annimon/asm/DirectiveConverter.java | 99 ++ src/com/annimon/asm/LexicLine.java | 16 + src/com/annimon/asm/LexicTable.java | 47 + src/com/annimon/asm/LexicalAnalyzer.java | 112 ++ .../annimon/asm/ListingGenerateHelper.java | 107 ++ src/com/annimon/asm/ListingGenerator.java | 63 + src/com/annimon/asm/Main.java | 22 + src/com/annimon/asm/SyntaxAnalyzer.java | 44 + src/com/annimon/asm/Var.java | 25 + src/com/annimon/asm/VarTable.java | 52 + src/com/annimon/asm/directives/Add.java | 167 ++ .../annimon/asm/directives/ByteRegister.java | 28 + src/com/annimon/asm/directives/ByteValue.java | 27 + src/com/annimon/asm/directives/Comma.java | 13 + src/com/annimon/asm/directives/DB.java | 13 + src/com/annimon/asm/directives/DW.java | 13 + src/com/annimon/asm/directives/Directive.java | 29 + src/com/annimon/asm/directives/ID.java | 33 + .../asm/directives/IListingGenerator.java | 20 + .../asm/directives/ISyntaxChecker.java | 20 + src/com/annimon/asm/directives/Idiv.java | 77 + .../annimon/asm/directives/InfinityValue.java | 27 + src/com/annimon/asm/directives/Mul.java | 77 + .../annimon/asm/directives/NumericValue.java | 26 + src/com/annimon/asm/directives/Pop.java | 64 + src/com/annimon/asm/directives/Push.java | 65 + src/com/annimon/asm/directives/Register.java | 25 + src/com/annimon/asm/directives/Variable.java | 61 + .../annimon/asm/directives/WordRegister.java | 27 + src/com/annimon/asm/directives/WordValue.java | 27 + .../exceptions/CommaExpectedException.java | 12 + .../exceptions/ExceptionWithLineNumber.java | 21 + .../asm/exceptions/FewArgumentsException.java | 16 + .../exceptions/TooManyArgumentsException.java | 16 + .../exceptions/WrongArgumentException.java | 12 + src/test.asm | 20 + 42 files changed, 3268 insertions(+) create mode 100644 build.xml create mode 100644 nbproject/build-impl.xml create mode 100644 nbproject/genfiles.properties create mode 100644 nbproject/project.properties create mode 100644 nbproject/project.xml create mode 100644 src/com/annimon/asm/AnalyzerPanel.java create mode 100644 src/com/annimon/asm/DirectiveConverter.java create mode 100644 src/com/annimon/asm/LexicLine.java create mode 100644 src/com/annimon/asm/LexicTable.java create mode 100644 src/com/annimon/asm/LexicalAnalyzer.java create mode 100644 src/com/annimon/asm/ListingGenerateHelper.java create mode 100644 src/com/annimon/asm/ListingGenerator.java create mode 100644 src/com/annimon/asm/Main.java create mode 100644 src/com/annimon/asm/SyntaxAnalyzer.java create mode 100644 src/com/annimon/asm/Var.java create mode 100644 src/com/annimon/asm/VarTable.java create mode 100644 src/com/annimon/asm/directives/Add.java create mode 100644 src/com/annimon/asm/directives/ByteRegister.java create mode 100644 src/com/annimon/asm/directives/ByteValue.java create mode 100644 src/com/annimon/asm/directives/Comma.java create mode 100644 src/com/annimon/asm/directives/DB.java create mode 100644 src/com/annimon/asm/directives/DW.java create mode 100644 src/com/annimon/asm/directives/Directive.java create mode 100644 src/com/annimon/asm/directives/ID.java create mode 100644 src/com/annimon/asm/directives/IListingGenerator.java create mode 100644 src/com/annimon/asm/directives/ISyntaxChecker.java create mode 100644 src/com/annimon/asm/directives/Idiv.java create mode 100644 src/com/annimon/asm/directives/InfinityValue.java create mode 100644 src/com/annimon/asm/directives/Mul.java create mode 100644 src/com/annimon/asm/directives/NumericValue.java create mode 100644 src/com/annimon/asm/directives/Pop.java create mode 100644 src/com/annimon/asm/directives/Push.java create mode 100644 src/com/annimon/asm/directives/Register.java create mode 100644 src/com/annimon/asm/directives/Variable.java create mode 100644 src/com/annimon/asm/directives/WordRegister.java create mode 100644 src/com/annimon/asm/directives/WordValue.java create mode 100644 src/com/annimon/asm/exceptions/CommaExpectedException.java create mode 100644 src/com/annimon/asm/exceptions/ExceptionWithLineNumber.java create mode 100644 src/com/annimon/asm/exceptions/FewArgumentsException.java create mode 100644 src/com/annimon/asm/exceptions/TooManyArgumentsException.java create mode 100644 src/com/annimon/asm/exceptions/WrongArgumentException.java create mode 100644 src/test.asm diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..47f60c4 --- /dev/null +++ b/build.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + Builds, tests, and runs the project AsmCompiler. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..456e20f --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..a6097e7 --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=3cfaa78e +build.xml.script.CRC32=02db17b9 +build.xml.stylesheet.CRC32=28e38971@1.56.0.46 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=3cfaa78e +nbproject/build-impl.xml.script.CRC32=ad298f4f +nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.0.48 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..bb5259b --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,73 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=AsmCompiler +application.vendor=aNNiMON +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/AsmCompiler.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.7 +javac.target=1.7 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=com.annimon.asm.Main +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=windows-1251 +src.dir=src +test.src.dir=test diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..8c1c1b6 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + AsmCompiler + + + + + + + + + diff --git a/src/com/annimon/asm/AnalyzerPanel.java b/src/com/annimon/asm/AnalyzerPanel.java new file mode 100644 index 0000000..3c25b7a --- /dev/null +++ b/src/com/annimon/asm/AnalyzerPanel.java @@ -0,0 +1,141 @@ +package com.annimon.asm; + +import com.annimon.asm.exceptions.ExceptionWithLineNumber; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.JTextComponent; +import javax.swing.text.Utilities; + +/** + * Основная панель приложения. + * @author aNNiMON + */ +public class AnalyzerPanel extends JPanel { + + private JLabel lineNumberLabel; + private JTextPane textPane; + private JButton analyzeButton; + + public AnalyzerPanel() { + lineNumberLabel = new JLabel("Line: 1"); + initTextPane(); + initAnalyzeButton(); + + setLayout(new BorderLayout(0, 0)); + add(lineNumberLabel, BorderLayout.NORTH); + add(new JScrollPane(textPane)); + add(analyzeButton, BorderLayout.SOUTH); + } + + public AnalyzerPanel(String text) { + textPane = new JTextPane(); + textPane.setPreferredSize(new Dimension(300, 300)); + textPane.setFont(new Font("Consolas", Font.PLAIN, 14)); + textPane.setText(text); + + setLayout(new BorderLayout(0, 0)); + add(new JScrollPane(textPane)); + } + + private void initTextPane() { + textPane = new JTextPane(); + textPane.setPreferredSize(new Dimension(300, 350)); + textPane.addCaretListener(new CaretListener() { + + @Override + public void caretUpdate(CaretEvent caretEvent) { + // Вывести позицию каретки в lineNumberLabel. + JTextComponent textComponent = (JTextComponent)caretEvent.getSource(); + + int pos = caretEvent.getDot(); + int rowNumber = (pos == 0) ? 1 : 0; + try { + int offset = pos; + while (offset > 0) { + offset = Utilities.getRowStart(textComponent, offset) - 1; + rowNumber++; + } + } catch (BadLocationException ex) {} + + lineNumberLabel.setText("Line: " + rowNumber); + } + }); + textPane.setFont(new Font("Consolas", Font.PLAIN, 14)); + textPane.setText(getTextFromResource("/test.asm")); + } + + private void initAnalyzeButton() { + analyzeButton = new JButton("Analyze"); + analyzeButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent actionEvent) { + String allText = textPane.getText(); + + // Лексический анализ - переводим набор директив в кодовую таблицу. + LexicalAnalyzer lex = new LexicalAnalyzer(allText); + lex.analyze(); + + // Синтаксический анализ - проверка правильности набора директив. + SyntaxAnalyzer syn = new SyntaxAnalyzer(lex.getLexicTable()); + try { + syn.analyze(); + } catch (ExceptionWithLineNumber ex) { + showMessageBox("Error", ex.getMessage(), true); + return; + } + + //showMessageBox("Info","Source is correct!", false); + // Генерируем листинг. + ListingGenerator gen = new ListingGenerator(lex.getLexicTable(), lex.getVarTable(), lex.getLines()); + String listing = gen.generate(); + + showListing(listing); + } + }); + } + + private void showMessageBox(String title, String text, boolean errorMessage) { + int msgType = errorMessage ? JOptionPane.ERROR_MESSAGE : + JOptionPane.INFORMATION_MESSAGE; + JOptionPane.showMessageDialog(this, text, title, msgType); + } + + private void showListing(String listing) { + JOptionPane.showMessageDialog(this, new AnalyzerPanel(listing), + "Listing", JOptionPane.PLAIN_MESSAGE); + } + + private String getTextFromResource(String res) { + try { + InputStream is = getClass().getResourceAsStream(res); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + StringBuilder text = new StringBuilder(); + String line; + while( (line = reader.readLine()) != null ) { + text.append(line).append(System.lineSeparator()); + } + + reader.close(); + return text.toString(); + } catch (IOException ex) {} + return ""; + } +} diff --git a/src/com/annimon/asm/DirectiveConverter.java b/src/com/annimon/asm/DirectiveConverter.java new file mode 100644 index 0000000..da6c017 --- /dev/null +++ b/src/com/annimon/asm/DirectiveConverter.java @@ -0,0 +1,99 @@ +package com.annimon.asm; + +import com.annimon.asm.directives.*; +import java.util.ArrayList; + +/** + * Конвертер директив. + * @author aNNiMON + */ +public class DirectiveConverter { + + private static final Directive[] DIRECTIVES = { + new Comma(), + new DB(), new DW(), + new InfinityValue(), new ByteValue(), new WordValue(), + new Add(), new Mul(), new Push(), new Pop(), new Idiv(), + new ByteRegister(), new WordRegister(), + new Variable() + }; + + /** + * Конвертировать директиву в её идентификатор. + * @param text директива (dw, push, 0 и т.д.) + * @return идентификатор директивы. + */ + public static int convert(String text) { + for (int i = 0; i < DIRECTIVES.length; i++) { + if (DIRECTIVES[i].isDirective(text)) { + return DIRECTIVES[i].getId(); + } + } + + return -1; + } + + /** + * Получить значение числа из строки. + * @param text строка с числом. + * @return Integer число. + */ + public static Integer parseInteger(String text) { + int value; + if (text.toLowerCase().endsWith("h")) { + char first = Character.toLowerCase(text.charAt(0)); + if ( (first >= 'a') && (first <= 'f') ) { + // Первый символ hex числа не должен быть от a до f + return null; + } + + String hex = text.substring(0, text.length() - 1); + try { + value = Integer.parseInt(hex, 16); + return value; + } catch (NumberFormatException nfe) { + return null; + } + } + try { + value = Integer.parseInt(text); + return value; + } catch (NumberFormatException nfe) { + return null; + } + } + + /** + * Разбить строку на подстроки с учётом запятых. + * @param text текст. + * @return массив строк. + */ + public static String[] split(String text) { + ArrayList parts = new ArrayList<>(); + int length = text.length(); + StringBuilder sb = new StringBuilder(); + for (int ch = 0; ch < length; ch++) { + int i = text.charAt(ch); + if (i == ' ') { + if (sb.length() > 0) { + parts.add(sb.toString().trim()); + sb.setLength(0); + } + } else if (i == ',') { + if (sb.length() > 0) { + parts.add(sb.toString().trim()); + parts.add(","); + sb.setLength(0); + } + } else { + sb.append((char) i); + } + } + if (sb.length() > 0) { + parts.add(sb.toString().trim()); + } + String[] m = new String[parts.size()]; + m = parts.toArray(m); + return m; + } +} diff --git a/src/com/annimon/asm/LexicLine.java b/src/com/annimon/asm/LexicLine.java new file mode 100644 index 0000000..935e77a --- /dev/null +++ b/src/com/annimon/asm/LexicLine.java @@ -0,0 +1,16 @@ +package com.annimon.asm; + +/** + * + * @author aNNiMON + */ +public class LexicLine { + + int lineNumber; + int[] line; + + public LexicLine(int lineNumber, int[] line) { + this.lineNumber = lineNumber; + this.line = line; + } +} diff --git a/src/com/annimon/asm/LexicTable.java b/src/com/annimon/asm/LexicTable.java new file mode 100644 index 0000000..e01828c --- /dev/null +++ b/src/com/annimon/asm/LexicTable.java @@ -0,0 +1,47 @@ +package com.annimon.asm; + +import java.util.ArrayList; + +/** + * @author aNNiMON + */ +public class LexicTable { + + private ArrayList lexicLines; + + public LexicTable() { + lexicLines = new ArrayList<>(); + } + + public void addLexicLine(int lineNumber, int[] line) { + lexicLines.add(new LexicLine(lineNumber, line)); + } + + public int getSize() { + return lexicLines.size(); + } + + public LexicLine getLexicAt(int pos) { + return lexicLines.get(pos); + } + + public void updateLexicAt(int pos, LexicLine line) { + lexicLines.set(pos, line); + } + + @Override + public String toString() { + StringBuilder text = new StringBuilder(); + for(LexicLine lexic : lexicLines) { + text.append(lexic.lineNumber).append(':'); + int[] array = lexic.line; + for (int i = 0; i < array.length; i++) { + text.append(" "); + text.append(array[i]); + } + text.append(System.lineSeparator()); + } + return text.toString(); + } + +} diff --git a/src/com/annimon/asm/LexicalAnalyzer.java b/src/com/annimon/asm/LexicalAnalyzer.java new file mode 100644 index 0000000..1525e69 --- /dev/null +++ b/src/com/annimon/asm/LexicalAnalyzer.java @@ -0,0 +1,112 @@ +package com.annimon.asm; + +import com.annimon.asm.directives.ID; + +/** + * Лексический анализатор. + * @author aNNiMON + */ +public class LexicalAnalyzer { + + private String[] lines; + private LexicTable lexicTable; + private VarTable varTable; + + public LexicalAnalyzer(String text) { + lines = text.split(System.lineSeparator()); + + lexicTable = new LexicTable(); + varTable = new VarTable(); + } + + public LexicTable getLexicTable() { + return lexicTable; + } + + public VarTable getVarTable() { + return varTable; + } + + public String[] getLines() { + return lines; + } + + public void analyze() { + for (int i = 0; i < lines.length; i++) { + analyzeLine(i); + } + analyzeVariables(); + System.out.println(lexicTable.toString()); + } + + private void analyzeLine(int lineNumber) { + String line = lines[lineNumber]; + // Если строка не содержит команд - пропускаем. + if (line.isEmpty()) return; + String[] parts = DirectiveConverter.split(line); + + // Конвертируем директивы в соответствующие им идентификаторы. + int length = parts.length; + int[] lexicIds = new int[length]; + for (int i = 0; i < length; i++) { + lexicIds[i] = DirectiveConverter.convert(parts[i].trim()); + } + + // Добавляем полученную строку в таблицу. + lexicTable.addLexicLine(lineNumber+1, lexicIds); + } + + private void analyzeVariables() { + // Добавляем переменные в таблицу. + for (int i = 0; i < lexicTable.getSize(); i++) { + LexicLine line = lexicTable.getLexicAt(i); + addVariableToTable(line); + } + // Сопоставляем типы переменных. + for (int i = 0; i < lexicTable.getSize(); i++) { + LexicLine line = lexicTable.getLexicAt(i); + line = assignVariableTypes(line); + lexicTable.updateLexicAt(i, line); + } + } + + private void addVariableToTable(LexicLine lexicLine) { + if (lexicLine.line[0] != ID.VAR) return; + if (lexicLine.line.length != 3) return; + + String line = lines[lexicLine.lineNumber - 1]; + // Если строка не содержит команд - пропускаем. + if (line.isEmpty()) return; + String[] parts = DirectiveConverter.split(line); + if (parts.length != 3) return; + + String varName = parts[0].trim(); + int typeId = lexicLine.line[1]; + String value = parts[2].trim(); + + varTable.addVariable(varName, typeId, value); + } + + private LexicLine assignVariableTypes(LexicLine lexicLine) { + // Для переменных назначаем их тип. + int[] lexicIds = lexicLine.line; + + for (int i = 0; i < lexicIds.length; i++) { + if (lexicIds[i] == ID.VAR) { + // Назначаем тип из таблицы переменных. + String line = lines[lexicLine.lineNumber - 1]; + if (line.isEmpty()) continue; + String[] parts = DirectiveConverter.split(line); + if (parts.length != lexicIds.length) continue; + + lexicIds[i] = varTable.getTypeOfVariable(parts[i]); + } + } + lexicLine.line = lexicIds; + + return lexicLine; + } + + + +} diff --git a/src/com/annimon/asm/ListingGenerateHelper.java b/src/com/annimon/asm/ListingGenerateHelper.java new file mode 100644 index 0000000..c8d61ca --- /dev/null +++ b/src/com/annimon/asm/ListingGenerateHelper.java @@ -0,0 +1,107 @@ +package com.annimon.asm; + +import com.annimon.asm.directives.ID; + +/** + * + * @author aNNiMON + */ +public class ListingGenerateHelper { + + private static final String[] SEGMENT_REGISTERS = { + "es", "cs", "ss", "ds" + }; + + /** + * Конвертировать число в строку в HEX формате. + * @param value число + * @return строковое значение числа в HEX. + */ + public static String toHexString(int value) { + String str = Integer.toString(value, 16).toUpperCase(); + if (str.length() == 1) str = "0" + str; + else if (str.length() == 3) str = "0" + str; + return str; + } + + /** + * Записать слово в Little Endian формате. + * @param value число 2 байта. + * @return строка "LL HH" + */ + public static String toLittleEndianString(short value) { + return toHexString(value & 0xFF) + " " + + toHexString((value >> 8) & 0xFF); + } + + /** + * Сконвертировать текст в слово и записать его в Little Endian формате. + * @param text строка с числом. + * @return строка "LL HH" либо "XX" в зависимости от размера. + */ + public static String toLittleEndianString(String text) { + Integer value = DirectiveConverter.parseInteger(text); + if (value == null) return ""; + + if ( (-128 <= value.intValue()) && (value.intValue() <= 255) ) { + return toHexString(value); + } + + return toLittleEndianString(value.shortValue()); + } + + /** + * Проверка, является ли операнд регистром или памятью (r/m). + * @param id тип операнда. + * @return true - операнд регистр или память. + */ + public static boolean isRegisterOrMemory(int id) { + boolean isMemory = ( (id == ID.VAR_BYTE) || (id == ID.VAR_WORD) ); + return (isRegister(id) || isMemory); + } + + /** + * Проверка, является ли операнд регистром. + * @param id тип операнда. + * @return true - операнд регистр. + */ + public static boolean isRegister(int id) { + return ( (id == ID.REGISTER_BYTE) || (id == ID.REGISTER_WORD) ); + } + + /** + * Получить код регистра. + * @param register строковое значение регистра. + * @return код регистра (3 бита). + */ + public static byte getRegisterCode(String register) { + String reg = register.toLowerCase(); + + switch (reg) { + case "ax": case "al": return 0b000; + case "cx": case "cl": return 0b001; + case "dx": case "dl": return 0b010; + case "bx": case "bl": return 0b011; + case "sp": case "ah": return 0b100; + case "bp": case "ch": return 0b101; + case "si": case "dh": return 0b110; + case "di": case "bh": return 0b111; + } + + return 0; + } + + /** + * Получить код сегментного регистра. + * @param register строковое значение регистра. + * @return код сегментного регистра, либо -1, если регистр не сегментный. + */ + public static byte getSegmentRegisterCode(String register) { + String reg = register.toLowerCase(); + for (byte i = 0; i < SEGMENT_REGISTERS.length; i++) { + if (reg.equals(SEGMENT_REGISTERS[i])) return i; + } + return -1; + } + +} diff --git a/src/com/annimon/asm/ListingGenerator.java b/src/com/annimon/asm/ListingGenerator.java new file mode 100644 index 0000000..01a339e --- /dev/null +++ b/src/com/annimon/asm/ListingGenerator.java @@ -0,0 +1,63 @@ +package com.annimon.asm; + +import com.annimon.asm.directives.Add; +import com.annimon.asm.directives.ID; +import com.annimon.asm.directives.IListingGenerator; +import com.annimon.asm.directives.Idiv; +import com.annimon.asm.directives.Mul; +import com.annimon.asm.directives.Pop; +import com.annimon.asm.directives.Push; +import com.annimon.asm.directives.Variable; + +/** + * Генерирование листинга. + * @author aNNiMON + */ +public class ListingGenerator { + + private static final IListingGenerator[] DIRECTIVES = { + new Variable(), + new Add(), new Mul(), new Pop(), new Push(), new Idiv() + }; + + private LexicTable lexicTable; + private VarTable varTable; + private String[] lines; + + public ListingGenerator(LexicTable lexicTable, VarTable varTable, String[] lines) { + this.lexicTable = lexicTable; + this.varTable = varTable; + this.lines = lines; + } + + public String generate() { + StringBuilder text = new StringBuilder(); + short offset = VarTable.getOffset(); + + for (int i = 0; i < lexicTable.getSize(); i++) { + LexicLine line = lexicTable.getLexicAt(i); + String[] parts = DirectiveConverter.split(lines[line.lineNumber - 1]); + + String strLine = generateLine(line.lineNumber, line.line, parts).trim(); + // Вывод адреса для всех директив, кроме переменных. + if ( (line.line[0] != ID.VAR_BYTE) && (line.line[0] != ID.VAR_WORD) ) { + text.append(ListingGenerateHelper.toHexString(offset)).append(" "); + int lineSize = strLine.split(" ").length; + offset += lineSize; + } + text.append(strLine); + text.append(System.lineSeparator()); + } + + return text.toString(); + } + + private String generateLine(int lineNumber, int[] lexic, String[] parts) { + for (int i = 0; i < DIRECTIVES.length; i++) { + String text = DIRECTIVES[i].generate(lineNumber, lexic, parts, varTable); + if (!text.isEmpty()) return text; + } + return ""; + } + +} diff --git a/src/com/annimon/asm/Main.java b/src/com/annimon/asm/Main.java new file mode 100644 index 0000000..cac71a4 --- /dev/null +++ b/src/com/annimon/asm/Main.java @@ -0,0 +1,22 @@ +package com.annimon.asm; + +import javax.swing.JFrame; + +/** + * @author aNNiMON + */ +public class Main extends JFrame { + + public static void main(String[] args) { + new Main().setVisible(true); + } + + public Main() { + super("Assembler analyzer"); + setDefaultCloseOperation(EXIT_ON_CLOSE); + + add(new AnalyzerPanel()); + pack(); + } + +} diff --git a/src/com/annimon/asm/SyntaxAnalyzer.java b/src/com/annimon/asm/SyntaxAnalyzer.java new file mode 100644 index 0000000..d274dd9 --- /dev/null +++ b/src/com/annimon/asm/SyntaxAnalyzer.java @@ -0,0 +1,44 @@ +package com.annimon.asm; + +import com.annimon.asm.directives.Add; +import com.annimon.asm.directives.Mul; +import com.annimon.asm.directives.Pop; +import com.annimon.asm.directives.Push; +import com.annimon.asm.directives.ISyntaxChecker; +import com.annimon.asm.directives.Idiv; +import com.annimon.asm.directives.Variable; +import com.annimon.asm.exceptions.ExceptionWithLineNumber; + +/** + * Синтаксический анализатор. + * @author aNNiMON + */ +public class SyntaxAnalyzer { + + private static final ISyntaxChecker[] DIRECTIVES = { + new Add(), new Mul(), new Push(), new Pop(), new Idiv(), + new Variable() + }; + + private LexicTable lexicTable; + + public SyntaxAnalyzer(LexicTable lexicTable) { + this.lexicTable = lexicTable; + } + + public void analyze() throws ExceptionWithLineNumber { + for (int i = 0; i < lexicTable.getSize(); i++) { + LexicLine line = lexicTable.getLexicAt(i); + analyzeLine(line.lineNumber, line.line); + } + } + + private void analyzeLine(int lineNumber, int[] lexic) throws ExceptionWithLineNumber { + for (int i = 0; i < DIRECTIVES.length; i++) { + if (DIRECTIVES[i].check(lineNumber, lexic)) { + return; + } + } + } + +} diff --git a/src/com/annimon/asm/Var.java b/src/com/annimon/asm/Var.java new file mode 100644 index 0000000..2d74a30 --- /dev/null +++ b/src/com/annimon/asm/Var.java @@ -0,0 +1,25 @@ +package com.annimon.asm; + +/** + * Класс переменной. + * @author aNNiMON + */ +public class Var { + + String name; + int type; + String value; + short addr; + + public Var(String name, int type, String value, short addr) { + this.name = name; + this.type = type; + this.value = value; + this.addr = addr; + } + + public short getAddress() { + return addr; + } + +} diff --git a/src/com/annimon/asm/VarTable.java b/src/com/annimon/asm/VarTable.java new file mode 100644 index 0000000..bf7df67 --- /dev/null +++ b/src/com/annimon/asm/VarTable.java @@ -0,0 +1,52 @@ +package com.annimon.asm; + +import com.annimon.asm.directives.ID; +import java.util.ArrayList; + +/** + * Таблица переменных. + * @author aNNiMON + */ +public class VarTable { + + private ArrayList variables; + private static short offset; + + public VarTable() { + variables = new ArrayList<>(); + offset = 0x103; + } + + public static short getOffset() { + return offset; + } + + public void addVariable(String name, int type, String value) { + addVariable(name, type, value, offset); + int size = (type == ID.DW) ? 2 : 1; + offset += size; + } + + public void addVariable(String name, int type, String value, short address) { + variables.add(new Var(name, type, value, address)); + } + + public int getTypeOfVariable(String name) { + for (Var var : variables) { + if (var.name.equalsIgnoreCase(name)) { + if (var.type == ID.DB) return ID.VAR_BYTE; + else if (var.type == ID.DW) return ID.VAR_WORD; + } + } + return ID.VAR; + } + + public short getAddressOfVariable(String name) { + for (Var var : variables) { + if (var.name.equalsIgnoreCase(name)) { + return var.getAddress(); + } + } + return ID.VAR; + } +} diff --git a/src/com/annimon/asm/directives/Add.java b/src/com/annimon/asm/directives/Add.java new file mode 100644 index 0000000..adc10d1 --- /dev/null +++ b/src/com/annimon/asm/directives/Add.java @@ -0,0 +1,167 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.ListingGenerateHelper; +import com.annimon.asm.VarTable; +import com.annimon.asm.exceptions.CommaExpectedException; +import com.annimon.asm.exceptions.ExceptionWithLineNumber; +import com.annimon.asm.exceptions.FewArgumentsException; +import com.annimon.asm.exceptions.TooManyArgumentsException; +import com.annimon.asm.exceptions.WrongArgumentException; + +/** + * + * @author aNNiMON + */ +public class Add extends Directive implements ISyntaxChecker, IListingGenerator { + + public Add() { + super("add", ID.ADD); + } + + @Override + public boolean check(int lineNumber, int[] ids) throws ExceptionWithLineNumber { + if (ids[0] != getId()) return false; + + if (ids.length < 4) throw new FewArgumentsException(lineNumber); + else if (ids.length > 4) throw new TooManyArgumentsException(lineNumber); + + if (ids[2] != ID.COMMA) throw new CommaExpectedException(lineNumber); + + int[] pairs = new int[] { + // Регистр - Регистр + ID.REGISTER_BYTE, ID.REGISTER_BYTE, + ID.REGISTER_WORD, ID.REGISTER_WORD, + ID.REGISTER_WORD, ID.REGISTER_BYTE, + + // Регистр - Память + ID.REGISTER_BYTE, ID.VAR_BYTE, + ID.REGISTER_WORD, ID.VAR_WORD, + ID.REGISTER_WORD, ID.VAR_BYTE, + + // Память - Регистр + ID.VAR_BYTE, ID.REGISTER_BYTE, + ID.VAR_WORD, ID.REGISTER_WORD, + ID.VAR_WORD, ID.REGISTER_BYTE, + + // Регистр - Значение + ID.REGISTER_BYTE, ID.NUMBER_BYTE, + ID.REGISTER_WORD, ID.NUMBER_WORD, + ID.REGISTER_WORD, ID.NUMBER_BYTE, + + // Память - Значение + ID.VAR_BYTE, ID.NUMBER_BYTE, + ID.VAR_WORD, ID.NUMBER_WORD, + ID.VAR_WORD, ID.NUMBER_BYTE, + }; + + boolean correct = false; + for(int i = 0; i < pairs.length; i += 2) { + if ( (ids[1] == pairs[i]) && (ids[3] == pairs[i+1]) ) { + correct = true; + break; + } + } + if (!correct) throw new WrongArgumentException(lineNumber); + return true; + } + + @Override + public String generate(int lineNumber, int[] ids, String[] strs, VarTable vars) { + if (ids[0] != getId()) return ""; + + StringBuilder sb = new StringBuilder(); + + if ( (ids[3] == ID.NUMBER_BYTE) || (ids[3] == ID.NUMBER_WORD) ) { + + // ADD acc, imm 0000010w | data + if ( (strs[1].equalsIgnoreCase("ax")) || (strs[1].equalsIgnoreCase("al")) ) { + byte val = 0b00000100; + if (strs[1].equalsIgnoreCase("ax")) val |= 1; + + sb.append(ListingGenerateHelper.toHexString(val)) + .append(' ') + .append(ListingGenerateHelper.toLittleEndianString(strs[3])); + return sb.toString(); + } + + // ADD r/m, imm 100000sw | mod000r/m | data + if (ListingGenerateHelper.isRegisterOrMemory(ids[1])) { + int val = 0b100000_00; + if ( (ids[1] == ID.REGISTER_WORD) || (ids[1] == ID.VAR_WORD) ) { + val |= 1; + } + + String varaddr = " "; + if ( (ids[1] == ID.VAR_BYTE) || (ids[1] == ID.VAR_WORD)) { + short offset = vars.getAddressOfVariable(strs[1]); + varaddr += ListingGenerateHelper.toLittleEndianString(offset) + " "; + } + + // mod + int modreg = 0b00; + if (ids[1] == ID.REGISTER_WORD) modreg = 0b11; + modreg <<= 6; + + // r/m + if (strs[1].toLowerCase().indexOf("si") != -1) modreg |= 0b100; + else if (strs[1].toLowerCase().indexOf("di") != -1) modreg |= 0b101; + else modreg |= 0b110; + + sb.append(ListingGenerateHelper.toHexString(val)) + .append(' ') + .append(ListingGenerateHelper.toHexString(modreg)) + .append(varaddr) + .append(ListingGenerateHelper.toLittleEndianString(strs[3])); + return sb.toString(); + } + } + + // ADD r/m, reg 000000dw | modregr/m + if (ListingGenerateHelper.isRegisterOrMemory(ids[1])) { + int val = 0b000000_00; + if ( (ids[1] == ID.REGISTER_WORD) || (ids[1] == ID.VAR_WORD) ) { + val |= 1; + } + if ( (ids[1] == ID.REGISTER_BYTE) || (ids[1] == ID.REGISTER_WORD) ) { + val |= 0b10; + } + + String varaddr = " "; + if ( (ids[1] == ID.VAR_BYTE) || (ids[1] == ID.VAR_WORD)) { + short offset = vars.getAddressOfVariable(strs[1]); + varaddr += ListingGenerateHelper.toLittleEndianString(offset) + " "; + } + + // mod + int modreg = 0b00; + if ( (ListingGenerateHelper.isRegister(ids[1])) && + (ListingGenerateHelper.isRegister(ids[3])) ) { + // Если оба операнда - регистры, то mod = 11 + modreg |= 0b11; + } + modreg <<= 6; + + // r/m + if (modreg == 0) modreg = 0b110; + else { + // Если mod != 11, то r/m - id регистра. + modreg |= ListingGenerateHelper.getRegisterCode(strs[3]); + } + + // reg - id регистра первого операнда, либо 0. + byte regID = ListingGenerateHelper.getRegisterCode(strs[1]); + if (modreg == 0b110) { + regID = ListingGenerateHelper.getRegisterCode(strs[3]); + } + regID <<= 3; + modreg |= regID; + + sb.append(ListingGenerateHelper.toHexString(val)) + .append(' ') + .append(ListingGenerateHelper.toHexString(modreg)) + .append(varaddr); + } + + return sb.toString(); + } +} diff --git a/src/com/annimon/asm/directives/ByteRegister.java b/src/com/annimon/asm/directives/ByteRegister.java new file mode 100644 index 0000000..48dda50 --- /dev/null +++ b/src/com/annimon/asm/directives/ByteRegister.java @@ -0,0 +1,28 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public class ByteRegister extends Register { + + public ByteRegister() { + super(); + } + + @Override + public int getId() { + return ID.REGISTER_BYTE; + } + + @Override + protected String[] getRegisterNames() { + return new String[] { + "ah", "al", + "bh", "bl", + "ch", "cl", + "dh", "dl", + }; + } + +} diff --git a/src/com/annimon/asm/directives/ByteValue.java b/src/com/annimon/asm/directives/ByteValue.java new file mode 100644 index 0000000..af5c4dd --- /dev/null +++ b/src/com/annimon/asm/directives/ByteValue.java @@ -0,0 +1,27 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public class ByteValue extends NumericValue { + + public ByteValue() { + super(); + } + + @Override + public int getId() { + return ID.NUMBER_BYTE; + } + + @Override + protected boolean checkRange(Integer value) { + if (value == null) return false; + if ( (-128 <= value.intValue()) && (value.intValue() <= 255) ) { + return true; + } + return false; + } + +} diff --git a/src/com/annimon/asm/directives/Comma.java b/src/com/annimon/asm/directives/Comma.java new file mode 100644 index 0000000..d06fbde --- /dev/null +++ b/src/com/annimon/asm/directives/Comma.java @@ -0,0 +1,13 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public class Comma extends Directive { + + public Comma() { + super(",", ID.COMMA); + } + +} diff --git a/src/com/annimon/asm/directives/DB.java b/src/com/annimon/asm/directives/DB.java new file mode 100644 index 0000000..363666b --- /dev/null +++ b/src/com/annimon/asm/directives/DB.java @@ -0,0 +1,13 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public class DB extends Directive { + + public DB() { + super("db", ID.DB); + } + +} diff --git a/src/com/annimon/asm/directives/DW.java b/src/com/annimon/asm/directives/DW.java new file mode 100644 index 0000000..3e347a1 --- /dev/null +++ b/src/com/annimon/asm/directives/DW.java @@ -0,0 +1,13 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public class DW extends Directive { + + public DW() { + super("dw", ID.DW); + } + +} diff --git a/src/com/annimon/asm/directives/Directive.java b/src/com/annimon/asm/directives/Directive.java new file mode 100644 index 0000000..5f245c6 --- /dev/null +++ b/src/com/annimon/asm/directives/Directive.java @@ -0,0 +1,29 @@ +package com.annimon.asm.directives; + +/** + * Базовый класс директив. + * @author aNNiMON + */ +public abstract class Directive { + + protected String name; + protected int id; + + protected Directive(String name, int id) { + this.name = name; + this.id = id; + } + + public int getId() { + return id; + } + + /** + * Проверка на соответствие директивы тексту. + * @param text строка директивы, которую нужно проверить. + * @return результат совпадения. + */ + public boolean isDirective(String text) { + return text.equalsIgnoreCase(name); + } +} diff --git a/src/com/annimon/asm/directives/ID.java b/src/com/annimon/asm/directives/ID.java new file mode 100644 index 0000000..e64c839 --- /dev/null +++ b/src/com/annimon/asm/directives/ID.java @@ -0,0 +1,33 @@ +package com.annimon.asm.directives; + +/** + * Список идентификаторов директив. + * @author aNNiMON + */ +public interface ID { + + public static final int + NULL = -1, + + DB = 0, + DW = 1, + + COMMA = 5, + + ADD = 10, + MUL = 11, + PUSH = 12, + POP = 13, + IDIV = 14, + + NUMBER_BYTE = 100, + NUMBER_WORD = 101, + NUMBER_INFINITY = 102, + + REGISTER_BYTE = 200, + REGISTER_WORD = 201, + + VAR = 500, + VAR_BYTE = 501, + VAR_WORD = 502; +} diff --git a/src/com/annimon/asm/directives/IListingGenerator.java b/src/com/annimon/asm/directives/IListingGenerator.java new file mode 100644 index 0000000..ca657c7 --- /dev/null +++ b/src/com/annimon/asm/directives/IListingGenerator.java @@ -0,0 +1,20 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.VarTable; + +/** + * Генерирование листинга. + * @author aNNiMON + */ +public interface IListingGenerator { + + /** + * Сгенерировать строку листинга. + * @param lineNumber номер строки. + * @param ids последовательность идентификаторов в строке. + * @param strs строковая последовательность идентификаторов. + * @param var таблица переменных + * @return строка листинга. + */ + public String generate(int lineNumber, int[] ids, String[] strs, VarTable var); +} diff --git a/src/com/annimon/asm/directives/ISyntaxChecker.java b/src/com/annimon/asm/directives/ISyntaxChecker.java new file mode 100644 index 0000000..715ac6b --- /dev/null +++ b/src/com/annimon/asm/directives/ISyntaxChecker.java @@ -0,0 +1,20 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.exceptions.ExceptionWithLineNumber; + +/** + * Проверка синтаксиса. + * @author aNNiMON + */ +public interface ISyntaxChecker { + + /** + * Проверить синтаксис. + * @param lineNumber номер строки. + * @param ids последовательность идентификаторов в строке. + * @return правильность последовательности директив. + * @throws ExceptionWithLineNumber + */ + public boolean check(int lineNumber, int[] ids) throws ExceptionWithLineNumber; + +} diff --git a/src/com/annimon/asm/directives/Idiv.java b/src/com/annimon/asm/directives/Idiv.java new file mode 100644 index 0000000..9383b1c --- /dev/null +++ b/src/com/annimon/asm/directives/Idiv.java @@ -0,0 +1,77 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.ListingGenerateHelper; +import com.annimon.asm.VarTable; +import com.annimon.asm.exceptions.ExceptionWithLineNumber; +import com.annimon.asm.exceptions.FewArgumentsException; +import com.annimon.asm.exceptions.TooManyArgumentsException; +import com.annimon.asm.exceptions.WrongArgumentException; + +/** + * + * @author aNNiMON + */ +public class Idiv extends Directive implements ISyntaxChecker, IListingGenerator { + + public Idiv() { + super("idiv", ID.IDIV); + } + + @Override + public boolean check(int lineNumber, int[] ids) throws ExceptionWithLineNumber { + if (ids[0] != getId()) return false; + + if (ids.length < 2) throw new FewArgumentsException(lineNumber, 1); + else if (ids.length > 2) throw new TooManyArgumentsException(lineNumber, 1); + + if ( (ids[1] != ID.REGISTER_BYTE) && (ids[1] != ID.REGISTER_WORD) && + (ids[1] != ID.VAR_BYTE) && (ids[1] != ID.VAR_WORD) ) { + throw new WrongArgumentException(lineNumber); + } + return true; + } + + @Override + public String generate(int lineNumber, int[] ids, String[] strs, VarTable vars) { + if (ids[0] != getId()) return ""; + + // MUL r/m 1111011w | mod111r/m + StringBuilder sb = new StringBuilder(); + int val = 0b1111011_0; + if ( (ids[1] == ID.REGISTER_WORD) || (ids[1] == ID.VAR_WORD) ) { + val |= 1; + } + + String varaddr = " "; + if ( (ids[1] == ID.VAR_BYTE) || (ids[1] == ID.VAR_WORD)) { + short offset = vars.getAddressOfVariable(strs[1]); + varaddr += ListingGenerateHelper.toLittleEndianString(offset) + " "; + } + + // mod + int modrm = 0b00; + if (ListingGenerateHelper.isRegister(ids[1])) { + // Если операнд - регистр, то mod = 11 + modrm |= 0b11; + } + modrm <<= 6; + + // r/m + if (modrm == 0) modrm = 0b110; + else { + // Если mod != 11, то r/m - id регистра. + modrm |= ListingGenerateHelper.getRegisterCode(strs[1]); + } + + // reg - 111 + byte regID = 0b111; + regID <<= 3; + modrm |= regID; + + sb.append(ListingGenerateHelper.toHexString(val)) + .append(' ') + .append(ListingGenerateHelper.toHexString(modrm)) + .append(varaddr); + return sb.toString(); + } +} diff --git a/src/com/annimon/asm/directives/InfinityValue.java b/src/com/annimon/asm/directives/InfinityValue.java new file mode 100644 index 0000000..3ec59e8 --- /dev/null +++ b/src/com/annimon/asm/directives/InfinityValue.java @@ -0,0 +1,27 @@ +package com.annimon.asm.directives; + +/** + * Неопределённое значение (?) + * @author aNNiMON + */ +public class InfinityValue extends NumericValue { + + public InfinityValue() { + super(); + } + + @Override + public int getId() { + return ID.NUMBER_INFINITY; + } + + @Override + public boolean isDirective(String text) { + return text.equals("?"); + } + + @Override + protected boolean checkRange(Integer value) { + return true; + } +} diff --git a/src/com/annimon/asm/directives/Mul.java b/src/com/annimon/asm/directives/Mul.java new file mode 100644 index 0000000..33bf9ce --- /dev/null +++ b/src/com/annimon/asm/directives/Mul.java @@ -0,0 +1,77 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.ListingGenerateHelper; +import com.annimon.asm.VarTable; +import com.annimon.asm.exceptions.ExceptionWithLineNumber; +import com.annimon.asm.exceptions.FewArgumentsException; +import com.annimon.asm.exceptions.TooManyArgumentsException; +import com.annimon.asm.exceptions.WrongArgumentException; + +/** + * + * @author aNNiMON + */ +public class Mul extends Directive implements ISyntaxChecker, IListingGenerator { + + public Mul() { + super("mul", ID.MUL); + } + + @Override + public boolean check(int lineNumber, int[] ids) throws ExceptionWithLineNumber { + if (ids[0] != getId()) return false; + + if (ids.length < 2) throw new FewArgumentsException(lineNumber, 1); + else if (ids.length > 2) throw new TooManyArgumentsException(lineNumber, 1); + + if ( (ids[1] != ID.REGISTER_BYTE) && (ids[1] != ID.REGISTER_WORD) && + (ids[1] != ID.VAR_BYTE) && (ids[1] != ID.VAR_WORD) ) { + throw new WrongArgumentException(lineNumber); + } + return true; + } + + @Override + public String generate(int lineNumber, int[] ids, String[] strs, VarTable vars) { + if (ids[0] != getId()) return ""; + + // MUL r/m 1111011w | mod100r/m + StringBuilder sb = new StringBuilder(); + int val = 0b1111011_0; + if ( (ids[1] == ID.REGISTER_WORD) || (ids[1] == ID.VAR_WORD) ) { + val |= 1; + } + + String varaddr = " "; + if ( (ids[1] == ID.VAR_BYTE) || (ids[1] == ID.VAR_WORD)) { + short offset = vars.getAddressOfVariable(strs[1]); + varaddr += ListingGenerateHelper.toLittleEndianString(offset) + " "; + } + + // mod + int modrm = 0b00; + if (ListingGenerateHelper.isRegister(ids[1])) { + // Если операнд - регистр, то mod = 11 + modrm |= 0b11; + } + modrm <<= 6; + + // r/m + if (modrm == 0) modrm = 0b110; + else { + // Если mod != 11, то r/m - id регистра. + modrm |= ListingGenerateHelper.getRegisterCode(strs[1]); + } + + // reg - 100 + byte regID = 0b100; + regID <<= 3; + modrm |= regID; + + sb.append(ListingGenerateHelper.toHexString(val)) + .append(' ') + .append(ListingGenerateHelper.toHexString(modrm)) + .append(varaddr); + return sb.toString(); + } +} diff --git a/src/com/annimon/asm/directives/NumericValue.java b/src/com/annimon/asm/directives/NumericValue.java new file mode 100644 index 0000000..2dd1a25 --- /dev/null +++ b/src/com/annimon/asm/directives/NumericValue.java @@ -0,0 +1,26 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.DirectiveConverter; + +/** + * + * @author aNNiMON + */ +public abstract class NumericValue extends Directive { + + public NumericValue() { + super("", ID.NULL); + } + + @Override + public boolean isDirective(String text) { + if (text == null) return false; + + Integer value = DirectiveConverter.parseInteger(text); + return checkRange(value); + } + + protected abstract boolean checkRange(Integer value); + + +} diff --git a/src/com/annimon/asm/directives/Pop.java b/src/com/annimon/asm/directives/Pop.java new file mode 100644 index 0000000..2312cd8 --- /dev/null +++ b/src/com/annimon/asm/directives/Pop.java @@ -0,0 +1,64 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.ListingGenerateHelper; +import com.annimon.asm.VarTable; +import com.annimon.asm.exceptions.ExceptionWithLineNumber; +import com.annimon.asm.exceptions.FewArgumentsException; +import com.annimon.asm.exceptions.WrongArgumentException; + +/** + * + * @author aNNiMON + */ +public class Pop extends Directive implements ISyntaxChecker, IListingGenerator { + + public Pop() { + super("pop", ID.POP); + } + + @Override + public boolean check(int lineNumber, int[] ids) throws ExceptionWithLineNumber { + if (ids[0] != getId()) return false; + + if (ids.length < 2) throw new FewArgumentsException(lineNumber, 1); + for (int i = 1; i < ids.length; i++) { + if ( (ids[i] != ID.REGISTER_WORD) && + (ids[i] != ID.VAR_WORD)) { + throw new WrongArgumentException(lineNumber); + } + } + return true; + } + + @Override + public String generate(int lineNumber, int[] ids, String[] strs, VarTable vars) { + if (ids[0] != getId()) return ""; + + StringBuilder sb = new StringBuilder(); + + if (ids[1] == ID.REGISTER_WORD) { + byte segmentID = ListingGenerateHelper.getSegmentRegisterCode(strs[1]); + if (segmentID != -1) { + //seg (только cs, ds, ss, es) 00seg111 + byte val = (byte) (0b00_000_111 | (segmentID << 3)); + sb.append(ListingGenerateHelper.toHexString(val)); + } else { + // reg16/32 01011reg + byte regID = ListingGenerateHelper.getRegisterCode(strs[1]); + byte val = (byte) (0b01011_000 | regID); + sb.append(ListingGenerateHelper.toHexString(val)); + } + } else if (ids[1] == ID.VAR_WORD) { + //r/m16/32 10001111 | mod110r/m + short offset = vars.getAddressOfVariable(strs[1]); + + sb.append(ListingGenerateHelper.toHexString(0b10001111)) + .append(' ') + .append(ListingGenerateHelper.toHexString(0b00_000_110)) + .append(' ') + .append(ListingGenerateHelper.toLittleEndianString(offset)); + } + + return sb.toString(); + } +} diff --git a/src/com/annimon/asm/directives/Push.java b/src/com/annimon/asm/directives/Push.java new file mode 100644 index 0000000..888bfa6 --- /dev/null +++ b/src/com/annimon/asm/directives/Push.java @@ -0,0 +1,65 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.ListingGenerateHelper; +import com.annimon.asm.VarTable; +import com.annimon.asm.exceptions.ExceptionWithLineNumber; +import com.annimon.asm.exceptions.FewArgumentsException; +import com.annimon.asm.exceptions.WrongArgumentException; + +/** + * + * @author aNNiMON + */ +public class Push extends Directive implements ISyntaxChecker, IListingGenerator { + + public Push() { + super("push", ID.PUSH); + } + + @Override + public boolean check(int lineNumber, int[] ids) throws ExceptionWithLineNumber { + if (ids[0] != getId()) return false; + + if (ids.length < 2) throw new FewArgumentsException(lineNumber, 1); + for (int i = 1; i < ids.length; i++) { + if ( (ids[i] != ID.REGISTER_WORD) && + (ids[i] != ID.VAR_WORD)) { + throw new WrongArgumentException(lineNumber); + } + } + return true; + + } + + @Override + public String generate(int lineNumber, int[] ids, String[] strs, VarTable vars) { + if (ids[0] != getId()) return ""; + + StringBuilder sb = new StringBuilder(); + + if (ids[1] == ID.REGISTER_WORD) { + byte segmentID = ListingGenerateHelper.getSegmentRegisterCode(strs[1]); + if (segmentID != -1) { + //seg (только cs, ds, ss, es) 00seg110 + byte val = (byte) (0b00_000_110 | (segmentID << 3)); + sb.append(ListingGenerateHelper.toHexString(val)); + } else { + // reg16/32 01010reg + byte regID = ListingGenerateHelper.getRegisterCode(strs[1]); + byte val = (byte) (0b01010_000 | regID); + sb.append(ListingGenerateHelper.toHexString(val)); + } + } else if (ids[1] == ID.VAR_WORD) { + // r/m16/32 11111111 | mod110r/m + short offset = vars.getAddressOfVariable(strs[1]); + + sb.append(ListingGenerateHelper.toHexString(0b11111111)) + .append(' ') + .append(ListingGenerateHelper.toHexString(0b00_110_110)) + .append(' ') + .append(ListingGenerateHelper.toLittleEndianString(offset)); + } + + return sb.toString(); + } +} diff --git a/src/com/annimon/asm/directives/Register.java b/src/com/annimon/asm/directives/Register.java new file mode 100644 index 0000000..7e0815e --- /dev/null +++ b/src/com/annimon/asm/directives/Register.java @@ -0,0 +1,25 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public abstract class Register extends Directive { + + public Register() { + super("", ID.NULL); + } + + @Override + public boolean isDirective(String text) { + String[] names = getRegisterNames(); + for (int i = 0; i < names.length; i++) { + if (text.equalsIgnoreCase(names[i])) { + return true; + } + } + return false; + } + + protected abstract String[] getRegisterNames(); +} diff --git a/src/com/annimon/asm/directives/Variable.java b/src/com/annimon/asm/directives/Variable.java new file mode 100644 index 0000000..5ed3214 --- /dev/null +++ b/src/com/annimon/asm/directives/Variable.java @@ -0,0 +1,61 @@ +package com.annimon.asm.directives; + +import com.annimon.asm.ListingGenerateHelper; +import com.annimon.asm.VarTable; +import com.annimon.asm.exceptions.ExceptionWithLineNumber; +import com.annimon.asm.exceptions.FewArgumentsException; +import com.annimon.asm.exceptions.TooManyArgumentsException; +import com.annimon.asm.exceptions.WrongArgumentException; +import java.util.regex.Pattern; + +/** + * + * @author aNNiMON + */ +public class Variable extends Directive implements ISyntaxChecker, IListingGenerator { + + public Variable() { + super("", ID.VAR); + } + + @Override + public boolean isDirective(String text) { + if (Pattern.matches(Pattern.compile("^[^0-9]*[\\w.@_$]").pattern(), text)) { + return true; + } + return false; + } + + @Override + public boolean check(int lineNumber, int[] ids) throws ExceptionWithLineNumber { + if (ids[0] != getId()) return false; + + if (ids.length < 3) throw new FewArgumentsException(lineNumber, 2); + else if (ids.length > 3) throw new TooManyArgumentsException(lineNumber, 2); + + boolean db = ( (ids[1] == ID.DB) && + ((ids[2] == ID.NUMBER_BYTE) || (ids[2] == ID.NUMBER_INFINITY)) ); + boolean dw = ( (ids[1] == ID.DW) && + ((ids[2] == ID.NUMBER_WORD) || (ids[2] == ID.NUMBER_INFINITY)) ); + if (!db && !dw) { + throw new WrongArgumentException(lineNumber); + } + return true; + } + + @Override + public String generate(int lineNumber, int[] ids, String[] strs, VarTable vars) { + if ( (ids[0] != ID.VAR_BYTE) && (ids[0] != ID.VAR_WORD) ) return ""; + + StringBuilder sb = new StringBuilder(); + int offset = vars.getAddressOfVariable(strs[0]); + sb.append(ListingGenerateHelper.toHexString(offset)).append(" "); + + if (ids[2] == ID.NUMBER_INFINITY) { + sb.append("??"); + if (ids[1] == ID.DW) sb.append(" ??"); + } else sb.append( ListingGenerateHelper.toLittleEndianString(strs[2]) ); + + return sb.toString(); + } +} diff --git a/src/com/annimon/asm/directives/WordRegister.java b/src/com/annimon/asm/directives/WordRegister.java new file mode 100644 index 0000000..1207f3c --- /dev/null +++ b/src/com/annimon/asm/directives/WordRegister.java @@ -0,0 +1,27 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public class WordRegister extends Register { + + public WordRegister() { + super(); + } + + @Override + public int getId() { + return ID.REGISTER_WORD; + } + + @Override + protected String[] getRegisterNames() { + return new String[] { + "ax", "bx", "cx", "dx", + "cs", "ds", "ss", "es", + "sp", "bp", "si", "di" + }; + } + +} diff --git a/src/com/annimon/asm/directives/WordValue.java b/src/com/annimon/asm/directives/WordValue.java new file mode 100644 index 0000000..cdf9f40 --- /dev/null +++ b/src/com/annimon/asm/directives/WordValue.java @@ -0,0 +1,27 @@ +package com.annimon.asm.directives; + +/** + * + * @author aNNiMON + */ +public class WordValue extends NumericValue { + + public WordValue() { + super(); + } + + @Override + public int getId() { + return ID.NUMBER_WORD; + } + + @Override + protected boolean checkRange(Integer value) { + if (value == null) return false; + if ( (-32768 <= value.intValue()) && (value.intValue() <= 65535) ) { + return true; + } + return false; + } + +} diff --git a/src/com/annimon/asm/exceptions/CommaExpectedException.java b/src/com/annimon/asm/exceptions/CommaExpectedException.java new file mode 100644 index 0000000..c56b740 --- /dev/null +++ b/src/com/annimon/asm/exceptions/CommaExpectedException.java @@ -0,0 +1,12 @@ +package com.annimon.asm.exceptions; + +/** + * Класс ошибки пропущенной запятой. + * @author aNNiMON + */ +public class CommaExpectedException extends ExceptionWithLineNumber { + + public CommaExpectedException(int lineNumber) { + super("Comma expected", lineNumber); + } +} diff --git a/src/com/annimon/asm/exceptions/ExceptionWithLineNumber.java b/src/com/annimon/asm/exceptions/ExceptionWithLineNumber.java new file mode 100644 index 0000000..5157e4e --- /dev/null +++ b/src/com/annimon/asm/exceptions/ExceptionWithLineNumber.java @@ -0,0 +1,21 @@ +package com.annimon.asm.exceptions; + +/** + * Базовый класс для ошибок с отображением номера ошибочной строки. + * @author aNNiMON + */ +public abstract class ExceptionWithLineNumber extends Exception { + + private int lineNumber; + + public ExceptionWithLineNumber(String message, int lineNumber) { + super(message); + this.lineNumber = lineNumber; + } + + @Override + public String getMessage() { + return super.getMessage() + " at line " + lineNumber; + } + +} diff --git a/src/com/annimon/asm/exceptions/FewArgumentsException.java b/src/com/annimon/asm/exceptions/FewArgumentsException.java new file mode 100644 index 0000000..0ad20fc --- /dev/null +++ b/src/com/annimon/asm/exceptions/FewArgumentsException.java @@ -0,0 +1,16 @@ +package com.annimon.asm.exceptions; + +/** + * Класс ошибки, когда указано слишком мало аргументов команды, нежели ожидалось. + * @author aNNiMON + */ +public class FewArgumentsException extends ExceptionWithLineNumber { + + public FewArgumentsException(int lineNumber) { + super("Few arguments", lineNumber); + } + + public FewArgumentsException(int lineNumber, int necessaryArguments) { + super("Few arguments, need " + necessaryArguments + " args", lineNumber); + } +} diff --git a/src/com/annimon/asm/exceptions/TooManyArgumentsException.java b/src/com/annimon/asm/exceptions/TooManyArgumentsException.java new file mode 100644 index 0000000..1eea990 --- /dev/null +++ b/src/com/annimon/asm/exceptions/TooManyArgumentsException.java @@ -0,0 +1,16 @@ +package com.annimon.asm.exceptions; + +/** + * Класс ошибки для превышенного количества аргументов команды. + * @author aNNiMON + */ +public class TooManyArgumentsException extends ExceptionWithLineNumber { + + public TooManyArgumentsException(int lineNumber) { + super("Too many arguments", lineNumber); + } + + public TooManyArgumentsException(int lineNumber, int necessaryArguments) { + super("Too many arguments, need " + necessaryArguments + " args", lineNumber); + } +} diff --git a/src/com/annimon/asm/exceptions/WrongArgumentException.java b/src/com/annimon/asm/exceptions/WrongArgumentException.java new file mode 100644 index 0000000..051882d --- /dev/null +++ b/src/com/annimon/asm/exceptions/WrongArgumentException.java @@ -0,0 +1,12 @@ +package com.annimon.asm.exceptions; + +/** + * Класс ошибки неверного аргумента, когда не совпадает ожидаемый тип. + * @author aNNiMON + */ +public class WrongArgumentException extends ExceptionWithLineNumber { + + public WrongArgumentException(int lineNumber) { + super("Wrong argument", lineNumber); + } +} diff --git a/src/test.asm b/src/test.asm new file mode 100644 index 0000000..45d728e --- /dev/null +++ b/src/test.asm @@ -0,0 +1,20 @@ +num1 db 12 +num2 dw 2000 +num3 dw ? +num4 db 77h +num5 dw 12345 + +add num1, 40 +add num2, bx +push num2 +pop ax +idiv cx +add al, cl +mul dh +push dx +add si, dx +mul num2 +pop num3 +push es +pop ss +idiv num1 \ No newline at end of file