2019-08-04 03:54:07 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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 {
|
2019-08-04 21:48:42 +00:00
|
|
|
|
// The ACME docs say that ACME sources should use the ".a" extension. However, this
|
|
|
|
|
// is already used for static libraries on UNIX systems, which means filename
|
|
|
|
|
// completion in shells tends to ignore them, and it can cause confusion in
|
|
|
|
|
// makefile rules. Since ".S" is pretty universal for assembly language sources,
|
|
|
|
|
// I'm sticking with that.
|
|
|
|
|
private const string ASM_FILE_SUFFIX = "_acme.S"; // must start with underscore
|
2019-08-04 03:54:07 +00:00
|
|
|
|
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; } }
|
|
|
|
|
|
2020-10-17 23:10:48 +00:00
|
|
|
|
// IGenerator
|
|
|
|
|
public int StartOffset { get { return 0; } }
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
/// <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);
|
2021-07-31 21:56:17 +00:00
|
|
|
|
private static CommonUtil.Version V0_97 = new CommonUtil.Version(0, 97);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
// Set if we're inside a "pseudopc" block, which will need to be closed.
|
2019-08-04 21:48:42 +00:00
|
|
|
|
private bool mInPseudoPcBlock;
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
2021-07-31 21:42:36 +00:00
|
|
|
|
// v0.97 started treating '\' in constants as an escape character.
|
|
|
|
|
private bool mBackslashEscapes = true;
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
// Pseudo-op string constants.
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
private static PseudoOp.PseudoOpNames sDataOpNames =
|
|
|
|
|
new PseudoOp.PseudoOpNames(new Dictionary<string, string> {
|
|
|
|
|
{ "EquDirective", "=" },
|
2019-08-29 19:14:47 +00:00
|
|
|
|
//VarDirective
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
{ "OrgDirective", "!pseudopc" },
|
|
|
|
|
//RegWidthDirective // !al, !as, !rl, !rs
|
2020-07-09 22:17:47 +00:00
|
|
|
|
//DataBankDirective
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
{ "DefineData1", "!byte" },
|
|
|
|
|
{ "DefineData2", "!word" },
|
|
|
|
|
{ "DefineData3", "!24" },
|
|
|
|
|
{ "DefineData4", "!32" },
|
|
|
|
|
//DefineBigData2
|
|
|
|
|
//DefineBigData3
|
|
|
|
|
//DefineBigData4
|
|
|
|
|
{ "Fill", "!fill" },
|
|
|
|
|
{ "Dense", "!hex" },
|
2019-10-19 03:28:02 +00:00
|
|
|
|
//Junk
|
|
|
|
|
{ "Align", "!align" },
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
{ "StrGeneric", "!text" }, // can use !xor for high ASCII
|
|
|
|
|
//StrReverse
|
|
|
|
|
//StrNullTerm
|
|
|
|
|
//StrLen8
|
|
|
|
|
//StrLen16
|
|
|
|
|
//StrDci
|
|
|
|
|
});
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// IGenerator
|
|
|
|
|
public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps,
|
|
|
|
|
out Formatter.FormatConfig formatConfig) {
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
pseudoOps = sDataOpNames;
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
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;
|
2021-07-31 21:56:17 +00:00
|
|
|
|
Quirks = new AssemblerQuirks();
|
2021-07-31 21:42:36 +00:00
|
|
|
|
if (asmVersion != null) {
|
2021-07-31 21:56:17 +00:00
|
|
|
|
mAsmVersion = asmVersion.Version; // Use the actual version.
|
2021-07-31 21:42:36 +00:00
|
|
|
|
} else {
|
2021-07-31 21:56:17 +00:00
|
|
|
|
mAsmVersion = V0_97; // No assembler installed, use default.
|
2021-07-31 21:42:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// 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.SinglePassAssembler = true;
|
|
|
|
|
Quirks.SinglePassNoLabelCorrection = true;
|
2021-07-31 21:42:36 +00:00
|
|
|
|
if (mAsmVersion < V0_97) {
|
|
|
|
|
Quirks.BlockMoveArgsNoHash = true;
|
|
|
|
|
mBackslashEscapes = false;
|
|
|
|
|
}
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2020-07-20 01:39:27 +00:00
|
|
|
|
config.mOperandWrapLen = 64;
|
2019-08-04 03:54:07 +00:00
|
|
|
|
config.mForceDirectOpcodeSuffix = "+1";
|
|
|
|
|
config.mForceAbsOpcodeSuffix = "+2";
|
|
|
|
|
config.mForceLongOpcodeSuffix = "+3";
|
|
|
|
|
config.mForceDirectOperandPrefix = string.Empty;
|
|
|
|
|
config.mForceAbsOperandPrefix = string.Empty;
|
|
|
|
|
config.mForceLongOperandPrefix = string.Empty;
|
2019-11-13 01:24:41 +00:00
|
|
|
|
config.mLocalVariableLabelPrefix = ".";
|
2019-08-04 03:54:07 +00:00
|
|
|
|
config.mEndOfLineCommentDelimiter = ";";
|
|
|
|
|
config.mFullLineCommentDelimiterBase = ";";
|
|
|
|
|
config.mBoxLineCommentDelimiter = ";";
|
2019-11-13 01:24:41 +00:00
|
|
|
|
config.mNonUniqueLabelPrefix = "@";
|
2019-12-11 01:41:00 +00:00
|
|
|
|
config.mCommaSeparatedDense = false;
|
2019-08-04 03:54:07 +00:00
|
|
|
|
config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Common;
|
2019-08-14 22:25:09 +00:00
|
|
|
|
|
|
|
|
|
Formatter.DelimiterSet charSet = new Formatter.DelimiterSet();
|
|
|
|
|
charSet.Set(CharEncoding.Encoding.Ascii, Formatter.SINGLE_QUOTE_DELIM);
|
|
|
|
|
charSet.Set(CharEncoding.Encoding.HighAscii,
|
|
|
|
|
new Formatter.DelimiterDef(string.Empty, '\'', '\'', " | $80"));
|
|
|
|
|
config.mCharDelimiters = charSet;
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IGenerator
|
2020-10-17 23:10:48 +00:00
|
|
|
|
public GenerationResults GenerateSource(BackgroundWorker worker) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
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);
|
2019-11-17 01:15:03 +00:00
|
|
|
|
// While '.' labels are limited to the current zone, '@' labels are visible
|
|
|
|
|
// between global labels. (This is poorly documented.)
|
|
|
|
|
mLocalizer.LocalPrefix = "@";
|
2019-11-18 00:05:51 +00:00
|
|
|
|
mLocalizer.QuirkNoOpcodeMnemonics = true;
|
2019-11-17 01:15:03 +00:00
|
|
|
|
mLocalizer.Analyze();
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
// 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,
|
2021-07-31 21:56:17 +00:00
|
|
|
|
"acme", mAsmVersion, AsmAcme.OPTIONS));
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 21:48:42 +00:00
|
|
|
|
if (HasNonZeroBankCode()) {
|
|
|
|
|
// don't try
|
|
|
|
|
OutputLine(SourceFormatter.FullLineCommentDelimiter +
|
|
|
|
|
"ACME can't handle 65816 code that lives outside bank zero");
|
|
|
|
|
int orgAddr = Project.AddrMap.Get(0);
|
|
|
|
|
OutputOrgDirective(0, orgAddr);
|
|
|
|
|
OutputDenseHex(0, Project.FileData.Length, string.Empty, string.Empty);
|
|
|
|
|
} else {
|
|
|
|
|
GenCommon.Generate(this, sw, worker);
|
|
|
|
|
}
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
if (mInPseudoPcBlock) {
|
|
|
|
|
OutputLine(string.Empty, CLOSE_PSEUDOPC, string.Empty, string.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mOutStream = null;
|
|
|
|
|
|
2020-10-17 23:10:48 +00:00
|
|
|
|
return new GenerationResults(pathNames, string.Empty);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 21:48:42 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether the project has any code assembled outside bank zero.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool HasNonZeroBankCode() {
|
|
|
|
|
if (Project.CpuDef.HasAddr16) {
|
|
|
|
|
// Not possible on this CPU.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
foreach (AddressMap.AddressMapEntry ent in Project.AddrMap) {
|
|
|
|
|
if (ent.Addr > 0xffff) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// 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";
|
2020-10-11 21:35:17 +00:00
|
|
|
|
} else if (cpuDef.Type == CpuDef.CpuType.CpuW65C02) {
|
|
|
|
|
cpuStr = "w65c02";
|
2019-08-04 03:54:07 +00:00
|
|
|
|
} 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) {
|
2020-10-11 21:35:17 +00:00
|
|
|
|
if (Project.CpuDef.Type == CpuDef.CpuType.Cpu65C02 ||
|
|
|
|
|
Project.CpuDef.Type == CpuDef.CpuType.CpuW65C02) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
if (op == OpDef.OpWDM_WDM || op == OpDef.OpBRK_StackInt) {
|
|
|
|
|
// ACME doesn't like these to have an operand. Output as hex.
|
2019-08-04 03:54:07 +00:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return string.Empty; // indicate original is fine
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-20 21:05:17 +00:00
|
|
|
|
// IGenerator
|
|
|
|
|
public FormatDescriptor ModifyInstructionOperandFormat(int offset, FormatDescriptor dfd,
|
|
|
|
|
int operand) {
|
|
|
|
|
return dfd;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 20:30:25 +00:00
|
|
|
|
// IGenerator
|
|
|
|
|
public void UpdateCharacterEncoding(FormatDescriptor dfd) { }
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// 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,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
break;
|
|
|
|
|
case FormatDescriptor.Type.NumericBE:
|
|
|
|
|
opcodeStr = sDataOpNames.GetDefineBigData(length);
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
if (string.IsNullOrEmpty(opcodeStr)) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// 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,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
|
|
|
|
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;
|
2019-10-19 03:28:02 +00:00
|
|
|
|
case FormatDescriptor.Type.Junk:
|
|
|
|
|
int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length);
|
|
|
|
|
if (fillVal >= 0 && GenCommon.CheckJunkAlign(offset, dfd, Project.AddrMap)) {
|
|
|
|
|
// !align ANDVALUE, EQUALVALUE [, FILLVALUE]
|
|
|
|
|
opcodeStr = sDataOpNames.Align;
|
|
|
|
|
int alignVal = 1 << FormatDescriptor.AlignmentToPower(dfd.FormatSubType);
|
|
|
|
|
operandStr = (alignVal - 1).ToString() +
|
|
|
|
|
",0," + formatter.FormatHexValue(fillVal, 2);
|
|
|
|
|
} else if (fillVal >= 0) {
|
|
|
|
|
// treat same as Fill
|
|
|
|
|
opcodeStr = sDataOpNames.Fill;
|
|
|
|
|
operandStr = length + "," + formatter.FormatHexValue(fillVal, 2);
|
|
|
|
|
} else {
|
|
|
|
|
// treat same as Dense
|
|
|
|
|
multiLine = true;
|
|
|
|
|
opcodeStr = operandStr = null;
|
|
|
|
|
OutputDenseHex(offset, length, labelStr, commentStr);
|
|
|
|
|
}
|
|
|
|
|
break;
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
case FormatDescriptor.Type.StringGeneric:
|
|
|
|
|
case FormatDescriptor.Type.StringReverse:
|
|
|
|
|
case FormatDescriptor.Type.StringNullTerm:
|
|
|
|
|
case FormatDescriptor.Type.StringL8:
|
|
|
|
|
case FormatDescriptor.Type.StringL16:
|
|
|
|
|
case FormatDescriptor.Type.StringDci:
|
2019-08-04 03:54:07 +00:00
|
|
|
|
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;
|
2020-07-20 01:39:27 +00:00
|
|
|
|
int maxPerLine = formatter.OperandWrapLen / formatter.CharsPerDenseByte;
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-16 04:33:10 +00:00
|
|
|
|
if (singleValue && length > 1) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 19:14:47 +00:00
|
|
|
|
// IGenerator
|
2019-09-01 17:55:19 +00:00
|
|
|
|
public void OutputLocalVariableTable(int offset, List<DefSymbol> newDefs,
|
|
|
|
|
LocalVariableTable allDefs) {
|
2019-11-04 21:34:49 +00:00
|
|
|
|
// We can do better here, but it requires knowing whether anything in "newDefs"
|
|
|
|
|
// overwrote a previous entry. If everything is new, we don't need to start
|
|
|
|
|
// a new zone, and can just output newDefs. (We don't need to start a new zone
|
|
|
|
|
// on a "clear previous".)
|
2019-09-01 17:55:19 +00:00
|
|
|
|
OutputLine(string.Empty, "!zone", "Z" + offset.ToString("x6"), string.Empty);
|
|
|
|
|
for (int i = 0; i < allDefs.Count; i++) {
|
|
|
|
|
DefSymbol defSym = allDefs[i];
|
|
|
|
|
|
|
|
|
|
string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter,
|
|
|
|
|
Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2019-09-01 17:55:19 +00:00
|
|
|
|
OutputEquDirective(SourceFormatter.FormatVariableLabel(defSym.Label),
|
|
|
|
|
valueStr, defSym.Comment);
|
|
|
|
|
}
|
2019-08-29 19:14:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// IGenerator
|
|
|
|
|
public void OutputOrgDirective(int offset, int address) {
|
2020-05-14 23:37:33 +00:00
|
|
|
|
// If there's only one address range, just set the "real" PC. If there's more
|
|
|
|
|
// than one we can run out of space if the source file has a chunk in high memory
|
|
|
|
|
// followed by a chunk in low memory, because the "real" PC determines when the
|
|
|
|
|
// 64KB bank is overrun.
|
2019-08-04 03:54:07 +00:00
|
|
|
|
if (offset == 0) {
|
2020-05-14 23:37:33 +00:00
|
|
|
|
// first one
|
|
|
|
|
if (Project.AddrMap.Count == 1) {
|
|
|
|
|
OutputLine("*", "=", SourceFormatter.FormatHexValue(address, 4), string.Empty);
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
// set the real PC to address zero to ensure we get a full 64KB
|
|
|
|
|
OutputLine("*", "=", SourceFormatter.FormatHexValue(0, 4), string.Empty);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-14 23:37:33 +00:00
|
|
|
|
|
|
|
|
|
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;
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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();
|
2019-09-18 05:02:05 +00:00
|
|
|
|
TextUtil.AppendPaddedString(mLineBuilder, label, 0);
|
|
|
|
|
TextUtil.AppendPaddedString(mLineBuilder, opcode, mColumnWidths[0]);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
TextUtil.AppendPaddedString(mLineBuilder, operand,
|
2019-09-18 05:02:05 +00:00
|
|
|
|
mColumnWidths[0] + mColumnWidths[1]);
|
|
|
|
|
TextUtil.AppendPaddedString(mLineBuilder, comment,
|
2019-08-04 03:54:07 +00:00
|
|
|
|
mColumnWidths[0] + mColumnWidths[1] + mColumnWidths[2]);
|
|
|
|
|
|
|
|
|
|
mOutStream.WriteLine(mLineBuilder.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OutputString(int offset, string labelStr, string commentStr) {
|
|
|
|
|
Formatter formatter = SourceFormatter;
|
|
|
|
|
byte[] data = Project.FileData;
|
|
|
|
|
Anattrib attr = Project.GetAnattrib(offset);
|
|
|
|
|
FormatDescriptor dfd = attr.DataDescriptor;
|
|
|
|
|
Debug.Assert(dfd != null);
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
Debug.Assert(dfd.IsString);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
Debug.Assert(dfd.Length > 0);
|
|
|
|
|
|
2019-08-16 04:33:10 +00:00
|
|
|
|
string opcodeStr;
|
|
|
|
|
CharEncoding.Convert charConv;
|
|
|
|
|
switch (dfd.FormatSubType) {
|
|
|
|
|
case FormatDescriptor.SubType.Ascii:
|
|
|
|
|
opcodeStr = sDataOpNames.StrGeneric;
|
|
|
|
|
charConv = CharEncoding.ConvertAscii;
|
|
|
|
|
break;
|
|
|
|
|
case FormatDescriptor.SubType.HighAscii:
|
2019-08-18 00:35:01 +00:00
|
|
|
|
opcodeStr = sDataOpNames.StrGeneric;
|
|
|
|
|
charConv = CharEncoding.ConvertHighAscii;
|
|
|
|
|
break;
|
2019-08-16 04:33:10 +00:00
|
|
|
|
case FormatDescriptor.SubType.C64Petscii:
|
|
|
|
|
opcodeStr = "!pet";
|
|
|
|
|
charConv = CharEncoding.ConvertC64Petscii;
|
|
|
|
|
break;
|
|
|
|
|
case FormatDescriptor.SubType.C64Screen:
|
|
|
|
|
opcodeStr = "!scr";
|
|
|
|
|
charConv = CharEncoding.ConvertC64ScreenCode;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
OutputNoJoy(offset, dfd.Length, labelStr, commentStr);
|
|
|
|
|
return;
|
2019-08-10 21:24:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
int leadingBytes = 0;
|
|
|
|
|
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
switch (dfd.FormatType) {
|
|
|
|
|
case FormatDescriptor.Type.StringGeneric:
|
|
|
|
|
case FormatDescriptor.Type.StringReverse:
|
|
|
|
|
case FormatDescriptor.Type.StringNullTerm:
|
2019-08-09 23:42:30 +00:00
|
|
|
|
case FormatDescriptor.Type.StringDci:
|
2019-08-21 00:55:12 +00:00
|
|
|
|
// Last byte may be output as hex.
|
2019-08-04 03:54:07 +00:00
|
|
|
|
break;
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
case FormatDescriptor.Type.StringL8:
|
2019-08-16 04:33:10 +00:00
|
|
|
|
// Length byte will be output as hex.
|
2019-08-04 03:54:07 +00:00
|
|
|
|
leadingBytes = 1;
|
|
|
|
|
break;
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
case FormatDescriptor.Type.StringL16:
|
2019-08-16 04:33:10 +00:00
|
|
|
|
// Length byte will be output as hex.
|
2019-08-04 03:54:07 +00:00
|
|
|
|
leadingBytes = 2;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-14 00:22:21 +00:00
|
|
|
|
StringOpFormatter stropf = new StringOpFormatter(SourceFormatter,
|
2021-07-31 21:42:36 +00:00
|
|
|
|
Formatter.DOUBLE_QUOTE_DELIM, StringOpFormatter.RawOutputStyle.CommaSep, charConv,
|
|
|
|
|
mBackslashEscapes);
|
2019-08-14 00:22:21 +00:00
|
|
|
|
stropf.FeedBytes(data, offset, dfd.Length, leadingBytes,
|
|
|
|
|
StringOpFormatter.ReverseMode.Forward);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
|
2019-08-18 00:35:01 +00:00
|
|
|
|
if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii && stropf.HasEscapedText) {
|
|
|
|
|
// Can't !xor the output, because while it works for string data it
|
|
|
|
|
// also flips the high bits on the unprintable bytes we output as raw hex.
|
2021-08-01 03:22:21 +00:00
|
|
|
|
// We'd need to tell the string formatter to flip the high bit on the byte.
|
2019-08-18 00:35:01 +00:00
|
|
|
|
OutputNoJoy(offset, dfd.Length, labelStr, commentStr);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii) {
|
|
|
|
|
OutputLine(string.Empty, "!xor", "$80 {", string.Empty);
|
|
|
|
|
}
|
2019-08-09 23:42:30 +00:00
|
|
|
|
foreach (string str in stropf.Lines) {
|
|
|
|
|
OutputLine(labelStr, opcodeStr, str, commentStr);
|
|
|
|
|
labelStr = commentStr = string.Empty; // only show on first
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
2019-08-18 00:35:01 +00:00
|
|
|
|
if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii) {
|
|
|
|
|
OutputLine(string.Empty, "}", string.Empty, string.Empty);
|
|
|
|
|
}
|
2019-08-04 03:54:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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
|
2020-10-17 23:10:48 +00:00
|
|
|
|
public void Configure(GenerationResults results, string workDirectory) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// Clone pathNames, in case the caller decides to modify the original.
|
2020-10-17 23:10:48 +00:00
|
|
|
|
mPathNames = CommonUtil.Container.CopyStringList(results.PathNames);
|
2019-08-04 03:54:07 +00:00
|
|
|
|
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
|
|
|
|
|
}
|