2019-05-28 01:46:09 +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;
|
|
|
|
|
|
2019-07-20 20:28:10 +00:00
|
|
|
|
namespace SourceGen.AsmGen {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
#region IGenerator
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generate source code compatible with the 64tass assembler
|
|
|
|
|
/// (https://sourceforge.net/projects/tass64/).
|
|
|
|
|
///
|
|
|
|
|
/// The assembler is officially called "64tass", but it's sometimes written "tass64" because
|
|
|
|
|
/// in some cases you can't start an identifier with a number.
|
|
|
|
|
///
|
|
|
|
|
/// We need to deal with a couple of unusual aspects:
|
|
|
|
|
/// (1) The prefix for a local label is '_', which is generally a legal character. So
|
|
|
|
|
/// if somebody creates a label with a leading '_', and it's not actually local, we have
|
|
|
|
|
/// to "de-local" it somehow.
|
|
|
|
|
/// (2) By default, labels are handled in a case-insensitive fashion, which is extremely
|
|
|
|
|
/// rare for programming languages. Case sensitivity can be enabled with the "-C" flag.
|
|
|
|
|
/// Anybody who wants to assemble the generated code will need to be aware of this.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class GenTass64 : IGenerator {
|
|
|
|
|
private const string ASM_FILE_SUFFIX = "_64tass.S"; // must start with underscore
|
2019-08-21 20:30:25 +00:00
|
|
|
|
private const string ASCII_ENC_NAME = "sg_ascii";
|
|
|
|
|
private const string HIGH_ASCII_ENC_NAME = "sg_hiascii";
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
// 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
|
2021-10-05 03:41:19 +00:00
|
|
|
|
public LabelLocalizer Localizer { get { return mLocalizer; } }
|
2020-10-17 23:10:48 +00:00
|
|
|
|
|
|
|
|
|
public int StartOffset {
|
|
|
|
|
get {
|
|
|
|
|
return mHasPrgHeader ? 2 : 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-28 01:46:09 +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>
|
2020-10-17 23:10:48 +00:00
|
|
|
|
/// True if the first two bytes look like the header of a PRG file.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
/// </summary>
|
2020-10-17 23:10:48 +00:00
|
|
|
|
private bool mHasPrgHeader;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2020-10-17 23:10:48 +00:00
|
|
|
|
/// StringBuilder to use when composing a line. Held here to reduce allocations.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
/// </summary>
|
2020-10-17 23:10:48 +00:00
|
|
|
|
private StringBuilder mLineBuilder = new StringBuilder(100);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
2021-10-05 03:41:19 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Label localization helper.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private LabelLocalizer mLocalizer;
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Stream to send the output to.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private StreamWriter mOutStream;
|
|
|
|
|
|
2019-08-21 20:30:25 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// What encoding are we currently set up for.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private CharEncoding.Encoding mCurrentEncoding;
|
|
|
|
|
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Output mode; determines how ORG is handled.
|
|
|
|
|
/// </summary>
|
2021-09-17 00:02:19 +00:00
|
|
|
|
private enum OutputMode {
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
Unknown = 0, Loadable = 1, Streamable = 2
|
|
|
|
|
}
|
2021-09-17 00:02:19 +00:00
|
|
|
|
private OutputMode mOutputMode;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Current pseudo-PC depth. 0 is the "real" PC.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private int mPcDepth;
|
|
|
|
|
private bool mFirstIsOpen;
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
/// <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 V1_53 = new CommonUtil.Version(1, 53, 1515);
|
2021-08-09 21:26:25 +00:00
|
|
|
|
private static CommonUtil.Version V1_54 = new CommonUtil.Version(1, 54, 1900);
|
|
|
|
|
private static CommonUtil.Version V1_55 = new CommonUtil.Version(1, 55, 2176);
|
2021-08-09 20:47:04 +00:00
|
|
|
|
private static CommonUtil.Version V1_56 = new CommonUtil.Version(1, 56, 2625);
|
2019-05-28 01:46:09 +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", ".var" },
|
2021-09-22 21:39:39 +00:00
|
|
|
|
{ "ArStartDirective", ".logical" },
|
|
|
|
|
{ "ArEndDirective", ".here" },
|
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
|
|
|
|
//RegWidthDirective // .as, .al, .xs, .xl
|
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", ".long" },
|
|
|
|
|
{ "DefineData4", ".dword" },
|
|
|
|
|
//DefineBigData2
|
|
|
|
|
//DefineBigData3
|
|
|
|
|
//DefineBigData4
|
|
|
|
|
{ "Fill", ".fill" },
|
2019-12-11 01:41:00 +00:00
|
|
|
|
{ "Dense", ".byte" }, // not really dense, just comma-separated bytes
|
2021-10-13 21:48:05 +00:00
|
|
|
|
{ "Uninit", ".fill" },
|
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" },
|
|
|
|
|
//StrReverse
|
|
|
|
|
{ "StrNullTerm", ".null" },
|
|
|
|
|
{ "StrLen8", ".ptext" },
|
|
|
|
|
//StrLen16
|
|
|
|
|
{ "StrDci", ".shift" }
|
|
|
|
|
});
|
2019-05-28 01:46:09 +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-05-28 01:46:09 +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;
|
|
|
|
|
Quirks = new AssemblerQuirks();
|
2021-07-31 21:56:17 +00:00
|
|
|
|
if (asmVersion != null) {
|
|
|
|
|
mAsmVersion = asmVersion.Version; // Use the actual version.
|
|
|
|
|
} else {
|
2021-08-09 20:47:04 +00:00
|
|
|
|
mAsmVersion = V1_56; // No assembler installed, use default.
|
2021-07-31 21:56:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-19 23:09:11 +00:00
|
|
|
|
Quirks.StackIntOperandIsImmediate = true;
|
Label rework, part 6
Correct handling of local variables. We now correctly uniquify them
with regard to non-unique labels. Because local vars can effectively
have global scope we mostly want to treat them as global, but they're
uniquified relative to other globals very late in the process, so we
can't just throw them in the symbol table and be done. Fortunately
local variables exist in a separate namespace, so we just need to
uniquify the variables relative to the post-localization symbol table.
In other words, we take the symbol table, apply the label map, and
rename any variable that clashes.
This also fixes an older problem where we weren't masking the
leading '_' on variable labels when generating 64tass output.
The code list now makes non-unique labels obvious, but you can't tell
the difference between unique global and unique local. What's more,
the default type value in Edit Label is now adjusted to Global for
unique locals that were auto-generated. To make it a bit easier to
figure out what's what, the Info panel now has a "label type" line
that reports the type.
The 2023-non-unique-labels test had some additional tests added to
exercise conflicts with local variables. The 2019-local-variables
test output changed slightly because the de-duplicated variable
naming convention was simplified.
2019-11-18 21:26:03 +00:00
|
|
|
|
Quirks.LeadingUnderscoreSpecial = true;
|
2020-07-03 20:58:18 +00:00
|
|
|
|
Quirks.Need24BitsForAbsPBR = true;
|
2020-10-11 21:35:17 +00:00
|
|
|
|
Quirks.BitNumberIsArg = true;
|
2021-08-09 20:47:04 +00:00
|
|
|
|
Quirks.BankZeroAbsPBRRestrict = true;
|
2019-05-28 01:46:09 +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.Tass64);
|
|
|
|
|
mColumnWidths = (int[])config.ColumnWidths.Clone();
|
2020-10-17 23:10:48 +00:00
|
|
|
|
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
// 64tass emulates a loader on a 64K system. The address you specify with
|
|
|
|
|
// "* = <addr>" tells the loader where the code lives. If the project runs off the
|
|
|
|
|
// end of memory, you get a warning message and an output file that has the last
|
|
|
|
|
// part as the first part, because the loader wraps around.
|
|
|
|
|
//
|
|
|
|
|
// If (start_addr + total_len) doesn't fit without wrapping, we want to start
|
|
|
|
|
// the code with "* = 0" (or omit it entirely) and use ".logical" for the first.
|
|
|
|
|
// chunk. This allows us to generate the full 64K. Note that 65816 code that
|
|
|
|
|
// starts outside bank 0 will always fail this test.
|
|
|
|
|
//
|
|
|
|
|
// Thus there are two modes: "loadable" and "streamable". We could output everything
|
|
|
|
|
// as streamable but that's kind of ugly and prevents the PRG optimization.
|
|
|
|
|
//
|
|
|
|
|
// If the file has more than 64K of data in it, we need to add "--long-address" to
|
|
|
|
|
// the command-line arguments.
|
|
|
|
|
|
|
|
|
|
// Get start address. If this is a PRG file, the start address is the address
|
|
|
|
|
// of offset +000002.
|
|
|
|
|
bool hasPrgHeader = GenCommon.HasPrgHeader(project);
|
|
|
|
|
int offAdj = hasPrgHeader ? 2 : 0;
|
2021-09-17 00:02:19 +00:00
|
|
|
|
int startAddr = project.AddrMap.OffsetToAddress(offAdj);
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
if (startAddr + project.FileDataLength - offAdj > 65536) {
|
|
|
|
|
// Does not fit into memory at load address.
|
2021-09-17 00:02:19 +00:00
|
|
|
|
mOutputMode = OutputMode.Streamable;
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
mHasPrgHeader = false;
|
|
|
|
|
} else {
|
2021-09-17 00:02:19 +00:00
|
|
|
|
mOutputMode = OutputMode.Loadable;
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
mHasPrgHeader = hasPrgHeader;
|
|
|
|
|
}
|
|
|
|
|
//Debug.WriteLine("startAddr=$" + startAddr.ToString("x6") +
|
|
|
|
|
// " outputMode=" + mOutputMode + " hasPrg=" + mHasPrgHeader);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
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
|
|
|
|
/// Configures the assembler-specific format items. May be called without a Project.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
private void SetFormatConfigValues(ref Formatter.FormatConfig config) {
|
|
|
|
|
// Must be lower case when --case-sensitive is used.
|
|
|
|
|
config.mUpperOpcodes = false;
|
|
|
|
|
config.mUpperPseudoOpcodes = false;
|
|
|
|
|
config.mUpperOperandA = false;
|
|
|
|
|
config.mUpperOperandS = false;
|
|
|
|
|
config.mUpperOperandXY = false;
|
2020-07-20 01:39:27 +00:00
|
|
|
|
config.mOperandWrapLen = 64;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
config.mBankSelectBackQuote = true;
|
|
|
|
|
|
2019-08-04 03:54:07 +00:00
|
|
|
|
config.mForceDirectOpcodeSuffix = string.Empty;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
config.mForceAbsOpcodeSuffix = string.Empty;
|
|
|
|
|
config.mForceLongOpcodeSuffix = string.Empty;
|
|
|
|
|
config.mForceDirectOperandPrefix = string.Empty;
|
|
|
|
|
config.mForceAbsOperandPrefix = "@w"; // word
|
|
|
|
|
config.mForceLongOperandPrefix = "@l"; // long
|
|
|
|
|
config.mEndOfLineCommentDelimiter = ";";
|
|
|
|
|
config.mFullLineCommentDelimiterBase = ";";
|
|
|
|
|
config.mBoxLineCommentDelimiter = ";";
|
2019-11-13 01:24:41 +00:00
|
|
|
|
config.mNonUniqueLabelPrefix = ""; // should be '_', but that's a valid label char
|
2019-12-11 01:41:00 +00:00
|
|
|
|
config.mCommaSeparatedDense = true;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Common;
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IGenerator
|
2020-10-17 23:10:48 +00:00
|
|
|
|
public GenerationResults GenerateSource(BackgroundWorker worker) {
|
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
|
|
|
|
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);
|
2019-08-16 21:46:17 +00:00
|
|
|
|
|
2019-08-21 20:30:25 +00:00
|
|
|
|
// Configure delimiters for single-character operands.
|
|
|
|
|
Formatter.DelimiterSet charDelimSet = new Formatter.DelimiterSet();
|
|
|
|
|
charDelimSet.Set(CharEncoding.Encoding.C64Petscii, Formatter.SINGLE_QUOTE_DELIM);
|
|
|
|
|
charDelimSet.Set(CharEncoding.Encoding.C64ScreenCode, Formatter.SINGLE_QUOTE_DELIM);
|
|
|
|
|
charDelimSet.Set(CharEncoding.Encoding.Ascii, Formatter.SINGLE_QUOTE_DELIM);
|
|
|
|
|
charDelimSet.Set(CharEncoding.Encoding.HighAscii,
|
|
|
|
|
new Formatter.DelimiterDef(string.Empty, '\'', '\'', " | $80"));
|
|
|
|
|
|
|
|
|
|
config.mCharDelimiters = charDelimSet;
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
SourceFormatter = new Formatter(config);
|
|
|
|
|
|
|
|
|
|
string msg = string.Format(Res.Strings.PROGRESS_GENERATING_FMT, pathName);
|
|
|
|
|
worker.ReportProgress(0, msg);
|
|
|
|
|
|
2021-10-05 03:41:19 +00:00
|
|
|
|
mLocalizer = new LabelLocalizer(Project);
|
|
|
|
|
mLocalizer.LocalPrefix = "_";
|
|
|
|
|
mLocalizer.QuirkNoOpcodeMnemonics = true;
|
|
|
|
|
mLocalizer.Analyze();
|
2020-10-17 23:10:48 +00:00
|
|
|
|
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
bool needLongAddress = Project.FileDataLength > 65536 + (mHasPrgHeader ? 2 : 0);
|
2020-10-17 23:10:48 +00:00
|
|
|
|
string extraOptions = string.Empty +
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
(needLongAddress ? AsmTass64.LONG_ADDRESS : string.Empty) +
|
2020-10-17 23:10:48 +00:00
|
|
|
|
(mHasPrgHeader ? string.Empty : AsmTass64.NOSTART);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
2021-09-17 00:02:19 +00:00
|
|
|
|
mPcDepth = 0;
|
|
|
|
|
mFirstIsOpen = true;
|
|
|
|
|
|
2019-05-28 01:46:09 +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
|
|
|
|
"64tass", mAsmVersion, AsmTass64.BASE_OPTIONS + extraOptions));
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GenCommon.Generate(this, sw, worker);
|
|
|
|
|
}
|
|
|
|
|
mOutStream = null;
|
|
|
|
|
|
2020-10-17 23:10:48 +00:00
|
|
|
|
return new GenerationResults(pathNames, extraOptions);
|
2019-05-28 01:46:09 +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-05-28 01:46:09 +00:00
|
|
|
|
} else if (cpuDef.Type == CpuDef.CpuType.Cpu6502 && cpuDef.HasUndocumented) {
|
|
|
|
|
cpuStr = "6502i";
|
|
|
|
|
} else {
|
|
|
|
|
cpuStr = "6502";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(".cpu"),
|
|
|
|
|
'\"' + cpuStr + '\"', string.Empty);
|
2019-08-16 21:46:17 +00:00
|
|
|
|
|
2019-08-21 20:30:25 +00:00
|
|
|
|
// C64 PETSCII and C64 screen codes are built in. Define ASCII if we also
|
|
|
|
|
// need that.
|
|
|
|
|
mCurrentEncoding = CharEncoding.Encoding.C64Petscii;
|
|
|
|
|
|
|
|
|
|
CheckAsciiFormats(out bool hasAscii, out bool hasHighAscii);
|
|
|
|
|
if (hasHighAscii) {
|
2020-07-02 15:14:42 +00:00
|
|
|
|
OutputLine(string.Empty, ".enc", '"' + HIGH_ASCII_ENC_NAME + '"', string.Empty);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
OutputLine(string.Empty, ".cdef", "$20,$7e,$a0", string.Empty);
|
|
|
|
|
mCurrentEncoding = CharEncoding.Encoding.HighAscii;
|
|
|
|
|
}
|
|
|
|
|
if (hasAscii) {
|
2020-07-02 15:14:42 +00:00
|
|
|
|
OutputLine(string.Empty, ".enc", '"' + ASCII_ENC_NAME + '"', string.Empty);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
OutputLine(string.Empty, ".cdef", "$20,$7e,$20", string.Empty);
|
|
|
|
|
mCurrentEncoding = CharEncoding.Encoding.Ascii;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CheckAsciiFormats(out bool hasAscii, out bool hasHighAscii) {
|
|
|
|
|
int offset = 0;
|
|
|
|
|
hasAscii = hasHighAscii = false;
|
|
|
|
|
while (offset < Project.FileData.Length) {
|
|
|
|
|
Anattrib attr = Project.GetAnattrib(offset);
|
|
|
|
|
FormatDescriptor dfd = attr.DataDescriptor;
|
|
|
|
|
if (dfd != null) {
|
|
|
|
|
if (dfd.FormatSubType == FormatDescriptor.SubType.Ascii) {
|
|
|
|
|
Debug.Assert(dfd.IsNumeric || dfd.IsString);
|
|
|
|
|
hasAscii = true;
|
|
|
|
|
} else if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii) {
|
|
|
|
|
hasHighAscii = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (hasAscii && hasHighAscii) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-19 23:23:42 +00:00
|
|
|
|
if (attr.IsInstructionStart) {
|
|
|
|
|
// look for embedded instructions, which might have formatted char data
|
|
|
|
|
int len;
|
|
|
|
|
for (len = 1; len < attr.Length; len++) {
|
|
|
|
|
if (Project.GetAnattrib(offset + len).IsInstructionStart) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
offset += len;
|
|
|
|
|
} else {
|
|
|
|
|
// data items
|
|
|
|
|
offset += attr.Length;
|
|
|
|
|
}
|
2019-08-21 20:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
// 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-05-28 01:46:09 +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.OpSHA_DPIndIndexY) {
|
|
|
|
|
// not recognized ($93)
|
2021-08-09 21:26:25 +00:00
|
|
|
|
if (mAsmVersion < V1_55) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-19 23:09:11 +00:00
|
|
|
|
if (op == OpDef.OpWDM_WDM) {
|
2021-08-09 21:26:25 +00:00
|
|
|
|
// 64tass v1.53 doesn't know what this is.
|
|
|
|
|
// 64tass v1.55 doesn't like this to have an operand.
|
|
|
|
|
// Output as hex.
|
2019-05-28 01:46:09 +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-21 22:29:00 +00:00
|
|
|
|
CharEncoding.Encoding newEnc = PseudoOp.SubTypeToEnc(dfd.FormatSubType);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
if (newEnc == CharEncoding.Encoding.Unknown) {
|
|
|
|
|
// probably not a character operand
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (newEnc != mCurrentEncoding) {
|
|
|
|
|
switch (newEnc) {
|
|
|
|
|
case CharEncoding.Encoding.Ascii:
|
2020-07-02 15:14:42 +00:00
|
|
|
|
OutputLine(string.Empty, ".enc", '"' + ASCII_ENC_NAME + '"', string.Empty);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
break;
|
|
|
|
|
case CharEncoding.Encoding.HighAscii:
|
|
|
|
|
// If this is a numeric operand (not string), and we're currently in
|
|
|
|
|
// ASCII mode, the "| $80" in the delimiter will handle this without
|
|
|
|
|
// the need for a .enc. Much less clutter for sources that have plain
|
|
|
|
|
// ASCII strings but test high ASCII constants.
|
|
|
|
|
if (mCurrentEncoding == CharEncoding.Encoding.Ascii && !dfd.IsString) {
|
|
|
|
|
newEnc = mCurrentEncoding;
|
|
|
|
|
} else {
|
2020-07-02 15:14:42 +00:00
|
|
|
|
OutputLine(string.Empty, ".enc", '"' + HIGH_ASCII_ENC_NAME + '"',
|
|
|
|
|
string.Empty);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case CharEncoding.Encoding.C64Petscii:
|
2020-07-02 15:14:42 +00:00
|
|
|
|
OutputLine(string.Empty, ".enc", "\"none\"", string.Empty);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
break;
|
|
|
|
|
case CharEncoding.Encoding.C64ScreenCode:
|
2020-07-02 15:14:42 +00:00
|
|
|
|
OutputLine(string.Empty, ".enc", "\"screen\"", string.Empty);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
mCurrentEncoding = newEnc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 01:46:09 +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) {
|
2020-10-17 23:10:48 +00:00
|
|
|
|
labelStr = Localizer.ConvLabel(attr.Symbol.Label);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
UpdateCharacterEncoding(dfd);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable,
|
2020-10-17 23:10:48 +00:00
|
|
|
|
Localizer.LabelMap, dfd, operand, length,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2019-05-28 01:46:09 +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-05-28 01:46:09 +00:00
|
|
|
|
// Nothing defined, output as comma-separated single-byte values.
|
|
|
|
|
GenerateShortSequence(offset, length, out opcodeStr, out operandStr);
|
|
|
|
|
} else {
|
2019-08-21 20:30:25 +00:00
|
|
|
|
UpdateCharacterEncoding(dfd);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
operand = RawData.GetWord(data, offset, length, true);
|
|
|
|
|
operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable,
|
2020-10-17 23:10:48 +00:00
|
|
|
|
Localizer.LabelMap, dfd, operand, length,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2019-05-28 01:46:09 +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;
|
2021-10-13 21:48:05 +00:00
|
|
|
|
case FormatDescriptor.Type.Uninit:
|
|
|
|
|
// TODO: use the special syntax for uninit byte/word/dword if possible.
|
2019-10-19 03:28:02 +00:00
|
|
|
|
case FormatDescriptor.Type.Junk:
|
2021-10-13 21:48:05 +00:00
|
|
|
|
bool canAlign = (dfd.FormatType == FormatDescriptor.Type.Junk);
|
2019-10-19 03:28:02 +00:00
|
|
|
|
int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length);
|
2021-10-13 21:48:05 +00:00
|
|
|
|
if (canAlign && fillVal >= 0 &&
|
|
|
|
|
GenCommon.CheckJunkAlign(offset, dfd, Project.AddrMap)) {
|
2019-10-19 03:28:02 +00:00
|
|
|
|
// .align <expression>[, <fill>]
|
|
|
|
|
opcodeStr = sDataOpNames.Align;
|
|
|
|
|
int alignVal = 1 << FormatDescriptor.AlignmentToPower(dfd.FormatSubType);
|
|
|
|
|
operandStr = alignVal.ToString() +
|
|
|
|
|
"," + formatter.FormatHexValue(fillVal, 2);
|
2021-10-13 21:48:05 +00:00
|
|
|
|
} else if (fillVal >= 0 && (length > 1 || fillVal == 0x00)) {
|
|
|
|
|
// If multi-byte, or single byte and zero, treat same as Fill.
|
2019-10-19 03:28:02 +00:00
|
|
|
|
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-05-28 01:46:09 +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-12-11 01:41:00 +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;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
2019-12-11 01:41:00 +00:00
|
|
|
|
string operandStr = formatter.FormatDenseHex(data, offset + i, subLen);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
2019-12-11 01:41:00 +00:00
|
|
|
|
OutputLine(labelStr, opcodeStr, operandStr, commentStr);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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-05-28 01:46:09 +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) {
|
|
|
|
|
foreach (DefSymbol defSym in newDefs) {
|
|
|
|
|
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
|
|
|
|
OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label),
|
|
|
|
|
SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective),
|
|
|
|
|
valueStr, SourceFormatter.FormatEolComment(defSym.Comment));
|
|
|
|
|
}
|
2019-08-29 19:14:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
// IGenerator
|
ORG rework, part 6
Added support for non-addressable regions, which are useful for things
like file headers stripped out by the system loader, or chunks that
get loaded into non-addressable graphics RAM. Regions are specified
with the "NA" address value. The code list displays the address field
greyed out, starting from zero (which is kind of handy if you want to
know the relative offset within the region).
Putting labels in non-addressable regions doesn't make sense, but
symbol resolution is complicated enough that we really only have two
options: ignore the labels entirely, or allow them but warn of their
presence. The problem isn't so much the label, which you could
legitimately want to access from an extension script, but rather the
references to them from code or data. So we keep the label and add a
warning to the Messages list when we see a reference.
Moved NON_ADDR constants to Address class. AddressMap now has a copy.
This is awkward because Asm65 and CommonUtil don't share.
Updated the asm code generators to understand NON_ADDR, and reworked
the API so that Merlin and cc65 output is correct for nested regions.
Address region changes are now noted in the anattribs array, which
makes certain operations faster than checking the address map. It
also fixes a failure to recognize mid-instruction region changes in
the code analyzer.
Tweaked handling of synthetic regions, which are non-addressable areas
generated by the linear address map traversal to fill in any "holes".
The address region editor now treats attempts to edit them as
creation of a new region.
2021-10-01 01:07:21 +00:00
|
|
|
|
public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
// 64tass separates the "compile offset", which determines where the output fits
|
|
|
|
|
// into the generated binary, and "program counter", which determines the code
|
|
|
|
|
// the assembler generates. Since we need to explicitly specify every byte in
|
2021-09-17 00:02:19 +00:00
|
|
|
|
// the output file, having a distinct compile offset isn't useful here. We want
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
// to set it once before the first line of code, then leave it alone.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
//
|
|
|
|
|
// Any subsequent ORG changes are made to the program counter, and take the form
|
2021-09-17 00:02:19 +00:00
|
|
|
|
// of a pair of ops (".logical <addr>" to open, ".here" to end). Omitting the .here
|
2019-05-28 01:46:09 +00:00
|
|
|
|
// causes an error.
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
//
|
|
|
|
|
// If this is a "streamable" file, meaning it won't actually load into 64K of RAM
|
|
|
|
|
// without wrapping around, then we skip the "* = addr" (same as "* = 0") and just
|
|
|
|
|
// start with ".logical" segments.
|
2021-09-17 00:02:19 +00:00
|
|
|
|
//
|
|
|
|
|
// The assembler's approach is best represented by having an address region that
|
|
|
|
|
// spans the entire file, with one or more "logical" regions inside. In practice
|
|
|
|
|
// (especially for multi-bank 65816 code) that may not be the case, but the
|
|
|
|
|
// assembler is still expecting us to start with a "* =" and then fit everything
|
|
|
|
|
// inside that. So we treat the first region specially, whether or not it wraps
|
|
|
|
|
// the rest of the file.
|
|
|
|
|
Debug.Assert(mPcDepth >= 0);
|
ORG rework, part 6
Added support for non-addressable regions, which are useful for things
like file headers stripped out by the system loader, or chunks that
get loaded into non-addressable graphics RAM. Regions are specified
with the "NA" address value. The code list displays the address field
greyed out, starting from zero (which is kind of handy if you want to
know the relative offset within the region).
Putting labels in non-addressable regions doesn't make sense, but
symbol resolution is complicated enough that we really only have two
options: ignore the labels entirely, or allow them but warn of their
presence. The problem isn't so much the label, which you could
legitimately want to access from an extension script, but rather the
references to them from code or data. So we keep the label and add a
warning to the Messages list when we see a reference.
Moved NON_ADDR constants to Address class. AddressMap now has a copy.
This is awkward because Asm65 and CommonUtil don't share.
Updated the asm code generators to understand NON_ADDR, and reworked
the API so that Merlin and cc65 output is correct for nested regions.
Address region changes are now noted in the anattribs array, which
makes certain operations faster than checking the address map. It
also fixes a failure to recognize mid-instruction region changes in
the code analyzer.
Tweaked handling of synthetic regions, which are non-addressable areas
generated by the linear address map traversal to fill in any "holes".
The address region editor now treats attempts to edit them as
creation of a new region.
2021-10-01 01:07:21 +00:00
|
|
|
|
int nextAddress = change.Address;
|
|
|
|
|
if (nextAddress == Address.NON_ADDR) {
|
|
|
|
|
// Start non-addressable regions at zero to ensure they don't overflow bank.
|
|
|
|
|
nextAddress = 0;
|
|
|
|
|
}
|
|
|
|
|
if (change.IsStart) {
|
2021-10-05 03:41:19 +00:00
|
|
|
|
if (change.Region.HasValidPreLabel) {
|
|
|
|
|
string labelStr = mLocalizer.ConvLabel(change.Region.PreLabel);
|
|
|
|
|
OutputLine(labelStr, string.Empty, string.Empty, string.Empty);
|
|
|
|
|
}
|
2021-09-17 00:02:19 +00:00
|
|
|
|
if (mPcDepth == 0 && mFirstIsOpen) {
|
|
|
|
|
mPcDepth++;
|
|
|
|
|
|
|
|
|
|
// Set the "real" PC for the first address change. If we're in "loadable"
|
|
|
|
|
// mode, just set "*=". If we're in "streaming" mode, we set "*=" to zero
|
|
|
|
|
// and then use a pseudo-PC.
|
|
|
|
|
if (mOutputMode == OutputMode.Loadable) {
|
|
|
|
|
OutputLine("*", "=",
|
ORG rework, part 6
Added support for non-addressable regions, which are useful for things
like file headers stripped out by the system loader, or chunks that
get loaded into non-addressable graphics RAM. Regions are specified
with the "NA" address value. The code list displays the address field
greyed out, starting from zero (which is kind of handy if you want to
know the relative offset within the region).
Putting labels in non-addressable regions doesn't make sense, but
symbol resolution is complicated enough that we really only have two
options: ignore the labels entirely, or allow them but warn of their
presence. The problem isn't so much the label, which you could
legitimately want to access from an extension script, but rather the
references to them from code or data. So we keep the label and add a
warning to the Messages list when we see a reference.
Moved NON_ADDR constants to Address class. AddressMap now has a copy.
This is awkward because Asm65 and CommonUtil don't share.
Updated the asm code generators to understand NON_ADDR, and reworked
the API so that Merlin and cc65 output is correct for nested regions.
Address region changes are now noted in the anattribs array, which
makes certain operations faster than checking the address map. It
also fixes a failure to recognize mid-instruction region changes in
the code analyzer.
Tweaked handling of synthetic regions, which are non-addressable areas
generated by the linear address map traversal to fill in any "holes".
The address region editor now treats attempts to edit them as
creation of a new region.
2021-10-01 01:07:21 +00:00
|
|
|
|
SourceFormatter.FormatHexValue(nextAddress, 4), string.Empty);
|
2021-09-17 00:02:19 +00:00
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
// Set the real PC to address zero to ensure we get a full 64KB. The
|
|
|
|
|
// assembler assumes this as a default, so it can be omitted.
|
|
|
|
|
//OutputLine("*", "=", SourceFormatter.FormatHexValue(0, 4), string.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-02 20:47:05 +00:00
|
|
|
|
|
|
|
|
|
AddressMap.AddressRegion region = change.Region;
|
|
|
|
|
string addrStr;
|
2021-10-09 16:58:37 +00:00
|
|
|
|
if (region.HasValidIsRelative) {
|
2021-10-02 20:47:05 +00:00
|
|
|
|
int diff = nextAddress - region.PreLabelAddress;
|
|
|
|
|
string pfxStr;
|
|
|
|
|
if (diff >= 0) {
|
|
|
|
|
pfxStr = "*+";
|
|
|
|
|
} else {
|
|
|
|
|
pfxStr = "*-";
|
|
|
|
|
diff = -diff;
|
|
|
|
|
}
|
|
|
|
|
addrStr = pfxStr + SourceFormatter.FormatHexValue(diff, 4);
|
|
|
|
|
} else {
|
|
|
|
|
addrStr = SourceFormatter.FormatHexValue(nextAddress, 4);
|
|
|
|
|
}
|
2021-09-22 21:39:39 +00:00
|
|
|
|
OutputLine(string.Empty,
|
|
|
|
|
SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective),
|
2021-10-02 20:47:05 +00:00
|
|
|
|
addrStr,
|
2021-09-22 21:39:39 +00:00
|
|
|
|
string.Empty);
|
2021-09-17 00:02:19 +00:00
|
|
|
|
mPcDepth++;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
} else {
|
2021-09-17 00:02:19 +00:00
|
|
|
|
mPcDepth--;
|
|
|
|
|
if (mPcDepth > 0 || !mFirstIsOpen) {
|
|
|
|
|
// close previous block
|
2021-09-22 21:39:39 +00:00
|
|
|
|
OutputLine(string.Empty,
|
|
|
|
|
SourceFormatter.FormatPseudoOp(sDataOpNames.ArEndDirective),
|
2019-05-28 01:46:09 +00:00
|
|
|
|
string.Empty, string.Empty);
|
2021-09-17 00:02:19 +00:00
|
|
|
|
} else {
|
|
|
|
|
// mark initial "*=" region as closed, but don't output anything
|
|
|
|
|
mFirstIsOpen = false;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
ORG rework, part 6
Added support for non-addressable regions, which are useful for things
like file headers stripped out by the system loader, or chunks that
get loaded into non-addressable graphics RAM. Regions are specified
with the "NA" address value. The code list displays the address field
greyed out, starting from zero (which is kind of handy if you want to
know the relative offset within the region).
Putting labels in non-addressable regions doesn't make sense, but
symbol resolution is complicated enough that we really only have two
options: ignore the labels entirely, or allow them but warn of their
presence. The problem isn't so much the label, which you could
legitimately want to access from an extension script, but rather the
references to them from code or data. So we keep the label and add a
warning to the Messages list when we see a reference.
Moved NON_ADDR constants to Address class. AddressMap now has a copy.
This is awkward because Asm65 and CommonUtil don't share.
Updated the asm code generators to understand NON_ADDR, and reworked
the API so that Merlin and cc65 output is correct for nested regions.
Address region changes are now noted in the anattribs array, which
makes certain operations faster than checking the address map. It
also fixes a failure to recognize mid-instruction region changes in
the code analyzer.
Tweaked handling of synthetic regions, which are non-addressable areas
generated by the linear address map traversal to fill in any "holes".
The address region editor now treats attempts to edit them as
creation of a new region.
2021-10-01 01:07:21 +00:00
|
|
|
|
// IGenerator
|
|
|
|
|
public void FlushArDirectives() { }
|
|
|
|
|
|
2019-05-28 01:46:09 +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) ? ".xl" : ".xs";
|
|
|
|
|
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) {
|
2019-08-31 01:33:05 +00:00
|
|
|
|
// Break the line if the label is long and it's not a .EQ/.VAR directive.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(label) &&
|
|
|
|
|
!string.Equals(opcode, sDataOpNames.EquDirective,
|
2019-08-31 01:33:05 +00:00
|
|
|
|
StringComparison.InvariantCultureIgnoreCase) &&
|
|
|
|
|
!string.Equals(opcode, sDataOpNames.VarDirective,
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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-05-28 01:46:09 +00:00
|
|
|
|
TextUtil.AppendPaddedString(mLineBuilder, operand,
|
2019-09-18 05:02:05 +00:00
|
|
|
|
mColumnWidths[0] + mColumnWidths[1]);
|
|
|
|
|
TextUtil.AppendPaddedString(mLineBuilder, comment,
|
2019-05-28 01:46:09 +00:00
|
|
|
|
mColumnWidths[0] + mColumnWidths[1] + mColumnWidths[2]);
|
|
|
|
|
|
|
|
|
|
mOutStream.WriteLine(mLineBuilder.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OutputString(int offset, string labelStr, string commentStr) {
|
2019-08-16 21:46:17 +00:00
|
|
|
|
// Generic strings whose encoding matches the configured text encoding are output
|
|
|
|
|
// with a simple .text directive.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
//
|
|
|
|
|
// CString and L8String have directives (.null, .ptext), but we can only use
|
|
|
|
|
// them if the string fits on one line and doesn't include delimiters.
|
|
|
|
|
//
|
2019-08-10 02:13:58 +00:00
|
|
|
|
// We might be able to define a macro for Reverse.
|
2019-08-16 21:46:17 +00:00
|
|
|
|
//
|
|
|
|
|
// We don't currently switch character encodings in the middle of a file. We could
|
|
|
|
|
// do so to flip between PETSCII, screen codes, low ASCII, and high ASCII, but it
|
|
|
|
|
// adds a lot of noise and it's unclear that this is generally useful.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
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-05-28 01:46:09 +00:00
|
|
|
|
Debug.Assert(dfd.Length > 0);
|
|
|
|
|
|
2019-08-16 21:46:17 +00:00
|
|
|
|
CharEncoding.Convert charConv = null;
|
2019-08-16 04:33:10 +00:00
|
|
|
|
switch (dfd.FormatSubType) {
|
|
|
|
|
case FormatDescriptor.SubType.Ascii:
|
2019-08-21 20:30:25 +00:00
|
|
|
|
charConv = CharEncoding.ConvertAscii;
|
|
|
|
|
break;
|
|
|
|
|
case FormatDescriptor.SubType.HighAscii:
|
|
|
|
|
charConv = CharEncoding.ConvertHighAscii;
|
2019-08-16 04:33:10 +00:00
|
|
|
|
break;
|
|
|
|
|
case FormatDescriptor.SubType.C64Petscii:
|
2019-08-21 20:30:25 +00:00
|
|
|
|
charConv = CharEncoding.ConvertC64Petscii;
|
2019-08-16 21:46:17 +00:00
|
|
|
|
break;
|
2019-08-16 04:33:10 +00:00
|
|
|
|
case FormatDescriptor.SubType.C64Screen:
|
2019-08-21 20:30:25 +00:00
|
|
|
|
charConv = CharEncoding.ConvertC64ScreenCode;
|
2019-08-16 21:46:17 +00:00
|
|
|
|
break;
|
2019-08-16 04:33:10 +00:00
|
|
|
|
default:
|
2019-08-16 21:46:17 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (charConv == null) {
|
|
|
|
|
OutputNoJoy(offset, dfd.Length, labelStr, commentStr);
|
|
|
|
|
return;
|
2019-08-16 04:33:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 20:30:25 +00:00
|
|
|
|
// Issue a .enc, if needed.
|
|
|
|
|
UpdateCharacterEncoding(dfd);
|
|
|
|
|
|
2019-08-16 04:33:10 +00:00
|
|
|
|
Formatter formatter = SourceFormatter;
|
|
|
|
|
byte[] data = Project.FileData;
|
2019-08-09 23:42:30 +00:00
|
|
|
|
int hiddenLeadingBytes = 0;
|
|
|
|
|
int shownLeadingBytes = 0;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
int trailingBytes = 0;
|
2019-08-09 23:42:30 +00:00
|
|
|
|
string opcodeStr;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
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:
|
2019-08-09 23:42:30 +00:00
|
|
|
|
opcodeStr = sDataOpNames.StrGeneric;
|
2019-05-28 01:46:09 +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.StringNullTerm:
|
2019-08-09 23:42:30 +00:00
|
|
|
|
opcodeStr = sDataOpNames.StrNullTerm;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
trailingBytes = 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.StringL8:
|
2019-08-09 23:42:30 +00:00
|
|
|
|
opcodeStr = sDataOpNames.StrLen8;
|
|
|
|
|
hiddenLeadingBytes = 1;
|
2019-05-28 01:46:09 +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.StringL16:
|
2019-08-09 23:42:30 +00:00
|
|
|
|
opcodeStr = sDataOpNames.StrGeneric;
|
|
|
|
|
shownLeadingBytes = 2;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
break;
|
2019-08-10 02:13:58 +00:00
|
|
|
|
case FormatDescriptor.Type.StringDci:
|
|
|
|
|
opcodeStr = sDataOpNames.StrDci;
|
2021-08-08 22:38:39 +00:00
|
|
|
|
if ((Project.FileData[offset + dfd.Length - 1] & 0x80) == 0) {
|
2019-08-21 20:30:25 +00:00
|
|
|
|
// ".shift" directive only works for strings where the low bit starts
|
|
|
|
|
// clear and ends high.
|
2021-08-10 21:08:39 +00:00
|
|
|
|
// TODO(maybe): this is sub-optimal for high-ASCII DCI strings.
|
2019-08-21 20:30:25 +00:00
|
|
|
|
OutputNoJoy(offset, dfd.Length, labelStr, commentStr);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-08-10 02:13:58 +00:00
|
|
|
|
break;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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,
|
|
|
|
|
false);
|
2021-08-10 21:08:39 +00:00
|
|
|
|
stropf.IsDciString = (dfd.FormatType == FormatDescriptor.Type.StringDci);
|
2019-08-09 23:42:30 +00:00
|
|
|
|
|
|
|
|
|
// Feed bytes in, skipping over hidden bytes (leading L8, trailing null).
|
|
|
|
|
stropf.FeedBytes(data, offset + hiddenLeadingBytes,
|
2019-08-14 00:22:21 +00:00
|
|
|
|
dfd.Length - hiddenLeadingBytes - trailingBytes, shownLeadingBytes,
|
|
|
|
|
StringOpFormatter.ReverseMode.Forward);
|
2019-08-09 23:42:30 +00:00
|
|
|
|
Debug.Assert(stropf.Lines.Count > 0);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
2019-08-09 23:42:30 +00:00
|
|
|
|
// See if we need to do this over.
|
|
|
|
|
bool redo = false;
|
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:
|
2019-08-09 23:42:30 +00:00
|
|
|
|
case FormatDescriptor.Type.StringL16:
|
|
|
|
|
// All good the first time.
|
2019-05-28 01:46:09 +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.StringNullTerm:
|
|
|
|
|
case FormatDescriptor.Type.StringL8:
|
2019-08-10 02:13:58 +00:00
|
|
|
|
case FormatDescriptor.Type.StringDci:
|
|
|
|
|
if (stropf.Lines.Count != 1) {
|
|
|
|
|
// Must be single-line.
|
2019-08-09 23:42:30 +00:00
|
|
|
|
opcodeStr = sDataOpNames.StrGeneric;
|
2021-08-10 21:08:39 +00:00
|
|
|
|
stropf.IsDciString = false;
|
2019-08-09 23:42:30 +00:00
|
|
|
|
redo = true;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-09 23:42:30 +00:00
|
|
|
|
if (redo) {
|
|
|
|
|
//Debug.WriteLine("REDO off=+" + offset.ToString("x6") + ": " + dfd.FormatType);
|
|
|
|
|
|
|
|
|
|
// This time, instead of skipping over leading length bytes, we include them
|
|
|
|
|
// explicitly.
|
|
|
|
|
stropf.Reset();
|
2019-08-14 00:22:21 +00:00
|
|
|
|
stropf.FeedBytes(data, offset, dfd.Length, hiddenLeadingBytes,
|
|
|
|
|
StringOpFormatter.ReverseMode.Forward);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-09 23:42:30 +00:00
|
|
|
|
opcodeStr = formatter.FormatPseudoOp(opcodeStr);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
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-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion IGenerator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region IAssembler
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cross-assembler execution interface.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class AsmTass64 : IAssembler {
|
2019-08-20 18:21:30 +00:00
|
|
|
|
// Standard options. For historical reasons the assembler expects PETSCII input by
|
|
|
|
|
// default, and requires "--ascii" for ASCII/UTF-8 input. This flag switches the
|
|
|
|
|
// default "none" encoding from "raw" to something that converts characters to
|
|
|
|
|
// PETSCII, so if you want to output strings in another format (such as ASCII) an
|
|
|
|
|
// explicit encoding must be specified.
|
2020-10-17 23:10:48 +00:00
|
|
|
|
public const string BASE_OPTIONS = "--ascii --case-sensitive -Wall";
|
|
|
|
|
public const string LONG_ADDRESS = " --long-address";
|
|
|
|
|
public const string NOSTART = " --nostart";
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
// Paths from generator.
|
|
|
|
|
private List<string> mPathNames;
|
|
|
|
|
|
|
|
|
|
// Directory to make current before executing assembler.
|
|
|
|
|
private string mWorkDirectory;
|
|
|
|
|
|
2020-10-17 23:10:48 +00:00
|
|
|
|
// Additional options specified by the source generator.
|
|
|
|
|
private string mExtraOptions;
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
// IAssembler
|
|
|
|
|
public void GetExeIdentifiers(out string humanName, out string exeName) {
|
|
|
|
|
humanName = "64tass Assembler";
|
|
|
|
|
exeName = "64tass";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.Tass64);
|
|
|
|
|
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: "64tass Turbo Assembler Macro V1.53.1515\r\n"
|
|
|
|
|
// Linux - Stdout: "64tass Turbo Assembler Macro V1.53.1515?\n"
|
|
|
|
|
|
|
|
|
|
const string PREFIX = "Macro V";
|
|
|
|
|
string str = cmd.Stdout;
|
|
|
|
|
int start = str.IndexOf(PREFIX);
|
|
|
|
|
int end = (start < 0) ? -1 : str.IndexOfAny(new char[] { '?', '\r', '\n' }, start + 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) {
|
|
|
|
|
// Clone path names, in case the caller decides to modify the original.
|
|
|
|
|
mPathNames = CommonUtil.Container.CopyStringList(results.PathNames);
|
|
|
|
|
mExtraOptions = results.ExtraOptions;
|
2019-05-28 01:46:09 +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.Tass64);
|
|
|
|
|
if (string.IsNullOrEmpty(config.ExecutablePath)) {
|
|
|
|
|
Debug.WriteLine("Assembler not configured");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
worker.ReportProgress(0, Res.Strings.PROGRESS_ASSEMBLING);
|
|
|
|
|
|
|
|
|
|
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,
|
2020-10-17 23:10:48 +00:00
|
|
|
|
BASE_OPTIONS + mExtraOptions +
|
|
|
|
|
" \"" + pathName + "\"" + " -o \"" + outFileName + "\"",
|
2019-05-28 01:46:09 +00:00
|
|
|
|
mWorkDirectory, null);
|
|
|
|
|
cmd.Execute();
|
|
|
|
|
|
|
|
|
|
// Can't really do anything with a "cancel" request.
|
|
|
|
|
|
|
|
|
|
// Output filename is the input filename without the ".S". 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
|
|
|
|
|
}
|