1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-12-01 22:50:35 +00:00

First pass at ACME support

I managed to work around most of the quirks, but there's still an
issue with 65816 code.

Also, enabled word wrapping in the AsmGen text boxes.
This commit is contained in:
Andy McFadden 2019-08-03 20:54:07 -07:00
parent d97d715ae3
commit 1ad9caa783
11 changed files with 808 additions and 32 deletions

View File

@ -59,6 +59,7 @@ namespace Asm65 {
// functional changes to assembly output // functional changes to assembly output
public bool mSuppressHexNotation; // omit '$' before hex digits public bool mSuppressHexNotation; // omit '$' before hex digits
public bool mSuppressImpliedAcc; // emit just "LSR" rather than "LSR A"?
public bool mAllowHighAsciiCharConst; // can we do high-ASCII character constants? public bool mAllowHighAsciiCharConst; // can we do high-ASCII character constants?
// (this might need to be generalized) // (this might need to be generalized)
@ -66,6 +67,7 @@ namespace Asm65 {
public string mForceDirectOperandPrefix; // these may be null or empty public string mForceDirectOperandPrefix; // these may be null or empty
public string mForceAbsOpcodeSuffix; public string mForceAbsOpcodeSuffix;
public string mForceAbsOperandPrefix; public string mForceAbsOperandPrefix;
public string mForceDirectOpcodeSuffix;
public string mForceLongOpcodeSuffix; public string mForceLongOpcodeSuffix;
public string mForceLongOperandPrefix; public string mForceLongOperandPrefix;
@ -117,7 +119,7 @@ namespace Asm65 {
// Bits and pieces. // Bits and pieces.
char mHexFmtChar; char mHexFmtChar;
string mHexPrefix; string mHexPrefix;
char mAccChar; string mAccChar;
char mXregChar; char mXregChar;
char mYregChar; char mYregChar;
char mSregChar; char mSregChar;
@ -250,9 +252,9 @@ namespace Asm65 {
mHexPrefix = "$"; mHexPrefix = "$";
} }
if (mFormatConfig.mUpperOperandA) { if (mFormatConfig.mUpperOperandA) {
mAccChar = 'A'; mAccChar = "A";
} else { } else {
mAccChar = 'a'; mAccChar = "a";
} }
if (mFormatConfig.mUpperOperandXY) { if (mFormatConfig.mUpperOperandXY) {
mXregChar = 'X'; mXregChar = 'X';
@ -487,7 +489,9 @@ namespace Asm65 {
public string FormatMnemonic(string mnemonic, OpDef.WidthDisambiguation wdis) { public string FormatMnemonic(string mnemonic, OpDef.WidthDisambiguation wdis) {
string opcodeStr = mnemonic; string opcodeStr = mnemonic;
if (wdis == OpDef.WidthDisambiguation.ForceDirect) { if (wdis == OpDef.WidthDisambiguation.ForceDirect) {
// nothing to do for opcode if (!string.IsNullOrEmpty(mFormatConfig.mForceDirectOpcodeSuffix)) {
opcodeStr += mFormatConfig.mForceDirectOpcodeSuffix;
}
} else if (wdis == OpDef.WidthDisambiguation.ForceAbs) { } else if (wdis == OpDef.WidthDisambiguation.ForceAbs) {
if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOpcodeSuffix)) { if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOpcodeSuffix)) {
opcodeStr += mFormatConfig.mForceAbsOpcodeSuffix; opcodeStr += mFormatConfig.mForceAbsOpcodeSuffix;
@ -573,7 +577,11 @@ namespace Asm65 {
fmt = "[{0}]"; fmt = "[{0}]";
break; break;
case AddressMode.Acc: case AddressMode.Acc:
fmt = "" + mAccChar; if (mFormatConfig.mSuppressImpliedAcc) {
fmt = string.Empty;
} else {
fmt = mAccChar;
}
break; break;
case AddressMode.DPIndIndexY: case AddressMode.DPIndIndexY:
fmt = "({0})," + mYregChar; fmt = "({0})," + mYregChar;

746
SourceGen/AsmGen/AsmAcme.cs Normal file
View File

@ -0,0 +1,746 @@
/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using Asm65;
using CommonUtil;
namespace SourceGen.AsmGen {
#region IGenerator
/// <summary>
/// Generate source code compatible with the ACME assembler
/// (https://sourceforge.net/projects/acme-crossass/).
/// </summary>
public class GenAcme : IGenerator {
private const string ASM_FILE_SUFFIX = "_acme.a"; // must start with underscore
private const int MAX_OPERAND_LEN = 64;
private const string CLOSE_PSEUDOPC = "} ;!pseudopc";
// IGenerator
public DisasmProject Project { get; private set; }
// IGenerator
public Formatter SourceFormatter { get; private set; }
// IGenerator
public AppSettings Settings { get; private set; }
// IGenerator
public AssemblerQuirks Quirks { get; private set; }
// IGenerator
public LabelLocalizer Localizer { get { return mLocalizer; } }
/// <summary>
/// Working directory, i.e. where we write our output file(s).
/// </summary>
private string mWorkDirectory;
/// <summary>
/// If set, long labels get their own line.
/// </summary>
private bool mLongLabelNewLine;
/// <summary>
/// Output column widths.
/// </summary>
private int[] mColumnWidths;
/// <summary>
/// Base filename. Typically the project file name without the ".dis65" extension.
/// </summary>
private string mFileNameBase;
/// <summary>
/// StringBuilder to use when composing a line. Held here to reduce allocations.
/// </summary>
private StringBuilder mLineBuilder = new StringBuilder(100);
/// <summary>
/// Label localization helper.
/// </summary>
private LabelLocalizer mLocalizer;
/// <summary>
/// Stream to send the output to.
/// </summary>
private StreamWriter mOutStream;
/// <summary>
/// Holds detected version of configured assembler.
/// </summary>
private CommonUtil.Version mAsmVersion = CommonUtil.Version.NO_VERSION;
// Version we're coded against.
private static CommonUtil.Version V0_96_4 = new CommonUtil.Version(0, 96, 4);
// Set if we're inside a "pseudopc" block, which will need to be closed.
private bool mInPseudoPcBlock = false;
// Pseudo-op string constants.
private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames() {
EquDirective = "=",
OrgDirective = "!pseudopc",
//RegWidthDirective // !al, !as, !rl, !rs
DefineData1 = "!byte",
DefineData2 = "!word",
DefineData3 = "!24",
DefineData4 = "!32",
//DefineBigData2
//DefineBigData3
//DefineBigData4
Fill = "!fill",
Dense = "!hex",
StrGeneric = "!text", // can use !xor for high ASCII
//StrReverse
//StrNullTerm
//StrLen8
//StrLen16
//StrDci
//StrDciReverse
};
// IGenerator
public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps,
out Formatter.FormatConfig formatConfig) {
pseudoOps = sDataOpNames.GetCopy();
formatConfig = new Formatter.FormatConfig();
SetFormatConfigValues(ref formatConfig);
}
// IGenerator
public void Configure(DisasmProject project, string workDirectory, string fileNameBase,
AssemblerVersion asmVersion, AppSettings settings) {
Debug.Assert(project != null);
Debug.Assert(!string.IsNullOrEmpty(workDirectory));
Debug.Assert(!string.IsNullOrEmpty(fileNameBase));
Project = project;
// 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
// uses 16-bit addressing to zero-page labels for backward references if there
// are any forward references at all. The easy way to deal with this is to make
// all zero-page label references have explicit widths.
//
// Example:
// * = $1000
// jmp zero
// !pseudopc $0000 {
// zero nop
// lda zero
// rts
// }
Quirks = new AssemblerQuirks();
Quirks.SinglePassAssembler = true;
Quirks.SinglePassNoLabelCorrection = true;
mWorkDirectory = workDirectory;
mFileNameBase = fileNameBase;
Settings = settings;
mLongLabelNewLine = Settings.GetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, false);
AssemblerConfig config = AssemblerConfig.GetConfig(settings,
AssemblerInfo.Id.Acme);
mColumnWidths = (int[])config.ColumnWidths.Clone();
}
/// <summary>
/// Configures the assembler-specific format items.
/// </summary>
private void SetFormatConfigValues(ref Formatter.FormatConfig config) {
config.mSuppressImpliedAcc = true;
config.mForceDirectOpcodeSuffix = "+1";
config.mForceAbsOpcodeSuffix = "+2";
config.mForceLongOpcodeSuffix = "+3";
config.mForceDirectOperandPrefix = string.Empty;
config.mForceAbsOperandPrefix = string.Empty;
config.mForceLongOperandPrefix = string.Empty;
config.mEndOfLineCommentDelimiter = ";";
config.mFullLineCommentDelimiterBase = ";";
config.mBoxLineCommentDelimiter = ";";
config.mAllowHighAsciiCharConst = false;
config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Common;
}
// IGenerator
public List<string> GenerateSource(BackgroundWorker worker) {
List<string> pathNames = new List<string>(1);
string fileName = mFileNameBase + ASM_FILE_SUFFIX;
string pathName = Path.Combine(mWorkDirectory, fileName);
pathNames.Add(pathName);
Formatter.FormatConfig config = new Formatter.FormatConfig();
GenCommon.ConfigureFormatterFromSettings(Settings, ref config);
SetFormatConfigValues(ref config);
SourceFormatter = new Formatter(config);
string msg = string.Format(Res.Strings.PROGRESS_GENERATING_FMT, pathName);
worker.ReportProgress(0, msg);
mLocalizer = new LabelLocalizer(Project);
if (!Settings.GetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, false)) {
mLocalizer.LocalPrefix = "@";
mLocalizer.Analyze();
}
// Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {
mOutStream = sw;
if (Settings.GetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, false)) {
OutputLine(SourceFormatter.FullLineCommentDelimiter +
string.Format(Res.Strings.GENERATED_FOR_VERSION_FMT,
"acme", V0_96_4, AsmAcme.OPTIONS));
}
GenCommon.Generate(this, sw, worker);
if (mInPseudoPcBlock) {
OutputLine(string.Empty, CLOSE_PSEUDOPC, string.Empty, string.Empty);
}
}
mOutStream = null;
return pathNames;
}
// IGenerator
public void OutputAsmConfig() {
CpuDef cpuDef = Project.CpuDef;
string cpuStr;
if (cpuDef.Type == CpuDef.CpuType.Cpu65816) {
cpuStr = "65816";
} else if (cpuDef.Type == CpuDef.CpuType.Cpu65C02) {
cpuStr = "65c02";
} else if (cpuDef.Type == CpuDef.CpuType.Cpu6502 && cpuDef.HasUndocumented) {
cpuStr = "6510";
} else {
cpuStr = "6502";
}
OutputLine(string.Empty, SourceFormatter.FormatPseudoOp("!cpu"), cpuStr, string.Empty);
}
// IGenerator
public string ModifyOpcode(int offset, OpDef op) {
if (op.IsUndocumented) {
if (Project.CpuDef.Type == CpuDef.CpuType.Cpu65C02) {
// none of the "LDD" stuff is handled
return null;
}
if ((op.Mnemonic == OpName.ANC && op.Opcode != 0x0b) ||
(op.Mnemonic == OpName.JAM && op.Opcode != 0x02)) {
// There are multiple opcodes that match the mnemonic. Output the
// mnemonic for the first one and hex for the rest.
return null;
} else if (op.Mnemonic == OpName.NOP || op.Mnemonic == OpName.DOP ||
op.Mnemonic == OpName.TOP) {
// the various undocumented no-ops aren't handled
return null;
} else if (op.Mnemonic == OpName.SBC) {
// this is the alternate reference to SBC
return null;
} else if (op == OpDef.OpALR_Imm) {
// ACME wants "ASR" instead for $4b
return "asr";
} else if (op == OpDef.OpLAX_Imm) {
// ACME spits out an error on $ab
return null;
}
}
if (op == OpDef.OpWDM_WDM) {
// ACME doesn't like this to have an operand. Output as hex.
return null;
}
return string.Empty; // indicate original is fine
}
// IGenerator
public void GenerateShortSequence(int offset, int length, out string opcode,
out string operand) {
Debug.Assert(length >= 1 && length <= 4);
// Use a comma-separated list of individual hex bytes.
opcode = sDataOpNames.DefineData1;
StringBuilder sb = new StringBuilder(length * 4);
for (int i = 0; i < length; i++) {
if (i != 0) {
sb.Append(',');
}
sb.Append(SourceFormatter.FormatHexValue(Project.FileData[offset + i], 2));
}
operand = sb.ToString();
}
// IGenerator
public void OutputDataOp(int offset) {
Formatter formatter = SourceFormatter;
byte[] data = Project.FileData;
Anattrib attr = Project.GetAnattrib(offset);
string labelStr = string.Empty;
if (attr.Symbol != null) {
labelStr = mLocalizer.ConvLabel(attr.Symbol.Label);
}
string commentStr = SourceFormatter.FormatEolComment(Project.Comments[offset]);
string opcodeStr, operandStr;
FormatDescriptor dfd = attr.DataDescriptor;
Debug.Assert(dfd != null);
int length = dfd.Length;
Debug.Assert(length > 0);
bool multiLine = false;
switch (dfd.FormatType) {
case FormatDescriptor.Type.Default:
if (length != 1) {
Debug.Assert(false);
length = 1;
}
opcodeStr = sDataOpNames.DefineData1;
int operand = RawData.GetWord(data, offset, length, false);
operandStr = formatter.FormatHexValue(operand, length * 2);
break;
case FormatDescriptor.Type.NumericLE:
opcodeStr = sDataOpNames.GetDefineData(length);
operand = RawData.GetWord(data, offset, length, false);
operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable,
mLocalizer.LabelMap, dfd, operand, length,
PseudoOp.FormatNumericOpFlags.None);
break;
case FormatDescriptor.Type.NumericBE:
opcodeStr = sDataOpNames.GetDefineBigData(length);
if (opcodeStr == null) {
// Nothing defined, output as comma-separated single-byte values.
GenerateShortSequence(offset, length, out opcodeStr, out operandStr);
} else {
operand = RawData.GetWord(data, offset, length, true);
operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable,
mLocalizer.LabelMap, dfd, operand, length,
PseudoOp.FormatNumericOpFlags.None);
}
break;
case FormatDescriptor.Type.Fill:
opcodeStr = sDataOpNames.Fill;
operandStr = length + "," + formatter.FormatHexValue(data[offset], 2);
break;
case FormatDescriptor.Type.Dense:
multiLine = true;
opcodeStr = operandStr = null;
OutputDenseHex(offset, length, labelStr, commentStr);
break;
case FormatDescriptor.Type.String:
multiLine = true;
opcodeStr = operandStr = null;
OutputString(offset, labelStr, commentStr);
break;
default:
opcodeStr = "???";
operandStr = "***";
break;
}
if (!multiLine) {
opcodeStr = formatter.FormatPseudoOp(opcodeStr);
OutputLine(labelStr, opcodeStr, operandStr, commentStr);
}
}
private void OutputDenseHex(int offset, int length, string labelStr, string commentStr) {
Formatter formatter = SourceFormatter;
byte[] data = Project.FileData;
int maxPerLine = MAX_OPERAND_LEN / 2;
string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.Dense);
for (int i = 0; i < length; i += maxPerLine) {
int subLen = length - i;
if (subLen > maxPerLine) {
subLen = maxPerLine;
}
string operandStr = formatter.FormatDenseHex(data, offset + i, subLen);
OutputLine(labelStr, opcodeStr, operandStr, commentStr);
labelStr = commentStr = string.Empty;
}
}
/// <summary>
/// Outputs formatted data in an unformatted way, because the code generator couldn't
/// figure out how to do something better.
/// </summary>
private void OutputNoJoy(int offset, int length, string labelStr, string commentStr) {
byte[] data = Project.FileData;
Debug.Assert(length > 0);
Debug.Assert(offset >= 0 && offset < data.Length);
bool singleValue = true;
byte val = data[offset];
for (int i = 1; i < length; i++) {
if (data[offset + i] != val) {
singleValue = false;
break;
}
}
if (singleValue) {
string opcodeStr = SourceFormatter.FormatPseudoOp(sDataOpNames.Fill);
string operandStr = length + "," + SourceFormatter.FormatHexValue(val, 2);
OutputLine(labelStr, opcodeStr, operandStr, commentStr);
} else {
OutputDenseHex(offset, length, labelStr, commentStr);
}
}
// IGenerator
public void OutputEquDirective(string name, string valueStr, string comment) {
OutputLine(name, SourceFormatter.FormatPseudoOp(sDataOpNames.EquDirective),
valueStr, SourceFormatter.FormatEolComment(comment));
}
// IGenerator
public void OutputOrgDirective(int offset, int address) {
// For the first one, set the "real" PC. For all subsequent directives, set the
// "pseudo" PC.
if (offset == 0) {
OutputLine("*", "=", SourceFormatter.FormatHexValue(address, 4), string.Empty);
} else {
if (mInPseudoPcBlock) {
// close previous block
OutputLine(string.Empty, CLOSE_PSEUDOPC, string.Empty, string.Empty);
}
OutputLine(string.Empty, sDataOpNames.OrgDirective,
SourceFormatter.FormatHexValue(address, 4) + " {", string.Empty);
mInPseudoPcBlock = true;
}
}
// IGenerator
public void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX) {
if (prevM != newM) {
string mop = (newM == 0) ? "!al" : "!as";
OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(mop),
string.Empty, string.Empty);
}
if (prevX != newX) {
string xop = (newX == 0) ? "!rl" : "!rs";
OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(xop),
string.Empty, string.Empty);
}
}
// IGenerator
public void OutputLine(string fullLine) {
mOutStream.WriteLine(fullLine);
}
// IGenerator
public void OutputLine(string label, string opcode, string operand, string comment) {
// Break the line if the label is long and it's not a .EQ directive.
if (!string.IsNullOrEmpty(label) &&
!string.Equals(opcode, sDataOpNames.EquDirective,
StringComparison.InvariantCultureIgnoreCase)) {
if (mLongLabelNewLine && label.Length >= mColumnWidths[0]) {
mOutStream.WriteLine(label);
label = string.Empty;
}
}
mLineBuilder.Clear();
TextUtil.AppendPaddedString(mLineBuilder, label, mColumnWidths[0]);
TextUtil.AppendPaddedString(mLineBuilder, opcode, mColumnWidths[0] + mColumnWidths[1]);
TextUtil.AppendPaddedString(mLineBuilder, operand,
mColumnWidths[0] + mColumnWidths[1] + mColumnWidths[2]);
if (string.IsNullOrEmpty(comment)) {
// Trim trailing spaces off of opcode or operand. If they want trailing
// spaces at the end of a comment, that's fine.
CommonUtil.TextUtil.TrimEnd(mLineBuilder);
} else {
mLineBuilder.Append(comment);
}
mOutStream.WriteLine(mLineBuilder.ToString());
}
private void OutputString(int offset, string labelStr, string commentStr) {
// Normal ASCII strings are handled with a simple !text directive.
//
// We could probably do something fancy with !xor to
// make high-ASCII work nicely.
Formatter formatter = SourceFormatter;
byte[] data = Project.FileData;
Anattrib attr = Project.GetAnattrib(offset);
FormatDescriptor dfd = attr.DataDescriptor;
Debug.Assert(dfd != null);
Debug.Assert(dfd.FormatType == FormatDescriptor.Type.String);
Debug.Assert(dfd.Length > 0);
bool highAscii = false;
int leadingBytes = 0;
int trailingBytes = 0;
bool showLeading = false;
bool showTrailing = false;
switch (dfd.FormatSubType) {
case FormatDescriptor.SubType.None:
highAscii = (data[offset] & 0x80) != 0;
break;
case FormatDescriptor.SubType.Dci:
highAscii = (data[offset] & 0x80) != 0;
trailingBytes = 1;
showTrailing = true;
break;
case FormatDescriptor.SubType.Reverse:
highAscii = (data[offset] & 0x80) != 0;
break;
case FormatDescriptor.SubType.DciReverse:
highAscii = (data[offset + dfd.Length - 1] & 0x80) != 0;
leadingBytes = 1;
showLeading = true;
break;
case FormatDescriptor.SubType.CString:
highAscii = (data[offset] & 0x80) != 0;
trailingBytes = 1;
showTrailing = true;
break;
case FormatDescriptor.SubType.L8String:
if (dfd.Length > 1) {
highAscii = (data[offset + 1] & 0x80) != 0;
}
leadingBytes = 1;
showLeading = true;
break;
case FormatDescriptor.SubType.L16String:
if (dfd.Length > 2) {
highAscii = (data[offset + 2] & 0x80) != 0;
}
leadingBytes = 2;
showLeading = true;
break;
default:
Debug.Assert(false);
return;
}
char delim = '"';
StringGather gath = null;
// Run the string through so we can see if it'll fit on one line. As a minor
// optimization, we skip this step for "generic" strings, which are probably
// the most common thing.
if (dfd.FormatSubType != FormatDescriptor.SubType.None || highAscii) {
gath = new StringGather(this, labelStr, "???", commentStr, delim,
delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, true);
FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading,
trailingBytes, showTrailing);
Debug.Assert(gath.NumLinesOutput > 0);
}
string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.StrGeneric);
switch (dfd.FormatSubType) {
case FormatDescriptor.SubType.None:
// TODO(someday): something fancy with encodings to handle high-ASCII text?
break;
case FormatDescriptor.SubType.Dci:
case FormatDescriptor.SubType.Reverse:
case FormatDescriptor.SubType.DciReverse:
// Fully configured above.
break;
case FormatDescriptor.SubType.CString:
case FormatDescriptor.SubType.L8String:
case FormatDescriptor.SubType.L16String:
// Implement as macro?
break;
default:
Debug.Assert(false);
return;
}
if (highAscii) {
OutputNoJoy(offset, dfd.Length, labelStr, commentStr);
return;
}
// Create a new StringGather, with the final opcode choice.
gath = new StringGather(this, labelStr, opcodeStr, commentStr, delim,
delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, false);
FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading,
trailingBytes, showTrailing);
}
/// <summary>
/// Feeds the bytes into the StringGather.
/// </summary>
private void FeedGath(StringGather gath, byte[] data, int offset, int length,
int leadingBytes, bool showLeading, int trailingBytes, bool showTrailing) {
int startOffset = offset;
int strEndOffset = offset + length - trailingBytes;
if (showLeading) {
while (leadingBytes-- > 0) {
gath.WriteByte(data[offset++]);
}
} else {
offset += leadingBytes;
}
for (; offset < strEndOffset; offset++) {
gath.WriteChar((char)(data[offset] & 0x7f));
}
while (showTrailing && trailingBytes-- > 0) {
gath.WriteByte(data[offset++]);
}
gath.Finish();
}
}
#endregion IGenerator
#region IAssembler
/// <summary>
/// Cross-assembler execution interface.
/// </summary>
public class AsmAcme : IAssembler {
public const string OPTIONS = "";
// Paths from generator.
private List<string> mPathNames;
// Directory to make current before executing assembler.
private string mWorkDirectory;
// IAssembler
public void GetExeIdentifiers(out string humanName, out string exeName) {
humanName = "ACME Assembler";
exeName = "acme";
}
// IAssembler
public AssemblerConfig GetDefaultConfig() {
return new AssemblerConfig(string.Empty, new int[] { 8, 8, 11, 73 });
}
// IAssembler
public AssemblerVersion QueryVersion() {
AssemblerConfig config =
AssemblerConfig.GetConfig(AppSettings.Global, AssemblerInfo.Id.Acme);
if (config == null || string.IsNullOrEmpty(config.ExecutablePath)) {
return null;
}
ShellCommand cmd = new ShellCommand(config.ExecutablePath, "--version",
Directory.GetCurrentDirectory(), null);
cmd.Execute();
if (string.IsNullOrEmpty(cmd.Stdout)) {
return null;
}
// Windows - Stdout: "This is ACME, release 0.96.4 ("Fenchurch"), 22 Dec 2017 ..."
// Linux - Stderr: "This is ACME, release 0.96.4 ("Fenchurch"), 20 Apr 2019 ..."
const string PREFIX = "release ";
string str = cmd.Stdout;
int start = str.IndexOf(PREFIX);
int end = (start < 0) ? -1 : str.IndexOf(' ', start + PREFIX.Length + 1);
if (start < 0 || end < 0 || start + PREFIX.Length >= end) {
Debug.WriteLine("Couldn't find version in " + str);
return null;
}
start += PREFIX.Length;
string versionStr = str.Substring(start, end - start);
CommonUtil.Version version = CommonUtil.Version.Parse(versionStr);
if (!version.IsValid) {
return null;
}
return new AssemblerVersion(versionStr, version);
}
// IAssembler
public void Configure(List<string> pathNames, string workDirectory) {
// Clone pathNames, in case the caller decides to modify the original.
mPathNames = new List<string>(pathNames.Count);
foreach (string str in pathNames) {
mPathNames.Add(str);
}
mWorkDirectory = workDirectory;
}
// IAssembler
public AssemblerResults RunAssembler(BackgroundWorker worker) {
// Reduce input file to a partial path if possible. This is really just to make
// what we display to the user a little easier to read.
string pathName = mPathNames[0];
if (pathName.StartsWith(mWorkDirectory)) {
pathName = pathName.Remove(0, mWorkDirectory.Length + 1);
} else {
// Unexpected, but shouldn't be a problem.
Debug.WriteLine("NOTE: source file is not in work directory");
}
AssemblerConfig config =
AssemblerConfig.GetConfig(AppSettings.Global, AssemblerInfo.Id.Acme);
if (string.IsNullOrEmpty(config.ExecutablePath)) {
Debug.WriteLine("Assembler not configured");
return null;
}
worker.ReportProgress(0, Res.Strings.PROGRESS_ASSEMBLING);
// Output file name is source file name with the ".a".
string outFileName = pathName.Substring(0, pathName.Length - 2);
// Wrap pathname in quotes in case it has spaces.
// (Do we need to shell-escape quotes in the pathName?)
ShellCommand cmd = new ShellCommand(config.ExecutablePath,
OPTIONS + " -o \"" + outFileName + "\"" + " \"" + pathName + "\"" ,
mWorkDirectory, null);
cmd.Execute();
// Can't really do anything with a "cancel" request.
// Output filename is the input filename without the ".a". Since the filename
// was generated by us we can be confident in the format.
string outputFile = mPathNames[0].Substring(0, mPathNames[0].Length - 2);
return new AssemblerResults(cmd.FullCommandLine, cmd.ExitCode, cmd.Stdout,
cmd.Stderr, outputFile);
}
}
#endregion IAssembler
}

View File

@ -125,18 +125,7 @@ namespace SourceGen.AsmGen {
// IGenerator // IGenerator
public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps, public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps,
out Formatter.FormatConfig formatConfig) { out Formatter.FormatConfig formatConfig) {
pseudoOps = new PseudoOp.PseudoOpNames() { pseudoOps = sDataOpNames.GetCopy();
EquDirective = "=",
OrgDirective = ".org",
DefineData1 = ".byte",
DefineData2 = ".word",
DefineData3 = ".faraddr",
DefineData4 = ".dword",
DefineBigData2 = ".dbyt",
Fill = ".res",
StrGeneric = ".byte",
StrNullTerm = ".asciiz",
};
formatConfig = new Formatter.FormatConfig(); formatConfig = new Formatter.FormatConfig();
SetFormatConfigValues(ref formatConfig); SetFormatConfigValues(ref formatConfig);
@ -182,6 +171,7 @@ namespace SourceGen.AsmGen {
/// Configures the assembler-specific format items. /// Configures the assembler-specific format items.
/// </summary> /// </summary>
private void SetFormatConfigValues(ref Formatter.FormatConfig config) { private void SetFormatConfigValues(ref Formatter.FormatConfig config) {
config.mForceDirectOpcodeSuffix = string.Empty;
config.mForceAbsOpcodeSuffix = string.Empty; config.mForceAbsOpcodeSuffix = string.Empty;
config.mForceLongOpcodeSuffix = string.Empty; config.mForceLongOpcodeSuffix = string.Empty;
config.mForceDirectOperandPrefix = "z:"; // zero config.mForceDirectOperandPrefix = "z:"; // zero

View File

@ -183,6 +183,7 @@ namespace SourceGen.AsmGen {
/// Configures the assembler-specific format items. /// Configures the assembler-specific format items.
/// </summary> /// </summary>
private void SetFormatConfigValues(ref Formatter.FormatConfig config) { private void SetFormatConfigValues(ref Formatter.FormatConfig config) {
config.mForceDirectOpcodeSuffix = string.Empty;
config.mForceAbsOpcodeSuffix = ":"; config.mForceAbsOpcodeSuffix = ":";
config.mForceLongOpcodeSuffix = "l"; config.mForceLongOpcodeSuffix = "l";
config.mForceDirectOperandPrefix = string.Empty; config.mForceDirectOperandPrefix = string.Empty;

View File

@ -137,7 +137,7 @@ namespace SourceGen.AsmGen {
// IGenerator // IGenerator
public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps, public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps,
out Formatter.FormatConfig formatConfig) { out Formatter.FormatConfig formatConfig) {
pseudoOps = sDataOpNames; pseudoOps = sDataOpNames.GetCopy();
formatConfig = new Formatter.FormatConfig(); formatConfig = new Formatter.FormatConfig();
SetFormatConfigValues(ref formatConfig); SetFormatConfigValues(ref formatConfig);
@ -177,6 +177,7 @@ namespace SourceGen.AsmGen {
config.mBankSelectBackQuote = true; config.mBankSelectBackQuote = true;
config.mForceDirectOpcodeSuffix = string.Empty;
config.mForceAbsOpcodeSuffix = string.Empty; config.mForceAbsOpcodeSuffix = string.Empty;
config.mForceLongOpcodeSuffix = string.Empty; config.mForceLongOpcodeSuffix = string.Empty;
config.mForceDirectOperandPrefix = string.Empty; config.mForceDirectOperandPrefix = string.Empty;

View File

@ -32,6 +32,7 @@ namespace SourceGen.AsmGen {
public enum Id { public enum Id {
Unknown = 0, Unknown = 0,
Tass64, Tass64,
Acme,
Cc65, Cc65,
Merlin32, Merlin32,
} }
@ -44,6 +45,7 @@ namespace SourceGen.AsmGen {
private static AssemblerInfo[] sInfo = new AssemblerInfo[] { private static AssemblerInfo[] sInfo = new AssemblerInfo[] {
new AssemblerInfo(Id.Unknown, "???", null, null), new AssemblerInfo(Id.Unknown, "???", null, null),
new AssemblerInfo(Id.Tass64, "64tass", typeof(GenTass64), typeof(AsmTass64)), new AssemblerInfo(Id.Tass64, "64tass", typeof(GenTass64), typeof(AsmTass64)),
new AssemblerInfo(Id.Acme, "ACME", typeof(GenAcme), typeof(AsmAcme)),
new AssemblerInfo(Id.Cc65, "cc65", typeof(GenCc65), typeof(AsmCc65)), new AssemblerInfo(Id.Cc65, "cc65", typeof(GenCc65), typeof(AsmCc65)),
new AssemblerInfo(Id.Merlin32, "Merlin 32", typeof(GenMerlin32), typeof(AsmMerlin32)), new AssemblerInfo(Id.Merlin32, "Merlin 32", typeof(GenMerlin32), typeof(AsmMerlin32)),
}; };
@ -165,7 +167,7 @@ namespace SourceGen.AsmGen {
public override string ToString() { public override string ToString() {
return "Asm " + ((int)AssemblerId).ToString() + ": " + Name; return "AsmInfo " + ((int)AssemblerId).ToString() + ": " + Name;
} }
} }
} }

View File

@ -175,8 +175,10 @@ namespace SourceGen.AsmGen {
(op.AddrMode == OpDef.AddressMode.DP || (op.AddrMode == OpDef.AddressMode.DP ||
op.AddrMode == OpDef.AddressMode.DPIndexX) || op.AddrMode == OpDef.AddressMode.DPIndexX) ||
op.AddrMode == OpDef.AddressMode.DPIndexY) { op.AddrMode == OpDef.AddressMode.DPIndexY) {
// Could be a forward reference to a direct-page label. // Could be a forward reference to a direct-page label. For ACME, we don't
if (IsForwardLabelReference(gen, offset)) { // care if it's forward or not.
if ((gen.Quirks.SinglePassNoLabelCorrection && IsLabelReference(gen, offset)) ||
IsForwardLabelReference(gen, offset)) {
wdis = OpDef.WidthDisambiguation.ForceDirect; wdis = OpDef.WidthDisambiguation.ForceDirect;
} }
} }
@ -315,19 +317,36 @@ namespace SourceGen.AsmGen {
/// <param name="offset">Offset of instruction opcode.</param> /// <param name="offset">Offset of instruction opcode.</param>
/// <returns>True if the instruction's operand is a forward reference to a label.</returns> /// <returns>True if the instruction's operand is a forward reference to a label.</returns>
private static bool IsForwardLabelReference(IGenerator gen, int offset) { private static bool IsForwardLabelReference(IGenerator gen, int offset) {
return (GetLabelOffsetFromOperand(gen, offset) > offset);
}
/// <summary>
/// Determines whether the instruction at the specified offset has an operand
/// that references a symbol.
/// </summary>
/// <param name="gen">Source generator reference.</param>
/// <param name="offset">Offset of instruction opcode.</param>
/// <returns>True if the instruction's operand is a forward reference to a label.</returns>
private static bool IsLabelReference(IGenerator gen, int offset) {
return (GetLabelOffsetFromOperand(gen, offset) >= 0);
}
/// <summary>
/// Determines the offset of the label that the operand's symbol references.
/// </summary>
/// <param name="gen">Source generator reference.</param>
/// <param name="offset">Offset of instruction opcode.</param>
/// <returns>The offset of the label, or -1 if the operand isn't a symbolic reference
/// to a known label.</returns>
private static int GetLabelOffsetFromOperand(IGenerator gen, int offset) {
DisasmProject proj = gen.Project; DisasmProject proj = gen.Project;
Debug.Assert(proj.GetAnattrib(offset).IsInstructionStart); Debug.Assert(proj.GetAnattrib(offset).IsInstructionStart);
FormatDescriptor dfd = proj.GetAnattrib(offset).DataDescriptor; FormatDescriptor dfd = proj.GetAnattrib(offset).DataDescriptor;
if (dfd == null || !dfd.HasSymbol) { if (dfd == null || !dfd.HasSymbol) {
return false; return -1;
} }
int labelOffset = proj.FindLabelOffsetByName(dfd.SymbolRef.Label); return proj.FindLabelOffsetByName(dfd.SymbolRef.Label);
if (labelOffset <= offset) {
// Doesn't exist, or is backward reference.
return false;
}
return true;
} }
/// <summary> /// <summary>

View File

@ -185,8 +185,14 @@ namespace SourceGen.AsmGen {
public bool NoPcRelBankWrap { get; set; } public bool NoPcRelBankWrap { get; set; }
/// <summary> /// <summary>
/// Is the assembler implemented as a single pass? /// Is the assembler implemented as a single pass? (e.g. cc65)
/// </summary> /// </summary>
public bool SinglePassAssembler { get; set; } public bool SinglePassAssembler { get; set; }
/// <summary>
/// Is the assembler's label width determination performed only in the first pass,
/// and not corrected when the actual width is determined?
/// </summary>
public bool SinglePassNoLabelCorrection { get; set; }
} }
} }

View File

@ -80,7 +80,8 @@ limitations under the License.
</Grid> </Grid>
<TextBox Grid.Row="1" Name="previewTextBox" IsReadOnly="True" Margin="0,4,0,0" <TextBox Grid.Row="1" Name="previewTextBox" IsReadOnly="True" Margin="0,4,0,0"
FontFamily="{StaticResource GeneralMonoFont}" VerticalScrollBarVisibility="Auto"> FontFamily="{StaticResource GeneralMonoFont}" VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap">
sample text1 sample text1
</TextBox> </TextBox>
@ -96,7 +97,8 @@ limitations under the License.
</DockPanel> </DockPanel>
<TextBox DockPanel.Dock="Top" Name="cmdOutputTextBox" IsReadOnly="True" Margin="0,4,0,0" <TextBox DockPanel.Dock="Top" Name="cmdOutputTextBox" IsReadOnly="True" Margin="0,4,0,0"
FontFamily="{StaticResource GeneralMonoFont}" VerticalScrollBarVisibility="Auto"> FontFamily="{StaticResource GeneralMonoFont}" VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap">
sample text2 sample text2
</TextBox> </TextBox>
</DockPanel> </DockPanel>

View File

@ -62,6 +62,7 @@
<DependentUpon>App.xaml</DependentUpon> <DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="AsmGen\AsmAcme.cs" />
<Compile Include="AsmGen\AsmCc65.cs" /> <Compile Include="AsmGen\AsmCc65.cs" />
<Compile Include="AsmGen\AsmMerlin32.cs" /> <Compile Include="AsmGen\AsmMerlin32.cs" />
<Compile Include="AsmGen\AsmTass64.cs" /> <Compile Include="AsmGen\AsmTass64.cs" />

View File

@ -24,7 +24,7 @@ limitations under the License.
mc:Ignorable="d" mc:Ignorable="d"
Title="Source Generation Test" Title="Source Generation Test"
Width="640" Height="480" MinWidth="640" MinHeight="480" Width="640" Height="480" MinWidth="640" MinHeight="480"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" ResizeMode="CanResizeWithGrip"
Closing="Window_Closing"> Closing="Window_Closing">
<Window.Resources> <Window.Resources>