Skip to content

Commit

Permalink
Merge pull request #5 from Konamiman/add-linkstor80-compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
Konamiman authored Mar 20, 2023
2 parents 832afbf + e96b6ec commit 420f3a6
Show file tree
Hide file tree
Showing 104 changed files with 5,260 additions and 645 deletions.
1 change: 1 addition & 0 deletions Assembler/Assembler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<RootNamespace>Konamiman.Nestor80.Assembler</RootNamespace>
<AssemblyVersion></AssemblyVersion>
</PropertyGroup>

</Project>
7 changes: 7 additions & 0 deletions Assembler/AssemblyConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,12 @@ public class AssemblyConfiguration
/// Maximum amount of content that will be read from files included with the INCBIN instruction.
/// </summary>
public int MaxIncbinFileSize { get; init; } = 65536;

/// <summary>
/// True to produce relocatable files that are compatible with LINK-80
/// (so public and external symbols are limited to 6 ASCII-only characters, and the set of
/// arithmetic operators allowed for expressions with external references is limited).
/// </summary>
public bool Link80Compatibility { get; init; } = false;
}
}
8 changes: 6 additions & 2 deletions Assembler/AssemblySourceProcessor.CpuInstructions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,11 @@ CpuInstrArgType.Word or
}

private static bool CpuInstrArgTypeIsByte(CpuInstrArgType type) =>
type is CpuInstrArgType.Byte or CpuInstrArgType.ByteInParenthesis or CpuInstrArgType.OffsetFromCurrentLocation or CpuInstrArgType.IxOffset or CpuInstrArgType.IyOffset;
//Note that we aren't including CpuInstrArgType.OffsetFromCurrentLocation even though it's of byte type, that's on purpose.
//We need expressions of that type to be always evaluated in pass 2 by AdjustInstructionLineForExpression, instead of
//generating a relocatable expression with "store as byte";
//the resulting offset will later be adjusted by ProcessArgumentForInstruction.
type is CpuInstrArgType.Byte or CpuInstrArgType.ByteInParenthesis or CpuInstrArgType.IxOffset or CpuInstrArgType.IyOffset;

private static void CompleteInstructionLine(CpuInstructionLine line)
{
Expand Down Expand Up @@ -524,7 +528,7 @@ private static RelocatableOutputPart RelocatableFromAddress(Address address, int
{
return address.IsAbsolute ?
null :
new RelocatableValue() { Type = address.Type, Value = address.Value, Index = index, IsByte = (size == 1) };
new RelocatableValue() { Type = address.Type, Value = address.Value, Index = index, IsByte = (size == 1), CommonName = address.CommonBlockName };
}
}
}
20 changes: 15 additions & 5 deletions Assembler/AssemblySourceProcessor.PseudoOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ void AddZero()
}
else {
AddZero();
relocatables.Add(new RelocatableValue() { Index = index, IsByte = isByte, Type = value.Type, Value = value.Value });
relocatables.Add(new RelocatableValue() { Index = index, IsByte = isByte, Type = value.Type, Value = value.Value, CommonName = value.CommonBlockName });
if(!isByte) index++;
}
}
Expand Down Expand Up @@ -935,15 +935,20 @@ static ProcessedSourceLine ProcessSetProgramName(string opcode, SourceLineWalker
effectiveLineLength = walker.SourceLine.Length;
}

var match = ProgramNameRegex.Match(rawName);
var match = programNameRegex.Match(rawName);
if(!match.Success) {
AddError(AssemblyErrorCode.InvalidArgument, $"{opcode.ToUpper()}: the program name must be in the format ('NAME')");
return new ProgramNameLine() { EffectiveLineLength = effectiveLineLength };
}

var name = match.Groups["name"].Value;

programName = (name.Length > MaxEffectiveExternalNameLength ? name[..MaxEffectiveExternalNameLength] : name).ToUpper();
if(link80Compatibility) {
programName = (name.Length > MaxEffectiveExternalNameLength ? name[..MaxEffectiveExternalNameLength] : name).ToUpper();
}
else {
programName = name;
}
programNameInstructionFound = true;

return new ProgramNameLine() { Name = rawName, EffectiveLineLength = effectiveLineLength };
Expand All @@ -955,7 +960,12 @@ static ProcessedSourceLine ProcessSetListingTitleLine(string opcode, SourceLineW

if(!programNameInstructionFound) {
var firstWord = title.Split(' ', '\t')[0];
programName = firstWord.Length > MaxEffectiveExternalNameLength ? firstWord[..MaxEffectiveExternalNameLength] : firstWord;
if(link80Compatibility) {
programName = firstWord.Length > MaxEffectiveExternalNameLength ? firstWord[..MaxEffectiveExternalNameLength] : firstWord;
}
else {
programName = firstWord;
}
}

return new SetListingTitleLine() { Title = title, EffectiveLineLength = walker.SourceLine.Length };
Expand Down Expand Up @@ -1070,7 +1080,7 @@ static ProcessedSourceLine ProcessRequestLinkFilesLine(string opcode, SourceLine
else if(!externalSymbolRegex.IsMatch(filename)) {
AddError(AssemblyErrorCode.InvalidArgument, $"{opcode.ToUpper()}: {filename} is not a valid linker request symbol name, it contains invalid characters");
}
else if(filename.Length > MaxEffectiveRequestFilenameLength) {
else if(link80Compatibility && filename.Length > MaxEffectiveRequestFilenameLength) {
var truncated = filename[..MaxEffectiveRequestFilenameLength].ToUpper();
AddError(AssemblyErrorCode.TruncatedRequestFilename, $"{opcode.ToUpper()}: {filename} is too long, it will be truncated to {truncated}");
filenames.Add(truncated);
Expand Down
56 changes: 39 additions & 17 deletions Assembler/AssemblySourceProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static partial class AssemblySourceProcessor
private static int errorsGenerated = 0;
private static string programName = null;
private static bool programNameInstructionFound = false;
private static bool link80Compatibility;

private static readonly string[] z80RegisterNames = new[] {
"A", "B", "C", "D", "E", "F", "H", "L", "I", "R",
Expand Down Expand Up @@ -76,13 +77,20 @@ public static partial class AssemblySourceProcessor

private static CpuType currentCpu;

private static readonly Regex labelRegex = new("^[\\w$@?._][\\w$@?._0-9]*:{0,2}$", RegxOp);
private static readonly Regex externalSymbolRegex = new("^[a-zA-Z_$@?.][a-zA-Z_$@?.0-9]*$", RegxOp);
private static readonly Regex ProgramNameRegex = new(@"^\('(?<name>[a-zA-Z_$@?.][a-zA-Z_$@?.0-9]*)'\)", RegxOp);
private static readonly Regex labelRegex = new("^([^\\d\\W]|[$@?._])[\\w$@?._]*:{0,2}$", RegxOp);

private static Regex externalSymbolRegex;
private static readonly Regex externalSymbolRegex_full = new("^([^\\d\\W]|[$@?._])[\\w$@?._]*$", RegxOp);
private static readonly Regex externalSymbolRegex_link80 = new("^[A-Z_$@?.][A-Z_$@?.0-9]*$", RegxOp);

private static Regex programNameRegex;
private static readonly Regex programNameRegex_full = new(@"^\('(?<name>([^\d\W]|[$@?._])[\w$@?._]*)'\)", RegxOp);
private static readonly Regex programNameRegex_link80 = new(@"^\('(?<name>[A-Z_$@?.][A-Z_$@?.0-9]*)'\)", RegxOp);

private static readonly Regex LegacySubtitleRegex = new(@"^\('(?<name>[^']*)'\)", RegxOp);
private static readonly Regex printStringExpressionRegex = new(@"(?<=\{)[^}]*(?=\})", RegxOp);
private static readonly Regex commonBlockNameRegex = new(@"^/[ \t]*/|/[A-Z$@?._][A-Z$@?._0-9]*/$", RegxOp);

//Constant definitions are considered pseudo-ops, but they are handled as a special case
//(instead of being included in PseudoOpProcessors) because the actual opcode comes after the name of the constant
private static readonly string[] constantDefinitionOpcodes = { "EQU", "DEFL", "ASET" };
Expand Down Expand Up @@ -156,6 +164,7 @@ public static AssemblyResult Assemble(Stream sourceStream, Encoding sourceStream

ProcessPredefinedsymbols(configuration.PredefinedSymbols);
maxErrors = configuration.MaxErrors;
link80Compatibility = configuration.Link80Compatibility;

SetCurrentCpu(configuration.CpuName);
buildType = configuration.BuildType;
Expand All @@ -170,6 +179,12 @@ public static AssemblyResult Assemble(Stream sourceStream, Encoding sourceStream
Expression.GetSymbol = GetSymbolForExpression;
Expression.ModularizeSymbolName = name => state.Modularize(name);
Expression.AllowEscapesInStrings = configuration.AllowEscapesInStrings;
Expression.Link80Compatibility = link80Compatibility;
SymbolInfo.Link80Compatibility = link80Compatibility;
LinkItem.Link80Compatibility = link80Compatibility;

externalSymbolRegex = link80Compatibility ? externalSymbolRegex_link80 : externalSymbolRegex_full;
programNameRegex = link80Compatibility ? programNameRegex_link80 : programNameRegex_full;

incbinBuffer = new byte[configuration.MaxIncbinFileSize];

Expand Down Expand Up @@ -226,15 +241,17 @@ public static AssemblyResult Assemble(Stream sourceStream, Encoding sourceStream
AddError(AssemblyErrorCode.SameEffectivePublic, $"Public symbols {names} actually refer to the same one: {dupe.First().EffectiveName}", withLineNumber: false);
}

var duplicateCommonBlocks = state
.GetCommonBlockSizes()
.Where(x => x.Key.Length > MaxEffectiveExternalNameLength)
.GroupBy(n => n.Key[..MaxEffectiveExternalNameLength])
.Where(n => n.Count() > 1)
.ToArray();
foreach(var dupe in duplicateCommonBlocks) {
var names = string.Join(", ", dupe.Select(d => d.Key).ToArray());
AddError(AssemblyErrorCode.SameEffectiveCommon, $"Common block names {names} actually refer to the same one: {dupe.First().Key[..MaxEffectiveExternalNameLength]}", withLineNumber: false);
if(configuration.Link80Compatibility) {
var duplicateCommonBlocks = state
.GetCommonBlockSizes()
.Where(x => x.Key.Length > MaxEffectiveExternalNameLength)
.GroupBy(n => n.Key[..MaxEffectiveExternalNameLength])
.Where(n => n.Count() > 1)
.ToArray();
foreach(var dupe in duplicateCommonBlocks) {
var names = string.Join(", ", dupe.Select(d => d.Key).ToArray());
AddError(AssemblyErrorCode.SameEffectiveCommon, $"Common block names {names} actually refer to the same one: {dupe.First().Key[..MaxEffectiveExternalNameLength]}", withLineNumber: false);
}
}

if(buildType == BuildType.Automatic)
Expand Down Expand Up @@ -265,7 +282,7 @@ public static AssemblyResult Assemble(Stream sourceStream, Encoding sourceStream
EndAddressArea = state.EndAddress is null ? AddressType.ASEG : state.EndAddress.Type,
EndAddress = (ushort)(state.EndAddress is null ? 0 : state.EndAddress.Value),
BuildType = buildType,
MaxRelocatableSymbolLength = MaxEffectiveExternalNameLength,
MaxRelocatableSymbolLength = configuration.Link80Compatibility ? MaxEffectiveExternalNameLength : int.MaxValue,
MacroNames = state.NamedMacros.Keys.ToArray()
};
}
Expand Down Expand Up @@ -722,6 +739,10 @@ private static void ProcessExpressionsPendingEvaluation(ProcessedSourceLine proc
}

if(referencedSymbols.Any(s => s.IsExternal)) {
if(expressionPendingEvaluation.ArgumentType is CpuInstrArgType.OffsetFromCurrentLocation) {
AddError(AssemblyErrorCode.InvalidExpression, $"Invalid expression for {processedLine.Opcode.ToUpper()}: the expression can't contain external references");
continue;
}
hasExternalReferences = true;
}
else {
Expand Down Expand Up @@ -774,7 +795,8 @@ private static void ProcessExpressionsPendingEvaluation(ProcessedSourceLine proc
Index = expressionPendingEvaluation.LocationInOutput,
IsByte = expressionPendingEvaluation.IsByte,
Type = expressionValue.Type,
Value = expressionValue.Value
Value = expressionValue.Value,
CommonName = expressionValue.CommonBlockName
});
}
}
Expand Down Expand Up @@ -815,8 +837,8 @@ private static LinkItemsGroup GetLinkItemsGroupFromExpression(ProcessedSourceLin
}
else if(part is ArithmeticOperator op) {
if(op is not UnaryPlusOperator) {
if(op.ExtendedLinkItemType is null) {
AddError(AssemblyErrorCode.InvalidForRelocatable, $"Operator {op} is not allowed in expressions involving external references");
if(link80Compatibility && op.ExtendedLinkItemType > ArithmeticOperator.SmallestExtendedOperatorCode) {
AddError(AssemblyErrorCode.InvalidForRelocatable, $"Operator {op} is not allowed in expressions involving external references in LINK-80 compatibility mode");
return null;
}
items.Add(LinkItem.ForArithmeticOperator((ArithmeticOperatorCode)op.ExtendedLinkItemType));
Expand Down
24 changes: 19 additions & 5 deletions Assembler/Expressions/Expression.Evaluation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,15 +228,22 @@ private Address EvaluateCore(bool stopOnSymbolFound, bool throwOnUnknownSymbol)
}

if(uop is not UnaryPlusOperator && !poppedAddress.IsAbsolute && isByte) {
if(uop.ExtendedLinkItemType is null) {
Throw($"Operator {uop} is not allowed in expressions involving relocatable addresses that evaluate to a single byte", AssemblyErrorCode.InvalidForRelocatable);
if(Link80Compatibility && uop.ExtendedLinkItemType > ArithmeticOperator.SmallestExtendedOperatorCode) {
Throw($"Operator {uop} is not allowed in expressions involving relocatable addresses that evaluate to a single byte in LINK-80 compatibility mode", AssemblyErrorCode.InvalidForRelocatable);
return null;
}
HasRelocatableToStoreAsByte = true;
return null;
}

operationResult = uop.Operate(poppedAddress, null);
operationResult = null;
try {
operationResult = uop.Operate(poppedAddress, null);
}
catch(Exception ex) {
Throw($"Error when applying operator {uop}: {ex.Message}");
}

stack.Push(operationResult);
}
else if(item is BinaryOperator bop) {
Expand All @@ -257,16 +264,23 @@ private Address EvaluateCore(bool stopOnSymbolFound, bool throwOnUnknownSymbol)
}

if((!poppedAddress1.IsAbsolute || !poppedAddress2.IsAbsolute) && isByte) {
if(bop.ExtendedLinkItemType is null) {
Throw($"Operator {bop} is not allowed in expressions involving relocatable addresses that evaluate to a single byte", AssemblyErrorCode.InvalidForRelocatable);
if(Link80Compatibility && bop.ExtendedLinkItemType > ArithmeticOperator.SmallestExtendedOperatorCode) {
Throw($"Operator {bop} is not allowed in expressions involving relocatable addresses that evaluate to a single byte in LINK-80 compatibility mode", AssemblyErrorCode.InvalidForRelocatable);
return null;
}

HasRelocatableToStoreAsByte = true;
return null;
}

operationResult = null;
try {
operationResult = bop.Operate(poppedAddress1, poppedAddress2);
}
catch(Exception ex) {
Throw($"Error when applying operator {bop}: {ex.Message}");
}

stack.Push(operationResult);
}
else {
Expand Down
5 changes: 5 additions & 0 deletions Assembler/Expressions/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ public static Encoding OutputStringEncoding
/// </summary>
public static Func<string, string> ModularizeSymbolName { get; set; } = name => name;

/// <summary>
/// A flag indicating if compatibility with LINK-80 must be preserved.
/// </summary>
public static bool Link80Compatibility { get; set; } = false;

/// <summary>
/// The parts that compose the expression, will be in postfix format
/// after <see cref="Postfixize"/> is executed.
Expand Down
14 changes: 7 additions & 7 deletions Assembler/Expressions/ExpressionParts/ArithmeticOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ namespace Konamiman.Nestor80.Assembler.Expressions.ExpressionParts
/// </summary>
internal abstract class ArithmeticOperator : IExpressionPart
{
/// <summary>
/// The smallest operator code defined by the extended relocatable file format.
/// </summary>
public static int SmallestExtendedOperatorCode = 16;

protected ArithmeticOperator() { }

public abstract int Precedence { get; }
Expand All @@ -16,14 +21,9 @@ protected ArithmeticOperator() { }
public abstract bool IsUnary { get; }

/// <summary>
/// The "Arithmetic operator" extended link item as defined by the LINK-80 relocatable
/// file format admits only a subset of the existing operators.
/// Those will have this property redefined so that it returns the proper operator code
/// as expected by LINK-80.
/// The "Arithmetic operator" extended link item as defined by the LINK-80 relocatable file format.
/// </summary>
public virtual byte? ExtendedLinkItemType => null;

public bool AllowedForRelocatableSymbols => ExtendedLinkItemType != null;
public abstract byte ExtendedLinkItemType { get; }

protected static Address AbsoluteZero => new Address(AddressType.ASEG, 0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ internal class AndOperator : BinaryOperator

public override string Name => "AND";

public override byte ExtendedLinkItemType => 24;

protected override Address OperateCore(Address value1, Address value2)
{
// At least one of the operands must be Absolute
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Konamiman.Nestor80.Assembler.Expressions.ExpressionParts;

namespace Konamiman.Nestor80.Assembler.Expressions.ExpressionParts.ArithmeticOperators
namespace Konamiman.Nestor80.Assembler.Expressions.ExpressionParts.ArithmeticOperators
{
internal abstract class BinaryOperator : ArithmeticOperator
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal class DivideOperator : BinaryOperator

public override string Name => "/";

public override byte? ExtendedLinkItemType => 10;
public override byte ExtendedLinkItemType => 10;

protected override Address OperateCore(Address value1, Address value2)
{
Expand All @@ -24,7 +24,7 @@ protected override Address OperateCore(Address value1, Address value2)

unchecked
{
return new Address(value1.Type, (ushort)(value1.Value / value2.Value));
return new Address(value1.Type, (ushort)(value1.Value / value2.Value), value1.CommonBlockName);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ internal class EqualsOperator : BinaryOperator

public override string Name => "EQ";

public override byte ExtendedLinkItemType => 18;

protected override Address OperateCore(Address value1, Address value2)
{
// Both addresses must be in the same mode
Expand Down
Loading

0 comments on commit 420f3a6

Please sign in to comment.