From 4056dc9f4fdbf60837fc1d8ab28dcd230de7d243 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Sun, 11 Feb 2024 20:38:19 -0600 Subject: [PATCH] Add constants and qualified type names to lexer, and use instruction schema to decode UIB instrctions --- libs/UIX.Asm/InstructionSet.cs | 62 ++++++++++++++-------------- libs/UIX.Asm/Lexer.Body.cs | 64 +++++++++++++++++++++-------- libs/UIX.Asm/Lexer.Directives.cs | 54 ++++++++++++++++++++---- libs/UIX.Asm/Lexer.Imports.cs | 32 ++++----------- libs/UIX.Asm/Lexer.cs | 33 +++++++++++++++ libs/UIX.Asm/Models/Directives.cs | 10 +++-- libs/UIX.Asm/Models/Imports.cs | 16 ++------ libs/UIX.Asm/Models/Operands.cs | 37 +++++++++++++---- libs/UIX.Asm/Models/SyntaxModels.cs | 14 +++++++ 9 files changed, 216 insertions(+), 106 deletions(-) diff --git a/libs/UIX.Asm/InstructionSet.cs b/libs/UIX.Asm/InstructionSet.cs index 1c61db3..2040c33 100644 --- a/libs/UIX.Asm/InstructionSet.cs +++ b/libs/UIX.Asm/InstructionSet.cs @@ -119,31 +119,31 @@ internal static class InstructionSet [OpCode.ReturnValue] = [], [OpCode.ReturnVoid] = [], - [OpCode.ConstructObject] = Inst_UInt16, + [OpCode.ConstructObject] = [LiteralDataType.TypeIndex], [OpCode.ConstructObjectIndirect] = Inst_UInt16, - [OpCode.InitializeInstance] = Inst_UInt16, - [OpCode.LookupSymbol] = Inst_UInt16, - [OpCode.WriteSymbol] = Inst_UInt16, - [OpCode.WriteSymbolPeek] = Inst_UInt16, - [OpCode.ClearSymbol] = Inst_UInt16, - [OpCode.PropertyInitialize] = Inst_UInt16, - [OpCode.PropertyInitializeIndirect] = Inst_UInt16, - [OpCode.PropertyListAdd] = Inst_UInt16, - [OpCode.PropertyAssign] = Inst_UInt16, - [OpCode.PropertyAssignStatic] = Inst_UInt16, - [OpCode.PropertyGet] = Inst_UInt16, - [OpCode.PropertyGetPeek] = Inst_UInt16, - [OpCode.PropertyGetStatic] = Inst_UInt16, - [OpCode.MethodInvoke] = Inst_UInt16, - [OpCode.MethodInvokePeek] = Inst_UInt16, - [OpCode.MethodInvokeStatic] = Inst_UInt16, - [OpCode.MethodInvokePushLastParam] = Inst_UInt16, - [OpCode.MethodInvokeStaticPushLastParam] = Inst_UInt16, - [OpCode.VerifyTypeCast] = Inst_UInt16, - [OpCode.IsCheck] = Inst_UInt16, - [OpCode.As] = Inst_UInt16, - [OpCode.TypeOf] = Inst_UInt16, - [OpCode.PushConstant] = Inst_UInt16, + [OpCode.InitializeInstance] = [LiteralDataType.TypeIndex], + [OpCode.LookupSymbol] = [LiteralDataType.SymbolRefIndex], + [OpCode.WriteSymbol] = [LiteralDataType.SymbolRefIndex], + [OpCode.WriteSymbolPeek] = [LiteralDataType.SymbolRefIndex], + [OpCode.ClearSymbol] = [LiteralDataType.SymbolRefIndex], + [OpCode.PropertyInitialize] = [LiteralDataType.PropertyIndex], + [OpCode.PropertyInitializeIndirect] = [LiteralDataType.PropertyIndex], + [OpCode.PropertyListAdd] = [LiteralDataType.PropertyIndex], + [OpCode.PropertyAssign] = [LiteralDataType.PropertyIndex], + [OpCode.PropertyAssignStatic] = [LiteralDataType.PropertyIndex], + [OpCode.PropertyGet] = [LiteralDataType.PropertyIndex], + [OpCode.PropertyGetPeek] = [LiteralDataType.PropertyIndex], + [OpCode.PropertyGetStatic] = [LiteralDataType.PropertyIndex], + [OpCode.MethodInvoke] = [LiteralDataType.MethodIndex], + [OpCode.MethodInvokePeek] = [LiteralDataType.MethodIndex], + [OpCode.MethodInvokeStatic] = [LiteralDataType.MethodIndex], + [OpCode.MethodInvokePushLastParam] = [LiteralDataType.MethodIndex], + [OpCode.MethodInvokeStaticPushLastParam] = [LiteralDataType.MethodIndex], + [OpCode.VerifyTypeCast] = [LiteralDataType.TypeIndex], + [OpCode.IsCheck] = [LiteralDataType.TypeIndex], + [OpCode.As] = [LiteralDataType.TypeIndex], + [OpCode.TypeOf] = [LiteralDataType.TypeIndex], + [OpCode.PushConstant] = [LiteralDataType.ConstantIndex], [OpCode.ConstructListenerStorage] = Inst_UInt16, [OpCode.JumpIfFalse] = Inst_UInt32, @@ -154,16 +154,16 @@ internal static class InstructionSet [OpCode.EnterDebugState] = [LiteralDataType.Int32], - [OpCode.ConstructObjectParam] = Inst_UInt16x2, - [OpCode.ConstructFromString] = Inst_UInt16x2, - [OpCode.PropertyDictionaryAdd] = Inst_UInt16x2, - [OpCode.ConvertType] = Inst_UInt16x2, + [OpCode.ConstructObjectParam] = [LiteralDataType.TypeIndex, LiteralDataType.UInt16], + [OpCode.ConstructFromString] = [LiteralDataType.TypeIndex, LiteralDataType.ConstantIndex], + [OpCode.PropertyDictionaryAdd] = [LiteralDataType.TypeIndex, LiteralDataType.ConstantIndex], + [OpCode.ConvertType] = [LiteralDataType.TypeIndex, LiteralDataType.TypeIndex], - [OpCode.JumpIfDictionaryContains] = [LiteralDataType.UInt16, LiteralDataType.UInt16, LiteralDataType.UInt32], + [OpCode.JumpIfDictionaryContains] = [LiteralDataType.PropertyIndex, LiteralDataType.UInt16, LiteralDataType.UInt32], - [OpCode.ConstructFromBinary] = [LiteralDataType.UInt16, LiteralDataType.Bytes], + [OpCode.ConstructFromBinary] = [LiteralDataType.TypeIndex, LiteralDataType.Bytes], - [OpCode.Operation] = [LiteralDataType.UInt16, LiteralDataType.Byte], + [OpCode.Operation] = [LiteralDataType.OpHostIndex, LiteralDataType.Byte], [OpCode.Listen] = [LiteralDataType.UInt16, LiteralDataType.Byte, LiteralDataType.UInt16, LiteralDataType.UInt32], [OpCode.DestructiveListen] = [LiteralDataType.UInt16, LiteralDataType.Byte, LiteralDataType.UInt16, LiteralDataType.UInt32, LiteralDataType.UInt32], diff --git a/libs/UIX.Asm/Lexer.Body.cs b/libs/UIX.Asm/Lexer.Body.cs index cdce252..d48cdda 100644 --- a/libs/UIX.Asm/Lexer.Body.cs +++ b/libs/UIX.Asm/Lexer.Body.cs @@ -44,7 +44,7 @@ partial class Lexer } else { - List operands = new(); + Operand[] operands = null; var endOfInstructionResult = StatementEnd(input); input = endOfInstructionResult.Remainder; @@ -52,34 +52,62 @@ partial class Lexer { input = ConsumeWhitespace(input); - var operandsResult = Parse.Ref(() => AlphanumericText).DelimitedBy(Parse.Char(',').Token())(input); - input = operandsResult.Remainder; - var opCode = InstructionSet.MnemonicToOpCode(identifier); var schema = InstructionSet.InstructionSchema[opCode]; + operands = new Operand[schema.Length]; - int schemaIndex = 0; - foreach (var operandContent in operandsResult.Value) + for (int operandIndex = 0; operandIndex < schema.Length; operandIndex++) { - var operandType = schema[schemaIndex++]; + Operand operand; + var operandType = schema[operandIndex]; - object operandValue = operandType switch + var operandConstPrefixResult = Parse.Char('@')(input); + input = operandConstPrefixResult.Remainder; + if (operandConstPrefixResult.WasSuccessful) { - LiteralDataType.Byte => byte.Parse(operandContent), - LiteralDataType.UInt16 => ushort.Parse(operandContent), - LiteralDataType.UInt32 => uint.Parse(operandContent), - LiteralDataType.Int32 => int.Parse(operandContent), - LiteralDataType.Bytes or _ => operandContent, - }; + if (!OperandLiteral.IsIndex(operandType)) + return Result.Failure(input, "Invalid instruction", [$"Constant references are not allowed for this operand, expected a {operandType}"]); - operands.Add(new(operandValue, operandType, operandContent) + var operandConstResult = Identifier(input); + input = operandConstResult.Remainder; + if (!operandConstResult.WasSuccessful) + return Result.Failure(input, "Invalid instruction", ["Invalid constant name"]); + + operand = new OperandReference(operandConstResult.Value) + { + Line = line, + }; + } + else { - Line = line, - }); + var operandContentResult = AlphanumericText(input); + input = operandContentResult.Remainder; + if (!operandContentResult.WasSuccessful) + return Result.Failure(input, "Invalid instruction", ["Invalid operand"]); + + var operandContent = operandContentResult.Value; + object operandValue = OperandLiteral.ReduceDataType(operandType) switch + { + LiteralDataType.Byte => byte.Parse(operandContent), + LiteralDataType.UInt16 => ushort.Parse(operandContent), + LiteralDataType.UInt32 => uint.Parse(operandContent), + LiteralDataType.Int32 => int.Parse(operandContent), + LiteralDataType.Bytes or _ => operandContent, + }; + + operand = new OperandLiteral(operandValue, operandType, operandContent) + { + Line = line, + }; + } + + operands[operandIndex] = operand; + + input = Parse.Char(',').Token()(input).Remainder; } } - bodyItem = new Instruction(identifier, operands) + bodyItem = new Instruction(identifier, operands ?? []) { Line = line, Column = column, diff --git a/libs/UIX.Asm/Lexer.Directives.cs b/libs/UIX.Asm/Lexer.Directives.cs index 301a805..8cd12af 100644 --- a/libs/UIX.Asm/Lexer.Directives.cs +++ b/libs/UIX.Asm/Lexer.Directives.cs @@ -30,12 +30,12 @@ partial class Lexer case "SECTION": if (StatementEnd(input).WasSuccessful) - return Result.Failure(input, "Invalid section directive", ["Expected a section name"]); + return Result.Failure(input, "Invalid section directive", ["Expected a section name"]); var sectionNameResult = Parse.Letter.AtLeastOnce().Token().Text()(input); input = sectionNameResult.Remainder; if (!sectionNameResult.WasSuccessful) - return Result.Failure(input, "Invalid section name", ["Expected a section name containing only letters"]); + return Result.Failure(input, "Invalid section name", ["Expected a section name containing only letters"]); directive = new SectionDirective(sectionNameResult.Value) { @@ -44,27 +44,65 @@ partial class Lexer }; break; + case "CONSTANT": + if (StatementEnd(input).WasSuccessful) + return Result.Failure(input, "Invalid constant directive", ["Expected a consant declaration"]); + + var constNameResult = Identifier.Token()(input); + input = constNameResult.Remainder; + if (!constNameResult.WasSuccessful) + return Result.Failure(input, "Invalid constant directive", ["Expected a name for the constant"]); + + input = Parse.Char('=').Token()(input).Remainder; + + var typeNameResult = QualifiedTypeName.Token()(input); + input = typeNameResult.Remainder; + if (!typeNameResult.WasSuccessful) + return Result.Failure(input, "Invalid constant directive", ["Expected qualified name of type to construct"]); + + var openBracketResult = Parse.Char('(').Token()(input); + input = openBracketResult.Remainder; + if (!openBracketResult.WasSuccessful) + return Result.Failure(input, "Invalid constant directive", ["Expected '('"]); + + var contentResult = Parse.CharExcept(')').AtLeastOnce().Text().Token()(input); + input = contentResult.Remainder; + if (!contentResult.WasSuccessful) + return Result.Failure(input, "Invalid constant directive", ["Expected constant value"]); + + var closeBracketResult = Parse.Char(')').Token()(input); + input = closeBracketResult.Remainder; + if (!closeBracketResult.WasSuccessful) + return Result.Failure(input, "Invalid constant directive", ["Expected ')'"]); + + directive = new ConstantDirective(constNameResult.Value, typeNameResult.Value, contentResult.Value) + { + Line = line, + Column = column, + }; + break; + case "EXPORT": if (StatementEnd(input).WasSuccessful) - return Result.Failure(input, "Invalid export directive", ["Expected export information"]); + return Result.Failure(input, "Invalid export directive", ["Expected export information"]); var labelPrefixResult = Identifier.Token()(input); input = labelPrefixResult.Remainder; if (!labelPrefixResult.WasSuccessful) - return Result.Failure(input, "Invalid export directive", ["Expected prefix of labels to export"]); + return Result.Failure(input, "Invalid export directive", ["Expected prefix of labels to export"]); var listenerCountResult = WholeNumber.Token()(input); input = listenerCountResult.Remainder; if (!listenerCountResult.WasSuccessful) - return Result.Failure(input, "Invalid export directive", ["Expected listener count"]); + return Result.Failure(input, "Invalid export directive", ["Expected listener count"]); if (!uint.TryParse(listenerCountResult.Value, out var listenerCount)) - return Result.Failure(input, "Invalid export directive", ["Expected export listener count to be an unsigned integer"]); + return Result.Failure(input, "Invalid export directive", ["Expected export listener count to be an unsigned integer"]); var baseTypeNameResult = AlphanumericText.Token()(input); input = baseTypeNameResult.Remainder; if (!baseTypeNameResult.WasSuccessful) - return Result.Failure(input, "Invalid export directive", ["Expected base type name"]); + return Result.Failure(input, "Invalid export directive", ["Expected base type name"]); var labelPrefix = labelPrefixResult.Value; var baseTypeName = baseTypeNameResult.Value; @@ -76,7 +114,7 @@ partial class Lexer break; default: - return Result.Failure(input, $"Unknown import type '{directiveIdResult.Value}'", ["Expected 'export', 'import', or 'section'"]); + return Result.Failure(input, $"Unknown import type '{directiveIdResult.Value}'", ["Expected 'export', 'import', or 'section'"]); } return Result.Success(directive, input); diff --git a/libs/UIX.Asm/Lexer.Imports.cs b/libs/UIX.Asm/Lexer.Imports.cs index 1a4cc86..5bb968d 100644 --- a/libs/UIX.Asm/Lexer.Imports.cs +++ b/libs/UIX.Asm/Lexer.Imports.cs @@ -48,36 +48,18 @@ partial class Lexer break; case "TYPE": - var typePrefixResult = Identifier.Token()(input); - input = typePrefixResult.Remainder; - if (!typePrefixResult.WasSuccessful) - return Result.Failure(input, "Invalid type import", ["Expected a valid namespace prefix"]); + input = ConsumeWhitespace(input); - var typeNamespaceDelimitterResult = Parse.Char(':')(input); - input = typeNamespaceDelimitterResult.Remainder; + var typeNameResult = ParseQualifiedTypeName(input); + input = typeNameResult.Remainder; + if (!typeNameResult.WasSuccessful) + return Result.Failure(input, "Invalid type import", ["Expected qualified type name"]); - string typeName, typePrefix; - if (typeNamespaceDelimitterResult.WasSuccessful) - { - var typeNameResult = Identifier(input); - input = typeNameResult.Remainder; - if (!typeNameResult.WasSuccessful) - return Result.Failure(input, "Invalid type import", ["Expected a valid type name"]); - - typePrefix = typePrefixResult.Value; - typeName = typeNameResult.Value; - } - else - { - typePrefix = null; - typeName = typePrefixResult.Value; - } - - import = new TypeImport(typePrefix, typeName); + import = new TypeImport(typeNameResult.Value); break; default: - return Result.Failure(input, $"Unknown import type '{importTypeResult.Value}'", ["Expected 'ns'"]); + return Result.Failure(input, $"Unknown import type '{importTypeResult.Value}'", ["Expected 'ns', 'type'"]); } return Result.Success(import, input); diff --git a/libs/UIX.Asm/Lexer.cs b/libs/UIX.Asm/Lexer.cs index ddaf3f9..2e70473 100644 --- a/libs/UIX.Asm/Lexer.cs +++ b/libs/UIX.Asm/Lexer.cs @@ -18,6 +18,8 @@ public static partial class Lexer public static readonly Parser StatementEnd = Parse.Char(';').Return(";").Or(Parse.LineTerminator); + public static readonly Parser QualifiedTypeName = ParseQualifiedTypeName; + public static readonly Parser Import = ParseImport; public static readonly Parser Directive = ParseDirective; @@ -29,4 +31,35 @@ public static partial class Lexer select new Program(body); private static IInput ConsumeWhitespace(IInput input) => Parse.WhiteSpace.Many()(input).Remainder; + + private static IResult ParseQualifiedTypeName(IInput input) + { + var typePrefixResult = Identifier(input); + input = typePrefixResult.Remainder; + if (!typePrefixResult.WasSuccessful) + return Result.Failure(input, "Invalid type name", ["Expected a valid namespace prefix"]); + + var typeNamespaceDelimitterResult = Parse.Char(':')(input); + input = typeNamespaceDelimitterResult.Remainder; + + string typeName, typePrefix; + if (typeNamespaceDelimitterResult.WasSuccessful) + { + var typeNameResult = Identifier(input); + input = typeNameResult.Remainder; + if (!typeNameResult.WasSuccessful) + return Result.Failure(input, "Invalid type name", ["Expected a valid type name"]); + + typePrefix = typePrefixResult.Value; + typeName = typeNameResult.Value; + } + else + { + typePrefix = null; + typeName = typePrefixResult.Value; + } + + QualifiedTypeName qualifiedName = new(typePrefix, typeName); + return Result.Success(qualifiedName, input); + } } diff --git a/libs/UIX.Asm/Models/Directives.cs b/libs/UIX.Asm/Models/Directives.cs index 06b4ff4..1ab206f 100644 --- a/libs/UIX.Asm/Models/Directives.cs +++ b/libs/UIX.Asm/Models/Directives.cs @@ -14,16 +14,18 @@ public record SectionDirective : Directive public record ConstantDirective : Directive { - public ConstantDirective(string name, string typeName, string content) : base("constant") + public ConstantDirective(string name, QualifiedTypeName typeName, string content) : base("constant") { Name = name; TypeName = typeName; - ValueString = content; + Content = content; } public string Name { get; } - public string TypeName { get; } - public string ValueString { get; } + public QualifiedTypeName TypeName { get; } + public string Content { get; } + + public override string ToString() => $"{base.ToString()} {Name} = {TypeName}({Content})"; } public record ExportDirective : Directive diff --git a/libs/UIX.Asm/Models/Imports.cs b/libs/UIX.Asm/Models/Imports.cs index 9dfed20..ebb8526 100644 --- a/libs/UIX.Asm/Models/Imports.cs +++ b/libs/UIX.Asm/Models/Imports.cs @@ -16,20 +16,12 @@ public record NamespaceImport : ImportDirective public record TypeImport : ImportDirective { - public TypeImport(string namespacePrefix, string name) : base("type") + public TypeImport(QualifiedTypeName qualifiedName) : base("type") { - NamespacePrefix = namespacePrefix; - Name = name; + QualifiedName = qualifiedName; } - public string NamespacePrefix { get; init; } - public string Name { get; init; } + public QualifiedTypeName QualifiedName { get; init; } - public override string ToString() - { - if (NamespacePrefix is null) - return $"{base.ToString()} {Name}"; - else - return $"{base.ToString()} {NamespacePrefix}:{Name}"; - } + public override string ToString() => $"{base.ToString()} {QualifiedName}"; } diff --git a/libs/UIX.Asm/Models/Operands.cs b/libs/UIX.Asm/Models/Operands.cs index 7de78a5..99e80c5 100644 --- a/libs/UIX.Asm/Models/Operands.cs +++ b/libs/UIX.Asm/Models/Operands.cs @@ -1,12 +1,26 @@ namespace Microsoft.Iris.Asm.Models; +/// +/// The data type of a literal expression. +/// +/// +/// If bit 4 is set, the literal is meant to be an index. +/// public enum LiteralDataType : byte { - Byte, - UInt16, - UInt32, - Int32, - Bytes, + Byte = 0b0000, + UInt16 = 0b0001, + UInt32 = 0b0010, + Int32 = 0b0011, + + Bytes = 0b0100, + + TypeIndex = 0b1000, + PropertyIndex = 0b1001, + MethodIndex = 0b1010, + ConstantIndex = 0b1011, + SymbolRefIndex = 0b1100, + OpHostIndex = 0b1101, } public abstract record Operand(object Value, string Content = null) : AsmItem @@ -24,15 +38,22 @@ public record OperandLiteral : Operand public LiteralDataType DataType { get; init; } public override string ToString() => base.ToString(); + + public static LiteralDataType ReduceDataType(LiteralDataType dataType) + { + return IsIndex(dataType) ? LiteralDataType.UInt16 : dataType; + } + + public static bool IsIndex(LiteralDataType dataType) => ((byte)dataType & (1 << 3)) != 0; } public record OperandReference : Operand { - public OperandReference(string labelName) : base(labelName, labelName) + public OperandReference(string constantName) : base(constantName, constantName) { } - public string LabelName => Content; + public string ConstantName => Content; - public override string ToString() => base.ToString(); + public override string ToString() => $"@{base.ToString()}"; } diff --git a/libs/UIX.Asm/Models/SyntaxModels.cs b/libs/UIX.Asm/Models/SyntaxModels.cs index 545da4c..9c5879b 100644 --- a/libs/UIX.Asm/Models/SyntaxModels.cs +++ b/libs/UIX.Asm/Models/SyntaxModels.cs @@ -11,6 +11,17 @@ public record Label(string Name) : CodeItem public override string ToString() => $"{Name}:"; } +public record QualifiedTypeName(string NamespacePrefix, string TypeName) : AsmItem +{ + public override string ToString() + { + if (NamespacePrefix is null) + return TypeName; + else + return $"{NamespacePrefix}:{TypeName}"; + } +} + public record Program { public Program(IEnumerable body) @@ -44,6 +55,9 @@ public record Program sb.AppendJoin(lineEnding, Imports.Select(i => i.ToString())); sb.Append(lineEnding); sb.Append(lineEnding); + sb.AppendJoin(lineEnding, Directives.Where(d => d is not (IImportDirective or ExportDirective)).Select(i => i.ToString())); + sb.Append(lineEnding); + sb.Append(lineEnding); foreach (var bodyItem in Code) {