1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-30 06:29:32 +00:00

Update ACME generator for v0.97

Two things changed: (1) string literals can now hold backslash
escapes like "\n"; (2) MVN/MVP operands can now be prefixed with '#'.
The former was a breaking change because any string with "\" must
be changed to "\\".  This is now handled by the string operand
formatter.

Also, improved test harness output.  Show the assembler versions at
the end, and include assembler failure messages in the collected
output.
This commit is contained in:
Andy McFadden 2021-07-31 14:42:36 -07:00
parent c16e646701
commit 8c053c29f2
15 changed files with 124 additions and 75 deletions

View File

@ -43,6 +43,7 @@ namespace Asm65 {
private Formatter.DelimiterDef mDelimiterDef;
private RawOutputStyle mRawStyle;
private bool mBackslashEscapes;
private int mMaxOperandLen;
// Reference to array with 16 hex digits. (May be upper or lower case.)
@ -80,15 +81,18 @@ namespace Asm65 {
/// <param name="delimiterDef">String delimiter values.</param>
/// <param name="byteStyle">How to format raw byte data.</param>
/// <param name="charConv">Character conversion delegate.</param>
/// <param name="backslashEscapes">True if "\" must be escaped with "\\".</param>
public StringOpFormatter(Formatter formatter, Formatter.DelimiterDef delimiterDef,
RawOutputStyle byteStyle, CharEncoding.Convert charConv) {
mRawStyle = byteStyle;
mMaxOperandLen = formatter.OperandWrapLen;
CharConv = charConv;
RawOutputStyle byteStyle, CharEncoding.Convert charConv,
bool backslashEscapes) {
mDelimiterDef = delimiterDef;
mBuffer = new char[mMaxOperandLen];
mRawStyle = byteStyle;
CharConv = charConv;
mBackslashEscapes = backslashEscapes;
mMaxOperandLen = formatter.OperandWrapLen;
mHexChars = formatter.HexDigits;
mBuffer = new char[mMaxOperandLen];
Lines = new List<string>();
// suffix not used, so we don't expect it to be set to something
@ -113,7 +117,7 @@ namespace Asm65 {
/// isn't printable, the raw character value will be written as a byte instead.
/// </summary>
/// <param name="rawCh">Raw character value.</param>
public void WriteChar(byte rawCh) {
private void WriteChar(byte rawCh) {
Debug.Assert(mState != State.Finished);
char ch = CharConv(rawCh);
@ -160,7 +164,7 @@ namespace Asm65 {
/// Write a hex value into the buffer.
/// </summary>
/// <param name="val">Value to add.</param>
public void WriteByte(byte val) {
private void WriteByte(byte val) {
Debug.Assert(mState != State.Finished);
HasEscapedText = true;
@ -271,17 +275,26 @@ namespace Asm65 {
}
for (int off = endOffset - 1; off >= chunkOffset; off--) {
WriteChar(data[off]);
if (data[off] == '\\' && mBackslashEscapes) {
WriteChar(data[off]);
}
}
}
} else if (revMode == ReverseMode.FullReverse) {
for (; offset < strEndOffset; offset++) {
int posn = startOffset + (strEndOffset - offset) - 1;
WriteChar(data[posn]);
if (data[posn] == '\\' && mBackslashEscapes) {
WriteChar(data[posn]);
}
}
} else {
Debug.Assert(revMode == ReverseMode.Forward);
for (; offset < strEndOffset; offset++) {
WriteChar(data[offset]);
if (data[offset] == '\\' && mBackslashEscapes) {
WriteChar(data[offset]);
}
}
}

View File

@ -103,6 +103,12 @@ namespace SourceGen.AsmGen {
// Set if we're inside a "pseudopc" block, which will need to be closed.
private bool mInPseudoPcBlock;
// v0.97 started treating '\' in constants as an escape character.
private bool mBackslashEscapes = true;
// Interesting versions.
private static CommonUtil.Version V0_97 = new CommonUtil.Version(0, 97);
// Pseudo-op string constants.
private static PseudoOp.PseudoOpNames sDataOpNames =
@ -150,6 +156,14 @@ namespace SourceGen.AsmGen {
Project = project;
if (asmVersion != null) {
// Use the actual version.
mAsmVersion = asmVersion.Version;
} else {
// No assembler installed. Use v0.97.
mAsmVersion = V0_97;
}
// ACME isn't a single-pass assembler, but the code that determines label widths
// only runs in the first pass and doesn't get corrected. So unlike cc65, which
// generates correct zero-page acceses once the label's value is known, ACME
@ -168,7 +182,10 @@ namespace SourceGen.AsmGen {
Quirks = new AssemblerQuirks();
Quirks.SinglePassAssembler = true;
Quirks.SinglePassNoLabelCorrection = true;
Quirks.BlockMoveArgsNoHash = true;
if (mAsmVersion < V0_97) {
Quirks.BlockMoveArgsNoHash = true;
mBackslashEscapes = false;
}
mWorkDirectory = workDirectory;
mFileNameBase = fileNameBase;
@ -654,7 +671,8 @@ namespace SourceGen.AsmGen {
}
StringOpFormatter stropf = new StringOpFormatter(SourceFormatter,
Formatter.DOUBLE_QUOTE_DELIM,StringOpFormatter.RawOutputStyle.CommaSep, charConv);
Formatter.DOUBLE_QUOTE_DELIM, StringOpFormatter.RawOutputStyle.CommaSep, charConv,
mBackslashEscapes);
stropf.FeedBytes(data, offset, dfd.Length, leadingBytes,
StringOpFormatter.ReverseMode.Forward);

View File

@ -713,7 +713,8 @@ namespace SourceGen.AsmGen {
}
StringOpFormatter stropf = new StringOpFormatter(SourceFormatter,
Formatter.DOUBLE_QUOTE_DELIM, StringOpFormatter.RawOutputStyle.CommaSep, charConv);
Formatter.DOUBLE_QUOTE_DELIM, StringOpFormatter.RawOutputStyle.CommaSep, charConv,
false);
stropf.FeedBytes(data, offset, dfd.Length - trailingBytes, leadingBytes,
StringOpFormatter.ReverseMode.Forward);

View File

@ -616,7 +616,7 @@ namespace SourceGen.AsmGen {
StringOpFormatter stropf = new StringOpFormatter(SourceFormatter,
new Formatter.DelimiterDef(delim),
StringOpFormatter.RawOutputStyle.DenseHex, charConv);
StringOpFormatter.RawOutputStyle.DenseHex, charConv, false);
if (dfd.FormatType == FormatDescriptor.Type.StringDci) {
// DCI is awkward because the character encoding flips on the last byte. Rather
// than clutter up StringOpFormatter for this rare item, we just accept low/high

View File

@ -762,7 +762,8 @@ namespace SourceGen.AsmGen {
}
StringOpFormatter stropf = new StringOpFormatter(SourceFormatter,
Formatter.DOUBLE_QUOTE_DELIM,StringOpFormatter.RawOutputStyle.CommaSep, charConv);
Formatter.DOUBLE_QUOTE_DELIM,StringOpFormatter.RawOutputStyle.CommaSep, charConv,
false);
if (dfd.FormatType == FormatDescriptor.Type.StringDci) {
// DCI is awkward because the character encoding flips on the last byte. Rather
// than clutter up StringOpFormatter for this rare item, we just accept low/high

View File

@ -545,7 +545,7 @@ namespace SourceGen {
}
StringOpFormatter stropf = new StringOpFormatter(formatter, delDef,
StringOpFormatter.RawOutputStyle.CommaSep, charConv);
StringOpFormatter.RawOutputStyle.CommaSep, charConv, false);
stropf.FeedBytes(data, offset + hiddenLeadingBytes,
dfd.Length - hiddenLeadingBytes - trailingBytes, 0, revMode);

View File

@ -23,40 +23,6 @@ your web site, you can "export" the formatted code as text or HTML.
This is explained in more detail <a href="#export-source">below</a>.
<h2><a name="supported">Supported Assemblers</a></h2>
<p>SourceGen currently supports the following cross-assemblers:</p>
<ul>
<li><a href="#64tass">64tass</a></li>
<li><a href="#acme">ACME</a></li>
<li><a href="#cc65">cc65</a></li>
<li><a href="#merlin32">Merlin 32</a></li>
</ul>
<h3><a name="version">Version-Specific Code Generation</a></h3>
<p>Code generation must be tailored to the specific version of the
assembler. This is most easily understood with an example.</p>
<p>If the code has a statement like <code>MVN #$01,#$02</code>, the
assembler is expected to output <code>54 02 01</code>, with the arguments
reversed. cc65 v2.17 got it backward; the behavior was fixed in v2.18. The
bug means we can't generate the same <code>MVN</code>/<code>MVP</code>
instructions for both versions of the assembler.</p>
<p>Having version-dependent source code is a bad idea. If we generated
reversed operands (<code>MVN #$02,#$01</code>), we'd get the correct
output with v2.17, but the wrong output for v2.18. Unambiguous code can
be generated for all versions of the assembler by just outputting raw hex
bytes, but that's ugly and annoying, so we don't want to be stuck doing
that forever. We want to detect which version of the assembler is in
use, and output actual <code>MVN</code>/<code>MVP</code> instructions
when producing code for newer versions of the assembler.</p>
<p>When you configure a cross-assembler, SourceGen runs the executable with
version query args, and extracts the version information from the output
stream. This is used by the generator to ensure that the output will compile.
If no assembler is configured, SourceGen will produce code optimized
for the latest version of the assembler.</p>
<h2><a name="generate">Generating Source Code</a></h2>
<p>Cross assemblers tend to generate additional files, either compiler
@ -149,7 +115,41 @@ SourceGen. However, SourceGen can generally work around assembler bugs,
so any failure is an opportunity for improvement.</p>
<h2><a name="quirks">Assembler-Specific Bugs &amp; Quirks</a></h2>
<h2><a name="supported">Supported Assemblers</a></h2>
<p>SourceGen currently supports the following cross-assemblers:</p>
<ul>
<li><a href="#64tass">64tass</a></li>
<li><a href="#acme">ACME</a></li>
<li><a href="#cc65">cc65</a></li>
<li><a href="#merlin32">Merlin 32</a></li>
</ul>
<h3><a name="version">Version-Specific Code Generation</a></h3>
<p>Code generation must be tailored to the specific version of the
assembler. This is most easily understood with an example.</p>
<p>If the code has a statement like <code>MVN #$01,#$02</code>, the
assembler is expected to output <code>54 02 01</code>, with the arguments
reversed. cc65 v2.17 got it backward; the behavior was fixed in v2.18. The
bug means we can't generate the same <code>MVN</code>/<code>MVP</code>
instructions for both versions of the assembler.</p>
<p>Having version-dependent source code is a bad idea. If we generated
reversed operands (<code>MVN #$02,#$01</code>), we'd get the correct
output with v2.17, but the wrong output for v2.18. Unambiguous code can
be generated for all versions of the assembler by just outputting raw hex
bytes, but that's ugly and annoying, so we don't want to be stuck doing
that forever. We want to detect which version of the assembler is in
use, and output actual <code>MVN</code>/<code>MVP</code> instructions
when producing code for newer versions of the assembler.</p>
<p>When you configure a cross-assembler, SourceGen runs the executable with
version query args, and extracts the version information from the output
stream. This is used by the generator to ensure that the output will compile.
If no assembler is configured, SourceGen will produce code optimized
for the latest version of the assembler.</p>
<h3><a name="quirks">Assembler-Specific Bugs &amp; Quirks</a></h3>
<p>This is a list of bugs and quirky behavior in cross-assemblers that
SourceGen works around when generating code.</p>
@ -166,14 +166,15 @@ code, but also needs to know how to handle the corner cases.</p>
<h3><a name="64tass">64tass</a></h3>
<p>Code is generated for 64tass v1.53.1515 or later.
<p>Tested versions: v1.53.1515, v1.54.1900
<a href="https://sourceforge.net/projects/tass64/">[web site]</a></p>
<p>Bugs:</p>
<ul>
<li>Undocumented opcode <code>SHA (ZP),Y</code> ($93) is not supported;
<li>[Fixed in v1.54.2176?]
Undocumented opcode <code>SHA (ZP),Y</code> ($93) is not supported;
the assembler appears to be expecting <code>SHA ABS,X</code> instead.</li>
<li>WDM is not supported.</li>
<li>[Fixed in v1.54.2176?] WDM is not supported.</li>
</ul>
<p>Quirks:</p>
@ -217,7 +218,7 @@ code, but also needs to know how to handle the corner cases.</p>
<h3><a name="acme">ACME</a></h3>
<p>Code is generated for ACME v0.96.4 or later.
<p>Tested versions: v0.96.4
<a href="https://sourceforge.net/projects/acme-crossass/">[web site]</a></p>
<p>Bugs:</p>
@ -243,7 +244,8 @@ code, but also needs to know how to handle the corner cases.</p>
<code>ASR</code> instead.</li>
<li>Does not allow the accumulator to be specified explicitly as an
operand, e.g. you can't write <code>LSR A</code>.</li>
<li>Syntax for <code>MVN</code>/<code>MVP</code> doesn't allow '#'
<li>[Fixed in v0.97.]
Syntax for <code>MVN</code>/<code>MVP</code> doesn't allow '#'
before 8-bit operands.</li>
<li>Officially, the preferred file extension for ACME source code is ".a",
but this is already used on UNIX systems for static libraries (which
@ -255,7 +257,7 @@ code, but also needs to know how to handle the corner cases.</p>
<h3><a name="cc65">cc65</a></h3>
<p>Code is generated for cc65 v2.17 or v2.18.
<p>Tested versions: v2.17, v2.18
<a href="https://cc65.github.io/">[web site]</a></p>
<p>Bugs:</p>
@ -297,7 +299,7 @@ code, but also needs to know how to handle the corner cases.</p>
<h3><a name="merlin32">Merlin 32</a></h3>
<p>Code is generated for Merlin 32 v1.0.
<p>Tested Versions: v1.0
<a href="https://www.brutaldeluxe.fr/products/crossdevtools/merlin/">[web site]</a>
<a href="https://github.com/apple2accumulator/merlin32/issues">[bug tracker]</a>
</p>

View File

@ -99,10 +99,6 @@ and 65816 code. The official web site is
<li><a href="codegen.html">Code Generation &amp; Assembly</a>
<ul>
<li><a href="codegen.html#supported">Supported Assemblers</a>
<ul>
<li><a href="codegen.html#version">Version-Specific Code Generation</a></li>
</ul></li>
<li><a href="codegen.html#generate">Generating Source Code</a>
<ul>
<li><a href="codegen.html#localizer">Label Localizer</a></li>
@ -110,12 +106,16 @@ and 65816 code. The official web site is
<li><a href="codegen.html#platform-features">Platform-Specific Features</a></li>
</ul></li>
<li><a href="codegen.html#assemble">Cross-Assembling Generated Code</a></li>
<li><a href="codegen.html#quirks">Assembler-Specific Bugs &amp; Quirks</a>
<li><a href="codegen.html#supported">Supported Assemblers</a>
<ul>
<li><a href="codegen.html#64tass">64tass</a></li>
<li><a href="codegen.html#acme">ACME</a></li>
<li><a href="codegen.html#cc65">cc65</a></li>
<li><a href="codegen.html#merlin32">Merlin 32</a></li>
<li><a href="codegen.html#version">Version-Specific Code Generation</a></li>
<li><a href="codegen.html#quirks">Assembler-Specific Bugs &amp; Quirks</a>
<ul>
<li><a href="codegen.html#64tass">64tass</a></li>
<li><a href="codegen.html#acme">ACME</a></li>
<li><a href="codegen.html#cc65">cc65</a></li>
<li><a href="codegen.html#merlin32">Merlin 32</a></li>
</ul></li>
</ul></li>
<li><a href="codegen.html#export-source">Exporting Source Code</a>
</ul></li>

View File

@ -87,7 +87,7 @@ L101F ora ($ff,x)
L10AB eor ($ff,x)
!byte $42,$ff
eor $ff,S
mvp $fe,$ff
mvp #$fe,#$ff
eor $ff
lsr $ff
eor [$ff]
@ -104,7 +104,7 @@ L10AB eor ($ff,x)
@L10CE eor ($ff),y
eor ($ff)
eor ($ff,S),y
mvn $fe,$ff
mvn #$fe,#$ff
eor $ff,x
lsr $ff,x
eor [$ff],y

View File

@ -87,7 +87,7 @@ L101F ora ($00,x)
L10AB eor ($00,x)
!byte $42,$00
eor $00,S
mvp $00,$00
mvp #$00,#$00
eor $00
lsr $00
eor [$00]
@ -104,7 +104,7 @@ L10AB eor ($00,x)
@L10CE eor ($00),y
eor ($00)
eor ($00,S),y
mvn $00,$00
mvn #$00,#$00
eor $00,x
lsr $00,x
eor [$00],y

View File

@ -88,7 +88,7 @@ L101F ora (L0080,x)
L10AB eor (L0080,x)
!byte $42,$80
eor $80,S
mvp $84,$83
mvp #$84,#$83
eor+1 L0080
lsr+1 L0080
eor [L0080]
@ -105,7 +105,7 @@ L10AB eor (L0080,x)
@L10CE eor (L0080),y
eor (L0080)
eor ($80,S),y
mvn $84,$83
mvn #$84,#$83
eor+1 L0080,x
lsr+1 L0080,x
eor [L0080],y

View File

@ -45,7 +45,7 @@
!pet $93,"PETSCII with ",$96,"control",$05," codes",$0d
!byte $83
!text " !",$22,"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVW"
!text "XYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!text "XYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!byte $83
!hex a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
!hex c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf
@ -134,7 +134,7 @@
!byte $1e
!byte $1f
!text " !",$22,"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVW"
!text "XYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!text "XYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!byte $7f
!byte $80
!byte $81

View File

@ -45,7 +45,7 @@
!pet $93,"PETSCII with ",$96,"control",$05," codes",$0d
!byte $83
!text " !",$22,"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVW"
!text "XYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!text "XYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!byte $83
!hex a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
!hex c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf

View File

@ -45,7 +45,7 @@
!pet $93,"PETSCII with ",$96,"control",$05," codes",$0d
!byte $83
!text " !",$22,"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVW"
!text "XYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!text "XYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
!byte $83
!hex a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
!hex c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf

View File

@ -155,6 +155,8 @@ namespace SourceGen.Tests {
ReportProgress(successCount + " of " + testCases.Count + " tests passed\r\n");
}
PrintAsmVersions();
return mResults;
}
@ -317,6 +319,7 @@ namespace SourceGen.Tests {
ReportErrMsg("assembler returned code=" + asmResults.ExitCode);
ReportFailure();
didFail = true;
results.AsmResults = asmResults;
continue;
}
@ -373,6 +376,17 @@ namespace SourceGen.Tests {
return !didFail;
}
private void PrintAsmVersions() {
ReportProgress("\nTested assemblers:");
IEnumerator<AssemblerInfo> iter = AssemblerInfo.GetInfoEnumerator();
while (iter.MoveNext()) {
AssemblerInfo info = iter.Current;
AssemblerVersion version = AssemblerVersionCache.GetVersion(info.AssemblerId);
ReportProgress(" " + info.Name + " v" + version.VersionStr);
}
ReportProgress("\n");
}
/// <summary>
/// Gets a copy of the AppSettings with a standard set of formatting options (e.g. lower
/// case for everything).