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:
parent
d97d715ae3
commit
1ad9caa783
@ -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
746
SourceGen/AsmGen/AsmAcme.cs
Normal 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
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user