Skip to content

Commit

Permalink
Version 4.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
informedcitizenry committed Oct 13, 2023
1 parent 9ad340c commit 345e1ff
Show file tree
Hide file tree
Showing 27 changed files with 232 additions and 130 deletions.
8 changes: 4 additions & 4 deletions Docs/CPUSpecificAssembly.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

| Name | Family | Notable usage |
|-----------|-----------|-----------------------------------------------|
| 4510 | 65xx | Commodore 65 (Unreleased C64 successor) |
| 45GS02 | 65xx | Commodore 65 (Unreleased C64 successor) |
| 6502 | 65xx | Many popular 8-bit computer & arcade games |
| 6502i | 65xx | Undocumented 6502 instructions |
| 65C02 | 65xx | Apple IIc, Atari Lynx |
Expand Down Expand Up @@ -70,9 +70,9 @@ You can change this behavior explicitly by pre-fixing the operand with the width
jsr [24] $ffd2 // > .c005 22 d2 ff 00
```

## 65xx Long Branch Pseudo Mnemonics
## 65xx Far Branch Pseudo Mnemonics

For convenience, long branch instructions are available to the programmer, where the distance is calculated to the offset relative to the current instruction before assembly, and if the branch is too far, an absolute jump is created. The pseudo-mnemonics for these long branches are `jcc`, `jcs`, `jeq`, `jmi`, `jne`, `jpl`, `jvc`, and `jvs`.
For convenience, far branch instructions are available to the programmer, where the distance is calculated to the offset relative to the current instruction before assembly, and if the branch is too far, an absolute jump is created. The pseudo-mnemonics for these long branches are `jcc`, `jcs`, `jeq`, `jmi`, `jne`, `jpl`, `jvc`, and `jvs`.

```
* = $c000
Expand Down Expand Up @@ -186,7 +186,7 @@ Note also the `nop` mnemonic is unavailable in m65 mode because the opcode `$ea`

## GB80 Indirect and IO-addressing

The LR35902 CPU used in the Game Boy is a variant of the Z80 that lacks certain addressing modes, such as the indexed modes. It also does not support the `in` and `out` IO commands. Game Boy assemblers typically use the convention of addressing adding the high page address either to an offset or the `C` register. 6502.Net mimics this same form:
The LR35902 CPU used in the Game Boy is a variant of the Z80 that lacks certain addressing modes, such as the indexed modes. It also does not support the `in` and `out` IO commands. Due to this limitation, the Game Boy instead maps the upper page of memory for IO devices. Assemblers typically use the convention of addressing adding the high page address either to an offset or the `C` register. 6502.Net mimics this same form:

```
ld ($ff00+c),a // same as 'out (c), a', assembles to: e2
Expand Down
15 changes: 14 additions & 1 deletion Docs/Macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cr .macro
.endmacro
```

Macros are invoked with a leading . preceding the macro name, much like a directive.
Macros are invoked with a leading `.` preceding the macro name, much like a directive.

```
.cr // will expand to:
Expand Down Expand Up @@ -133,6 +133,19 @@ long_branch .macro mnemif, mnemifnot, dest
.long_branch bne,beq,$2000
```

## Macros and Scopes

Because macros are evaluated in a pre-process phase, they are considered "global" regardless of whether they appear in source within scope blocks, which are defined only during assemble time. Therefore a macro in a scope block would be invoked as if it were in the global scope.

```
myscope .block
mymacro .macro
lda #\1
.endmacro
.endblock
.mymacro // not 'myscope.mymacro'
```

## Other Topics

* [Getting Started](/Docs/GettingStarted.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,8 @@ public M65xxInstructionEncoder(AssemblyServices services)

}

private static Instruction? LookupOpcode(int key, List<Dictionary<int, Instruction>>? lookups)
private static Instruction? LookupOpcode(int key, List<Dictionary<int, Instruction>> lookups)
{
if (lookups == null)
{
return null;
}
for (int i = lookups.Count - 1; i >= 0; i--)
{
if (lookups[i].TryGetValue(key, out Instruction? opcode))
Expand Down Expand Up @@ -164,6 +160,11 @@ protected override (string, int) OnDecode(byte[] bytes, bool isSingleInstruction
return ($"{pseudo}:jmp ${jmp:x4} ", 5);
}
}
if (instr.Opcode == 0 && isSingleInstruction && bytes.Length == 2)
{
// brk #$2a
return (string.Format("brk #${0:x2}", bytes[1]), 2);
}
return (string.Format(instr.DisassemblyFormat, operandVals), instr.Size);
}

Expand Down

Large diffs are not rendered by default.

25 changes: 20 additions & 5 deletions Sixty502DotNet.Shared/CodeGen/Encoders/Z80InstructionEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,27 @@ public override bool VisitCpuInstructionGB80Index([NotNull] SyntaxParser.CpuInst
return EmitOpcode(0xe2, context);
}

public override bool VisitCpuInstructionGB80AccIncrement([NotNull] SyntaxParser.CpuInstructionGB80AccIncrementContext context)
{
if (Cpuid[0] != 'g')
{
return false;
}
int opcodeHex;
if (context.a0 != null)
{
opcodeHex = context.inc.Type == SyntaxParser.Plus ? 0x2a : 0x3a;
}
else
{
opcodeHex = context.inc.Type == SyntaxParser.Plus ? 0x22 : 0x32;
}
return EmitOpcode(opcodeHex, context);
}

public override bool VisitCpuInstructionGB80StackOffset([NotNull] SyntaxParser.CpuInstructionGB80StackOffsetContext context)
{
if (context.Start.Type != SyntaxParser.LD || Cpuid[0] != 'g')
if (Cpuid[0] != 'g')
{
return false;
}
Expand All @@ -490,10 +508,7 @@ public override bool HandleDirective(SyntaxParser.DirectiveContext directive, Sy
return false;
}

protected override void OnReset()
{

}
protected override void OnReset() { }

protected override void OnSetCpu(string cpuid)
{
Expand Down
73 changes: 39 additions & 34 deletions Sixty502DotNet.Shared/Errors/ErrorListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,15 @@ private static string GetOffendingSymbolText(IToken offendingSymbol)
{
return preprocStat.d;
}
if (context is SyntaxParser.StatBlockContext statBlock)
return context switch
{
return statBlock.b;
}
if (context is SyntaxParser.StatFuncDeclContext statFunc)
{
return statFunc.Function().Symbol;
}
if (context is SyntaxParser.StatEnumDeclContext statEnum)
{
return statEnum.Enum().Symbol;
}
if (context is SyntaxParser.InstructionContext instrContext)
{
return instrContext.Start;
}
if (context is SyntaxParser.IfBlockContext ifBlock)
{
return ifBlock.Start;
}
return null;
SyntaxParser.StatBlockContext statBlock => statBlock.b,
SyntaxParser.StatFuncDeclContext statFunc => statFunc.Function().Symbol,
SyntaxParser.StatEnumDeclContext statEnum => statEnum.Enum().Symbol,
SyntaxParser.InstructionContext instrContext => instrContext.Start,
SyntaxParser.IfBlockContext ifBlock => ifBlock.Start,
_ => null
};
}

private static readonly Dictionary<int, int> s_blockTypes = new()
Expand All @@ -64,6 +52,11 @@ private static string GetOffendingSymbolText(IToken offendingSymbol)

public override void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException? e)
{
if (offendingSymbol.Type == SyntaxParser.Unrecogized)
{
msg = $"Unrecognized character '{GetOffendingSymbolText(offendingSymbol)}'";
goto logError;
}
Parser? parser = recognizer as Parser;
if (parser == null && (e is NoViableAltException || e is LexerNoViableAltException))
{
Expand All @@ -88,8 +81,13 @@ public override void SyntaxError(TextWriter output, IRecognizer recognizer, ITok
goto logError;
}
}
if (e?.GetExpectedTokens()?.Count > 0)
if (e?.GetExpectedTokens()?.Count > 0 && e.Context is not SyntaxParser.EosContext)
{
if (offendingSymbol.Type == SyntaxParser.UnclosedLiteral)
{
msg = $"unclosed string literal";
goto logError;
}
if (e.Context is SyntaxParser.EosContext)
{
msg = $"end of statement expected but found '{GetOffendingSymbolText(offendingSymbol)}'";
Expand All @@ -98,26 +96,33 @@ public override void SyntaxError(TextWriter output, IRecognizer recognizer, ITok
var expected = e.GetExpectedTokens().ToList();
if (expected.Count < 5)
{
List<string> expectedNames = new();
for (int i = 0; i < expected.Count; i++)
if (expected.Count > 1)
{
if (expected[i] < 0)
msg = $"Invalid context for '{GetOffendingSymbolText(offendingSymbol)}'";
goto logError;
}
string expectedName = "statement";
if (expected[0] > -1)
{
string litName = vocab.GetDisplayName(expected[0]);
if (!string.IsNullOrEmpty(litName))
{
expectedNames.Add("statement");
continue;
expectedName = litName;
}
expectedNames.Add(vocab.GetLiteralName(expected[i]));
}
string messageFormat = "{0} expected but found '{1}'";
if (expected.Count > 1)
{
messageFormat = "One of: {0} expected but found '{1}'";
}
msg = string.Format(messageFormat, string.Join(',', expectedNames), GetOffendingSymbolText(offendingSymbol));
msg = string.Format(messageFormat, expectedName, GetOffendingSymbolText(offendingSymbol));
goto logError;
}
}
msg = $"Unexpected: '{offendingSymbol.Text}'";
if (offendingSymbol.Type == SyntaxParser.NL || offendingSymbol.Type == SyntaxParser.Eof)
{
msg = $"Unexpected end of statement";
}
else
{
msg = $"Unexpected: '{offendingSymbol.Text}'";
}
}
logError:
msg = EndOfFileRegex().Replace(msg, "end of file");
Expand All @@ -129,7 +134,7 @@ public override void SyntaxError(TextWriter output, IRecognizer recognizer, ITok
/// Gets the list of <see cref="Error"/> objects the parser encountered
/// during parsing.
/// </summary>
public List<Error> Errors { get; init; }
public HashSet<Error> Errors { get; init; }

[GeneratedRegex("'?EOF'?")]
private static partial Regex EndOfFileRegex();
Expand Down
35 changes: 35 additions & 0 deletions Sixty502DotNet.Shared/Errors/IllegalQuantityError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2017-2023 informedcitizenry <informedcitizenry@gmail.com>
//
// Licensed under the MIT license. See LICENSE for full license information.
//
//-----------------------------------------------------------------------------

using System;
using Antlr4.Runtime;

namespace Sixty502DotNet.Shared;

public class IllegalQuantityError : Error
{
/// <summary>
/// Construct a new instance of an <see cref="IllegalQuantityError"/> class.
/// </summary>
/// <param name="parserRuleContext">The <see cref="ParserRuleContext"/>
/// that is the source of the error.</param>
public IllegalQuantityError(ParserRuleContext parserRuleContext)
: base(parserRuleContext, "Illegal quantity")
{
}

/// <summary>
/// Construct a new instance of an <see cref="Error"/> class.
/// </summary>
/// <param name="offendingSymbol">The <see cref="IToken"/> that is the
/// source of the error.</param>
public IllegalQuantityError(IToken? offendingSymbol)
: base(offendingSymbol, "Illegal quantity")
{
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
{
if (parameters.Count == 0)
{
throw new Error(callSite.expr(), "Too few parameters to function");
throw new Error(callSite, "Too few parameters to function");
}
int offset = 0;
int size = 0;
Expand All @@ -33,7 +33,6 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
{
if (parameters.Count > 3)
{

throw new Error(callSite.exprList().expr()[^1], "Too many parameters to function");
}
size = parameters[2].AsInt();
Expand All @@ -56,7 +55,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
}
if (offset + size > file.Data.Length)
{
throw new Error(callSite.exprList().expr()[1], "Offset and/or size specified too large");
throw new Error(callSite.exprList(), "Offset and/or size specified too large");
}
ArrayValue bytes = new();
for (int i = 0; i < size; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
int addr = (int)parameters![0].AsDouble();
if (addr < short.MinValue || addr > ushort.MaxValue)
{
throw new Error(callSite.exprList().expr()[0], "Illegal quantity");
throw new IllegalQuantityError(callSite.exprList().expr()[0]);
}
var bytes = _codeOutput.GetRange(addr & 0xffff, _packed ? 5 : 6);
double conv = ToDouble(bytes.ToList(), _packed);
Expand Down Expand Up @@ -92,7 +92,7 @@ public static IEnumerable<byte> ToBytes(double value, bool packed)
var ieeeConv = new Ieee754Converter(value);
var ieee = ieeeConv.Binary;

if (ieee == 0 || ieee == 0x8000000000000000)
if (ieee == 0 || ieee == 0x8000000000000000UL)
{
// zero or negative zero
return bytes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
{
if (parameters?.Count < 1)
{
throw new Error(callSite.expr(), "Too few parameters to function");
throw new Error(callSite, "Too few parameters to function");
}
if (parameters![0] is not StringValue format)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
int addr = (int)parameters![0].AsDouble();
if (addr < short.MinValue || addr > ushort.MaxValue)
{
throw new Error(callSite.exprList().expr()[0], "Illegal quantity");
throw new IllegalQuantityError(callSite.exprList().expr()[0]);
}
return new NumericValue(_output.Peek(addr & 0xffff));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
int value = (int)parameters![1].AsDouble();
if (addr < short.MinValue || addr > ushort.MaxValue || value < sbyte.MinValue || value > byte.MaxValue)
{
throw new Error(callSite.exprList().expr()[0], "Illegal quantity");
throw new IllegalQuantityError(callSite.exprList().expr()[0]);
}
ValueBase current = new NumericValue(_codeOutput.Peek(addr & 0xffff));
_codeOutput.Poke(addr & 0xffff, (byte)(value & 0xff));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
{
if (!parameters[0].TypeCompatible(parameters[1]))
{
throw new TypeMismatchError(callSite.exprList().expr()[0]);
throw new TypeMismatchError(callSite.exprList().expr()[1]);
}
try
{
return parameters[0].AddWith(parameters[1]);
}
catch (ArgumentException)
{
throw new Error(callSite.exprList().expr()[0], "Could not concatenate collections");
throw new Error(callSite.exprList(), "Could not concatenate collections");
}
}
}
Expand Down Expand Up @@ -499,7 +499,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
ArrayValue array = (ArrayValue)parameters[0];
if (parameters[1] is not FunctionObject funcObj)
{
throw new Error(callSite.exprList().expr()[1], "Parameter must be a predicate");
throw new Error(callSite.exprList().expr()[0], "Parameter must be a predicate");
}

for (int i = 0; i < array.Count; i++)
Expand Down Expand Up @@ -584,7 +584,7 @@ protected override ValueBase OnInvoke(SyntaxParser.ExpressionCallContext callSit
}
if (parameters.Count < 2)
{
throw new Error(callSite.exprList(), "Too few parameters for function");
throw new Error(callSite, "Too few parameters for function");
}
int start = parameters[1].AsInt();
ValueBase str = parameters[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public static int ConvertLiteral(ITerminalNode terminal)
}
catch
{
throw new Error(terminal.Symbol, "Illegal quantity");
throw new IllegalQuantityError(terminal.Symbol);
}
}

Expand Down

0 comments on commit 345e1ff

Please sign in to comment.