From d8d84f2c0893ba94eea392a0b766d7586946ef5b Mon Sep 17 00:00:00 2001 From: Jeff Walker Date: Sat, 31 Oct 2020 19:52:42 -0700 Subject: [PATCH] start --- .editorconfig | 42 + .gitattributes | 20 + .gitignore | 12 + .gitmodules | 3 + Azoth.Tools.Bootstrap.sln | 238 ++++ Azoth.Tools.Bootstrap.sln.DotSettings | 2 + Compiler.API/AssemblyBuilder.cs | 66 + Compiler.API/AzothCompiler.cs | 122 ++ Compiler.API/Compiler.API.csproj | 37 + Compiler.API/ILAssembler.cs | 292 ++++ Compiler.API/README.md | 3 + Compiler.AST/AbstractSyntaxTree.children.cs | 172 +++ Compiler.AST/AbstractSyntaxTree.tree | 112 ++ Compiler.AST/AbstractSyntaxTree.tree.cs | 475 +++++++ Compiler.AST/Compiler.AST.csproj | 48 + Compiler.AST/IAbstractSyntax.cs | 7 + Compiler.AST/IExpression.cs | 9 + Compiler.AST/Walkers/AbstractSyntaxWalker.cs | 57 + Compiler.CST/AccessModifier.cs | 9 + Compiler.CST/Compiler.CST.csproj | 56 + Compiler.CST/ExpressionSemanticsExtensions.cs | 37 + Compiler.CST/IArgumentSyntax.cs | 12 + Compiler.CST/IBlockExpressionSyntax.cs | 10 + Compiler.CST/IBodyStatementSyntax.cs | 9 + Compiler.CST/IClassDeclarationSyntax.cs | 9 + Compiler.CST/IDeclarationSyntax.cs | 15 + Compiler.CST/IEntityDeclarationSyntax.cs | 10 + Compiler.CST/IExpressionSyntax.cs | 14 + Compiler.CST/IHasContainingLexicalScope.cs | 9 + ...tImmutabilityConversionExpressionSyntax.cs | 9 + ...IImplicitNoneConversionExpressionSyntax.cs | 10 + ...plicitNumericConversionExpressionSyntax.cs | 9 + ...licitOptionalConversionExpressionSyntax.cs | 9 + Compiler.CST/IInvocableDeclarationSyntax.cs | 9 + Compiler.CST/IMoveExpressionSyntax.cs | 9 + Compiler.CST/IMutateExpressionSyntax.cs | 11 + Compiler.CST/INameExpressionSyntax.cs | 14 + Compiler.CST/INonMemberDeclarationSyntax.cs | 12 + .../INonMemberEntityDeclarationSyntax.cs | 9 + Compiler.CST/IShareExpressionSyntax.cs | 10 + Compiler.CST/ISyntax.cs | 7 + Compiler.CST/ITypeNameSyntax.cs | 11 + Compiler.CST/ITypeSyntax.cs | 10 + .../IUnqualifiedInvocationExpressionSyntax.cs | 11 + Compiler.CST/PackageSyntax.cs | 86 ++ Compiler.CST/README.md | 47 + Compiler.CST/SyntaxTree.children.cs | 215 +++ Compiler.CST/SyntaxTree.tree | 133 ++ Compiler.CST/SyntaxTree.tree.cs | 574 ++++++++ Compiler.CST/Walkers/SyntaxWalker.cs | 57 + Compiler.CodeGen/AssemblyInfo.cs | 3 + Compiler.CodeGen/ChildrenCodeTemplate.cs | 430 ++++++ .../ChildrenCodeTemplate.custom.cs | 27 + Compiler.CodeGen/ChildrenCodeTemplate.tt | 41 + Compiler.CodeGen/CodeBuilder.cs | 19 + Compiler.CodeGen/Compiler.CodeGen.csproj | 62 + Compiler.CodeGen/Config/Grammar.cs | 72 + Compiler.CodeGen/Config/GrammarProperty.cs | 14 + Compiler.CodeGen/Config/GrammarRule.cs | 23 + Compiler.CodeGen/Config/GrammarSymbol.cs | 53 + Compiler.CodeGen/Config/GrammarType.cs | 63 + Compiler.CodeGen/NamespaceComparer.cs | 32 + Compiler.CodeGen/Parser.cs | 151 ++ Compiler.CodeGen/Program.cs | 65 + Compiler.CodeGen/TreeCodeTemplate.cs | 404 ++++++ Compiler.CodeGen/TreeCodeTemplate.custom.cs | 117 ++ Compiler.CodeGen/TreeCodeTemplate.tt | 21 + Compiler.Core/CodeBuilder.cs | 81 ++ Compiler.Core/CodeFile.cs | 36 + Compiler.Core/CodePath.cs | 41 + Compiler.Core/CodeReference.cs | 19 + Compiler.Core/CodeText.cs | 67 + Compiler.Core/Compiler.Core.csproj | 33 + Compiler.Core/Diagnostic.cs | 48 + Compiler.Core/DiagnosticLevel.cs | 21 + Compiler.Core/DiagnosticPhase.cs | 9 + Compiler.Core/Diagnostics.cs | 58 + Compiler.Core/ExpressionSemantics.cs | 52 + .../FatalCompilationErrorException.cs | 15 + Compiler.Core/ICodeFileSource.cs | 13 + Compiler.Core/Operators/AccessOperator.cs | 8 + .../Operators/AccessOperatorExtensions.cs | 17 + Compiler.Core/Operators/AssignmentOperator.cs | 11 + .../Operators/AssignmentOperatorExtensions.cs | 20 + Compiler.Core/Operators/BinaryOperator.cs | 22 + .../Operators/BinaryOperatorExtensions.cs | 31 + Compiler.Core/Operators/UnaryOperator.cs | 9 + .../Operators/UnaryOperatorExtensions.cs | 22 + .../Operators/UnaryOperatorFixity.cs | 8 + Compiler.Core/ParseContext.cs | 14 + Compiler.Core/Promises/AcyclicPromise.cs | 111 ++ Compiler.Core/Promises/DerivedPromise.cs | 31 + Compiler.Core/Promises/IPromise.cs | 16 + Compiler.Core/Promises/Promise.cs | 68 + Compiler.Core/Promises/PromiseExtensions.cs | 12 + Compiler.Core/Promises/PromiseState.cs | 9 + Compiler.Core/README.md | 5 + Compiler.Core/TextLines.cs | 79 ++ Compiler.Core/TextPosition.cs | 69 + Compiler.Core/TextSpan.cs | 106 ++ Compiler.Emit.C/CCodeBuilder.cs | 113 ++ Compiler.Emit.C/CLangCompiler.cs | 50 + Compiler.Emit.C/Code.cs | 31 + Compiler.Emit.C/CodeEmitter.cs | 38 + Compiler.Emit.C/Compiler.Emit.C.csproj | 51 + Compiler.Emit.C/ConsoleCompilerOutput.cs | 18 + Compiler.Emit.C/ControlFlowEmitter.cs | 341 +++++ Compiler.Emit.C/DeclarationEmitter.cs | 179 +++ Compiler.Emit.C/ICompilerOutput.cs | 7 + Compiler.Emit.C/IConverter.cs | 7 + Compiler.Emit.C/IEmitter.cs | 7 + Compiler.Emit.C/InternalsVisibleTo.cs | 3 + Compiler.Emit.C/NameMangler.cs | 243 ++++ Compiler.Emit.C/PackageEmitter.cs | 98 ++ Compiler.Emit.C/ParameterConverter.cs | 32 + .../Properties/Resources.Designer.cs | 111 ++ Compiler.Emit.C/Properties/Resources.resx | 127 ++ Compiler.Emit.C/RuntimeLibrary.c | 122 ++ Compiler.Emit.C/RuntimeLibrary.h | 84 ++ Compiler.Emit.C/TypeConverter.cs | 46 + Compiler.Emit/Compiler.Emit.csproj | 31 + Compiler.Emit/Emitter.cs | 10 + Compiler.IntermediateLanguage/CFG/Block.cs | 25 + .../CFG/ControlFlowGraph.cs | 33 + .../CFG/Instructions/AssignmentInstruction.cs | 22 + .../Instructions/BooleanLogicInstruction.cs | 31 + .../CFG/Instructions/BooleanLogicOperator.cs | 23 + .../CFG/Instructions/CallInstruction.cs | 78 ++ .../Instructions/CallVirtualInstruction.cs | 47 + .../CFG/Instructions/CompareInstruction.cs | 35 + .../CompareInstructionOperator.cs | 31 + .../CFG/Instructions/ConvertInstruction.cs | 33 + .../Instructions/FieldAccessInstruction.cs | 30 + .../CFG/Instructions/Instruction.cs | 33 + .../CFG/Instructions/InstructionWithResult.cs | 31 + .../CFG/Instructions/LoadBoolInstruction.cs | 22 + .../Instructions/LoadIntegerInstruction.cs | 25 + .../CFG/Instructions/LoadNoneInstruction.cs | 22 + .../CFG/Instructions/LoadStringInstruction.cs | 25 + .../CFG/Instructions/NegateInstruction.cs | 30 + .../CFG/Instructions/NewObjectInstruction.cs | 41 + .../CFG/Instructions/NumericInstruction.cs | 36 + .../NumericInstructionOperator.cs | 27 + .../CFG/Instructions/SomeInstruction.cs | 28 + .../CFG/Operands/Operand.cs | 17 + .../CFG/Operands/VariableReference.cs | 35 + .../CFG/Places/FieldPlace.cs | 30 + .../CFG/Places/Place.cs | 23 + .../CFG/Places/VariablePlace.cs | 27 + Compiler.IntermediateLanguage/CFG/Scope.cs | 48 + .../CFG/StringConstant.cs | 14 + .../TerminatorInstructions/GotoInstruction.cs | 20 + .../TerminatorInstructions/IfInstruction.cs | 30 + .../ReturnValueInstruction.cs | 21 + .../ReturnVoidInstruction.cs | 18 + .../TerminatorInstruction.cs | 37 + Compiler.IntermediateLanguage/CFG/Variable.cs | 48 + .../CFG/VariableDeclaration.cs | 81 ++ Compiler.IntermediateLanguage/ClassIL.cs | 18 + .../Compiler.IntermediateLanguage.csproj | 36 + .../ConstructorIL.cs | 31 + .../DeclarationIL.cs | 29 + Compiler.IntermediateLanguage/FieldIL.cs | 18 + .../FieldInitializationIL.cs | 17 + .../FieldParameterIL.cs | 15 + Compiler.IntermediateLanguage/FunctionIL.cs | 34 + .../IInvocableDeclarationIL.cs | 17 + .../MethodDeclarationIL.cs | 32 + .../NamedParameterIL.cs | 15 + Compiler.IntermediateLanguage/PackageIL.cs | 43 + Compiler.IntermediateLanguage/ParameterIL.cs | 30 + Compiler.IntermediateLanguage/README.md | 72 + .../SelfParameterIL.cs | 15 + .../Compiler.LexicalScopes.csproj | 24 + Compiler.LexicalScopes/LexicalScope.cs | 24 + Compiler.LexicalScopes/NestedScope.cs | 60 + Compiler.LexicalScopes/PackagesScope.cs | 37 + Compiler.Lexing/Compiler.Lexing.csproj | 33 + Compiler.Lexing/ITokenIterator.cs | 17 + Compiler.Lexing/LexError.cs | 70 + Compiler.Lexing/Lexer.cs | 564 ++++++++ Compiler.Lexing/TokenIterator.cs | 32 + Compiler.Lexing/TokenIteratorExtensions.cs | 41 + Compiler.Names/Compiler.Names.csproj | 30 + Compiler.Names/Name.cs | 51 + Compiler.Names/NamespaceName.cs | 99 ++ Compiler.Names/SpecialTypeName.cs | 52 + Compiler.Names/TypeName.cs | 52 + Compiler.Parsing/CompilationUnitParser.cs | 48 + Compiler.Parsing/Compiler.Parsing.csproj | 35 + Compiler.Parsing/ModifierParser.cs | 33 + .../SyntaxNotImplementedChecker.cs | 66 + Compiler.Parsing/PackageParser.cs | 9 + Compiler.Parsing/ParseAs.cs | 11 + Compiler.Parsing/ParseError.cs | 84 ++ Compiler.Parsing/ParseFailedException.cs | 21 + Compiler.Parsing/Parser.Declarations.cs | 292 ++++ Compiler.Parsing/Parser.Expressions.cs | 501 +++++++ Compiler.Parsing/Parser.Lists.cs | 91 ++ Compiler.Parsing/Parser.Names.cs | 15 + Compiler.Parsing/Parser.Parameters.cs | 63 + Compiler.Parsing/Parser.Statements.cs | 129 ++ Compiler.Parsing/Parser.Types.cs | 143 ++ Compiler.Parsing/Parser.UsingDirectives.cs | 33 + Compiler.Parsing/Parser.cs | 38 + Compiler.Parsing/RecursiveDescentParser.cs | 23 + Compiler.Parsing/TokenIteratorExtensions.cs | 128 ++ .../Tree/AbstractMethodDeclarationSyntax.cs | 33 + Compiler.Parsing/Tree/ArgumentSyntax.cs | 21 + .../Tree/AssignmentExpressionSyntax.cs | 35 + .../AssociatedFunctionDeclarationSyntax.cs | 46 + .../Tree/BinaryOperatorExpressionSyntax.cs | 62 + .../Tree/BlockExpressionSyntax.cs | 32 + Compiler.Parsing/Tree/BodySyntax.cs | 30 + .../Tree/BoolLiteralExpressionSyntax.cs | 26 + .../Tree/BreakExpressionSyntax.cs | 29 + Compiler.Parsing/Tree/CapabilityTypeSyntax.cs | 27 + .../Tree/ClassDeclarationSyntax.cs | 82 ++ .../Tree/CompilationUnitSyntax.cs | 42 + .../Tree/ConcreteMethodDeclarationSyntax.cs | 38 + .../Tree/ConstructorDeclarationSyntax.cs | 46 + Compiler.Parsing/Tree/DeclarationSyntax.cs | 48 + .../Tree/ExpressionStatementSyntax.cs | 27 + Compiler.Parsing/Tree/ExpressionSyntax.cs | 68 + .../Tree/FieldDeclarationSyntax.cs | 49 + Compiler.Parsing/Tree/FieldParameterSyntax.cs | 31 + .../Tree/ForeachExpressionSyntax.cs | 49 + .../Tree/FunctionDeclarationSyntax.cs | 62 + Compiler.Parsing/Tree/IfExpressionSyntax.cs | 37 + .../Tree/IntegerLiteralExpressionSyntax.cs | 26 + .../Tree/InvocableDeclarationSyntax.cs | 33 + .../Tree/InvocationExpressionSyntax.cs | 32 + .../Tree/LiteralExpressionSyntax.cs | 16 + Compiler.Parsing/Tree/LoopExpressionSyntax.cs | 24 + .../Tree/MemberDeclarationSyntax.cs | 29 + .../Tree/MethodDeclarationSyntax.cs | 41 + Compiler.Parsing/Tree/MoveExpressionSyntax.cs | 38 + .../Tree/MutateExpressionSyntax.cs | 31 + Compiler.Parsing/Tree/NameExpressionSyntax.cs | 63 + Compiler.Parsing/Tree/NameSyntax.cs | 26 + Compiler.Parsing/Tree/NamedParameterSyntax.cs | 43 + .../Tree/NamespaceDeclarationSyntax.cs | 68 + .../Tree/NewObjectExpressionSyntax.cs | 47 + Compiler.Parsing/Tree/NextExpressionSyntax.cs | 21 + .../Tree/NoneLiteralExpressionSyntax.cs | 18 + Compiler.Parsing/Tree/OptionalTypeSyntax.cs | 21 + Compiler.Parsing/Tree/ParameterSyntax.cs | 25 + .../QualifiedInvocationExpressionSyntax.cs | 55 + .../Tree/QualifiedNameExpressionSyntax.cs | 38 + .../Tree/ResultStatementSyntax.cs | 27 + .../Tree/ReturnExpressionSyntax.cs | 34 + Compiler.Parsing/Tree/SelfExpressionSyntax.cs | 27 + Compiler.Parsing/Tree/SelfParameterSyntax.cs | 31 + Compiler.Parsing/Tree/StatementSyntax.cs | 13 + .../Tree/StringLiteralExpressionSyntax.cs | 26 + Compiler.Parsing/Tree/Syntax.cs | 20 + Compiler.Parsing/Tree/TypeNameSyntax.cs | 56 + Compiler.Parsing/Tree/TypeSyntax.cs | 46 + .../Tree/UnaryOperatorExpressionSyntax.cs | 40 + .../UnqualifiedInvocationExpressionSyntax.cs | 65 + .../Tree/UnsafeExpressionSyntax.cs | 26 + Compiler.Parsing/Tree/UsingDirectiveSyntax.cs | 23 + .../VariableDeclarationStatementSyntax.cs | 58 + .../Tree/WhileExpressionSyntax.cs | 31 + .../Compiler.Primitives.csproj | 30 + Compiler.Primitives/Intrinsic.cs | 55 + Compiler.Primitives/Primitive.cs | 67 + Compiler.Semantics/AST/ASTBuilder.cs | 535 +++++++ .../AST/Tree/AbstractMethodDeclaration.cs | 39 + Compiler.Semantics/AST/Tree/AbstractSyntax.cs | 19 + .../AST/Tree/AssignmentExpression.cs | 37 + .../AST/Tree/AssociatedFunctionDeclaration.cs | 38 + .../AST/Tree/BinaryOperatorExpression.cs | 62 + .../AST/Tree/BlockExpression.cs | 33 + Compiler.Semantics/AST/Tree/Body.cs | 23 + .../AST/Tree/BoolLiteralExpression.cs | 30 + .../AST/Tree/BorrowExpression.cs | 33 + .../AST/Tree/BreakExpression.cs | 31 + .../AST/Tree/ClassDeclaration.cs | 34 + .../AST/Tree/ConcreteMethodDeclaration.cs | 42 + .../AST/Tree/ConstructorDeclaration.cs | 38 + Compiler.Semantics/AST/Tree/Declaration.cs | 21 + Compiler.Semantics/AST/Tree/Expression.cs | 27 + .../AST/Tree/ExpressionStatement.cs | 22 + .../AST/Tree/FieldAccessExpression.cs | 37 + .../AST/Tree/FieldDeclaration.cs | 30 + Compiler.Semantics/AST/Tree/FieldParameter.cs | 28 + .../AST/Tree/ForeachExpression.cs | 42 + .../AST/Tree/FunctionDeclaration.cs | 35 + .../AST/Tree/FunctionInvocationExpression.cs | 35 + Compiler.Semantics/AST/Tree/IfExpression.cs | 36 + .../AST/Tree/ImplicitConversionExpression.cs | 24 + .../Tree/ImplicitImmutabilityConversion.cs | 28 + .../Tree/ImplicitNoneConversionExpression.cs | 28 + .../ImplicitNumericConversionExpression.cs | 27 + .../ImplicitOptionalConversionExpression.cs | 28 + .../AST/Tree/IntegerLiteralExpression.cs | 31 + .../AST/Tree/InvocableDeclaration.cs | 25 + .../AST/Tree/LiteralExpression.cs | 15 + Compiler.Semantics/AST/Tree/LoopExpression.cs | 29 + .../AST/Tree/MethodInvocationExpression.cs | 38 + Compiler.Semantics/AST/Tree/MoveExpression.cs | 33 + Compiler.Semantics/AST/Tree/NameExpression.cs | 32 + Compiler.Semantics/AST/Tree/NamedParameter.cs | 33 + .../AST/Tree/NewObjectExpression.cs | 35 + Compiler.Semantics/AST/Tree/NextExpression.cs | 20 + .../AST/Tree/NoneLiteralExpression.cs | 17 + Compiler.Semantics/AST/Tree/Package.cs | 50 + Compiler.Semantics/AST/Tree/Parameter.cs | 16 + .../AST/Tree/ResultStatement.cs | 21 + .../AST/Tree/ReturnExpression.cs | 29 + Compiler.Semantics/AST/Tree/SelfExpression.cs | 33 + Compiler.Semantics/AST/Tree/SelfParameter.cs | 24 + .../AST/Tree/ShareExpression.cs | 33 + Compiler.Semantics/AST/Tree/Statement.cs | 11 + .../AST/Tree/StringLiteralExpression.cs | 30 + .../AST/Tree/UnaryOperatorExpression.cs | 42 + .../AST/Tree/UnsafeExpression.cs | 29 + .../AST/Tree/VariableDeclarationStatement.cs | 37 + .../AST/Tree/WhileExpression.cs | 32 + Compiler.Semantics/Basic/BasicAnalyzer.cs | 107 ++ Compiler.Semantics/Basic/BasicBodyAnalyzer.cs | 1233 +++++++++++++++++ .../ImplicitConversionExpression.cs | 36 + .../ImplicitExpressionSyntax.cs | 46 + ...mplicitImmutabilityConversionExpression.cs | 25 + .../ImplicitOperations/ImplicitMoveSyntax.cs | 41 + .../ImplicitMutateExpressionSyntax.cs | 42 + .../ImplicitNoneConversionExpression.cs | 30 + .../ImplicitNumericConversionExpression.cs | 25 + .../ImplicitOptionalConversionExpression.cs | 27 + .../ImplicitShareExpressionSyntax.cs | 41 + .../FunctionInvocationExpressionSyntax.cs | 97 ++ Compiler.Semantics/Compiler.Semantics.csproj | 41 + .../DataFlow/BackwardDataFlowAnalyzer.cs | 74 + .../DataFlow/DataFlowAnalysis.cs | 56 + .../DataFlow/ForwardDataFlowAnalyzer.cs | 75 + .../DataFlow/IBackwardDataFlowAnalysis.cs | 13 + .../DataFlow/IBackwardDataFlowAnalyzer.cs | 18 + .../DataFlow/IForwardDataFlowAnalysis.cs | 13 + .../DataFlow/IForwardDataFlowAnalyzer.cs | 18 + Compiler.Semantics/DataFlow/VariableFlags.cs | 62 + .../DeclarationNumberAssigner.cs | 68 + Compiler.Semantics/Errors/BorrowError.cs | 63 + Compiler.Semantics/Errors/NameBindingError.cs | 84 ++ Compiler.Semantics/Errors/SemanticError.cs | 72 + Compiler.Semantics/Errors/TypeError.cs | 112 ++ Compiler.Semantics/ILGen/BlockBuilder.cs | 40 + .../ILGen/ControlFlowGraphBuilder.cs | 111 ++ .../ILGen/ControlFlowGraphFabrication.cs | 779 +++++++++++ .../ILGen/DeclarationBuilder.cs | 171 +++ Compiler.Semantics/ILGen/IlFactory.cs | 18 + .../LexicalScopes/LexicalScopesBuilder.cs | 103 ++ .../LexicalScopesBuilderWalker.cs | 190 +++ Compiler.Semantics/LexicalScopes/Namespace.cs | 30 + .../LexicalScopes/NonMemberSymbol.cs | 36 + .../Liveness/LivenessAnalysis.cs | 80 ++ .../Liveness/LivenessAnalyzer.cs | 24 + .../Liveness/OldLivenessAnalyzer.cs | 157 +++ Compiler.Semantics/README.md | 35 + Compiler.Semantics/SemanticAnalyzer.cs | 144 ++ .../Symbols/Entities/EntitySymbolBuilder.cs | 259 ++++ .../Namespaces/NamespaceSymbolBuilder.cs | 75 + Compiler.Semantics/Types/TypeResolver.cs | 80 ++ .../ExpressionSemanticsValidator.cs | 36 + .../Validation/SymbolValidator.cs | 108 ++ .../Validation/TypeFulfillmentValidator.cs | 44 + .../Validation/TypeKnownValidator.cs | 74 + .../BindingMutabilityAnalysis.cs | 85 ++ .../BindingMutabilityAnalyzer.cs | 24 + .../DefiniteAssignmentAnalysis.cs | 83 ++ .../DefiniteAssignmentAnalyzer.cs | 24 + .../Moves/UseOfMovedValueAnalysis.cs | 106 ++ .../Moves/UseOfMovedValueAnalyzer.cs | 24 + .../Variables/Shadowing/BindingScope.cs | 20 + .../Variables/Shadowing/EmptyBindingScope.cs | 25 + .../Variables/Shadowing/ShadowChecker.cs | 76 + .../Variables/Shadowing/VariableBinding.cs | 36 + .../Shadowing/VariableBindingScope.cs | 44 + Compiler.Symbols/BindingSymbol.cs | 26 + Compiler.Symbols/Compiler.Symbols.csproj | 32 + Compiler.Symbols/ConstructorSymbol.cs | 51 + Compiler.Symbols/FieldSymbol.cs | 43 + Compiler.Symbols/FunctionOrMethodSymbol.cs | 25 + Compiler.Symbols/FunctionSymbol.cs | 55 + Compiler.Symbols/InternalsVisibleTo.cs | 3 + Compiler.Symbols/InvocableSymbol.cs | 32 + Compiler.Symbols/MethodSymbol.cs | 50 + Compiler.Symbols/NamedBindingSymbol.cs | 24 + Compiler.Symbols/NamespaceOrPackageSymbol.cs | 23 + Compiler.Symbols/NamespaceSymbol.cs | 40 + Compiler.Symbols/ObjectTypeSymbol.cs | 50 + Compiler.Symbols/PackageSymbol.cs | 37 + Compiler.Symbols/PrimitiveTypeSymbol.cs | 42 + Compiler.Symbols/README.md | 3 + Compiler.Symbols/SelfParameterSymbol.cs | 40 + Compiler.Symbols/Symbol.cs | 62 + Compiler.Symbols/Trees/FixedSymbolTree.cs | 39 + Compiler.Symbols/Trees/ISymbolTree.cs | 18 + Compiler.Symbols/Trees/PrimitiveSymbolTree.cs | 35 + Compiler.Symbols/Trees/SymbolForest.cs | 43 + Compiler.Symbols/Trees/SymbolTreeBuilder.cs | 77 + Compiler.Symbols/TypeSymbol.cs | 24 + Compiler.Symbols/VariableSymbol.cs | 51 + Compiler.Tokens/BareIdentifierToken.cs | 20 + Compiler.Tokens/Compiler.Tokens.csproj | 58 + Compiler.Tokens/EscapedIdentifierToken.cs | 21 + Compiler.Tokens/FalseKeywordToken.cs | 7 + Compiler.Tokens/IAccessModifierToken.cs | 13 + Compiler.Tokens/IAccessOperatorToken.cs | 12 + Compiler.Tokens/IAssignmentToken.cs | 18 + Compiler.Tokens/IBareIdentifierToken.cs | 9 + Compiler.Tokens/IBinaryOperatorToken.cs | 44 + Compiler.Tokens/IBindingToken.cs | 12 + Compiler.Tokens/IBooleanLiteralToken.cs | 15 + Compiler.Tokens/ICapabilityToken.cs | 18 + Compiler.Tokens/IEscapedIdentifierToken.cs | 4 + Compiler.Tokens/IEssentialToken.cs | 21 + .../IIdentifierOrUnderscoreToken.cs | 19 + Compiler.Tokens/IIntegerLiteralToken.cs | 9 + Compiler.Tokens/IKeywordToken.cs | 14 + Compiler.Tokens/ILiteralToken.cs | 19 + Compiler.Tokens/IMemberNameToken.cs | 20 + Compiler.Tokens/IModiferToken.cs | 19 + Compiler.Tokens/IOperatorToken.cs | 66 + Compiler.Tokens/IPrimitiveTypeToken.cs | 46 + Compiler.Tokens/IPunctuationToken.cs | 26 + Compiler.Tokens/IStringLiteralToken.cs | 7 + Compiler.Tokens/IToken.cs | 18 + Compiler.Tokens/ITriviaToken.cs | 52 + Compiler.Tokens/ITypeKindKeywordToken.cs | 7 + Compiler.Tokens/IdentifierToken.cs | 21 + Compiler.Tokens/IntegerLiteralToken.cs | 30 + Compiler.Tokens/KeywordTokens.cs | 712 ++++++++++ Compiler.Tokens/KeywordTokens.tt | 137 ++ Compiler.Tokens/OperatorPrecedence.cs | 19 + Compiler.Tokens/README.md | 19 + Compiler.Tokens/StringLiteralToken.cs | 31 + Compiler.Tokens/Token.cs | 18 + Compiler.Tokens/TokenTypes.Keywords.cs | 81 ++ Compiler.Tokens/TokenTypes.ReservedWord.cs | 102 ++ Compiler.Tokens/Tokens.cs | 538 +++++++ Compiler.Tokens/Tokens.tt | 88 ++ Compiler.Tokens/TrueKeywordToken.cs | 7 + Compiler.Types/AnyType.cs | 54 + Compiler.Types/BoolConstantType.cs | 36 + Compiler.Types/BoolType.cs | 20 + Compiler.Types/Compiler.Types.csproj | 32 + Compiler.Types/DataType.cs | 127 ++ Compiler.Types/DataTypeExtensions.cs | 84 ++ Compiler.Types/EmptyType.cs | 48 + Compiler.Types/FixedSizeIntegerType.cs | 30 + Compiler.Types/IntegerConstantType.cs | 51 + Compiler.Types/IntegerType.cs | 17 + Compiler.Types/InternalsVisibleTo.cs | 3 + Compiler.Types/Metatype.cs | 54 + Compiler.Types/NeverType.cs | 26 + Compiler.Types/NumericType.cs | 16 + Compiler.Types/ObjectType.cs | 111 ++ Compiler.Types/OptionalType.cs | 55 + Compiler.Types/PointerSizedIntegerType.cs | 23 + Compiler.Types/README.md | 12 + Compiler.Types/ReferenceCapability.cs | 115 ++ .../ReferenceCapabilityExtensions.cs | 183 +++ Compiler.Types/ReferenceType.cs | 44 + Compiler.Types/ReferenceTypeExtensions.cs | 14 + Compiler.Types/SimpleType.cs | 42 + Compiler.Types/StringConstantType.cs | 4 + Compiler.Types/TypeSemantics.cs | 70 + Compiler.Types/UnknownType.cs | 47 + Compiler.Types/ValueType.cs | 12 + Compiler.Types/VoidType.cs | 26 + Framework/BitArrayExtensions.cs | 38 + Framework/CollectionDebugView.cs | 28 + Framework/DictionaryDebugView.cs | 23 + Framework/DictionaryExtensions.cs | 40 + Framework/EnumerableExtensions.cs | 142 ++ Framework/FixedDictionary.cs | 72 + Framework/FixedList.cs | 65 + Framework/FixedSet.cs | 60 + Framework/Framework.csproj | 29 + Framework/Generator.cs | 31 + Framework/QueueExtensions.cs | 13 + Framework/Requires.cs | 45 + Framework/Self.cs | 57 + Framework/SetExtensions.cs | 12 + Framework/StackExtensions.cs | 13 + Framework/StringExtensions.cs | 89 ++ Framework/TypeExtensions.cs | 34 + Framework/TypeSet.cs | 31 + Framework/UnreachableCodeException.cs | 24 + Framework/Void.cs | 6 + IL/IL.csproj | 15 + IL/Instruction.cs | 12 + IL/OpCode.cs | 7 + Interpreter/Interpreter.csproj | 15 + Interpreter/MemoryLayout/AzothObject.cs | 18 + Interpreter/MemoryLayout/AzothObjectSlot.cs | 13 + Interpreter/MemoryLayout/AzothReference.cs | 14 + Interpreter/MemoryLayout/AzothValue.cs | 15 + Interpreter/MemoryLayout/VTableRef.cs | 12 + LICENSE | 21 + README.md | 82 ++ Tests.Asserts/DiagnosticAsserts.cs | 23 + Tests.Asserts/GlobalSuppressions.cs | 13 + Tests.Asserts/README.md | 3 + Tests.Asserts/Tests.Asserts.csproj | 36 + Tests.Asserts/TypeAsserts.cs | 17 + .../ConformanceTests.cs | 282 ++++ .../GlobalSuppressions.cs | 8 + .../Helpers/CompilerOutputAdapter.cs | 21 + .../Helpers/RuntimeLibraryFixture.cs | 35 + .../Helpers/TestCase.cs | 58 + .../Tests.Compiler.Conformance.csproj | 54 + .../GlobalSuppressions.cs | 8 + Tests.Unit.Compiler.CodeGen/ParserTests.cs | 440 ++++++ .../Tests.Unit.Compiler.CodeGen.csproj | 33 + .../Tests.Unit.Compiler.Core.csproj | 37 + Tests.Unit.Compiler.Core/TextLinesTests.cs | 69 + .../GlobalSuppressions.cs | 8 + .../NameManglerTests.cs | 33 + Tests.Unit.Compiler.Emit.C/ResourcesTests.cs | 38 + .../Tests.Unit.Compiler.Emit.C.csproj | 48 + .../Fakes/FakeParseContext.cs | 13 + .../GlobalSuppressions.cs | 8 + .../Helpers/Arbitrary.cs | 326 +++++ .../Helpers/AssertExtensions.cs | 60 + .../Helpers/DebugFormatExtensions.cs | 21 + .../Helpers/LexResult.cs | 68 + .../Helpers/PsuedoToken.cs | 80 ++ Tests.Unit.Compiler.Lexing/LexerTests.cs | 338 +++++ .../Tests.Unit.Compiler.Lexing.csproj | 50 + .../GlobalSuppressions.cs | 8 + .../NamespaceNameTests.cs | 136 ++ .../Tests.Unit.Compiler.Names.csproj | 39 + .../ConstructorTests.cs | 21 + .../FunctionSymbolTests.cs | 66 + .../GlobalSuppressions.cs | 8 + .../MethodSymbolTests.cs | 64 + .../NamespaceSymbolTests.cs | 39 + .../PackageSymbolTests.cs | 17 + Tests.Unit.Compiler.Symbols/SymbolTests.cs | 32 + .../Tests.Unit.Compiler.Symbols.csproj | 41 + .../TypeSymbolTests.cs | 44 + .../VariableSymbolTests.cs | 49 + Tests.Unit.Compiler.Types/AnyTypeTests.cs | 126 ++ .../BoolConstantTypeTests.cs | 83 ++ Tests.Unit.Compiler.Types/BoolTypeTests.cs | 90 ++ .../DataTypeExtensionsTests.cs | 81 ++ Tests.Unit.Compiler.Types/DataTypeTests.cs | 18 + .../GlobalSuppressions.cs | 8 + .../IntegerConstantTypeTests.cs | 92 ++ Tests.Unit.Compiler.Types/NeverTypeTests.cs | 74 + Tests.Unit.Compiler.Types/ObjectTypeTests.cs | 37 + .../OptionalTypeTests.cs | 81 ++ .../ReferenceCapabilityAssignmentTestCase.cs | 23 + .../ReferenceCapabilityTests.cs | 49 + .../SizedIntegerTypeTests.cs | 44 + .../Tests.Unit.Compiler.Types.csproj | 39 + Tests.Unit.Compiler.Types/UnknownTypeTests.cs | 42 + .../UnsizedIntegerTypeTests.cs | 66 + Tests.Unit.Compiler.Types/VoidTypeTests.cs | 75 + .../EnumerableExtensionTests.cs | 30 + Tests.Unit.Framework/FixedListTests.cs | 19 + Tests.Unit.Framework/FixedSetTests.cs | 19 + Tests.Unit.Framework/GlobalSuppressions.cs | 8 + .../Tests.Unit.Framework.csproj | 50 + Tests.Unit.Framework/YieldTests.cs | 51 + Tests.Unit/CSharpNameResolutionTests.cs | 27 + Tests.Unit/DotNetFrameworkTests.cs | 125 ++ Tests.Unit/Fakes/FakeCodeFile.cs | 13 + Tests.Unit/Fakes/FakeCodeReference.cs | 23 + Tests.Unit/Helpers/GeneratorExtensions.cs | 24 + Tests.Unit/Helpers/SolutionDirectory.cs | 19 + Tests.Unit/README.md | 3 + Tests.Unit/SymbolTestFixture.cs | 163 +++ Tests.Unit/Tests.Unit.csproj | 56 + azlab/Build/Project.cs | 31 + azlab/Build/ProjectReference.cs | 16 + azlab/Build/ProjectSet.cs | 313 +++++ azlab/CommandOptionExtensions.cs | 24 + azlab/Config/ProjectConfig.cs | 59 + azlab/Config/ProjectConfigSet.cs | 40 + azlab/Config/ProjectDependencyConfig.cs | 13 + azlab/Config/ProjectTemplate.cs | 9 + azlab/Program.cs | 88 ++ azlab/azlab.csproj | 38 + azoth.language.tests | 1 + azothc/Program.cs | 31 + .../Properties/PublishProfiles/Win-x64.pubxml | 16 + azothc/azothc.csproj | 35 + docs/CoreFeatures.md | 35 + docs/Implemented.md | 1 + runtime/.gitignore | 1 + runtime/azlab-project.vson | 5 + runtime/src/Optional.az | 7 + runtime/src/String.az | 27 + 596 files changed, 33285 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Azoth.Tools.Bootstrap.sln create mode 100644 Azoth.Tools.Bootstrap.sln.DotSettings create mode 100644 Compiler.API/AssemblyBuilder.cs create mode 100644 Compiler.API/AzothCompiler.cs create mode 100644 Compiler.API/Compiler.API.csproj create mode 100644 Compiler.API/ILAssembler.cs create mode 100644 Compiler.API/README.md create mode 100644 Compiler.AST/AbstractSyntaxTree.children.cs create mode 100644 Compiler.AST/AbstractSyntaxTree.tree create mode 100644 Compiler.AST/AbstractSyntaxTree.tree.cs create mode 100644 Compiler.AST/Compiler.AST.csproj create mode 100644 Compiler.AST/IAbstractSyntax.cs create mode 100644 Compiler.AST/IExpression.cs create mode 100644 Compiler.AST/Walkers/AbstractSyntaxWalker.cs create mode 100644 Compiler.CST/AccessModifier.cs create mode 100644 Compiler.CST/Compiler.CST.csproj create mode 100644 Compiler.CST/ExpressionSemanticsExtensions.cs create mode 100644 Compiler.CST/IArgumentSyntax.cs create mode 100644 Compiler.CST/IBlockExpressionSyntax.cs create mode 100644 Compiler.CST/IBodyStatementSyntax.cs create mode 100644 Compiler.CST/IClassDeclarationSyntax.cs create mode 100644 Compiler.CST/IDeclarationSyntax.cs create mode 100644 Compiler.CST/IEntityDeclarationSyntax.cs create mode 100644 Compiler.CST/IExpressionSyntax.cs create mode 100644 Compiler.CST/IHasContainingLexicalScope.cs create mode 100644 Compiler.CST/IImplicitImmutabilityConversionExpressionSyntax.cs create mode 100644 Compiler.CST/IImplicitNoneConversionExpressionSyntax.cs create mode 100644 Compiler.CST/IImplicitNumericConversionExpressionSyntax.cs create mode 100644 Compiler.CST/IImplicitOptionalConversionExpressionSyntax.cs create mode 100644 Compiler.CST/IInvocableDeclarationSyntax.cs create mode 100644 Compiler.CST/IMoveExpressionSyntax.cs create mode 100644 Compiler.CST/IMutateExpressionSyntax.cs create mode 100644 Compiler.CST/INameExpressionSyntax.cs create mode 100644 Compiler.CST/INonMemberDeclarationSyntax.cs create mode 100644 Compiler.CST/INonMemberEntityDeclarationSyntax.cs create mode 100644 Compiler.CST/IShareExpressionSyntax.cs create mode 100644 Compiler.CST/ISyntax.cs create mode 100644 Compiler.CST/ITypeNameSyntax.cs create mode 100644 Compiler.CST/ITypeSyntax.cs create mode 100644 Compiler.CST/IUnqualifiedInvocationExpressionSyntax.cs create mode 100644 Compiler.CST/PackageSyntax.cs create mode 100644 Compiler.CST/README.md create mode 100644 Compiler.CST/SyntaxTree.children.cs create mode 100644 Compiler.CST/SyntaxTree.tree create mode 100644 Compiler.CST/SyntaxTree.tree.cs create mode 100644 Compiler.CST/Walkers/SyntaxWalker.cs create mode 100644 Compiler.CodeGen/AssemblyInfo.cs create mode 100644 Compiler.CodeGen/ChildrenCodeTemplate.cs create mode 100644 Compiler.CodeGen/ChildrenCodeTemplate.custom.cs create mode 100644 Compiler.CodeGen/ChildrenCodeTemplate.tt create mode 100644 Compiler.CodeGen/CodeBuilder.cs create mode 100644 Compiler.CodeGen/Compiler.CodeGen.csproj create mode 100644 Compiler.CodeGen/Config/Grammar.cs create mode 100644 Compiler.CodeGen/Config/GrammarProperty.cs create mode 100644 Compiler.CodeGen/Config/GrammarRule.cs create mode 100644 Compiler.CodeGen/Config/GrammarSymbol.cs create mode 100644 Compiler.CodeGen/Config/GrammarType.cs create mode 100644 Compiler.CodeGen/NamespaceComparer.cs create mode 100644 Compiler.CodeGen/Parser.cs create mode 100644 Compiler.CodeGen/Program.cs create mode 100644 Compiler.CodeGen/TreeCodeTemplate.cs create mode 100644 Compiler.CodeGen/TreeCodeTemplate.custom.cs create mode 100644 Compiler.CodeGen/TreeCodeTemplate.tt create mode 100644 Compiler.Core/CodeBuilder.cs create mode 100644 Compiler.Core/CodeFile.cs create mode 100644 Compiler.Core/CodePath.cs create mode 100644 Compiler.Core/CodeReference.cs create mode 100644 Compiler.Core/CodeText.cs create mode 100644 Compiler.Core/Compiler.Core.csproj create mode 100644 Compiler.Core/Diagnostic.cs create mode 100644 Compiler.Core/DiagnosticLevel.cs create mode 100644 Compiler.Core/DiagnosticPhase.cs create mode 100644 Compiler.Core/Diagnostics.cs create mode 100644 Compiler.Core/ExpressionSemantics.cs create mode 100644 Compiler.Core/FatalCompilationErrorException.cs create mode 100644 Compiler.Core/ICodeFileSource.cs create mode 100644 Compiler.Core/Operators/AccessOperator.cs create mode 100644 Compiler.Core/Operators/AccessOperatorExtensions.cs create mode 100644 Compiler.Core/Operators/AssignmentOperator.cs create mode 100644 Compiler.Core/Operators/AssignmentOperatorExtensions.cs create mode 100644 Compiler.Core/Operators/BinaryOperator.cs create mode 100644 Compiler.Core/Operators/BinaryOperatorExtensions.cs create mode 100644 Compiler.Core/Operators/UnaryOperator.cs create mode 100644 Compiler.Core/Operators/UnaryOperatorExtensions.cs create mode 100644 Compiler.Core/Operators/UnaryOperatorFixity.cs create mode 100644 Compiler.Core/ParseContext.cs create mode 100644 Compiler.Core/Promises/AcyclicPromise.cs create mode 100644 Compiler.Core/Promises/DerivedPromise.cs create mode 100644 Compiler.Core/Promises/IPromise.cs create mode 100644 Compiler.Core/Promises/Promise.cs create mode 100644 Compiler.Core/Promises/PromiseExtensions.cs create mode 100644 Compiler.Core/Promises/PromiseState.cs create mode 100644 Compiler.Core/README.md create mode 100644 Compiler.Core/TextLines.cs create mode 100644 Compiler.Core/TextPosition.cs create mode 100644 Compiler.Core/TextSpan.cs create mode 100644 Compiler.Emit.C/CCodeBuilder.cs create mode 100644 Compiler.Emit.C/CLangCompiler.cs create mode 100644 Compiler.Emit.C/Code.cs create mode 100644 Compiler.Emit.C/CodeEmitter.cs create mode 100644 Compiler.Emit.C/Compiler.Emit.C.csproj create mode 100644 Compiler.Emit.C/ConsoleCompilerOutput.cs create mode 100644 Compiler.Emit.C/ControlFlowEmitter.cs create mode 100644 Compiler.Emit.C/DeclarationEmitter.cs create mode 100644 Compiler.Emit.C/ICompilerOutput.cs create mode 100644 Compiler.Emit.C/IConverter.cs create mode 100644 Compiler.Emit.C/IEmitter.cs create mode 100644 Compiler.Emit.C/InternalsVisibleTo.cs create mode 100644 Compiler.Emit.C/NameMangler.cs create mode 100644 Compiler.Emit.C/PackageEmitter.cs create mode 100644 Compiler.Emit.C/ParameterConverter.cs create mode 100644 Compiler.Emit.C/Properties/Resources.Designer.cs create mode 100644 Compiler.Emit.C/Properties/Resources.resx create mode 100644 Compiler.Emit.C/RuntimeLibrary.c create mode 100644 Compiler.Emit.C/RuntimeLibrary.h create mode 100644 Compiler.Emit.C/TypeConverter.cs create mode 100644 Compiler.Emit/Compiler.Emit.csproj create mode 100644 Compiler.Emit/Emitter.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Block.cs create mode 100644 Compiler.IntermediateLanguage/CFG/ControlFlowGraph.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/AssignmentInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicOperator.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/CallInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/CallVirtualInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/CompareInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/CompareInstructionOperator.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/ConvertInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/FieldAccessInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/Instruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/InstructionWithResult.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/LoadBoolInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/LoadIntegerInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/LoadNoneInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/LoadStringInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/NegateInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/NewObjectInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/NumericInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/NumericInstructionOperator.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Instructions/SomeInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Operands/Operand.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Operands/VariableReference.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Places/FieldPlace.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Places/Place.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Places/VariablePlace.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Scope.cs create mode 100644 Compiler.IntermediateLanguage/CFG/StringConstant.cs create mode 100644 Compiler.IntermediateLanguage/CFG/TerminatorInstructions/GotoInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/TerminatorInstructions/IfInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnValueInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnVoidInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/TerminatorInstructions/TerminatorInstruction.cs create mode 100644 Compiler.IntermediateLanguage/CFG/Variable.cs create mode 100644 Compiler.IntermediateLanguage/CFG/VariableDeclaration.cs create mode 100644 Compiler.IntermediateLanguage/ClassIL.cs create mode 100644 Compiler.IntermediateLanguage/Compiler.IntermediateLanguage.csproj create mode 100644 Compiler.IntermediateLanguage/ConstructorIL.cs create mode 100644 Compiler.IntermediateLanguage/DeclarationIL.cs create mode 100644 Compiler.IntermediateLanguage/FieldIL.cs create mode 100644 Compiler.IntermediateLanguage/FieldInitializationIL.cs create mode 100644 Compiler.IntermediateLanguage/FieldParameterIL.cs create mode 100644 Compiler.IntermediateLanguage/FunctionIL.cs create mode 100644 Compiler.IntermediateLanguage/IInvocableDeclarationIL.cs create mode 100644 Compiler.IntermediateLanguage/MethodDeclarationIL.cs create mode 100644 Compiler.IntermediateLanguage/NamedParameterIL.cs create mode 100644 Compiler.IntermediateLanguage/PackageIL.cs create mode 100644 Compiler.IntermediateLanguage/ParameterIL.cs create mode 100644 Compiler.IntermediateLanguage/README.md create mode 100644 Compiler.IntermediateLanguage/SelfParameterIL.cs create mode 100644 Compiler.LexicalScopes/Compiler.LexicalScopes.csproj create mode 100644 Compiler.LexicalScopes/LexicalScope.cs create mode 100644 Compiler.LexicalScopes/NestedScope.cs create mode 100644 Compiler.LexicalScopes/PackagesScope.cs create mode 100644 Compiler.Lexing/Compiler.Lexing.csproj create mode 100644 Compiler.Lexing/ITokenIterator.cs create mode 100644 Compiler.Lexing/LexError.cs create mode 100644 Compiler.Lexing/Lexer.cs create mode 100644 Compiler.Lexing/TokenIterator.cs create mode 100644 Compiler.Lexing/TokenIteratorExtensions.cs create mode 100644 Compiler.Names/Compiler.Names.csproj create mode 100644 Compiler.Names/Name.cs create mode 100644 Compiler.Names/NamespaceName.cs create mode 100644 Compiler.Names/SpecialTypeName.cs create mode 100644 Compiler.Names/TypeName.cs create mode 100644 Compiler.Parsing/CompilationUnitParser.cs create mode 100644 Compiler.Parsing/Compiler.Parsing.csproj create mode 100644 Compiler.Parsing/ModifierParser.cs create mode 100644 Compiler.Parsing/NotImplemented/SyntaxNotImplementedChecker.cs create mode 100644 Compiler.Parsing/PackageParser.cs create mode 100644 Compiler.Parsing/ParseAs.cs create mode 100644 Compiler.Parsing/ParseError.cs create mode 100644 Compiler.Parsing/ParseFailedException.cs create mode 100644 Compiler.Parsing/Parser.Declarations.cs create mode 100644 Compiler.Parsing/Parser.Expressions.cs create mode 100644 Compiler.Parsing/Parser.Lists.cs create mode 100644 Compiler.Parsing/Parser.Names.cs create mode 100644 Compiler.Parsing/Parser.Parameters.cs create mode 100644 Compiler.Parsing/Parser.Statements.cs create mode 100644 Compiler.Parsing/Parser.Types.cs create mode 100644 Compiler.Parsing/Parser.UsingDirectives.cs create mode 100644 Compiler.Parsing/Parser.cs create mode 100644 Compiler.Parsing/RecursiveDescentParser.cs create mode 100644 Compiler.Parsing/TokenIteratorExtensions.cs create mode 100644 Compiler.Parsing/Tree/AbstractMethodDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/ArgumentSyntax.cs create mode 100644 Compiler.Parsing/Tree/AssignmentExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/AssociatedFunctionDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/BinaryOperatorExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/BlockExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/BodySyntax.cs create mode 100644 Compiler.Parsing/Tree/BoolLiteralExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/BreakExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/CapabilityTypeSyntax.cs create mode 100644 Compiler.Parsing/Tree/ClassDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/CompilationUnitSyntax.cs create mode 100644 Compiler.Parsing/Tree/ConcreteMethodDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/ConstructorDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/DeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/ExpressionStatementSyntax.cs create mode 100644 Compiler.Parsing/Tree/ExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/FieldDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/FieldParameterSyntax.cs create mode 100644 Compiler.Parsing/Tree/ForeachExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/FunctionDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/IfExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/IntegerLiteralExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/InvocableDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/InvocationExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/LiteralExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/LoopExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/MemberDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/MethodDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/MoveExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/MutateExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/NameExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/NameSyntax.cs create mode 100644 Compiler.Parsing/Tree/NamedParameterSyntax.cs create mode 100644 Compiler.Parsing/Tree/NamespaceDeclarationSyntax.cs create mode 100644 Compiler.Parsing/Tree/NewObjectExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/NextExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/NoneLiteralExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/OptionalTypeSyntax.cs create mode 100644 Compiler.Parsing/Tree/ParameterSyntax.cs create mode 100644 Compiler.Parsing/Tree/QualifiedInvocationExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/QualifiedNameExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/ResultStatementSyntax.cs create mode 100644 Compiler.Parsing/Tree/ReturnExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/SelfExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/SelfParameterSyntax.cs create mode 100644 Compiler.Parsing/Tree/StatementSyntax.cs create mode 100644 Compiler.Parsing/Tree/StringLiteralExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/Syntax.cs create mode 100644 Compiler.Parsing/Tree/TypeNameSyntax.cs create mode 100644 Compiler.Parsing/Tree/TypeSyntax.cs create mode 100644 Compiler.Parsing/Tree/UnaryOperatorExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/UnqualifiedInvocationExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/UnsafeExpressionSyntax.cs create mode 100644 Compiler.Parsing/Tree/UsingDirectiveSyntax.cs create mode 100644 Compiler.Parsing/Tree/VariableDeclarationStatementSyntax.cs create mode 100644 Compiler.Parsing/Tree/WhileExpressionSyntax.cs create mode 100644 Compiler.Primitives/Compiler.Primitives.csproj create mode 100644 Compiler.Primitives/Intrinsic.cs create mode 100644 Compiler.Primitives/Primitive.cs create mode 100644 Compiler.Semantics/AST/ASTBuilder.cs create mode 100644 Compiler.Semantics/AST/Tree/AbstractMethodDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/AbstractSyntax.cs create mode 100644 Compiler.Semantics/AST/Tree/AssignmentExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/AssociatedFunctionDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/BinaryOperatorExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/BlockExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/Body.cs create mode 100644 Compiler.Semantics/AST/Tree/BoolLiteralExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/BorrowExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/BreakExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/ClassDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/ConcreteMethodDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/ConstructorDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/Declaration.cs create mode 100644 Compiler.Semantics/AST/Tree/Expression.cs create mode 100644 Compiler.Semantics/AST/Tree/ExpressionStatement.cs create mode 100644 Compiler.Semantics/AST/Tree/FieldAccessExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/FieldDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/FieldParameter.cs create mode 100644 Compiler.Semantics/AST/Tree/ForeachExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/FunctionDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/FunctionInvocationExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/IfExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/ImplicitConversionExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/ImplicitImmutabilityConversion.cs create mode 100644 Compiler.Semantics/AST/Tree/ImplicitNoneConversionExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/ImplicitNumericConversionExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/ImplicitOptionalConversionExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/IntegerLiteralExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/InvocableDeclaration.cs create mode 100644 Compiler.Semantics/AST/Tree/LiteralExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/LoopExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/MethodInvocationExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/MoveExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/NameExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/NamedParameter.cs create mode 100644 Compiler.Semantics/AST/Tree/NewObjectExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/NextExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/NoneLiteralExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/Package.cs create mode 100644 Compiler.Semantics/AST/Tree/Parameter.cs create mode 100644 Compiler.Semantics/AST/Tree/ResultStatement.cs create mode 100644 Compiler.Semantics/AST/Tree/ReturnExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/SelfExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/SelfParameter.cs create mode 100644 Compiler.Semantics/AST/Tree/ShareExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/Statement.cs create mode 100644 Compiler.Semantics/AST/Tree/StringLiteralExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/UnaryOperatorExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/UnsafeExpression.cs create mode 100644 Compiler.Semantics/AST/Tree/VariableDeclarationStatement.cs create mode 100644 Compiler.Semantics/AST/Tree/WhileExpression.cs create mode 100644 Compiler.Semantics/Basic/BasicAnalyzer.cs create mode 100644 Compiler.Semantics/Basic/BasicBodyAnalyzer.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitConversionExpression.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitExpressionSyntax.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitImmutabilityConversionExpression.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitMoveSyntax.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitMutateExpressionSyntax.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitNoneConversionExpression.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitNumericConversionExpression.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitOptionalConversionExpression.cs create mode 100644 Compiler.Semantics/Basic/ImplicitOperations/ImplicitShareExpressionSyntax.cs create mode 100644 Compiler.Semantics/Basic/InferredSyntax/FunctionInvocationExpressionSyntax.cs create mode 100644 Compiler.Semantics/Compiler.Semantics.csproj create mode 100644 Compiler.Semantics/DataFlow/BackwardDataFlowAnalyzer.cs create mode 100644 Compiler.Semantics/DataFlow/DataFlowAnalysis.cs create mode 100644 Compiler.Semantics/DataFlow/ForwardDataFlowAnalyzer.cs create mode 100644 Compiler.Semantics/DataFlow/IBackwardDataFlowAnalysis.cs create mode 100644 Compiler.Semantics/DataFlow/IBackwardDataFlowAnalyzer.cs create mode 100644 Compiler.Semantics/DataFlow/IForwardDataFlowAnalysis.cs create mode 100644 Compiler.Semantics/DataFlow/IForwardDataFlowAnalyzer.cs create mode 100644 Compiler.Semantics/DataFlow/VariableFlags.cs create mode 100644 Compiler.Semantics/DeclarationNumbers/DeclarationNumberAssigner.cs create mode 100644 Compiler.Semantics/Errors/BorrowError.cs create mode 100644 Compiler.Semantics/Errors/NameBindingError.cs create mode 100644 Compiler.Semantics/Errors/SemanticError.cs create mode 100644 Compiler.Semantics/Errors/TypeError.cs create mode 100644 Compiler.Semantics/ILGen/BlockBuilder.cs create mode 100644 Compiler.Semantics/ILGen/ControlFlowGraphBuilder.cs create mode 100644 Compiler.Semantics/ILGen/ControlFlowGraphFabrication.cs create mode 100644 Compiler.Semantics/ILGen/DeclarationBuilder.cs create mode 100644 Compiler.Semantics/ILGen/IlFactory.cs create mode 100644 Compiler.Semantics/LexicalScopes/LexicalScopesBuilder.cs create mode 100644 Compiler.Semantics/LexicalScopes/LexicalScopesBuilderWalker.cs create mode 100644 Compiler.Semantics/LexicalScopes/Namespace.cs create mode 100644 Compiler.Semantics/LexicalScopes/NonMemberSymbol.cs create mode 100644 Compiler.Semantics/Liveness/LivenessAnalysis.cs create mode 100644 Compiler.Semantics/Liveness/LivenessAnalyzer.cs create mode 100644 Compiler.Semantics/Liveness/OldLivenessAnalyzer.cs create mode 100644 Compiler.Semantics/README.md create mode 100644 Compiler.Semantics/SemanticAnalyzer.cs create mode 100644 Compiler.Semantics/Symbols/Entities/EntitySymbolBuilder.cs create mode 100644 Compiler.Semantics/Symbols/Namespaces/NamespaceSymbolBuilder.cs create mode 100644 Compiler.Semantics/Types/TypeResolver.cs create mode 100644 Compiler.Semantics/Validation/ExpressionSemanticsValidator.cs create mode 100644 Compiler.Semantics/Validation/SymbolValidator.cs create mode 100644 Compiler.Semantics/Validation/TypeFulfillmentValidator.cs create mode 100644 Compiler.Semantics/Validation/TypeKnownValidator.cs create mode 100644 Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalysis.cs create mode 100644 Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalyzer.cs create mode 100644 Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalysis.cs create mode 100644 Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalyzer.cs create mode 100644 Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalysis.cs create mode 100644 Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalyzer.cs create mode 100644 Compiler.Semantics/Variables/Shadowing/BindingScope.cs create mode 100644 Compiler.Semantics/Variables/Shadowing/EmptyBindingScope.cs create mode 100644 Compiler.Semantics/Variables/Shadowing/ShadowChecker.cs create mode 100644 Compiler.Semantics/Variables/Shadowing/VariableBinding.cs create mode 100644 Compiler.Semantics/Variables/Shadowing/VariableBindingScope.cs create mode 100644 Compiler.Symbols/BindingSymbol.cs create mode 100644 Compiler.Symbols/Compiler.Symbols.csproj create mode 100644 Compiler.Symbols/ConstructorSymbol.cs create mode 100644 Compiler.Symbols/FieldSymbol.cs create mode 100644 Compiler.Symbols/FunctionOrMethodSymbol.cs create mode 100644 Compiler.Symbols/FunctionSymbol.cs create mode 100644 Compiler.Symbols/InternalsVisibleTo.cs create mode 100644 Compiler.Symbols/InvocableSymbol.cs create mode 100644 Compiler.Symbols/MethodSymbol.cs create mode 100644 Compiler.Symbols/NamedBindingSymbol.cs create mode 100644 Compiler.Symbols/NamespaceOrPackageSymbol.cs create mode 100644 Compiler.Symbols/NamespaceSymbol.cs create mode 100644 Compiler.Symbols/ObjectTypeSymbol.cs create mode 100644 Compiler.Symbols/PackageSymbol.cs create mode 100644 Compiler.Symbols/PrimitiveTypeSymbol.cs create mode 100644 Compiler.Symbols/README.md create mode 100644 Compiler.Symbols/SelfParameterSymbol.cs create mode 100644 Compiler.Symbols/Symbol.cs create mode 100644 Compiler.Symbols/Trees/FixedSymbolTree.cs create mode 100644 Compiler.Symbols/Trees/ISymbolTree.cs create mode 100644 Compiler.Symbols/Trees/PrimitiveSymbolTree.cs create mode 100644 Compiler.Symbols/Trees/SymbolForest.cs create mode 100644 Compiler.Symbols/Trees/SymbolTreeBuilder.cs create mode 100644 Compiler.Symbols/TypeSymbol.cs create mode 100644 Compiler.Symbols/VariableSymbol.cs create mode 100644 Compiler.Tokens/BareIdentifierToken.cs create mode 100644 Compiler.Tokens/Compiler.Tokens.csproj create mode 100644 Compiler.Tokens/EscapedIdentifierToken.cs create mode 100644 Compiler.Tokens/FalseKeywordToken.cs create mode 100644 Compiler.Tokens/IAccessModifierToken.cs create mode 100644 Compiler.Tokens/IAccessOperatorToken.cs create mode 100644 Compiler.Tokens/IAssignmentToken.cs create mode 100644 Compiler.Tokens/IBareIdentifierToken.cs create mode 100644 Compiler.Tokens/IBinaryOperatorToken.cs create mode 100644 Compiler.Tokens/IBindingToken.cs create mode 100644 Compiler.Tokens/IBooleanLiteralToken.cs create mode 100644 Compiler.Tokens/ICapabilityToken.cs create mode 100644 Compiler.Tokens/IEscapedIdentifierToken.cs create mode 100644 Compiler.Tokens/IEssentialToken.cs create mode 100644 Compiler.Tokens/IIdentifierOrUnderscoreToken.cs create mode 100644 Compiler.Tokens/IIntegerLiteralToken.cs create mode 100644 Compiler.Tokens/IKeywordToken.cs create mode 100644 Compiler.Tokens/ILiteralToken.cs create mode 100644 Compiler.Tokens/IMemberNameToken.cs create mode 100644 Compiler.Tokens/IModiferToken.cs create mode 100644 Compiler.Tokens/IOperatorToken.cs create mode 100644 Compiler.Tokens/IPrimitiveTypeToken.cs create mode 100644 Compiler.Tokens/IPunctuationToken.cs create mode 100644 Compiler.Tokens/IStringLiteralToken.cs create mode 100644 Compiler.Tokens/IToken.cs create mode 100644 Compiler.Tokens/ITriviaToken.cs create mode 100644 Compiler.Tokens/ITypeKindKeywordToken.cs create mode 100644 Compiler.Tokens/IdentifierToken.cs create mode 100644 Compiler.Tokens/IntegerLiteralToken.cs create mode 100644 Compiler.Tokens/KeywordTokens.cs create mode 100644 Compiler.Tokens/KeywordTokens.tt create mode 100644 Compiler.Tokens/OperatorPrecedence.cs create mode 100644 Compiler.Tokens/README.md create mode 100644 Compiler.Tokens/StringLiteralToken.cs create mode 100644 Compiler.Tokens/Token.cs create mode 100644 Compiler.Tokens/TokenTypes.Keywords.cs create mode 100644 Compiler.Tokens/TokenTypes.ReservedWord.cs create mode 100644 Compiler.Tokens/Tokens.cs create mode 100644 Compiler.Tokens/Tokens.tt create mode 100644 Compiler.Tokens/TrueKeywordToken.cs create mode 100644 Compiler.Types/AnyType.cs create mode 100644 Compiler.Types/BoolConstantType.cs create mode 100644 Compiler.Types/BoolType.cs create mode 100644 Compiler.Types/Compiler.Types.csproj create mode 100644 Compiler.Types/DataType.cs create mode 100644 Compiler.Types/DataTypeExtensions.cs create mode 100644 Compiler.Types/EmptyType.cs create mode 100644 Compiler.Types/FixedSizeIntegerType.cs create mode 100644 Compiler.Types/IntegerConstantType.cs create mode 100644 Compiler.Types/IntegerType.cs create mode 100644 Compiler.Types/InternalsVisibleTo.cs create mode 100644 Compiler.Types/Metatype.cs create mode 100644 Compiler.Types/NeverType.cs create mode 100644 Compiler.Types/NumericType.cs create mode 100644 Compiler.Types/ObjectType.cs create mode 100644 Compiler.Types/OptionalType.cs create mode 100644 Compiler.Types/PointerSizedIntegerType.cs create mode 100644 Compiler.Types/README.md create mode 100644 Compiler.Types/ReferenceCapability.cs create mode 100644 Compiler.Types/ReferenceCapabilityExtensions.cs create mode 100644 Compiler.Types/ReferenceType.cs create mode 100644 Compiler.Types/ReferenceTypeExtensions.cs create mode 100644 Compiler.Types/SimpleType.cs create mode 100644 Compiler.Types/StringConstantType.cs create mode 100644 Compiler.Types/TypeSemantics.cs create mode 100644 Compiler.Types/UnknownType.cs create mode 100644 Compiler.Types/ValueType.cs create mode 100644 Compiler.Types/VoidType.cs create mode 100644 Framework/BitArrayExtensions.cs create mode 100644 Framework/CollectionDebugView.cs create mode 100644 Framework/DictionaryDebugView.cs create mode 100644 Framework/DictionaryExtensions.cs create mode 100644 Framework/EnumerableExtensions.cs create mode 100644 Framework/FixedDictionary.cs create mode 100644 Framework/FixedList.cs create mode 100644 Framework/FixedSet.cs create mode 100644 Framework/Framework.csproj create mode 100644 Framework/Generator.cs create mode 100644 Framework/QueueExtensions.cs create mode 100644 Framework/Requires.cs create mode 100644 Framework/Self.cs create mode 100644 Framework/SetExtensions.cs create mode 100644 Framework/StackExtensions.cs create mode 100644 Framework/StringExtensions.cs create mode 100644 Framework/TypeExtensions.cs create mode 100644 Framework/TypeSet.cs create mode 100644 Framework/UnreachableCodeException.cs create mode 100644 Framework/Void.cs create mode 100644 IL/IL.csproj create mode 100644 IL/Instruction.cs create mode 100644 IL/OpCode.cs create mode 100644 Interpreter/Interpreter.csproj create mode 100644 Interpreter/MemoryLayout/AzothObject.cs create mode 100644 Interpreter/MemoryLayout/AzothObjectSlot.cs create mode 100644 Interpreter/MemoryLayout/AzothReference.cs create mode 100644 Interpreter/MemoryLayout/AzothValue.cs create mode 100644 Interpreter/MemoryLayout/VTableRef.cs create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Tests.Asserts/DiagnosticAsserts.cs create mode 100644 Tests.Asserts/GlobalSuppressions.cs create mode 100644 Tests.Asserts/README.md create mode 100644 Tests.Asserts/Tests.Asserts.csproj create mode 100644 Tests.Asserts/TypeAsserts.cs create mode 100644 Tests.Compiler.Conformance/ConformanceTests.cs create mode 100644 Tests.Compiler.Conformance/GlobalSuppressions.cs create mode 100644 Tests.Compiler.Conformance/Helpers/CompilerOutputAdapter.cs create mode 100644 Tests.Compiler.Conformance/Helpers/RuntimeLibraryFixture.cs create mode 100644 Tests.Compiler.Conformance/Helpers/TestCase.cs create mode 100644 Tests.Compiler.Conformance/Tests.Compiler.Conformance.csproj create mode 100644 Tests.Unit.Compiler.CodeGen/GlobalSuppressions.cs create mode 100644 Tests.Unit.Compiler.CodeGen/ParserTests.cs create mode 100644 Tests.Unit.Compiler.CodeGen/Tests.Unit.Compiler.CodeGen.csproj create mode 100644 Tests.Unit.Compiler.Core/Tests.Unit.Compiler.Core.csproj create mode 100644 Tests.Unit.Compiler.Core/TextLinesTests.cs create mode 100644 Tests.Unit.Compiler.Emit.C/GlobalSuppressions.cs create mode 100644 Tests.Unit.Compiler.Emit.C/NameManglerTests.cs create mode 100644 Tests.Unit.Compiler.Emit.C/ResourcesTests.cs create mode 100644 Tests.Unit.Compiler.Emit.C/Tests.Unit.Compiler.Emit.C.csproj create mode 100644 Tests.Unit.Compiler.Lexing/Fakes/FakeParseContext.cs create mode 100644 Tests.Unit.Compiler.Lexing/GlobalSuppressions.cs create mode 100644 Tests.Unit.Compiler.Lexing/Helpers/Arbitrary.cs create mode 100644 Tests.Unit.Compiler.Lexing/Helpers/AssertExtensions.cs create mode 100644 Tests.Unit.Compiler.Lexing/Helpers/DebugFormatExtensions.cs create mode 100644 Tests.Unit.Compiler.Lexing/Helpers/LexResult.cs create mode 100644 Tests.Unit.Compiler.Lexing/Helpers/PsuedoToken.cs create mode 100644 Tests.Unit.Compiler.Lexing/LexerTests.cs create mode 100644 Tests.Unit.Compiler.Lexing/Tests.Unit.Compiler.Lexing.csproj create mode 100644 Tests.Unit.Compiler.Names/GlobalSuppressions.cs create mode 100644 Tests.Unit.Compiler.Names/NamespaceNameTests.cs create mode 100644 Tests.Unit.Compiler.Names/Tests.Unit.Compiler.Names.csproj create mode 100644 Tests.Unit.Compiler.Symbols/ConstructorTests.cs create mode 100644 Tests.Unit.Compiler.Symbols/FunctionSymbolTests.cs create mode 100644 Tests.Unit.Compiler.Symbols/GlobalSuppressions.cs create mode 100644 Tests.Unit.Compiler.Symbols/MethodSymbolTests.cs create mode 100644 Tests.Unit.Compiler.Symbols/NamespaceSymbolTests.cs create mode 100644 Tests.Unit.Compiler.Symbols/PackageSymbolTests.cs create mode 100644 Tests.Unit.Compiler.Symbols/SymbolTests.cs create mode 100644 Tests.Unit.Compiler.Symbols/Tests.Unit.Compiler.Symbols.csproj create mode 100644 Tests.Unit.Compiler.Symbols/TypeSymbolTests.cs create mode 100644 Tests.Unit.Compiler.Symbols/VariableSymbolTests.cs create mode 100644 Tests.Unit.Compiler.Types/AnyTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/BoolConstantTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/BoolTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/DataTypeExtensionsTests.cs create mode 100644 Tests.Unit.Compiler.Types/DataTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/GlobalSuppressions.cs create mode 100644 Tests.Unit.Compiler.Types/IntegerConstantTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/NeverTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/ObjectTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/OptionalTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/ReferenceCapabilityAssignmentTestCase.cs create mode 100644 Tests.Unit.Compiler.Types/ReferenceCapabilityTests.cs create mode 100644 Tests.Unit.Compiler.Types/SizedIntegerTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/Tests.Unit.Compiler.Types.csproj create mode 100644 Tests.Unit.Compiler.Types/UnknownTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/UnsizedIntegerTypeTests.cs create mode 100644 Tests.Unit.Compiler.Types/VoidTypeTests.cs create mode 100644 Tests.Unit.Framework/EnumerableExtensionTests.cs create mode 100644 Tests.Unit.Framework/FixedListTests.cs create mode 100644 Tests.Unit.Framework/FixedSetTests.cs create mode 100644 Tests.Unit.Framework/GlobalSuppressions.cs create mode 100644 Tests.Unit.Framework/Tests.Unit.Framework.csproj create mode 100644 Tests.Unit.Framework/YieldTests.cs create mode 100644 Tests.Unit/CSharpNameResolutionTests.cs create mode 100644 Tests.Unit/DotNetFrameworkTests.cs create mode 100644 Tests.Unit/Fakes/FakeCodeFile.cs create mode 100644 Tests.Unit/Fakes/FakeCodeReference.cs create mode 100644 Tests.Unit/Helpers/GeneratorExtensions.cs create mode 100644 Tests.Unit/Helpers/SolutionDirectory.cs create mode 100644 Tests.Unit/README.md create mode 100644 Tests.Unit/SymbolTestFixture.cs create mode 100644 Tests.Unit/Tests.Unit.csproj create mode 100644 azlab/Build/Project.cs create mode 100644 azlab/Build/ProjectReference.cs create mode 100644 azlab/Build/ProjectSet.cs create mode 100644 azlab/CommandOptionExtensions.cs create mode 100644 azlab/Config/ProjectConfig.cs create mode 100644 azlab/Config/ProjectConfigSet.cs create mode 100644 azlab/Config/ProjectDependencyConfig.cs create mode 100644 azlab/Config/ProjectTemplate.cs create mode 100644 azlab/Program.cs create mode 100644 azlab/azlab.csproj create mode 160000 azoth.language.tests create mode 100644 azothc/Program.cs create mode 100644 azothc/Properties/PublishProfiles/Win-x64.pubxml create mode 100644 azothc/azothc.csproj create mode 100644 docs/CoreFeatures.md create mode 100644 docs/Implemented.md create mode 100644 runtime/.gitignore create mode 100644 runtime/azlab-project.vson create mode 100644 runtime/src/Optional.az create mode 100644 runtime/src/String.az diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7a4712fb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,42 @@ +# http://editorconfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{c,h,rsrc,ail}] +end_of_line = lf + +[*.{xml,csproj}] +indent_size = 2 + +[*.{bat,cs,sln,csproj}] +end_of_line = crlf + +# .NET formatting settings: +[*.cs] +dotnet_sort_system_directives_first = true + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = silent + +# CA1715: Identifiers should have correct prefix +dotnet_diagnostic.CA1715.severity = silent + +# CA1710: Identifiers should have correct suffix +dotnet_diagnostic.CA1710.severity = silent + +# CA1032: Implement standard exception constructors +dotnet_diagnostic.CA1032.severity = silent + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = silent + +# CA1716: Identifiers should not match keywords +dotnet_diagnostic.CA1716.severity = silent diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4ec27259 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,20 @@ +* text=auto + +*.vson text eol=lf encoding=UTF-8 +*.json text eol=lf encoding=UTF-8 +*.ad text encoding=UTF-8 +*.md text +*.bat text eol=crlf +*.c text eol=lf +*.h text eol=lf +*.rsrc text eol=lf +*.sh text eol=lf +*.yml text eol=lf + +*.cs text eol=crlf +*.sln text eol=crlf +*.csproj text eol=crlf + +# These files must be UTF-8 w/ BOM to work, so treat them as binary +RuntimeLibrary.c binary +RuntimeLibrary.h binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ce14da81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# VS Files +.vs/ +bin/ +obj/ +*.user +launchSettings.json + +# VS Code Files +.vscode/ + +# OSX Files +.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..fe84f693 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "azoth.language.tests"] + path = azoth.language.tests + url = https://github.com/azoth-lang/azoth.language.tests.git diff --git a/Azoth.Tools.Bootstrap.sln b/Azoth.Tools.Bootstrap.sln new file mode 100644 index 00000000..2d6f503b --- /dev/null +++ b/Azoth.Tools.Bootstrap.sln @@ -0,0 +1,238 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29001.49 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "azothc", "azothc\azothc.csproj", "{E51046BD-E182-43CB-81C5-261064D784BD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "azlab", "azlab\azlab.csproj", "{3EC50ACC-86FF-4975-A50C-C5F43F5C2D18}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Core", "Compiler.Core\Compiler.Core.csproj", "{28A30670-D9C8-4ACF-9EA6-B3A73254AC6B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Framework", "Framework\Framework.csproj", "{8C18643D-60EE-4BBD-91E3-08122ADD3C59}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DCF0C530-CBBD-4D5F-BA54-423091616D72}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + .gitmodules = .gitmodules + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.API", "Compiler.API\Compiler.API.csproj", "{51A803B9-C590-4AEA-B74F-E40038CD029C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Emit", "Compiler.Emit\Compiler.Emit.csproj", "{94598655-2653-4BEA-B973-09A11932C86A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{8D74A93C-CA04-4283-9FBD-31C8D9B08F34}" + ProjectSection(SolutionItems) = preProject + docs\CoreFeatures.md = docs\CoreFeatures.md + docs\Implemented.md = docs\Implemented.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Semantics", "Compiler.Semantics\Compiler.Semantics.csproj", "{A8ACDE04-DE4D-4BD5-A839-476B7D2CFE24}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Emit.C", "Compiler.Emit.C\Compiler.Emit.C.csproj", "{C89E990F-3501-42E0-95A8-648C55D1CAA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit", "Tests.Unit\Tests.Unit.csproj", "{F93B7E34-0B72-4AC7-84DA-E722936DF90B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Compiler.Conformance", "Tests.Compiler.Conformance\Tests.Compiler.Conformance.csproj", "{6B3ABEDA-B691-4D5F-979E-870EFCCBEB3E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Compiler.Core", "Tests.Unit.Compiler.Core\Tests.Unit.Compiler.Core.csproj", "{6F295FAC-30EA-4297-9482-B0BF04261E34}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Compiler.Emit.C", "Tests.Unit.Compiler.Emit.C\Tests.Unit.Compiler.Emit.C.csproj", "{33139B8B-BB89-4FE1-9671-6D1ABD6F8F42}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Framework", "Tests.Unit.Framework\Tests.Unit.Framework.csproj", "{0C355843-45E6-4531-8044-5E4423DAAE53}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Lexing", "Compiler.Lexing\Compiler.Lexing.csproj", "{794D1EE6-A091-4B32-8A89-08C5A095DADA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Parsing", "Compiler.Parsing\Compiler.Parsing.csproj", "{9E0558AC-5A8A-402D-B0B5-C8CB8833D0C7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Tokens", "Compiler.Tokens\Compiler.Tokens.csproj", "{2F5A2A61-05AB-4E97-A6E9-CC79E7A5DE2D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Compiler.Lexing", "Tests.Unit.Compiler.Lexing\Tests.Unit.Compiler.Lexing.csproj", "{D10C503D-AD29-4845-827A-054D8FA6F384}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Names", "Compiler.Names\Compiler.Names.csproj", "{CDFD4AFB-FD42-483C-AA80-876E1CD4E00B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.CST", "Compiler.CST\Compiler.CST.csproj", "{9BB1E4FE-D7B8-4864-9C50-6F960F2543E2}" + ProjectSection(ProjectDependencies) = postProject + {4D709360-03ED-4A76-8EBB-18B89462A9DB} = {4D709360-03ED-4A76-8EBB-18B89462A9DB} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Primitives", "Compiler.Primitives\Compiler.Primitives.csproj", "{847178B8-0137-48B3-8B05-B3EFDA1C90D8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.IntermediateLanguage", "Compiler.IntermediateLanguage\Compiler.IntermediateLanguage.csproj", "{C8CA4E6C-305E-406A-8BED-2B8605F1867C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Asserts", "Tests.Asserts\Tests.Asserts.csproj", "{718FA0E8-8FD8-4913-89FE-C2D9E9D58AAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.CodeGen", "Compiler.CodeGen\Compiler.CodeGen.csproj", "{4D709360-03ED-4A76-8EBB-18B89462A9DB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Types", "Compiler.Types\Compiler.Types.csproj", "{FAEB3A90-FD3D-416D-8443-31E245033560}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Compiler.Types", "Tests.Unit.Compiler.Types\Tests.Unit.Compiler.Types.csproj", "{342F648A-6AEF-4755-B98C-62C6E5BBCFF2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.Symbols", "Compiler.Symbols\Compiler.Symbols.csproj", "{016047CF-74AA-4611-B995-D47EF2454CAD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Compiler.Symbols", "Tests.Unit.Compiler.Symbols\Tests.Unit.Compiler.Symbols.csproj", "{A65EEF14-F7D5-4DBD-BCAC-9AF67B275EB2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Compiler.Names", "Tests.Unit.Compiler.Names\Tests.Unit.Compiler.Names.csproj", "{C7752F7D-AF11-47A2-84C3-95D35166E02F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Unit.Compiler.CodeGen", "Tests.Unit.Compiler.CodeGen\Tests.Unit.Compiler.CodeGen.csproj", "{C57EB532-BEE5-4238-BF35-4B77F77E5FD5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.AST", "Compiler.AST\Compiler.AST.csproj", "{411991A2-008D-4661-8307-6E07FC813704}" + ProjectSection(ProjectDependencies) = postProject + {4D709360-03ED-4A76-8EBB-18B89462A9DB} = {4D709360-03ED-4A76-8EBB-18B89462A9DB} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compiler.LexicalScopes", "Compiler.LexicalScopes\Compiler.LexicalScopes.csproj", "{C78245F3-71A5-4628-8796-79B8FC3C7690}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Interpreter", "Interpreter\Interpreter.csproj", "{F7F56D0F-1892-46C5-BCAD-E69BA600EED1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IL", "IL\IL.csproj", "{2014D6A8-26C6-4C6F-B0C7-6E01066484F2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E51046BD-E182-43CB-81C5-261064D784BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E51046BD-E182-43CB-81C5-261064D784BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E51046BD-E182-43CB-81C5-261064D784BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E51046BD-E182-43CB-81C5-261064D784BD}.Release|Any CPU.Build.0 = Release|Any CPU + {3EC50ACC-86FF-4975-A50C-C5F43F5C2D18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EC50ACC-86FF-4975-A50C-C5F43F5C2D18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EC50ACC-86FF-4975-A50C-C5F43F5C2D18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EC50ACC-86FF-4975-A50C-C5F43F5C2D18}.Release|Any CPU.Build.0 = Release|Any CPU + {28A30670-D9C8-4ACF-9EA6-B3A73254AC6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28A30670-D9C8-4ACF-9EA6-B3A73254AC6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28A30670-D9C8-4ACF-9EA6-B3A73254AC6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28A30670-D9C8-4ACF-9EA6-B3A73254AC6B}.Release|Any CPU.Build.0 = Release|Any CPU + {8C18643D-60EE-4BBD-91E3-08122ADD3C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C18643D-60EE-4BBD-91E3-08122ADD3C59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C18643D-60EE-4BBD-91E3-08122ADD3C59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C18643D-60EE-4BBD-91E3-08122ADD3C59}.Release|Any CPU.Build.0 = Release|Any CPU + {51A803B9-C590-4AEA-B74F-E40038CD029C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51A803B9-C590-4AEA-B74F-E40038CD029C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51A803B9-C590-4AEA-B74F-E40038CD029C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51A803B9-C590-4AEA-B74F-E40038CD029C}.Release|Any CPU.Build.0 = Release|Any CPU + {94598655-2653-4BEA-B973-09A11932C86A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94598655-2653-4BEA-B973-09A11932C86A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94598655-2653-4BEA-B973-09A11932C86A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94598655-2653-4BEA-B973-09A11932C86A}.Release|Any CPU.Build.0 = Release|Any CPU + {A8ACDE04-DE4D-4BD5-A839-476B7D2CFE24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8ACDE04-DE4D-4BD5-A839-476B7D2CFE24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8ACDE04-DE4D-4BD5-A839-476B7D2CFE24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8ACDE04-DE4D-4BD5-A839-476B7D2CFE24}.Release|Any CPU.Build.0 = Release|Any CPU + {C89E990F-3501-42E0-95A8-648C55D1CAA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C89E990F-3501-42E0-95A8-648C55D1CAA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C89E990F-3501-42E0-95A8-648C55D1CAA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C89E990F-3501-42E0-95A8-648C55D1CAA0}.Release|Any CPU.Build.0 = Release|Any CPU + {F93B7E34-0B72-4AC7-84DA-E722936DF90B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F93B7E34-0B72-4AC7-84DA-E722936DF90B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F93B7E34-0B72-4AC7-84DA-E722936DF90B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F93B7E34-0B72-4AC7-84DA-E722936DF90B}.Release|Any CPU.Build.0 = Release|Any CPU + {6B3ABEDA-B691-4D5F-979E-870EFCCBEB3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B3ABEDA-B691-4D5F-979E-870EFCCBEB3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B3ABEDA-B691-4D5F-979E-870EFCCBEB3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B3ABEDA-B691-4D5F-979E-870EFCCBEB3E}.Release|Any CPU.Build.0 = Release|Any CPU + {6F295FAC-30EA-4297-9482-B0BF04261E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F295FAC-30EA-4297-9482-B0BF04261E34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F295FAC-30EA-4297-9482-B0BF04261E34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F295FAC-30EA-4297-9482-B0BF04261E34}.Release|Any CPU.Build.0 = Release|Any CPU + {33139B8B-BB89-4FE1-9671-6D1ABD6F8F42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33139B8B-BB89-4FE1-9671-6D1ABD6F8F42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33139B8B-BB89-4FE1-9671-6D1ABD6F8F42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33139B8B-BB89-4FE1-9671-6D1ABD6F8F42}.Release|Any CPU.Build.0 = Release|Any CPU + {0C355843-45E6-4531-8044-5E4423DAAE53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C355843-45E6-4531-8044-5E4423DAAE53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C355843-45E6-4531-8044-5E4423DAAE53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C355843-45E6-4531-8044-5E4423DAAE53}.Release|Any CPU.Build.0 = Release|Any CPU + {794D1EE6-A091-4B32-8A89-08C5A095DADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {794D1EE6-A091-4B32-8A89-08C5A095DADA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {794D1EE6-A091-4B32-8A89-08C5A095DADA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {794D1EE6-A091-4B32-8A89-08C5A095DADA}.Release|Any CPU.Build.0 = Release|Any CPU + {9E0558AC-5A8A-402D-B0B5-C8CB8833D0C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E0558AC-5A8A-402D-B0B5-C8CB8833D0C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E0558AC-5A8A-402D-B0B5-C8CB8833D0C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E0558AC-5A8A-402D-B0B5-C8CB8833D0C7}.Release|Any CPU.Build.0 = Release|Any CPU + {2F5A2A61-05AB-4E97-A6E9-CC79E7A5DE2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F5A2A61-05AB-4E97-A6E9-CC79E7A5DE2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F5A2A61-05AB-4E97-A6E9-CC79E7A5DE2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F5A2A61-05AB-4E97-A6E9-CC79E7A5DE2D}.Release|Any CPU.Build.0 = Release|Any CPU + {D10C503D-AD29-4845-827A-054D8FA6F384}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D10C503D-AD29-4845-827A-054D8FA6F384}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D10C503D-AD29-4845-827A-054D8FA6F384}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D10C503D-AD29-4845-827A-054D8FA6F384}.Release|Any CPU.Build.0 = Release|Any CPU + {CDFD4AFB-FD42-483C-AA80-876E1CD4E00B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDFD4AFB-FD42-483C-AA80-876E1CD4E00B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDFD4AFB-FD42-483C-AA80-876E1CD4E00B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDFD4AFB-FD42-483C-AA80-876E1CD4E00B}.Release|Any CPU.Build.0 = Release|Any CPU + {9BB1E4FE-D7B8-4864-9C50-6F960F2543E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BB1E4FE-D7B8-4864-9C50-6F960F2543E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BB1E4FE-D7B8-4864-9C50-6F960F2543E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BB1E4FE-D7B8-4864-9C50-6F960F2543E2}.Release|Any CPU.Build.0 = Release|Any CPU + {847178B8-0137-48B3-8B05-B3EFDA1C90D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {847178B8-0137-48B3-8B05-B3EFDA1C90D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {847178B8-0137-48B3-8B05-B3EFDA1C90D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {847178B8-0137-48B3-8B05-B3EFDA1C90D8}.Release|Any CPU.Build.0 = Release|Any CPU + {C8CA4E6C-305E-406A-8BED-2B8605F1867C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8CA4E6C-305E-406A-8BED-2B8605F1867C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8CA4E6C-305E-406A-8BED-2B8605F1867C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8CA4E6C-305E-406A-8BED-2B8605F1867C}.Release|Any CPU.Build.0 = Release|Any CPU + {718FA0E8-8FD8-4913-89FE-C2D9E9D58AAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {718FA0E8-8FD8-4913-89FE-C2D9E9D58AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {718FA0E8-8FD8-4913-89FE-C2D9E9D58AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {718FA0E8-8FD8-4913-89FE-C2D9E9D58AAB}.Release|Any CPU.Build.0 = Release|Any CPU + {4D709360-03ED-4A76-8EBB-18B89462A9DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D709360-03ED-4A76-8EBB-18B89462A9DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D709360-03ED-4A76-8EBB-18B89462A9DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D709360-03ED-4A76-8EBB-18B89462A9DB}.Release|Any CPU.Build.0 = Release|Any CPU + {FAEB3A90-FD3D-416D-8443-31E245033560}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAEB3A90-FD3D-416D-8443-31E245033560}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAEB3A90-FD3D-416D-8443-31E245033560}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAEB3A90-FD3D-416D-8443-31E245033560}.Release|Any CPU.Build.0 = Release|Any CPU + {342F648A-6AEF-4755-B98C-62C6E5BBCFF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {342F648A-6AEF-4755-B98C-62C6E5BBCFF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {342F648A-6AEF-4755-B98C-62C6E5BBCFF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {342F648A-6AEF-4755-B98C-62C6E5BBCFF2}.Release|Any CPU.Build.0 = Release|Any CPU + {016047CF-74AA-4611-B995-D47EF2454CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {016047CF-74AA-4611-B995-D47EF2454CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {016047CF-74AA-4611-B995-D47EF2454CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {016047CF-74AA-4611-B995-D47EF2454CAD}.Release|Any CPU.Build.0 = Release|Any CPU + {A65EEF14-F7D5-4DBD-BCAC-9AF67B275EB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A65EEF14-F7D5-4DBD-BCAC-9AF67B275EB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A65EEF14-F7D5-4DBD-BCAC-9AF67B275EB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A65EEF14-F7D5-4DBD-BCAC-9AF67B275EB2}.Release|Any CPU.Build.0 = Release|Any CPU + {C7752F7D-AF11-47A2-84C3-95D35166E02F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7752F7D-AF11-47A2-84C3-95D35166E02F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7752F7D-AF11-47A2-84C3-95D35166E02F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7752F7D-AF11-47A2-84C3-95D35166E02F}.Release|Any CPU.Build.0 = Release|Any CPU + {C57EB532-BEE5-4238-BF35-4B77F77E5FD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C57EB532-BEE5-4238-BF35-4B77F77E5FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C57EB532-BEE5-4238-BF35-4B77F77E5FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C57EB532-BEE5-4238-BF35-4B77F77E5FD5}.Release|Any CPU.Build.0 = Release|Any CPU + {411991A2-008D-4661-8307-6E07FC813704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {411991A2-008D-4661-8307-6E07FC813704}.Debug|Any CPU.Build.0 = Debug|Any CPU + {411991A2-008D-4661-8307-6E07FC813704}.Release|Any CPU.ActiveCfg = Release|Any CPU + {411991A2-008D-4661-8307-6E07FC813704}.Release|Any CPU.Build.0 = Release|Any CPU + {C78245F3-71A5-4628-8796-79B8FC3C7690}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C78245F3-71A5-4628-8796-79B8FC3C7690}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C78245F3-71A5-4628-8796-79B8FC3C7690}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C78245F3-71A5-4628-8796-79B8FC3C7690}.Release|Any CPU.Build.0 = Release|Any CPU + {F7F56D0F-1892-46C5-BCAD-E69BA600EED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7F56D0F-1892-46C5-BCAD-E69BA600EED1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7F56D0F-1892-46C5-BCAD-E69BA600EED1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7F56D0F-1892-46C5-BCAD-E69BA600EED1}.Release|Any CPU.Build.0 = Release|Any CPU + {2014D6A8-26C6-4C6F-B0C7-6E01066484F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2014D6A8-26C6-4C6F-B0C7-6E01066484F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2014D6A8-26C6-4C6F-B0C7-6E01066484F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2014D6A8-26C6-4C6F-B0C7-6E01066484F2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {64B1198F-39D1-48BD-B689-D9949391AEE6} + EndGlobalSection +EndGlobal diff --git a/Azoth.Tools.Bootstrap.sln.DotSettings b/Azoth.Tools.Bootstrap.sln.DotSettings new file mode 100644 index 00000000..72e0c3f3 --- /dev/null +++ b/Azoth.Tools.Bootstrap.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Compiler.API/AssemblyBuilder.cs b/Compiler.API/AssemblyBuilder.cs new file mode 100644 index 00000000..3ade38df --- /dev/null +++ b/Compiler.API/AssemblyBuilder.cs @@ -0,0 +1,66 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.API +{ + public class AssemblyBuilder : CodeBuilder + { + public new const string LineTerminator = "\n"; + + /// Whether we need a blank separator line before the next declaration + public bool NeedsDeclarationSeparatorLine { get; private set; } + + public AssemblyBuilder(string indentCharacters = " ") + : base(indentCharacters, LineTerminator) + { + } + + public override void EndLine(string value) + { + base.EndLine(value); + NeedsDeclarationSeparatorLine = true; + } + + public override void EndLine() + { + base.EndLine(); + NeedsDeclarationSeparatorLine = true; + } + + public override void AppendLine(string value) + { + base.AppendLine(value); + NeedsDeclarationSeparatorLine = true; + } + + public override void BlankLine() + { + base.BlankLine(); + NeedsDeclarationSeparatorLine = false; + } + + public virtual void DeclarationSeparatorLine() + { + if (!NeedsDeclarationSeparatorLine) return; + base.BlankLine(); + NeedsDeclarationSeparatorLine = false; + } + + public override void BeginBlock() + // ensures CurrentIndentDepth == old(CurrentIndentDepth) + 1 + { + base.AppendLine("{"); + base.BeginBlock(); + NeedsDeclarationSeparatorLine = false; + } + + public override void EndBlock() + // ensures CurrentIndentDepth == old(CurrentIndentDepth) - 1 + { + Requires.That(nameof(CurrentIndent), CurrentIndentDepth > 0, "indent depth must not be zero"); + base.EndBlock(); + base.AppendLine("}"); + NeedsDeclarationSeparatorLine = true; + } + } +} diff --git a/Compiler.API/AzothCompiler.cs b/Compiler.API/AzothCompiler.cs new file mode 100644 index 00000000..c2784605 --- /dev/null +++ b/Compiler.API/AzothCompiler.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Parsing; +using Azoth.Tools.Bootstrap.Compiler.Semantics; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.API +{ + public class AzothCompiler + { + /// + /// Whether to store the liveness analysis for each function and method. + /// Default Value: false + /// + public bool SaveLivenessAnalysis { get; set; } = false; + + /// + /// Whether to store the borrow checker claims for each function and method. + /// Default Value: false + /// + public bool SaveReachabilityGraphs { get; set; } = false; + + public Task CompilePackageAsync( + Name name, + IEnumerable files, + FixedDictionary> referenceTasks) + { + return CompilePackageAsync(name, files, referenceTasks, TaskScheduler.Default); + } + + public async Task CompilePackageAsync( + Name name, + IEnumerable fileSources, + FixedDictionary> referenceTasks, + TaskScheduler taskScheduler) + { + var lexer = new Lexer(); + var parser = new CompilationUnitParser(); + var parseBlock = new TransformBlock( + async (fileSource) => + { + var file = await fileSource.LoadAsync().ConfigureAwait(false); + var context = new ParseContext(file, new Diagnostics()); + var tokens = lexer.Lex(context).WhereNotTrivia(); + return parser.Parse(tokens); + }, new ExecutionDataflowBlockOptions() + { + TaskScheduler = taskScheduler, + EnsureOrdered = false, + }); + + foreach (var fileSource in fileSources) + parseBlock.Post(fileSource); + + parseBlock.Complete(); + + await parseBlock.Completion.ConfigureAwait(false); + + if (!parseBlock.TryReceiveAll(out var compilationUnits)) + throw new Exception("Not all compilation units are ready"); + + var referencePairs = await Task + .WhenAll(referenceTasks.Select(async kv => + (alias: kv.Key, package: await kv.Value.ConfigureAwait(false)))) + .ConfigureAwait(false); + var references = referencePairs.ToFixedDictionary(r => r.alias, r => r.package); + + // TODO add the references to the package syntax + var packageSyntax = new PackageSyntax(name, compilationUnits.ToFixedSet(), references); + + var analyzer = new SemanticAnalyzer() + { + SaveLivenessAnalysis = SaveLivenessAnalysis, + SaveReachabilityGraphs = SaveReachabilityGraphs, + }; + + return analyzer.Check(packageSyntax); + } + + public PackageIL CompilePackage( + string name, + IEnumerable fileSources, + FixedDictionary references) + { + return CompilePackage(name, fileSources.Select(s => s.Load()), references); + } + + public PackageIL CompilePackage( + string name, + IEnumerable files, + FixedDictionary references) + { + var lexer = new Lexer(); + var parser = new CompilationUnitParser(); + var compilationUnits = files + .Select(file => + { + var context = new ParseContext(file, new Diagnostics()); + var tokens = lexer.Lex(context).WhereNotTrivia(); + return parser.Parse(tokens); + }) + .ToFixedSet(); + var packageSyntax = new PackageSyntax(name, compilationUnits, references); + + var analyzer = new SemanticAnalyzer() + { + SaveLivenessAnalysis = SaveLivenessAnalysis, + SaveReachabilityGraphs = SaveReachabilityGraphs, + }; + + return analyzer.Check(packageSyntax); + } + } +} diff --git a/Compiler.API/Compiler.API.csproj b/Compiler.API/Compiler.API.csproj new file mode 100644 index 00000000..aa868008 --- /dev/null +++ b/Compiler.API/Compiler.API.csproj @@ -0,0 +1,37 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.API + Azoth.Tools.Bootstrap.Compiler.API + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/Compiler.API/ILAssembler.cs b/Compiler.API/ILAssembler.cs new file mode 100644 index 00000000..40bf1806 --- /dev/null +++ b/Compiler.API/ILAssembler.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.API +{ + public class ILAssembler + { + protected const int StandardStatementWidth = 50; + + public string Disassemble(PackageIL package) + { + var builder = new AssemblyBuilder(); + foreach (var declaration in package.GetNonMemberDeclarations()) + { + Disassemble(declaration, builder); + builder.BlankLine(); + } + + return builder.Code; + } + + public void Disassemble(DeclarationIL declaration, AssemblyBuilder builder) + { + switch (declaration) + { + default: + throw ExhaustiveMatch.Failed(declaration); + case FunctionIL function: + Disassemble(function, builder); + break; + case MethodDeclarationIL method: + Disassemble(method, builder); + break; + case ConstructorIL constructor: + Disassemble(constructor, builder); + break; + case ClassIL type: + Disassemble(type, builder); + break; + case FieldIL field: + Disassemble(field, builder); + break; + } + } + + private static void Disassemble(FunctionIL function, AssemblyBuilder builder) + { + var parameters = FormatParameters(function.Parameters); + builder.BeginLine("fn "); + builder.Append(Convert(function.Symbol)); + builder.Append("("); + builder.Append(parameters); + builder.Append(")"); + builder.EndLine(); + builder.BeginLine(builder.IndentCharacters); + builder.Append("-> "); + builder.EndLine(function.Symbol.ReturnDataType.ToString()); + if (function.IL != null) + { + builder.BeginBlock(); + Disassemble(function.IL, builder); + builder.EndBlock(); + } + } + + private static string FormatParameters(IEnumerable parameters) + { + var formatted = string.Join(", ", parameters.Select(FormatParameter)); + return formatted; + } + + private static string FormatParameter(ParameterIL parameter) + { + var format = parameter.IsMutableBinding ? "var {0}: {1}" : "{0}: {1}"; + return parameter switch + { + // TODO what about escaping the name? + NamedParameterIL param => + string.Format(CultureInfo.InvariantCulture, format, param.Symbol.Name, param.DataType), + // TODO is this the correct name for self? + SelfParameterIL param => + string.Format(CultureInfo.InvariantCulture, format, "self", param.DataType), + FieldParameterIL param => $".{param.InitializeField.Name}", + _ => throw ExhaustiveMatch.Failed(parameter) + }; + } + + private static void Disassemble(MethodDeclarationIL method, AssemblyBuilder builder) + { + var parameters = FormatParameters(method.Parameters.Prepend(method.SelfParameter)); + builder.BeginLine("fn "); + builder.Append(Convert(method.Symbol)); + builder.Append("("); + builder.Append(parameters); + builder.Append(")"); + builder.EndLine(); + builder.BeginLine(builder.IndentCharacters); + builder.Append("-> "); + builder.EndLine(method.Symbol.ReturnDataType.ToString()); + if (method.IL != null) + { + builder.BeginBlock(); + Disassemble(method.IL, builder); + builder.EndBlock(); + } + } + + private static void Disassemble(ConstructorIL constructor, AssemblyBuilder builder) + { + var parameters = FormatParameters(constructor.Parameters); + builder.BeginLine("fn "); + builder.Append(Convert(constructor.Symbol)); + builder.Append("("); + builder.Append(parameters); + builder.Append(")"); + builder.EndLine(); + builder.BeginLine(builder.IndentCharacters); + builder.Append("-> "); + builder.EndLine(constructor.Symbol.ContainingSymbol.DeclaresDataType.ToString()); + builder.BeginBlock(); + Disassemble(constructor.IL, builder); + builder.EndBlock(); + } + + private void Disassemble(ClassIL @class, AssemblyBuilder builder) + { + builder.BeginLine("class "); + builder.Append(Convert(@class.Symbol)); + builder.EndLine(); + builder.BeginBlock(); + foreach (var member in @class.Members) + { + builder.DeclarationSeparatorLine(); + Disassemble(member, builder); + } + builder.EndBlock(); + } + + private static void Disassemble(ControlFlowGraph cfg, AssemblyBuilder builder) + { + if (cfg is null) + { + builder.AppendLine("// Control Flow Graph not available"); + return; + } + + foreach (var declaration in cfg.LocalVariables) + Disassemble(declaration, builder); + + if (cfg.LocalVariables.Any(v => v.TypeIsNotEmpty)) + builder.BlankLine(); + + //if (Disassemble(cfg.BorrowClaims?.ParameterClaims, builder)) + // builder.BlankLine(); + + foreach (var block in cfg.Blocks) + Disassemble(cfg, block, builder); + } + + private static void Disassemble(VariableDeclaration declaration, AssemblyBuilder builder) + { + if (declaration.TypeIsNotEmpty) + builder.AppendLine(declaration.ToStatementString().PadRight(StandardStatementWidth) + + declaration.ContextCommentString()); + } + + private static void Disassemble(ControlFlowGraph cfg, Block block, AssemblyBuilder builder) + { + var labelIndent = Math.Max(builder.CurrentIndentDepth - 1, 0); + builder.Append(string.Concat(Enumerable.Repeat(builder.IndentCharacters, labelIndent))); + builder.Append("bb"); + builder.Append(block.Number.ToString(CultureInfo.InvariantCulture)); + builder.EndLine(":"); + foreach (var instruction in block.Instructions) + { + _ = cfg; + //Disassemble(cfg, cfg.LiveVariables?.Before(instruction), builder); + builder.AppendLine(instruction.ToInstructionString().PadRight(StandardStatementWidth) + + instruction.ContextCommentString()); + //var insertedDeletes = cfg.InsertedDeletes; + //var insertedDeletes = cfg.InsertedDeletes ?? throw new InvalidOperationException("Inserted deletes is null"); + //Disassemble(insertedDeletes.After(instruction), builder); + //Disassemble(cfg.BorrowClaims?.After(instruction), builder); + } + + var terminator = block.Terminator; + builder.AppendLine(terminator.ToInstructionString().PadRight(StandardStatementWidth)); + } + + //private static void Disassemble( + // ControlFlowGraph cfg, + // BitArray? liveVariables, + // AssemblyBuilder builder) + //{ + // if (liveVariables is null || liveVariables.Cast().All(x => x == false)) return; + + // var variables = string.Join(", ", liveVariables.TrueIndexes() + // .Select(i => FormatVariableName(cfg.VariableDeclarations[i]))); + // builder.BeginLine("// live: "); + // builder.EndLine(variables); + //} + + //private static string FormatVariableName(VariableDeclaration declaration) + //{ + // return declaration.Name != null + // ? $"{declaration.Variable}({declaration.Name})" + // : declaration.Variable.ToString(); + //} + + //private static void Disassemble( + // IReadOnlyList deletes, + // AssemblyBuilder builder) + //{ + // foreach (var delete in deletes) + // builder.AppendLine(delete.ToStatementString().PadRight(StandardStatementWidth) + // + delete.ContextCommentString()); + //} + + //private static bool Disassemble(Claims? claims, AssemblyBuilder builder) + //{ + // if (claims is null) return false; + + // foreach (var claim in claims.AsEnumerable()) + // { + // builder.BeginLine("// "); + // builder.EndLine(claim.ToString()); + // } + + // return claims.Any(); + //} + + private static void Disassemble(FieldIL field, AssemblyBuilder builder) + { + var binding = field.IsMutableBinding ? "var" : "let"; + builder.AppendLine($"{binding} {field.Symbol.Name}: {field.DataType};"); + } + + private static string Convert(TypeSymbol symbol) + { + if (symbol.ContainingSymbol is null) return Convert(symbol.Name); + return Convert(symbol.ContainingSymbol) + "." + Convert(symbol.Name); + } + + private static string Convert(FunctionSymbol symbol) + { + var name = symbol.ContainingSymbol switch + { + NamespaceOrPackageSymbol ns => Convert(ns), + ObjectTypeSymbol sym => Convert(sym), + _ => throw new NotSupportedException(symbol.GetType().ToString()) + }; + + name += "."+ Convert(symbol.Name); + return name; + } + + private static string Convert(MethodSymbol symbol) + { + return Convert(symbol.ContainingSymbol) + "." + Convert(symbol.Name); + } + + private static string Convert(ConstructorSymbol symbol) + { + var name = Convert(symbol.ContainingSymbol) + ".new"; + if (!(symbol.Name is null)) + name += "." + Convert(symbol.Name); + return name; + } + + private static string Convert(NamespaceOrPackageSymbol symbol) + { + return symbol switch + { + NamespaceSymbol sym => Convert(sym.ContainingSymbol) + "." + Convert(sym.Name), + PackageSymbol sym => sym.Name + "::", + _ => throw ExhaustiveMatch.Failed(symbol) + }; + } + + private static string Convert(TypeName name) + { + return name.ToString(); + } + } +} diff --git a/Compiler.API/README.md b/Compiler.API/README.md new file mode 100644 index 00000000..15e50018 --- /dev/null +++ b/Compiler.API/README.md @@ -0,0 +1,3 @@ +# Azoth.Tools.Bootstrap.Compiler.API + +The API project contains classes in the `Azoth.Tools.Bootstrap.Compiler.API` namespace that form an API to using the compiler. diff --git a/Compiler.AST/AbstractSyntaxTree.children.cs b/Compiler.AST/AbstractSyntaxTree.children.cs new file mode 100644 index 00000000..03298cbf --- /dev/null +++ b/Compiler.AST/AbstractSyntaxTree.children.cs @@ -0,0 +1,172 @@ +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Diagnostics; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.AST +{ + [GeneratedCode("AzothCompilerCodeGen", null)] + public static class IAbstractSyntaxExtensions + { + [DebuggerStepThrough] + public static IEnumerable Children(this IAbstractSyntax node) + { + switch(node) + { + default: + throw ExhaustiveMatch.Failed(node); + case IClassDeclaration n: + foreach(var child in n.Members) + yield return child; + yield break; + case IFunctionDeclaration n: + foreach(var child in n.Parameters) + yield return child; + yield return n.Body; + yield break; + case IAbstractMethodDeclaration n: + yield return n.SelfParameter; + foreach(var child in n.Parameters) + yield return child; + yield break; + case IConcreteMethodDeclaration n: + yield return n.SelfParameter; + foreach(var child in n.Parameters) + yield return child; + yield return n.Body; + yield break; + case IConstructorDeclaration n: + yield return n.ImplicitSelfParameter; + foreach(var child in n.Parameters) + yield return child; + yield return n.Body; + yield break; + case IFieldDeclaration n: + yield break; + case IAssociatedFunctionDeclaration n: + foreach(var child in n.Parameters) + yield return child; + yield return n.Body; + yield break; + case INamedParameter n: + if(!(n.DefaultValue is null)) + yield return n.DefaultValue; + yield break; + case ISelfParameter n: + yield break; + case IFieldParameter n: + if(!(n.DefaultValue is null)) + yield return n.DefaultValue; + yield break; + case IBody n: + foreach(var child in n.Statements) + yield return child; + yield break; + case IResultStatement n: + yield return n.Expression; + yield break; + case IVariableDeclarationStatement n: + if(!(n.Initializer is null)) + yield return n.Initializer; + yield break; + case IExpressionStatement n: + yield return n.Expression; + yield break; + case IBlockExpression n: + foreach(var child in n.Statements) + yield return child; + yield break; + case INewObjectExpression n: + foreach(var child in n.Arguments) + yield return child; + yield break; + case IUnsafeExpression n: + yield return n.Expression; + yield break; + case IBoolLiteralExpression n: + yield break; + case IIntegerLiteralExpression n: + yield break; + case INoneLiteralExpression n: + yield break; + case IStringLiteralExpression n: + yield break; + case IAssignmentExpression n: + yield return n.LeftOperand; + yield return n.RightOperand; + yield break; + case IBinaryOperatorExpression n: + yield return n.LeftOperand; + yield return n.RightOperand; + yield break; + case IUnaryOperatorExpression n: + yield return n.Operand; + yield break; + case IIfExpression n: + yield return n.Condition; + yield return n.ThenBlock; + if(!(n.ElseClause is null)) + yield return n.ElseClause; + yield break; + case ILoopExpression n: + yield return n.Block; + yield break; + case IWhileExpression n: + yield return n.Condition; + yield return n.Block; + yield break; + case IForeachExpression n: + yield return n.InExpression; + yield return n.Block; + yield break; + case IBreakExpression n: + if(!(n.Value is null)) + yield return n.Value; + yield break; + case INextExpression n: + yield break; + case IReturnExpression n: + if(!(n.Value is null)) + yield return n.Value; + yield break; + case IImplicitImmutabilityConversionExpression n: + yield return n.Expression; + yield break; + case IImplicitNoneConversionExpression n: + yield return n.Expression; + yield break; + case IImplicitNumericConversionExpression n: + yield return n.Expression; + yield break; + case IImplicitOptionalConversionExpression n: + yield return n.Expression; + yield break; + case IFunctionInvocationExpression n: + foreach(var child in n.Arguments) + yield return child; + yield break; + case IMethodInvocationExpression n: + yield return n.Context; + foreach(var child in n.Arguments) + yield return child; + yield break; + case INameExpression n: + yield break; + case ISelfExpression n: + yield break; + case IFieldAccessExpression n: + yield return n.Context; + yield break; + case IBorrowExpression n: + yield return n.Referent; + yield break; + case IMoveExpression n: + yield return n.Referent; + yield break; + case IShareExpression n: + yield return n.Referent; + yield break; + } + } + } +} diff --git a/Compiler.AST/AbstractSyntaxTree.tree b/Compiler.AST/AbstractSyntaxTree.tree new file mode 100644 index 00000000..d55d616b --- /dev/null +++ b/Compiler.AST/AbstractSyntaxTree.tree @@ -0,0 +1,112 @@ +◊namespace Azoth.Tools.Bootstrap.Compiler.AST; +◊base 'IAbstractSyntax'; +◊prefix I; +◊list FixedList; +◊using System.Numerics; +◊using Azoth.Tools.Bootstrap.Compiler.Core; +◊using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +◊using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +◊using Azoth.Tools.Bootstrap.Compiler.Symbols; +◊using Azoth.Tools.Bootstrap.Compiler.Types; +◊using Azoth.Tools.Bootstrap.Framework; + +'IAbstractSyntax' = Span:'TextSpan'; + +// ---------- Special Parts +BodyOrBlock = Statements:Statement*; +ElseClause; +BlockOrResult: ElseClause; + +// ---------- Bindings +Binding = Symbol:'BindingSymbol'; +LocalBinding: Binding = Symbol:'NamedBindingSymbol'; + +// ---------- Declarations +// Note: in the AST all declarations are entities because there are no namespace declarations +Declaration = File:'CodeFile' Symbol:'Symbol' NameSpan:'TextSpan'; +/// A declaration that could contain executable code (i.e. invocable declaration or field declaration) +ExecutableDeclaration: Declaration; +InvocableDeclaration: Declaration = Symbol:'InvocableSymbol' Parameters:ConstructorParameter*; +ConcreteInvocableDeclaration: InvocableDeclaration, ExecutableDeclaration = Body; + +// ---------- Non-Member Declarations +NonMemberDeclaration: Declaration; +ClassDeclaration: NonMemberDeclaration = Symbol:'ObjectTypeSymbol' Members:MemberDeclaration* DefaultConstructorSymbol:'ConstructorSymbol'?; +FunctionDeclaration: NonMemberDeclaration, ConcreteInvocableDeclaration = Symbol:'FunctionSymbol' Parameters:NamedParameter* Body; + +// ---------- Member Declarations +MemberDeclaration: Declaration = DeclaringClass:'IClassDeclaration'; +MethodDeclaration: MemberDeclaration = Symbol:'MethodSymbol' SelfParameter Parameters:NamedParameter*; +AbstractMethodDeclaration: MethodDeclaration = SelfParameter Parameters:NamedParameter*; +ConcreteMethodDeclaration: MethodDeclaration, ConcreteInvocableDeclaration = Symbol:'MethodSymbol' SelfParameter Parameters:NamedParameter* Body; +ConstructorDeclaration: MemberDeclaration, ConcreteInvocableDeclaration = Symbol:'ConstructorSymbol' ImplicitSelfParameter:SelfParameter Parameters:ConstructorParameter* Body; +FieldDeclaration: MemberDeclaration, ExecutableDeclaration, Binding = Symbol:'FieldSymbol'; +AssociatedFunctionDeclaration: MemberDeclaration, ConcreteInvocableDeclaration = Symbol:'FunctionSymbol' Parameters:NamedParameter* Body; + +// ---------- Parameters -------------- +Parameter = Unused:'bool'; +/// A parameter that can be declared in a constructor +ConstructorParameter: Parameter; +/// A parameter that creates a binding, i.e. a named or self parameter +BindingParameter: Parameter, Binding; +NamedParameter: Parameter, ConstructorParameter, BindingParameter, LocalBinding = Symbol:'VariableSymbol' DefaultValue:Expression?; +SelfParameter: Parameter, BindingParameter = Symbol:'SelfParameterSymbol'; +FieldParameter: Parameter, ConstructorParameter = ReferencedSymbol:'FieldSymbol' DefaultValue:Expression?; + +// ---------- Function Parts +Body: BodyOrBlock = Statements:BodyStatement*; + +// ---------- Statements +Statement; +ResultStatement: Statement, BlockOrResult = Expression; +BodyStatement: Statement; +VariableDeclarationStatement: BodyStatement, LocalBinding = NameSpan:'TextSpan' Symbol:'VariableSymbol' Initializer:Expression? VariableIsLiveAfter:'Promise'; +ExpressionStatement: BodyStatement = Expression; + +// ---------- Expressions +Expression = DataType:'DataType' Semantics:'ExpressionSemantics'; +AssignableExpression: Expression; +BlockExpression: Expression, BlockOrResult, BodyOrBlock = Statements:Statement*; +NewObjectExpression: Expression = ReferencedSymbol:'ConstructorSymbol' Arguments:Expression*; +UnsafeExpression: Expression = Expression; + +// ---------- Literal Expressions +LiteralExpression: Expression; +BoolLiteralExpression: LiteralExpression = Value:'bool'; +IntegerLiteralExpression: LiteralExpression = Value:'BigInteger'; +NoneLiteralExpression: LiteralExpression; +StringLiteralExpression: LiteralExpression = Value:'string'; + +// ---------- Operator Expressions +AssignmentExpression: Expression = LeftOperand:AssignableExpression Operator:'AssignmentOperator' RightOperand:Expression; +BinaryOperatorExpression: Expression = LeftOperand:Expression Operator:'BinaryOperator' RightOperand:Expression; +UnaryOperatorExpression: Expression = Fixity:'UnaryOperatorFixity' Operator:'UnaryOperator' Operand:Expression; + +// ---------- Control Flow Expressions +IfExpression: Expression, ElseClause = Condition:Expression ThenBlock:BlockOrResult ElseClause?; +LoopExpression: Expression = Block:BlockExpression; +WhileExpression: Expression = Condition:Expression Block:BlockExpression; +ForeachExpression: Expression, LocalBinding = Symbol:'VariableSymbol' InExpression:Expression Block:BlockExpression VariableIsLiveAfterAssignment:'Promise'; +BreakExpression: Expression = Value:Expression?; +NextExpression: Expression; +ReturnExpression: Expression = Value:Expression?; + +// ---------- Implicit Conversion Expressions +ImplicitConversionExpression: Expression = Expression DataType:'DataType'; +ImplicitImmutabilityConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'ObjectType'; +ImplicitNoneConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'OptionalType'; +ImplicitNumericConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'NumericType'; +ImplicitOptionalConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'OptionalType'; + +// ---------- Invocation Expressions +InvocationExpression: Expression = ReferencedSymbol:'InvocableSymbol' Arguments:Expression*; +FunctionInvocationExpression: InvocationExpression = ReferencedSymbol:'FunctionSymbol' Arguments:Expression*; +MethodInvocationExpression: InvocationExpression = Context:Expression ReferencedSymbol:'MethodSymbol' Arguments:Expression*; + +// ---------- Variable Expressions +NameExpression: AssignableExpression = ReferencedSymbol:'NamedBindingSymbol' VariableIsLiveAfter:'Promise'; +SelfExpression: Expression = ReferencedSymbol:'SelfParameterSymbol' IsImplicit:'bool'; +FieldAccessExpression: AssignableExpression = Context:Expression AccessOperator:'AccessOperator' ReferencedSymbol:'FieldSymbol'; +BorrowExpression: Expression = ReferencedSymbol:'BindingSymbol' Referent:Expression; +MoveExpression: Expression = ReferencedSymbol:'BindingSymbol' Referent:Expression; +ShareExpression: Expression = ReferencedSymbol:'BindingSymbol' Referent:Expression; diff --git a/Compiler.AST/AbstractSyntaxTree.tree.cs b/Compiler.AST/AbstractSyntaxTree.tree.cs new file mode 100644 index 00000000..54254629 --- /dev/null +++ b/Compiler.AST/AbstractSyntaxTree.tree.cs @@ -0,0 +1,475 @@ +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.AST +{ + [Closed( + typeof(IBodyOrBlock), + typeof(IElseClause), + typeof(IBinding), + typeof(IDeclaration), + typeof(IParameter), + typeof(IStatement), + typeof(IExpression))] + public partial interface IAbstractSyntax + { + TextSpan Span { get; } + } + + [Closed( + typeof(IBody), + typeof(IBlockExpression))] + public partial interface IBodyOrBlock : IAbstractSyntax + { + FixedList Statements { get; } + } + + [Closed( + typeof(IBlockOrResult), + typeof(IIfExpression))] + public partial interface IElseClause : IAbstractSyntax + { + } + + [Closed( + typeof(IResultStatement), + typeof(IBlockExpression))] + public partial interface IBlockOrResult : IElseClause + { + } + + [Closed( + typeof(ILocalBinding), + typeof(IFieldDeclaration), + typeof(IBindingParameter))] + public partial interface IBinding : IAbstractSyntax + { + BindingSymbol Symbol { get; } + } + + [Closed( + typeof(INamedParameter), + typeof(IVariableDeclarationStatement), + typeof(IForeachExpression))] + public partial interface ILocalBinding : IBinding + { + new NamedBindingSymbol Symbol { get; } + } + + [Closed( + typeof(IExecutableDeclaration), + typeof(IInvocableDeclaration), + typeof(INonMemberDeclaration), + typeof(IMemberDeclaration))] + public partial interface IDeclaration : IAbstractSyntax + { + CodeFile File { get; } + Symbol Symbol { get; } + TextSpan NameSpan { get; } + } + + [Closed( + typeof(IConcreteInvocableDeclaration), + typeof(IFieldDeclaration))] + public partial interface IExecutableDeclaration : IDeclaration + { + } + + [Closed( + typeof(IConcreteInvocableDeclaration))] + public partial interface IInvocableDeclaration : IDeclaration + { + new InvocableSymbol Symbol { get; } + FixedList Parameters { get; } + } + + [Closed( + typeof(IFunctionDeclaration), + typeof(IConcreteMethodDeclaration), + typeof(IConstructorDeclaration), + typeof(IAssociatedFunctionDeclaration))] + public partial interface IConcreteInvocableDeclaration : IInvocableDeclaration, IExecutableDeclaration + { + IBody Body { get; } + } + + [Closed( + typeof(IClassDeclaration), + typeof(IFunctionDeclaration))] + public partial interface INonMemberDeclaration : IDeclaration + { + } + + public partial interface IClassDeclaration : INonMemberDeclaration + { + new ObjectTypeSymbol Symbol { get; } + FixedList Members { get; } + ConstructorSymbol? DefaultConstructorSymbol { get; } + } + + public partial interface IFunctionDeclaration : INonMemberDeclaration, IConcreteInvocableDeclaration + { + new FunctionSymbol Symbol { get; } + new FixedList Parameters { get; } + } + + [Closed( + typeof(IMethodDeclaration), + typeof(IConstructorDeclaration), + typeof(IFieldDeclaration), + typeof(IAssociatedFunctionDeclaration))] + public partial interface IMemberDeclaration : IDeclaration + { + IClassDeclaration DeclaringClass { get; } + } + + [Closed( + typeof(IAbstractMethodDeclaration), + typeof(IConcreteMethodDeclaration))] + public partial interface IMethodDeclaration : IMemberDeclaration + { + new MethodSymbol Symbol { get; } + ISelfParameter SelfParameter { get; } + FixedList Parameters { get; } + } + + public partial interface IAbstractMethodDeclaration : IMethodDeclaration + { + } + + public partial interface IConcreteMethodDeclaration : IMethodDeclaration, IConcreteInvocableDeclaration + { + new MethodSymbol Symbol { get; } + new FixedList Parameters { get; } + } + + public partial interface IConstructorDeclaration : IMemberDeclaration, IConcreteInvocableDeclaration + { + new ConstructorSymbol Symbol { get; } + ISelfParameter ImplicitSelfParameter { get; } + } + + public partial interface IFieldDeclaration : IMemberDeclaration, IExecutableDeclaration, IBinding + { + new FieldSymbol Symbol { get; } + } + + public partial interface IAssociatedFunctionDeclaration : IMemberDeclaration, IConcreteInvocableDeclaration + { + new FunctionSymbol Symbol { get; } + new FixedList Parameters { get; } + } + + [Closed( + typeof(IConstructorParameter), + typeof(IBindingParameter), + typeof(INamedParameter), + typeof(ISelfParameter), + typeof(IFieldParameter))] + public partial interface IParameter : IAbstractSyntax + { + bool Unused { get; } + } + + [Closed( + typeof(INamedParameter), + typeof(IFieldParameter))] + public partial interface IConstructorParameter : IParameter + { + } + + [Closed( + typeof(INamedParameter), + typeof(ISelfParameter))] + public partial interface IBindingParameter : IParameter, IBinding + { + } + + public partial interface INamedParameter : IParameter, IConstructorParameter, IBindingParameter, ILocalBinding + { + new VariableSymbol Symbol { get; } + IExpression? DefaultValue { get; } + } + + public partial interface ISelfParameter : IParameter, IBindingParameter + { + new SelfParameterSymbol Symbol { get; } + } + + public partial interface IFieldParameter : IParameter, IConstructorParameter + { + FieldSymbol ReferencedSymbol { get; } + IExpression? DefaultValue { get; } + } + + public partial interface IBody : IBodyOrBlock + { + new FixedList Statements { get; } + } + + [Closed( + typeof(IResultStatement), + typeof(IBodyStatement))] + public partial interface IStatement : IAbstractSyntax + { + } + + public partial interface IResultStatement : IStatement, IBlockOrResult + { + IExpression Expression { get; } + } + + [Closed( + typeof(IVariableDeclarationStatement), + typeof(IExpressionStatement))] + public partial interface IBodyStatement : IStatement + { + } + + public partial interface IVariableDeclarationStatement : IBodyStatement, ILocalBinding + { + TextSpan NameSpan { get; } + new VariableSymbol Symbol { get; } + IExpression? Initializer { get; } + Promise VariableIsLiveAfter { get; } + } + + public partial interface IExpressionStatement : IBodyStatement + { + IExpression Expression { get; } + } + + [Closed( + typeof(IAssignableExpression), + typeof(IBlockExpression), + typeof(INewObjectExpression), + typeof(IUnsafeExpression), + typeof(ILiteralExpression), + typeof(IAssignmentExpression), + typeof(IBinaryOperatorExpression), + typeof(IUnaryOperatorExpression), + typeof(IIfExpression), + typeof(ILoopExpression), + typeof(IWhileExpression), + typeof(IForeachExpression), + typeof(IBreakExpression), + typeof(INextExpression), + typeof(IReturnExpression), + typeof(IImplicitConversionExpression), + typeof(IInvocationExpression), + typeof(ISelfExpression), + typeof(IBorrowExpression), + typeof(IMoveExpression), + typeof(IShareExpression))] + public partial interface IExpression : IAbstractSyntax + { + DataType DataType { get; } + ExpressionSemantics Semantics { get; } + } + + [Closed( + typeof(INameExpression), + typeof(IFieldAccessExpression))] + public partial interface IAssignableExpression : IExpression + { + } + + public partial interface IBlockExpression : IExpression, IBlockOrResult, IBodyOrBlock + { + } + + public partial interface INewObjectExpression : IExpression + { + ConstructorSymbol ReferencedSymbol { get; } + FixedList Arguments { get; } + } + + public partial interface IUnsafeExpression : IExpression + { + IExpression Expression { get; } + } + + [Closed( + typeof(IBoolLiteralExpression), + typeof(IIntegerLiteralExpression), + typeof(INoneLiteralExpression), + typeof(IStringLiteralExpression))] + public partial interface ILiteralExpression : IExpression + { + } + + public partial interface IBoolLiteralExpression : ILiteralExpression + { + bool Value { get; } + } + + public partial interface IIntegerLiteralExpression : ILiteralExpression + { + BigInteger Value { get; } + } + + public partial interface INoneLiteralExpression : ILiteralExpression + { + } + + public partial interface IStringLiteralExpression : ILiteralExpression + { + string Value { get; } + } + + public partial interface IAssignmentExpression : IExpression + { + IAssignableExpression LeftOperand { get; } + AssignmentOperator Operator { get; } + IExpression RightOperand { get; } + } + + public partial interface IBinaryOperatorExpression : IExpression + { + IExpression LeftOperand { get; } + BinaryOperator Operator { get; } + IExpression RightOperand { get; } + } + + public partial interface IUnaryOperatorExpression : IExpression + { + UnaryOperatorFixity Fixity { get; } + UnaryOperator Operator { get; } + IExpression Operand { get; } + } + + public partial interface IIfExpression : IExpression, IElseClause + { + IExpression Condition { get; } + IBlockOrResult ThenBlock { get; } + IElseClause? ElseClause { get; } + } + + public partial interface ILoopExpression : IExpression + { + IBlockExpression Block { get; } + } + + public partial interface IWhileExpression : IExpression + { + IExpression Condition { get; } + IBlockExpression Block { get; } + } + + public partial interface IForeachExpression : IExpression, ILocalBinding + { + new VariableSymbol Symbol { get; } + IExpression InExpression { get; } + IBlockExpression Block { get; } + Promise VariableIsLiveAfterAssignment { get; } + } + + public partial interface IBreakExpression : IExpression + { + IExpression? Value { get; } + } + + public partial interface INextExpression : IExpression + { + } + + public partial interface IReturnExpression : IExpression + { + IExpression? Value { get; } + } + + [Closed( + typeof(IImplicitImmutabilityConversionExpression), + typeof(IImplicitNoneConversionExpression), + typeof(IImplicitNumericConversionExpression), + typeof(IImplicitOptionalConversionExpression))] + public partial interface IImplicitConversionExpression : IExpression + { + IExpression Expression { get; } + } + + public partial interface IImplicitImmutabilityConversionExpression : IImplicitConversionExpression + { + ObjectType ConvertToType { get; } + } + + public partial interface IImplicitNoneConversionExpression : IImplicitConversionExpression + { + OptionalType ConvertToType { get; } + } + + public partial interface IImplicitNumericConversionExpression : IImplicitConversionExpression + { + NumericType ConvertToType { get; } + } + + public partial interface IImplicitOptionalConversionExpression : IImplicitConversionExpression + { + OptionalType ConvertToType { get; } + } + + [Closed( + typeof(IFunctionInvocationExpression), + typeof(IMethodInvocationExpression))] + public partial interface IInvocationExpression : IExpression + { + InvocableSymbol ReferencedSymbol { get; } + FixedList Arguments { get; } + } + + public partial interface IFunctionInvocationExpression : IInvocationExpression + { + new FunctionSymbol ReferencedSymbol { get; } + } + + public partial interface IMethodInvocationExpression : IInvocationExpression + { + IExpression Context { get; } + new MethodSymbol ReferencedSymbol { get; } + } + + public partial interface INameExpression : IAssignableExpression + { + NamedBindingSymbol ReferencedSymbol { get; } + Promise VariableIsLiveAfter { get; } + } + + public partial interface ISelfExpression : IExpression + { + SelfParameterSymbol ReferencedSymbol { get; } + bool IsImplicit { get; } + } + + public partial interface IFieldAccessExpression : IAssignableExpression + { + IExpression Context { get; } + AccessOperator AccessOperator { get; } + FieldSymbol ReferencedSymbol { get; } + } + + public partial interface IBorrowExpression : IExpression + { + BindingSymbol ReferencedSymbol { get; } + IExpression Referent { get; } + } + + public partial interface IMoveExpression : IExpression + { + BindingSymbol ReferencedSymbol { get; } + IExpression Referent { get; } + } + + public partial interface IShareExpression : IExpression + { + BindingSymbol ReferencedSymbol { get; } + IExpression Referent { get; } + } + +} diff --git a/Compiler.AST/Compiler.AST.csproj b/Compiler.AST/Compiler.AST.csproj new file mode 100644 index 00000000..1cf925c6 --- /dev/null +++ b/Compiler.AST/Compiler.AST.csproj @@ -0,0 +1,48 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.AST + Azoth.Tools.Bootstrap.Compiler.AST + enable + True + + + + DEBUG;TRACE + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + AbstractSyntaxTree.tree + + + AbstractSyntaxTree.tree + + + + + + + + diff --git a/Compiler.AST/IAbstractSyntax.cs b/Compiler.AST/IAbstractSyntax.cs new file mode 100644 index 00000000..257f462a --- /dev/null +++ b/Compiler.AST/IAbstractSyntax.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.AST +{ + public partial interface IAbstractSyntax + { + string ToString(); + } +} diff --git a/Compiler.AST/IExpression.cs b/Compiler.AST/IExpression.cs new file mode 100644 index 00000000..459ad378 --- /dev/null +++ b/Compiler.AST/IExpression.cs @@ -0,0 +1,9 @@ +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.AST +{ + public partial interface IExpression + { + string ToGroupedString(OperatorPrecedence surroundingPrecedence); + } +} diff --git a/Compiler.AST/Walkers/AbstractSyntaxWalker.cs b/Compiler.AST/Walkers/AbstractSyntaxWalker.cs new file mode 100644 index 00000000..64650b1a --- /dev/null +++ b/Compiler.AST/Walkers/AbstractSyntaxWalker.cs @@ -0,0 +1,57 @@ +using System.Diagnostics; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Compiler.AST.Walkers +{ + public abstract class AbstractSyntaxWalker + { + [DebuggerHidden] + protected void Walk(IAbstractSyntax? syntax, T arg) + { + if (syntax is null) return; + WalkNonNull(syntax, arg); + } + + protected abstract void WalkNonNull(IAbstractSyntax syntax, T arg); + + [DebuggerHidden] + protected void WalkChildren(IAbstractSyntax syntax, T arg) + { + foreach (var child in syntax.Children()) + WalkNonNull(child, arg); + } + + [DebuggerHidden] + protected void WalkChildrenInReverse(IAbstractSyntax syntax, T arg) + { + foreach (var child in syntax.Children().Reverse()) + WalkNonNull(child, arg); + } + } + + public abstract class AbstractSyntaxWalker + { + [DebuggerHidden] + protected void Walk(IAbstractSyntax? syntax) + { + if (syntax is null) return; + WalkNonNull(syntax); + } + + protected abstract void WalkNonNull(IAbstractSyntax syntax); + + [DebuggerHidden] + protected void WalkChildren(IAbstractSyntax syntax) + { + foreach (var child in syntax.Children()) + WalkNonNull(child); + } + + [DebuggerHidden] + protected void WalkChildrenInReverse(IAbstractSyntax syntax) + { + foreach (var child in syntax.Children().Reverse()) + WalkNonNull(child); + } + } +} diff --git a/Compiler.CST/AccessModifier.cs b/Compiler.CST/AccessModifier.cs new file mode 100644 index 00000000..e05c6242 --- /dev/null +++ b/Compiler.CST/AccessModifier.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public enum AccessModifier + { + Private = 1, + Public, + Published, + } +} diff --git a/Compiler.CST/Compiler.CST.csproj b/Compiler.CST/Compiler.CST.csproj new file mode 100644 index 00000000..7a7ed8a2 --- /dev/null +++ b/Compiler.CST/Compiler.CST.csproj @@ -0,0 +1,56 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.CST + Azoth.Tools.Bootstrap.Compiler.CST + enable + True + + + + DEBUG;TRACE + false + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + SyntaxTree.tree + + + SyntaxTree.tree + + + + + + + + diff --git a/Compiler.CST/ExpressionSemanticsExtensions.cs b/Compiler.CST/ExpressionSemanticsExtensions.cs new file mode 100644 index 00000000..5faaca9e --- /dev/null +++ b/Compiler.CST/ExpressionSemanticsExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public static class ExpressionSemanticsExtensions + { + public static string Action(this ExpressionSemantics valueSemantics) + { + string mutability = valueSemantics switch + { + ExpressionSemantics.Never => "never", + ExpressionSemantics.Void => "void", + ExpressionSemantics.Move => "move", + ExpressionSemantics.Copy => "copy", + ExpressionSemantics.Acquire => "own", + ExpressionSemantics.Borrow => "borrow", + ExpressionSemantics.Share => "share", + ExpressionSemantics.CreateReference => "ref", + _ => throw ExhaustiveMatch.Failed(valueSemantics), + }; + + return mutability; + } + + /// + /// Validates that expression semantics have been assigned. + /// + [DebuggerHidden] + public static ExpressionSemantics Assigned(this ExpressionSemantics? semantics) + { + return semantics ?? throw new InvalidOperationException("Expression semantics not assigned"); + } + } +} diff --git a/Compiler.CST/IArgumentSyntax.cs b/Compiler.CST/IArgumentSyntax.cs new file mode 100644 index 00000000..af1111e1 --- /dev/null +++ b/Compiler.CST/IArgumentSyntax.cs @@ -0,0 +1,12 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// This is needed because it is not possible to put ref IExpressionSyntax + /// directly into a list. + /// + /// TODO remove type once expression is immutable + /// + public partial interface IArgumentSyntax + { + } +} diff --git a/Compiler.CST/IBlockExpressionSyntax.cs b/Compiler.CST/IBlockExpressionSyntax.cs new file mode 100644 index 00000000..91ee7c59 --- /dev/null +++ b/Compiler.CST/IBlockExpressionSyntax.cs @@ -0,0 +1,10 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// A block expression. Not to be used to represent function + /// or type bodies. + /// + public partial interface IBlockExpressionSyntax + { + } +} diff --git a/Compiler.CST/IBodyStatementSyntax.cs b/Compiler.CST/IBodyStatementSyntax.cs new file mode 100644 index 00000000..4ff0a8ea --- /dev/null +++ b/Compiler.CST/IBodyStatementSyntax.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// A statement that can appear directly in a method body. (i.e. not a result statement) + /// + public partial interface IBodyStatementSyntax + { + } +} diff --git a/Compiler.CST/IClassDeclarationSyntax.cs b/Compiler.CST/IClassDeclarationSyntax.cs new file mode 100644 index 00000000..3c3d6a9e --- /dev/null +++ b/Compiler.CST/IClassDeclarationSyntax.cs @@ -0,0 +1,9 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public partial interface IClassDeclarationSyntax + { + void CreateDefaultConstructor(SymbolTreeBuilder symbolTree); + } +} diff --git a/Compiler.CST/IDeclarationSyntax.cs b/Compiler.CST/IDeclarationSyntax.cs new file mode 100644 index 00000000..894f04f0 --- /dev/null +++ b/Compiler.CST/IDeclarationSyntax.cs @@ -0,0 +1,15 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public partial interface IDeclarationSyntax + { + /// + /// The span of whatever would count as the "name" of this declaration + /// for things like operator overloads, constructors and destructors, + /// this won't be just an identifier. For example, it could be: + /// * "operator +" + /// * "new foo" + /// * "delete" + /// + //TextSpan NameSpan { get; } + } +} diff --git a/Compiler.CST/IEntityDeclarationSyntax.cs b/Compiler.CST/IEntityDeclarationSyntax.cs new file mode 100644 index 00000000..055f9752 --- /dev/null +++ b/Compiler.CST/IEntityDeclarationSyntax.cs @@ -0,0 +1,10 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// All non-namespace declarations since a namespace doesn't really create + /// a thing, it just defines a group of names. + /// + public partial interface IEntityDeclarationSyntax + { + } +} diff --git a/Compiler.CST/IExpressionSyntax.cs b/Compiler.CST/IExpressionSyntax.cs new file mode 100644 index 00000000..47fa99c3 --- /dev/null +++ b/Compiler.CST/IExpressionSyntax.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public partial interface IExpressionSyntax + { + [DisallowNull] DataType? DataType { get; set; } + [DisallowNull] ExpressionSemantics? Semantics { get; set; } + string ToGroupedString(OperatorPrecedence surroundingPrecedence); + } +} diff --git a/Compiler.CST/IHasContainingLexicalScope.cs b/Compiler.CST/IHasContainingLexicalScope.cs new file mode 100644 index 00000000..dd479fef --- /dev/null +++ b/Compiler.CST/IHasContainingLexicalScope.cs @@ -0,0 +1,9 @@ +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public interface IHasContainingLexicalScope + { + LexicalScope ContainingLexicalScope { get; set; } + } +} diff --git a/Compiler.CST/IImplicitImmutabilityConversionExpressionSyntax.cs b/Compiler.CST/IImplicitImmutabilityConversionExpressionSyntax.cs new file mode 100644 index 00000000..37a13210 --- /dev/null +++ b/Compiler.CST/IImplicitImmutabilityConversionExpressionSyntax.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Cast away mutability + /// + public partial interface IImplicitImmutabilityConversionExpressionSyntax + { + } +} diff --git a/Compiler.CST/IImplicitNoneConversionExpressionSyntax.cs b/Compiler.CST/IImplicitNoneConversionExpressionSyntax.cs new file mode 100644 index 00000000..21c6b408 --- /dev/null +++ b/Compiler.CST/IImplicitNoneConversionExpressionSyntax.cs @@ -0,0 +1,10 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Convert `none` to a specific optional type + /// + // TODO is this needed? shouldn't `none` have the type `never?` which would have an implicit conversion? + public partial interface IImplicitNoneConversionExpressionSyntax + { + } +} diff --git a/Compiler.CST/IImplicitNumericConversionExpressionSyntax.cs b/Compiler.CST/IImplicitNumericConversionExpressionSyntax.cs new file mode 100644 index 00000000..8587aa6a --- /dev/null +++ b/Compiler.CST/IImplicitNumericConversionExpressionSyntax.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Implicit conversion between simple numeric types + /// + public partial interface IImplicitNumericConversionExpressionSyntax + { + } +} diff --git a/Compiler.CST/IImplicitOptionalConversionExpressionSyntax.cs b/Compiler.CST/IImplicitOptionalConversionExpressionSyntax.cs new file mode 100644 index 00000000..ed28157f --- /dev/null +++ b/Compiler.CST/IImplicitOptionalConversionExpressionSyntax.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Convert a type to option of that type (i.e. "Some(value)") + /// + public partial interface IImplicitOptionalConversionExpressionSyntax + { + } +} diff --git a/Compiler.CST/IInvocableDeclarationSyntax.cs b/Compiler.CST/IInvocableDeclarationSyntax.cs new file mode 100644 index 00000000..c90b4b65 --- /dev/null +++ b/Compiler.CST/IInvocableDeclarationSyntax.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Base type for any declaration that declares a callable thing + /// + public partial interface IInvocableDeclarationSyntax + { + } +} diff --git a/Compiler.CST/IMoveExpressionSyntax.cs b/Compiler.CST/IMoveExpressionSyntax.cs new file mode 100644 index 00000000..5b17411c --- /dev/null +++ b/Compiler.CST/IMoveExpressionSyntax.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// i.e. `move name`. A move takes ownership of something from a variable + /// + public partial interface IMoveExpressionSyntax + { + } +} diff --git a/Compiler.CST/IMutateExpressionSyntax.cs b/Compiler.CST/IMutateExpressionSyntax.cs new file mode 100644 index 00000000..258bc065 --- /dev/null +++ b/Compiler.CST/IMutateExpressionSyntax.cs @@ -0,0 +1,11 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// i.e. `mut exp`. A mutate expression allows use of a variable to mutate the value. That + /// is, the result is writable reference type. Note that mutate expressions + /// don't apply to value types since they are passed by move, copy, or reference. + /// + public partial interface IMutateExpressionSyntax + { + } +} diff --git a/Compiler.CST/INameExpressionSyntax.cs b/Compiler.CST/INameExpressionSyntax.cs new file mode 100644 index 00000000..f0c1b403 --- /dev/null +++ b/Compiler.CST/INameExpressionSyntax.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// An expression that is a single unqualified name + /// + public partial interface INameExpressionSyntax + { + IEnumerable> LookupInContainingScope(); + } +} diff --git a/Compiler.CST/INonMemberDeclarationSyntax.cs b/Compiler.CST/INonMemberDeclarationSyntax.cs new file mode 100644 index 00000000..84c8c44d --- /dev/null +++ b/Compiler.CST/INonMemberDeclarationSyntax.cs @@ -0,0 +1,12 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Things that can be declared outside of a class + /// + public partial interface INonMemberDeclarationSyntax + { + NamespaceOrPackageSymbol ContainingNamespaceSymbol { get; set; } + } +} diff --git a/Compiler.CST/INonMemberEntityDeclarationSyntax.cs b/Compiler.CST/INonMemberEntityDeclarationSyntax.cs new file mode 100644 index 00000000..ecd14485 --- /dev/null +++ b/Compiler.CST/INonMemberEntityDeclarationSyntax.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Basically, non-member, non-namespace declarations + /// + public partial interface INonMemberEntityDeclarationSyntax + { + } +} diff --git a/Compiler.CST/IShareExpressionSyntax.cs b/Compiler.CST/IShareExpressionSyntax.cs new file mode 100644 index 00000000..8fa06457 --- /dev/null +++ b/Compiler.CST/IShareExpressionSyntax.cs @@ -0,0 +1,10 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// A bare variable of a reference type is generally a share of that reference. + /// Share expressions are inserted into the AST where needed to reflect this. + /// + public partial interface IShareExpressionSyntax + { + } +} diff --git a/Compiler.CST/ISyntax.cs b/Compiler.CST/ISyntax.cs new file mode 100644 index 00000000..5af9e301 --- /dev/null +++ b/Compiler.CST/ISyntax.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public partial interface ISyntax + { + string ToString(); + } +} diff --git a/Compiler.CST/ITypeNameSyntax.cs b/Compiler.CST/ITypeNameSyntax.cs new file mode 100644 index 00000000..22041e70 --- /dev/null +++ b/Compiler.CST/ITypeNameSyntax.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public partial interface ITypeNameSyntax + { + IEnumerable> LookupInContainingScope(); + } +} diff --git a/Compiler.CST/ITypeSyntax.cs b/Compiler.CST/ITypeSyntax.cs new file mode 100644 index 00000000..7d011c75 --- /dev/null +++ b/Compiler.CST/ITypeSyntax.cs @@ -0,0 +1,10 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public partial interface ITypeSyntax + { + [DisallowNull] DataType? NamedType { get; set; } + } +} diff --git a/Compiler.CST/IUnqualifiedInvocationExpressionSyntax.cs b/Compiler.CST/IUnqualifiedInvocationExpressionSyntax.cs new file mode 100644 index 00000000..f45fad49 --- /dev/null +++ b/Compiler.CST/IUnqualifiedInvocationExpressionSyntax.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + public partial interface IUnqualifiedInvocationExpressionSyntax + { + IEnumerable> LookupInContainingScope(); + } +} diff --git a/Compiler.CST/PackageSyntax.cs b/Compiler.CST/PackageSyntax.cs new file mode 100644 index 00000000..d308a67b --- /dev/null +++ b/Compiler.CST/PackageSyntax.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Primitives; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + /// + /// Represents an entire package worth of syntax + /// + /// Doesn't inherit from because it is never + /// matched as part of syntax. It is always treated as the singular root. + public class PackageSyntax + { + public PackageSymbol Symbol { get; } + public SymbolTreeBuilder SymbolTree { get; } + public SymbolForest SymbolTrees { get; } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public FixedSet CompilationUnits { get; } + public FixedSet AllEntityDeclarations { get; } + public FixedDictionary References { get; } + public IEnumerable ReferencedPackages => References.Values; + public Diagnostics Diagnostics { get; } + + public PackageSyntax( + Name name, + FixedSet compilationUnits, + FixedDictionary references) + { + Symbol = new PackageSymbol(name); + SymbolTree = new SymbolTreeBuilder(Symbol); + CompilationUnits = compilationUnits; + AllEntityDeclarations = GetEntityDeclarations(CompilationUnits).ToFixedSet(); + References = references; + SymbolTrees = new SymbolForest(Primitive.SymbolTree, SymbolTree, ReferencedPackages.Select(p => p.SymbolTree)); + Diagnostics = new Diagnostics(CompilationUnits.SelectMany(cu => cu.Diagnostics)); + } + + /// + /// It wouldn't make sense to get all declarations including non-member because + /// that includes namespace declarations. However, some namespaces come from + /// the implicit namespace of a compilation unit or are implicitly declared, + /// so it wouldn't give a full list of the namespaces. + /// + private static IEnumerable GetEntityDeclarations( + FixedSet compilationUnits) + { + var declarations = new Queue(); + declarations.EnqueueRange(compilationUnits.SelectMany(cu => cu.Declarations)); + while (declarations.TryDequeue(out var declaration)) + { + switch (declaration) + { + default: + throw ExhaustiveMatch.Failed(declaration); + case IMemberDeclarationSyntax syn: + yield return syn; + break; + case IFunctionDeclarationSyntax syn: + yield return syn; + break; + case INamespaceDeclarationSyntax syn: + declarations.EnqueueRange(syn.Declarations); + break; + case IClassDeclarationSyntax syn: + yield return syn; + declarations.EnqueueRange(syn.Members); + break; + } + } + } + + public override string ToString() + { + return $"package {Symbol.Name.Text}: {CompilationUnits.Count} Compilation Units"; + } + } +} diff --git a/Compiler.CST/README.md b/Compiler.CST/README.md new file mode 100644 index 00000000..a82c6325 --- /dev/null +++ b/Compiler.CST/README.md @@ -0,0 +1,47 @@ +# Azoth.Tools.Bootstrap.Compiler.CST + +CST stands for Concrete Syntax Tree. The code is being transitioned to a separate CST generated by the parser and checked for syntactical issues (e.g. abstract method in non-abstract class) and AST generated from the CST by name binding and type checking. + +## Old Readme + +The current architecture uses a single AST that results from parsing for name binding and type checking. It follows the strategy of transforming toward higher level representations as fast as possible. The parser generates things needed for later analysis like names and also simplifies slightly. For example, the parser drops parentheses out of the tree. Borrow checking is performed on an intermediate language (IR) representation of functions. However, this is attached directly to the AST nodes for functions. + +Both tokens and AST nodes have a separate interface and implementation layer. Other parts of the compiler deal only with the interfaces. For both the tokens and AST, transitioning to this separation brought a lot of improvement. Even though it produces more code, the flexibility of multiple inheritance hierarchies is very powerful and enables stronger typing. + +The AST is designed to be as concrete as possible. Meaning that each different kind of syntax construct has a separate node type. For example, it used to be the case that functions, methods, and associated functions were all the same node type. Now, each has a different node type. This has improved type safety by ensuring nodes have exactly the properties that make sense for them and that those properties have correct nullability. + +## Naming Conventions + +Each AST class ends in `Syntax` to distinguish them from intermediate language nodes. The category of the node is also in the name. While including the category is more verbose, not doing so leads to inconsistencies. There are always some node types for which leaving out the category produces a confusing name. Categories are: + +* Declaration +* Statement +* Expression + * OperatorExpression + * LiteralExpression + * ConversionExpression + * InvocationExpression +* Parameter +* Type + +## Traversing the AST + +AST traversal is done either with a tree walker or by matching on node type. Tree walkers are similar to the listener types of ANTLR. They provide an internal traversal. That is, the nodes are walked and the methods of the walker are called as nodes are visited. The other, more common way of traversing the AST is simply by matching on node type and recursing down the tree. When doing this, having more generic/broad switches can help to insulate traversals from AST changes. That is, if a traversal relies on a single large switch over all node types rather than on separate methods with separate switches for different node categories, fewer changes are required when the AST types change. + +It took a while to arrive at these as the traversal approach. Methods on the nodes with overrides were ruled out early since there are many traversals and they are too hard to work on if they are spread out among all the node classes. Instead, the visitor pattern was originally the preferred method of traversing the AST. This was very cumbersome though. It also made it very difficult to handle multiple node types as a single case. At one point, there was experimentation with using reflection to safely implement visitors but that was confusing to refactoring tools and spread the code out among many methods just like a visitor. Using switch statements to match on type was a near ideal syntax, but without exhaustiveness checking, it was incredibly error prone. With the creation and use of the `ExhaustiveMatching.Analyzer` package that provides the most flexible exhaustiveness checking of any language, that has changed. Now switching on type is the preferred approach in many cases. + +## Design Goals + +### Adaptability to Syntax Changes + +As the language is still being designed, it is important that the lexer, parser and concrete syntax tree be easy to modify. This is supported by keeping them as simple as possible. Code generation is used around tokens so that tokens can be easily added and removed. Several points in the lexer use generated lists of all tokens of a given type to guide the lexing process. + +### Correct by Construction + +Originally, tokens were not as strongly typed. They were using several struct types with an enum for the token type. This performance optimization was modelled on the Roslyn C# compiler. However, it was decided that having strongly typed tokens was more valuable and the transition was made to the current set of classes. This also allowed for more sophisticated type relationships such as a base class for keywords and a base class for operators. Ideally, missing tokens would be represented with a special token that included the type and position of the missing token. However, the C# type system didn't offer good options for representing that in a strongly typed way. It was decided to use `null` to represent missing tokens and accept the limitations that might imply. + +## Design History + +The design of the compiler abstract syntax tree (AST) has gone through many revisions. Initially, it was hoped there could be support for incremental compilation through reuse of unchanged portions of the syntax tree. That is the strategy taken by the Roslyn C# compiler. Eventually, it was decided that this was too complex. It is more important to get a compiler up and working. Even after this change of direction, the syntax tree was still very concrete, containing every token from the source file except whitespace, comments and unexpected tokens. In time it was realized that was a hold over and not needed. It was creating a lot of boilerplate code that wasn't being used by later phases. The Rust compiler throws way as much information as possible in the parsing phase and builds a fairly abstract syntax tree with file positions for only the most important elements. + +At that time, there was still a separate "analysis" tree that largely mirrored the syntax tree. This was used for name binding, type checking and any other analysis. The purpose of this was to make the analysis phase more strongly typed. However, all of these fields still had to be nullable so that the analysis could fill them in. Looking at the Rust compiler, it performs name binding on the syntax tree (except for member accesses etc. that require type information to resolved). They then transform it to their "high-level IR" for type checking. This tree simplifies a number of things like loops etc. Then they transform to "mid-level IR" for borrow checking. That means lots of types that mirror existing types. In C# just declaring types like that has a lot of boilerplate. It was decided that rather than separate these out for the sake of "purity" or incremental compilation that probably won't be done in this compiler, they should be collapsed for speed of development. diff --git a/Compiler.CST/SyntaxTree.children.cs b/Compiler.CST/SyntaxTree.children.cs new file mode 100644 index 00000000..314872e7 --- /dev/null +++ b/Compiler.CST/SyntaxTree.children.cs @@ -0,0 +1,215 @@ +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Diagnostics; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + [GeneratedCode("AzothCompilerCodeGen", null)] + public static class ISyntaxExtensions + { + [DebuggerStepThrough] + public static IEnumerable Children(this ISyntax node) + { + switch(node) + { + default: + throw ExhaustiveMatch.Failed(node); + case ICompilationUnitSyntax n: + foreach(var child in n.UsingDirectives) + yield return child; + foreach(var child in n.Declarations) + yield return child; + yield break; + case IUsingDirectiveSyntax n: + yield break; + case INamespaceDeclarationSyntax n: + foreach(var child in n.UsingDirectives) + yield return child; + foreach(var child in n.Declarations) + yield return child; + yield break; + case IClassDeclarationSyntax n: + foreach(var child in n.Members) + yield return child; + yield break; + case IFunctionDeclarationSyntax n: + foreach(var child in n.Parameters) + yield return child; + if(!(n.ReturnType is null)) + yield return n.ReturnType; + yield return n.Body; + yield break; + case IAbstractMethodDeclarationSyntax n: + yield return n.SelfParameter; + foreach(var child in n.Parameters) + yield return child; + if(!(n.ReturnType is null)) + yield return n.ReturnType; + yield break; + case IConcreteMethodDeclarationSyntax n: + yield return n.SelfParameter; + foreach(var child in n.Parameters) + yield return child; + if(!(n.ReturnType is null)) + yield return n.ReturnType; + yield return n.Body; + yield break; + case IConstructorDeclarationSyntax n: + yield return n.ImplicitSelfParameter; + foreach(var child in n.Parameters) + yield return child; + yield return n.Body; + yield break; + case IFieldDeclarationSyntax n: + yield return n.Type; + if(!(n.Initializer is null)) + yield return n.Initializer; + yield break; + case IAssociatedFunctionDeclarationSyntax n: + foreach(var child in n.Parameters) + yield return child; + if(!(n.ReturnType is null)) + yield return n.ReturnType; + yield return n.Body; + yield break; + case INamedParameterSyntax n: + yield return n.Type; + if(!(n.DefaultValue is null)) + yield return n.DefaultValue; + yield break; + case ISelfParameterSyntax n: + yield break; + case IFieldParameterSyntax n: + if(!(n.DefaultValue is null)) + yield return n.DefaultValue; + yield break; + case IArgumentSyntax n: + yield return n.Expression; + yield break; + case IBodySyntax n: + foreach(var child in n.Statements) + yield return child; + yield break; + case ITypeNameSyntax n: + yield break; + case IOptionalTypeSyntax n: + yield return n.Referent; + yield break; + case ICapabilityTypeSyntax n: + yield return n.ReferentType; + yield break; + case IResultStatementSyntax n: + yield return n.Expression; + yield break; + case IVariableDeclarationStatementSyntax n: + if(!(n.Type is null)) + yield return n.Type; + if(!(n.Initializer is null)) + yield return n.Initializer; + yield break; + case IExpressionStatementSyntax n: + yield return n.Expression; + yield break; + case IBlockExpressionSyntax n: + foreach(var child in n.Statements) + yield return child; + yield break; + case INewObjectExpressionSyntax n: + yield return n.Type; + foreach(var child in n.Arguments) + yield return child; + yield break; + case IUnsafeExpressionSyntax n: + yield return n.Expression; + yield break; + case IBoolLiteralExpressionSyntax n: + yield break; + case IIntegerLiteralExpressionSyntax n: + yield break; + case INoneLiteralExpressionSyntax n: + yield break; + case IStringLiteralExpressionSyntax n: + yield break; + case IAssignmentExpressionSyntax n: + yield return n.LeftOperand; + yield return n.RightOperand; + yield break; + case IBinaryOperatorExpressionSyntax n: + yield return n.LeftOperand; + yield return n.RightOperand; + yield break; + case IUnaryOperatorExpressionSyntax n: + yield return n.Operand; + yield break; + case IIfExpressionSyntax n: + yield return n.Condition; + yield return n.ThenBlock; + if(!(n.ElseClause is null)) + yield return n.ElseClause; + yield break; + case ILoopExpressionSyntax n: + yield return n.Block; + yield break; + case IWhileExpressionSyntax n: + yield return n.Condition; + yield return n.Block; + yield break; + case IForeachExpressionSyntax n: + yield return n.InExpression; + if(!(n.Type is null)) + yield return n.Type; + yield return n.Block; + yield break; + case IBreakExpressionSyntax n: + if(!(n.Value is null)) + yield return n.Value; + yield break; + case INextExpressionSyntax n: + yield break; + case IReturnExpressionSyntax n: + if(!(n.Value is null)) + yield return n.Value; + yield break; + case IImplicitImmutabilityConversionExpressionSyntax n: + yield return n.Expression; + yield break; + case IImplicitNoneConversionExpressionSyntax n: + yield return n.Expression; + yield break; + case IImplicitNumericConversionExpressionSyntax n: + yield return n.Expression; + yield break; + case IImplicitOptionalConversionExpressionSyntax n: + yield return n.Expression; + yield break; + case IUnqualifiedInvocationExpressionSyntax n: + foreach(var child in n.Arguments) + yield return child; + yield break; + case IQualifiedInvocationExpressionSyntax n: + yield return n.Context; + foreach(var child in n.Arguments) + yield return child; + yield break; + case INameExpressionSyntax n: + yield break; + case ISelfExpressionSyntax n: + yield break; + case IQualifiedNameExpressionSyntax n: + yield return n.Context; + yield return n.Field; + yield break; + case IMutateExpressionSyntax n: + yield return n.Referent; + yield break; + case IMoveExpressionSyntax n: + yield return n.Referent; + yield break; + case IShareExpressionSyntax n: + yield return n.Referent; + yield break; + } + } + } +} diff --git a/Compiler.CST/SyntaxTree.tree b/Compiler.CST/SyntaxTree.tree new file mode 100644 index 00000000..d26bf15d --- /dev/null +++ b/Compiler.CST/SyntaxTree.tree @@ -0,0 +1,133 @@ +◊namespace Azoth.Tools.Bootstrap.Compiler.CST; +◊walkers_namespace Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +◊base 'ISyntax'; +◊prefix I; +◊suffix Syntax; +◊list FixedList; +◊using System.Numerics; +◊using Azoth.Tools.Bootstrap.Compiler.Core; +◊using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +◊using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +◊using Azoth.Tools.Bootstrap.Compiler.Names; +◊using Azoth.Tools.Bootstrap.Compiler.Symbols; +◊using Azoth.Tools.Bootstrap.Compiler.Tokens; +◊using Azoth.Tools.Bootstrap.Compiler.Types; +◊using Azoth.Tools.Bootstrap.Framework; + +'ISyntax' = Span:'TextSpan'; +// Note: by declaring the AllEntityDeclarations property type with quotes, it avoids the system assuming it must represent a direct child syntax node +CompilationUnit = File:'CodeFile' ImplicitNamespaceName:'NamespaceName' UsingDirectives:UsingDirective* Declarations:NonMemberDeclaration* Diagnostics:'Diagnostic'*; +UsingDirective = Name:'NamespaceName'; + +// ---------- Special Parts +BodyOrBlock = Statements:Statement*; +ElseClause; +BlockOrResult: ElseClause; + +// ---------- Bindings +Binding = IsMutableBinding:'bool' Symbol:'IPromise'; +LocalBinding: Binding = Symbol:'IPromise'; + +// ---------- Declarations +Declaration: 'ISyntax', 'IHasContainingLexicalScope' = File:'CodeFile' Name:'Name'? NameSpan:'TextSpan' Symbol:'IPromise'; +EntityDeclaration: Declaration = AccessModifier:'IAccessModifierToken'?; +InvocableDeclaration: EntityDeclaration = Parameters:ConstructorParameter* Symbol:'IPromise'; +ConcreteInvocableDeclaration: InvocableDeclaration = Body; + +// ---------- Non-Member Declarations +NonMemberDeclaration: Declaration = ContainingNamespaceName:'NamespaceName' Name:'Name'; +// TODO if a compound namespace declaration was actually mutiple declarations, it would simplify other things +NamespaceDeclaration: NonMemberDeclaration = IsGlobalQualified:'bool' DeclaredNames:'NamespaceName' FullName:'NamespaceName' Symbol:'Promise' UsingDirectives:UsingDirective* Declarations:NonMemberDeclaration*; + +// ---------- Non-Member Entity Declarations +NonMemberEntityDeclaration: EntityDeclaration, NonMemberDeclaration = Name:'Name'; +ClassDeclaration: NonMemberEntityDeclaration = MutableModifier:'IMutableKeywordToken'? Name:'Name' Symbol:'AcyclicPromise' Members:MemberDeclaration* DefaultConstructorSymbol:'ConstructorSymbol'?; +FunctionDeclaration: NonMemberEntityDeclaration, ConcreteInvocableDeclaration = Name:'Name' Parameters:NamedParameter* ReturnType:Type? Body Symbol:'AcyclicPromise'; + +// ---------- Member Declarations +// Note: by declaring the DeclaringClass property type with quotes, it avoids the system assuming it must represent a child syntax node +MemberDeclaration: EntityDeclaration = DeclaringClass:'IClassDeclarationSyntax'; +MethodDeclaration: MemberDeclaration, InvocableDeclaration = Name:'Name' SelfParameter Parameters:NamedParameter* ReturnType:Type? Symbol:'AcyclicPromise'; +AbstractMethodDeclaration: MethodDeclaration = SelfParameter Parameters:NamedParameter* ReturnType:Type?; +ConcreteMethodDeclaration: MethodDeclaration, ConcreteInvocableDeclaration = SelfParameter Parameters:NamedParameter* ReturnType:Type? Body; +ConstructorDeclaration: MemberDeclaration, ConcreteInvocableDeclaration = ImplicitSelfParameter:SelfParameter Parameters:ConstructorParameter* Body Symbol:'AcyclicPromise'; +FieldDeclaration: MemberDeclaration, Binding = Name:'Name' Type Symbol:'AcyclicPromise' &Initializer:Expression?; +AssociatedFunctionDeclaration: MemberDeclaration, ConcreteInvocableDeclaration = Name:'Name' Parameters:NamedParameter* ReturnType:Type? Body Symbol:'AcyclicPromise'; + +// ---------- Parameters -------------- +Parameter = Name:'Name'? DataType:'IPromise' Unused:'bool'; +/// A parameter that can be declared in a constructor +ConstructorParameter: Parameter; +/// A parameter that creates a binding, i.e. a named or self parameter +BindingParameter: Parameter, Binding; +NamedParameter: Parameter, ConstructorParameter, BindingParameter, LocalBinding = Name:'Name' DeclarationNumber:'Promise' Type Symbol:'Promise' DefaultValue:Expression?; +SelfParameter: Parameter, BindingParameter = MutableSelf:'bool' Symbol:'Promise'; +FieldParameter: Parameter, ConstructorParameter = ReferencedSymbol:'Promise' DefaultValue:Expression?; + + +// ---------- Function Parts +Argument = &Expression; +Body: BodyOrBlock = Statements:BodyStatement*; + +// ---------- Types +Type; +TypeName: Type, 'IHasContainingLexicalScope' = Name:'TypeName' ReferencedSymbol:'Promise'; +OptionalType: Type = Referent:Type; +CapabilityType: Type = ReferentType:Type Capability:'ReferenceCapability'; + +// ---------- Statements +Statement; +ResultStatement: Statement, BlockOrResult = &Expression; +BodyStatement: Statement; +VariableDeclarationStatement: BodyStatement, LocalBinding = NameSpan:'TextSpan' Name:'Name' DeclarationNumber:'Promise' Type? InferMutableType:'bool' Symbol:'Promise' &Initializer:Expression?; +ExpressionStatement: BodyStatement = &Expression; + +// ---------- Expressions +Expression; +AssignableExpression: Expression; +BlockExpression: Expression, BlockOrResult, BodyOrBlock = Statements:Statement*; +NewObjectExpression: Expression = Type:TypeName ConstructorName:'Name'? ConstructorNameSpan:'TextSpan'? Arguments:Argument* ReferencedSymbol:'Promise'; +UnsafeExpression: Expression = &Expression; + +// ---------- Literal Expressions +LiteralExpression: Expression; +BoolLiteralExpression: LiteralExpression = Value:'bool'; +IntegerLiteralExpression: LiteralExpression = Value:'BigInteger'; +NoneLiteralExpression: LiteralExpression; +StringLiteralExpression: LiteralExpression = Value:'string'; + +// ---------- Operator Expressions +AssignmentExpression: Expression = &LeftOperand:AssignableExpression Operator:'AssignmentOperator' &RightOperand:Expression; +BinaryOperatorExpression: Expression = &LeftOperand:Expression Operator:'BinaryOperator' &RightOperand:Expression; +UnaryOperatorExpression: Expression = Fixity:'UnaryOperatorFixity' Operator:'UnaryOperator' &Operand:Expression; + +// ---------- Control Flow Expressions +IfExpression: Expression, ElseClause = &Condition:Expression ThenBlock:BlockOrResult ElseClause?; +LoopExpression: Expression = Block:BlockExpression; +WhileExpression: Expression = &Condition:Expression Block:BlockExpression; +ForeachExpression: Expression, LocalBinding = VariableName:'Name' DeclarationNumber:'Promise' &InExpression:Expression Type? Symbol:'Promise' Block:BlockExpression; +BreakExpression: Expression = &Value:Expression?; +NextExpression: Expression; +ReturnExpression: Expression = &Value:Expression?; + +// ---------- Implicit Conversion Expressions +ImplicitConversionExpression: Expression = Expression DataType:'DataType'; +ImplicitImmutabilityConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'ObjectType'; +ImplicitNoneConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'OptionalType'; +ImplicitNumericConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'NumericType'; +ImplicitOptionalConversionExpression: ImplicitConversionExpression = Expression ConvertToType:'OptionalType'; + +// ---------- Invocation Expressions +InvocationExpression: Expression = InvokedName:'Name' InvokedNameSpan:'TextSpan' Arguments:Argument* ReferencedSymbol:'IPromise'; +UnqualifiedInvocationExpression: InvocationExpression, 'IHasContainingLexicalScope' = Namespace:'NamespaceName' Arguments:Argument* ReferencedSymbol:'Promise'; +// TODO in the CST, we don't know that this is a method invocation, it is just a qualified invocation (not even member because namespaces don't have members) +// TODO remove IHasContainingLexicalScope when not needed because of conversion to FunctionInvocationExpression +QualifiedInvocationExpression: InvocationExpression, 'IHasContainingLexicalScope' = &Context:Expression Arguments:Argument* ReferencedSymbol:'Promise'; + +// ---------- Variable Expressions +NameExpression: AssignableExpression, 'IHasContainingLexicalScope' = Name:'Name'? ReferencedSymbol:'Promise'; +SelfExpression: Expression = IsImplicit:'bool' ReferencedSymbol:'Promise'; +QualifiedNameExpression: AssignableExpression = &Context:Expression AccessOperator:'AccessOperator' Field:NameExpression ReferencedSymbol:'IPromise'; +MutateExpression: Expression = &Referent:Expression ReferencedSymbol:'Promise'; +MoveExpression: Expression = &Referent:Expression ReferencedSymbol:'Promise'; +ShareExpression: Expression = &Referent:Expression ReferencedSymbol:'Promise'; diff --git a/Compiler.CST/SyntaxTree.tree.cs b/Compiler.CST/SyntaxTree.tree.cs new file mode 100644 index 00000000..3f876d43 --- /dev/null +++ b/Compiler.CST/SyntaxTree.tree.cs @@ -0,0 +1,574 @@ +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.CST +{ + [Closed( + typeof(ICompilationUnitSyntax), + typeof(IUsingDirectiveSyntax), + typeof(IBodyOrBlockSyntax), + typeof(IElseClauseSyntax), + typeof(IBindingSyntax), + typeof(IDeclarationSyntax), + typeof(IParameterSyntax), + typeof(IArgumentSyntax), + typeof(ITypeSyntax), + typeof(IStatementSyntax), + typeof(IExpressionSyntax))] + public partial interface ISyntax + { + TextSpan Span { get; } + } + + public partial interface ICompilationUnitSyntax : ISyntax + { + CodeFile File { get; } + NamespaceName ImplicitNamespaceName { get; } + FixedList UsingDirectives { get; } + FixedList Declarations { get; } + FixedList Diagnostics { get; } + } + + public partial interface IUsingDirectiveSyntax : ISyntax + { + NamespaceName Name { get; } + } + + [Closed( + typeof(IBodySyntax), + typeof(IBlockExpressionSyntax))] + public partial interface IBodyOrBlockSyntax : ISyntax + { + FixedList Statements { get; } + } + + [Closed( + typeof(IBlockOrResultSyntax), + typeof(IIfExpressionSyntax))] + public partial interface IElseClauseSyntax : ISyntax + { + } + + [Closed( + typeof(IResultStatementSyntax), + typeof(IBlockExpressionSyntax))] + public partial interface IBlockOrResultSyntax : IElseClauseSyntax + { + } + + [Closed( + typeof(ILocalBindingSyntax), + typeof(IFieldDeclarationSyntax), + typeof(IBindingParameterSyntax))] + public partial interface IBindingSyntax : ISyntax + { + bool IsMutableBinding { get; } + IPromise Symbol { get; } + } + + [Closed( + typeof(INamedParameterSyntax), + typeof(IVariableDeclarationStatementSyntax), + typeof(IForeachExpressionSyntax))] + public partial interface ILocalBindingSyntax : IBindingSyntax + { + new IPromise Symbol { get; } + } + + [Closed( + typeof(IEntityDeclarationSyntax), + typeof(INonMemberDeclarationSyntax))] + public partial interface IDeclarationSyntax : ISyntax, IHasContainingLexicalScope + { + CodeFile File { get; } + Name? Name { get; } + TextSpan NameSpan { get; } + IPromise Symbol { get; } + } + + [Closed( + typeof(IInvocableDeclarationSyntax), + typeof(INonMemberEntityDeclarationSyntax), + typeof(IMemberDeclarationSyntax))] + public partial interface IEntityDeclarationSyntax : IDeclarationSyntax + { + IAccessModifierToken? AccessModifier { get; } + } + + [Closed( + typeof(IConcreteInvocableDeclarationSyntax), + typeof(IMethodDeclarationSyntax))] + public partial interface IInvocableDeclarationSyntax : IEntityDeclarationSyntax + { + FixedList Parameters { get; } + new IPromise Symbol { get; } + } + + [Closed( + typeof(IFunctionDeclarationSyntax), + typeof(IConcreteMethodDeclarationSyntax), + typeof(IConstructorDeclarationSyntax), + typeof(IAssociatedFunctionDeclarationSyntax))] + public partial interface IConcreteInvocableDeclarationSyntax : IInvocableDeclarationSyntax + { + IBodySyntax Body { get; } + } + + [Closed( + typeof(INamespaceDeclarationSyntax), + typeof(INonMemberEntityDeclarationSyntax))] + public partial interface INonMemberDeclarationSyntax : IDeclarationSyntax + { + NamespaceName ContainingNamespaceName { get; } + new Name Name { get; } + } + + public partial interface INamespaceDeclarationSyntax : INonMemberDeclarationSyntax + { + bool IsGlobalQualified { get; } + NamespaceName DeclaredNames { get; } + NamespaceName FullName { get; } + new Promise Symbol { get; } + FixedList UsingDirectives { get; } + FixedList Declarations { get; } + } + + [Closed( + typeof(IClassDeclarationSyntax), + typeof(IFunctionDeclarationSyntax))] + public partial interface INonMemberEntityDeclarationSyntax : IEntityDeclarationSyntax, INonMemberDeclarationSyntax + { + new Name Name { get; } + } + + public partial interface IClassDeclarationSyntax : INonMemberEntityDeclarationSyntax + { + IMutableKeywordToken? MutableModifier { get; } + new AcyclicPromise Symbol { get; } + FixedList Members { get; } + ConstructorSymbol? DefaultConstructorSymbol { get; } + } + + public partial interface IFunctionDeclarationSyntax : INonMemberEntityDeclarationSyntax, IConcreteInvocableDeclarationSyntax + { + new Name Name { get; } + new FixedList Parameters { get; } + ITypeSyntax? ReturnType { get; } + new AcyclicPromise Symbol { get; } + } + + [Closed( + typeof(IMethodDeclarationSyntax), + typeof(IConstructorDeclarationSyntax), + typeof(IFieldDeclarationSyntax), + typeof(IAssociatedFunctionDeclarationSyntax))] + public partial interface IMemberDeclarationSyntax : IEntityDeclarationSyntax + { + IClassDeclarationSyntax DeclaringClass { get; } + } + + [Closed( + typeof(IAbstractMethodDeclarationSyntax), + typeof(IConcreteMethodDeclarationSyntax))] + public partial interface IMethodDeclarationSyntax : IMemberDeclarationSyntax, IInvocableDeclarationSyntax + { + new Name Name { get; } + ISelfParameterSyntax SelfParameter { get; } + new FixedList Parameters { get; } + ITypeSyntax? ReturnType { get; } + new AcyclicPromise Symbol { get; } + } + + public partial interface IAbstractMethodDeclarationSyntax : IMethodDeclarationSyntax + { + } + + public partial interface IConcreteMethodDeclarationSyntax : IMethodDeclarationSyntax, IConcreteInvocableDeclarationSyntax + { + new FixedList Parameters { get; } + } + + public partial interface IConstructorDeclarationSyntax : IMemberDeclarationSyntax, IConcreteInvocableDeclarationSyntax + { + ISelfParameterSyntax ImplicitSelfParameter { get; } + new AcyclicPromise Symbol { get; } + } + + public partial interface IFieldDeclarationSyntax : IMemberDeclarationSyntax, IBindingSyntax + { + new Name Name { get; } + ITypeSyntax Type { get; } + new AcyclicPromise Symbol { get; } + [DisallowNull] ref IExpressionSyntax? Initializer { get; } + } + + public partial interface IAssociatedFunctionDeclarationSyntax : IMemberDeclarationSyntax, IConcreteInvocableDeclarationSyntax + { + new Name Name { get; } + new FixedList Parameters { get; } + ITypeSyntax? ReturnType { get; } + new AcyclicPromise Symbol { get; } + } + + [Closed( + typeof(IConstructorParameterSyntax), + typeof(IBindingParameterSyntax), + typeof(INamedParameterSyntax), + typeof(ISelfParameterSyntax), + typeof(IFieldParameterSyntax))] + public partial interface IParameterSyntax : ISyntax + { + Name? Name { get; } + IPromise DataType { get; } + bool Unused { get; } + } + + [Closed( + typeof(INamedParameterSyntax), + typeof(IFieldParameterSyntax))] + public partial interface IConstructorParameterSyntax : IParameterSyntax + { + } + + [Closed( + typeof(INamedParameterSyntax), + typeof(ISelfParameterSyntax))] + public partial interface IBindingParameterSyntax : IParameterSyntax, IBindingSyntax + { + } + + public partial interface INamedParameterSyntax : IParameterSyntax, IConstructorParameterSyntax, IBindingParameterSyntax, ILocalBindingSyntax + { + new Name Name { get; } + Promise DeclarationNumber { get; } + ITypeSyntax Type { get; } + new Promise Symbol { get; } + IExpressionSyntax? DefaultValue { get; } + } + + public partial interface ISelfParameterSyntax : IParameterSyntax, IBindingParameterSyntax + { + bool MutableSelf { get; } + new Promise Symbol { get; } + } + + public partial interface IFieldParameterSyntax : IParameterSyntax, IConstructorParameterSyntax + { + Promise ReferencedSymbol { get; } + IExpressionSyntax? DefaultValue { get; } + } + + public partial interface IArgumentSyntax : ISyntax + { + ref IExpressionSyntax Expression { get; } + } + + public partial interface IBodySyntax : IBodyOrBlockSyntax + { + new FixedList Statements { get; } + } + + [Closed( + typeof(ITypeNameSyntax), + typeof(IOptionalTypeSyntax), + typeof(ICapabilityTypeSyntax))] + public partial interface ITypeSyntax : ISyntax + { + } + + public partial interface ITypeNameSyntax : ITypeSyntax, IHasContainingLexicalScope + { + TypeName Name { get; } + Promise ReferencedSymbol { get; } + } + + public partial interface IOptionalTypeSyntax : ITypeSyntax + { + ITypeSyntax Referent { get; } + } + + public partial interface ICapabilityTypeSyntax : ITypeSyntax + { + ITypeSyntax ReferentType { get; } + ReferenceCapability Capability { get; } + } + + [Closed( + typeof(IResultStatementSyntax), + typeof(IBodyStatementSyntax))] + public partial interface IStatementSyntax : ISyntax + { + } + + public partial interface IResultStatementSyntax : IStatementSyntax, IBlockOrResultSyntax + { + ref IExpressionSyntax Expression { get; } + } + + [Closed( + typeof(IVariableDeclarationStatementSyntax), + typeof(IExpressionStatementSyntax))] + public partial interface IBodyStatementSyntax : IStatementSyntax + { + } + + public partial interface IVariableDeclarationStatementSyntax : IBodyStatementSyntax, ILocalBindingSyntax + { + TextSpan NameSpan { get; } + Name Name { get; } + Promise DeclarationNumber { get; } + ITypeSyntax? Type { get; } + bool InferMutableType { get; } + new Promise Symbol { get; } + [DisallowNull] ref IExpressionSyntax? Initializer { get; } + } + + public partial interface IExpressionStatementSyntax : IBodyStatementSyntax + { + ref IExpressionSyntax Expression { get; } + } + + [Closed( + typeof(IAssignableExpressionSyntax), + typeof(IBlockExpressionSyntax), + typeof(INewObjectExpressionSyntax), + typeof(IUnsafeExpressionSyntax), + typeof(ILiteralExpressionSyntax), + typeof(IAssignmentExpressionSyntax), + typeof(IBinaryOperatorExpressionSyntax), + typeof(IUnaryOperatorExpressionSyntax), + typeof(IIfExpressionSyntax), + typeof(ILoopExpressionSyntax), + typeof(IWhileExpressionSyntax), + typeof(IForeachExpressionSyntax), + typeof(IBreakExpressionSyntax), + typeof(INextExpressionSyntax), + typeof(IReturnExpressionSyntax), + typeof(IImplicitConversionExpressionSyntax), + typeof(IInvocationExpressionSyntax), + typeof(ISelfExpressionSyntax), + typeof(IMutateExpressionSyntax), + typeof(IMoveExpressionSyntax), + typeof(IShareExpressionSyntax))] + public partial interface IExpressionSyntax : ISyntax + { + } + + [Closed( + typeof(INameExpressionSyntax), + typeof(IQualifiedNameExpressionSyntax))] + public partial interface IAssignableExpressionSyntax : IExpressionSyntax + { + } + + public partial interface IBlockExpressionSyntax : IExpressionSyntax, IBlockOrResultSyntax, IBodyOrBlockSyntax + { + } + + public partial interface INewObjectExpressionSyntax : IExpressionSyntax + { + ITypeNameSyntax Type { get; } + Name? ConstructorName { get; } + TextSpan? ConstructorNameSpan { get; } + FixedList Arguments { get; } + Promise ReferencedSymbol { get; } + } + + public partial interface IUnsafeExpressionSyntax : IExpressionSyntax + { + ref IExpressionSyntax Expression { get; } + } + + [Closed( + typeof(IBoolLiteralExpressionSyntax), + typeof(IIntegerLiteralExpressionSyntax), + typeof(INoneLiteralExpressionSyntax), + typeof(IStringLiteralExpressionSyntax))] + public partial interface ILiteralExpressionSyntax : IExpressionSyntax + { + } + + public partial interface IBoolLiteralExpressionSyntax : ILiteralExpressionSyntax + { + bool Value { get; } + } + + public partial interface IIntegerLiteralExpressionSyntax : ILiteralExpressionSyntax + { + BigInteger Value { get; } + } + + public partial interface INoneLiteralExpressionSyntax : ILiteralExpressionSyntax + { + } + + public partial interface IStringLiteralExpressionSyntax : ILiteralExpressionSyntax + { + string Value { get; } + } + + public partial interface IAssignmentExpressionSyntax : IExpressionSyntax + { + ref IAssignableExpressionSyntax LeftOperand { get; } + AssignmentOperator Operator { get; } + ref IExpressionSyntax RightOperand { get; } + } + + public partial interface IBinaryOperatorExpressionSyntax : IExpressionSyntax + { + ref IExpressionSyntax LeftOperand { get; } + BinaryOperator Operator { get; } + ref IExpressionSyntax RightOperand { get; } + } + + public partial interface IUnaryOperatorExpressionSyntax : IExpressionSyntax + { + UnaryOperatorFixity Fixity { get; } + UnaryOperator Operator { get; } + ref IExpressionSyntax Operand { get; } + } + + public partial interface IIfExpressionSyntax : IExpressionSyntax, IElseClauseSyntax + { + ref IExpressionSyntax Condition { get; } + IBlockOrResultSyntax ThenBlock { get; } + IElseClauseSyntax? ElseClause { get; } + } + + public partial interface ILoopExpressionSyntax : IExpressionSyntax + { + IBlockExpressionSyntax Block { get; } + } + + public partial interface IWhileExpressionSyntax : IExpressionSyntax + { + ref IExpressionSyntax Condition { get; } + IBlockExpressionSyntax Block { get; } + } + + public partial interface IForeachExpressionSyntax : IExpressionSyntax, ILocalBindingSyntax + { + Name VariableName { get; } + Promise DeclarationNumber { get; } + ref IExpressionSyntax InExpression { get; } + ITypeSyntax? Type { get; } + new Promise Symbol { get; } + IBlockExpressionSyntax Block { get; } + } + + public partial interface IBreakExpressionSyntax : IExpressionSyntax + { + [DisallowNull] ref IExpressionSyntax? Value { get; } + } + + public partial interface INextExpressionSyntax : IExpressionSyntax + { + } + + public partial interface IReturnExpressionSyntax : IExpressionSyntax + { + [DisallowNull] ref IExpressionSyntax? Value { get; } + } + + [Closed( + typeof(IImplicitImmutabilityConversionExpressionSyntax), + typeof(IImplicitNoneConversionExpressionSyntax), + typeof(IImplicitNumericConversionExpressionSyntax), + typeof(IImplicitOptionalConversionExpressionSyntax))] + public partial interface IImplicitConversionExpressionSyntax : IExpressionSyntax + { + IExpressionSyntax Expression { get; } + DataType DataType { get; } + } + + public partial interface IImplicitImmutabilityConversionExpressionSyntax : IImplicitConversionExpressionSyntax + { + ObjectType ConvertToType { get; } + } + + public partial interface IImplicitNoneConversionExpressionSyntax : IImplicitConversionExpressionSyntax + { + OptionalType ConvertToType { get; } + } + + public partial interface IImplicitNumericConversionExpressionSyntax : IImplicitConversionExpressionSyntax + { + NumericType ConvertToType { get; } + } + + public partial interface IImplicitOptionalConversionExpressionSyntax : IImplicitConversionExpressionSyntax + { + OptionalType ConvertToType { get; } + } + + [Closed( + typeof(IUnqualifiedInvocationExpressionSyntax), + typeof(IQualifiedInvocationExpressionSyntax))] + public partial interface IInvocationExpressionSyntax : IExpressionSyntax + { + Name InvokedName { get; } + TextSpan InvokedNameSpan { get; } + FixedList Arguments { get; } + IPromise ReferencedSymbol { get; } + } + + public partial interface IUnqualifiedInvocationExpressionSyntax : IInvocationExpressionSyntax, IHasContainingLexicalScope + { + NamespaceName Namespace { get; } + new Promise ReferencedSymbol { get; } + } + + public partial interface IQualifiedInvocationExpressionSyntax : IInvocationExpressionSyntax, IHasContainingLexicalScope + { + ref IExpressionSyntax Context { get; } + new Promise ReferencedSymbol { get; } + } + + public partial interface INameExpressionSyntax : IAssignableExpressionSyntax, IHasContainingLexicalScope + { + Name? Name { get; } + Promise ReferencedSymbol { get; } + } + + public partial interface ISelfExpressionSyntax : IExpressionSyntax + { + bool IsImplicit { get; } + Promise ReferencedSymbol { get; } + } + + public partial interface IQualifiedNameExpressionSyntax : IAssignableExpressionSyntax + { + ref IExpressionSyntax Context { get; } + AccessOperator AccessOperator { get; } + INameExpressionSyntax Field { get; } + IPromise ReferencedSymbol { get; } + } + + public partial interface IMutateExpressionSyntax : IExpressionSyntax + { + ref IExpressionSyntax Referent { get; } + Promise ReferencedSymbol { get; } + } + + public partial interface IMoveExpressionSyntax : IExpressionSyntax + { + ref IExpressionSyntax Referent { get; } + Promise ReferencedSymbol { get; } + } + + public partial interface IShareExpressionSyntax : IExpressionSyntax + { + ref IExpressionSyntax Referent { get; } + Promise ReferencedSymbol { get; } + } + +} diff --git a/Compiler.CST/Walkers/SyntaxWalker.cs b/Compiler.CST/Walkers/SyntaxWalker.cs new file mode 100644 index 00000000..b800e588 --- /dev/null +++ b/Compiler.CST/Walkers/SyntaxWalker.cs @@ -0,0 +1,57 @@ +using System.Diagnostics; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Compiler.CST.Walkers +{ + public abstract class SyntaxWalker + { + [DebuggerHidden] + protected void Walk(ISyntax? syntax, T arg) + { + if (syntax is null) return; + WalkNonNull(syntax, arg); + } + + protected abstract void WalkNonNull(ISyntax syntax, T arg); + + [DebuggerHidden] + protected void WalkChildren(ISyntax syntax, T arg) + { + foreach (var child in syntax.Children()) + WalkNonNull(child, arg); + } + + [DebuggerHidden] + protected void WalkChildrenInReverse(ISyntax syntax, T arg) + { + foreach (var child in syntax.Children().Reverse()) + WalkNonNull(child, arg); + } + } + + public abstract class SyntaxWalker + { + [DebuggerHidden] + protected void Walk(ISyntax? syntax) + { + if (syntax is null) return; + WalkNonNull(syntax); + } + + protected abstract void WalkNonNull(ISyntax syntax); + + [DebuggerHidden] + protected void WalkChildren(ISyntax syntax) + { + foreach (var child in syntax.Children()) + WalkNonNull(child); + } + + [DebuggerHidden] + protected void WalkChildrenInReverse(ISyntax syntax) + { + foreach (var child in syntax.Children().Reverse()) + WalkNonNull(child); + } + } +} diff --git a/Compiler.CodeGen/AssemblyInfo.cs b/Compiler.CodeGen/AssemblyInfo.cs new file mode 100644 index 00000000..ddd4f31b --- /dev/null +++ b/Compiler.CodeGen/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azoth.Tools.Bootstrap.Tests.Unit.Compiler.CodeGen")] diff --git a/Compiler.CodeGen/ChildrenCodeTemplate.cs b/Compiler.CodeGen/ChildrenCodeTemplate.cs new file mode 100644 index 00000000..3d6e20bd --- /dev/null +++ b/Compiler.CodeGen/ChildrenCodeTemplate.cs @@ -0,0 +1,430 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 16.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + + #line 1 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public partial class ChildrenCodeTemplate : ChildrenCodeTemplateBase + { +#line hidden + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("using System.CodeDom.Compiler;\r\nusing System.Collections.Generic;\r\nusing System.D" + + "iagnostics;\r\nusing ExhaustiveMatching;\r\n\r\nnamespace "); + + #line 11 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(grammar.Namespace)); + + #line default + #line hidden + this.Write("\r\n{\r\n [GeneratedCode(\"AzothCompilerCodeGen\", null)]\r\n public static class "); + + #line 14 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(TypeName(grammar.BaseType))); + + #line default + #line hidden + this.Write("Extensions\r\n {\r\n [DebuggerStepThrough]\r\n public static IEnumerab" + + "le<"); + + #line 17 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(TypeName(grammar.BaseType))); + + #line default + #line hidden + this.Write("> Children(this "); + + #line 17 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(TypeName(grammar.BaseType))); + + #line default + #line hidden + this.Write(" node)\r\n {\r\n switch(node)\r\n {\r\n defau" + + "lt:\r\n throw ExhaustiveMatch.Failed(node);\r\n"); + + #line 23 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + foreach(var rule in grammar.Rules.Where(grammar.IsLeaf)) { + + #line default + #line hidden + this.Write(" case "); + + #line 24 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(TypeName(rule.Nonterminal))); + + #line default + #line hidden + this.Write(" n:\r\n"); + + #line 25 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + foreach(var property in rule.Properties.Where(grammar.IsNonTerminal)) { + + #line default + #line hidden + + #line 26 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + if(property.Type.IsList) { + + #line default + #line hidden + this.Write(" foreach(var child in n."); + + #line 27 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(property.Name)); + + #line default + #line hidden + this.Write(")\r\n yield return child;\r\n"); + + #line 29 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + } else if(property.Type.IsOptional) { + + #line default + #line hidden + this.Write(" if(!(n."); + + #line 30 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(property.Name)); + + #line default + #line hidden + this.Write(" is null))\r\n yield return n."); + + #line 31 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(property.Name)); + + #line default + #line hidden + this.Write(";\r\n"); + + #line 32 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + } else { + + #line default + #line hidden + this.Write(" yield return n."); + + #line 33 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(property.Name)); + + #line default + #line hidden + this.Write(";\r\n"); + + #line 34 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + } + + #line default + #line hidden + + #line 35 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + } + + #line default + #line hidden + this.Write(" yield break;\r\n"); + + #line 37 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\ChildrenCodeTemplate.tt" + } + + #line default + #line hidden + this.Write(" }\r\n }\r\n }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + + #line default + #line hidden + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public class ChildrenCodeTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Compiler.CodeGen/ChildrenCodeTemplate.custom.cs b/Compiler.CodeGen/ChildrenCodeTemplate.custom.cs new file mode 100644 index 00000000..6075364d --- /dev/null +++ b/Compiler.CodeGen/ChildrenCodeTemplate.custom.cs @@ -0,0 +1,27 @@ +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.CodeGen.Config; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + public partial class ChildrenCodeTemplate + { + private readonly Grammar grammar; + + public ChildrenCodeTemplate(Grammar grammar) + { + this.grammar = grammar; + } + + private string TypeName(GrammarSymbol symbol) + { + if (symbol.IsQuoted) + return symbol.Text; + + // If it is a nonterminal, then transform the name + if (grammar.Rules.Any(r => r.Nonterminal == symbol)) + return $"{grammar.Prefix}{symbol.Text}{grammar.Suffix}"; + + return symbol.Text; + } + } +} diff --git a/Compiler.CodeGen/ChildrenCodeTemplate.tt b/Compiler.CodeGen/ChildrenCodeTemplate.tt new file mode 100644 index 00000000..444a6b07 --- /dev/null +++ b/Compiler.CodeGen/ChildrenCodeTemplate.tt @@ -0,0 +1,41 @@ +<#@ template language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Diagnostics; +using ExhaustiveMatching; + +namespace <#=grammar.Namespace #> +{ + [GeneratedCode("AzothCompilerCodeGen", null)] + public static class <#=TypeName(grammar.BaseType)#>Extensions + { + [DebuggerStepThrough] + public static IEnumerable<<#=TypeName(grammar.BaseType)#>> Children(this <#=TypeName(grammar.BaseType)#> node) + { + switch(node) + { + default: + throw ExhaustiveMatch.Failed(node); +<# foreach(var rule in grammar.Rules.Where(grammar.IsLeaf)) { #> + case <#=TypeName(rule.Nonterminal) #> n: +<# foreach(var property in rule.Properties.Where(grammar.IsNonTerminal)) { #> +<# if(property.Type.IsList) { #> + foreach(var child in n.<#=property.Name #>) + yield return child; +<# } else if(property.Type.IsOptional) { #> + if(!(n.<#=property.Name #> is null)) + yield return n.<#=property.Name #>; +<# } else { #> + yield return n.<#=property.Name #>; +<# } #> +<# } #> + yield break; +<# } #> + } + } + } +} diff --git a/Compiler.CodeGen/CodeBuilder.cs b/Compiler.CodeGen/CodeBuilder.cs new file mode 100644 index 00000000..136ea8d4 --- /dev/null +++ b/Compiler.CodeGen/CodeBuilder.cs @@ -0,0 +1,19 @@ +using Azoth.Tools.Bootstrap.Compiler.CodeGen.Config; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + public static class CodeBuilder + { + public static string GenerateTree(Grammar grammar) + { + var template = new TreeCodeTemplate(grammar); + return template.TransformText(); + } + + public static string GenerateChildren(Grammar grammar) + { + var template = new ChildrenCodeTemplate(grammar); + return template.TransformText(); + } + } +} diff --git a/Compiler.CodeGen/Compiler.CodeGen.csproj b/Compiler.CodeGen/Compiler.CodeGen.csproj new file mode 100644 index 00000000..ab3e618a --- /dev/null +++ b/Compiler.CodeGen/Compiler.CodeGen.csproj @@ -0,0 +1,62 @@ + + + + Exe + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.CodeGen + CompilerCodeGen + enable + + + + DEBUG;TRACE + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + TextTemplatingFilePreprocessor + TreeCodeTemplate.cs + + + + TextTemplatingFilePreprocessor + ChildrenCodeTemplate.cs + + + + + + + + + + True + True + TreeCodeTemplate.tt + + + True + True + ChildrenCodeTemplate.tt + + + + diff --git a/Compiler.CodeGen/Config/Grammar.cs b/Compiler.CodeGen/Config/Grammar.cs new file mode 100644 index 00000000..7938fd03 --- /dev/null +++ b/Compiler.CodeGen/Config/Grammar.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen.Config +{ + public class Grammar + { + public string? Namespace { get; } + public GrammarSymbol? BaseType { get; } + public string Prefix { get; } + public string Suffix { get; } + public string ListType { get; } + public FixedList UsingNamespaces { get; } + public FixedList Rules { get; } + + public Grammar( + string? @namespace, + GrammarSymbol? baseType, + string prefix, + string suffix, + string listType, + IEnumerable usingNamespaces, + IEnumerable rules) + { + Namespace = @namespace; + BaseType = baseType; + Prefix = prefix; + Suffix = suffix; + ListType = listType; + UsingNamespaces = usingNamespaces.ToFixedList(); + Rules = rules.ToFixedList(); + } + + public void Validate() + { + foreach (var rule in Rules.Where(IsLeaf)) + { + var baseNonTerminalPropertyNames + = AncestorRules(rule) + .SelectMany(r => r.Properties) + .Where(IsNonTerminal).Select(p => p.Name); + var nonTerminalPropertyNames = rule.Properties.Where(IsNonTerminal).Select(p => p.Name); + var missingProperties = baseNonTerminalPropertyNames.Except(nonTerminalPropertyNames).ToList(); + if (missingProperties.Any()) + throw new ValidationException($"Rule for {rule.Nonterminal} is missing inherited properties: {string.Join(", ", missingProperties)}. Can't determine order to visit children."); + } + } + + public IEnumerable ParentRules(GrammarRule rule) + { + return Rules.Where(r => rule.Parents.Contains(r.Nonterminal)); + } + + public IEnumerable AncestorRules(GrammarRule rule) + { + var parents = ParentRules(rule).ToList(); + return parents.Concat(parents.SelectMany(AncestorRules)).Distinct(); + } + + public bool IsLeaf(GrammarRule rule) + { + return !Rules.Any(r => r.Parents.Contains(rule.Nonterminal)); + } + + public bool IsNonTerminal(GrammarProperty property) + { + return Rules.Any(r => r.Nonterminal == property.Type.Symbol); + } + } +} diff --git a/Compiler.CodeGen/Config/GrammarProperty.cs b/Compiler.CodeGen/Config/GrammarProperty.cs new file mode 100644 index 00000000..c9e7f46d --- /dev/null +++ b/Compiler.CodeGen/Config/GrammarProperty.cs @@ -0,0 +1,14 @@ +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen.Config +{ + public class GrammarProperty + { + public string Name { get; } + public GrammarType Type { get; } + + public GrammarProperty(string name, GrammarType type) + { + Name = name; + Type = type; + } + } +} diff --git a/Compiler.CodeGen/Config/GrammarRule.cs b/Compiler.CodeGen/Config/GrammarRule.cs new file mode 100644 index 00000000..4bf0eccb --- /dev/null +++ b/Compiler.CodeGen/Config/GrammarRule.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen.Config +{ + public class GrammarRule + { + public GrammarSymbol Nonterminal { get; } + public FixedList Parents { get; } + + public FixedList Properties { get; } + + public GrammarRule( + GrammarSymbol nonterminal, + IEnumerable parents, + IEnumerable properties) + { + Nonterminal = nonterminal; + Parents = parents.ToFixedList(); + Properties = properties.ToFixedList(); + } + } +} diff --git a/Compiler.CodeGen/Config/GrammarSymbol.cs b/Compiler.CodeGen/Config/GrammarSymbol.cs new file mode 100644 index 00000000..3e4e22dc --- /dev/null +++ b/Compiler.CodeGen/Config/GrammarSymbol.cs @@ -0,0 +1,53 @@ +using System; +using System.Diagnostics; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen.Config +{ + [DebuggerDisplay("{" + nameof(ToString) + ",nq}")] + public sealed class GrammarSymbol : IEquatable + { + public string Text { get; } + public bool IsQuoted { get; } + + public GrammarSymbol(string text, bool isQuoted = false) + { + Text = text; + IsQuoted = isQuoted; + } + + public bool Equals(GrammarSymbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Text == other.Text + && IsQuoted == other.IsQuoted; + } + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is GrammarSymbol other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Text, IsQuoted); + } + + public static bool operator ==(GrammarSymbol? left, GrammarSymbol? right) + { + return Equals(left, right); + } + + public static bool operator !=(GrammarSymbol? left, GrammarSymbol? right) + { + return !Equals(left, right); + } + + public override string ToString() + { + return IsQuoted ? $"'{Text}'" : Text; + } + } +} diff --git a/Compiler.CodeGen/Config/GrammarType.cs b/Compiler.CodeGen/Config/GrammarType.cs new file mode 100644 index 00000000..63cef162 --- /dev/null +++ b/Compiler.CodeGen/Config/GrammarType.cs @@ -0,0 +1,63 @@ +using System; +using System.Diagnostics; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen.Config +{ + [DebuggerDisplay("{" + nameof(ToString) + ",nq}")] + public sealed class GrammarType : IEquatable + { + public GrammarSymbol Symbol { get; } + public bool IsRef { get; } + public bool IsOptional { get; } + public bool IsList { get; } + + public GrammarType(GrammarSymbol symbol, bool isRef, bool isOptional, bool isList) + { + Symbol = symbol; + IsOptional = isOptional; + IsList = isList; + IsRef = isRef; + } + + public bool Equals(GrammarType? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Symbol.Equals(other.Symbol) + && IsRef == other.IsRef + && IsOptional == other.IsOptional + && IsList == other.IsList; + } + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is GrammarType other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Symbol, IsRef, IsOptional, IsList); + } + + public static bool operator ==(GrammarType? left, GrammarType? right) + { + return Equals(left, right); + } + + public static bool operator !=(GrammarType? left, GrammarType? right) + { + return !Equals(left, right); + } + + public override string ToString() + { + var type = Symbol.ToString(); + if (IsRef) type = "&" + type; + if (IsOptional) type += "?"; + if (IsList) type += "*"; + return type; + } + } +} diff --git a/Compiler.CodeGen/NamespaceComparer.cs b/Compiler.CodeGen/NamespaceComparer.cs new file mode 100644 index 00000000..cb99948d --- /dev/null +++ b/Compiler.CodeGen/NamespaceComparer.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + public class NamespaceComparer : IComparer + { + #region Singleton + public static NamespaceComparer Instance { get; } = new NamespaceComparer(); + + private NamespaceComparer() { } + #endregion + + public int Compare(string? x, string? y) + { + var xParts = x!.Split('.'); + var yParts = y!.Split('.'); + + if (xParts[0] == "System" && yParts[0] != "System") return -1; + if (xParts[0] != "System" && yParts[0] == "System") return 1; + + var length = Math.Min(xParts.Length, yParts.Length); + for (int i = 0; i < length; i++) + { + var cmp = string.Compare(xParts[i], yParts[i], StringComparison.InvariantCulture); + if (cmp != 0) return cmp; + } + + return xParts.Length.CompareTo(yParts.Length); + } + } +} diff --git a/Compiler.CodeGen/Parser.cs b/Compiler.CodeGen/Parser.cs new file mode 100644 index 00000000..f5087b0b --- /dev/null +++ b/Compiler.CodeGen/Parser.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.CodeGen.Config; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + internal static class Parser + { + public static Grammar ReadGrammarConfig(string grammar) + { + var lines = new List(); + using (var reader = new StringReader(grammar)) + { + string? line; + while ((line = reader.ReadLine()) != null) + lines.Add(line); + } + + var ns = GetConfig(lines, "namespace"); + var baseType = ParseSymbol(GetConfig(lines, "base")); + var prefix = GetConfig(lines, "prefix") ?? ""; + var suffix = GetConfig(lines, "suffix") ?? ""; + var listType = GetConfig(lines, "list") ?? "List"; + var usingNamespaces = GetUsingNamespaces(lines); + var rules = GetRules(lines).Select(r => DefaultBaseType(r, baseType)); + return new Grammar(ns, baseType, prefix, suffix, listType, usingNamespaces, rules); + } + + private static string? GetConfig(IEnumerable lines, string config) + { + var start = Program.DirectiveMarker + config; + var line = lines.SingleOrDefault(l => l.StartsWith(start, StringComparison.InvariantCulture)); + line = line?.Substring(start.Length); + line = line?.TrimEnd(';'); // TODO error if no semicolon + line = line?.Trim(); + return line; + } + + private static IEnumerable GetUsingNamespaces(IEnumerable lines) + { + const string start = Program.DirectiveMarker + "using"; + lines = lines.Where(l => l.StartsWith(start, StringComparison.InvariantCulture)); + // TODO error if no semicolon + return lines.Select(l => l.Substring(start.Length).TrimEnd(';').Trim()); + } + + private static GrammarRule DefaultBaseType(GrammarRule rule, GrammarSymbol? baseType) + { + if (baseType is null + || rule.Parents.Any() + || rule.Nonterminal == baseType) return rule; + return new GrammarRule(rule.Nonterminal, baseType.YieldValue(), rule.Properties); + } + + private static IEnumerable GetRules(List lines) + { + var ruleLines = lines.Select(l => l.Trim()) + .Where(l => !l.StartsWith(Program.DirectiveMarker, StringComparison.InvariantCulture) + && !l.StartsWith("//", StringComparison.InvariantCulture) + && !string.IsNullOrWhiteSpace(l)) + .Select(l => l.TrimEnd(';')) // TODO error if no semicolon + .ToList()!; + foreach (var ruleLine in ruleLines) + { + var equalSplit = ruleLine.Split('='); + if (equalSplit.Length > 2) + throw new FormatException($"Too many equal signs on line: '{ruleLine}'"); + var declaration = equalSplit[0]; + var (nonterminal, parents) = ParseDeclaration(declaration); + var definition = equalSplit.Length == 2 ? equalSplit[1].Trim() : null; + var properties = ParseDefinition(definition).ToList(); + if (properties.Select(p => p.Name).Distinct().Count() != properties.Count) + throw new FormatException($"Rule for {nonterminal} contains duplicate property definitions"); + + yield return new GrammarRule(nonterminal, parents, properties); + } + } + + private static IEnumerable ParseDefinition(string? definition) + { + if (definition is null) yield break; + + var properties = definition.SplitOrEmpty(' ').Where(v => !string.IsNullOrWhiteSpace(v)); + foreach (var property in properties) + { + var trimmedProperty = property; + + var isRef = trimmedProperty.StartsWith('&'); + trimmedProperty = isRef ? trimmedProperty[1..] : trimmedProperty; + var isList = trimmedProperty.EndsWith('*'); + trimmedProperty = isList ? trimmedProperty[..^1] : trimmedProperty; + var isOptional = trimmedProperty.EndsWith('?'); + trimmedProperty = isOptional ? trimmedProperty[..^1] : trimmedProperty; + var parts = trimmedProperty.Split(':').Select(p => p.Trim()).ToArray(); + + switch (parts.Length) + { + case 1: + { + var name = parts[0]; + var grammarType = new GrammarType(ParseSymbol(name), isRef, isOptional, isList); + yield return new GrammarProperty(name, grammarType); + } + break; + case 2: + { + var name = parts[0]; + var type = parts[1]; + var grammarType = new GrammarType(ParseSymbol(type), isRef, isOptional, isList); + yield return new GrammarProperty(name, grammarType); + } + break; + default: + throw new FormatException($"Too many colons in definition: '{definition}'"); + } + } + } + + private static (GrammarSymbol nonterminal, IEnumerable parents) ParseDeclaration(string declaration) + { + var declarationSplit = declaration.SplitOrEmpty(':'); + if (declarationSplit.Count > 2) throw new FormatException($"Too many colons in declaration: '{declaration}'"); + var nonterminal = ParseSymbol(declarationSplit[0].Trim()); + var parents = declarationSplit.Count == 2 ? declarationSplit[1] : null; + var parentSymbols = ParseParents(parents); + return (nonterminal, parentSymbols); + } + + private static IEnumerable ParseParents(string? parents) + { + if (parents is null) return Enumerable.Empty(); + + return parents + .Split(',') + .Select(p => p.Trim()) + .Select(p => ParseSymbol(p)); + } + + [return: NotNullIfNotNull("symbol")] + private static GrammarSymbol? ParseSymbol(string? symbol) + { + if (symbol is null) return null; + if (symbol.StartsWith('\'') && symbol.EndsWith('\'')) return new GrammarSymbol(symbol[1..^1], true); + return new GrammarSymbol(symbol); + } + } +} diff --git a/Compiler.CodeGen/Program.cs b/Compiler.CodeGen/Program.cs new file mode 100644 index 00000000..1bcff039 --- /dev/null +++ b/Compiler.CodeGen/Program.cs @@ -0,0 +1,65 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + public static class Program + { + public const string DirectiveMarker = "◊"; + + [SuppressMessage("Design", "CA1031:Do not catch general exception types", + Justification = "Main method catching all exceptions to prevent exit")] + public static int Main(string[] args) + { + if (args.Length != 1) + { + Console.WriteLine("Expected exactly one argument: CompilerCodeGen "); + return -1; + } + + try + { + Console.WriteLine("~~~~~~ Compiler Code Generator"); + var inputPath = args[0]; + var treeOutputPath = Path.ChangeExtension(inputPath, ".tree.cs"); + var childrenOutputPath = Path.ChangeExtension(inputPath, ".children.cs"); + Console.WriteLine($"Input: {inputPath}"); + Console.WriteLine($"Tree Output: {treeOutputPath}"); + Console.WriteLine($"Children Output: {childrenOutputPath}"); + + var inputFile = File.ReadAllText(inputPath) + ?? throw new InvalidOperationException("null from reading input file"); + var grammar = Parser.ReadGrammarConfig(inputFile); + + grammar.Validate(); + + var treeCode = CodeBuilder.GenerateTree(grammar); + WriteIfChanged(treeOutputPath, treeCode); + + var walkerCode = CodeBuilder.GenerateChildren(grammar); + WriteIfChanged(childrenOutputPath, walkerCode); + return 0; + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + Console.WriteLine(ex.StackTrace); + return -1; + } + } + + /// + /// Only write code if it has changed so VS doesn't think the file is + /// constantly changing and needs to be recompiled. + /// + private static void WriteIfChanged(string filePath, string code) + { + var previousCode = File.Exists(filePath) + ? File.ReadAllText(filePath, new UTF8Encoding(false, true)) + : null; + if (code != previousCode) File.WriteAllText(filePath, code); + } + } +} diff --git a/Compiler.CodeGen/TreeCodeTemplate.cs b/Compiler.CodeGen/TreeCodeTemplate.cs new file mode 100644 index 00000000..1972d8b7 --- /dev/null +++ b/Compiler.CodeGen/TreeCodeTemplate.cs @@ -0,0 +1,404 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 16.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + + #line 1 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public partial class TreeCodeTemplate : TreeCodeTemplateBase + { +#line hidden + /// + /// Create the template output + /// + public virtual string TransformText() + { + + #line 6 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + foreach(var usingNamespace in OrderedNamespaces()) { + + #line default + #line hidden + this.Write("using "); + + #line 7 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(usingNamespace)); + + #line default + #line hidden + this.Write(";\r\n"); + + #line 8 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + } + + #line default + #line hidden + this.Write("\r\nnamespace "); + + #line 10 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(grammar.Namespace)); + + #line default + #line hidden + this.Write("\r\n{\r\n"); + + #line 12 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + foreach(var rule in grammar.Rules) { + + #line default + #line hidden + + #line 13 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(ClosedType(rule))); + + #line default + #line hidden + this.Write(" public partial interface "); + + #line 13 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(TypeName(rule.Nonterminal))); + + #line default + #line hidden + + #line 13 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(BaseTypes(rule))); + + #line default + #line hidden + this.Write("\r\n {\r\n"); + + #line 15 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + foreach(var property in rule.Properties.Where(p => NeedsDeclared(rule, p))) { + + #line default + #line hidden + this.Write(" "); + + #line 16 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(IsNewDefinition(rule, property) ? "new " : "")); + + #line default + #line hidden + + #line 16 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(TypeName(property.Type))); + + #line default + #line hidden + this.Write(" "); + + #line 16 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(property.Name)); + + #line default + #line hidden + this.Write(" { get; }\r\n"); + + #line 17 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + } + + #line default + #line hidden + this.Write(" }\r\n\r\n"); + + #line 20 "C:\dataFast\azoth-lang\azoth.tools.bootstrap\Compiler.CodeGen\TreeCodeTemplate.tt" + } + + #line default + #line hidden + this.Write("}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + + #line default + #line hidden + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public class TreeCodeTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Compiler.CodeGen/TreeCodeTemplate.custom.cs b/Compiler.CodeGen/TreeCodeTemplate.custom.cs new file mode 100644 index 00000000..5ee9241b --- /dev/null +++ b/Compiler.CodeGen/TreeCodeTemplate.custom.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Azoth.Tools.Bootstrap.Compiler.CodeGen.Config; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.CodeGen +{ + public partial class TreeCodeTemplate + { + private readonly Grammar grammar; + + public TreeCodeTemplate(Grammar grammar) + { + this.grammar = grammar; + } + + private IEnumerable OrderedNamespaces() + { + return grammar.UsingNamespaces + .Append("System.Diagnostics.CodeAnalysis") + .Append("ExhaustiveMatching") + .Distinct() + .OrderBy(v => v, NamespaceComparer.Instance); + } + + private string TypeName(GrammarSymbol symbol) + { + return symbol.IsQuoted ? symbol.Text : $"{grammar.Prefix}{symbol.Text}{grammar.Suffix}"; + } + + /// + /// A property needs declared under three conditions: + /// 1. there is no definition of the property in the parent + /// 2. the single parent definition has a different type + /// 3. the property is defined in multiple parents, in that case it is + /// ambitious unless it is redefined in the current interface. + /// + private bool NeedsDeclared(GrammarRule rule, GrammarProperty property) + { + var baseProperties = BaseProperties(rule, property.Name).ToList(); + return baseProperties.Count != 1 || baseProperties[0].Type != property.Type; + } + + /// + /// Something is a new definition if it replaces some parent definition. + /// + private bool IsNewDefinition(GrammarRule rule, GrammarProperty property) + { + return BaseProperties(rule, property.Name).Any(); + } + + private string TypeName(GrammarType type) + { + var value = TypeName(type.Symbol); + if (type.IsRef) + { + value = "ref " + value; + if (type.IsOptional) value = "[DisallowNull] " + value; + } + if (type.IsOptional) value += "?"; + if (type.IsList) value = $"{grammar.ListType}<{value}>"; + return value; + } + + private string BaseTypes(GrammarRule rule) + { + var parents = rule.Parents.Select(TypeName); + if (!rule.Parents.Any()) + return ""; + + return " : " + string.Join(", ", parents); + } + + /// + /// Definitions for the property on the parents of the rule. + /// + private IEnumerable BaseProperties(GrammarRule rule, string propertyName) + { + return grammar.ParentRules(rule).SelectMany(r => PropertyDefinitions(r, propertyName)); + } + + /// + /// Get the property definitions for a rule. If that rule defines the property itself, that + /// is the one definition. When the rule doesn't define the property, base classes are + /// recursively searched for definitions. Multiple definitions are returned when multiple + /// parents of a rule contain definitions of the property without it being defined on that rule. + /// + private IEnumerable PropertyDefinitions(GrammarRule rule, string propertyName) + { + var property = rule.Properties.SingleOrDefault(p => p.Name == propertyName); + if (!(property is null)) return property.Yield(); + + return BaseProperties(rule, propertyName); + } + + private FixedList ChildRules(GrammarRule rule) + { + return grammar.Rules.Where(r => r.Parents.Contains(rule.Nonterminal)).ToFixedList(); + } + + private string ClosedType(GrammarRule rule) + { + var children = ChildRules(rule); + if (!children.Any()) return ""; + var builder = new StringBuilder(); + builder.AppendLine(" [Closed("); + var lastChild = children[^1]; + foreach (var child in children) + { + builder.Append($" typeof({TypeName(child.Nonterminal)})"); + builder.AppendLine(child == lastChild ? ")]" : ","); + } + return builder.ToString(); + } + } +} diff --git a/Compiler.CodeGen/TreeCodeTemplate.tt b/Compiler.CodeGen/TreeCodeTemplate.tt new file mode 100644 index 00000000..9fdedf9f --- /dev/null +++ b/Compiler.CodeGen/TreeCodeTemplate.tt @@ -0,0 +1,21 @@ +<#@ template language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<# foreach(var usingNamespace in OrderedNamespaces()) { #> +using <#=usingNamespace #>; +<# } #> + +namespace <#=grammar.Namespace #> +{ +<# foreach(var rule in grammar.Rules) {#> +<#=ClosedType(rule) #> public partial interface <#= TypeName(rule.Nonterminal) #><#= BaseTypes(rule) #> + { +<# foreach(var property in rule.Properties.Where(p => NeedsDeclared(rule, p))) { #> + <#=IsNewDefinition(rule, property) ? "new " : "" #><#=TypeName(property.Type) #> <#=property.Name #> { get; } +<# } #> + } + +<# } #> +} diff --git a/Compiler.Core/CodeBuilder.cs b/Compiler.Core/CodeBuilder.cs new file mode 100644 index 00000000..41ca606e --- /dev/null +++ b/Compiler.Core/CodeBuilder.cs @@ -0,0 +1,81 @@ +using System; +using System.Text; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public abstract class CodeBuilder + { + private readonly StringBuilder code = new StringBuilder(); + public string Code => code.ToString(); + public string IndentCharacters { get; } + public string LineTerminator { get; } + public int CurrentIndentDepth { get; private set; } + public string CurrentIndent => IndentCharacters.Repeat(CurrentIndentDepth); + + protected CodeBuilder(string indentCharacters) + : this(indentCharacters, Environment.NewLine) + { + } + + protected CodeBuilder(string indentCharacters, string lineTerminator) + { + IndentCharacters = indentCharacters; + LineTerminator = lineTerminator; + } + + public virtual void AppendIndent() + { + code.Insert(code.Length, IndentCharacters, CurrentIndentDepth); + } + + public virtual void BeginLine(string value) + { + AppendIndent(); + code.Append(value); + } + + public virtual void Append(string value) + { + code.Append(value); + } + + public virtual void EndLine(string value) + { + code.Append(value); + code.Append(LineTerminator); + } + + public virtual void EndLine() + { + code.Append(LineTerminator); + } + + public virtual void AppendLine(string value) + { + AppendIndent(); + code.Append(value); + code.Append(LineTerminator); + } + + public virtual void BlankLine() + { + code.Append(LineTerminator); + } + + public virtual void BeginBlock() + // ensures CurrentIndentDepth == old(CurrentIndentDepth) + 1 + { + CurrentIndentDepth++; + } + + public virtual void EndBlock() + // requires CurrentIndentDepth > 0 + // ensures CurrentIndentDepth == old(CurrentIndentDepth)-1 + { + if (CurrentIndentDepth <= 0) + throw new InvalidOperationException("Can't end block when indent depth is zero."); + CurrentIndentDepth--; + } + } +} diff --git a/Compiler.Core/CodeFile.cs b/Compiler.Core/CodeFile.cs new file mode 100644 index 00000000..d141093f --- /dev/null +++ b/Compiler.Core/CodeFile.cs @@ -0,0 +1,36 @@ +using System.IO; +using System.Text; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + /// A CodeFile represents the combination of CodeText and CodePath + public class CodeFile + { + /// Source code files are encoded with UTF-8 without a BOM. C# UTF-8 include + /// the BOM by default. So we make our own Encoding object. + + public static readonly Encoding Encoding = new UTF8Encoding(false); + + public CodeReference Reference { get; } + + public CodeText Code { get; } + + public CodeFile(CodeReference reference, CodeText text) + { + Code = text; + Reference = reference; + } + + public static CodeFile Load(string path) + { + return Load(path, FixedList.Empty); + } + + public static CodeFile Load(string path, FixedList @namespace) + { + var fullPath = Path.GetFullPath(path); + return new CodeFile(new CodePath(fullPath, @namespace), new CodeText(File.ReadAllText(fullPath, Encoding))); + } + } +} diff --git a/Compiler.Core/CodePath.cs b/Compiler.Core/CodePath.cs new file mode 100644 index 00000000..f2ea3be8 --- /dev/null +++ b/Compiler.Core/CodePath.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Threading.Tasks; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + /// A CodeReference to a file on disk referenced by its path. + public class CodePath : CodeReference, ICodeFileSource + { + public string Path { get; } + + public CodePath(string path) + : this(path, FixedList.Empty) + { + } + + public CodePath(string path, FixedList @namespace) + : base(@namespace) + { + Requires.That(nameof(path), System.IO.Path.IsPathFullyQualified(path), "must be fully qualified"); + Path = path; + } + + public override string ToString() + { + return Path; + } + + public CodeFile Load() + { + var text = File.ReadAllText(Path, CodeFile.Encoding); + return new CodeFile(this, new CodeText(text)); + } + + public async Task LoadAsync() + { + var text = await File.ReadAllTextAsync(Path, CodeFile.Encoding).ConfigureAwait(false); + return new CodeFile(this, new CodeText(text)); + } + } +} diff --git a/Compiler.Core/CodeReference.cs b/Compiler.Core/CodeReference.cs new file mode 100644 index 00000000..ac860ee9 --- /dev/null +++ b/Compiler.Core/CodeReference.cs @@ -0,0 +1,19 @@ +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + /// Some kind of reference to where the source code came from. For example, + /// this might be a path on disk or a network URL or a git hash or what + /// template file the code was generated from. + public abstract class CodeReference + { + public FixedList Namespace { get; } + + protected CodeReference(FixedList @namespace) + { + Namespace = @namespace; + } + + public abstract override string ToString(); + } +} diff --git a/Compiler.Core/CodeText.cs b/Compiler.Core/CodeText.cs new file mode 100644 index 00000000..e4c8d3d8 --- /dev/null +++ b/Compiler.Core/CodeText.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + /// The text of a source code file + public class CodeText + { + + public string Text { get; } + + public int Length => Text.Length; + + public TextLines Lines => lines.Value; + + private readonly Lazy lines; + + [SuppressMessage("Design", "CA1043:Use Integral Or String Argument For Indexers", Justification = "TextSpan is a struct and represents an index range")] + public string this[TextSpan span] => Text.Substring(span.Start, span.Length); + public char this[int index] => Text[index]; + + public CodeText(string text) + { + Text = text; + lines = new Lazy(GetLines); + } + + private TextLines GetLines() + { + return new TextLines(Text); + } + + public TextPosition PositionOfStart(in TextSpan span) + { + return PositionOf(span.Start); + } + + public TextPosition PositionOfEnd(in TextSpan span) + { + if (span.IsEmpty) + return PositionOfStart(span); + // End is one past, we want the actual last char + return PositionOf(span.End - 1); + } + + private TextPosition PositionOf(int charOffset) + { + var lineIndex = Lines.LineIndexContainingOffset(charOffset); + var lineStart = Lines.StartOfLine[lineIndex]; + + // TODO handle Unicode + var column = charOffset - lineStart + 1; // column is one based + // Account for tabs being multiple columns + // TODO switch to a for loop when we have range expressions + var i = lineStart; + while (i < charOffset) + { + if (Text[i] == '\t') + column += 3; // tabs are 4 columns, but the character was already counted as 1 + i += 1; + } + + // The line number is one based while the variable was zero based + return new TextPosition(charOffset, lineIndex + 1, column); + } + } +} diff --git a/Compiler.Core/Compiler.Core.csproj b/Compiler.Core/Compiler.Core.csproj new file mode 100644 index 00000000..7bf0dbe8 --- /dev/null +++ b/Compiler.Core/Compiler.Core.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Core + Azoth.Tools.Bootstrap.Compiler.Core + 8.0 + enable + + + + + + + + DEBUG;TRACE + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Compiler.Core/Diagnostic.cs b/Compiler.Core/Diagnostic.cs new file mode 100644 index 00000000..04cb4070 --- /dev/null +++ b/Compiler.Core/Diagnostic.cs @@ -0,0 +1,48 @@ +using System; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public class Diagnostic + { + public CodeFile File { get; } + public TextSpan Span { get; } + public TextPosition StartPosition { get; } + public TextPosition EndPosition { get; } + public DiagnosticLevel Level { get; } + public DiagnosticPhase Phase { get; } + public int ErrorCode { get; } + public string Message { get; } + + public Diagnostic( + CodeFile file, + TextSpan span, + DiagnosticLevel level, + DiagnosticPhase phase, + int errorCode, + string message) + { + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentException("Can't be null or whitespace", nameof(message)); + + Requires.ValidEnum(nameof(level), level); + Requires.ValidEnum(nameof(phase), phase); + + File = file; + Span = span; + StartPosition = file.Code.PositionOfStart(span); + EndPosition = file.Code.PositionOfEnd(span); + Level = level; + Phase = phase; + ErrorCode = errorCode; + Message = message; + } + + public bool IsFatal => Level == DiagnosticLevel.FatalCompilationError; + + public override string ToString() + { + return $"{Level} {ErrorCode}: {Message} @{File.Reference}@{Span}"; + } + } +} diff --git a/Compiler.Core/DiagnosticLevel.cs b/Compiler.Core/DiagnosticLevel.cs new file mode 100644 index 00000000..2cad6e5f --- /dev/null +++ b/Compiler.Core/DiagnosticLevel.cs @@ -0,0 +1,21 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public enum DiagnosticLevel + { + Info = 1, + Warning, + /// + /// An error which will occur at runtime but doesn't prevent compilation (i.e. pre-condition not met) + /// + RuntimeError, + /// + /// A compilation error that doesn't prevent further compilation. For example, using `!=` for + /// not equal. + /// + CompilationError, + /// + /// An error which prevents further compilation steps + /// + FatalCompilationError, + } +} diff --git a/Compiler.Core/DiagnosticPhase.cs b/Compiler.Core/DiagnosticPhase.cs new file mode 100644 index 00000000..f2917f8f --- /dev/null +++ b/Compiler.Core/DiagnosticPhase.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public enum DiagnosticPhase + { + Lexing = 1, + Parsing, + Analysis, + } +} diff --git a/Compiler.Core/Diagnostics.cs b/Compiler.Core/Diagnostics.cs new file mode 100644 index 00000000..01d9f857 --- /dev/null +++ b/Compiler.Core/Diagnostics.cs @@ -0,0 +1,58 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + [DebuggerDisplay("Count = {items.Count}")] + [DebuggerTypeProxy(typeof(CollectionDebugView<>))] + [SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Core to compiler domain")] + public class Diagnostics : IEnumerable + { + private readonly List items = new List(); + + public Diagnostics() { } + + public Diagnostics(IEnumerable diagnostics) + { + items.AddRange(diagnostics); + } + + public int Count => items.Count; + + public void Add(Diagnostic diagnostic) + { + items.Add(diagnostic); + } + + public void Add(IEnumerable diagnostics) + { + items.AddRange(diagnostics); + } + + public FixedList Build() + { + items.Sort((d1, d2) => d1.StartPosition.CompareTo(d2.StartPosition)); + return items.ToFixedList(); + } + + public void ThrowIfFatalErrors() + { + if (items.Any(i => i.IsFatal)) + throw new FatalCompilationErrorException(items.ToFixedList()); + } + + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + } +} diff --git a/Compiler.Core/ExpressionSemantics.cs b/Compiler.Core/ExpressionSemantics.cs new file mode 100644 index 00000000..f004f1ed --- /dev/null +++ b/Compiler.Core/ExpressionSemantics.cs @@ -0,0 +1,52 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + /// + /// The semantics of the value of an expression + /// + /// Move - the value is moved + /// Copy - the value is copied, either memcopy or using copy initializer + /// Acquire - take ownership from (can apply to isolated, owned, and held) + /// Borrow - copy the reference, borrow the object + /// Share - copy the reference, share the object + /// + [SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names", Justification = "Name not plural")] + public enum ExpressionSemantics + { + /// + /// Never returns or has unknown return + /// + Never = -1, + /// + /// Expressions of type `void`, don't produce a value + /// + Void = 0, + /// + /// The value is moved + /// + Move = 1, + /// + /// The value is copied. For expression, does not indicate whether it is + /// safe to bit copy the type or a copy function is needed + /// + Copy, + /// + /// The ownership is transferred between the references. Leaves the + /// giver in a moved state. + /// + Acquire, + /// + /// Copy a reference, borrow the referent + /// + Borrow, + /// + /// Copy a reference, share the referent + /// + Share, + /// + /// Take a reference to a place. Used for LValues + /// + CreateReference, + } +} diff --git a/Compiler.Core/FatalCompilationErrorException.cs b/Compiler.Core/FatalCompilationErrorException.cs new file mode 100644 index 00000000..e7c93a57 --- /dev/null +++ b/Compiler.Core/FatalCompilationErrorException.cs @@ -0,0 +1,15 @@ +using System; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public class FatalCompilationErrorException : Exception + { + public FixedList Diagnostics { get; } + + public FatalCompilationErrorException(FixedList diagnostics) + { + Diagnostics = diagnostics; + } + } +} diff --git a/Compiler.Core/ICodeFileSource.cs b/Compiler.Core/ICodeFileSource.cs new file mode 100644 index 00000000..293c0aa5 --- /dev/null +++ b/Compiler.Core/ICodeFileSource.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + /// + /// A source for + /// + public interface ICodeFileSource + { + CodeFile Load(); + Task LoadAsync(); + } +} diff --git a/Compiler.Core/Operators/AccessOperator.cs b/Compiler.Core/Operators/AccessOperator.cs new file mode 100644 index 00000000..b3e4a9b6 --- /dev/null +++ b/Compiler.Core/Operators/AccessOperator.cs @@ -0,0 +1,8 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public enum AccessOperator + { + Standard, + Conditional, + } +} \ No newline at end of file diff --git a/Compiler.Core/Operators/AccessOperatorExtensions.cs b/Compiler.Core/Operators/AccessOperatorExtensions.cs new file mode 100644 index 00000000..e67f8ac4 --- /dev/null +++ b/Compiler.Core/Operators/AccessOperatorExtensions.cs @@ -0,0 +1,17 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public static class AccessOperatorExtensions + { + public static string ToSymbolString(this AccessOperator @operator) + { + return @operator switch + { + AccessOperator.Standard => ".", + AccessOperator.Conditional => "?.", + _ => throw ExhaustiveMatch.Failed(@operator) + }; + } + } +} diff --git a/Compiler.Core/Operators/AssignmentOperator.cs b/Compiler.Core/Operators/AssignmentOperator.cs new file mode 100644 index 00000000..50f6c8ae --- /dev/null +++ b/Compiler.Core/Operators/AssignmentOperator.cs @@ -0,0 +1,11 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public enum AssignmentOperator + { + Simple, + Plus, + Minus, + Asterisk, + Slash + } +} \ No newline at end of file diff --git a/Compiler.Core/Operators/AssignmentOperatorExtensions.cs b/Compiler.Core/Operators/AssignmentOperatorExtensions.cs new file mode 100644 index 00000000..ba107e6f --- /dev/null +++ b/Compiler.Core/Operators/AssignmentOperatorExtensions.cs @@ -0,0 +1,20 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public static class AssignmentOperatorExtensions + { + public static string ToSymbolString(this AssignmentOperator @operator) + { + return @operator switch + { + AssignmentOperator.Simple => "=", + AssignmentOperator.Plus => "+=", + AssignmentOperator.Minus => "-=", + AssignmentOperator.Asterisk => "*=", + AssignmentOperator.Slash => "/=", + _ => throw ExhaustiveMatch.Failed(@operator) + }; + } + } +} diff --git a/Compiler.Core/Operators/BinaryOperator.cs b/Compiler.Core/Operators/BinaryOperator.cs new file mode 100644 index 00000000..d46736e9 --- /dev/null +++ b/Compiler.Core/Operators/BinaryOperator.cs @@ -0,0 +1,22 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public enum BinaryOperator + { + Plus, + Minus, + Asterisk, + Slash, + EqualsEquals, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + And, + Or, + DotDot, + LessThanDotDot, + DotDotLessThan, + LessThanDotDotLessThan, + } +} \ No newline at end of file diff --git a/Compiler.Core/Operators/BinaryOperatorExtensions.cs b/Compiler.Core/Operators/BinaryOperatorExtensions.cs new file mode 100644 index 00000000..66837058 --- /dev/null +++ b/Compiler.Core/Operators/BinaryOperatorExtensions.cs @@ -0,0 +1,31 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public static class BinaryOperatorExtensions + { + public static string ToSymbolString(this BinaryOperator @operator) + { + return @operator switch + { + BinaryOperator.Plus => "+", + BinaryOperator.Minus => "-", + BinaryOperator.Asterisk => "*", + BinaryOperator.Slash => "/", + BinaryOperator.EqualsEquals => "==", + BinaryOperator.NotEqual => "=/=", + BinaryOperator.LessThan => "<", + BinaryOperator.LessThanOrEqual => "<=", + BinaryOperator.GreaterThan => ">", + BinaryOperator.GreaterThanOrEqual => ">=", + BinaryOperator.And => "and", + BinaryOperator.Or => "or", + BinaryOperator.DotDot => "..", + BinaryOperator.LessThanDotDot => "<..", + BinaryOperator.DotDotLessThan => "..<", + BinaryOperator.LessThanDotDotLessThan => "<..<", + _ => throw ExhaustiveMatch.Failed(@operator) + }; + } + } +} diff --git a/Compiler.Core/Operators/UnaryOperator.cs b/Compiler.Core/Operators/UnaryOperator.cs new file mode 100644 index 00000000..eac62d50 --- /dev/null +++ b/Compiler.Core/Operators/UnaryOperator.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public enum UnaryOperator + { + Not, + Minus, + Plus, + } +} diff --git a/Compiler.Core/Operators/UnaryOperatorExtensions.cs b/Compiler.Core/Operators/UnaryOperatorExtensions.cs new file mode 100644 index 00000000..9285813c --- /dev/null +++ b/Compiler.Core/Operators/UnaryOperatorExtensions.cs @@ -0,0 +1,22 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public static class UnaryOperatorExtensions + { + public static string ToSymbolString(this UnaryOperator @operator) + { + switch (@operator) + { + default: + throw ExhaustiveMatch.Failed(@operator); + case UnaryOperator.Not: + return "not "; + case UnaryOperator.Minus: + return "-"; + case UnaryOperator.Plus: + return "+"; + } + } + } +} diff --git a/Compiler.Core/Operators/UnaryOperatorFixity.cs b/Compiler.Core/Operators/UnaryOperatorFixity.cs new file mode 100644 index 00000000..6effd0d4 --- /dev/null +++ b/Compiler.Core/Operators/UnaryOperatorFixity.cs @@ -0,0 +1,8 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core.Operators +{ + public enum UnaryOperatorFixity + { + Prefix, + Postfix + } +} diff --git a/Compiler.Core/ParseContext.cs b/Compiler.Core/ParseContext.cs new file mode 100644 index 00000000..e9c64294 --- /dev/null +++ b/Compiler.Core/ParseContext.cs @@ -0,0 +1,14 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public class ParseContext + { + public CodeFile File { get; } + public Diagnostics Diagnostics { get; } + + public ParseContext(CodeFile file, Diagnostics diagnostics) + { + File = file; + Diagnostics = diagnostics; + } + } +} diff --git a/Compiler.Core/Promises/AcyclicPromise.cs b/Compiler.Core/Promises/AcyclicPromise.cs new file mode 100644 index 00000000..62f5e60f --- /dev/null +++ b/Compiler.Core/Promises/AcyclicPromise.cs @@ -0,0 +1,111 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Promises +{ + /// + /// A promise that helps in detecting cycles in the computation of promised + /// values so that they can be forced to be acyclic. + /// + [DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] + public class AcyclicPromise : IPromise + { + private PromiseState state; + public bool IsFulfilled => state == PromiseState.Fulfilled; + private T value = default!; + + public AcyclicPromise() + { + state = PromiseState.Pending; + } + + public AcyclicPromise(T value) + { + this.value = value; + state = PromiseState.Fulfilled; + } + + [DebuggerHidden] + public void BeginFulfilling() + { + Requires.That(nameof(state), state == PromiseState.Pending, "must be pending is " + state); + state = PromiseState.InProgress; + } + + [DebuggerHidden] + public bool TryBeginFulfilling(Action? whenInProgress = null) + { + switch (state) + { + default: + throw ExhaustiveMatch.Failed(state); + case PromiseState.InProgress: + whenInProgress?.Invoke(); + return false; + case PromiseState.Fulfilled: + // We have already resolved it + return false; + case PromiseState.Pending: + state = PromiseState.InProgress; + // we need to compute it + return true; + } + } + + [DebuggerHidden] + public T Fulfill(T value) + { + Requires.That(nameof(state), state == PromiseState.InProgress, "must be in progress is " + state); + this.value = value; + state = PromiseState.Fulfilled; + return value; + } + + [DebuggerHidden] + public T Result + { + get + { + if (!IsFulfilled) throw new InvalidOperationException("Promise not fulfilled"); + + return value; + } + } + + public IPromise? As() + { + if (this is IPromise convertedPromise) return convertedPromise; + if (IsFulfilled && Result is TSub convertedValue) + return new Promise(convertedValue); + return null; + } + + // Useful for debugging + public override string ToString() + { + switch (state) + { + default: +#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations + throw ExhaustiveMatch.Failed(state); +#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations + case PromiseState.Pending: + return "⧼pending⧽"; + case PromiseState.InProgress: + return "⧼in progress⧽"; + case PromiseState.Fulfilled: + return value?.ToString() ?? "⧼null⧽"; + } + } + } + + public static class AcyclicPromise + { + public static AcyclicPromise ForValue(T value) + { + return new AcyclicPromise(value); + } + } +} diff --git a/Compiler.Core/Promises/DerivedPromise.cs b/Compiler.Core/Promises/DerivedPromise.cs new file mode 100644 index 00000000..4eaa144e --- /dev/null +++ b/Compiler.Core/Promises/DerivedPromise.cs @@ -0,0 +1,31 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Promises +{ + /// + /// A promise derived from another promise by transforming the value + /// + internal class DerivedPromise : IPromise + { + private readonly IPromise promise; + private readonly Func selector; + + public DerivedPromise(IPromise promise, Func selector) + { + this.promise = promise; + this.selector = selector; + } + + public bool IsFulfilled => promise.IsFulfilled; + + public S Result => selector(promise.Result); + + public IPromise? As() + { + if (this is IPromise convertedPromise) return convertedPromise; + if (IsFulfilled && Result is TSub convertedValue) + return new Promise(convertedValue); + return null; + } + } +} diff --git a/Compiler.Core/Promises/IPromise.cs b/Compiler.Core/Promises/IPromise.cs new file mode 100644 index 00000000..317b60bf --- /dev/null +++ b/Compiler.Core/Promises/IPromise.cs @@ -0,0 +1,16 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core.Promises +{ + public interface IPromise + { + bool IsFulfilled { get; } + T Result { get; } + /// + /// Convert this promise to another kind of promise if possible. + /// + /// + /// This can't be an extension method because it would have to have two + /// type parameters. + /// + IPromise? As(); + } +} diff --git a/Compiler.Core/Promises/Promise.cs b/Compiler.Core/Promises/Promise.cs new file mode 100644 index 00000000..ba6fbe2f --- /dev/null +++ b/Compiler.Core/Promises/Promise.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Promises +{ + /// + /// A simple promise of a future value. The value can be set only once. + /// + [DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] + public class Promise : IPromise + { + public bool IsFulfilled { get; private set; } + private T value = default!; + + [DebuggerHidden] + public Promise() { } + + [DebuggerHidden] + public Promise(T value) + { + this.value = value; + IsFulfilled = true; + } + + [DebuggerHidden] + public T Fulfill(T value) + { + Requires.That(nameof(IsFulfilled), !IsFulfilled, "must not already be fulfilled"); + this.value = value; + IsFulfilled = true; + return value; + } + + [DebuggerHidden] + public T Result + { + get + { + if (!IsFulfilled) throw new InvalidOperationException("Promise not fulfilled"); + + return value; + } + } + + public IPromise? As() + { + if (this is IPromise convertedPromise) return convertedPromise; + if (IsFulfilled && Result is TSub convertedValue) + return new Promise(convertedValue); + return null; + } + + // Useful for debugging + public override string ToString() + { + return IsFulfilled ? value?.ToString() ?? "⧼null⧽" : "⧼pending⧽"; + } + } + + public static class Promise + { + public static Promise ForValue(T value) + { + return new Promise(value); + } + } +} diff --git a/Compiler.Core/Promises/PromiseExtensions.cs b/Compiler.Core/Promises/PromiseExtensions.cs new file mode 100644 index 00000000..793d5fa3 --- /dev/null +++ b/Compiler.Core/Promises/PromiseExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Core.Promises +{ + public static class PromiseExtensions + { + public static IPromise Select(this IPromise promise, Func selector) + { + return new DerivedPromise(promise, selector); + } + } +} diff --git a/Compiler.Core/Promises/PromiseState.cs b/Compiler.Core/Promises/PromiseState.cs new file mode 100644 index 00000000..fe7106f6 --- /dev/null +++ b/Compiler.Core/Promises/PromiseState.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Core.Promises +{ + public enum PromiseState + { + Pending = 0, + InProgress, + Fulfilled + } +} diff --git a/Compiler.Core/README.md b/Compiler.Core/README.md new file mode 100644 index 00000000..93fd94eb --- /dev/null +++ b/Compiler.Core/README.md @@ -0,0 +1,5 @@ +# Azoth.Tools.Bootstrap.Compiler.Core + +## Naming Notes + +* Use `Code` instead of `Source`: The word "source" is often ambiguous. When combined with other words it is unclear whether it means the source code or simply the source of some data. Additionally, dictionaries do not give "source code" as one of the definitions of "source". The phrase "source code" is a little long, however, the word "code" does have a definition as computer code or source code. diff --git a/Compiler.Core/TextLines.cs b/Compiler.Core/TextLines.cs new file mode 100644 index 00000000..ab03862b --- /dev/null +++ b/Compiler.Core/TextLines.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public class TextLines + { + public int Count => startOfLine.Count; + public IReadOnlyList StartOfLine { get; } + // Keep the original list since read only list doesn't have binary search + private readonly List startOfLine; + private readonly int textLength; + + public TextLines(string text) + { + textLength = text.Length; + startOfLine = LineStarts(text); + StartOfLine = startOfLine.AsReadOnly(); + } + + private static List LineStarts(string text) + { + var length = text.Length; + var startOfLine = new List { 0 }; // there is always a first line + + // performance critical + var position = 0; + while (position < length) + { + var c = text[position]; + position += 1; + + // Common case - ASCII & not a line break + if (c > '\r' && c <= '\x7F') continue; + + if (c == '\r') + { + // Assumes that the only 2-char line break sequence is CR+LF + if (position < length && text[position] == '\n') + position += 1; + } + else if (c == '\n' + || c == '\x0B' // vertical tab + || c == '\f' + || c == '\x85' // next line + || c == '\u2028' // line separator + || c == '\u2029') // paragraph separator + { + // Do Nothing + } + else continue; + + // Already advanced position, so next line starts there + startOfLine.Add(position); + } + + return startOfLine; + } + + /// + /// Returns zero based line index + /// + public int LineIndexContainingOffset(int charOffset) + { + // Start is allowed to be equal to length to allow for a zero length span after the last character + if (charOffset < 0 || charOffset > textLength) + throw new ArgumentOutOfRangeException(nameof(charOffset), charOffset, + $"Character offset not in text lines of length {textLength}"); + + var searchResult = startOfLine.BinarySearch(charOffset); + if (searchResult < 0) + { + // Not found, bitwise complement of next larger element + return ~searchResult - 1; + } + return searchResult; + } + } +} diff --git a/Compiler.Core/TextPosition.cs b/Compiler.Core/TextPosition.cs new file mode 100644 index 00000000..ff444f4c --- /dev/null +++ b/Compiler.Core/TextPosition.cs @@ -0,0 +1,69 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + public readonly struct TextPosition : IEquatable, IComparable + { + public int CharacterOffset { get; } // Zero based + public int Line { get; } // One based + public int Column { get; } // One based + + public TextPosition(int characterOffset, int line, int column) + { + CharacterOffset = characterOffset; + Line = line; + Column = column; + } + + public override bool Equals(object? obj) + { + if (obj is TextPosition other) return CharacterOffset == other.CharacterOffset; + return false; + } + + public bool Equals(TextPosition other) + { + return CharacterOffset == other.CharacterOffset; + } + + public int CompareTo(TextPosition other) + { + return CharacterOffset.CompareTo(other.CharacterOffset); + } + + public override int GetHashCode() + { + return HashCode.Combine(CharacterOffset); + } + + public static bool operator ==(TextPosition left, TextPosition right) + { + return left.Equals(right); + } + + public static bool operator !=(TextPosition left, TextPosition right) + { + return !left.Equals(right); + } + + public static bool operator <(TextPosition left, TextPosition right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator <=(TextPosition left, TextPosition right) + { + return left.CompareTo(right) <= 0; + } + + public static bool operator >(TextPosition left, TextPosition right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator >=(TextPosition left, TextPosition right) + { + return left.CompareTo(right) >= 0; + } + } +} diff --git a/Compiler.Core/TextSpan.cs b/Compiler.Core/TextSpan.cs new file mode 100644 index 00000000..30345d7b --- /dev/null +++ b/Compiler.Core/TextSpan.cs @@ -0,0 +1,106 @@ +using System; +using System.Diagnostics; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Core +{ + [DebuggerDisplay("positions {Start} to {End}")] + public readonly struct TextSpan : IEquatable + { + public int Start { get; } + public int End => Start + Length; + public int Length { get; } + public bool IsEmpty => Length == 0; + + public TextSpan(int start, int length) + { + Requires.Positive(nameof(start), start); + Requires.Positive(nameof(length), length); + Start = start; + Length = length; + } + + [System.Diagnostics.Contracts.Pure] + public static TextSpan FromStartEnd(int start, int end) + { + Requires.Positive(nameof(start), start); + Requires.Positive(nameof(end), end); + return new TextSpan(start, end - start); + } + + [System.Diagnostics.Contracts.Pure] + public static TextSpan Covering(TextSpan x, TextSpan y) + { + return FromStartEnd(Math.Min(x.Start, y.Start), Math.Max(x.End, y.End)); + } + + [System.Diagnostics.Contracts.Pure] + public static TextSpan Covering(params TextSpan?[] spans) + { + return spans.Where(s => s != null).Cast().Aggregate(Covering); + } + + /// + /// Returns a zero length span that occurs at the start of the current span + /// + public TextSpan AtStart() + { + return new TextSpan(Start, 0); + } + + /// + /// Returns a zero length span that occurs at the end of the current span + /// + [System.Diagnostics.Contracts.Pure] + public TextSpan AtEnd() + { + return new TextSpan(End, 0); + } + + [System.Diagnostics.Contracts.Pure] + + public string GetText(string text) + { + return text.Substring(Start, Length); + } + + #region Equality + [System.Diagnostics.Contracts.Pure] + public override bool Equals(object? obj) + { + return obj is TextSpan span && Equals(span); + } + + [System.Diagnostics.Contracts.Pure] + public bool Equals(TextSpan other) + { + return Start == other.Start && + Length == other.Length; + } + + [System.Diagnostics.Contracts.Pure] + public override int GetHashCode() + { + return HashCode.Combine(Start, Length); + } + + [System.Diagnostics.Contracts.Pure] + public static bool operator ==(TextSpan span1, TextSpan span2) + { + return span1.Equals(span2); + } + + [System.Diagnostics.Contracts.Pure] + public static bool operator !=(TextSpan span1, TextSpan span2) + { + return !(span1 == span2); + } + #endregion + + public override string ToString() + { + return $"TextSpan({Start},{Length})"; + } + } +} diff --git a/Compiler.Emit.C/CCodeBuilder.cs b/Compiler.Emit.C/CCodeBuilder.cs new file mode 100644 index 00000000..97e82e93 --- /dev/null +++ b/Compiler.Emit.C/CCodeBuilder.cs @@ -0,0 +1,113 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class CCodeBuilder : CodeBuilder + { + public new const string LineTerminator = "\n"; + + /// Whether we need a blank separator line before the next declaration + public bool NeedsDeclarationSeparatorLine { get; private set; } + + /// Whether we need a blank separator line before the next statement + public bool NeedsStatementSeparatorLine { get; private set; } + + public CCodeBuilder(string indentCharacters = " ") + : base(indentCharacters, LineTerminator) + { + } + + public override void EndLine(string value) + { + base.EndLine(value); + NeedsDeclarationSeparatorLine = true; + NeedsStatementSeparatorLine = false; + } + + public override void EndLine() + { + base.EndLine(); + NeedsDeclarationSeparatorLine = true; + NeedsStatementSeparatorLine = false; + } + + public override void AppendLine(string value) + { + base.AppendLine(value); + NeedsDeclarationSeparatorLine = true; + NeedsStatementSeparatorLine = false; + } + + public override void BlankLine() + { + base.BlankLine(); + NeedsDeclarationSeparatorLine = false; + NeedsStatementSeparatorLine = false; + } + + public virtual void DeclarationSeparatorLine() + { + if (!NeedsDeclarationSeparatorLine) return; + base.BlankLine(); + NeedsDeclarationSeparatorLine = false; + NeedsStatementSeparatorLine = false; + } + + public virtual void StatementSeparatorLine() + { + if (!NeedsStatementSeparatorLine) return; + base.BlankLine(); + NeedsDeclarationSeparatorLine = false; + NeedsStatementSeparatorLine = false; + } + + public override void BeginBlock() + // ensures CurrentIndentDepth == old(CurrentIndentDepth) + 1 + { + base.AppendLine("{"); + base.BeginBlock(); + NeedsDeclarationSeparatorLine = false; + NeedsStatementSeparatorLine = false; + } + + public virtual void BeginBlockWith(string value) + // ensures CurrentIndentDepth == old(CurrentIndentDepth) + 1 + { + base.AppendLine(value); + base.BeginBlock(); + NeedsDeclarationSeparatorLine = false; + NeedsStatementSeparatorLine = false; + } + + public override void EndBlock() + // ensures CurrentIndentDepth == old(CurrentIndentDepth) - 1 + { + Requires.That(nameof(CurrentIndent), CurrentIndentDepth > 0, "indent depth must not be zero"); + base.EndBlock(); + base.AppendLine("}"); + NeedsDeclarationSeparatorLine = true; + NeedsStatementSeparatorLine = true; + } + + public virtual void EndBlockWithSemicolon() + // ensures CurrentIndentDepth == old(CurrentIndentDepth) - 1 + { + Requires.That(nameof(CurrentIndent), CurrentIndentDepth > 0, "indent depth must not be zero"); + base.EndBlock(); + base.AppendLine("};"); + NeedsDeclarationSeparatorLine = true; // These end classes and things that need a separator + NeedsStatementSeparatorLine = false; // Statements shouldn't come after blocks with semicolons + } + + public virtual void EndBlockWith(string value) + // ensures CurrentIndentDepth == old(CurrentIndentDepth) - 1 + { + Requires.That(nameof(CurrentIndent), CurrentIndentDepth > 0, "indent depth must not be zero"); + base.EndBlock(); + base.AppendLine(value); + NeedsDeclarationSeparatorLine = true; + NeedsStatementSeparatorLine = false; // TODO + } + } +} diff --git a/Compiler.Emit.C/CLangCompiler.cs b/Compiler.Emit.C/CLangCompiler.cs new file mode 100644 index 00000000..2835544c --- /dev/null +++ b/Compiler.Emit.C/CLangCompiler.cs @@ -0,0 +1,50 @@ +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class CLangCompiler + { + public int Compile( + ICompilerOutput output, + string[] sourceFiles, + string[] headerSearchPaths, + string outputPath) + { + // used to have: -Wno-incompatible-pointer-types + var options = "-std=c11 -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -Wall -Wno-unused-label"; + // Next thing is needed for windows + options += " -Xclang -flto-visibility-public-std"; + if (Path.GetExtension(outputPath) == ".dll") // TODO take this as an argument or something + options += " --shared"; + var sources = string.Join(' ', sourceFiles); + var headers = string.Join(' ', headerSearchPaths.Select(h => "--include-directory " + h)); + var arguments = $"{sources} -o {outputPath} {headers} {options}"; + output.WriteLine("clang arguments:"); + output.WriteLine(arguments); + var startInfo = new ProcessStartInfo("clang", arguments) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false, + }; + var process = new Process() { StartInfo = startInfo }; + // To prevent blocking on a full buffer, we have to process output as it happens + process.OutputDataReceived += ProcessOutput; + process.ErrorDataReceived += ProcessOutput; + output.WriteLine("clang output:"); + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + return process.ExitCode; + + void ProcessOutput(object s, DataReceivedEventArgs e) + { + output.WriteLine(e.Data); + } + } + } +} diff --git a/Compiler.Emit.C/Code.cs b/Compiler.Emit.C/Code.cs new file mode 100644 index 00000000..6403980a --- /dev/null +++ b/Compiler.Emit.C/Code.cs @@ -0,0 +1,31 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class Code + { + public CCodeBuilder Includes { get; } = new CCodeBuilder(); + public CCodeBuilder TypeIdDeclaration { get; } = new CCodeBuilder(); + public CCodeBuilder TypeDeclarations { get; } = new CCodeBuilder(); + public CCodeBuilder FunctionDeclarations { get; } = new CCodeBuilder(); + public CCodeBuilder StructDeclarations { get; } = new CCodeBuilder(); + public CCodeBuilder GlobalDefinitions { get; } = new CCodeBuilder(); + public CCodeBuilder Definitions { get; } = new CCodeBuilder(); + + public override string ToString() + { + return string.Concat( + Includes.Code, + CCodeBuilder.LineTerminator, + TypeIdDeclaration.Code, + CCodeBuilder.LineTerminator, + TypeDeclarations.Code, + CCodeBuilder.LineTerminator, + FunctionDeclarations.Code, + CCodeBuilder.LineTerminator, + StructDeclarations.Code, + CCodeBuilder.LineTerminator, + GlobalDefinitions.Code, + CCodeBuilder.LineTerminator, + Definitions.Code); + } + } +} diff --git a/Compiler.Emit.C/CodeEmitter.cs b/Compiler.Emit.C/CodeEmitter.cs new file mode 100644 index 00000000..05de08ab --- /dev/null +++ b/Compiler.Emit.C/CodeEmitter.cs @@ -0,0 +1,38 @@ +using Azoth.Tools.Bootstrap.Compiler.Emit.C.Properties; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class CodeEmitter : Emitter + { + public static string RuntimeLibraryCode => Resources.RuntimeLibraryCode; + public const string RuntimeLibraryCodeFileName = "RuntimeLibrary.c"; + public static string RuntimeLibraryHeader => Resources.RuntimeLibraryHeader; + public const string RuntimeLibraryHeaderFileName = "RuntimeLibrary.h"; + + private readonly PackageEmitter packageEmitter; + private readonly Code code = new Code(); + + public CodeEmitter() + { + var nameMangler = new NameMangler(); + var typeConverter = new TypeConverter(nameMangler); + var parameterConverter = new ParameterConverter(nameMangler, typeConverter); + var controlFlowEmitter = new ControlFlowEmitter(nameMangler, typeConverter); + var declarationEmitter = new DeclarationEmitter(nameMangler, parameterConverter, typeConverter, controlFlowEmitter); + packageEmitter = new PackageEmitter(nameMangler, declarationEmitter); + packageEmitter.EmitPreamble(code); + } + + public override void Emit(PackageIL package) + { + packageEmitter.Emit(package, code); + } + + public override string GetEmittedCode() + { + packageEmitter.EmitPostamble(code); + return code.ToString(); + } + } +} diff --git a/Compiler.Emit.C/Compiler.Emit.C.csproj b/Compiler.Emit.C/Compiler.Emit.C.csproj new file mode 100644 index 00000000..feecf9a3 --- /dev/null +++ b/Compiler.Emit.C/Compiler.Emit.C.csproj @@ -0,0 +1,51 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Emit.C + Azoth.Tools.Bootstrap.Compiler.Emit.C + 8.0 + enable + en-US + + + + DEBUG;TRACE + true + + + + + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/Compiler.Emit.C/ConsoleCompilerOutput.cs b/Compiler.Emit.C/ConsoleCompilerOutput.cs new file mode 100644 index 00000000..a32eff83 --- /dev/null +++ b/Compiler.Emit.C/ConsoleCompilerOutput.cs @@ -0,0 +1,18 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class ConsoleCompilerOutput : ICompilerOutput + { + #region Singleton + public static readonly ConsoleCompilerOutput Instance = new ConsoleCompilerOutput(); + + private ConsoleCompilerOutput() { } + #endregion + + public void WriteLine(string message) + { + Console.WriteLine(message); + } + } +} diff --git a/Compiler.Emit.C/ControlFlowEmitter.cs b/Compiler.Emit.C/ControlFlowEmitter.cs new file mode 100644 index 00000000..03133a6b --- /dev/null +++ b/Compiler.Emit.C/ControlFlowEmitter.cs @@ -0,0 +1,341 @@ +using System; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; +using static Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions.CompareInstructionOperator; +using static Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions.NumericInstructionOperator; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class ControlFlowEmitter : IEmitter + { + private readonly NameMangler nameMangler; + private readonly IConverter typeConverter; + + public ControlFlowEmitter( + NameMangler nameMangler, + IConverter typeConverter) + { + this.typeConverter = typeConverter; + this.nameMangler = nameMangler; + } + + public void Emit(IInvocableDeclarationIL invocable, Code code) + { + var cfg = invocable.IL!; + var definitions = code.Definitions; + + if (invocable is ConstructorIL constructor) + { + foreach (var fieldInitialization in constructor.FieldInitializations) + EmitFieldInitialization(fieldInitialization, definitions); + if (constructor.FieldInitializations.Any()) definitions.BlankLine(); + } + + foreach (var declaration in cfg.VariableDeclarations.Where(v => v.TypeIsNotEmpty)) + EmitVariable(declaration, definitions); + + if (cfg.VariableDeclarations.Any(v => v.TypeIsNotEmpty)) + definitions.BlankLine(); + + foreach (var block in cfg.Blocks) + EmitBlock(block, invocable.IsConstructor, definitions); + } + + private void EmitFieldInitialization(FieldInitializationIL fieldInitialization, CCodeBuilder code) + { + var fieldName = nameMangler.Mangle(fieldInitialization.Field); + var parameterName = nameMangler.Mangle(fieldInitialization.Field.Name); + // Emit direct access to avoid any issues about whether corresponding variables exist + code.AppendLine($"_self._self->{fieldName} = {parameterName};"); + } + + private void EmitVariable(VariableDeclaration declaration, CCodeBuilder code) + { + Requires.That(nameof(declaration), declaration.TypeIsNotEmpty, "tried to look up variable that does not exist"); + var initializer = declaration.IsParameter ? $" = {(declaration.Symbol is SelfParameterSymbol ? nameMangler.SelfName : nameMangler.Mangle(declaration.Symbol!.Name!))}" : ""; + code.AppendLine($"{typeConverter.Convert(declaration.Type)} _{declaration.Variable.Name}{initializer}; // {declaration}"); + } + + private void EmitBlock(Block block, bool isConstructor, CCodeBuilder code) + { + code.AppendLine($"bb{block.Number}:"); + code.BeginBlock(); + foreach (var instruction in block.Instructions) + EmitInstruction(instruction, code); + + EmitInstruction(block.Terminator, isConstructor, code); + code.EndBlock(); + } + + private void EmitInstruction(Instruction instruction, CCodeBuilder code) + { + code.AppendLine("// " + instruction); + switch (instruction) + { + default: + throw ExhaustiveMatch.Failed(instruction); + case InstructionWithResult ins: + EmitInstructionWithResult(ins, code); + break; + case CallInstruction ins: + { + if (!(ins.ResultPlace is null)) + EmitResultPlace(ins.ResultPlace, code); + else + code.BeginLine(""); + + string self = ""; + if (ins.IsMethodCall) self = ConvertOperand(ins.Self!); + + var mangledName = nameMangler.Mangle(ins.Function); + var arguments = ins.Arguments.Select(ConvertOperand); + if (ins.IsMethodCall) arguments = arguments.Prepend(self); + code.EndLine($"{mangledName}({string.Join(", ", arguments)});"); + } + break; + case CallVirtualInstruction ins: + { + if (!(ins.ResultPlace is null)) + EmitResultPlace(ins.ResultPlace, code); + else + code.BeginLine(""); + + var self = ConvertOperand(ins.Self); + var mangledName = nameMangler.MangleMethod(ins.Method); + var arguments = ins.Arguments.Select(ConvertOperand).Prepend(self); + code.EndLine($"{self}._vtable->{mangledName}({string.Join(", ", arguments)});"); + } + break; + } + } + + private void EmitResultPlace(Place resultPlace, CCodeBuilder code) + { + code.BeginLine($"{ConvertPlace(resultPlace)} = "); + } + + private void EmitInstructionWithResult(InstructionWithResult instruction, CCodeBuilder code) + { + EmitResultPlace(instruction.ResultPlace, code); + switch (instruction) + { + default: + throw ExhaustiveMatch.Failed(instruction); + case NewObjectInstruction ins: + { + var mangledName = nameMangler.Mangle(ins.Constructor); + var typeName = nameMangler.Mangle(ins.ConstructedType); + var selfArgument = $"({typeName}){{&{typeName}___vtable, malloc(sizeof({typeName}___Self))}}"; + var arguments = ins.Arguments.Select(ConvertOperand).Prepend(selfArgument); + code.EndLine($"{mangledName}({string.Join(", ", arguments)});"); + } + break; + case AssignmentInstruction ins: + code.EndLine($"{ConvertOperand(ins.Operand)};"); + break; + case CompareInstruction ins: + { + var left = ConvertOperand(ins.LeftOperand); + var right = ConvertOperand(ins.RightOperand); + var operationType = ConvertSimpleType(ins.Type); + string op = ins.Operator switch + { + Equal => "eq", + NotEqual => "ne", + LessThan => "lt", + LessThanOrEqual => "lte", + GreaterThan => "gt", + GreaterThanOrEqual => "gte", + _ => throw ExhaustiveMatch.Failed(ins.Operator) + }; + + code.EndLine($"{operationType}__{op}({left}, {right});"); + } + break; + case NumericInstruction ins: + { + var left = ConvertOperand(ins.LeftOperand); + var right = ConvertOperand(ins.RightOperand); + var operationType = ConvertSimpleType(ins.Type); + string op = ins.Operator switch + { + Add => "add", + Subtract => "sub", + Multiply => "mul", + Divide => "div", + _ => throw ExhaustiveMatch.Failed(ins.Operator) + }; + + code.EndLine($"{operationType}__{op}({left}, {right});"); + } + break; + case LoadIntegerInstruction ins: + code.EndLine($"({ConvertSimpleType(ins.Type)}){{{ins.Value}}};"); + break; + case LoadBoolInstruction ins: + { + var booleanValue = ins.Value ? 1 : 0; + code.EndLine($"({ConvertSimpleType(DataType.Bool)}){{{booleanValue}}};"); + } + break; + case LoadStringInstruction ins: + { + var constantLength = StringConstant.GetByteLength(ins.Value); + const string selfArgument = "(String){&String___vtable, malloc(sizeof(String___Self))}"; + var sizeArgument = $"(_size){{{constantLength}}}"; + var bytesArgument = $"(_size){{(uintptr_t)u8\"{ins.Value.Escape()}\"}}"; + code.EndLine($"String___new__3({selfArgument}, {sizeArgument}, {bytesArgument});"); + } + break; + case LoadNoneInstruction ins: + switch (ins.Type.Referent) + { + default: + throw ExhaustiveMatch.Failed(ins.Type.Referent); + case NeverType _: // `never?` is always none + case OptionalType _: // `T??` + case AnyType _: // `Any?` + throw new NotImplementedException(); + case ObjectType objectType: + { + var typeName = nameMangler.Mangle(objectType); + code.EndLine($"({ins.Type}){{&{typeName}___vtable, NULL}};"); + } + break; + case SimpleType simpleType: + { + var typeName = ConvertSimpleType(simpleType); + code.EndLine($"_opt__{typeName}__none;"); + } + break; + case UnknownType _: + throw new InvalidOperationException("Instruction uses unknown type (Load None)"); + case VoidType _: + throw new InvalidOperationException("Instruction uses invalid type `void?` (Load None)"); + } + break; + case NegateInstruction ins: + { + var operand = ConvertOperand(ins.Operand); + var operationType = ConvertSimpleType(ins.Type); + + code.EndLine($"{operationType}__neg({operand});"); + } + break; + case ConvertInstruction ins: + { + var valueToConvert = ConvertOperand(ins.Operand); + var fromType = ConvertSimpleType(ins.FromType); + var toType = ConvertSimpleType(ins.ToType); + code.EndLine($"_convert__{fromType}__{toType}({valueToConvert});"); + } + break; + case FieldAccessInstruction ins: + { + var fieldName = nameMangler.Mangle(ins.Field); + code.EndLine($"{ConvertOperand(ins.Operand)}._self->{fieldName};"); + } + break; + case SomeInstruction ins: + { + var someValue = ConvertOperand(ins.Operand); + if (ins.Type.Referent is ReferenceType) + code.EndLine(someValue); + else + { + var typeName = nameMangler.Mangle(ins.Type.Referent); + code.EndLine($"_opt__{typeName}__Some({someValue});"); + } + } + break; + case BooleanLogicInstruction ins: + { + var left = ConvertOperand(ins.LeftOperand); + var right = ConvertOperand(ins.RightOperand); + var operationType = nameMangler.Mangle(DataType.Bool); + string op = ins.Operator switch + { + // If a binary operator was emitted for a boolean operation, + // then it doesn't short circuit, we just call the function + BooleanLogicOperator.And => "and", + BooleanLogicOperator.Or => "or", + _ => throw ExhaustiveMatch.Failed(ins.Operator) + }; + + code.EndLine($"{operationType}__{op}({left}, {right});"); + } + break; + + + //case DeleteStatement deleteStatement: + //{ + // var self = ConvertValue(deleteStatement.Place); + // var typeName = nameMangler.Mangle(deleteStatement.Type); + // // TODO once deletes are implemented, call them + // //code.AppendLine($"{self}._vtable->{typeName}___delete__1({self});"); + // code.AppendLine($"free({self}._self);"); + // break; + //} + } + } + + private static void EmitInstruction(TerminatorInstruction instruction, bool isConstructor, CCodeBuilder code) + { + code.AppendLine("// " + instruction); + switch (instruction) + { + default: + throw ExhaustiveMatch.Failed(instruction); + case GotoInstruction ins: + code.AppendLine($"goto bb{ins.BlockNumber};"); + break; + case IfInstruction ins: + code.AppendLine($"if({ConvertOperand(ins.Condition)}._value) goto bb{ins.ThenBlockNumber}; else goto bb{ins.ElseBlockNumber};"); + break; + case ReturnValueInstruction ins: + code.AppendLine($"return {ConvertOperand(ins.Value)};"); + break; + case ReturnVoidInstruction _: + code.AppendLine(isConstructor ? "return _0;" : "return;"); + break; + } + } + + private string ConvertPlace(Place place) + { + switch (place) + { + default: + throw ExhaustiveMatch.Failed(place); + case VariablePlace p: + return "_" + p.Variable.Name; + case FieldPlace p: + var fieldName = nameMangler.Mangle(p.Field); + return $"{ConvertOperand(p.Target)}._self->{fieldName}"; + } + } + + private static string ConvertOperand(Operand value) + { + return value switch + { + VariableReference op => "_" + op.Variable.Name, + _ => throw ExhaustiveMatch.Failed(value) + }; + } + + private string ConvertSimpleType(SimpleType type) + { + return nameMangler.Mangle(type); + } + } +} diff --git a/Compiler.Emit.C/DeclarationEmitter.cs b/Compiler.Emit.C/DeclarationEmitter.cs new file mode 100644 index 00000000..0c390aa2 --- /dev/null +++ b/Compiler.Emit.C/DeclarationEmitter.cs @@ -0,0 +1,179 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class DeclarationEmitter : IEmitter + { + private readonly NameMangler nameMangler; + private readonly IConverter parameterConverter; + private readonly IConverter typeConverter; + private readonly IEmitter controlFlowEmitter; + + public DeclarationEmitter( + NameMangler nameMangler, + IConverter parameterConverter, + IConverter typeConverter, + IEmitter controlFlowEmitter) + { + this.nameMangler = nameMangler; + this.parameterConverter = parameterConverter; + this.typeConverter = typeConverter; + this.controlFlowEmitter = controlFlowEmitter; + } + + public void Emit(DeclarationIL declaration, Code code) + { + switch (declaration) + { + default: + throw ExhaustiveMatch.Failed(declaration); + case FunctionIL function: + if (function.IsExternal) + EmitExternalFunctionSignature(function, code); + else + EmitFunction(function, code); + break; + case MethodDeclarationIL method: + EmitMethod(method, code); + break; + case ConstructorIL constructor: + EmitConstructor(constructor, code); + break; + case ClassIL type: + EmitType(type, code); + break; + case FieldIL _: + // fields are emitted as part of the type + break; + } + } + + private void EmitExternalFunctionSignature(FunctionIL function, Code code) + { + // I think external functions are supposed to not be mangled? + var name = function.Symbol.Name.Text; + var parameters = Convert(function.Parameters); + var returnType = typeConverter.Convert(function.Symbol.ReturnDataType.Known()); + code.FunctionDeclarations.AppendLine($"{returnType} {name}({parameters});"); + } + + private void EmitFunction(FunctionIL function, Code code) + { + var name = nameMangler.MangleName(function); + var parameters = Convert(function.Parameters); + var returnType = typeConverter.Convert(function.Symbol.ReturnDataType.Known()); + + // Write out the function declaration for C so we can call functions defined after others + code.FunctionDeclarations.AppendLine($"{returnType} {name}({parameters});"); + + code.Definitions.DeclarationSeparatorLine(); + code.Definitions.AppendLine($"{returnType} {name}({parameters})"); + code.Definitions.BeginBlock(); + controlFlowEmitter.Emit(function, code); + code.Definitions.EndBlock(); + } + + private void EmitMethod(MethodDeclarationIL method, Code code) + { + if (method.IL is null) return; + + var name = nameMangler.MangleName(method); + var parameters = Convert(method.Parameters.Prepend(method.SelfParameter)); + var returnType = typeConverter.Convert(method.Symbol.ReturnDataType.Known()); + + // Write out the function declaration for C so we can call functions defined after others + code.FunctionDeclarations.AppendLine($"{returnType} {name}({parameters});"); + + code.Definitions.DeclarationSeparatorLine(); + code.Definitions.AppendLine($"{returnType} {name}({parameters})"); + code.Definitions.BeginBlock(); + controlFlowEmitter.Emit(method, code); + code.Definitions.EndBlock(); + } + + private void EmitConstructor(ConstructorIL constructor, Code code) + { + // Don't emit constructors without control flow, they are generic + // TODO need to handle better + if (constructor.IL is null) return; + + var name = nameMangler.MangleName(constructor); + var parameters = Convert(constructor.Parameters); + var returnType = typeConverter.Convert(constructor.Symbol.ContainingSymbol.DeclaresDataType.Known()); + + // Write out the function declaration for C so we can call functions defined after others + code.FunctionDeclarations.AppendLine($"// {constructor.Symbol}"); + code.FunctionDeclarations.AppendLine($"{returnType} {name}({parameters});"); + + code.Definitions.DeclarationSeparatorLine(); + code.Definitions.AppendLine($"// {constructor.Symbol}"); + code.Definitions.AppendLine($"{returnType} {name}({parameters})"); + code.Definitions.BeginBlock(); + controlFlowEmitter.Emit(constructor, code); + code.Definitions.EndBlock(); + } + + private string Convert(IEnumerable parameters) + { + return string.Join(", ", parameters.Select(parameterConverter.Convert)); + } + + private void EmitType(ClassIL @class, Code code) + { + var typeName = nameMangler.MangleName(@class); + + // Struct Forward Declarations + var selfType = $"{typeName}___Self"; + var vtableType = $"{typeName}___VTable"; + var types = code.TypeDeclarations; + types.AppendLine($"typedef struct {selfType} {selfType};"); + types.AppendLine($"typedef struct {vtableType} {vtableType};"); + // Declare the full type because when it is used as a field, its + // size will need to be known. However, order within struct + // declarations is not defined. + types.AppendLine($"typedef struct {typeName}"); + types.BeginBlock(); + types.AppendLine($"{vtableType} const* restrict _vtable;"); + types.AppendLine($"{selfType}* restrict _self;"); + types.EndBlockWith($"}} {typeName};"); + + var structs = code.StructDeclarations; + structs.AppendLine($"struct {selfType}"); + structs.BeginBlock(); + foreach (var field in @class.Members.OfType()) + { + var binding = field.Symbol.IsMutableBinding ? "var" : "let"; + structs.AppendLine($"// {binding} {field.Symbol.Name}: {field.DataType}"); + var fieldType = typeConverter.Convert(field.DataType.Known()); + var fieldName = nameMangler.MangleName(field); + structs.AppendLine($"{fieldType} {fieldName};"); + } + structs.EndBlockWithSemicolon(); + structs.AppendLine($"struct {vtableType}"); + structs.BeginBlock(); + foreach (var method in @class.Members.OfType()) + { + var name = nameMangler.MangleMethodName(method); + var parameters = Convert(method.Parameters.Prepend(method.SelfParameter)); + var returnType = typeConverter.Convert(method.Symbol.ReturnDataType.Known()); + structs.AppendLine($"{returnType} (*{name})({parameters});"); + } + structs.EndBlockWithSemicolon(); + + var globals = code.StructDeclarations; + globals.AppendLine($"const {vtableType} {typeName}___vtable = ({vtableType})"); + globals.BeginBlock(); + foreach (var method in @class.Members.OfType()) + { + var fieldName = nameMangler.MangleMethodName(method); + var functionName = nameMangler.MangleName(method); + globals.AppendLine($".{fieldName} = {functionName},"); + } + globals.EndBlockWithSemicolon(); + } + } +} diff --git a/Compiler.Emit.C/ICompilerOutput.cs b/Compiler.Emit.C/ICompilerOutput.cs new file mode 100644 index 00000000..5a6d10f5 --- /dev/null +++ b/Compiler.Emit.C/ICompilerOutput.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public interface ICompilerOutput + { + void WriteLine(string message); + } +} diff --git a/Compiler.Emit.C/IConverter.cs b/Compiler.Emit.C/IConverter.cs new file mode 100644 index 00000000..2f3fcc40 --- /dev/null +++ b/Compiler.Emit.C/IConverter.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public interface IConverter + { + string Convert(T value); + } +} diff --git a/Compiler.Emit.C/IEmitter.cs b/Compiler.Emit.C/IEmitter.cs new file mode 100644 index 00000000..99312538 --- /dev/null +++ b/Compiler.Emit.C/IEmitter.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public interface IEmitter + { + void Emit(T value, Code code); + } +} diff --git a/Compiler.Emit.C/InternalsVisibleTo.cs b/Compiler.Emit.C/InternalsVisibleTo.cs new file mode 100644 index 00000000..eb82e54b --- /dev/null +++ b/Compiler.Emit.C/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Emit.C")] diff --git a/Compiler.Emit.C/NameMangler.cs b/Compiler.Emit.C/NameMangler.cs new file mode 100644 index 00000000..a2f7c914 --- /dev/null +++ b/Compiler.Emit.C/NameMangler.cs @@ -0,0 +1,243 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; +using Nunycode; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + /// + /// This new name mangling scheme was adopted because the old scheme was hard + /// to read when output to the console. clang would not correctly output the + /// Unicode characters in it. The new scheme doesn't guarantee uniqueness, + /// but should be good enough until we switch to LLVM. + /// + /// * replace " " with "_" + /// * use "__" to separate segments + /// * use "__" and a number for arity + /// * special names are prefixed with "_" because names don't normally start with underscore + /// * encode with punycode + /// + /// + public class NameMangler + { + private const string Separator = "__"; + + // Note, we don't have to worry about whether the identifier starts with + // a number because it will end up prefixed anyway. + private static readonly Regex StandardIdentifierPattern = new Regex(@"^[_0-9a-zA-Z]+$", RegexOptions.Compiled); + + public string SelfName { get; } = "_self"; + + public string MangleName(FunctionIL function) + { + return Mangle(function.Symbol); + } + public string MangleName(MethodDeclarationIL method) + { + return Mangle(method.Symbol); + } + public string MangleName(FieldIL field) + { + return Mangle(field.Symbol); + } + public string MangleMethodName(MethodDeclarationIL method) + { + return MangleMethod(method.Symbol); + } + public string MangleName(ConstructorIL constructor) + { + return Mangle(constructor.Symbol); + } + public string MangleName(ClassIL @class) + { + return Mangle(@class.Symbol); + } + + public object Mangle(DataType type) + { + return type switch + { + SimpleType simpleType => Mangle(simpleType), + ObjectType objectType => Mangle(objectType), + _ => throw new NotImplementedException(), + //_ => throw ExhaustiveMatch.Failed(type), + }; + } + + public string Mangle(SimpleType type) + { + return Mangle(type.Name); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public string Mangle(ObjectType type) + { + // builder with room for the characters we are likely to add + var builder = new StringBuilder(EstimateNamespaceSize(type.ContainingNamespace) + EstimateSize(type.Name) + 5); + MangleNamespace(type.ContainingNamespace, builder); + Mangle(type.Name, builder); + return Punycode.ToAscii(builder.ToString()); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public string Mangle(TypeName name) + { + var builder = new StringBuilder(EstimateSize(name)); + Mangle(name, builder); + return Punycode.ToAscii(builder.ToString()); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public string Mangle(Symbol symbol) + { + var builder = new StringBuilder(EstimateSize(symbol.ContainingSymbol)); + Mangle(symbol, builder); + return Punycode.ToAscii(builder.ToString()); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public string MangleMethod(MethodSymbol symbol) + { + var builder = new StringBuilder(EstimateSize(symbol.ContainingSymbol)); + MangleMethod(symbol, builder); + return Punycode.ToAscii(builder.ToString()); + } + + private static int EstimateNamespaceSize(NamespaceName namespaceName) + { + return namespaceName.Segments.Sum(s => s.Text.Length + 2); + } + + private static int EstimateSize(TypeName? typeName) + { + return typeName switch + { + null => 0, + SpecialTypeName specialName => 1 + specialName.Text.Length, + Name name => name.Text.Length, + _ => throw ExhaustiveMatch.Failed(typeName) + }; + } + + private static int EstimateSize(Symbol? symbol) + { + // TODO this doesn't account for Arity etc + if (symbol is null || symbol is PackageSymbol) return 0; + return EstimateSize(symbol.ContainingSymbol) + 2 + EstimateSize(symbol.Name); + } + + private static void MangleNamespace(NamespaceName namespaceName, StringBuilder builder) + { + foreach (var name in namespaceName.Segments) + { + builder.Append(name.Text); + builder.Append(Separator); + } + } + + private static void Mangle(TypeName typeName, StringBuilder builder) + { + switch (typeName) + { + default: + throw ExhaustiveMatch.Failed(typeName); + case Name name: + ManglePart(name.Text, builder); + break; + case SpecialTypeName specialName: + builder.Append('_'); + ManglePart(specialName.Text, builder); + break; + } + } + + private static void Mangle(Symbol? symbol, StringBuilder builder) + { + switch (symbol) + { + default: + throw new NotImplementedException(symbol.GetType().Name); + case null: + // Nothing + break; + case NamespaceSymbol sym: + Mangle(sym.ContainingSymbol, builder); + AppendSeparator(sym.ContainingSymbol, builder); + Mangle(sym.Name, builder); + break; + case PackageSymbol _: + // Nothing + break; + case FunctionSymbol sym: + Mangle(sym.ContainingSymbol, builder); + AppendSeparator(sym.ContainingSymbol, builder); + Mangle(sym.Name, builder); + builder.Append(Separator); + builder.Append(sym.Arity); + break; + case ObjectTypeSymbol sym: + Mangle(sym.ContainingSymbol, builder); + AppendSeparator(sym.ContainingSymbol, builder); + Mangle(sym.Name, builder); + break; + case MethodSymbol sym: + Mangle(sym.ContainingSymbol, builder); + builder.Append(Separator); + MangleMethod(sym, builder); + break; + case PrimitiveTypeSymbol sym: + Mangle(sym.ContainingSymbol, builder); + AppendSeparator(sym.ContainingSymbol, builder); + Mangle(sym.Name, builder); + break; + case ConstructorSymbol sym: + Mangle(sym.ContainingSymbol, builder); + builder.Append(Separator); + builder.Append("_new"); + if (!(sym.Name is null)) + { + builder.Append("_"); + Mangle(sym.Name, builder); + } + builder.Append(Separator); + builder.Append(sym.Arity + 1); // add one for self parameter + break; + case FieldSymbol sym: + Mangle(sym.Name, builder); + break; + } + } + + private static void MangleMethod(MethodSymbol symbol, StringBuilder builder) + { + Mangle(symbol.Name, builder); + builder.Append(Separator); + builder.Append(symbol.Arity + 1); // add one for self parameter + } + + private static void AppendSeparator(Symbol? symbol, StringBuilder builder) + { + if (symbol is null || symbol is PackageSymbol) return; + builder.Append(Separator); + } + + internal static void ManglePart(string name, StringBuilder builder) + { + // Fast path no need to escape anything + if (StandardIdentifierPattern.IsMatch(name)) + { + builder.Append(name); + return; + } + + builder.Append(name.Replace(' ', '_')); + } + } +} diff --git a/Compiler.Emit.C/PackageEmitter.cs b/Compiler.Emit.C/PackageEmitter.cs new file mode 100644 index 00000000..a51855b4 --- /dev/null +++ b/Compiler.Emit.C/PackageEmitter.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class PackageEmitter : IEmitter + { + private readonly NameMangler nameMangler; + private readonly IEmitter declarationEmitter; + + public PackageEmitter( + NameMangler nameMangler, + IEmitter declarationEmitter) + { + this.nameMangler = nameMangler; + this.declarationEmitter = declarationEmitter; + } + + public void Emit(PackageIL package, Code code) + { + foreach (var declaration in package.Declarations) + declarationEmitter.Emit(declaration, code); + + EmitEntryPointAdapter(package.EntryPoint, code); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public void EmitPreamble(Code code) + { + // Setup the beginning of each section + code.Includes.AppendLine($"#include \"{CodeEmitter.RuntimeLibraryHeaderFileName}\""); + + code.TypeIdDeclaration.AppendLine("// Type ID Declarations"); + // Type ID Enum + code.TypeIdDeclaration.AppendLine("enum _Type_ID"); + code.TypeIdDeclaration.BeginBlock(); + code.TypeIdDeclaration.AppendLine("_never = 0,"); + // TODO setup primitive types? + + code.TypeDeclarations.AppendLine("// Type Declarations"); + code.FunctionDeclarations.AppendLine("// Function Declarations"); + code.StructDeclarations.AppendLine("// Struct Declarations"); + code.GlobalDefinitions.AppendLine("// Global Definitions"); + code.Definitions.AppendLine("// Definitions"); + } + + public void EmitEntryPointAdapter(FunctionIL entryPoint, Code code) + { + if (entryPoint is null) return; + + code.Definitions.DeclarationSeparatorLine(); + code.Definitions.AppendLine("// Entry Point Adapter"); + code.Definitions.AppendLine("int32_t main(const int argc, char const * const * const argv)"); + code.Definitions.BeginBlock(); + var arguments = new List(); + foreach (var parameterType in entryPoint.Parameters.Select(p => p.DataType).Cast()) + { + if (parameterType.ContainingNamespace == SystemConsole + && parameterType.Name == "Console") + { + code.Definitions.AppendLine( + "system__console__Console console = { &system__console__Console___vtable, malloc(sizeof(system__console__Console___Self)) };"); + arguments.Add("system__console__Console___new__1(console)"); + } + else if (parameterType.ContainingNamespace == SystemConsole + && parameterType.Name == "Arguments") + throw new NotImplementedException(); + else + throw new Exception($"Unexpected type for parameter to main: {parameterType}"); + } + var joinedArguments = string.Join(", ", arguments); + if (entryPoint.Symbol.ReturnDataType == DataType.Void) + { + code.Definitions.AppendLine($"{nameMangler.Mangle(entryPoint.Symbol)}({joinedArguments});"); + code.Definitions.AppendLine("return 0;"); + } + else + code.Definitions.AppendLine($"return {nameMangler.Mangle(entryPoint.Symbol)}({joinedArguments})._value;"); + + code.Definitions.EndBlock(); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public void EmitPostamble(Code code) + { + // Close the Type_ID enum + code.TypeIdDeclaration.EndBlockWithSemicolon(); + code.TypeIdDeclaration.AppendLine("typedef enum _Type_ID _Type_ID;"); + } + + private static readonly NamespaceName SystemConsole = new NamespaceName("system", "console"); + } +} diff --git a/Compiler.Emit.C/ParameterConverter.cs b/Compiler.Emit.C/ParameterConverter.cs new file mode 100644 index 00000000..a50baa02 --- /dev/null +++ b/Compiler.Emit.C/ParameterConverter.cs @@ -0,0 +1,32 @@ +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class ParameterConverter : IConverter + { + private readonly NameMangler nameMangler; + private readonly IConverter typeConverter; + + public ParameterConverter( + NameMangler nameMangler, + IConverter typeConverter) + { + this.typeConverter = typeConverter; + this.nameMangler = nameMangler; + } + + public string Convert(ParameterIL parameter) + { + var type = typeConverter.Convert(parameter.DataType); + return parameter switch + { + NamedParameterIL param => $"{type} {nameMangler.Mangle(param.Symbol.Name)}", + SelfParameterIL _ => $"{type} {nameMangler.SelfName}", + FieldParameterIL param => $"{type} {nameMangler.Mangle(param.InitializeField.Name)}", + _ => throw ExhaustiveMatch.Failed(parameter) + }; + } + } +} diff --git a/Compiler.Emit.C/Properties/Resources.Designer.cs b/Compiler.Emit.C/Properties/Resources.Designer.cs new file mode 100644 index 00000000..6a309cf3 --- /dev/null +++ b/Compiler.Emit.C/Properties/Resources.Designer.cs @@ -0,0 +1,111 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Azoth.Tools.Bootstrap.Compiler.Emit.C.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to #include "RuntimeLibrary.h" + ///#include <stdio.h> + ///#include <string.h> + /// + ///// Reminder: `extern` function declarations are so the compiler knows what + ///// object file to put the non-inline copies of inlined functions in. + /// + ///#define EXTERN_OPTIONAL_TYPE(type) \ + ///extern inline _opt__##type _opt__##type##__Some(type x); \ + ///const _opt__##type _opt__##type##__none = (_opt__##type) {0}; + /// + ///// `bool` type + ///extern inline _bool _bool__and(_bool x, _bool y); + ///extern inline _bool _bool__or(_bool x, _bool y); + /// + ///EXTERN_OPTIONAL_TYPE(_boo [rest of string was truncated]";. + /// + internal static string RuntimeLibraryCode { + get { + return ResourceManager.GetString("RuntimeLibraryCode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to #include <stdint.h> + /// + ///// Rather than including all of stdlib.h we only declare the things we need. + ///void* malloc(size_t size); + ///void free(void* ptr); + /// + ///// Cross platform way to mark a variable or parameter as unused to avoid warnings + ///#define _UNUSED(x) (void)(x) + /// + ///#define OPTIONAL_TYPE(type) \ + ///typedef struct { _Bool _hasValue; type _value; } _opt__##type; \ + ///inline _opt__##type _opt__##type##__Some(type x) { return (_opt__##type) {1, x}; } \ + ///extern const _opt__##type _opt__##type##__none; + /// + ///// `bool` type + ///typedef [rest of string was truncated]";. + /// + internal static string RuntimeLibraryHeader { + get { + return ResourceManager.GetString("RuntimeLibraryHeader", resourceCulture); + } + } + } +} diff --git a/Compiler.Emit.C/Properties/Resources.resx b/Compiler.Emit.C/Properties/Resources.resx new file mode 100644 index 00000000..6e553901 --- /dev/null +++ b/Compiler.Emit.C/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\RuntimeLibrary.c;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + + ..\RuntimeLibrary.h;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/Compiler.Emit.C/RuntimeLibrary.c b/Compiler.Emit.C/RuntimeLibrary.c new file mode 100644 index 00000000..012d1479 --- /dev/null +++ b/Compiler.Emit.C/RuntimeLibrary.c @@ -0,0 +1,122 @@ +#include "RuntimeLibrary.h" +#include +#include + +// Reminder: `extern` function declarations are so the compiler knows what +// object file to put the non-inline copies of inlined functions in. + +#define EXTERN_OPTIONAL_TYPE(type) \ +extern inline _opt__##type _opt__##type##__Some(type x); \ +const _opt__##type _opt__##type##__none = (_opt__##type) {0}; + +// `bool` type +extern inline _bool _bool__and(_bool x, _bool y); +extern inline _bool _bool__or(_bool x, _bool y); + +EXTERN_OPTIONAL_TYPE(_bool) + +// Extern Integer Type Operations Macro +#define EXTERN_INTEGER_OPERATIONS(type) \ +extern inline type type##__neg(type x); \ +extern inline type type##__add(type x, type y); \ +extern inline type type##__sub(type x, type y); \ +extern inline type type##__mul(type x, type y); \ +extern inline type type##__div(type x, type y); \ +extern inline type type##__remainder__2(type x, type y); \ +extern inline _bool type##__eq(type x, type y); \ +extern inline _bool type##__ne(type x, type y); \ +extern inline _bool type##__lt(type x, type y); \ +extern inline _bool type##__lte(type x, type y); \ +extern inline _bool type##__gt(type x, type y); \ +extern inline _bool type##__gte(type x, type y); + +// `int` type +EXTERN_INTEGER_OPERATIONS(_int) +EXTERN_OPTIONAL_TYPE(_int) + +// `uint` type +EXTERN_INTEGER_OPERATIONS(_uint) + +// `byte` type +EXTERN_INTEGER_OPERATIONS(_byte) + +// `size` type +EXTERN_INTEGER_OPERATIONS(_size) + +// `offset` type +EXTERN_INTEGER_OPERATIONS(_offset) + +// conversion functions +extern inline _int _convert___byte___int(_byte value); + +// Forward declare String that is in std lib for use by intrinsics +typedef struct String___Self String___Self; +typedef struct String___VTable String___VTable; +struct String +{ + String___VTable const* restrict _vtable; + String___Self* restrict _self; +}; +extern String___VTable String___vtable; + +String String___new__3(String _self, _size byte_count, _size bytes); + +// Intrinsic Functions +String _uint__to_display_string__1(_uint value) +{ + int length = snprintf(NULL, 0, "%u", value._value); + char* str = malloc(length + 1); + snprintf(str, length + 1, "%u", value._value); + String instance = (String){&String___vtable, malloc(sizeof(String))}; + _size byte_count = (_size) { length }; + _size bytes = (_size) { (uintptr_t)str }; + return String___new__3(instance, byte_count, bytes); +} +_size intrinsics__mem_allocate__1(_size length) +{ + void* mem = malloc(length._value); + return (_size) { (uintptr_t)mem }; +} +void intrinsics__mem_deallocate__1(_size ptr) +{ + free((void*)ptr._value); +} +void intrinsics__mem_copy__3(_size from_ptr, _size to_ptr, _size length) +{ + memcpy((void*)to_ptr._value, (void*)from_ptr._value, length._value); +} +extern inline void intrinsics__mem_set_byte__2(_size ptr, _byte byte); +extern inline _byte intrinsics__mem_get_byte__1(_size ptr); +void intrinsics__print_utf8__2(_size ptr, _size length) +{ + printf("%.*s", (int)length._value, (char*)ptr._value); +} +_size intrinsics__read_utf8_line__2(_size ptr, _size length) +{ + gets_s((char*)ptr._value, length._value); + return (_size) { strnlen_s((char*)ptr._value, 1024) }; +} + +// Test of calling windows memory allocation functions, rather than including +// windows.h, we directly declare the functions. This demonstrates that external +// function calls could do this. +//#include +typedef void* LPVOID; +typedef LPVOID HANDLE; +typedef size_t SIZE_T; +typedef uint32_t DWORD; +typedef int32_t BOOL; + +HANDLE GetProcessHeap(); +LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes); +LPVOID HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes); +BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); +DWORD GetLastError(); + +void test() +{ + void* ptr = HeapAlloc(GetProcessHeap(), 0, 10); + HeapFree(GetProcessHeap(), 0, ptr); + //void* ptr = aligned_alloc(8, 32); + //free(ptr); +} diff --git a/Compiler.Emit.C/RuntimeLibrary.h b/Compiler.Emit.C/RuntimeLibrary.h new file mode 100644 index 00000000..5c0e8c4b --- /dev/null +++ b/Compiler.Emit.C/RuntimeLibrary.h @@ -0,0 +1,84 @@ +#include + +// Rather than including all of stdlib.h we only declare the things we need. +void* malloc(size_t size); +void free(void* ptr); + +// Cross platform way to mark a variable or parameter as unused to avoid warnings +#define _UNUSED(x) (void)(x) + +#define OPTIONAL_TYPE(type) \ +typedef struct { _Bool _hasValue; type _value; } _opt__##type; \ +inline _opt__##type _opt__##type##__Some(type x) { return (_opt__##type) {1, x}; } \ +extern const _opt__##type _opt__##type##__none; + +// `bool` type +typedef struct { _Bool _value; } _bool; + +inline _bool _bool__and(_bool x, _bool y) { return (_bool) { x._value& y._value }; } +inline _bool _bool__or(_bool x, _bool y) { return (_bool) { x._value | y._value }; } + +OPTIONAL_TYPE(_bool) + +// Integer Type Operations Macro +#define INTEGER_OPERATIONS(type) \ +inline type type##__neg(type x) { return (type) { -x._value }; } \ +inline type type##__add(type x, type y) { return (type) { x._value + y._value }; } \ +inline type type##__sub(type x, type y) { return (type) { x._value - y._value }; } \ +inline type type##__mul(type x, type y) { return (type) { x._value* y._value }; } \ +inline type type##__div(type x, type y) { return (type) { x._value / y._value }; } \ +inline type type##__remainder__2(type x, type y) { return (type) { x._value% y._value }; } \ +inline _bool type##__eq(type x, type y) { return (_bool) { x._value == y._value }; } \ +inline _bool type##__ne(type x, type y) { return (_bool) { x._value != y._value }; } \ +inline _bool type##__lt(type x, type y) { return (_bool) { x._value < y._value }; } \ +inline _bool type##__lte(type x, type y) { return (_bool) { x._value <= y._value }; } \ +inline _bool type##__gt(type x, type y) { return (_bool) { x._value > y._value }; } \ +inline _bool type##__gte(type x, type y) { return (_bool) { x._value >= y._value }; } + +// `int` type +typedef struct { int32_t _value; } _int; + +INTEGER_OPERATIONS(_int) +OPTIONAL_TYPE(_int) + +// `uint` type +typedef struct { uint32_t _value; } _uint; + +INTEGER_OPERATIONS(_uint) + +// `byte` type +typedef struct { uint8_t _value; } _byte; + +INTEGER_OPERATIONS(_byte) + +// `size` type +typedef struct { uintptr_t _value; } _size; + +INTEGER_OPERATIONS(_size) + +// `offset` type +typedef struct { intptr_t _value; } _offset; + +INTEGER_OPERATIONS(_offset) + +// conversions +inline _int _convert___byte___int(_byte value) { return (_int) {value._value}; } + +// Forward declare String that is in std lib for use by intrinsics +typedef struct String String; + +// Intrinsic Functions +String _uint__to_display_string__1(_uint value); +_size intrinsics__mem_allocate__1(_size length); +void intrinsics__mem_deallocate__1(_size ptr); +void intrinsics__mem_copy__3(_size from_ptr, _size to_ptr, _size length); +inline void intrinsics__mem_set_byte__2(_size ptr, _byte byte) +{ + *((uint8_t*)ptr._value) = byte._value; +} +inline _byte intrinsics__mem_get_byte__1(_size ptr) +{ + return (_byte) { *((uint8_t*)ptr._value) }; +} +void intrinsics__print_utf8__2(_size ptr, _size length); +_size intrinsics__read_utf8_line__2(_size ptr, _size length); diff --git a/Compiler.Emit.C/TypeConverter.cs b/Compiler.Emit.C/TypeConverter.cs new file mode 100644 index 00000000..fce9c8fe --- /dev/null +++ b/Compiler.Emit.C/TypeConverter.cs @@ -0,0 +1,46 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit.C +{ + public class TypeConverter : IConverter + { + private readonly NameMangler nameMangler; + + public TypeConverter(NameMangler nameMangler) + { + this.nameMangler = nameMangler; + } + + public string Convert(DataType type) + { + switch (type) + { + default: + throw new NotImplementedException(); + //throw ExhaustiveMatch.Failed(type); + case VoidType _: + case NeverType _: + return "void"; + case SimpleType simpleType: + return nameMangler.Mangle(simpleType.Name); + case ObjectType t: + return nameMangler.Mangle(t); + //case PointerType ptr: + // var referenced = ptr.Referent.AssertKnown(); + // if (referenced == DataType.Any) + // return "void*"; + // return Convert(referenced) + "*"; + //case FunctionType functionType: + // return $"{Convert(functionType.ReturnType)}(*)({string.Join(", ", functionType.ParameterTypes.Select(Convert))})"; + case OptionalType optionalType: + { + if (optionalType.Referent is ReferenceType referenceType) + return Convert(referenceType); + + return "_opt__" + Convert(optionalType.Referent); + } + } + } + } +} diff --git a/Compiler.Emit/Compiler.Emit.csproj b/Compiler.Emit/Compiler.Emit.csproj new file mode 100644 index 00000000..bab65035 --- /dev/null +++ b/Compiler.Emit/Compiler.Emit.csproj @@ -0,0 +1,31 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Emit + Azoth.Tools.Bootstrap.Compiler.Emit + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/Compiler.Emit/Emitter.cs b/Compiler.Emit/Emitter.cs new file mode 100644 index 00000000..df1b465d --- /dev/null +++ b/Compiler.Emit/Emitter.cs @@ -0,0 +1,10 @@ +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; + +namespace Azoth.Tools.Bootstrap.Compiler.Emit +{ + public abstract class Emitter + { + public abstract void Emit(PackageIL package); + public abstract string GetEmittedCode(); + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Block.cs b/Compiler.IntermediateLanguage/CFG/Block.cs new file mode 100644 index 00000000..fca4e9b6 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Block.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG +{ + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + public class Block + { + public int Number { get; } + public FixedList Instructions { get; } + public TerminatorInstruction Terminator { get; } + + public Block(int number, FixedList instructions, TerminatorInstruction terminator) + { + Number = number; + Instructions = instructions; + Terminator = terminator; + } + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => $"BB #{Number}, Instructions={Instructions.Count+1}"; + } +} diff --git a/Compiler.IntermediateLanguage/CFG/ControlFlowGraph.cs b/Compiler.IntermediateLanguage/CFG/ControlFlowGraph.cs new file mode 100644 index 00000000..3139fff6 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/ControlFlowGraph.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG +{ + public class ControlFlowGraph + { + public CodeFile File { get; } + public FixedList VariableDeclarations { get; } + public IEnumerable Parameters => VariableDeclarations.Where(v => v.IsParameter); + public IEnumerable LocalVariables => VariableDeclarations.Where(v => !v.IsParameter); + //public VariableDeclaration ReturnVariable => VariableDeclarations[0]; + //public DataType ReturnType => ReturnVariable.Type; + public FixedList Blocks { get; } + + /// + /// If requested, the semantic analyzer will store the live variables here + /// + //public LiveVariables? LiveVariables { get; set; } + + public ControlFlowGraph( + CodeFile file, + IEnumerable variableDeclarations, + IEnumerable blocks) + { + File = file; + VariableDeclarations = variableDeclarations.ToFixedList(); + Blocks = blocks.ToFixedList(); + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/AssignmentInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/AssignmentInstruction.cs new file mode 100644 index 00000000..31d05cab --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/AssignmentInstruction.cs @@ -0,0 +1,22 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class AssignmentInstruction : InstructionWithResult + { + public Operand Operand { get; } + + public AssignmentInstruction(Place resultPlace, Operand operand, TextSpan span, Scope scope) + : base(resultPlace, span, scope) + { + Operand = operand; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = {Operand};"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicInstruction.cs new file mode 100644 index 00000000..ad7eae25 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicInstruction.cs @@ -0,0 +1,31 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class BooleanLogicInstruction : InstructionWithResult + { + public BooleanLogicOperator Operator { get; } + public Operand LeftOperand { get; } + public Operand RightOperand { get; } + + public BooleanLogicInstruction( + Place resultPlace, + BooleanLogicOperator @operator, + Operand leftOperand, + Operand rightOperand, + Scope scope) + : base(resultPlace, TextSpan.Covering(leftOperand.Span, rightOperand.Span), scope) + { + Operator = @operator; + LeftOperand = leftOperand; + RightOperand = rightOperand; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = {Operator.ToInstructionString()} {LeftOperand}, {RightOperand}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicOperator.cs b/Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicOperator.cs new file mode 100644 index 00000000..79bea4a2 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/BooleanLogicOperator.cs @@ -0,0 +1,23 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public enum BooleanLogicOperator + { + And = 1, + Or, + } + + public static class BooleanLogicOperatorExtensions + { + public static string ToInstructionString(this BooleanLogicOperator @operator) + { + return @operator switch + { + BooleanLogicOperator.And => "AND", + BooleanLogicOperator.Or => "OR", + _ => throw ExhaustiveMatch.Failed(@operator) + }; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/CallInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/CallInstruction.cs new file mode 100644 index 00000000..d2a5fd18 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/CallInstruction.cs @@ -0,0 +1,78 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class CallInstruction : Instruction + { + public Place? ResultPlace { get; } + public InvocableSymbol Function { get; } + public Operand? Self { get; } + public bool IsMethodCall => !(Self is null); + public FixedList Arguments { get; } + public int Arity => Arguments.Count; + + private CallInstruction(Place? resultPlace, Operand? self, InvocableSymbol function, FixedList arguments, TextSpan span, Scope scope) + : base(span, scope) + { + ResultPlace = resultPlace; + Self = self; + Function = function; + Arguments = arguments; + } + + public static CallInstruction ForFunction( + Place resultPlace, + FunctionSymbol function, + FixedList arguments, + TextSpan span, + Scope scope) + { + return new CallInstruction(resultPlace, null, function, arguments, span, scope); + } + + public static CallInstruction ForFunction( + FunctionSymbol function, + FixedList arguments, + TextSpan span, + Scope scope) + { + return new CallInstruction(null, null, function, arguments, span, scope); + } + + public static CallInstruction ForMethod( + Place resultPlace, + Operand self, + MethodSymbol method, + FixedList arguments, + TextSpan span, + Scope scope) + { + return new CallInstruction(resultPlace, self, method, arguments, span, scope); + } + + public static CallInstruction ForMethod( + Operand self, + MethodSymbol method, + FixedList arguments, + TextSpan span, + Scope scope) + { + return new CallInstruction(null, self, method, arguments, span, scope); + } + + public override string ToInstructionString() + { + var result = ResultPlace != null ? $"{ResultPlace} = " : ""; + var selfArgument = Self?.ToString(); + if (!(selfArgument is null)) + selfArgument = $"({selfArgument})."; + var arguments = string.Join(", ", Arguments); + var callType = IsMethodCall ? "METHOD" : "FN"; + return $"{result}CALL.{callType}{selfArgument}({arguments}) {Function}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/CallVirtualInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/CallVirtualInstruction.cs new file mode 100644 index 00000000..aa9d8e7f --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/CallVirtualInstruction.cs @@ -0,0 +1,47 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class CallVirtualInstruction : Instruction + { + public Place? ResultPlace { get; } + public MethodSymbol Method { get; } + public Operand Self { get; } + public FixedList Arguments { get; } + public int Arity => Arguments.Count; + + public CallVirtualInstruction( + Place? resultPlace, + Operand self, + MethodSymbol method, + FixedList arguments, + TextSpan span, + Scope scope) + : base(span, scope) + { + ResultPlace = resultPlace; + Self = self; + Method = method; + Arguments = arguments; + } + + public CallVirtualInstruction( + Operand self, + MethodSymbol method, + FixedList arguments, + TextSpan span, + Scope scope) + : this(null, self, method, arguments, span, scope) { } + + public override string ToInstructionString() + { + var result = ResultPlace != null ? $"{ResultPlace} = " : ""; + var arguments = string.Join(", ", Arguments); + return $"{result}CALL.VIRT({Self}).({arguments}) {Method}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/CompareInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/CompareInstruction.cs new file mode 100644 index 00000000..b106c3a4 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/CompareInstruction.cs @@ -0,0 +1,35 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class CompareInstruction : InstructionWithResult + { + public CompareInstructionOperator Operator { get; } + public NumericType Type { get; } + public Operand LeftOperand { get; } + public Operand RightOperand { get; } + + public CompareInstruction( + Place resultPlace, + CompareInstructionOperator @operator, + NumericType type, + Operand leftOperand, + Operand rightOperand, + Scope scope) + : base(resultPlace, TextSpan.Covering(leftOperand.Span, rightOperand.Span), scope) + { + Operator = @operator; + LeftOperand = leftOperand; + RightOperand = rightOperand; + Type = type; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = {Operator.ToInstructionString()}[{Type}] {LeftOperand}, {RightOperand}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/CompareInstructionOperator.cs b/Compiler.IntermediateLanguage/CFG/Instructions/CompareInstructionOperator.cs new file mode 100644 index 00000000..eb00ba87 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/CompareInstructionOperator.cs @@ -0,0 +1,31 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public enum CompareInstructionOperator + { + Equal, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + } + + public static class CompareInstructionOperatorExtensions + { + public static string ToInstructionString(this CompareInstructionOperator @operator) + { + return @operator switch + { + CompareInstructionOperator.Equal => "EQ", + CompareInstructionOperator.NotEqual => "NE", + CompareInstructionOperator.LessThan => "LT", + CompareInstructionOperator.LessThanOrEqual => "LTE", + CompareInstructionOperator.GreaterThan => "GT", + CompareInstructionOperator.GreaterThanOrEqual => "GTE", + _ => throw ExhaustiveMatch.Failed(@operator) + }; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/ConvertInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/ConvertInstruction.cs new file mode 100644 index 00000000..0d0c10c1 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/ConvertInstruction.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class ConvertInstruction : InstructionWithResult + { + public Operand Operand { get; } + public NumericType FromType { get; } + public NumericType ToType { get; } + + public ConvertInstruction( + Place resultPlace, + Operand operand, + NumericType fromType, + NumericType toType, + TextSpan span, + Scope scope) + : base(resultPlace, span, scope) + { + Operand = operand; + FromType = fromType; + ToType = toType; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = CONVERT[{FromType},{ToType}] {Operand}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/FieldAccessInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/FieldAccessInstruction.cs new file mode 100644 index 00000000..957ffed9 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/FieldAccessInstruction.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class FieldAccessInstruction : InstructionWithResult + { + public Operand Operand { get; } + public FieldSymbol Field { get; } + + public FieldAccessInstruction( + Place resultPlace, + Operand operand, + FieldSymbol field, + TextSpan span, + Scope scope) + : base(resultPlace, span, scope) + { + Operand = operand; + Field = field; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = LOAD[{Field.DataType}] {Operand}, {Field.ContainingSymbol}::{Field.Name}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/Instruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/Instruction.cs new file mode 100644 index 00000000..cd849f95 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/Instruction.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + [Closed( + typeof(CallInstruction), + typeof(CallVirtualInstruction), + typeof(InstructionWithResult))] + public abstract class Instruction + { + public TextSpan Span { get; } + public Scope Scope { get; } + + protected Instruction(TextSpan span, Scope scope) + { + Span = span; + Scope = scope; + } + + public override string ToString() + { + return ToInstructionString() + ContextCommentString(); + } + + public abstract string ToInstructionString(); + + public virtual string ContextCommentString() + { + return $" // at {Span} in {Scope}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/InstructionWithResult.cs b/Compiler.IntermediateLanguage/CFG/Instructions/InstructionWithResult.cs new file mode 100644 index 00000000..b25d7aae --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/InstructionWithResult.cs @@ -0,0 +1,31 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + [Closed( + typeof(AssignmentInstruction), + typeof(CompareInstruction), + typeof(ConvertInstruction), + typeof(FieldAccessInstruction), + typeof(LoadBoolInstruction), + typeof(LoadIntegerInstruction), + typeof(LoadStringInstruction), + typeof(LoadNoneInstruction), + typeof(NegateInstruction), + typeof(NewObjectInstruction), + typeof(NumericInstruction), + typeof(SomeInstruction), + typeof(BooleanLogicInstruction))] + public abstract class InstructionWithResult : Instruction + { + public Place ResultPlace { get; } + + protected InstructionWithResult(Place resultPlace, TextSpan span, Scope scope) + : base(span, scope) + { + ResultPlace = resultPlace; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/LoadBoolInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/LoadBoolInstruction.cs new file mode 100644 index 00000000..7efc9054 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/LoadBoolInstruction.cs @@ -0,0 +1,22 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class LoadBoolInstruction : InstructionWithResult + { + public bool Value { get; } + + public LoadBoolInstruction(Place resultPlace, bool value, TextSpan span, Scope scope) + : base(resultPlace, span, scope) + { + Value = value; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = LOAD[{DataType.Bool}] {Value}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/LoadIntegerInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/LoadIntegerInstruction.cs new file mode 100644 index 00000000..f6815bae --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/LoadIntegerInstruction.cs @@ -0,0 +1,25 @@ +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class LoadIntegerInstruction : InstructionWithResult + { + public BigInteger Value { get; } + public IntegerType Type { get; } + + public LoadIntegerInstruction(Place resultPlace, BigInteger value, IntegerType type, TextSpan span, Scope scope) + : base(resultPlace, span, scope) + { + Value = value; + Type = type; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = LOAD[{Type}] {Value}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/LoadNoneInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/LoadNoneInstruction.cs new file mode 100644 index 00000000..26c0e5fc --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/LoadNoneInstruction.cs @@ -0,0 +1,22 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class LoadNoneInstruction : InstructionWithResult + { + public OptionalType Type { get; } + + public LoadNoneInstruction(Place resultPlace, OptionalType type, TextSpan span, Scope scope) + : base(resultPlace, span, scope) + { + Type = type; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = LOAD[{Type}] none"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/LoadStringInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/LoadStringInstruction.cs new file mode 100644 index 00000000..a9b7497c --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/LoadStringInstruction.cs @@ -0,0 +1,25 @@ +using System.Text; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class LoadStringInstruction : InstructionWithResult + { + public static readonly Encoding Encoding = new UTF8Encoding(false); + + public string Value { get; } + + public LoadStringInstruction(Place resultPlace, string value, TextSpan span, Scope scope) + : base(resultPlace, span, scope) + { + Value = value; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = LOAD \"{Value.Escape()}\""; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/NegateInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/NegateInstruction.cs new file mode 100644 index 00000000..1edda57c --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/NegateInstruction.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class NegateInstruction : InstructionWithResult + { + public Operand Operand { get; } + public NumericType Type { get; } + + public NegateInstruction( + Place resultPlace, + NumericType type, + Operand operand, + TextSpan span, + Scope scope) + : base(resultPlace, span, scope) + { + Type = type; + Operand = operand; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = NEG[{Type}] {Operand}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/NewObjectInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/NewObjectInstruction.cs new file mode 100644 index 00000000..25b18d45 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/NewObjectInstruction.cs @@ -0,0 +1,41 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class NewObjectInstruction : InstructionWithResult + { + public ConstructorSymbol Constructor { get; } + /// + /// The type being constructed + /// + public ObjectType ConstructedType { get; } + public FixedList Arguments { get; } + public int Arity => Arguments.Count; + + public NewObjectInstruction( + Place resultPlace, + ConstructorSymbol constructor, + ObjectType constructedType, + FixedList arguments, + TextSpan span, + Scope scope) + : base(resultPlace, span, scope) + { + Constructor = constructor; + ConstructedType = constructedType; + Arguments = arguments; + } + + public override string ToInstructionString() + { + var result = ResultPlace != null ? $"{ResultPlace} = " : ""; + var arguments = string.Join(", ", Arguments); + return $"{result}NEW {Constructor}({arguments})"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/NumericInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/NumericInstruction.cs new file mode 100644 index 00000000..4dc12196 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/NumericInstruction.cs @@ -0,0 +1,36 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public class NumericInstruction : InstructionWithResult + { + public NumericInstructionOperator Operator { get; } + + public Operand LeftOperand { get; } + public Operand RightOperand { get; } + public NumericType Type { get; } + + public NumericInstruction( + Place resultPlace, + NumericInstructionOperator @operator, + NumericType type, + Operand leftOperand, + Operand rightOperand, + Scope scope) + : base(resultPlace, TextSpan.Covering(leftOperand.Span, rightOperand.Span), scope) + { + LeftOperand = leftOperand; + RightOperand = rightOperand; + Operator = @operator; + Type = type; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = {Operator.ToInstructionString()}[{Type}] {LeftOperand}, {RightOperand}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/NumericInstructionOperator.cs b/Compiler.IntermediateLanguage/CFG/Instructions/NumericInstructionOperator.cs new file mode 100644 index 00000000..aafc6897 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/NumericInstructionOperator.cs @@ -0,0 +1,27 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + public enum NumericInstructionOperator + { + Add, + Subtract, + Multiply, + Divide, + } + + public static class NumericInstructionOperatorExtensions + { + public static string ToInstructionString(this NumericInstructionOperator @operator) + { + return @operator switch + { + NumericInstructionOperator.Add => "ADD", + NumericInstructionOperator.Subtract => "SUB", + NumericInstructionOperator.Multiply => "MUL", + NumericInstructionOperator.Divide => "DIV", + _ => throw ExhaustiveMatch.Failed(@operator) + }; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Instructions/SomeInstruction.cs b/Compiler.IntermediateLanguage/CFG/Instructions/SomeInstruction.cs new file mode 100644 index 00000000..8094566d --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Instructions/SomeInstruction.cs @@ -0,0 +1,28 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions +{ + /// + /// Constructs a value of an optional type from the non-optional value + /// + public class SomeInstruction : InstructionWithResult + { + public Operand Operand { get; } + public OptionalType Type { get; } + + public SomeInstruction(Place resultPlace, OptionalType type, Operand operand, TextSpan span, Scope scope) + : base(resultPlace, span, scope) + { + Type = type; + Operand = operand; + } + + public override string ToInstructionString() + { + return $"{ResultPlace} = SOME[{Type}] {Operand}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Operands/Operand.cs b/Compiler.IntermediateLanguage/CFG/Operands/Operand.cs new file mode 100644 index 00000000..c767fbfd --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Operands/Operand.cs @@ -0,0 +1,17 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands +{ + [Closed( + typeof(VariableReference))] + public abstract class Operand + { + public TextSpan Span { get; } + + protected Operand(in TextSpan span) + { + Span = span; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Operands/VariableReference.cs b/Compiler.IntermediateLanguage/CFG/Operands/VariableReference.cs new file mode 100644 index 00000000..4ccdb355 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Operands/VariableReference.cs @@ -0,0 +1,35 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands +{ + public class VariableReference : Operand + { + public Variable Variable { get; } + + public VariableReference(Variable variable, TextSpan span) + : base(span) + { + Variable = variable; + } + + public override string ToString() + { + return Variable.ToString(); + } + + //public VariableReference AsOwn(TextSpan span) + //{ + // return new VariableReference(Variable, OldValueSemantics.Own, span); + //} + + //public VariableReference AsBorrow() + //{ + // return ValueSemantics == OldValueSemantics.Borrow ? this : new VariableReference(Variable, OldValueSemantics.Borrow, Span); + //} + + //public VariableReference AsAlias() + //{ + // return ValueSemantics == OldValueSemantics.Share ? this : new VariableReference(Variable, OldValueSemantics.Share, Span); + //} + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Places/FieldPlace.cs b/Compiler.IntermediateLanguage/CFG/Places/FieldPlace.cs new file mode 100644 index 00000000..62558632 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Places/FieldPlace.cs @@ -0,0 +1,30 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places +{ + public class FieldPlace : Place + { + public Operand Target { get; } + public FieldSymbol Field { get; } + + public FieldPlace(Operand target, FieldSymbol field, TextSpan span) + : base(span) + { + Target = target; + Field = field; + } + + public override Operand ToOperand(TextSpan span) + { + throw new NotImplementedException(); + } + + public override string ToString() + { + return $"({Target}).{Field.Name}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Places/Place.cs b/Compiler.IntermediateLanguage/CFG/Places/Place.cs new file mode 100644 index 00000000..04faa0c9 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Places/Place.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places +{ + [Closed( + typeof(VariablePlace), + typeof(FieldPlace))] + public abstract class Place + { + public TextSpan Span { get; } + + protected Place(TextSpan span) + { + Span = span; + } + + public abstract Operand ToOperand(TextSpan span); + + public abstract override string ToString(); + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Places/VariablePlace.cs b/Compiler.IntermediateLanguage/CFG/Places/VariablePlace.cs new file mode 100644 index 00000000..95268094 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Places/VariablePlace.cs @@ -0,0 +1,27 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places +{ + public class VariablePlace : Place + { + public Variable Variable { get; } + + public VariablePlace(Variable variable, TextSpan span) + : base(span) + { + Variable = variable; + } + + public override Operand ToOperand(TextSpan span) + { + // TODO is this the correct value semantics? + return new VariableReference(Variable, span); + } + + public override string ToString() + { + return Variable.ToString(); + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Scope.cs b/Compiler.IntermediateLanguage/CFG/Scope.cs new file mode 100644 index 00000000..747aa012 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Scope.cs @@ -0,0 +1,48 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG +{ + public struct Scope : IEquatable + { + private readonly int number; + + public static readonly Scope Outer = new Scope(0); + + private Scope(int number) + { + this.number = number; + } + + public Scope Next() => new Scope(number + 1); + + public override string ToString() + { + return "scope " + number; + } + + public static bool operator ==(Scope a, Scope b) + { + return a.number == b.number; + } + + public static bool operator !=(Scope a, Scope b) + { + return a.number != b.number; + } + + public override int GetHashCode() + { + return HashCode.Combine(number); + } + + public bool Equals(Scope other) + { + return this.number == other.number; + } + + public override bool Equals(object? obj) + { + return obj is Scope other && number == other.number; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/StringConstant.cs b/Compiler.IntermediateLanguage/CFG/StringConstant.cs new file mode 100644 index 00000000..6295c9f6 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/StringConstant.cs @@ -0,0 +1,14 @@ +using System.Text; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG +{ + public static class StringConstant + { + public static readonly Encoding Encoding = new UTF8Encoding(false); + + public static int GetByteLength(string value) + { + return Encoding.GetByteCount(value); + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/GotoInstruction.cs b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/GotoInstruction.cs new file mode 100644 index 00000000..4a9f2279 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/GotoInstruction.cs @@ -0,0 +1,20 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions +{ + public class GotoInstruction : TerminatorInstruction + { + public int BlockNumber { get; } + + public GotoInstruction(int blockNumber, TextSpan span, Scope scope) + : base(span, scope) + { + BlockNumber = blockNumber; + } + + public override string ToInstructionString() + { + return $"GOTO BB #{BlockNumber}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/IfInstruction.cs b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/IfInstruction.cs new file mode 100644 index 00000000..ad5f15ea --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/IfInstruction.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions +{ + public class IfInstruction : TerminatorInstruction + { + public Operand Condition { get; } + public int ThenBlockNumber { get; } + public int ElseBlockNumber { get; } + + public IfInstruction( + Operand condition, + int thenBlockNumber, + int elseBlockNumber, + TextSpan span, + Scope scope) + : base(span, scope) + { + Condition = condition; + ThenBlockNumber = thenBlockNumber; + ElseBlockNumber = elseBlockNumber; + } + + public override string ToInstructionString() + { + return $"IF {Condition} GOTO BB #{ThenBlockNumber} ELSE GOTO BB #{ElseBlockNumber};"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnValueInstruction.cs b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnValueInstruction.cs new file mode 100644 index 00000000..e021d90b --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnValueInstruction.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions +{ + public class ReturnValueInstruction : TerminatorInstruction + { + public Operand Value { get; } + + public ReturnValueInstruction(Operand value, TextSpan span, Scope scope) + : base(span, scope) + { + Value = value; + } + + public override string ToInstructionString() + { + return $"RETURN {Value}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnVoidInstruction.cs b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnVoidInstruction.cs new file mode 100644 index 00000000..5abd0735 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/ReturnVoidInstruction.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions +{ + public class ReturnVoidInstruction : TerminatorInstruction + { + public ReturnVoidInstruction(TextSpan span, Scope scope) + : base(span, scope) + + { + } + + public override string ToInstructionString() + { + return "RETURN"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/TerminatorInstruction.cs b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/TerminatorInstruction.cs new file mode 100644 index 00000000..833379e3 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/TerminatorInstructions/TerminatorInstruction.cs @@ -0,0 +1,37 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions +{ + /// + /// An instruction that terminates a block + /// + [Closed( + typeof(GotoInstruction), + typeof(IfInstruction), + typeof(ReturnValueInstruction), + typeof(ReturnVoidInstruction))] + public abstract class TerminatorInstruction + { + public TextSpan Span { get; } + public Scope Scope { get; } + + protected TerminatorInstruction(TextSpan span, Scope scope) + { + Span = span; + Scope = scope; + } + + public override string ToString() + { + return ToInstructionString() + ContextCommentString(); + } + + public abstract string ToInstructionString(); + + public virtual string ContextCommentString() + { + return $" // at {Span} in {Scope}"; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/Variable.cs b/Compiler.IntermediateLanguage/CFG/Variable.cs new file mode 100644 index 00000000..03b7125a --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/Variable.cs @@ -0,0 +1,48 @@ +using System; +using System.Globalization; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG +{ + public readonly struct Variable : IEquatable + { + public int Number { get; } + public string Name => Number.ToString(CultureInfo.InvariantCulture); + + public Variable(int number) + { + Number = number; + } + + public static readonly Variable Self = new Variable(0); + + public override string ToString() + { + return "%" + Name; + } + + public static bool operator ==(Variable a, Variable b) + { + return a.Number == b.Number; + } + + public static bool operator !=(Variable a, Variable b) + { + return a.Number != b.Number; + } + + public override int GetHashCode() + { + return HashCode.Combine(Number); + } + + public bool Equals(Variable other) + { + return Number == other.Number; + } + + public override bool Equals(object? obj) + { + return obj is Variable other && Number == other.Number; + } + } +} diff --git a/Compiler.IntermediateLanguage/CFG/VariableDeclaration.cs b/Compiler.IntermediateLanguage/CFG/VariableDeclaration.cs new file mode 100644 index 00000000..88168eb6 --- /dev/null +++ b/Compiler.IntermediateLanguage/CFG/VariableDeclaration.cs @@ -0,0 +1,81 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG +{ + public class VariableDeclaration + { + /// + /// Which IL variable is being declared + /// + public Variable Variable { get; } + + /// + /// What scope is this variable in. The %result or self parameter of a + /// constructor don't have scopes. + /// + public Scope? Scope { get; } + + /// + /// If this declaration corresponds to an argument or local variable, + /// The symbol. (All temp variables don't have a symbol) + /// + public BindingSymbol? Symbol { get; } + public bool IsParameter { get; } + public bool IsMutableBinding { get; } + public DataType Type { get; } + public bool TypeIsNotEmpty => !Type.IsEmpty; + + public VariableDeclaration( + bool isParameter, + bool isMutableBinding, + DataType type, + Variable variable, + Scope? scope, + BindingSymbol? symbol = null) + { + IsParameter = isParameter; + Variable = variable; + Scope = scope; + Symbol = symbol; + IsMutableBinding = isMutableBinding; + Type = type; + } + + public VariableReference Reference(TextSpan span) + { + return new VariableReference(Variable, span); + } + + //public VariableReference Move(TextSpan span) + //{ + // return new VariableReference(Variable, span); + //} + + public VariablePlace Place(TextSpan span) + { + return new VariablePlace(Variable, span); + } + + // Useful for debugging + public override string ToString() + { + return ToStatementString() + ContextCommentString(); + } + + public string ToStatementString() + { + var binding = IsMutableBinding ? "var" : "let"; + var name = Symbol is null ? "" : $"({Symbol.Name})"; + return $"{binding} {Variable}{name}: {Type};"; + } + + public string ContextCommentString() + { + return Scope != null ? $" // in {Scope}" : ""; + } + } +} diff --git a/Compiler.IntermediateLanguage/ClassIL.cs b/Compiler.IntermediateLanguage/ClassIL.cs new file mode 100644 index 00000000..ab2d4891 --- /dev/null +++ b/Compiler.IntermediateLanguage/ClassIL.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public class ClassIL : DeclarationIL + { + public FixedList Members { get; } + public new ObjectTypeSymbol Symbol { get; } + + public ClassIL(ObjectTypeSymbol symbol, FixedList members) + : base(false, symbol) + { + Symbol = symbol; + Members = members; + } + } +} diff --git a/Compiler.IntermediateLanguage/Compiler.IntermediateLanguage.csproj b/Compiler.IntermediateLanguage/Compiler.IntermediateLanguage.csproj new file mode 100644 index 00000000..98f6f556 --- /dev/null +++ b/Compiler.IntermediateLanguage/Compiler.IntermediateLanguage.csproj @@ -0,0 +1,36 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage + Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/Compiler.IntermediateLanguage/ConstructorIL.cs b/Compiler.IntermediateLanguage/ConstructorIL.cs new file mode 100644 index 00000000..212c90df --- /dev/null +++ b/Compiler.IntermediateLanguage/ConstructorIL.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public class ConstructorIL : DeclarationIL, IInvocableDeclarationIL + { + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "NA")] + bool IInvocableDeclarationIL.IsConstructor => true; + public FixedList Parameters { get; } + public int Arity => Parameters.Count; + public FixedList FieldInitializations { get; } + public ControlFlowGraph IL { get; } + public new ConstructorSymbol Symbol { get; } + + public ConstructorIL( + ConstructorSymbol symbol, + FixedList parameters, + FixedList fieldInitializations, + ControlFlowGraph il) + : base(true, symbol) + { + IL = il; + FieldInitializations = fieldInitializations; + Symbol = symbol; + Parameters = parameters; + } + } +} diff --git a/Compiler.IntermediateLanguage/DeclarationIL.cs b/Compiler.IntermediateLanguage/DeclarationIL.cs new file mode 100644 index 00000000..9285493e --- /dev/null +++ b/Compiler.IntermediateLanguage/DeclarationIL.cs @@ -0,0 +1,29 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + // TODO consider naming these just Class, Function, Field, Constructor. While they are declarations, the IL is what we think of as actually being the thing of which the code is declaring + [Closed( + typeof(ClassIL), + typeof(FunctionIL), + typeof(MethodDeclarationIL), + typeof(FieldIL), + typeof(ConstructorIL))] + public abstract class DeclarationIL + { + public bool IsMember { get; } + public Symbol Symbol { get; } + + protected DeclarationIL(bool isMember, Symbol symbol) + { + IsMember = isMember; + Symbol = symbol; + } + + public override string ToString() + { + return Symbol.ToString(); + } + } +} diff --git a/Compiler.IntermediateLanguage/FieldIL.cs b/Compiler.IntermediateLanguage/FieldIL.cs new file mode 100644 index 00000000..cac1df9b --- /dev/null +++ b/Compiler.IntermediateLanguage/FieldIL.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public class FieldIL : DeclarationIL + { + public bool IsMutableBinding => Symbol.IsMutableBinding; + public DataType DataType => Symbol.DataType; + public new FieldSymbol Symbol { get; } + + public FieldIL(FieldSymbol symbol) + : base(true, symbol) + { + Symbol = symbol; + } + } +} diff --git a/Compiler.IntermediateLanguage/FieldInitializationIL.cs b/Compiler.IntermediateLanguage/FieldInitializationIL.cs new file mode 100644 index 00000000..e978187b --- /dev/null +++ b/Compiler.IntermediateLanguage/FieldInitializationIL.cs @@ -0,0 +1,17 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + /// + /// A field initialization in an initialization caused by a constructor parameter + /// + public class FieldInitializationIL + { + public FieldSymbol Field { get; } + + public FieldInitializationIL(FieldSymbol field) + { + Field = field; + } + } +} diff --git a/Compiler.IntermediateLanguage/FieldParameterIL.cs b/Compiler.IntermediateLanguage/FieldParameterIL.cs new file mode 100644 index 00000000..36fe6387 --- /dev/null +++ b/Compiler.IntermediateLanguage/FieldParameterIL.cs @@ -0,0 +1,15 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public sealed class FieldParameterIL : ParameterIL + { + public new FieldSymbol InitializeField { get; } + + public FieldParameterIL(FieldSymbol initializeField) + : base(null, false, initializeField.DataType, initializeField) + { + InitializeField = initializeField; + } + } +} diff --git a/Compiler.IntermediateLanguage/FunctionIL.cs b/Compiler.IntermediateLanguage/FunctionIL.cs new file mode 100644 index 00000000..28ce685c --- /dev/null +++ b/Compiler.IntermediateLanguage/FunctionIL.cs @@ -0,0 +1,34 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public class FunctionIL : DeclarationIL, IInvocableDeclarationIL + { + public bool IsExternal { get; } + + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "NA")] + bool IInvocableDeclarationIL.IsConstructor => false; + + public FixedList Parameters { get; } + public int Arity => Parameters.Count; + public new FunctionSymbol Symbol { get; } + public ControlFlowGraph IL { get; } + + public FunctionIL( + bool isExternal, + bool isMember, + FunctionSymbol symbol, + FixedList parameters, + ControlFlowGraph il) + : base(isMember, symbol) + { + Parameters = parameters; + Symbol = symbol; + IL = il; + IsExternal = isExternal; + } + } +} diff --git a/Compiler.IntermediateLanguage/IInvocableDeclarationIL.cs b/Compiler.IntermediateLanguage/IInvocableDeclarationIL.cs new file mode 100644 index 00000000..aef52338 --- /dev/null +++ b/Compiler.IntermediateLanguage/IInvocableDeclarationIL.cs @@ -0,0 +1,17 @@ +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + [Closed( + typeof(FunctionIL), + typeof(MethodDeclarationIL), + typeof(ConstructorIL))] + public interface IInvocableDeclarationIL + { + bool IsConstructor { get; } + FixedList Parameters { get; } + ControlFlowGraph? IL { get; } + } +} diff --git a/Compiler.IntermediateLanguage/MethodDeclarationIL.cs b/Compiler.IntermediateLanguage/MethodDeclarationIL.cs new file mode 100644 index 00000000..bace83b1 --- /dev/null +++ b/Compiler.IntermediateLanguage/MethodDeclarationIL.cs @@ -0,0 +1,32 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public class MethodDeclarationIL : DeclarationIL, IInvocableDeclarationIL + { + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", + Justification = "NA")] + bool IInvocableDeclarationIL.IsConstructor => false; + public ParameterIL SelfParameter { get; } + public FixedList Parameters { get; } + public int Arity => Parameters.Count; + public new MethodSymbol Symbol { get; } + public ControlFlowGraph? IL { get; } + + public MethodDeclarationIL( + MethodSymbol symbol, + ParameterIL selfParameter, + FixedList parameters, + ControlFlowGraph? il) + : base(true, symbol) + { + SelfParameter = selfParameter; + Parameters = parameters; + Symbol = symbol; + IL = il; + } + } +} diff --git a/Compiler.IntermediateLanguage/NamedParameterIL.cs b/Compiler.IntermediateLanguage/NamedParameterIL.cs new file mode 100644 index 00000000..fe9b9364 --- /dev/null +++ b/Compiler.IntermediateLanguage/NamedParameterIL.cs @@ -0,0 +1,15 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public sealed class NamedParameterIL : ParameterIL + { + public new VariableSymbol Symbol { get; } + + public NamedParameterIL(VariableSymbol symbol) + : base(symbol, symbol.IsMutableBinding, symbol.DataType) + { + Symbol = symbol; + } + } +} diff --git a/Compiler.IntermediateLanguage/PackageIL.cs b/Compiler.IntermediateLanguage/PackageIL.cs new file mode 100644 index 00000000..3629918f --- /dev/null +++ b/Compiler.IntermediateLanguage/PackageIL.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public class PackageIL + { + public PackageSymbol Symbol { get; } + public FixedSymbolTree SymbolTree { get; } + public FixedList Diagnostics { get; internal set; } + /// + /// Referenced packages + /// + /// No longer need aliases. All references are now explicit. + public FixedSet References { get; } + public FixedSet Declarations { get; } + public FunctionIL EntryPoint { get; internal set; } + + public PackageIL( + FixedSymbolTree symbolTree, + FixedList diagnostics, + FixedSet references, + IEnumerable declarations, + FunctionIL entryPoint) + { + Symbol = symbolTree.Package; + SymbolTree = symbolTree; + Diagnostics = diagnostics; + References = references; + EntryPoint = entryPoint; + Declarations = declarations.ToFixedSet(); + } + + public IEnumerable GetNonMemberDeclarations() + { + return Declarations.Where(d => !d.IsMember); + } + } +} diff --git a/Compiler.IntermediateLanguage/ParameterIL.cs b/Compiler.IntermediateLanguage/ParameterIL.cs new file mode 100644 index 00000000..7d569f71 --- /dev/null +++ b/Compiler.IntermediateLanguage/ParameterIL.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + [Closed( + typeof(SelfParameterIL), + typeof(NamedParameterIL), + typeof(FieldParameterIL))] + public abstract class ParameterIL + { + public BindingSymbol? Symbol { get; } + public bool IsMutableBinding { get; } + public DataType DataType { get; internal set; } + public FieldSymbol? InitializeField { get; } + + protected ParameterIL( + BindingSymbol? symbol, + bool isMutableBinding, + DataType type, + FieldSymbol? initializeField = null) + { + Symbol = symbol; + IsMutableBinding = isMutableBinding; + DataType = type; + InitializeField = initializeField; + } + } +} diff --git a/Compiler.IntermediateLanguage/README.md b/Compiler.IntermediateLanguage/README.md new file mode 100644 index 00000000..9aa803ed --- /dev/null +++ b/Compiler.IntermediateLanguage/README.md @@ -0,0 +1,72 @@ +# Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage + +The Azoth Intermediate Language (IL) is both an intermediate representation (IR) for the compiler and the format for distributing machine independent Azoth libraries. + +## Structure + +The Azoth IL is loosely based on the Rust MIR. It uses a set of local variables whose names have been erased. Additionally, the compiler may introduce many temporary variables and these are freely mixed into the declared variable. + +## Syntax + +Each function or method has a meta data declaration. + +```IL +fn Namespace.Optional_Class_Name::Method_name(argument: int32, another_arg: Type) -> Namespace.Return_Type +{ + // Bindings + // The variable _0 is implicitly defined as the return type with a non-mutable binding + // The variables _1 to _n where n is the number of arguments refer to the arguments. Note that the argument names exist for meta-data purposes only and aren't really used in the IL + let _3: int32 // notice the full qualifying of even the default size + var _4: int32 // `var` indicates a mutable binding + + bb0: // basic blocks form a control flow graph + { + _3 = const 4:int32 // constant types are explicit + _2 = _1 + _3 // Type not specified, but the three types must be the same and must be valid for basic add + + } +} +``` + +## Assembly + +If desired a more flexible assembly language can be provided for human use. This would provide the following conveniences. + +* Use of arguments by name instead of index +* Declaration of named variables +* Declarations of variables at any point on the code +* Assignment of initial values as part of declaration statements; +* Comments introduced with double slash + +## Namespaces + +Like the CIL, the Azoth IL doesn't contain namespaces. Rather, each entity name is its fully qualified name. This means that a package can never expose an empty namespace. + + + +----- + +Instructions: + Call + VirtCall + BinOp (+, -) + Unary Op + +Block Terminator Instruction + Return operand + return void + +Places + VariablePlace + DiscardPlace + FieldPlace (operand, field name) + +Operand + VariableReference + FieldReference (operand, field name) + Constants + +Translation: + ConvertToOperand() -> Operand + ConvertIntoPlace(exp, place) - if place is discard, can skip some ops, like binary op, but may still need to evaluate things for side effects + diff --git a/Compiler.IntermediateLanguage/SelfParameterIL.cs b/Compiler.IntermediateLanguage/SelfParameterIL.cs new file mode 100644 index 00000000..0631108f --- /dev/null +++ b/Compiler.IntermediateLanguage/SelfParameterIL.cs @@ -0,0 +1,15 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage +{ + public sealed class SelfParameterIL : ParameterIL + { + public new SelfParameterSymbol Symbol { get; } + + public SelfParameterIL(SelfParameterSymbol symbol) + : base(symbol, symbol.IsMutableBinding, symbol.DataType) + { + Symbol = symbol; + } + } +} diff --git a/Compiler.LexicalScopes/Compiler.LexicalScopes.csproj b/Compiler.LexicalScopes/Compiler.LexicalScopes.csproj new file mode 100644 index 00000000..bdd9d15c --- /dev/null +++ b/Compiler.LexicalScopes/Compiler.LexicalScopes.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.LexicalScopes + Azoth.Tools.Bootstrap.Compiler.LexicalScopes + enable + + + + DEBUG;TRACE + true + + + + true + + + + + + + + diff --git a/Compiler.LexicalScopes/LexicalScope.cs b/Compiler.LexicalScopes/LexicalScope.cs new file mode 100644 index 00000000..21db97ff --- /dev/null +++ b/Compiler.LexicalScopes/LexicalScope.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.LexicalScopes +{ + /// + /// Lookup things by name in lexical scopes + /// + public abstract class LexicalScope + { + internal abstract PackagesScope ContainingPackagesScope { get; } + + public virtual PackageSymbol? LookupPackage(Name name) + { + return ContainingPackagesScope.LookupPackage(name); + } + + public abstract IEnumerable> LookupInGlobalScope(TypeName name); + + public abstract IEnumerable> Lookup(TypeName name, bool includeNested = true); + } +} diff --git a/Compiler.LexicalScopes/NestedScope.cs b/Compiler.LexicalScopes/NestedScope.cs new file mode 100644 index 00000000..bf9a74aa --- /dev/null +++ b/Compiler.LexicalScopes/NestedScope.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using SymbolDictionary = Azoth.Tools.Bootstrap.Framework.FixedDictionary>>; + +namespace Azoth.Tools.Bootstrap.Compiler.LexicalScopes +{ + public class NestedScope : LexicalScope + { + internal override PackagesScope ContainingPackagesScope { get; } + private readonly LexicalScope containingScope; + private readonly bool isGlobalScope; + private readonly SymbolDictionary symbolsInScope; + private readonly SymbolDictionary symbolsInNestedScopes; + + public static NestedScope Create( + LexicalScope containingScope, + SymbolDictionary symbolsInScope, + SymbolDictionary? symbolsInNestedScopes = null) + { + return new NestedScope(containingScope, false, symbolsInScope, + symbolsInNestedScopes ?? SymbolDictionary.Empty); + } + + public static NestedScope CreateGlobal( + LexicalScope containingScope, + SymbolDictionary symbolsInScope, + SymbolDictionary? symbolsInNestedScopes) + { + return new NestedScope(containingScope, true, symbolsInScope, + symbolsInNestedScopes ?? SymbolDictionary.Empty); + } + + private NestedScope( + LexicalScope containingScope, + bool isGlobalScope, + SymbolDictionary symbolsInScope, + SymbolDictionary symbolsInNestedScopes) + { + ContainingPackagesScope = containingScope.ContainingPackagesScope; + this.containingScope = containingScope; + this.isGlobalScope = isGlobalScope; + this.symbolsInScope = symbolsInScope; + this.symbolsInNestedScopes = symbolsInNestedScopes; + } + + public override IEnumerable> LookupInGlobalScope(TypeName name) + { + return !isGlobalScope ? containingScope.LookupInGlobalScope(name) : Lookup(name, false); + } + + public override IEnumerable> Lookup(TypeName name, bool includeNested = true) + { + if (symbolsInScope.TryGetValue(name, out var symbols)) return symbols; + if (includeNested && symbolsInNestedScopes.TryGetValue(name, out symbols)) return symbols; + return containingScope.Lookup(name, includeNested); + } + } +} diff --git a/Compiler.LexicalScopes/PackagesScope.cs b/Compiler.LexicalScopes/PackagesScope.cs new file mode 100644 index 00000000..3169e2c5 --- /dev/null +++ b/Compiler.LexicalScopes/PackagesScope.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.LexicalScopes +{ + public class PackagesScope : LexicalScope + { + internal override PackagesScope ContainingPackagesScope => this; + public PackageSymbol CurrentPackage { get; } + private readonly FixedDictionary packageAliases; + + public PackagesScope(PackageSymbol currentPackage, FixedDictionary packageAliases) + { + CurrentPackage = currentPackage; + this.packageAliases = packageAliases; + } + + public override PackageSymbol? LookupPackage(Name name) + { + return packageAliases.TryGetValue(name, out var package) ? package : null; + } + + public override IEnumerable> LookupInGlobalScope(TypeName name) + { + return Enumerable.Empty>(); + } + + public override IEnumerable> Lookup(TypeName name, bool includeNested = true) + { + return Enumerable.Empty>(); + } + } +} diff --git a/Compiler.Lexing/Compiler.Lexing.csproj b/Compiler.Lexing/Compiler.Lexing.csproj new file mode 100644 index 00000000..a60b6dea --- /dev/null +++ b/Compiler.Lexing/Compiler.Lexing.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Lexing + Azoth.Tools.Bootstrap.Compiler.Lexing + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Compiler.Lexing/ITokenIterator.cs b/Compiler.Lexing/ITokenIterator.cs new file mode 100644 index 00000000..c0fef7b4 --- /dev/null +++ b/Compiler.Lexing/ITokenIterator.cs @@ -0,0 +1,17 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Lexing +{ + public interface ITokenIterator + where TToken : class, IToken + { + ParseContext Context { get; } + + bool Next(); + + /// If current is accessed after Next() has returned false + TToken Current { get; } + } +} diff --git a/Compiler.Lexing/LexError.cs b/Compiler.Lexing/LexError.cs new file mode 100644 index 00000000..f5adb627 --- /dev/null +++ b/Compiler.Lexing/LexError.cs @@ -0,0 +1,70 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Lexing +{ + /// + /// Error Code Ranges: + /// 1001-1999: Lexical Errors + /// 2001-2999: Parsing Errors + /// 3001-3999: Type Errors + /// 4001-4999: Borrow Checking Errors + /// 5001-5999: Name Binding Errors + /// 6001-6999: Other Semantic Errors + /// + internal static class LexError + { + public static Diagnostic UnclosedBlockComment(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1001, + "End-of-file found, expected `*/`"); + } + + public static Diagnostic UnclosedStringLiteral(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1002, + "End-of-file in string constant"); + } + + public static Diagnostic InvalidEscapeSequence(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1003, + "Unrecognized escape sequence"); + } + + public static Diagnostic CStyleNotEquals(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1004, + "Use `≠` or `=/=` for not equal instead of `!=`"); + } + + public static Diagnostic UnexpectedCharacter(CodeFile file, TextSpan span, char character) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1005, + $"Unexpected character `{character}`"); + } + + public static Diagnostic ReservedWord(CodeFile file, TextSpan span, string word) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1006, + $"Reserved word `{word}` used as an identifier"); + } + + public static Diagnostic ContinueInsteadOfNext(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1007, + $"The word `continue` is a reserved word. Use the `next` keyword or escape the identifier as `\\continue` instead."); + } + + public static Diagnostic EscapedIdentifierShouldNotBeEscaped(CodeFile file, TextSpan span, string identifier) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1008, + $"The word `{identifier}` is not a keyword or reserved word, it should not be escaped."); + } + + public static Diagnostic ReservedOperator(CodeFile file, TextSpan span, string op) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Lexing, 1009, + $"Unexpected character(s) `{op}`, reserved for operator or punctuators"); + } + } +} diff --git a/Compiler.Lexing/Lexer.cs b/Compiler.Lexing/Lexer.cs new file mode 100644 index 00000000..ec5c88a8 --- /dev/null +++ b/Compiler.Lexing/Lexer.cs @@ -0,0 +1,564 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Lexing +{ + public class Lexer + { + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Unit testability")] + public ITokenIterator Lex(ParseContext context) + { + return new TokenIterator(context, Lex(context.File, context.Diagnostics)); + } + + private static IEnumerable Lex(CodeFile file, Diagnostics diagnostics) + { + var code = file.Code; + var text = code.Text; + var tokenStart = 0; + var tokenEnd = -1; // One past the end position to allow for zero length spans + while (tokenStart < text.Length) + { + var currentChar = text[tokenStart]; + switch (currentChar) + { + case '{': + yield return TokenFactory.OpenBrace(SymbolSpan()); + break; + case '}': + yield return TokenFactory.CloseBrace(SymbolSpan()); + break; + case '(': + yield return TokenFactory.OpenParen(SymbolSpan()); + break; + case ')': + yield return TokenFactory.CloseParen(SymbolSpan()); + break; + case '[': + case ']': + case '|': + case '&': + case '@': + case '`': + case '$': + yield return NewReservedOperator(); + break; + case ';': + yield return TokenFactory.Semicolon(SymbolSpan()); + break; + case ',': + yield return TokenFactory.Comma(SymbolSpan()); + break; + case '#': + yield return NextChar() switch + { + // it is `##` + '#' => NewReservedOperator(2), + // it is `#(` + '(' => NewReservedOperator(2), + // it is `#[` + '[' => NewReservedOperator(2), + // it is `#{` + '{' => NewReservedOperator(2), + // it is `#` + _ => NewReservedOperator() + }; + break; + case '.': + if (NextChar() is '.') + { + if (CharAt(2) is '<') + // it is `..<` + yield return TokenFactory.DotDotLessThan(SymbolSpan(3)); + else + // it is `..` + yield return TokenFactory.DotDot(SymbolSpan(2)); + } + else + yield return TokenFactory.Dot(SymbolSpan()); + break; + case ':': + if (NextChar() is ':' && CharAt(2) is '.') + // it is `::.` + yield return TokenFactory.ColonColonDot(SymbolSpan(3)); + else + // it is `:` + yield return TokenFactory.Colon(SymbolSpan()); + break; + case '?': + yield return NextChar() switch + { + // it is `??` + '?' => TokenFactory.QuestionQuestion(SymbolSpan(2)), + // it is `?.` + '.' => TokenFactory.QuestionDot(SymbolSpan(2)), + // it is `?` + _ => TokenFactory.Question(SymbolSpan()) + }; + break; + case '→': + yield return TokenFactory.RightArrow(SymbolSpan()); + break; + case '⇒': + yield return TokenFactory.RightDoubleArrow(SymbolSpan()); + break; + case '^': + if (NextChar() is '.') + // it is `^.` + yield return NewReservedOperator(2); + else + // it is `^` + yield return NewReservedOperator(); + break; + case '+': + if (NextChar() is '=') + // it is `+=` + yield return TokenFactory.PlusEquals(SymbolSpan(2)); + else + // it is `+` + yield return TokenFactory.Plus(SymbolSpan()); + break; + case '-': + yield return NextChar() switch + { + // it is `-=` + '=' => TokenFactory.MinusEquals(SymbolSpan(2)), + // it is `->` + '>' => TokenFactory.RightArrow(SymbolSpan(2)), + // it is `-` + _ => TokenFactory.Minus(SymbolSpan()) + }; + break; + case '*': + if (NextChar() is '=') + // it is `*=` + yield return TokenFactory.AsteriskEquals(SymbolSpan(2)); + else + // it is `*` + yield return TokenFactory.Asterisk(SymbolSpan()); + + break; + case '/': + switch (NextChar()) + { + case '/': + // it is a line comment `//` + tokenEnd = tokenStart + 2; + // Include newline at end + while (tokenEnd < text.Length) + { + currentChar = text[tokenEnd]; + tokenEnd += 1; + if (currentChar == '\r' || currentChar == '\n') + break; + } + + yield return TokenFactory.Comment(TokenSpan()); + break; + case '*': + // it is a block comment `/*` + tokenEnd = tokenStart + 2; + var lastCharWasStar = false; + // Include slash at end + for (; ; ) + { + // If we ran into the end of the file, error + if (tokenEnd >= text.Length) + { + diagnostics.Add(LexError.UnclosedBlockComment(file, + TextSpan.FromStartEnd(tokenStart, tokenEnd))); + break; + } + currentChar = text[tokenEnd]; + tokenEnd += 1; + if (lastCharWasStar && currentChar == '/') + break; + lastCharWasStar = currentChar == '*'; + } + + yield return TokenFactory.Comment(TokenSpan()); + break; + case '=': + // it is `/=` + yield return TokenFactory.SlashEquals(SymbolSpan(2)); + break; + default: + // it is `/` + yield return TokenFactory.Slash(SymbolSpan()); + break; + } + break; + case '=': + switch (NextChar()) + { + case '>': + // it is `=>` + yield return TokenFactory.RightDoubleArrow(SymbolSpan(2)); + break; + case '=': + // it is `==` + yield return TokenFactory.EqualsEquals(SymbolSpan(2)); + break; + case '/': + if (CharAt(2) is '=') + // it is `=/=` + yield return TokenFactory.NotEqual(SymbolSpan(3)); + else + goto default; + break; + default: + // it is `=` + yield return TokenFactory.Equals(SymbolSpan()); + break; + } + break; + case '≠': + yield return TokenFactory.NotEqual(SymbolSpan()); + break; + case '>': + if (NextChar() is '=') + // it is `>=` + yield return TokenFactory.GreaterThanOrEqual(SymbolSpan(2)); + else + // it is `>` + yield return TokenFactory.GreaterThan(SymbolSpan()); + break; + case '≥': + yield return TokenFactory.GreaterThanOrEqual(SymbolSpan()); + break; + case '<': + switch (NextChar()) + { + case '=': + // it is `<=` + yield return TokenFactory.LessThanOrEqual(SymbolSpan(2)); + break; + case ':': + // it is `<:` + yield return TokenFactory.LessThanColon(SymbolSpan(2)); + break; + case '.': + if (CharAt(2) is '.') + { + if (CharAt(3) is '<') + // it is `<..<` + yield return TokenFactory.LessThanDotDotLessThan(SymbolSpan(4)); + else + // it is `<..` + yield return TokenFactory.LessThanDotDot(SymbolSpan(3)); + } + else + goto default; + break; + default: + // it is `<` + yield return TokenFactory.LessThan(SymbolSpan()); + break; + } + break; + case '≤': + yield return TokenFactory.LessThanOrEqual(SymbolSpan()); + break; + case '"': + yield return LexString(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + tokenEnd = tokenStart + 1; + while (tokenEnd < text.Length && IsIntegerCharacter(text[tokenEnd])) + tokenEnd += 1; + + var span = TokenSpan(); + var value = BigInteger.Parse(code[span], CultureInfo.InvariantCulture); + yield return TokenFactory.IntegerLiteral(span, value); + break; + } + case '\\': + { + tokenEnd = tokenStart + 1; + while (tokenEnd < text.Length && IsIdentifierCharacter(text[tokenEnd])) + tokenEnd += 1; + + if (tokenEnd == tokenStart + 1) + yield return NewUnexpectedCharacter(); + else + yield return NewEscapedIdentifier(); + break; + } + default: + if (char.IsWhiteSpace(currentChar)) + { + tokenEnd = tokenStart + 1; + // Include whitespace at end + while (tokenEnd < text.Length && char.IsWhiteSpace(text[tokenEnd])) + tokenEnd += 1; + + yield return TokenFactory.Whitespace(TokenSpan()); + } + else if (IsIdentifierStartCharacter(currentChar)) + { + tokenEnd = tokenStart + 1; + while (tokenEnd < text.Length && IsIdentifierCharacter(text[tokenEnd])) + tokenEnd += 1; + + yield return NewIdentifierOrKeywordToken(); + } + else if (currentChar == '!' && NextChar() is '=') + { + var span = SymbolSpan(2); + diagnostics.Add(LexError.CStyleNotEquals(file, span)); + yield return TokenFactory.NotEqual(span); + } + else + yield return NewUnexpectedCharacter(); + + break; + } + tokenStart = tokenEnd; + } + + // The end of file token provides something to attach any final errors to + yield return TokenFactory.EndOfFile(SymbolSpan(0)); + yield break; + + TextSpan SymbolSpan(int length = 1) + { + var end = tokenStart + length; + return TokenSpan(end); + } + TextSpan TokenSpan(int? end = null) + { + tokenEnd = end ?? tokenEnd; + return TextSpan.FromStartEnd(tokenStart, tokenEnd); + } + + IToken NewIdentifierOrKeywordToken() + { + var span = TokenSpan(); + var value = code[span]; + if (TokenTypes.KeywordFactories.TryGetValue(value, out var keywordFactory)) + return keywordFactory(span); + + if (value == "continue") + diagnostics.Add(LexError.ContinueInsteadOfNext(file, span)); + else if (TokenTypes.ReservedWords.Contains(value) + || TokenTypes.IsReservedTypeName(value)) + diagnostics.Add(LexError.ReservedWord(file, span, value)); + + return TokenFactory.BareIdentifier(span, value); + } + IToken NewEscapedIdentifier() + { + var identifierStart = tokenStart + 1; + var span = TokenSpan(); + var value = text[identifierStart..tokenEnd]; + var isValidToEscape = TokenTypes.Keywords.Contains(value) + || TokenTypes.ReservedWords.Contains(value) + || TokenTypes.IsReservedTypeName(value) + || char.IsDigit(value[0]); + if (!isValidToEscape) + diagnostics.Add(LexError.EscapedIdentifierShouldNotBeEscaped(file, span, value)); + return TokenFactory.EscapedIdentifier(span, value); + } + IToken NewUnexpectedCharacter() + { + var span = SymbolSpan(); + var value = code[span]; + diagnostics.Add(LexError.UnexpectedCharacter(file, span, value[0])); + return TokenFactory.Unexpected(span); + } + IToken NewReservedOperator(int length = 1) + { + var span = SymbolSpan(length); + var value = code[span]; + diagnostics.Add(LexError.ReservedOperator(file, span, value)); + return TokenFactory.Unexpected(span); + } + + char? NextChar() + { + var index = tokenStart + 1; + return index < text.Length ? text[index] : default; + } + + char? CharAt(int offset) + { + var index = tokenStart + offset; + return index < text.Length ? text[index] : default; + } + + IToken LexString() + { + tokenEnd = tokenStart + 1; + var content = new StringBuilder(); + char currentChar; + while (tokenEnd < text.Length && (currentChar = text[tokenEnd]) != '"') + { + tokenEnd += 1; + + if (currentChar != '\\') + { + content.Append(currentChar); + continue; + } + + // Escape Sequence (i.e. "\\") + // In case of an invalid escape sequence, we just drop the `\` from the value + + if (tokenEnd >= text.Length) + { + // Just the slash is invalid + var errorSpan = TextSpan.FromStartEnd(tokenEnd - 1, tokenEnd); + diagnostics.Add(LexError.InvalidEscapeSequence(file, errorSpan)); + break; // we hit the end of file and need to not add to tokenEnd any more + } + + // Escape Sequence with next char (i.e. "\\x") + var escapeStart = tokenEnd - 1; + currentChar = text[tokenEnd]; + tokenEnd += 1; + switch (currentChar) + { + case '"': + case '\'': + case '\\': + content.Append(currentChar); + break; + case 'n': + content.Append('\n'); + break; + case 'r': + content.Append('\r'); + break; + case '0': + content.Append('\0'); + break; + case 't': + content.Append('\t'); + break; + case 'u': + { + if (tokenEnd < text.Length && text[tokenEnd] == '(') + tokenEnd += 1; + else + { + content.Append('u'); + var errorSpan = TextSpan.FromStartEnd(escapeStart, tokenEnd); + diagnostics.Add(LexError.InvalidEscapeSequence(file, errorSpan)); + break; + } + + var codepoint = new StringBuilder(6); + while (tokenEnd < text.Length && + IsHexDigit(currentChar = text[tokenEnd])) + { + codepoint.Append(currentChar); + tokenEnd += 1; + } + + int value; + if (codepoint.Length > 0 + && codepoint.Length <= 6 + && (value = Convert.ToInt32(codepoint.ToString(), 16)) <= 0x10FFFF) + { + // TODO disallow surrogate pairs + content.Append(char.ConvertFromUtf32(value)); + } + else + { + content.Append("u("); + content.Append(codepoint); + // Include the closing ')' in the escape sequence if it is present + if (tokenEnd < text.Length && text[tokenEnd] == ')') + { + content.Append(')'); + tokenEnd += 1; + } + var errorSpan = TextSpan.FromStartEnd(escapeStart, tokenEnd); + diagnostics.Add(LexError.InvalidEscapeSequence(file, errorSpan)); + break; + } + + if (tokenEnd < text.Length && text[tokenEnd] == ')') + tokenEnd += 1; + else + { + var errorSpan = TextSpan.FromStartEnd(escapeStart, tokenEnd); + diagnostics.Add(LexError.InvalidEscapeSequence(file, errorSpan)); + } + break; + } + default: + { + // Last two chars form the invalid sequence + var errorSpan = TextSpan.FromStartEnd(tokenEnd - 2, tokenEnd); + diagnostics.Add(LexError.InvalidEscapeSequence(file, errorSpan)); + // drop the `/` keep the character after + content.Append(currentChar); + break; + } + } + } + + if (tokenEnd < text.Length) + { + // To include the close quote + if (text[tokenEnd] == '"') + tokenEnd += 1; + } + else + diagnostics.Add(LexError.UnclosedStringLiteral(file, + TextSpan.FromStartEnd(tokenStart, tokenEnd))); + + return TokenFactory.StringLiteral(TextSpan.FromStartEnd(tokenStart, tokenEnd), content.ToString()); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + private static bool IsIntegerCharacter(char c) + { + return c >= '0' && c <= '9'; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + private static bool IsIdentifierStartCharacter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + private static bool IsIdentifierCharacter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || char.IsNumber(c); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + private static bool IsHexDigit(char c) + { + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); + } + } +} diff --git a/Compiler.Lexing/TokenIterator.cs b/Compiler.Lexing/TokenIterator.cs new file mode 100644 index 00000000..a80ad06d --- /dev/null +++ b/Compiler.Lexing/TokenIterator.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Lexing +{ + public class TokenIterator : ITokenIterator + where TToken : class, IToken + { + public ParseContext Context { get; } + private IEnumerator? tokens; + + public TokenIterator(ParseContext context, IEnumerable tokens) + { + Context = context; + this.tokens = tokens.GetEnumerator(); + Next(); + } + + public bool Next() + { + if (tokens is null) + return false; + if (!tokens.MoveNext()) + tokens = null; + return tokens != null; + } + + public TToken Current => (tokens ?? throw new InvalidOperationException("Can't access `TokenIterator.Current` after `Next()` has returned false")).Current; + } +} diff --git a/Compiler.Lexing/TokenIteratorExtensions.cs b/Compiler.Lexing/TokenIteratorExtensions.cs new file mode 100644 index 00000000..08afcae0 --- /dev/null +++ b/Compiler.Lexing/TokenIteratorExtensions.cs @@ -0,0 +1,41 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using ITriviaToken = Azoth.Tools.Bootstrap.Compiler.Tokens.ITriviaToken; + +namespace Azoth.Tools.Bootstrap.Compiler.Lexing +{ + public static class TokenIteratorExtensions + { + public static ITokenIterator WhereNotTrivia(this ITokenIterator tokens) + { + return new WhereNotTriviaIterator(tokens); + } + + private class WhereNotTriviaIterator : ITokenIterator + { + private readonly ITokenIterator tokens; + + public WhereNotTriviaIterator(ITokenIterator tokens) + { + this.tokens = tokens; + if (tokens.Current is ITriviaToken) + Next(); + } + + public ParseContext Context => tokens.Context; + + public bool Next() + { + do + { + if (!tokens.Next()) + return false; + } while (tokens.Current is ITriviaToken); + + return true; + } + + public IEssentialToken Current => (IEssentialToken)tokens.Current; + } + } +} diff --git a/Compiler.Names/Compiler.Names.csproj b/Compiler.Names/Compiler.Names.csproj new file mode 100644 index 00000000..e104c2b4 --- /dev/null +++ b/Compiler.Names/Compiler.Names.csproj @@ -0,0 +1,30 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Names + Azoth.Tools.Bootstrap.Compiler.Names + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Compiler.Names/Name.cs b/Compiler.Names/Name.cs new file mode 100644 index 00000000..277ac61d --- /dev/null +++ b/Compiler.Names/Name.cs @@ -0,0 +1,51 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.RegularExpressions; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Names +{ + /// + /// A standard name + /// + public sealed class Name : TypeName + { + private static readonly Regex NeedsQuoted = new Regex(@"[\\ #ₛ]", RegexOptions.Compiled); + + public Name(string text) + : base(text) { } + + public override bool Equals(TypeName? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is Name otherName + && Text == otherName.Text; + } + + public override int GetHashCode() + { + return HashCode.Combine(typeof(Name), Text); + } + + public override string ToString() + { + if (TokenTypes.Keywords.Contains(Text) + ||TokenTypes.ReservedWords.Contains(Text)) + return '\\' + Text; + + var text = Text.Escape(); + if (NeedsQuoted.IsMatch(text)) text += $@"\""{text}"""; + return text; + } + + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", + Justification = "Constructor is alternative")] + public static implicit operator Name(string text) + { + return new Name(text); + } + } +} diff --git a/Compiler.Names/NamespaceName.cs b/Compiler.Names/NamespaceName.cs new file mode 100644 index 00000000..7dd7c1dc --- /dev/null +++ b/Compiler.Names/NamespaceName.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Names +{ + public sealed class NamespaceName : IEquatable + { + public static readonly NamespaceName Global = new NamespaceName(FixedList.Empty); + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "WTF")] + public FixedList Segments { [DebuggerHidden] get; } + + public NamespaceName(FixedList segments) + { + Segments = segments; + } + + public NamespaceName(IEnumerable segments) + : this(segments.ToFixedList()) { } + + public NamespaceName(params string[] segments) + : this(segments.Select(s => new Name(s)).ToFixedList()) { } + + public NamespaceName(Name segment) + { + Segments = segment.Yield().ToFixedList(); + } + + public NamespaceName Qualify(NamespaceName name) + { + return new NamespaceName(Segments.Concat(name.Segments)); + } + + public IEnumerable NamespaceNames() + { + yield return Global; + for (int n = 1; n <= Segments.Count; n++) + yield return new NamespaceName(Segments.Take(n)); + } + + public override string ToString() + { + return string.Join('.', Segments); + } + + public bool Equals(NamespaceName? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Segments.Equals(other.Segments); + } + + public override bool Equals(object? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is NamespaceName name && Equals(name); + } + + public override int GetHashCode() + { + return HashCode.Combine(Segments); + } + + public static bool operator ==(NamespaceName? left, NamespaceName? right) + { + return Equals(left, right); + } + + public static bool operator !=(NamespaceName? left, NamespaceName? right) + { + return !Equals(left, right); + } + + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", + Justification = "Constructor is alternative")] + public static implicit operator NamespaceName(Name name) + { + return new NamespaceName(name); + } + + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", + Justification = "This is just a chained implicit conversion")] + public static implicit operator NamespaceName(string text) + { + return new NamespaceName(text); + } + + public bool IsNestedIn(NamespaceName ns) + { + return ns.Segments.Count < Segments.Count + && ns.Segments.SequenceEqual(Segments.Take(ns.Segments.Count)); + } + } +} diff --git a/Compiler.Names/SpecialTypeName.cs b/Compiler.Names/SpecialTypeName.cs new file mode 100644 index 00000000..76154c88 --- /dev/null +++ b/Compiler.Names/SpecialTypeName.cs @@ -0,0 +1,52 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Names +{ + /// + /// A special type name is the name of one of the special types in the system. + /// They are distinguished because for example `bool` is a special name, but + /// `\"bool"` is a regular name with the same text. + /// + public sealed class SpecialTypeName : TypeName + { + #region Empty and Simple Types + public static readonly SpecialTypeName Void = new SpecialTypeName("void"); + public static readonly SpecialTypeName Never = new SpecialTypeName("never"); + public static readonly SpecialTypeName Bool = new SpecialTypeName("bool"); + public static readonly SpecialTypeName Any = new SpecialTypeName("Any"); + public static readonly SpecialTypeName Byte = new SpecialTypeName("byte"); +#pragma warning disable CA1720 + public static readonly SpecialTypeName Int = new SpecialTypeName("int"); + public static readonly SpecialTypeName UInt = new SpecialTypeName("uint"); +#pragma warning restore CA1720 + public static readonly SpecialTypeName Size = new SpecialTypeName("size"); + public static readonly SpecialTypeName Offset = new SpecialTypeName("offset"); + #endregion + + #region Constant Types + public static readonly SpecialTypeName True = new SpecialTypeName("True"); + public static readonly SpecialTypeName False = new SpecialTypeName("False"); + public static readonly SpecialTypeName ConstInt = new SpecialTypeName("ConstInt"); + #endregion + + private SpecialTypeName(string text) + : base(text) { } + + public override bool Equals(TypeName? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is SpecialTypeName otherName && Text == otherName.Text; + } + + public override int GetHashCode() + { + return HashCode.Combine(typeof(SpecialTypeName), Text); + } + + public override string ToString() + { + return Text; + } + } +} diff --git a/Compiler.Names/TypeName.cs b/Compiler.Names/TypeName.cs new file mode 100644 index 00000000..cb54c449 --- /dev/null +++ b/Compiler.Names/TypeName.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Names +{ + /// + /// A name for a type. Type names may be standard names, or special names + /// + [Closed( + typeof(Name), + typeof(SpecialTypeName))] + public abstract class TypeName : IEquatable + { + public string Text { get; } + + protected TypeName(string text) + { + Text = text; + } + + public abstract bool Equals(TypeName? other); + + public override bool Equals(object? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is TypeName otherTypeName && Equals(otherTypeName); + } + + public abstract override int GetHashCode(); + + public static bool operator ==(TypeName? left, TypeName? right) + { + return Equals(left, right); + } + + public static bool operator !=(TypeName? left, TypeName? right) + { + return !Equals(left, right); + } + + public abstract override string ToString(); + + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", + Justification = "Name() constructor is alternative")] + public static implicit operator TypeName(string text) + { + return new Name(text); + } + } +} diff --git a/Compiler.Parsing/CompilationUnitParser.cs b/Compiler.Parsing/CompilationUnitParser.cs new file mode 100644 index 00000000..5047c4ed --- /dev/null +++ b/Compiler.Parsing/CompilationUnitParser.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Parsing.NotImplemented; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public class CompilationUnitParser + { + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Entry point class which may need to be non-static in future")] + public ICompilationUnitSyntax Parse(ITokenIterator tokens) + { + var implicitNamespaceName = ParseImplicitNamespaceName(tokens); + var parser = new Parser(tokens, implicitNamespaceName); + var usingDirectives = parser.ParseUsingDirectives(); + var declarations = parser.ParseNonMemberDeclarations(); + var eof = tokens.Required(); + var span = TextSpan.FromStartEnd(0, eof.End); + var diagnostics = tokens.Context.Diagnostics; + var compilationUnit = new CompilationUnitSyntax(implicitNamespaceName, span, + tokens.Context.File, usingDirectives, + declarations); + + CheckSyntax(compilationUnit, diagnostics); + compilationUnit.Attach(diagnostics.Build()); + return compilationUnit; + } + + private static NamespaceName ParseImplicitNamespaceName(ITokenIterator tokens) + { + NamespaceName name = NamespaceName.Global; + foreach (var segment in tokens.Context.File.Reference.Namespace) + name = name.Qualify(segment); + + return name; + } + + private static void CheckSyntax(CompilationUnitSyntax compilationUnit, Diagnostics diagnostics) + { + var notImplementedChecker = new SyntaxNotImplementedChecker(compilationUnit, diagnostics); + notImplementedChecker.Check(); + } + } +} diff --git a/Compiler.Parsing/Compiler.Parsing.csproj b/Compiler.Parsing/Compiler.Parsing.csproj new file mode 100644 index 00000000..e71f85d8 --- /dev/null +++ b/Compiler.Parsing/Compiler.Parsing.csproj @@ -0,0 +1,35 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Parsing + Azoth.Tools.Bootstrap.Compiler.Parsing + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Compiler.Parsing/ModifierParser.cs b/Compiler.Parsing/ModifierParser.cs new file mode 100644 index 00000000..89cec893 --- /dev/null +++ b/Compiler.Parsing/ModifierParser.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + internal class ModifierParser : RecursiveDescentParser + { + public ModifierParser(ITokenIterator tokens) + : base(tokens) { } + + public IAccessModifierToken? ParseAccessModifier() + { + return Tokens.Current switch + { + IAccessModifierToken _ => Tokens.RequiredToken(), + _ => null + }; + } + + public IMutableKeywordToken? ParseMutableModifier() + { + return Tokens.Current is IMutableKeywordToken ? Tokens.RequiredToken() : null; + } + + public void ParseEndOfModifiers() + { + while (!(Tokens.Current is IEndOfFileToken)) + { + Tokens.UnexpectedToken(); + } + } + } +} diff --git a/Compiler.Parsing/NotImplemented/SyntaxNotImplementedChecker.cs b/Compiler.Parsing/NotImplemented/SyntaxNotImplementedChecker.cs new file mode 100644 index 00000000..eba29d3c --- /dev/null +++ b/Compiler.Parsing/NotImplemented/SyntaxNotImplementedChecker.cs @@ -0,0 +1,66 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.NotImplemented +{ + /// + /// Reports errors for syntax that the parsing supports but the semantic + /// analyzer doesn't. + /// + internal class SyntaxNotImplementedChecker : SyntaxWalker + { + private readonly CompilationUnitSyntax compilationUnit; + private readonly Diagnostics diagnostics; + private readonly CodeFile file; + + public SyntaxNotImplementedChecker(CompilationUnitSyntax compilationUnit, Diagnostics diagnostics) + { + this.compilationUnit = compilationUnit; + this.diagnostics = diagnostics; + file = compilationUnit.File; + } + + public void Check() + { + WalkNonNull(compilationUnit); + } + + protected override void WalkNonNull(ISyntax syntax) + { + switch (syntax) + { + case INamedParameterSyntax syn: + if (!(syn.DefaultValue is null)) + diagnostics.Add(ParseError.NotImplemented(file, syn.DefaultValue.Span, "Default values")); + break; + case IFieldParameterSyntax syn: + if (!(syn.DefaultValue is null)) + diagnostics.Add(ParseError.NotImplemented(file, syn.DefaultValue.Span, "Default values")); + break; + case IConstructorDeclarationSyntax syn: + if (!(syn.Name is null)) + diagnostics.Add(ParseError.NotImplemented(file, syn.Span, "Named constructors")); + break; + case IFieldDeclarationSyntax syn: + if (!(syn.Initializer is null)) + diagnostics.Add(ParseError.NotImplemented(file, syn.Initializer.Span, "Field initializers")); + break; + case IForeachExpressionSyntax syn: + { + var inExpression = syn.InExpression; + if (!(inExpression is IBinaryOperatorExpressionSyntax exp) + || (exp.Operator != BinaryOperator.DotDot + && exp.Operator != BinaryOperator.LessThanDotDot + && exp.Operator != BinaryOperator.DotDotLessThan + && exp.Operator != BinaryOperator.LessThanDotDotLessThan)) + diagnostics.Add(ParseError.NotImplemented(file, inExpression.Span, "Foreach in non range expressions")); + } + break; + } + WalkChildren(syntax); + } + } +} diff --git a/Compiler.Parsing/PackageParser.cs b/Compiler.Parsing/PackageParser.cs new file mode 100644 index 00000000..03fafae3 --- /dev/null +++ b/Compiler.Parsing/PackageParser.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + /// + /// Parser for all the files in a package + /// + public class PackageParser + { + } +} diff --git a/Compiler.Parsing/ParseAs.cs b/Compiler.Parsing/ParseAs.cs new file mode 100644 index 00000000..e3188293 --- /dev/null +++ b/Compiler.Parsing/ParseAs.cs @@ -0,0 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + [SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names", Justification = "Not Plural")] + public enum ParseAs + { + Expression = 1, + Statement + } +} diff --git a/Compiler.Parsing/ParseError.cs b/Compiler.Parsing/ParseError.cs new file mode 100644 index 00000000..fd7eb8de --- /dev/null +++ b/Compiler.Parsing/ParseError.cs @@ -0,0 +1,84 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + /// + /// Error Code Ranges: + /// 1001-1999: Lexical Errors + /// 2001-2999: Parsing Errors + /// 3001-3999: Type Errors + /// 4001-4999: Borrow Checking Errors + /// 5001-5999: Name Binding Errors + /// 6001-6999: Other Semantic Errors + /// + internal static class ParseError + { + /// + /// Special temporary error for language features that are not implemented. For that reason + /// it breaks convention and uses error number 2000 + /// + public static Diagnostic NotImplemented(CodeFile file, TextSpan span, string feature) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 2000, + $"{feature} are not yet implemented"); + } + + public static Diagnostic IncompleteDeclaration(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2001, "Incomplete declaration"); + } + + public static Diagnostic UnexpectedToken(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2002, $"Unexpected token `{file.Code[span]}`"); + } + + public static Diagnostic MissingToken(CodeFile file, Type expected, IToken found) + { + return new Diagnostic(file, found.Span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2003, $"Expected `{expected.GetFriendlyName()}` found `{found.Text(file.Code)}`"); + } + + public static Diagnostic DeclarationNotAllowedInExternal(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2004, "Only function declarations are allowed in external blocks"); + } + + public static Diagnostic UnexpectedEndOfExpression(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2005, "Unexpected end of expression"); + } + + public static Diagnostic CantMoveOutOfExpression(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2006, + "Can't move out of expression. Can only move out of variable."); + } + + public static Diagnostic ResultStatementInBody(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2007, + "Result statements can't appear directly in function or method bodies. Must be in block expression."); + } + + public static Diagnostic ExtraSelfParameter(CodeFile file, in TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2008, + "There can be only one self parameter to a method."); + } + + public static Diagnostic SelfParameterMustBeFirst(CodeFile file, in TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2009, + "Self parameter must be the first parameter."); + } + + public static Diagnostic CantAssignIntoExpression(CodeFile file, in TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Parsing, 2010, + "Expression can not appear on the left hand side of an assignment."); + } + } +} diff --git a/Compiler.Parsing/ParseFailedException.cs b/Compiler.Parsing/ParseFailedException.cs new file mode 100644 index 00000000..b790b15f --- /dev/null +++ b/Compiler.Parsing/ParseFailedException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + /// + /// Used as control flow within the parser. When a parse error requiring us + /// to jump out to a higher syntax node occurs, this is the exception that + /// is thrown. + /// + public class ParseFailedException : Exception + { + public ParseFailedException() + { + } + + public ParseFailedException(string message) + : base(message) + { + } + } +} diff --git a/Compiler.Parsing/Parser.Declarations.cs b/Compiler.Parsing/Parser.Declarations.cs new file mode 100644 index 00000000..356bf252 --- /dev/null +++ b/Compiler.Parsing/Parser.Declarations.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + public FixedList ParseNonMemberDeclarations() + where T : IToken + { + var declarations = new List(); + // Stop at end of file or some token that contains these declarations + while (!Tokens.AtEnd()) + try + { + declarations.Add(ParseNonMemberDeclaration()); + } + catch (ParseFailedException) + { + // Ignore: we would have consumed something before failing, try to get the next declaration + } + + return declarations.ToFixedList(); + } + + public INonMemberDeclarationSyntax ParseNonMemberDeclaration() + { + var modifiers = ParseModifiers(); + + switch (Tokens.Current) + { + case INamespaceKeywordToken _: + return ParseNamespaceDeclaration(modifiers); + case IClassKeywordToken _: + return ParseClass(modifiers); + case IFunctionKeywordToken _: + return ParseFunction(modifiers); + default: + Tokens.UnexpectedToken(); + throw new ParseFailedException(); + } + } + + + + internal IMemberDeclarationSyntax ParseMemberDeclaration( + IClassDeclarationSyntax classDeclaration) + { + var modifiers = ParseModifiers(); + + switch (Tokens.Current) + { + case IFunctionKeywordToken _: + return ParseMemberFunction(classDeclaration, modifiers); + case INewKeywordToken _: + return ParseConstructor(classDeclaration, modifiers); + case ILetKeywordToken _: + return ParseField(classDeclaration, false, modifiers); + case IVarKeywordToken _: + return ParseField(classDeclaration, true, modifiers); + default: + Tokens.UnexpectedToken(); + throw new ParseFailedException(); + } + } + + [SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections. Instead use the collection directly", + Justification = "LastOrDefault() doesn't have a simple equivalent")] + private ModifierParser ParseModifiers() + { + var modifiers = AcceptMany(Tokens.AcceptToken); + var endOfModifiers = TokenFactory.EndOfFile(modifiers.LastOrDefault()?.Span.AtEnd() ?? Tokens.Current.Span.AtStart()); + var modifierTokens = new TokenIterator(Tokens.Context, modifiers.Append(endOfModifiers)); + return new ModifierParser(modifierTokens); + } + + #region Parse Namespaces + internal NamespaceDeclarationSyntax ParseNamespaceDeclaration( + ModifierParser modifiers) + { + modifiers.ParseEndOfModifiers(); + var ns = Tokens.Expect(); + var globalQualifier = Tokens.AcceptToken(); + var (name, nameSpan) = ParseNamespaceName(); + nameSpan = TextSpan.Covering(nameSpan, globalQualifier?.Span); + Tokens.Expect(); + var bodyParser = NamespaceBodyParser(name); + var usingDirectives = bodyParser.ParseUsingDirectives(); + var declarations = bodyParser.ParseNonMemberDeclarations(); + var closeBrace = Tokens.Expect(); + var span = TextSpan.Covering(ns, closeBrace); + return new NamespaceDeclarationSyntax(ContainingNamespace, span, File, globalQualifier != null, name, nameSpan, usingDirectives, declarations); + } + + private (NamespaceName, TextSpan) ParseNamespaceName() + { + var firstSegment = Tokens.RequiredToken(); + var span = firstSegment.Span; + NamespaceName name = firstSegment.Value; + + while (Tokens.Accept()) + { + var (nameSegment, segmentSpan) = Tokens.ExpectToken(); + // We need the span to cover a trailing dot + span = TextSpan.Covering(span, segmentSpan); + if (nameSegment is null) + break; + name = name.Qualify(nameSegment.Value); + } + + return (name, span); + } + #endregion + + #region Parse Functions + internal IFunctionDeclarationSyntax ParseFunction(ModifierParser modifiers) + { + var accessModifer = modifiers.ParseAccessModifier(); + modifiers.ParseEndOfModifiers(); + var fn = Tokens.Expect(); + var identifier = Tokens.RequiredToken(); + Name name = identifier.Value; + var bodyParser = BodyParser(); + var parameters = bodyParser.ParseParameters(bodyParser.ParseFunctionParameter); + var returnType = ParseReturn(); + var body = bodyParser.ParseFunctionBody(); + var span = TextSpan.Covering(fn, body.Span); + return new FunctionDeclarationSyntax(ContainingNamespace, span, File, accessModifer, identifier.Span, + name, parameters, returnType, body); + } + + private FixedList ParseParameters(Func parseParameter) + where TParameter : class, IParameterSyntax + { + Tokens.Expect(); + var parameters = ParseManySeparated(parseParameter); + Tokens.Expect(); + return parameters.ToFixedList(); + } + + private IBodySyntax ParseFunctionBody() + { + var openBrace = Tokens.Expect(); + var statements = ParseMany(ParseStatement); + foreach (var resultStatement in statements.OfType()) + Add(ParseError.ResultStatementInBody(File, resultStatement.Span)); + var closeBrace = Tokens.Expect(); + var span = TextSpan.Covering(openBrace, closeBrace); + return new BodySyntax(span, statements.OfType().ToFixedList()); + } + + private ITypeSyntax? ParseReturn() + { + return Tokens.Accept() ? ParseType(false) : null; + } + #endregion + + #region Parse Class Declarations + private IClassDeclarationSyntax ParseClass( + ModifierParser modifiers) + { + var accessModifier = modifiers.ParseAccessModifier(); + var mutableModifier = modifiers.ParseMutableModifier(); + modifiers.ParseEndOfModifiers(); + var @class = Tokens.Expect(); + var identifier = Tokens.RequiredToken(); + Name name = identifier.Value; + var headerSpan = TextSpan.Covering(@class, identifier.Span); + var bodyParser = BodyParser(); + return new ClassDeclarationSyntax(ContainingNamespace, headerSpan, File, accessModifier, mutableModifier, identifier.Span, + name, bodyParser.ParseClassBody); + } + + private (FixedList members, TextSpan span) ParseClassBody(IClassDeclarationSyntax declaringType) + { + var openBrace = Tokens.Expect(); + var members = ParseMemberDeclarations(declaringType); + var closeBrace = Tokens.Expect(); + var span = TextSpan.Covering(openBrace, closeBrace); + return (members, span); + } + #endregion + + #region Parse Member Declarations + private FixedList ParseMemberDeclarations( + IClassDeclarationSyntax declaringType) + { + return ParseMany(() => ParseMemberDeclaration(declaringType)); + } + + internal FieldDeclarationSyntax ParseField( + IClassDeclarationSyntax declaringType, + bool mutableBinding, + ModifierParser modifiers) + { + var accessModifer = modifiers.ParseAccessModifier(); + modifiers.ParseEndOfModifiers(); + // We should only be called when there is a binding keyword + var binding = Tokens.Required(); + var identifier = Tokens.RequiredToken(); + Name name = identifier.Value; + Tokens.Expect(); + var type = ParseType(false); + IExpressionSyntax? initializer = null; + if (Tokens.Accept()) + initializer = ParseExpression(); + + var semicolon = Tokens.Expect(); + var span = TextSpan.Covering(binding, semicolon); + return new FieldDeclarationSyntax(declaringType, span, File, accessModifer, mutableBinding, + identifier.Span, name, type, initializer); + } + + internal IMemberDeclarationSyntax ParseMemberFunction( + IClassDeclarationSyntax declaringType, + ModifierParser modifiers) + { + var accessModifer = modifiers.ParseAccessModifier(); + modifiers.ParseEndOfModifiers(); + var fn = Tokens.Expect(); + var identifier = Tokens.RequiredToken(); + Name name = identifier.Value; + var bodyParser = BodyParser(); + var parameters = bodyParser.ParseParameters(bodyParser.ParseMethodParameter); + var returnType = ParseReturn(); + + var selfParameter = parameters.OfType().FirstOrDefault(); + var namedParameters = parameters.Except(parameters.OfType()) + .Cast().ToFixedList(); + + // if no self parameter, it is an associated function + if (selfParameter is null) + { + var body = bodyParser.ParseFunctionBody(); + var span = TextSpan.Covering(fn, body.Span); + return new AssociatedFunctionDeclarationSyntax(declaringType, span, File, accessModifer, identifier.Span, name, namedParameters, returnType, body); + } + + if (!(parameters[0] is ISelfParameterSyntax)) + Add(ParseError.SelfParameterMustBeFirst(File, selfParameter.Span)); + + foreach (var extraSelfParameter in parameters.OfType().Skip(1)) + Add(ParseError.ExtraSelfParameter(File, extraSelfParameter.Span)); + + // It is a method that may or may not have a body + if (Tokens.Current is IOpenBraceToken) + { + var body = bodyParser.ParseFunctionBody(); + var span = TextSpan.Covering(fn, body.Span); + return new ConcreteMethodDeclarationSyntax(declaringType, span, File, accessModifer, + identifier.Span, name, selfParameter, namedParameters, returnType, body); + } + else + { + var semicolon = bodyParser.Tokens.Expect(); + var span = TextSpan.Covering(fn, semicolon); + return new AbstractMethodDeclarationSyntax(declaringType, span, File, accessModifer, + identifier.Span, name, selfParameter, namedParameters, returnType); + } + } + + internal ConstructorDeclarationSyntax ParseConstructor( + IClassDeclarationSyntax declaringType, + ModifierParser modifiers) + { + var accessModifer = modifiers.ParseAccessModifier(); + modifiers.ParseEndOfModifiers(); + var newKeywordSpan = Tokens.Expect(); + var identifier = Tokens.AcceptToken(); + Name? name = identifier is null ? null : (Name)identifier.Value; + var bodyParser = BodyParser(); + // Implicit self parameter is taken to be after the current token which is expected to be `(` + var selfParameter = new SelfParameterSyntax(Tokens.Current.Span.AtEnd(), true); + var parameters = bodyParser.ParseParameters(bodyParser.ParseConstructorParameter); + var body = bodyParser.ParseFunctionBody(); + // For now, just say constructors have no annotations + var span = TextSpan.Covering(newKeywordSpan, body.Span); + return new ConstructorDeclarationSyntax(declaringType, span, File, accessModifer, + TextSpan.Covering(newKeywordSpan, identifier?.Span), name, selfParameter, parameters, body); + } + #endregion + } +} diff --git a/Compiler.Parsing/Parser.Expressions.cs b/Compiler.Parsing/Parser.Expressions.cs new file mode 100644 index 00000000..2cba9e07 --- /dev/null +++ b/Compiler.Parsing/Parser.Expressions.cs @@ -0,0 +1,501 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; +using UnaryOperator = Azoth.Tools.Bootstrap.Compiler.Core.Operators.UnaryOperator; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + public IExpressionSyntax? AcceptExpression() + { + try + { + switch (Tokens.Current) + { + case ICloseParenToken _: + case ICloseBraceToken _: + case ISemicolonToken _: + case ICommaToken _: + case IRightArrowToken _: + return null; + default: + return ParseExpression(); + } + } + catch (ParseFailedException) + { + return null; + } + } + + public IExpressionSyntax ParseExpression() + { + return ParseExpression(OperatorPrecedence.Min); + } + + /// + /// For expressions, we switch to a precedence climbing parser. + /// + public IExpressionSyntax ParseExpression(OperatorPrecedence minPrecedence) + { + var expression = ParseAtom(); + + for (; ; ) + { + IBinaryOperatorToken? @operator = null; + OperatorPrecedence? precedence = null; + var leftAssociative = true; + switch (Tokens.Current) + { + case IEqualsToken _: + case IPlusEqualsToken _: + case IMinusEqualsToken _: + case IAsteriskEqualsToken _: + case ISlashEqualsToken _: + if (minPrecedence <= OperatorPrecedence.Assignment) + { + var assignmentOperator = BuildAssignmentOperator(Tokens.RequiredToken()); + var rightOperand = ParseExpression(); + if (expression is IAssignableExpressionSyntax assignableExpression) + expression = new AssignmentExpressionSyntax(assignableExpression, assignmentOperator, rightOperand); + else + // Don't assign expression, so it is just the right hand side of the assignment + Add(ParseError.CantAssignIntoExpression(File, expression.Span)); + continue; + } + break; + case IQuestionQuestionToken _: + if (minPrecedence <= OperatorPrecedence.Coalesce) + { + precedence = OperatorPrecedence.Coalesce; + @operator = Tokens.RequiredToken(); + } + break; + case IOrKeywordToken _: + if (minPrecedence <= OperatorPrecedence.LogicalOr) + { + precedence = OperatorPrecedence.LogicalOr; + @operator = Tokens.RequiredToken(); + } + break; + case IAndKeywordToken _: + if (minPrecedence <= OperatorPrecedence.LogicalAnd) + { + precedence = OperatorPrecedence.LogicalAnd; + @operator = Tokens.RequiredToken(); + } + break; + case IEqualsEqualsToken _: + case INotEqualToken _: + if (minPrecedence <= OperatorPrecedence.Equality) + { + precedence = OperatorPrecedence.Equality; + @operator = Tokens.RequiredToken(); + } + break; + case ILessThanToken _: + case ILessThanOrEqualToken _: + case IGreaterThanToken _: + case IGreaterThanOrEqualToken _: + case ILessThanColonToken _: // Subtype operator + case IAsKeywordToken _: + if (minPrecedence <= OperatorPrecedence.Relational) + { + precedence = OperatorPrecedence.Relational; + @operator = Tokens.RequiredToken(); + } + break; + case IDotDotToken _: + case ILessThanDotDotToken _: + case IDotDotLessThanToken _: + case ILessThanDotDotLessThanToken _: + if (minPrecedence <= OperatorPrecedence.Range) + { + precedence = OperatorPrecedence.Range; + @operator = Tokens.RequiredToken(); + } + break; + case IPlusToken _: + case IMinusToken _: + if (minPrecedence <= OperatorPrecedence.Additive) + { + precedence = OperatorPrecedence.Additive; + @operator = Tokens.RequiredToken(); + } + break; + case IAsteriskToken _: + case ISlashToken _: + if (minPrecedence <= OperatorPrecedence.Multiplicative) + { + precedence = OperatorPrecedence.Multiplicative; + @operator = Tokens.RequiredToken(); + } + break; + case IDotToken _: + case IQuestionDotToken _: + if (minPrecedence <= OperatorPrecedence.Primary) + { + // Member Access + var accessOperator = BuildAccessOperator(Tokens.RequiredToken()); + var nameSyntax = ParseName(); + if (!(Tokens.Current is IOpenParenToken)) + { + var memberAccessSpan = TextSpan.Covering(expression.Span, nameSyntax.Span); + expression = new QualifiedNameExpressionSyntax(memberAccessSpan, expression, accessOperator, nameSyntax.ToExpression()); + } + else + { + Tokens.RequiredToken(); + var arguments = ParseArguments(); + var closeParenSpan = Tokens.Expect(); + var invocationSpan = TextSpan.Covering(expression.Span, closeParenSpan); + expression = new QualifiedInvocationExpressionSyntax(invocationSpan, expression, nameSyntax.Name, nameSyntax.Span, arguments); + } + continue; + } + break; + default: + return expression; + } + + if (!(@operator is null) && + precedence is OperatorPrecedence operatorPrecedence) + { + if (leftAssociative) + operatorPrecedence += 1; + + var rightOperand = ParseExpression(operatorPrecedence); + expression = BuildBinaryOperatorExpression(expression, @operator, rightOperand); + } + else + { + // if we didn't match any operator + return expression; + } + } + } + + private static AssignmentOperator BuildAssignmentOperator(IAssignmentToken assignmentToken) + { + return assignmentToken switch + { + IEqualsToken _ => AssignmentOperator.Simple, + IPlusEqualsToken _ => AssignmentOperator.Plus, + IMinusEqualsToken _ => AssignmentOperator.Minus, + IAsteriskEqualsToken _ => AssignmentOperator.Asterisk, + ISlashEqualsToken _ => AssignmentOperator.Slash, + _ => throw ExhaustiveMatch.Failed(assignmentToken) + }; + } + + private static AccessOperator BuildAccessOperator(IAccessOperatorToken accessOperatorToken) + { + return accessOperatorToken switch + { + IDotToken _ => AccessOperator.Standard, + IQuestionDotToken _ => AccessOperator.Conditional, + _ => throw ExhaustiveMatch.Failed(accessOperatorToken) + }; + } + + private static IExpressionSyntax BuildBinaryOperatorExpression( + IExpressionSyntax left, + IBinaryOperatorToken operatorToken, + IExpressionSyntax right) + { + BinaryOperator binaryOperator = operatorToken switch + { + IPlusToken _ => BinaryOperator.Plus, + IMinusToken _ => BinaryOperator.Minus, + IAsteriskToken _ => BinaryOperator.Asterisk, + ISlashToken _ => BinaryOperator.Slash, + IEqualsEqualsToken _ => BinaryOperator.EqualsEquals, + INotEqualToken _ => BinaryOperator.NotEqual, + ILessThanToken _ => BinaryOperator.LessThan, + ILessThanOrEqualToken _ => BinaryOperator.LessThanOrEqual, + IGreaterThanToken _ => BinaryOperator.GreaterThan, + IGreaterThanOrEqualToken _ => BinaryOperator.GreaterThanOrEqual, + IAndKeywordToken _ => BinaryOperator.And, + IOrKeywordToken _ => BinaryOperator.Or, + IDotDotToken _ => BinaryOperator.DotDot, + ILessThanDotDotToken _ => BinaryOperator.LessThanDotDot, + IDotDotLessThanToken _ => BinaryOperator.DotDotLessThan, + ILessThanDotDotLessThanToken _ => BinaryOperator.LessThanDotDotLessThan, + IQuestionQuestionToken _ => throw new NotImplementedException(), + _ => throw ExhaustiveMatch.Failed(operatorToken) + }; + return new BinaryOperatorExpressionSyntax(left, binaryOperator, right); + } + + // An atom is the unit of an expression that occurs between infix operators, i.e. an identifier, literal, group, or new + private IExpressionSyntax ParseAtom() + { + switch (Tokens.Current) + { + default: + throw ExhaustiveMatch.Failed(Tokens.Current); + case ISelfKeywordToken _: + return ParseSelfExpression(); + case INewKeywordToken _: + { + var newKeyword = Tokens.Expect(); + var type = ParseTypeName(); + Tokens.Expect(); + var arguments = ParseArguments(); + var closeParen = Tokens.Expect(); + var span = TextSpan.Covering(newKeyword, closeParen); + return new NewObjectExpressionSyntax(span, type, null, null, arguments); + } + case IReturnKeywordToken _: + { + var returnKeyword = Tokens.Expect(); + var expression = Tokens.AtEnd() ? null : ParseExpression(); + var span = TextSpan.Covering(returnKeyword, expression?.Span); + return new ReturnExpressionSyntax(span, expression); + } + case IOpenParenToken _: + return ParseParenthesizedExpression(); + case IPlusToken _: + return ParsePrefixUnaryOperator(UnaryOperator.Plus); + case IMinusToken _: + return ParsePrefixUnaryOperator(UnaryOperator.Minus); + case INotKeywordToken _: + return ParsePrefixUnaryOperator(UnaryOperator.Not); + case IBooleanLiteralToken _: + { + var literal = Tokens.RequiredToken(); + return new BoolLiteralExpressionSyntax(literal.Span, literal.Value); + } + case IIntegerLiteralToken _: + { + var literal = Tokens.RequiredToken(); + return new IntegerLiteralExpressionSyntax(literal.Span, literal.Value); + } + case IStringLiteralToken _: + { + var literal = Tokens.RequiredToken(); + return new StringLiteralExpressionSyntax(literal.Span, literal.Value); + } + case INoneKeywordToken _: + { + var literal = Tokens.Required(); + return new NoneLiteralExpressionSyntax(literal); + } + case IIdentifierToken _: + { + var nameSyntax = ParseName(); + if (!(Tokens.Current is IOpenParenToken)) + return nameSyntax.ToExpression(); + Tokens.RequiredToken(); + var arguments = ParseArguments(); + var closeParenSpan = Tokens.Expect(); + var span = TextSpan.Covering(nameSyntax.Span, closeParenSpan); + return new UnqualifiedInvocationExpressionSyntax(span, nameSyntax.Name, nameSyntax.Span, arguments); + } + case IForeachKeywordToken _: + return ParseForeach(); + case IWhileKeywordToken _: + return ParseWhile(); + case ILoopKeywordToken _: + return ParseLoop(); + case IBreakKeywordToken _: + { + var breakKeyword = Tokens.Expect(); + // TODO parse label + var expression = AcceptExpression(); + var span = TextSpan.Covering(breakKeyword, expression?.Span); + return new BreakExpressionSyntax(span, expression); + } + case INextKeywordToken _: + { + var span = Tokens.Required(); + return new NextExpressionSyntax(span); + } + case IUnsafeKeywordToken _: + return ParseUnsafeExpression(); + case IIfKeywordToken _: + return ParseIf(); + case IDotToken dot: + { + // implicit self, don't consume the '.' it will be parsed next + return new SelfExpressionSyntax(dot.Span.AtStart(), true); + } + case IMutableKeywordToken _: + { + var mut = Tokens.Required(); + // `mut` is like a unary operator + var expression = ParseExpression(OperatorPrecedence.Unary); + var span = TextSpan.Covering(mut, expression.Span); + return new MutateExpressionSyntax(span, expression); + } + case IMoveKeywordToken _: + { + var move = Tokens.Required(); + // `move` is like a unary operator + var expression = ParseExpression(OperatorPrecedence.Unary); + var span = TextSpan.Covering(move, expression.Span); + if (expression is INameExpressionSyntax name) + return new MoveExpressionSyntax(span, name); + Add(ParseError.CantMoveOutOfExpression(File, span)); + return expression; + } + case IBinaryOperatorToken _: + case IAssignmentToken _: + case IQuestionDotToken _: + case ISemicolonToken _: + case ICloseParenToken _: + // If it is one of these, we assume there is a missing identifier + return ParseMissingIdentifier(); + case IOpenBraceToken _: + case ICloseBraceToken _: + case IColonToken _: + case IColonColonDotToken _: + case ILessThanColonToken _: + case ICommaToken _: + case IRightArrowToken _: + case IQuestionToken _: + case IKeywordToken _: + case IEndOfFileToken _: + Add(ParseError.UnexpectedEndOfExpression(File, Tokens.Current.Span.AtStart())); + throw new ParseFailedException("Unexpected end of expression"); + case IRightDoubleArrowToken _: + throw new NotImplementedException($"`{Tokens.Current.Text(File.Code)}` in expression position"); + } + } + + private ISelfExpressionSyntax ParseSelfExpression() + { + var selfKeyword = Tokens.Expect(); + return new SelfExpressionSyntax(selfKeyword, false); + } + + private INameExpressionSyntax ParseMissingIdentifier() + { + var identifierSpan = Tokens.Expect(); + return new NameExpressionSyntax(identifierSpan, null); + } + + private IUnsafeExpressionSyntax ParseUnsafeExpression() + { + var unsafeKeyword = Tokens.Expect(); + var isBlock = Tokens.Current is IOpenBraceToken; + var expression = isBlock + ? ParseBlock() + : ParseParenthesizedExpression(); + var span = TextSpan.Covering(unsafeKeyword, expression.Span); + return new UnsafeExpressionSyntax(span, expression); + } + + private IUnaryOperatorExpressionSyntax ParsePrefixUnaryOperator(UnaryOperator @operator) + { + var operatorSpan = Tokens.Required(); + var operand = ParseExpression(OperatorPrecedence.Unary); + var span = TextSpan.Covering(operatorSpan, operand.Span); + return new UnaryOperatorExpressionSyntax(span, UnaryOperatorFixity.Prefix, @operator, operand); + } + + private IForeachExpressionSyntax ParseForeach() + { + var foreachKeyword = Tokens.Expect(); + var mutableBinding = Tokens.Accept(); + var identifier = Tokens.RequiredToken(); + var variableName = identifier.Value; + ITypeSyntax? type = null; + if (Tokens.Accept()) + type = ParseType(false); + Tokens.Expect(); + var expression = ParseExpression(); + var block = ParseBlock(); + var span = TextSpan.Covering(foreachKeyword, block.Span); + return new ForeachExpressionSyntax(span, mutableBinding, variableName, type, expression, block); + } + + private IWhileExpressionSyntax ParseWhile() + { + var whileKeyword = Tokens.Expect(); + var condition = ParseExpression(); + var block = ParseBlock(); + var span = TextSpan.Covering(whileKeyword, block.Span); + return new WhileExpressionSyntax(span, condition, block); + } + + private ILoopExpressionSyntax ParseLoop() + { + var loopKeyword = Tokens.Expect(); + var block = ParseBlock(); + var span = TextSpan.Covering(loopKeyword, block.Span); + return new LoopExpressionSyntax(span, block); + } + + private IIfExpressionSyntax ParseIf(ParseAs parseAs = ParseAs.Expression) + { + var @if = Tokens.Expect(); + var condition = ParseExpression(); + var thenBlock = ParseBlockOrResult(); + var elseClause = AcceptElse(parseAs); + var span = TextSpan.Covering(@if, thenBlock.Span, elseClause?.Span); + if (parseAs == ParseAs.Statement + && elseClause is null + && thenBlock is IResultStatementSyntax) + { + var semicolon = Tokens.Expect(); + span = TextSpan.Covering(span, semicolon); + } + return new IfExpressionSyntax(span, condition, thenBlock, elseClause); + } + + private IElseClauseSyntax? AcceptElse(ParseAs parseAs) + { + if (!Tokens.Accept()) + return null; + var expression = Tokens.Current is IIfKeywordToken + ? (IElseClauseSyntax)ParseIf(parseAs) + : ParseBlockOrResult(); + if (parseAs == ParseAs.Statement + && expression is IResultStatementSyntax) + Tokens.Expect(); + return expression; + } + + /// + /// Parse an expression that is required to have parenthesis around it. + /// for example `unsafe(x);`. + /// + private IExpressionSyntax ParseParenthesizedExpression() + { + Tokens.Expect(); + var expression = ParseExpression(); + Tokens.Expect(); + return expression; + } + + public FixedList ParseArguments() + { + return AcceptManySeparated(AcceptArgument); + } + + private IArgumentSyntax? AcceptArgument() + { + var expression = AcceptExpression(); + if (expression is null) return null; + return new ArgumentSyntax(expression); + } + + public IBlockOrResultSyntax ParseBlockOrResult() + { + if (Tokens.Current is IOpenBraceToken) + return ParseBlock(); + + var rightDoubleArrow = Tokens.Expect(); + var expression = ParseExpression(); + var span = TextSpan.Covering(rightDoubleArrow, expression.Span); + return new ResultStatementSyntax(span, expression); + } + } +} diff --git a/Compiler.Parsing/Parser.Lists.cs b/Compiler.Parsing/Parser.Lists.cs new file mode 100644 index 00000000..1e4cefd1 --- /dev/null +++ b/Compiler.Parsing/Parser.Lists.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + /// + /// Accept items until not accepted + /// + public static FixedList AcceptMany(Func acceptItem) + where T : class + { + return new Generator(acceptItem) + .TakeWhile(t => t != null).ToFixedList()!; + } + + /// + /// Accept items as long as there is another separator + /// + public FixedList AcceptManySeparated(Func acceptItem) + where T : class + where TSeparator : class, IToken + { + var items = new List(); + var item = acceptItem(); + while (item != null) + { + items.Add(item); + if (!Tokens.Accept()) break; + + item = acceptItem(); + } + + return items.ToFixedList(); + } + + /// + /// Parse items until a terminator is found + /// + public FixedList ParseMany(Func parseItem) + where T : class + where TTerminator : IToken + { + var items = new List(); + while (!Tokens.AtEnd()) + items.Add(parseItem()); + + return items.ToFixedList(); + } + + /// + /// Parse an expected item and then parse additional items as long as there is + /// a separator. + /// + public FixedList ParseManySeparated(Func parseItem) + //where T : class + where TSeparator : class, IToken + { + var item = parseItem(); + if (item is null) throw new ParseFailedException(); + var items = new List() { item }; + + while (Tokens.Accept()) items.Add(parseItem()); + + return items.ToFixedList(); + } + + /// + /// Parse items as long as there is another separator or until a terminator is found + /// + public FixedList ParseManySeparated(Func parseItem) + where T : class + where TSeparator : class, IToken + where TTerminator : IToken + { + var items = new List(); + while (!Tokens.AtEnd()) + { + items.Add(parseItem()); + if (!Tokens.Accept()) + break; + } + + return items.ToFixedList(); + } + } +} diff --git a/Compiler.Parsing/Parser.Names.cs b/Compiler.Parsing/Parser.Names.cs new file mode 100644 index 00000000..903e9cb0 --- /dev/null +++ b/Compiler.Parsing/Parser.Names.cs @@ -0,0 +1,15 @@ +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + private NameSyntax ParseName() + { + var identifier = Tokens.RequiredToken(); + var name = identifier.Value; + return new NameSyntax(identifier.Span, name); + } + } +} diff --git a/Compiler.Parsing/Parser.Parameters.cs b/Compiler.Parsing/Parser.Parameters.cs new file mode 100644 index 00000000..5a43b567 --- /dev/null +++ b/Compiler.Parsing/Parser.Parameters.cs @@ -0,0 +1,63 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + public INamedParameterSyntax ParseFunctionParameter() + { + var span = Tokens.Current.Span; + var mutableBinding = Tokens.Accept(); + var identifier = Tokens.RequiredToken(); + var name = identifier.Value; + Tokens.Expect(); + var type = ParseType(true); + IExpressionSyntax? defaultValue = null; + if (Tokens.Accept()) defaultValue = ParseExpression(); + span = TextSpan.Covering(span, type.Span, defaultValue?.Span); + return new NamedParameterSyntax(span, mutableBinding, name, type, defaultValue); + } + + public IParameterSyntax ParseMethodParameter() + { + switch (Tokens.Current) + { + case IMutableKeywordToken _: + case ISelfKeywordToken _: + { + var span = Tokens.Current.Span; + var mutableSelf = Tokens.Accept(); + var selfSpan = Tokens.Expect(); + span = TextSpan.Covering(span, selfSpan); + return new SelfParameterSyntax(span, mutableSelf); + } + default: + return ParseFunctionParameter(); + } + } + + public IConstructorParameterSyntax ParseConstructorParameter() + { + switch (Tokens.Current) + { + case IDotToken _: + { + var dot = Tokens.Expect(); + var identifier = Tokens.RequiredToken(); + var equals = Tokens.AcceptToken(); + IExpressionSyntax? defaultValue = null; + if (equals != null) defaultValue = ParseExpression(); + var span = TextSpan.Covering(dot, identifier.Span, defaultValue?.Span); + Name name = identifier.Value; + return new FieldParameterSyntax(span, name, defaultValue); + } + default: + return ParseFunctionParameter(); + } + } + } +} diff --git a/Compiler.Parsing/Parser.Statements.cs b/Compiler.Parsing/Parser.Statements.cs new file mode 100644 index 00000000..fe9ccf7b --- /dev/null +++ b/Compiler.Parsing/Parser.Statements.cs @@ -0,0 +1,129 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + public IStatementSyntax ParseStatement() + { + switch (Tokens.Current) + { + case IOpenBraceToken _: + var block = ParseBlock(); + return new ExpressionStatementSyntax(block.Span, block); + case ILetKeywordToken _: + var let = Tokens.Expect(); + return ParseRestOfVariableDeclaration(let, false); + case IVarKeywordToken _: + var @var = Tokens.Expect(); + return ParseRestOfVariableDeclaration(@var, true); + case IForeachKeywordToken _: + var @foreach = ParseForeach(); + return new ExpressionStatementSyntax(@foreach.Span, @foreach); + case IWhileKeywordToken _: + var @while = ParseWhile(); + return new ExpressionStatementSyntax(@while.Span, @while); + case ILoopKeywordToken _: + var loop = ParseLoop(); + return new ExpressionStatementSyntax(loop.Span, loop); + case IIfKeywordToken _: + var @if = ParseIf(ParseAs.Statement); + return new ExpressionStatementSyntax(@if.Span, @if); + case IUnsafeKeywordToken _: + return ParseUnsafeStatement(); + default: + try + { + var expression = ParseExpression(); + var semicolon = Tokens.Expect(); + return new ExpressionStatementSyntax( + TextSpan.Covering(expression.Span, semicolon), expression); + } + catch (ParseFailedException) + { + SkipToEndOfStatement(); + throw; + } + } + } + + // Requires the binding has already been consumed + private IStatementSyntax ParseRestOfVariableDeclaration( + TextSpan binding, + bool mutableBinding) + { + var identifier = Tokens.RequiredToken(); + var name = identifier.Value; + ITypeSyntax? type = null; + bool inferMutableType = false; + if (Tokens.Accept()) + (type, inferMutableType) = ParseVariableDeclarationType(); + + IExpressionSyntax? initializer = null; + if (Tokens.Accept()) + initializer = ParseExpression(); + + var semicolon = Tokens.Expect(); + var span = TextSpan.Covering(binding, semicolon); + return new VariableDeclarationStatementSyntax(span, + mutableBinding, name, identifier.Span, type, inferMutableType, initializer); + } + + private (ITypeSyntax? Type, bool InferMutableType) ParseVariableDeclarationType() + { + throw new NotImplementedException(nameof(ParseVariableDeclarationType)); + //var mutableKeyword = Tokens.AcceptToken(); + //if (mutableKeyword is null) + // return (ParseType(false), false); + + //switch (Tokens.Current) + //{ + // case IEqualsToken _: + // case ISemicolonToken _: + // return (null, true); + // default: + // return (ParseMutableType(mutableKeyword), false); + //} + } + + private IExpressionStatementSyntax ParseUnsafeStatement() + { + var unsafeKeyword = Tokens.Expect(); + var isBlock = Tokens.Current is IOpenBraceToken; + var expression = isBlock ? ParseBlock() : ParseParenthesizedExpression(); + var span = TextSpan.Covering(unsafeKeyword, expression.Span); + var unsafeExpression = new UnsafeExpressionSyntax(span, expression); + if (!isBlock) + { + var semicolon = Tokens.Expect(); + span = TextSpan.Covering(span, semicolon); + } + return new ExpressionStatementSyntax(span, unsafeExpression); + } + + public IBlockExpressionSyntax ParseBlock() + { + var openBrace = Tokens.Expect(); + var statements = ParseMany(ParseStatement); + var closeBrace = Tokens.Expect(); + var span = TextSpan.Covering(openBrace, closeBrace); + return new BlockExpressionSyntax(span, statements); + } + + /// + /// Skip tokens until we reach what we assume to be the end of a statement + /// + private void SkipToEndOfStatement() + { + while (!Tokens.AtEnd()) + Tokens.Next(); + + // Consume the semicolon if we aren't at the end of the file. + _ = Tokens.Accept(); + } + } +} diff --git a/Compiler.Parsing/Parser.Types.cs b/Compiler.Parsing/Parser.Types.cs new file mode 100644 index 00000000..42fc7336 --- /dev/null +++ b/Compiler.Parsing/Parser.Types.cs @@ -0,0 +1,143 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; +using static Azoth.Tools.Bootstrap.Compiler.Types.ReferenceCapability; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + public ITypeSyntax ParseType(bool inParameter) + { + var typeSyntax = ParseTypeWithCapability(inParameter); + + IQuestionToken? question; + while ((question = Tokens.AcceptToken()) != null) + { + var span = TextSpan.Covering(typeSyntax.Span, question.Span); + return new OptionalTypeSyntax(span, typeSyntax); + } + + return typeSyntax; + } + + private ITypeSyntax ParseTypeWithCapability(bool inParameter) + { + var lent = Tokens.Current is ILentKeywordToken + ? Tokens.RequiredToken() : null; + + switch (Tokens.Current) + { + case IIsolatedKeywordToken _: + { + var isoKeyword = Tokens.RequiredToken(); + var referent = ParseBareType(); + var span = TextSpan.Covering(lent?.Span, isoKeyword.Span, referent.Span); + var capability = lent is null ? Isolated : LentIsolated; + return new CapabilityTypeSyntax(capability, referent, span); + } + case ITransitionKeywordToken _: + { + var trnKeyword = Tokens.RequiredToken(); + var referent = ParseBareType(); + var span = TextSpan.Covering(lent?.Span, trnKeyword.Span, referent.Span); + var capability = lent is null ? Transition : LentTransition; + return new CapabilityTypeSyntax(capability, referent, span); + } + case IConstKeywordToken _: + { + var constKeyword = Tokens.RequiredToken(); + var referent = ParseBareType(); + var span = TextSpan.Covering(lent?.Span, constKeyword.Span, referent.Span); + var capability = lent is null ? Const : LentConst; + return new CapabilityTypeSyntax(capability, referent, span); + } + case IMutableKeywordToken _: + { + var mutableKeyword = Tokens.RequiredToken(); + var referent = ParseBareType(); + var span = TextSpan.Covering(lent?.Span, mutableKeyword.Span, referent.Span); + var capability = lent != null || inParameter ? LentMutable : SharedMutable; + return new CapabilityTypeSyntax(capability, referent, span); + } + case ISharedKeywordToken _: + { + if (lent != null) Tokens.UnexpectedToken(); + // TODO If lent then error on the shared afterward. Make it lent + // We may have consumed the shared keyword token if it came after `lent` + var sharedKeyword = Tokens.AcceptToken(); + var mutableKeyword = Tokens.AcceptToken(); + var referent = ParseBareType(); + var span = TextSpan.Covering(lent?.Span, sharedKeyword?.Span, mutableKeyword?.Span, referent.Span); + ReferenceCapability capability; + if (lent is null) + capability = mutableKeyword is null ? Shared : SharedMutable; + else + capability = mutableKeyword is null ? Lent : LentMutable; + return new CapabilityTypeSyntax(capability, referent, span); + } + case IIdKeywordToken _: + { + // TODO If lent then error, but just make it an id + var idKeyword = Tokens.RequiredToken(); + var referent = ParseBareType(); + var span = TextSpan.Covering(lent?.Span, idKeyword.Span, referent.Span); + return new CapabilityTypeSyntax(Identity, referent, span); + } + default: + { + if (lent is null) + // Could be a value type + return ParseBareType(); + + var mutableKeyword = Tokens.AcceptToken(); + var referent = ParseBareType(); + var span = TextSpan.Covering(lent.Span, mutableKeyword?.Span, referent.Span); + var capability = mutableKeyword is null ? Lent : LentMutable; + return new CapabilityTypeSyntax(capability, referent, span); + } + } + } + + private ITypeSyntax ParseBareType() + { + return Tokens.Current switch + { + IPrimitiveTypeToken _ => ParsePrimitiveType(), + // otherwise we want a type name + _ => ParseTypeName() + }; + } + + private ITypeNameSyntax ParseTypeName() + { + var identifier = Tokens.RequiredToken(); + var name = identifier.Value; + return new TypeNameSyntax(identifier.Span, name); + } + + private ITypeNameSyntax ParsePrimitiveType() + { + var keyword = Tokens.RequiredToken(); + SpecialTypeName name = keyword switch + { + IVoidKeywordToken _ => SpecialTypeName.Void, + INeverKeywordToken _ => SpecialTypeName.Never, + IBoolKeywordToken _ => SpecialTypeName.Bool, + IAnyKeywordToken _ => SpecialTypeName.Any, + IByteKeywordToken _ => SpecialTypeName.Byte, + IIntKeywordToken _ => SpecialTypeName.Int, + IUIntKeywordToken _ => SpecialTypeName.UInt, + ISizeKeywordToken _ => SpecialTypeName.Size, + IOffsetKeywordToken _ => SpecialTypeName.Offset, + _ => throw ExhaustiveMatch.Failed(keyword) + }; + + return new TypeNameSyntax(keyword.Span, name); + } + } +} diff --git a/Compiler.Parsing/Parser.UsingDirectives.cs b/Compiler.Parsing/Parser.UsingDirectives.cs new file mode 100644 index 00000000..a61b9f12 --- /dev/null +++ b/Compiler.Parsing/Parser.UsingDirectives.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Parsing.Tree; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser + { + public FixedList ParseUsingDirectives() + { + return AcceptMany(AcceptUsingDirective); + } + + public IUsingDirectiveSyntax? AcceptUsingDirective() + { + var accept = Tokens.AcceptToken(); + if (accept is null) + return null; + var identifiers = ParseManySeparated<(IIdentifierToken?, TextSpan), IDotToken>( + () => Tokens.ExpectToken()); + NamespaceName name = NamespaceName.Global; + foreach (var (identifier, _) in identifiers) + if (!(identifier is null)) + name = name.Qualify(identifier.Value); + var semicolon = Tokens.Expect(); + var span = TextSpan.Covering(accept.Span, semicolon); + return new UsingDirectiveSyntax(span, name); + } + } +} diff --git a/Compiler.Parsing/Parser.cs b/Compiler.Parsing/Parser.cs new file mode 100644 index 00000000..c76008b5 --- /dev/null +++ b/Compiler.Parsing/Parser.cs @@ -0,0 +1,38 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public partial class Parser : RecursiveDescentParser + { + private readonly NamespaceName? containingNamespace; + private NamespaceName ContainingNamespace => containingNamespace + ?? throw new InvalidOperationException("Non-member declaration not nested inside a containing namespace"); + + public Parser(ITokenIterator tokens, NamespaceName? containingNamespace) + : base(tokens) + { + this.containingNamespace = containingNamespace; + } + + /// + /// A member parser drops the containingNamespace because there isn't one + /// + protected Parser BodyParser() + { + return containingNamespace is null ? this : new Parser(Tokens, null); + } + + /// + /// A nested parser establishes a nested naming context for things parsed by it. + /// + protected Parser NamespaceBodyParser(NamespaceName namespaceName) + { + _ = containingNamespace + ?? throw new InvalidOperationException("Namespace not nested inside a containing namespace"); + return new Parser(Tokens, containingNamespace.Qualify(namespaceName)); + } + } +} diff --git a/Compiler.Parsing/RecursiveDescentParser.cs b/Compiler.Parsing/RecursiveDescentParser.cs new file mode 100644 index 00000000..fd0a73ec --- /dev/null +++ b/Compiler.Parsing/RecursiveDescentParser.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + public class RecursiveDescentParser + { + protected CodeFile File { get; } + protected ITokenIterator Tokens { get; } + + public RecursiveDescentParser(ITokenIterator tokens) + { + File = tokens.Context.File; + Tokens = tokens; + } + + protected void Add(Diagnostic diagnostic) + { + Tokens.Context.Diagnostics.Add(diagnostic); + } + } +} diff --git a/Compiler.Parsing/TokenIteratorExtensions.cs b/Compiler.Parsing/TokenIteratorExtensions.cs new file mode 100644 index 00000000..e55d9c0a --- /dev/null +++ b/Compiler.Parsing/TokenIteratorExtensions.cs @@ -0,0 +1,128 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing +{ + /// + /// * Required: If the current token is of the given type consume it, otherwise leave it, but + /// add a compiler error that an expected token is missing AND throw a . + /// * Expected: If the current token is of the given type consume it, otherwise leave it, but + /// add a compiler error that an expected token is missing. + /// * Accept: If the current token is of the given type consume it, otherwise leave it. + /// + public static class TokenIteratorExtensions + { + #region Required + public static TextSpan Required(this ITokenIterator tokens) + where T : IToken + { + if (tokens.Current is T token) + { + tokens.Next(); + return token.Span; + } + + tokens.Context.Diagnostics.Add( + ParseError.MissingToken(tokens.Context.File, typeof(T), tokens.Current)); + throw new ParseFailedException($"Requires {typeof(T).GetFriendlyName()}, found {tokens.Current.GetType().GetFriendlyName()}"); + } + + public static T RequiredToken(this ITokenIterator tokens) + where T : IToken + { + if (tokens.Current is T token) + { + tokens.Next(); + return token; + } + + tokens.Context.Diagnostics.Add( + ParseError.MissingToken(tokens.Context.File, typeof(T), tokens.Current)); + throw new ParseFailedException($"Requires {typeof(T).GetFriendlyName()}, found {tokens.Current.GetType().GetFriendlyName()}"); + } + #endregion + + #region Accept + public static bool Accept(this ITokenIterator tokens) + where T : class, IToken + { + if (tokens.Current is T) + { + tokens.Next(); + return true; + } + + return false; + } + + public static T? AcceptToken(this ITokenIterator tokens) + where T : class, IToken + { + if (tokens.Current is T token) + { + tokens.Next(); + return token; + } + + return null; + } + #endregion + + #region Expect + public static TextSpan Expect(this ITokenIterator tokens) + where T : IToken + { + if (tokens.Current is T token) + { + tokens.Next(); + return token.Span; + } + + tokens.Context.Diagnostics.Add( + ParseError.MissingToken(tokens.Context.File, typeof(T), tokens.Current)); + // An empty span at the current location + return new TextSpan(tokens.Current.Span.Start, 0); + } + + public static (T?, TextSpan) ExpectToken(this ITokenIterator tokens) + where T : class, IToken + { + if (tokens.Current is T token) + { + tokens.Next(); + return (token, token.Span); + } + + tokens.Context.Diagnostics.Add(ParseError.MissingToken(tokens.Context.File, typeof(T), tokens.Current)); + return (null, new TextSpan(tokens.Current.Span.Start, 0)); + } + #endregion + + /// + /// The current token is unexpected, report an error and consume it. + /// + public static TextSpan UnexpectedToken(this ITokenIterator tokens) + { + // TODO shouldn't we ignore or combine unexpected token errors until we parse something successfully? + var span = tokens.Current.Span; + tokens.Context.Diagnostics.Add(ParseError.UnexpectedToken(tokens.Context.File, span)); + tokens.Next(); + return span; + } + + public static bool AtEnd(this ITokenIterator tokens) + where T : IToken + { + switch (tokens.Current) + { + case T _: + case IEndOfFileToken _: + return true; + default: + return false; + } + } + } +} diff --git a/Compiler.Parsing/Tree/AbstractMethodDeclarationSyntax.cs b/Compiler.Parsing/Tree/AbstractMethodDeclarationSyntax.cs new file mode 100644 index 00000000..dbc2852d --- /dev/null +++ b/Compiler.Parsing/Tree/AbstractMethodDeclarationSyntax.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class AbstractMethodDeclarationSyntax : MethodDeclarationSyntax + { + public AbstractMethodDeclarationSyntax( + IClassDeclarationSyntax declaringClass, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name name, + ISelfParameterSyntax selfParameter, + FixedList parameters, + ITypeSyntax? returnType) + : base(declaringClass, span, file, accessModifier, nameSpan, name, + selfParameter, parameters, returnType) + { + } + + public override string ToString() + { + var returnType = ReturnType != null ? " -> " + ReturnType : ""; + return $"fn {Name}({string.Join(", ", Parameters.Prepend(SelfParameter))}){returnType};"; + } + } +} diff --git a/Compiler.Parsing/Tree/ArgumentSyntax.cs b/Compiler.Parsing/Tree/ArgumentSyntax.cs new file mode 100644 index 00000000..07aac9d1 --- /dev/null +++ b/Compiler.Parsing/Tree/ArgumentSyntax.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.CST; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class ArgumentSyntax : Syntax, IArgumentSyntax + { + private IExpressionSyntax expression; + public ref IExpressionSyntax Expression => ref expression; + + public ArgumentSyntax(IExpressionSyntax expression) + : base(expression.Span) + { + this.expression = expression; + } + + public override string ToString() + { + return Expression.ToString(); + } + } +} diff --git a/Compiler.Parsing/Tree/AssignmentExpressionSyntax.cs b/Compiler.Parsing/Tree/AssignmentExpressionSyntax.cs new file mode 100644 index 00000000..6371d333 --- /dev/null +++ b/Compiler.Parsing/Tree/AssignmentExpressionSyntax.cs @@ -0,0 +1,35 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class AssignmentExpressionSyntax : ExpressionSyntax, IAssignmentExpressionSyntax + { + private IAssignableExpressionSyntax leftOperand; + public ref IAssignableExpressionSyntax LeftOperand => ref leftOperand; + + public AssignmentOperator Operator { get; } + private IExpressionSyntax rightOperand; + public ref IExpressionSyntax RightOperand => ref rightOperand; + + public AssignmentExpressionSyntax( + IAssignableExpressionSyntax leftOperand, + AssignmentOperator @operator, + IExpressionSyntax rightOperand) + : base(TextSpan.Covering(leftOperand.Span, rightOperand.Span)) + { + this.leftOperand = leftOperand; + this.rightOperand = rightOperand; + Operator = @operator; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Assignment; + + public override string ToString() + { + return $"{LeftOperand.ToGroupedString(ExpressionPrecedence)} {Operator.ToSymbolString()} {RightOperand.ToGroupedString(ExpressionPrecedence)}"; + } + } +} diff --git a/Compiler.Parsing/Tree/AssociatedFunctionDeclarationSyntax.cs b/Compiler.Parsing/Tree/AssociatedFunctionDeclarationSyntax.cs new file mode 100644 index 00000000..6c9610b1 --- /dev/null +++ b/Compiler.Parsing/Tree/AssociatedFunctionDeclarationSyntax.cs @@ -0,0 +1,46 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class AssociatedFunctionDeclarationSyntax : InvocableDeclarationSyntax, IAssociatedFunctionDeclarationSyntax + { + public IClassDeclarationSyntax DeclaringClass { get; } + public new Name Name { get; } + public new FixedList Parameters { get; } + public ITypeSyntax? ReturnType { get; } + public IBodySyntax Body { get; } + public new AcyclicPromise Symbol { get; } + + public AssociatedFunctionDeclarationSyntax( + IClassDeclarationSyntax declaringClass, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name name, + FixedList parameters, + ITypeSyntax? returnTypeSyntax, + IBodySyntax body) + : base(span, file, accessModifier, nameSpan, name, parameters, new AcyclicPromise()) + { + DeclaringClass = declaringClass; + Name = name; + Parameters = parameters; + ReturnType = returnTypeSyntax; + Body = body; + Symbol = (AcyclicPromise)base.Symbol; + } + + public override string ToString() + { + var returnType = ReturnType != null ? " -> " + ReturnType : ""; + return $"fn {Name}({string.Join(", ", Parameters)}){returnType} {Body}"; + } + } +} diff --git a/Compiler.Parsing/Tree/BinaryOperatorExpressionSyntax.cs b/Compiler.Parsing/Tree/BinaryOperatorExpressionSyntax.cs new file mode 100644 index 00000000..5ed1e106 --- /dev/null +++ b/Compiler.Parsing/Tree/BinaryOperatorExpressionSyntax.cs @@ -0,0 +1,62 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class BinaryOperatorExpressionSyntax : ExpressionSyntax, IBinaryOperatorExpressionSyntax + { + private IExpressionSyntax leftOperand; + public ref IExpressionSyntax LeftOperand => ref leftOperand; + + public BinaryOperator Operator { get; } + + + private IExpressionSyntax rightOperand; + public ref IExpressionSyntax RightOperand => ref rightOperand; + + public BinaryOperatorExpressionSyntax( + IExpressionSyntax leftOperand, + BinaryOperator @operator, + IExpressionSyntax rightOperand) + : base(TextSpan.Covering(leftOperand.Span, rightOperand.Span)) + { + this.leftOperand = leftOperand; + Operator = @operator; + this.rightOperand = rightOperand; + } + + protected override OperatorPrecedence ExpressionPrecedence => Operator switch + { + BinaryOperator.And => OperatorPrecedence.LogicalAnd, + BinaryOperator.Or => OperatorPrecedence.LogicalOr, + + BinaryOperator.Plus => OperatorPrecedence.Additive, + BinaryOperator.Minus => OperatorPrecedence.Additive, + + BinaryOperator.Asterisk => OperatorPrecedence.Multiplicative, + BinaryOperator.Slash => OperatorPrecedence.Multiplicative, + + BinaryOperator.DotDot => OperatorPrecedence.Range, + BinaryOperator.DotDotLessThan => OperatorPrecedence.Range, + BinaryOperator.LessThanDotDot => OperatorPrecedence.Range, + BinaryOperator.LessThanDotDotLessThan => OperatorPrecedence.Range, + + BinaryOperator.EqualsEquals => OperatorPrecedence.Relational, + BinaryOperator.NotEqual => OperatorPrecedence.Relational, + BinaryOperator.LessThan => OperatorPrecedence.Relational, + BinaryOperator.LessThanOrEqual => OperatorPrecedence.Relational, + BinaryOperator.GreaterThan => OperatorPrecedence.Relational, + BinaryOperator.GreaterThanOrEqual => OperatorPrecedence.Relational, + + _ => throw ExhaustiveMatch.Failed(Operator), + }; + + public override string ToString() + { + return $"{LeftOperand.ToGroupedString(ExpressionPrecedence)} {Operator.ToSymbolString()} {RightOperand.ToGroupedString(ExpressionPrecedence)}"; + } + } +} diff --git a/Compiler.Parsing/Tree/BlockExpressionSyntax.cs b/Compiler.Parsing/Tree/BlockExpressionSyntax.cs new file mode 100644 index 00000000..b90882d5 --- /dev/null +++ b/Compiler.Parsing/Tree/BlockExpressionSyntax.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class BlockExpressionSyntax : ExpressionSyntax, IBlockExpressionSyntax + { + public FixedList Statements { [DebuggerStepThrough] get; } + + public BlockExpressionSyntax( + TextSpan span, + FixedList statements) + : base(span) + { + Statements = statements; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + if (Statements.Any()) + return $"{{ {Statements.Count} Statements }} : {DataType}"; + + return $"{{ }} : {DataType}"; + } + } +} diff --git a/Compiler.Parsing/Tree/BodySyntax.cs b/Compiler.Parsing/Tree/BodySyntax.cs new file mode 100644 index 00000000..78925e42 --- /dev/null +++ b/Compiler.Parsing/Tree/BodySyntax.cs @@ -0,0 +1,30 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class BodySyntax : Syntax, IBodySyntax + { + public FixedList Statements { [DebuggerStepThrough] get; } + private readonly FixedList statements; + FixedList IBodyOrBlockSyntax.Statements + { + [DebuggerStepThrough] + get => statements; + } + + public BodySyntax(TextSpan span, FixedList statements) + : base(span) + { + Statements = statements; + this.statements = statements.ToFixedList(); + } + + public override string ToString() + { + return "{ … }"; + } + } +} diff --git a/Compiler.Parsing/Tree/BoolLiteralExpressionSyntax.cs b/Compiler.Parsing/Tree/BoolLiteralExpressionSyntax.cs new file mode 100644 index 00000000..98aeca56 --- /dev/null +++ b/Compiler.Parsing/Tree/BoolLiteralExpressionSyntax.cs @@ -0,0 +1,26 @@ +using System.Diagnostics; +using System.Globalization; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class BoolLiteralExpressionSyntax : LiteralExpressionSyntax, IBoolLiteralExpressionSyntax + { + public bool Value { [DebuggerStepThrough] get; } + + public BoolLiteralExpressionSyntax(TextSpan span, bool value) + : base(span, ExpressionSemantics.Copy) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return Value.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/Compiler.Parsing/Tree/BreakExpressionSyntax.cs b/Compiler.Parsing/Tree/BreakExpressionSyntax.cs new file mode 100644 index 00000000..9582e823 --- /dev/null +++ b/Compiler.Parsing/Tree/BreakExpressionSyntax.cs @@ -0,0 +1,29 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class BreakExpressionSyntax : ExpressionSyntax, IBreakExpressionSyntax + { + private IExpressionSyntax? value; + public ref IExpressionSyntax? Value => ref value; + + public BreakExpressionSyntax( + TextSpan span, + IExpressionSyntax? value) + : base(span, ExpressionSemantics.Void) + { + this.value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => Value != null ? OperatorPrecedence.Min : OperatorPrecedence.Primary; + + public override string ToString() + { + if (Value != null) + return $"break {Value}"; + return "break"; + } + } +} diff --git a/Compiler.Parsing/Tree/CapabilityTypeSyntax.cs b/Compiler.Parsing/Tree/CapabilityTypeSyntax.cs new file mode 100644 index 00000000..23b0cdee --- /dev/null +++ b/Compiler.Parsing/Tree/CapabilityTypeSyntax.cs @@ -0,0 +1,27 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class CapabilityTypeSyntax : TypeSyntax, ICapabilityTypeSyntax + { + public ITypeSyntax ReferentType { get; } + public ReferenceCapability Capability { get; } + + public CapabilityTypeSyntax( + ReferenceCapability referenceCapability, + ITypeSyntax referentType, + TextSpan span) + : base(span) + { + ReferentType = referentType; + Capability = referenceCapability; + } + + public override string ToString() + { + return $"{Capability} {ReferentType}"; + } + } +} diff --git a/Compiler.Parsing/Tree/ClassDeclarationSyntax.cs b/Compiler.Parsing/Tree/ClassDeclarationSyntax.cs new file mode 100644 index 00000000..bc6ef812 --- /dev/null +++ b/Compiler.Parsing/Tree/ClassDeclarationSyntax.cs @@ -0,0 +1,82 @@ +using System; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class ClassDeclarationSyntax : DeclarationSyntax, IClassDeclarationSyntax + { + public NamespaceName ContainingNamespaceName { get; } + + private NamespaceOrPackageSymbol? containingNamespaceSymbol; + public NamespaceOrPackageSymbol ContainingNamespaceSymbol + { + get => containingNamespaceSymbol + ?? throw new InvalidOperationException($"{ContainingNamespaceSymbol} not yet assigned"); + set + { + if (containingNamespaceSymbol != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingNamespaceSymbol)} repeatedly"); + containingNamespaceSymbol = value; + } + } + + public IAccessModifierToken? AccessModifier { get; } + public IMutableKeywordToken? MutableModifier { get; } + public new Name Name { get; } + public new AcyclicPromise Symbol { get; } + public FixedList Members { get; } + public ConstructorSymbol? DefaultConstructorSymbol { get; private set; } + + public ClassDeclarationSyntax( + NamespaceName containingNamespaceName, + TextSpan headerSpan, + CodeFile file, + IAccessModifierToken? accessModifier, + IMutableKeywordToken? mutableModifier, + TextSpan nameSpan, + Name name, + Func, TextSpan)> parseMembers) + : base(headerSpan, file, name, nameSpan, new AcyclicPromise()) + { + ContainingNamespaceName = containingNamespaceName; + AccessModifier = accessModifier; + MutableModifier = mutableModifier; + Name = name; + var (members, bodySpan) = parseMembers(this); + Members = members; + Span = TextSpan.Covering(headerSpan, bodySpan); + Symbol = (AcyclicPromise)base.Symbol; + } + + public void CreateDefaultConstructor(SymbolTreeBuilder symbolTree) + { + if (Members.Any(m => m is IConstructorDeclarationSyntax)) + return; + + if (!(DefaultConstructorSymbol is null)) + throw new InvalidOperationException($"Can't {nameof(CreateDefaultConstructor)} twice"); + + var constructedType = Symbol.Result.DeclaresDataType; + var constructorSymbol = new ConstructorSymbol(Symbol.Result, null, FixedList.Empty); + var selfParameterSymbol = new SelfParameterSymbol(constructorSymbol, constructedType); + + symbolTree.Add(constructorSymbol); + symbolTree.Add(selfParameterSymbol); + DefaultConstructorSymbol = constructorSymbol; + } + + public override string ToString() + { + return $"class {Name} {{ … }}"; + } + } +} diff --git a/Compiler.Parsing/Tree/CompilationUnitSyntax.cs b/Compiler.Parsing/Tree/CompilationUnitSyntax.cs new file mode 100644 index 00000000..e942c7be --- /dev/null +++ b/Compiler.Parsing/Tree/CompilationUnitSyntax.cs @@ -0,0 +1,42 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class CompilationUnitSyntax : Syntax, ICompilationUnitSyntax + { + public CodeFile File { get; } + public NamespaceName ImplicitNamespaceName { get; } + public FixedList UsingDirectives { get; } + public FixedList Declarations { get; } + //public FixedList AllEntityDeclarations { get; } + public FixedList Diagnostics { get; private set; } + + public CompilationUnitSyntax( + NamespaceName implicitNamespaceName, + TextSpan span, + CodeFile file, + FixedList usingDirectives, + FixedList declarations) + : base(span) + { + File = file; + ImplicitNamespaceName = implicitNamespaceName; + UsingDirectives = usingDirectives; + Declarations = declarations; + Diagnostics = FixedList.Empty; + } + + public void Attach(FixedList diagnostics) + { + Diagnostics = diagnostics; + } + + public override string ToString() + { + return File.Reference.ToString(); + } + } +} diff --git a/Compiler.Parsing/Tree/ConcreteMethodDeclarationSyntax.cs b/Compiler.Parsing/Tree/ConcreteMethodDeclarationSyntax.cs new file mode 100644 index 00000000..dbc131f2 --- /dev/null +++ b/Compiler.Parsing/Tree/ConcreteMethodDeclarationSyntax.cs @@ -0,0 +1,38 @@ +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + // TODO No error is reported if IConcreteMethodDeclarationSyntax is missing + internal class ConcreteMethodDeclarationSyntax : MethodDeclarationSyntax, IConcreteMethodDeclarationSyntax + { + public virtual IBodySyntax Body { get; } + + public ConcreteMethodDeclarationSyntax( + IClassDeclarationSyntax declaringClass, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name name, + ISelfParameterSyntax selfParameter, + FixedList parameters, + ITypeSyntax? returnType, + IBodySyntax body) + : base(declaringClass, span, file, accessModifier, nameSpan, name, selfParameter, + parameters, returnType) + { + Body = body; + } + + public override string ToString() + { + var returnType = ReturnType != null ? " -> " + ReturnType : ""; + return $"fn {Name}({string.Join(", ", Parameters.Prepend(SelfParameter))}){returnType} {Body}"; + } + } +} diff --git a/Compiler.Parsing/Tree/ConstructorDeclarationSyntax.cs b/Compiler.Parsing/Tree/ConstructorDeclarationSyntax.cs new file mode 100644 index 00000000..75fca487 --- /dev/null +++ b/Compiler.Parsing/Tree/ConstructorDeclarationSyntax.cs @@ -0,0 +1,46 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class ConstructorDeclarationSyntax : InvocableDeclarationSyntax, IConstructorDeclarationSyntax + { + public IClassDeclarationSyntax DeclaringClass { get; } + public ISelfParameterSyntax ImplicitSelfParameter { get; } + public new FixedList Parameters { get; } + public virtual IBodySyntax Body { get; } + public new AcyclicPromise Symbol { get; } + + public ConstructorDeclarationSyntax( + IClassDeclarationSyntax declaringType, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name? name, + ISelfParameterSyntax implicitSelfParameter, + FixedList parameters, + IBodySyntax body) + : base(span, file, accessModifier, nameSpan, name, parameters, + new AcyclicPromise()) + { + DeclaringClass = declaringType; + ImplicitSelfParameter = implicitSelfParameter; + Parameters = parameters; + Body = body; + Symbol = (AcyclicPromise)base.Symbol; + } + + public override string ToString() + { + return Name is null + ? $"new({string.Join(", ", Parameters)})" + : $"new {Name}({string.Join(", ", Parameters)})"; + } + } +} diff --git a/Compiler.Parsing/Tree/DeclarationSyntax.cs b/Compiler.Parsing/Tree/DeclarationSyntax.cs new file mode 100644 index 00000000..3c340e75 --- /dev/null +++ b/Compiler.Parsing/Tree/DeclarationSyntax.cs @@ -0,0 +1,48 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class DeclarationSyntax : Syntax, IDeclarationSyntax + { + private LexicalScope? containingLexicalScope; + public LexicalScope ContainingLexicalScope + { + [DebuggerStepThrough] + get => containingLexicalScope + ?? throw new InvalidOperationException($"{nameof(ContainingLexicalScope)} not yet assigned"); + [DebuggerStepThrough] + set + { + if (containingLexicalScope != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingLexicalScope)} repeatedly"); + containingLexicalScope = value; + } + } + + public CodeFile File { get; } + public Name? Name { get; } + public TextSpan NameSpan { get; } + public IPromise Symbol { get; } + + protected DeclarationSyntax( + TextSpan span, + CodeFile file, + Name? name, + TextSpan nameSpan, + IPromise symbol) + : base(span) + { + NameSpan = nameSpan; + Symbol = symbol; + File = file; + Name = name; + } + } +} diff --git a/Compiler.Parsing/Tree/ExpressionStatementSyntax.cs b/Compiler.Parsing/Tree/ExpressionStatementSyntax.cs new file mode 100644 index 00000000..f6edd142 --- /dev/null +++ b/Compiler.Parsing/Tree/ExpressionStatementSyntax.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class ExpressionStatementSyntax : StatementSyntax, IExpressionStatementSyntax + { + private IExpressionSyntax expression; + public ref IExpressionSyntax Expression + { + [DebuggerStepThrough] + get => ref expression; + } + + public ExpressionStatementSyntax(TextSpan span, IExpressionSyntax expression) + : base(span) + { + this.expression = expression; + } + + public override string ToString() + { + return Expression+";"; + } + } +} diff --git a/Compiler.Parsing/Tree/ExpressionSyntax.cs b/Compiler.Parsing/Tree/ExpressionSyntax.cs new file mode 100644 index 00000000..b78e13f2 --- /dev/null +++ b/Compiler.Parsing/Tree/ExpressionSyntax.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class ExpressionSyntax : Syntax, IExpressionSyntax + { + /// + /// If an expression has been poisoned, then it is errored in some way + /// and we won't report errors against it in the future. We may also + /// skip it for some processing. + /// + public bool Poisoned { [DebuggerStepThrough] get; private set; } + + private DataType? dataType; + [DisallowNull] + public DataType? DataType + { + [DebuggerStepThrough] + get => dataType; + set + { + if (dataType != null) + throw new InvalidOperationException("Can't set type repeatedly"); + dataType = value ?? throw new ArgumentNullException(nameof(DataType), + "Can't set type to null"); + } + } + + private ExpressionSemantics? semantics; + + [DisallowNull] + public ExpressionSemantics? Semantics + { + [DebuggerStepThrough] + get => semantics; + set + { + if (semantics != null) + throw new InvalidOperationException("Can't set semantics repeatedly"); + semantics = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + protected ExpressionSyntax(TextSpan span, ExpressionSemantics? semantics = null) + : base(span) + { + this.semantics = semantics; + } + + public void Poison() + { + Poisoned = true; + } + + protected abstract OperatorPrecedence ExpressionPrecedence { get; } + + public string ToGroupedString(OperatorPrecedence surroundingPrecedence) + { + return surroundingPrecedence > ExpressionPrecedence ? $"({this})" : ToString(); + } + } +} diff --git a/Compiler.Parsing/Tree/FieldDeclarationSyntax.cs b/Compiler.Parsing/Tree/FieldDeclarationSyntax.cs new file mode 100644 index 00000000..244c297b --- /dev/null +++ b/Compiler.Parsing/Tree/FieldDeclarationSyntax.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class FieldDeclarationSyntax : MemberDeclarationSyntax, IFieldDeclarationSyntax + { + public bool IsMutableBinding { get; } + public new Name Name { get; } + public new AcyclicPromise Symbol { get; } + IPromise IBindingSyntax.Symbol => Symbol; + public ITypeSyntax Type { get; } + private IExpressionSyntax? initializer; + [DisallowNull] public ref IExpressionSyntax? Initializer => ref initializer; + + public FieldDeclarationSyntax( + IClassDeclarationSyntax declaringClass, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + bool mutableBinding, + TextSpan nameSpan, + Name name, + ITypeSyntax type, + IExpressionSyntax? initializer) + : base(declaringClass, span, file, accessModifier, nameSpan, name, new AcyclicPromise()) + { + IsMutableBinding = mutableBinding; + Name = name; + Type = type; + this.initializer = initializer; + Symbol = (AcyclicPromise)base.Symbol; + } + + public override string ToString() + { + var result = $"{Name}: {Type}"; + if (Initializer != null) + result += Initializer.ToString(); + result += ";"; + return result; + } + } +} diff --git a/Compiler.Parsing/Tree/FieldParameterSyntax.cs b/Compiler.Parsing/Tree/FieldParameterSyntax.cs new file mode 100644 index 00000000..2a248abc --- /dev/null +++ b/Compiler.Parsing/Tree/FieldParameterSyntax.cs @@ -0,0 +1,31 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class FieldParameterSyntax : ParameterSyntax, IFieldParameterSyntax + { + public new Name Name { get; } + public Promise ReferencedSymbol { get; } = new Promise(); + public override IPromise DataType { get; } + public IExpressionSyntax? DefaultValue { get; } + + public FieldParameterSyntax(TextSpan span, Name name, IExpressionSyntax? defaultValue) + : base(span, name) + { + Name = name; + DefaultValue = defaultValue; + DataType = ReferencedSymbol.Select(s => s?.DataType ?? Types.DataType.Unknown); + } + + public override string ToString() + { + var defaultValue = DefaultValue != null ? " = " + DefaultValue : ""; + return $".{Name}{defaultValue}"; + } + } +} diff --git a/Compiler.Parsing/Tree/ForeachExpressionSyntax.cs b/Compiler.Parsing/Tree/ForeachExpressionSyntax.cs new file mode 100644 index 00000000..c743d1ee --- /dev/null +++ b/Compiler.Parsing/Tree/ForeachExpressionSyntax.cs @@ -0,0 +1,49 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class ForeachExpressionSyntax : ExpressionSyntax, IForeachExpressionSyntax + { + public bool IsMutableBinding { get; } + public Name VariableName { get; } + public Promise DeclarationNumber { get; } = new Promise(); + public Promise Symbol { get; } = new Promise(); + IPromise ILocalBindingSyntax.Symbol => Symbol; + IPromise IBindingSyntax.Symbol => Symbol; + + public ITypeSyntax? Type { get; } + private IExpressionSyntax inExpression; + public ref IExpressionSyntax InExpression => ref inExpression; + + public IBlockExpressionSyntax Block { get; } + + public ForeachExpressionSyntax( + TextSpan span, + bool isMutableBinding, + Name variableName, + ITypeSyntax? typeSyntax, + IExpressionSyntax inExpression, + IBlockExpressionSyntax block) + : base(span) + { + IsMutableBinding = isMutableBinding; + VariableName = variableName; + this.inExpression = inExpression; + Block = block; + Type = typeSyntax; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + var binding = IsMutableBinding ? "var " : ""; + return $"foreach {binding}{VariableName}: {Type} in {InExpression} {Block}"; + } + } +} diff --git a/Compiler.Parsing/Tree/FunctionDeclarationSyntax.cs b/Compiler.Parsing/Tree/FunctionDeclarationSyntax.cs new file mode 100644 index 00000000..361f0cd1 --- /dev/null +++ b/Compiler.Parsing/Tree/FunctionDeclarationSyntax.cs @@ -0,0 +1,62 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class FunctionDeclarationSyntax : InvocableDeclarationSyntax, IFunctionDeclarationSyntax + { + public NamespaceName ContainingNamespaceName { get; } + + private NamespaceOrPackageSymbol? containingNamespaceSymbol; + public NamespaceOrPackageSymbol ContainingNamespaceSymbol + { + get => containingNamespaceSymbol + ?? throw new InvalidOperationException($"{ContainingNamespaceSymbol} not yet assigned"); + set + { + if (containingNamespaceSymbol != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingNamespaceSymbol)} repeatedly"); + containingNamespaceSymbol = value; + } + } + public new Name Name { get; } + + public ITypeSyntax? ReturnType { [DebuggerStepThrough] get; } + public new FixedList Parameters { [DebuggerStepThrough] get; } + public IBodySyntax Body { [DebuggerStepThrough] get; } + public new AcyclicPromise Symbol { get; } + + public FunctionDeclarationSyntax( + NamespaceName containingNamespaceName, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name name, + FixedList parameters, + ITypeSyntax? returnType, + IBodySyntax body) + : base(span, file, accessModifier, nameSpan, name, parameters, new AcyclicPromise()) + { + ContainingNamespaceName = containingNamespaceName; + Name = name; + Parameters = parameters; + ReturnType = returnType; + Body = body; + Symbol = (AcyclicPromise)base.Symbol; + } + + public override string ToString() + { + var returnType = ReturnType != null ? " -> " + ReturnType : ""; + return $"fn {Name}({string.Join(", ", Parameters)}){returnType} {Body}"; + } + } +} diff --git a/Compiler.Parsing/Tree/IfExpressionSyntax.cs b/Compiler.Parsing/Tree/IfExpressionSyntax.cs new file mode 100644 index 00000000..c48c3331 --- /dev/null +++ b/Compiler.Parsing/Tree/IfExpressionSyntax.cs @@ -0,0 +1,37 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class IfExpressionSyntax : ExpressionSyntax, IIfExpressionSyntax + { + private IExpressionSyntax condition; + + public ref IExpressionSyntax Condition => ref condition; + + public IBlockOrResultSyntax ThenBlock { get; } + public IElseClauseSyntax? ElseClause { get; } + + public IfExpressionSyntax( + TextSpan span, + IExpressionSyntax condition, + IBlockOrResultSyntax thenBlock, + IElseClauseSyntax? elseClause) + : base(span) + { + this.condition = condition; + ThenBlock = thenBlock; + ElseClause = elseClause; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + if (ElseClause != null) + return $"if {Condition} {ThenBlock} else {ElseClause}"; + return $"if {Condition} {ThenBlock}"; + } + } +} diff --git a/Compiler.Parsing/Tree/IntegerLiteralExpressionSyntax.cs b/Compiler.Parsing/Tree/IntegerLiteralExpressionSyntax.cs new file mode 100644 index 00000000..b67a7acd --- /dev/null +++ b/Compiler.Parsing/Tree/IntegerLiteralExpressionSyntax.cs @@ -0,0 +1,26 @@ +using System.Globalization; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class IntegerLiteralExpressionSyntax : LiteralExpressionSyntax, IIntegerLiteralExpressionSyntax + { + public BigInteger Value { get; } + + public IntegerLiteralExpressionSyntax(TextSpan span, BigInteger value) + : base(span, ExpressionSemantics.Copy) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return Value.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/Compiler.Parsing/Tree/InvocableDeclarationSyntax.cs b/Compiler.Parsing/Tree/InvocableDeclarationSyntax.cs new file mode 100644 index 00000000..214b6adb --- /dev/null +++ b/Compiler.Parsing/Tree/InvocableDeclarationSyntax.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class InvocableDeclarationSyntax : DeclarationSyntax, IInvocableDeclarationSyntax + { + public IAccessModifierToken? AccessModifier { get; } + public FixedList Parameters { get; } + public new IPromise Symbol { get; } + + protected InvocableDeclarationSyntax( + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name? name, + IEnumerable parameters, + IPromise symbol) + : base(span, file, name, nameSpan, symbol) + { + AccessModifier = accessModifier; + Parameters = parameters.ToFixedList(); + Symbol = symbol; + } + } +} diff --git a/Compiler.Parsing/Tree/InvocationExpressionSyntax.cs b/Compiler.Parsing/Tree/InvocationExpressionSyntax.cs new file mode 100644 index 00000000..e2276241 --- /dev/null +++ b/Compiler.Parsing/Tree/InvocationExpressionSyntax.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class InvocationExpressionSyntax : ExpressionSyntax, IInvocationExpressionSyntax + { + public Name InvokedName { get; } + public TextSpan InvokedNameSpan { get; } + public FixedList Arguments { [DebuggerStepThrough] get; } + public IPromise ReferencedSymbol { get; } + + private protected InvocationExpressionSyntax( + TextSpan span, + Name invokedName, + TextSpan invokedNameSpan, + FixedList arguments, + IPromise referencedSymbol) + : base(span) + { + InvokedName = invokedName; + Arguments = arguments; + ReferencedSymbol = referencedSymbol; + InvokedNameSpan = invokedNameSpan; + } + } +} diff --git a/Compiler.Parsing/Tree/LiteralExpressionSyntax.cs b/Compiler.Parsing/Tree/LiteralExpressionSyntax.cs new file mode 100644 index 00000000..8340360b --- /dev/null +++ b/Compiler.Parsing/Tree/LiteralExpressionSyntax.cs @@ -0,0 +1,16 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class LiteralExpressionSyntax : ExpressionSyntax, ILiteralExpressionSyntax + { + protected LiteralExpressionSyntax(TextSpan span, ExpressionSemantics? semantics = null) + : base(span, semantics) + { + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + } +} diff --git a/Compiler.Parsing/Tree/LoopExpressionSyntax.cs b/Compiler.Parsing/Tree/LoopExpressionSyntax.cs new file mode 100644 index 00000000..9004f324 --- /dev/null +++ b/Compiler.Parsing/Tree/LoopExpressionSyntax.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class LoopExpressionSyntax : ExpressionSyntax, ILoopExpressionSyntax + { + public IBlockExpressionSyntax Block { get; } + + public LoopExpressionSyntax(TextSpan span, IBlockExpressionSyntax block) + : base(span) + { + Block = block; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"loop {Block}"; + } + } +} diff --git a/Compiler.Parsing/Tree/MemberDeclarationSyntax.cs b/Compiler.Parsing/Tree/MemberDeclarationSyntax.cs new file mode 100644 index 00000000..9fb904cf --- /dev/null +++ b/Compiler.Parsing/Tree/MemberDeclarationSyntax.cs @@ -0,0 +1,29 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class MemberDeclarationSyntax : DeclarationSyntax, IMemberDeclarationSyntax + { + public IClassDeclarationSyntax DeclaringClass { get; } + public IAccessModifierToken? AccessModifier { get; } + + protected MemberDeclarationSyntax( + IClassDeclarationSyntax declaringClass, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name? name, + IPromise symbol) + : base(span, file, name, nameSpan, symbol) + { + DeclaringClass = declaringClass; + AccessModifier = accessModifier; + } + } +} diff --git a/Compiler.Parsing/Tree/MethodDeclarationSyntax.cs b/Compiler.Parsing/Tree/MethodDeclarationSyntax.cs new file mode 100644 index 00000000..ddce37c9 --- /dev/null +++ b/Compiler.Parsing/Tree/MethodDeclarationSyntax.cs @@ -0,0 +1,41 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class MethodDeclarationSyntax : InvocableDeclarationSyntax, IMethodDeclarationSyntax + { + public IClassDeclarationSyntax DeclaringClass { get; } + public new Name Name { get; } + public ISelfParameterSyntax SelfParameter { get; } + public new FixedList Parameters { get; } + public ITypeSyntax? ReturnType { get; } + public new AcyclicPromise Symbol { get; } + + protected MethodDeclarationSyntax( + IClassDeclarationSyntax declaringClass, + TextSpan span, + CodeFile file, + IAccessModifierToken? accessModifier, + TextSpan nameSpan, + Name name, + ISelfParameterSyntax selfParameter, + FixedList parameters, + ITypeSyntax? returnType) + : base(span, file, accessModifier, nameSpan, name, + parameters, new AcyclicPromise()) + { + DeclaringClass = declaringClass; + Name = name; + SelfParameter = selfParameter; + Parameters = parameters; + ReturnType = returnType; + Symbol = (AcyclicPromise)base.Symbol; + } + } +} diff --git a/Compiler.Parsing/Tree/MoveExpressionSyntax.cs b/Compiler.Parsing/Tree/MoveExpressionSyntax.cs new file mode 100644 index 00000000..7ebf6b72 --- /dev/null +++ b/Compiler.Parsing/Tree/MoveExpressionSyntax.cs @@ -0,0 +1,38 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + [SuppressMessage("Performance", "CA1812:Class Never Instantiated")] + internal class MoveExpressionSyntax : ExpressionSyntax, IMoveExpressionSyntax + { + [SuppressMessage("Style", "IDE0044:Add readonly modifier", + Justification = "Can't be readonly because a reference to it is exposed")] + private IExpressionSyntax referent; + public ref IExpressionSyntax Referent + { + [DebuggerStepThrough] + get => ref referent; + } + + public Promise ReferencedSymbol { get; } = new Promise(); + + public MoveExpressionSyntax(TextSpan span, INameExpressionSyntax referent) + : base(span) // TODO this could be a move or acquire? + { + this.referent = referent; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return $"move {Referent}"; + } + } +} diff --git a/Compiler.Parsing/Tree/MutateExpressionSyntax.cs b/Compiler.Parsing/Tree/MutateExpressionSyntax.cs new file mode 100644 index 00000000..6f09fe92 --- /dev/null +++ b/Compiler.Parsing/Tree/MutateExpressionSyntax.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class MutateExpressionSyntax : ExpressionSyntax, IMutateExpressionSyntax + { + [SuppressMessage("Style", "IDE0044:Add readonly modifier", + Justification = "Can't be readonly because a reference to it is exposed")] + private IExpressionSyntax referent; + public ref IExpressionSyntax Referent => ref referent; + public Promise ReferencedSymbol { get; } = new Promise(); + + public MutateExpressionSyntax(TextSpan span, IExpressionSyntax referent) + : base(span, ExpressionSemantics.Borrow) + { + this.referent = referent; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return $"mut {Referent}"; + } + } +} diff --git a/Compiler.Parsing/Tree/NameExpressionSyntax.cs b/Compiler.Parsing/Tree/NameExpressionSyntax.cs new file mode 100644 index 00000000..f3e29cab --- /dev/null +++ b/Compiler.Parsing/Tree/NameExpressionSyntax.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + /// + /// A name of a variable or namespace + /// + internal class NameExpressionSyntax : ExpressionSyntax, INameExpressionSyntax + { + private LexicalScope? containingLexicalScope; + public LexicalScope ContainingLexicalScope + { + [DebuggerStepThrough] + get => + containingLexicalScope + ?? throw new InvalidOperationException($"{nameof(ContainingLexicalScope)} not yet assigned"); + [DebuggerStepThrough] + set + { + if (containingLexicalScope != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingLexicalScope)} repeatedly"); + containingLexicalScope = value; + } + } + // A null name means this syntax was generated as an assumed missing name and the name is unknown + public Name? Name { get; } + public Promise ReferencedSymbol { get; } = new Promise(); + + public NameExpressionSyntax(TextSpan span, Name? name) + : base(span) + { + Name = name; + } + + public IEnumerable> LookupInContainingScope() + { + if (containingLexicalScope == null) + throw new InvalidOperationException($"Can't lookup type name without {nameof(ContainingLexicalScope)}"); + + // If name is unknown, no symbols + if (Name is null) return Enumerable.Empty>(); + + return containingLexicalScope.Lookup(Name).Select(p => p.As()).WhereNotNull(); + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + public override string ToString() + { + return Name?.ToString() ?? "⧼unknown⧽"; + } + } +} diff --git a/Compiler.Parsing/Tree/NameSyntax.cs b/Compiler.Parsing/Tree/NameSyntax.cs new file mode 100644 index 00000000..83658895 --- /dev/null +++ b/Compiler.Parsing/Tree/NameSyntax.cs @@ -0,0 +1,26 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + /// + /// Used within the parse to represent a name that we aren't yet sure whether + /// it is a name expression, or a callable name + /// + internal readonly struct NameSyntax + { + public TextSpan Span { get; } + public Name Name { get; } + + public NameSyntax(TextSpan span, Name name) + { + Span = span; + Name = name; + } + + public NameExpressionSyntax ToExpression() + { + return new NameExpressionSyntax(Span, Name); + } + } +} diff --git a/Compiler.Parsing/Tree/NamedParameterSyntax.cs b/Compiler.Parsing/Tree/NamedParameterSyntax.cs new file mode 100644 index 00000000..8311270e --- /dev/null +++ b/Compiler.Parsing/Tree/NamedParameterSyntax.cs @@ -0,0 +1,43 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class NamedParameterSyntax : ParameterSyntax, INamedParameterSyntax + { + public bool IsMutableBinding { get; } + public new Name Name { get; } + public Promise DeclarationNumber { get; } = new Promise(); + public Promise Symbol { get; } = new Promise(); + IPromise IBindingSyntax.Symbol => Symbol; + IPromise ILocalBindingSyntax.Symbol => Symbol; + public ITypeSyntax Type { get; } + public override IPromise DataType { get; } + public IExpressionSyntax? DefaultValue { get; } + + public NamedParameterSyntax( + TextSpan span, + bool isMutableBinding, + Name name, + ITypeSyntax typeSyntax, + IExpressionSyntax? defaultValue) + : base(span, name) + { + IsMutableBinding = isMutableBinding; + Name = name; + Type = typeSyntax; + DefaultValue = defaultValue; + DataType = Symbol.Select(s => s.DataType); + } + + public override string ToString() + { + var defaultValue = DefaultValue != null ? " = " + DefaultValue : ""; + return $"{Name}: {Type}{defaultValue}"; + } + } +} diff --git a/Compiler.Parsing/Tree/NamespaceDeclarationSyntax.cs b/Compiler.Parsing/Tree/NamespaceDeclarationSyntax.cs new file mode 100644 index 00000000..01abb303 --- /dev/null +++ b/Compiler.Parsing/Tree/NamespaceDeclarationSyntax.cs @@ -0,0 +1,68 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class NamespaceDeclarationSyntax : DeclarationSyntax, INamespaceDeclarationSyntax + { + public NamespaceName ContainingNamespaceName { get; } + + private NamespaceOrPackageSymbol? containingNamespaceSymbol; + public NamespaceOrPackageSymbol ContainingNamespaceSymbol + { + get => + containingNamespaceSymbol + ?? throw new InvalidOperationException($"{ContainingNamespaceSymbol} not yet assigned"); + set + { + if (containingNamespaceSymbol != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingNamespaceSymbol)} repeatedly"); + containingNamespaceSymbol = value; + } + } + + /// + /// Whether this namespace declaration is in the global namespace, the + /// implicit file namespace is in the global namespace. As are namespaces + /// declared using the package qualifier `namespace ::example { }`. + /// + public bool IsGlobalQualified { get; } + public NamespaceName DeclaredNames { get; } + public new Name Name { get; } + public NamespaceName FullName { get; } + public new Promise Symbol { get; } + public FixedList UsingDirectives { get; } + public FixedList Declarations { get; } + + public NamespaceDeclarationSyntax( + NamespaceName containingNamespaceName, + TextSpan span, + CodeFile file, + bool isGlobalQualified, + NamespaceName declaredNames, + TextSpan nameSpan, + FixedList usingDirectives, + FixedList declarations) + : base(span, file, declaredNames.Segments[^1], nameSpan, new Promise()) + { + ContainingNamespaceName = containingNamespaceName; + DeclaredNames = declaredNames; + FullName = containingNamespaceName.Qualify(declaredNames); + Name = declaredNames.Segments[^1]; + UsingDirectives = usingDirectives; + Declarations = declarations; + IsGlobalQualified = isGlobalQualified; + Symbol = (Promise)base.Symbol; + } + + public override string ToString() + { + return $"namespace ::{FullName} {{ … }}"; + } + } +} diff --git a/Compiler.Parsing/Tree/NewObjectExpressionSyntax.cs b/Compiler.Parsing/Tree/NewObjectExpressionSyntax.cs new file mode 100644 index 00000000..7436fd38 --- /dev/null +++ b/Compiler.Parsing/Tree/NewObjectExpressionSyntax.cs @@ -0,0 +1,47 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class NewObjectExpressionSyntax : ExpressionSyntax, INewObjectExpressionSyntax + { + /// + /// Note that this could represent a named or unnamed constructor. So + /// for an unnamed constructor, it is really the type name. Conceptually + /// though, the type name is the name of the unnamed constructor. Thus, + /// this expression's type could be either an object type, or member type. + /// + public ITypeNameSyntax Type { get; } + public Name? ConstructorName { get; } + public TextSpan? ConstructorNameSpan { get; } + public FixedList Arguments { get; } + public Promise ReferencedSymbol { get; } = new Promise(); + + public NewObjectExpressionSyntax( + TextSpan span, + ITypeNameSyntax typeSyntax, + Name? constructorName, + TextSpan? constructorNameSpan, + FixedList arguments) + : base(span, ExpressionSemantics.Acquire) + { + Type = typeSyntax; + Arguments = arguments; + ConstructorName = constructorName; + ConstructorNameSpan = constructorNameSpan; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + var name = ConstructorName != null ? "."+ConstructorName : ""; + return $"new {Type}{name}({string.Join(", ", Arguments)})"; + } + } +} diff --git a/Compiler.Parsing/Tree/NextExpressionSyntax.cs b/Compiler.Parsing/Tree/NextExpressionSyntax.cs new file mode 100644 index 00000000..20c70e59 --- /dev/null +++ b/Compiler.Parsing/Tree/NextExpressionSyntax.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class NextExpressionSyntax : ExpressionSyntax, INextExpressionSyntax + { + public NextExpressionSyntax(TextSpan span) + : base(span, ExpressionSemantics.Never) + { + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return "next"; + } + } +} diff --git a/Compiler.Parsing/Tree/NoneLiteralExpressionSyntax.cs b/Compiler.Parsing/Tree/NoneLiteralExpressionSyntax.cs new file mode 100644 index 00000000..8688450b --- /dev/null +++ b/Compiler.Parsing/Tree/NoneLiteralExpressionSyntax.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class NoneLiteralExpressionSyntax : LiteralExpressionSyntax, INoneLiteralExpressionSyntax + { + public NoneLiteralExpressionSyntax(TextSpan span) + : base(span, ExpressionSemantics.Copy) + { + } + + public override string ToString() + { + return "none"; + } + } +} diff --git a/Compiler.Parsing/Tree/OptionalTypeSyntax.cs b/Compiler.Parsing/Tree/OptionalTypeSyntax.cs new file mode 100644 index 00000000..fb9235f6 --- /dev/null +++ b/Compiler.Parsing/Tree/OptionalTypeSyntax.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + class OptionalTypeSyntax : TypeSyntax, IOptionalTypeSyntax + { + public ITypeSyntax Referent { get; } + + public OptionalTypeSyntax(TextSpan span, ITypeSyntax referent) + : base(span) + { + Referent = referent; + } + + public override string ToString() + { + return $"{Referent}?"; + } + } +} diff --git a/Compiler.Parsing/Tree/ParameterSyntax.cs b/Compiler.Parsing/Tree/ParameterSyntax.cs new file mode 100644 index 00000000..b746efe1 --- /dev/null +++ b/Compiler.Parsing/Tree/ParameterSyntax.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class ParameterSyntax : Syntax, IParameterSyntax + { + [DebuggerHidden] + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public Name? Name { get; } + public abstract IPromise DataType { get; } + public bool Unused { get; } + + protected ParameterSyntax(TextSpan span, Name? name) + : base(span) + { + Name = name; + Unused = name?.Text.StartsWith('_') ?? false; + } + } +} diff --git a/Compiler.Parsing/Tree/QualifiedInvocationExpressionSyntax.cs b/Compiler.Parsing/Tree/QualifiedInvocationExpressionSyntax.cs new file mode 100644 index 00000000..84d97e0d --- /dev/null +++ b/Compiler.Parsing/Tree/QualifiedInvocationExpressionSyntax.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class QualifiedInvocationExpressionSyntax : InvocationExpressionSyntax, IQualifiedInvocationExpressionSyntax + { + private LexicalScope? containingLexicalScope; + public LexicalScope ContainingLexicalScope + { + [DebuggerStepThrough] + get => + containingLexicalScope + ?? throw new InvalidOperationException($"{nameof(ContainingLexicalScope)} not yet assigned"); + [DebuggerStepThrough] + set + { + if (containingLexicalScope != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingLexicalScope)} repeatedly"); + containingLexicalScope = value; + } + } + + private IExpressionSyntax context; + public ref IExpressionSyntax Context => ref context; + public new Promise ReferencedSymbol { get; } + + public QualifiedInvocationExpressionSyntax( + TextSpan span, + IExpressionSyntax context, + Name invokedName, + TextSpan invokedNameSpan, + FixedList arguments) + : base(span, invokedName, invokedNameSpan, arguments, new Promise()) + { + this.context = context; + ReferencedSymbol = (Promise)base.ReferencedSymbol; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"{Context.ToGroupedString(ExpressionPrecedence)}.{InvokedName}({string.Join(", ", Arguments)})"; + } + } +} diff --git a/Compiler.Parsing/Tree/QualifiedNameExpressionSyntax.cs b/Compiler.Parsing/Tree/QualifiedNameExpressionSyntax.cs new file mode 100644 index 00000000..ffde84ea --- /dev/null +++ b/Compiler.Parsing/Tree/QualifiedNameExpressionSyntax.cs @@ -0,0 +1,38 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class QualifiedNameExpressionSyntax : ExpressionSyntax, IQualifiedNameExpressionSyntax + { + private IExpressionSyntax context; + public ref IExpressionSyntax Context => ref context; + + public AccessOperator AccessOperator { get; } + public INameExpressionSyntax Field { get; } + public IPromise ReferencedSymbol => Field.ReferencedSymbol.Select(s => (FieldSymbol?)s); + + public QualifiedNameExpressionSyntax( + TextSpan span, + IExpressionSyntax context, + AccessOperator accessOperator, + INameExpressionSyntax field) + : base(span) + { + this.context = context; + AccessOperator = accessOperator; + Field = field; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"{Context.ToGroupedString(ExpressionPrecedence)}{AccessOperator.ToSymbolString()}{Field}"; + } + } +} diff --git a/Compiler.Parsing/Tree/ResultStatementSyntax.cs b/Compiler.Parsing/Tree/ResultStatementSyntax.cs new file mode 100644 index 00000000..9ac6c0ac --- /dev/null +++ b/Compiler.Parsing/Tree/ResultStatementSyntax.cs @@ -0,0 +1,27 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + /// + /// A result statement must be the last statement of the enclosing block + /// + internal class ResultStatementSyntax : StatementSyntax, IResultStatementSyntax + { + private IExpressionSyntax expression; + public ref IExpressionSyntax Expression => ref expression; + + public ResultStatementSyntax( + TextSpan span, + IExpressionSyntax expression) + : base(span) + { + this.expression = expression; + } + + public override string ToString() + { + return $"=> {Expression};"; + } + } +} diff --git a/Compiler.Parsing/Tree/ReturnExpressionSyntax.cs b/Compiler.Parsing/Tree/ReturnExpressionSyntax.cs new file mode 100644 index 00000000..dc97890a --- /dev/null +++ b/Compiler.Parsing/Tree/ReturnExpressionSyntax.cs @@ -0,0 +1,34 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class ReturnExpressionSyntax : ExpressionSyntax, IReturnExpressionSyntax + { + private IExpressionSyntax? value; + [DisallowNull] + public ref IExpressionSyntax? Value + { + [DebuggerStepThrough] + get => ref value; + } + + public ReturnExpressionSyntax( + TextSpan span, + IExpressionSyntax? value) + : base(span, ExpressionSemantics.Never) + { + this.value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return Value is null ? "return" : $"return {Value}"; + } + } +} diff --git a/Compiler.Parsing/Tree/SelfExpressionSyntax.cs b/Compiler.Parsing/Tree/SelfExpressionSyntax.cs new file mode 100644 index 00000000..c12f421c --- /dev/null +++ b/Compiler.Parsing/Tree/SelfExpressionSyntax.cs @@ -0,0 +1,27 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class SelfExpressionSyntax : ExpressionSyntax, ISelfExpressionSyntax + { + public bool IsImplicit { get; } + public Promise ReferencedSymbol { get; } = new Promise(); + + public SelfExpressionSyntax(TextSpan span, bool isImplicit) + : base(span) + { + IsImplicit = isImplicit; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return IsImplicit ? "⟦self⟧" : "self"; + } + } +} diff --git a/Compiler.Parsing/Tree/SelfParameterSyntax.cs b/Compiler.Parsing/Tree/SelfParameterSyntax.cs new file mode 100644 index 00000000..d6f98b0b --- /dev/null +++ b/Compiler.Parsing/Tree/SelfParameterSyntax.cs @@ -0,0 +1,31 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class SelfParameterSyntax : ParameterSyntax, ISelfParameterSyntax + { + public bool IsMutableBinding => false; + public bool MutableSelf { get; } + public Promise Symbol { get; } = new Promise(); + IPromise IBindingSyntax.Symbol => Symbol; + public override IPromise DataType { get; } + public SelfParameterSyntax(TextSpan span, bool mutableSelf) + : base(span, null) + { + MutableSelf = mutableSelf; + DataType = Symbol.Select(s => s.DataType); + } + + public override string ToString() + { + var value = "self"; + if (MutableSelf) + value = "mut " + value; + return value; + } + } +} diff --git a/Compiler.Parsing/Tree/StatementSyntax.cs b/Compiler.Parsing/Tree/StatementSyntax.cs new file mode 100644 index 00000000..ca0011cc --- /dev/null +++ b/Compiler.Parsing/Tree/StatementSyntax.cs @@ -0,0 +1,13 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class StatementSyntax : Syntax, IStatementSyntax + { + private protected StatementSyntax(TextSpan span) + : base(span) + { + } + } +} diff --git a/Compiler.Parsing/Tree/StringLiteralExpressionSyntax.cs b/Compiler.Parsing/Tree/StringLiteralExpressionSyntax.cs new file mode 100644 index 00000000..ba600754 --- /dev/null +++ b/Compiler.Parsing/Tree/StringLiteralExpressionSyntax.cs @@ -0,0 +1,26 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class StringLiteralExpressionSyntax : LiteralExpressionSyntax, IStringLiteralExpressionSyntax + { + public string Value { [DebuggerStepThrough] get; } + + public StringLiteralExpressionSyntax(TextSpan span, string value) + : base(span, ExpressionSemantics.Share) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"\"{Value.Escape()}\""; + } + } +} diff --git a/Compiler.Parsing/Tree/Syntax.cs b/Compiler.Parsing/Tree/Syntax.cs new file mode 100644 index 00000000..56ab26a4 --- /dev/null +++ b/Compiler.Parsing/Tree/Syntax.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + [DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] + internal abstract class Syntax : ISyntax + { + public TextSpan Span { get; protected set; } + + protected Syntax(TextSpan span) + { + Span = span; + } + + // This exists primarily for debugging use + public abstract override string ToString(); + } +} diff --git a/Compiler.Parsing/Tree/TypeNameSyntax.cs b/Compiler.Parsing/Tree/TypeNameSyntax.cs new file mode 100644 index 00000000..87389ae0 --- /dev/null +++ b/Compiler.Parsing/Tree/TypeNameSyntax.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + /// + /// The unqualified name of a type + /// + internal class TypeNameSyntax : TypeSyntax, ITypeNameSyntax + { + private LexicalScope? containingLexicalScope; + public LexicalScope ContainingLexicalScope + { + [DebuggerStepThrough] + get => containingLexicalScope + ?? throw new InvalidOperationException($"{nameof(ContainingLexicalScope)} not yet assigned"); + [DebuggerStepThrough] + set + { + if (containingLexicalScope != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingLexicalScope)} repeatedly"); + containingLexicalScope = value; + } + } + public TypeName Name { get; } + public Promise ReferencedSymbol { get; } = new Promise(); + + public TypeNameSyntax(TextSpan span, TypeName name) + : base(span) + { + Name = name; + } + + public IEnumerable> LookupInContainingScope() + { + if (containingLexicalScope != null) + return containingLexicalScope.Lookup(Name).Select(p => p.As()).WhereNotNull(); + + throw new InvalidOperationException($"Can't lookup type name without {nameof(ContainingLexicalScope)}"); + } + + public override string ToString() + { + return Name.ToString(); + } + } +} diff --git a/Compiler.Parsing/Tree/TypeSyntax.cs b/Compiler.Parsing/Tree/TypeSyntax.cs new file mode 100644 index 00000000..1e1324f9 --- /dev/null +++ b/Compiler.Parsing/Tree/TypeSyntax.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal abstract class TypeSyntax : Syntax, ITypeSyntax + { + private DataType? namedType; + + [DisallowNull] + public DataType? NamedType + { + get => namedType; + set + { + if (namedType != null) + throw new InvalidOperationException("Can't set type repeatedly"); + namedType = value ?? throw new ArgumentNullException(nameof(NamedType), + "Can't set type to null"); + } + } + + /// + /// If an type has been poisoned, then it is errored in some way + /// and we won't report errors against it in the future. We may also + /// skip it for some processing. + /// + public bool Poisoned { get; private set; } + + protected TypeSyntax(TextSpan span) + : base(span) + { + } + + public void Poison() + { + Poisoned = true; + } + + // Useful for debugging + public abstract override string ToString(); + } +} diff --git a/Compiler.Parsing/Tree/UnaryOperatorExpressionSyntax.cs b/Compiler.Parsing/Tree/UnaryOperatorExpressionSyntax.cs new file mode 100644 index 00000000..6933af03 --- /dev/null +++ b/Compiler.Parsing/Tree/UnaryOperatorExpressionSyntax.cs @@ -0,0 +1,40 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class UnaryOperatorExpressionSyntax : ExpressionSyntax, IUnaryOperatorExpressionSyntax + { + public UnaryOperatorFixity Fixity { get; } + public UnaryOperator Operator { get; } + private IExpressionSyntax operand; + public ref IExpressionSyntax Operand => ref operand; + + public UnaryOperatorExpressionSyntax( + TextSpan span, + UnaryOperatorFixity fixity, + UnaryOperator @operator, + IExpressionSyntax operand) + : base(span, ExpressionSemantics.Copy) + { + Operator = @operator; + this.operand = operand; + Fixity = fixity; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Unary; + + public override string ToString() + { + return Fixity switch + { + UnaryOperatorFixity.Prefix => $"{Operator.ToSymbolString()}{Operand.ToGroupedString(ExpressionPrecedence)}", + UnaryOperatorFixity.Postfix => $"{Operand.ToGroupedString(ExpressionPrecedence)}{Operator.ToSymbolString()}", + _ => throw ExhaustiveMatch.Failed(Fixity) + }; + } + } +} diff --git a/Compiler.Parsing/Tree/UnqualifiedInvocationExpressionSyntax.cs b/Compiler.Parsing/Tree/UnqualifiedInvocationExpressionSyntax.cs new file mode 100644 index 00000000..c73f49d3 --- /dev/null +++ b/Compiler.Parsing/Tree/UnqualifiedInvocationExpressionSyntax.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class UnqualifiedInvocationExpressionSyntax : InvocationExpressionSyntax, IUnqualifiedInvocationExpressionSyntax + { + private LexicalScope? containingLexicalScope; + public LexicalScope ContainingLexicalScope + { + [DebuggerStepThrough] + get => + containingLexicalScope + ?? throw new InvalidOperationException($"{nameof(ContainingLexicalScope)} not yet assigned"); + [DebuggerStepThrough] + set + { + if (containingLexicalScope != null) + throw new InvalidOperationException($"Can't set {nameof(ContainingLexicalScope)} repeatedly"); + containingLexicalScope = value; + } + } + + public NamespaceName Namespace { get; } + public new Promise ReferencedSymbol { get; } + + public UnqualifiedInvocationExpressionSyntax( + TextSpan span, + Name invokedName, + TextSpan invokedNameSpan, + FixedList arguments) + : base(span, invokedName, invokedNameSpan, arguments, new Promise()) + { + Namespace = NamespaceName.Global; + ReferencedSymbol = (Promise)base.ReferencedSymbol; + } + + public IEnumerable> LookupInContainingScope() + { + if (containingLexicalScope == null) + throw new InvalidOperationException($"Can't lookup function name without {nameof(ContainingLexicalScope)}"); + + // If name is unknown, no symbols + if (InvokedName is null) return Enumerable.Empty>(); + + return containingLexicalScope.Lookup(InvokedName).Select(p => p.As()).WhereNotNull(); + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + public override string ToString() + { + return $"{InvokedName}({string.Join(", ", Arguments)})"; + } + } +} diff --git a/Compiler.Parsing/Tree/UnsafeExpressionSyntax.cs b/Compiler.Parsing/Tree/UnsafeExpressionSyntax.cs new file mode 100644 index 00000000..6d6f5ade --- /dev/null +++ b/Compiler.Parsing/Tree/UnsafeExpressionSyntax.cs @@ -0,0 +1,26 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class UnsafeExpressionSyntax : ExpressionSyntax, IUnsafeExpressionSyntax + { + private IExpressionSyntax expression; + + public ref IExpressionSyntax Expression => ref expression; + + public UnsafeExpressionSyntax(TextSpan span, IExpressionSyntax expression) + : base(span) + { + this.expression = expression; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"unsafe ({Expression})"; + } + } +} diff --git a/Compiler.Parsing/Tree/UsingDirectiveSyntax.cs b/Compiler.Parsing/Tree/UsingDirectiveSyntax.cs new file mode 100644 index 00000000..a3f0a7f3 --- /dev/null +++ b/Compiler.Parsing/Tree/UsingDirectiveSyntax.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class UsingDirectiveSyntax : Syntax, IUsingDirectiveSyntax + { + // For now, we only support namespace names + public NamespaceName Name { get; } + + public UsingDirectiveSyntax(TextSpan span, NamespaceName name) + : base(span) + { + Name = name; + } + + public override string ToString() + { + return $"using {Name};"; + } + } +} diff --git a/Compiler.Parsing/Tree/VariableDeclarationStatementSyntax.cs b/Compiler.Parsing/Tree/VariableDeclarationStatementSyntax.cs new file mode 100644 index 00000000..11ea6b80 --- /dev/null +++ b/Compiler.Parsing/Tree/VariableDeclarationStatementSyntax.cs @@ -0,0 +1,58 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class VariableDeclarationStatementSyntax : StatementSyntax, IVariableDeclarationStatementSyntax + { + public bool IsMutableBinding { [DebuggerStepThrough] get; } + public Name Name { get; } + public Promise DeclarationNumber { get; } = new Promise(); + public Promise Symbol { get; } = new Promise(); + IPromise IBindingSyntax.Symbol => Symbol; + IPromise ILocalBindingSyntax.Symbol => Symbol; + public TextSpan NameSpan { [DebuggerStepThrough] get; } + public ITypeSyntax? Type { [DebuggerStepThrough] get; } + public bool InferMutableType { [DebuggerStepThrough] get; } + [SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = + "Can't be readonly because a reference to it is exposed")] + private IExpressionSyntax? initializer; + [DisallowNull] + public ref IExpressionSyntax? Initializer + { + [DebuggerStepThrough] + get => ref initializer; + } + + public VariableDeclarationStatementSyntax( + TextSpan span, + bool isMutableBinding, + Name name, + TextSpan nameSpan, + ITypeSyntax? typeSyntax, + bool inferMutableType, + IExpressionSyntax? initializer) + : base(span) + { + IsMutableBinding = isMutableBinding; + Name = name; + NameSpan = nameSpan; + Type = typeSyntax; + InferMutableType = inferMutableType; + this.initializer = initializer; + } + + public override string ToString() + { + var binding = IsMutableBinding ? "var" : "let"; + var type = Type != null ? ": " + Type : ""; + var initializer = Initializer != null ? " = " + Initializer : ""; + return $"{binding} {Name}{type}{initializer};"; + } + } +} diff --git a/Compiler.Parsing/Tree/WhileExpressionSyntax.cs b/Compiler.Parsing/Tree/WhileExpressionSyntax.cs new file mode 100644 index 00000000..2d13697a --- /dev/null +++ b/Compiler.Parsing/Tree/WhileExpressionSyntax.cs @@ -0,0 +1,31 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; + +namespace Azoth.Tools.Bootstrap.Compiler.Parsing.Tree +{ + internal class WhileExpressionSyntax : ExpressionSyntax, IWhileExpressionSyntax + { + private IExpressionSyntax condition; + public ref IExpressionSyntax Condition => ref condition; + + public IBlockExpressionSyntax Block { get; } + + public WhileExpressionSyntax( + TextSpan span, + IExpressionSyntax condition, + IBlockExpressionSyntax block) + : base(span) + { + this.condition = condition; + Block = block; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return $"while {Condition} {Block}"; + } + } +} diff --git a/Compiler.Primitives/Compiler.Primitives.csproj b/Compiler.Primitives/Compiler.Primitives.csproj new file mode 100644 index 00000000..e075bb46 --- /dev/null +++ b/Compiler.Primitives/Compiler.Primitives.csproj @@ -0,0 +1,30 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Primitives + Azoth.Tools.Bootstrap.Compiler.Primitives + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Compiler.Primitives/Intrinsic.cs b/Compiler.Primitives/Intrinsic.cs new file mode 100644 index 00000000..47ac9b62 --- /dev/null +++ b/Compiler.Primitives/Intrinsic.cs @@ -0,0 +1,55 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Primitives +{ + public static class Intrinsic + { + public static readonly FixedSymbolTree SymbolTree = DefineIntrinsicSymbols(); + + private static FixedSymbolTree DefineIntrinsicSymbols() + { + var intrinsicsPackage = new PackageSymbol("intrinsics"); + var tree = new SymbolTreeBuilder(intrinsicsPackage); + + var intrinsicsNamespace = new NamespaceSymbol(intrinsicsPackage, "intrinsics"); + + // params: length + var memAllocate = new FunctionSymbol(intrinsicsNamespace, "mem_allocate", Params(DataType.Size), DataType.Size); + tree.Add(memAllocate); + + // params: ptr + var memDeallocate = new FunctionSymbol(intrinsicsNamespace, "mem_deallocate", Params(DataType.Size)); + tree.Add(memDeallocate); + + // params: from_ptr, to_ptr, length + var memCopy = new FunctionSymbol(intrinsicsNamespace, "mem_copy", Params(DataType.Size, DataType.Size, DataType.Size)); + tree.Add(memCopy); + + // params: from_ptr, value + var memSetByte = new FunctionSymbol(intrinsicsNamespace, "mem_set_byte", Params(DataType.Size, DataType.Byte)); + tree.Add(memSetByte); + + // params: ptr + var memGetByte = new FunctionSymbol(intrinsicsNamespace, "mem_get_byte", Params(DataType.Size), DataType.Byte); + tree.Add(memGetByte); + + // params: ptr, length + var printUtf8 = new FunctionSymbol(intrinsicsNamespace, "print_utf8", Params(DataType.Size, DataType.Size)); + tree.Add(printUtf8); + + // params: ptr, length + var readUtf8Line = new FunctionSymbol(intrinsicsNamespace, "read_utf8_line", Params(DataType.Size, DataType.Size), DataType.Size); + tree.Add(readUtf8Line); + + return tree.Build(); + } + + private static FixedList Params(params DataType[] types) + { + return types.ToFixedList(); + } + } +} diff --git a/Compiler.Primitives/Primitive.cs b/Compiler.Primitives/Primitive.cs new file mode 100644 index 00000000..4ca875b2 --- /dev/null +++ b/Compiler.Primitives/Primitive.cs @@ -0,0 +1,67 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Primitives +{ + public static class Primitive + { + public static readonly PrimitiveSymbolTree SymbolTree = DefinePrimitiveSymbols(); + + private static PrimitiveSymbolTree DefinePrimitiveSymbols() + { + var tree = new SymbolTreeBuilder(); + + var stringType = new ObjectType(NamespaceName.Global, "String", false, ReferenceCapability.Shared); + + // Simple Types + BuildBoolSymbol(tree); + + BuildIntegerTypeSymbol(tree, DataType.Byte, stringType); + BuildIntegerTypeSymbol(tree, DataType.Int, stringType); + BuildIntegerTypeSymbol(tree, DataType.UInt, stringType); + + BuildIntegerTypeSymbol(tree, DataType.Size, stringType); + BuildIntegerTypeSymbol(tree, DataType.Offset, stringType); + + BuildEmptyType(tree, DataType.Void); + BuildEmptyType(tree, DataType.Never); + + return tree.BuildPrimitives(); + } + + private static void BuildBoolSymbol(SymbolTreeBuilder tree) + { + var symbol = new PrimitiveTypeSymbol(DataType.Bool); + tree.Add(symbol); + } + + private static void BuildIntegerTypeSymbol( + SymbolTreeBuilder tree, + IntegerType integerType, + DataType stringType) + { + var type = new PrimitiveTypeSymbol(integerType); + tree.Add(type); + + var remainderMethod = new MethodSymbol(type, "remainder", integerType, Params(integerType), integerType); + var displayStringMethod = new MethodSymbol(type, "to_display_string", integerType, Params(), stringType); + + tree.Add(remainderMethod); + tree.Add(displayStringMethod); + } + + private static void BuildEmptyType(SymbolTreeBuilder tree, EmptyType emptyType) + { + var symbol = new PrimitiveTypeSymbol(emptyType); + tree.Add(symbol); + } + + private static FixedList Params(params DataType[] types) + { + return types.ToFixedList(); + } + } +} diff --git a/Compiler.Semantics/AST/ASTBuilder.cs b/Compiler.Semantics/AST/ASTBuilder.cs new file mode 100644 index 00000000..8fabe2cd --- /dev/null +++ b/Compiler.Semantics/AST/ASTBuilder.cs @@ -0,0 +1,535 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST +{ + // ReSharper disable once UnusedMember.Global + [SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes", + Justification = "In Progress")] + internal class ASTBuilder + { + // ReSharper disable once UnusedMember.Global + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public Package BuildPackage(PackageSyntax packageSyntax) + { + var nonMemberDeclarations = packageSyntax.AllEntityDeclarations + .OfType() + .Select(BuildNonMemberDeclaration).ToFixedList(); + + var symbolTree = packageSyntax.SymbolTree.Build(); + return new Package(nonMemberDeclarations, symbolTree, packageSyntax.Diagnostics, packageSyntax.References); + } + + private static INonMemberDeclaration BuildNonMemberDeclaration(INonMemberEntityDeclarationSyntax entity) + { + return entity switch + { + IClassDeclarationSyntax syn => BuildClass(syn), + IFunctionDeclarationSyntax syn => BuildFunction(syn), + _ => throw ExhaustiveMatch.Failed(entity) + }; + } + + private static IClassDeclaration BuildClass(IClassDeclarationSyntax syn) + { + var symbol = syn.Symbol.Result; + var nameSpan = syn.NameSpan; + var defaultConstructorSymbol = syn.DefaultConstructorSymbol; + FixedList BuildMembers(IClassDeclaration c) + => syn.Members.Select(m => BuildMember(c, m)).ToFixedList(); + + return new ClassDeclaration(syn.File, syn.Span, symbol, nameSpan, defaultConstructorSymbol, BuildMembers); + } + + private static IMemberDeclaration BuildMember( + IClassDeclaration declaringClass, + IMemberDeclarationSyntax member) + { + return member switch + { + IAssociatedFunctionDeclarationSyntax syn => BuildAssociatedFunction(declaringClass, syn), + IAbstractMethodDeclarationSyntax syn => BuildAbstractMethod(declaringClass, syn), + IConcreteMethodDeclarationSyntax syn => BuildConcreteMethod(declaringClass, syn), + IConstructorDeclarationSyntax syn => BuildConstructor(declaringClass, syn), + IFieldDeclarationSyntax syn => BuildField(declaringClass, syn), + _ => throw ExhaustiveMatch.Failed(member) + }; + } + + private static IAssociatedFunctionDeclaration BuildAssociatedFunction( + IClassDeclaration declaringClass, + IAssociatedFunctionDeclarationSyntax syn) + { + var symbol = syn.Symbol.Result; + var nameSpan = syn.NameSpan; + var parameters = syn.Parameters.Select(BuildParameter).ToFixedList(); + var body = BuildBody(syn.Body); + return new AssociatedFunctionDeclaration(syn.File, syn.Span, declaringClass, symbol, nameSpan, parameters, body); + } + + private static IAbstractMethodDeclaration BuildAbstractMethod( + IClassDeclaration declaringClass, + IAbstractMethodDeclarationSyntax syn) + { + var symbol = syn.Symbol.Result; + var nameSpan = syn.NameSpan; + var selfParameter = BuildParameter(syn.SelfParameter); + var parameters = syn.Parameters.Select(BuildParameter).ToFixedList(); + return new AbstractMethodDeclaration(syn.File, syn.Span, declaringClass, symbol, nameSpan, selfParameter, parameters); + } + + private static IConcreteMethodDeclaration BuildConcreteMethod( + IClassDeclaration declaringClass, + IConcreteMethodDeclarationSyntax syn) + { + var symbol = syn.Symbol.Result; + var nameSpan = syn.NameSpan; + var selfParameter = BuildParameter(syn.SelfParameter); + var parameters = syn.Parameters.Select(BuildParameter).ToFixedList(); + var body = BuildBody(syn.Body); + return new ConcreteMethodDeclaration(syn.File, syn.Span, declaringClass, symbol, nameSpan, selfParameter, parameters, body); + } + + private static IConstructorDeclaration BuildConstructor( + IClassDeclaration declaringClass, + IConstructorDeclarationSyntax syn) + { + var symbol = syn.Symbol.Result; + var nameSpan = syn.NameSpan; + var selfParameter = BuildParameter(syn.ImplicitSelfParameter); + var parameters = syn.Parameters.Select(BuildParameter).ToFixedList(); + var body = BuildBody(syn.Body); + return new ConstructorDeclaration(syn.File, syn.Span, declaringClass, symbol, nameSpan, selfParameter, parameters, body); + } + + private static IFieldDeclaration BuildField( + IClassDeclaration declaringClass, + IFieldDeclarationSyntax syn) + { + var symbol = syn.Symbol.Result; + var nameSpan = syn.NameSpan; + return new FieldDeclaration(syn.File, syn.Span, declaringClass, symbol, nameSpan); + } + + private static IFunctionDeclaration BuildFunction(IFunctionDeclarationSyntax syn) + { + var symbol = syn.Symbol.Result; + var nameSpan = syn.NameSpan; + var parameters = syn.Parameters.Select(BuildParameter).ToFixedList(); + var body = BuildBody(syn.Body); + return new FunctionDeclaration(syn.File, syn.Span, symbol, nameSpan, parameters, body); + } + + private static IConstructorParameter BuildParameter(IConstructorParameterSyntax parameter) + { + return parameter switch + { + INamedParameterSyntax syn => BuildParameter(syn), + IFieldParameterSyntax syn => BuildParameter(syn), + _ => throw ExhaustiveMatch.Failed(parameter), + }; + } + + private static INamedParameter BuildParameter(INamedParameterSyntax syn) + { + var symbol = syn.Symbol.Result; + var defaultValue = BuildExpression(syn.DefaultValue); + return new NamedParameter(syn.Span, symbol, syn.Unused, defaultValue); + } + + private static ISelfParameter BuildParameter(ISelfParameterSyntax syn) + { + var symbol = syn.Symbol.Result; + var unused = syn.Unused; + return new SelfParameter(syn.Span, symbol, unused); + } + + private static IFieldParameter BuildParameter(IFieldParameterSyntax syn) + { + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + var defaultValue = BuildExpression(syn.DefaultValue); + return new FieldParameter(syn.Span, referencedSymbol, defaultValue); + } + + private static IBody BuildBody(IBodySyntax syn) + { + var statements = syn.Statements.Select(BuildBodyStatement).ToFixedList(); + return new Body(syn.Span, statements); + } + + private static IStatement BuildStatement(IStatementSyntax stmt) + { + return stmt switch + { + IResultStatementSyntax syn => BuildResultStatement(syn), + IBodyStatementSyntax syn => BuildBodyStatement(syn), + _ => throw ExhaustiveMatch.Failed(stmt), + }; + } + + private static IBodyStatement BuildBodyStatement(IBodyStatementSyntax stmt) + { + return stmt switch + { + IExpressionStatementSyntax syn => BuildExpressionStatement(syn), + IVariableDeclarationStatementSyntax syn => BuildVariableDeclaration(syn), + _ => throw ExhaustiveMatch.Failed(stmt), + }; + } + + private static IExpressionStatement BuildExpressionStatement(IExpressionStatementSyntax syn) + { + var expression = BuildExpression(syn.Expression); + return new ExpressionStatement(syn.Span, expression); + } + + private static IVariableDeclarationStatement BuildVariableDeclaration(IVariableDeclarationStatementSyntax syn) + { + var nameSpan = syn.NameSpan; + var symbol = syn.Symbol.Result; + var initializer = BuildExpression(syn.Initializer); + return new VariableDeclarationStatement(syn.Span, nameSpan, symbol, initializer); + } + + private static IResultStatement BuildResultStatement(IResultStatementSyntax syn) + { + var expression = BuildExpression(syn.Expression); + return new ResultStatement(syn.Span, expression); + } + + private static IBlockOrResult BuildBlockOrResult(IBlockOrResultSyntax syntax) + { + return syntax switch + { + IBlockExpressionSyntax syn => BuildBlockExpression(syn), + IResultStatementSyntax syn => BuildResultStatement(syn), + _ => throw ExhaustiveMatch.Failed(syntax), + }; + } + + + + [return: NotNullIfNotNull("expression")] + private static IExpression? BuildExpression(IExpressionSyntax? expression) + { + return expression switch + { + null => null, + IAssignmentExpressionSyntax syn => BuildAssignmentExpression(syn), + IBinaryOperatorExpressionSyntax syn => BuildBinaryOperatorExpression(syn), + IBlockExpressionSyntax syn => BuildBlockExpression(syn), + IBoolLiteralExpressionSyntax syn => BuildBoolLiteralExpression(syn), + IMutateExpressionSyntax syn => BuildBorrowExpression(syn), + IBreakExpressionSyntax syn => BuildBreakExpression(syn), + IQualifiedNameExpressionSyntax syn => BuildFieldAccessExpression(syn), + IForeachExpressionSyntax syn => BuildForeachExpression(syn), + IUnqualifiedInvocationExpressionSyntax syn => BuildFunctionInvocationExpression(syn), + IIfExpressionSyntax syn => BuildIfExpression(syn), + IImplicitImmutabilityConversionExpressionSyntax syn => BuildImplicitImmutabilityConversionExpression(syn), + IImplicitNoneConversionExpressionSyntax syn => BuildImplicitNoneConversionExpression(syn), + IImplicitNumericConversionExpressionSyntax syn => BuildImplicitNumericConversionExpression(syn), + IImplicitOptionalConversionExpressionSyntax syn => BuildImplicitOptionalConversionExpression(syn), + IIntegerLiteralExpressionSyntax syn => BuildIntegerLiteralExpression(syn), + INoneLiteralExpressionSyntax syn => BuildNoneLiteralExpression(syn), + IStringLiteralExpressionSyntax syn => BuildStringLiteralExpression(syn), + ILoopExpressionSyntax syn => BuildLoopExpression(syn), + IQualifiedInvocationExpressionSyntax syn => BuildMethodInvocationExpression(syn), + IMoveExpressionSyntax syn => BuildMoveExpression(syn), + INameExpressionSyntax syn => BuildNameExpression(syn), + INewObjectExpressionSyntax syn => BuildNewObjectExpression(syn), + INextExpressionSyntax syn => BuildNextExpression(syn), + IReturnExpressionSyntax syn => BuildReturnExpression(syn), + ISelfExpressionSyntax syn => BuildSelfExpression(syn), + IShareExpressionSyntax syn => BuildShareExpression(syn), + IUnaryOperatorExpressionSyntax syn => BuildUnaryOperatorExpression(syn), + IUnsafeExpressionSyntax syn => BuildUnsafeExpression(syn), + IWhileExpressionSyntax syn => BuildWhileExpression(syn), + _ => throw ExhaustiveMatch.Failed(expression), + }; + } + + private static IAssignmentExpression BuildAssignmentExpression(IAssignmentExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var leftOperand = BuildAssignableExpression(syn.LeftOperand); + var @operator = syn.Operator; + var rightOperand = BuildExpression(syn.RightOperand); + return new AssignmentExpression(syn.Span, type, semantics, leftOperand, @operator, rightOperand); + } + + private static IAssignableExpression BuildAssignableExpression(IAssignableExpressionSyntax expression) + { + return expression switch + { + IQualifiedNameExpressionSyntax syn => BuildFieldAccessExpression(syn), + INameExpressionSyntax syn => BuildNameExpression(syn), + _ => throw ExhaustiveMatch.Failed(expression), + }; + } + + private static IBinaryOperatorExpression BuildBinaryOperatorExpression(IBinaryOperatorExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var leftOperand = BuildExpression(syn.LeftOperand); + var @operator = syn.Operator; + var rightOperand = BuildExpression(syn.RightOperand); + return new BinaryOperatorExpression(syn.Span, type, semantics, leftOperand, @operator, rightOperand); + } + + private static IBlockExpression BuildBlockExpression(IBlockExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var statements = syn.Statements.Select(BuildStatement).ToFixedList(); + return new BlockExpression(syn.Span, type, semantics, statements); + } + + private static IBoolLiteralExpression BuildBoolLiteralExpression(IBoolLiteralExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var value = syn.Value; + return new BoolLiteralExpression(syn.Span, type, semantics, value); + } + + private static IBorrowExpression BuildBorrowExpression(IMutateExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + var referent = BuildExpression(syn.Referent); + return new BorrowExpression(syn.Span, type, semantics, referencedSymbol, referent); + } + + private static IBreakExpression BuildBreakExpression(IBreakExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var value = BuildExpression(syn.Value); + return new BreakExpression(syn.Span, type, semantics, value); + } + + private static IFieldAccessExpression BuildFieldAccessExpression(IQualifiedNameExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var context = BuildExpression(syn.Context); + var accessOperator = syn.AccessOperator; + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + return new FieldAccessExpression(syn.Span, type, semantics, context, accessOperator, referencedSymbol); + } + + private static IForeachExpression BuildForeachExpression(IForeachExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var symbol = syn.Symbol.Result; + var inExpression = BuildExpression(syn.InExpression); + var block = BuildBlockExpression(syn.Block); + return new ForeachExpression(syn.Span, type, semantics, symbol, inExpression, block); + } + + private static IFunctionInvocationExpression BuildFunctionInvocationExpression(IUnqualifiedInvocationExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + FixedList arguments = syn.Arguments.Select(a => BuildExpression(a.Expression)).ToFixedList(); + return new FunctionInvocationExpression(syn.Span, type, semantics, referencedSymbol, arguments); + } + + private static IIfExpression BuildIfExpression(IIfExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var condition = BuildExpression(syn.Condition); + var thenBlock = BuildBlockOrResult(syn.ThenBlock); + var elseClause = BuildElseClause(syn.ElseClause); + return new IfExpression(syn.Span, type, semantics, condition, thenBlock, elseClause); + } + + [return: NotNullIfNotNull("syn")] + private static IElseClause? BuildElseClause(IElseClauseSyntax? elseClause) + { + return elseClause switch + { + null => null, + IBlockOrResultSyntax syn => BuildBlockOrResult(syn), + IIfExpressionSyntax syn => BuildIfExpression(syn), + _ => throw ExhaustiveMatch.Failed(elseClause), + }; + } + + private static IImplicitImmutabilityConversionExpression BuildImplicitImmutabilityConversionExpression( + IImplicitImmutabilityConversionExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var expression = BuildExpression(syn.Expression); + var convertToType = syn.ConvertToType; + return new ImplicitImmutabilityConversion(syn.Span, type, semantics, expression, convertToType); + } + + private static IImplicitNoneConversionExpression BuildImplicitNoneConversionExpression(IImplicitNoneConversionExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var expression = BuildExpression(syn.Expression); + var convertToType = syn.ConvertToType; + return new ImplicitNoneConversionExpression(syn.Span, type, semantics, expression, convertToType); + } + + private static IImplicitNumericConversionExpression BuildImplicitNumericConversionExpression(IImplicitNumericConversionExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var expression = BuildExpression(syn.Expression); + var convertToType = syn.ConvertToType; + return new ImplicitNumericConversionExpression(syn.Span, type, semantics, expression, convertToType); + } + + private static IImplicitOptionalConversionExpression BuildImplicitOptionalConversionExpression(IImplicitOptionalConversionExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var expression = BuildExpression(syn.Expression); + var convertToType = syn.ConvertToType; + return new ImplicitOptionalConversionExpression(syn.Span, type, semantics, expression, convertToType); + } + + private static IIntegerLiteralExpression BuildIntegerLiteralExpression(IIntegerLiteralExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var value = syn.Value; + return new IntegerLiteralExpression(syn.Span, type, semantics, value); + } + + private static INoneLiteralExpression BuildNoneLiteralExpression(INoneLiteralExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + return new NoneLiteralExpression(syn.Span, type, semantics); + } + + private static IStringLiteralExpression BuildStringLiteralExpression(IStringLiteralExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var value = syn.Value; + return new StringLiteralExpression(syn.Span, type, semantics, value); + } + + private static ILoopExpression BuildLoopExpression(ILoopExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var block = BuildBlockExpression(syn.Block); + return new LoopExpression(syn.Span, type, semantics, block); + } + + private static IMethodInvocationExpression BuildMethodInvocationExpression(IQualifiedInvocationExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var context = BuildExpression(syn.Context); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + FixedList arguments = syn.Arguments.Select(a => BuildExpression(a.Expression)).ToFixedList(); + return new MethodInvocationExpression(syn.Span, type, semantics, context, referencedSymbol, arguments); + } + + private static IMoveExpression BuildMoveExpression(IMoveExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + var referent = BuildExpression(syn.Referent); + return new MoveExpression(syn.Span, type, semantics, referencedSymbol, referent); + } + + private static INameExpression BuildNameExpression(INameExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + return new NameExpression(syn.Span, type, semantics, referencedSymbol); + } + + private static INewObjectExpression BuildNewObjectExpression(INewObjectExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + FixedList arguments = syn.Arguments.Select(a => BuildExpression(a.Expression)).ToFixedList(); + return new NewObjectExpression(syn.Span, type, semantics, referencedSymbol, arguments); + } + + private static INextExpression BuildNextExpression(INextExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + return new NextExpression(syn.Span, type, semantics); + } + + private static IReturnExpression BuildReturnExpression(IReturnExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var value = BuildExpression(syn.Value); + return new ReturnExpression(syn.Span, type, semantics, value); + } + + private static ISelfExpression BuildSelfExpression(ISelfExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + var isImplicit = syn.IsImplicit; + return new SelfExpression(syn.Span, type, semantics, referencedSymbol, isImplicit); + } + + private static IShareExpression BuildShareExpression(IShareExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var referencedSymbol = syn.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + var referent = BuildExpression(syn.Referent); + return new ShareExpression(syn.Span, type, semantics, referencedSymbol, referent); + } + + private static IUnaryOperatorExpression BuildUnaryOperatorExpression(IUnaryOperatorExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var fixity = syn.Fixity; + var @operator = syn.Operator; + var operand = BuildExpression(syn.Operand); + return new UnaryOperatorExpression(syn.Span, type, semantics, fixity, @operator, operand); + } + + private static IUnsafeExpression BuildUnsafeExpression(IUnsafeExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var expression = BuildExpression(syn.Expression); + return new UnsafeExpression(syn.Span, type, semantics, expression); + } + + private static IWhileExpression BuildWhileExpression(IWhileExpressionSyntax syn) + { + var type = syn.DataType ?? throw new InvalidOperationException(); + var semantics = syn.Semantics.Assigned(); + var condition = BuildExpression(syn.Condition); + var block = BuildBlockExpression(syn.Block); + return new WhileExpression(syn.Span, type, semantics, condition, block); + } + } +} diff --git a/Compiler.Semantics/AST/Tree/AbstractMethodDeclaration.cs b/Compiler.Semantics/AST/Tree/AbstractMethodDeclaration.cs new file mode 100644 index 00000000..d599238b --- /dev/null +++ b/Compiler.Semantics/AST/Tree/AbstractMethodDeclaration.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class AbstractMethodDeclaration : InvocableDeclaration, IAbstractMethodDeclaration + { + public IClassDeclaration DeclaringClass { get; } + public new MethodSymbol Symbol { get; } + public ISelfParameter SelfParameter { get; } + public new FixedList Parameters { get; } + + public AbstractMethodDeclaration( + CodeFile file, + TextSpan span, + IClassDeclaration declaringClass, + MethodSymbol symbol, + TextSpan nameSpan, + ISelfParameter selfParameter, + FixedList parameters) + : base(file, span, symbol, nameSpan, parameters.ToFixedList()) + { + Symbol = symbol; + SelfParameter = selfParameter; + Parameters = parameters; + DeclaringClass = declaringClass; + } + + public override string ToString() + { + var returnType = Symbol.ReturnDataType != DataType.Void ? " -> " + Symbol.ReturnDataType : ""; + return $"fn {Symbol.Name}({string.Join(", ", Parameters.Prepend(SelfParameter))}){returnType};"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/AbstractSyntax.cs b/Compiler.Semantics/AST/Tree/AbstractSyntax.cs new file mode 100644 index 00000000..d7011e8b --- /dev/null +++ b/Compiler.Semantics/AST/Tree/AbstractSyntax.cs @@ -0,0 +1,19 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + [DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] + internal abstract class AbstractSyntax : IAbstractSyntax + { + public TextSpan Span { get; } + + protected AbstractSyntax(TextSpan span) + { + Span = span; + } + + public abstract override string ToString(); + } +} diff --git a/Compiler.Semantics/AST/Tree/AssignmentExpression.cs b/Compiler.Semantics/AST/Tree/AssignmentExpression.cs new file mode 100644 index 00000000..090a6d68 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/AssignmentExpression.cs @@ -0,0 +1,37 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class AssignmentExpression : Expression, IAssignmentExpression + { + public IAssignableExpression LeftOperand { [DebuggerStepThrough] get; } + public AssignmentOperator Operator { [DebuggerStepThrough] get; } + public IExpression RightOperand { [DebuggerStepThrough] get; } + + public AssignmentExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IAssignableExpression leftOperand, + AssignmentOperator @operator, + IExpression rightOperand) + : base(span, dataType, semantics) + { + LeftOperand = leftOperand; + Operator = @operator; + RightOperand = rightOperand; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Assignment; + + public override string ToString() + { + return $"{LeftOperand.ToGroupedString(ExpressionPrecedence)} {Operator.ToSymbolString()} {RightOperand.ToGroupedString(ExpressionPrecedence)}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/AssociatedFunctionDeclaration.cs b/Compiler.Semantics/AST/Tree/AssociatedFunctionDeclaration.cs new file mode 100644 index 00000000..743cd18e --- /dev/null +++ b/Compiler.Semantics/AST/Tree/AssociatedFunctionDeclaration.cs @@ -0,0 +1,38 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class AssociatedFunctionDeclaration : InvocableDeclaration, IAssociatedFunctionDeclaration + { + public IClassDeclaration DeclaringClass { get; } + public new FunctionSymbol Symbol { get; } + public new FixedList Parameters { get; } + public IBody Body { get; } + + public AssociatedFunctionDeclaration( + CodeFile file, + TextSpan span, + IClassDeclaration declaringClass, + FunctionSymbol symbol, + TextSpan nameSpan, + FixedList parameters, + IBody body) + : base(file, span, symbol, nameSpan, parameters.ToFixedList()) + { + Symbol = symbol; + Parameters = parameters; + Body = body; + DeclaringClass = declaringClass; + } + + public override string ToString() + { + var returnType = Symbol.ReturnDataType != DataType.Void ? " -> " + Symbol.ReturnDataType : ""; + return $"fn {Symbol.Name}({string.Join(", ", Parameters)}){returnType} {Body}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/BinaryOperatorExpression.cs b/Compiler.Semantics/AST/Tree/BinaryOperatorExpression.cs new file mode 100644 index 00000000..5b1986ac --- /dev/null +++ b/Compiler.Semantics/AST/Tree/BinaryOperatorExpression.cs @@ -0,0 +1,62 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class BinaryOperatorExpression : Expression, IBinaryOperatorExpression + { + public IExpression LeftOperand { get; } + public BinaryOperator Operator { get; } + public IExpression RightOperand { get; } + + public BinaryOperatorExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression leftOperand, + BinaryOperator @operator, + IExpression rightOperand) + : base(span, dataType, semantics) + { + LeftOperand = leftOperand; + Operator = @operator; + RightOperand = rightOperand; + } + + protected override OperatorPrecedence ExpressionPrecedence => + Operator switch + { + BinaryOperator.And => OperatorPrecedence.LogicalAnd, + BinaryOperator.Or => OperatorPrecedence.LogicalOr, + + BinaryOperator.Plus => OperatorPrecedence.Additive, + BinaryOperator.Minus => OperatorPrecedence.Additive, + + BinaryOperator.Asterisk => OperatorPrecedence.Multiplicative, + BinaryOperator.Slash => OperatorPrecedence.Multiplicative, + + BinaryOperator.DotDot => OperatorPrecedence.Range, + BinaryOperator.DotDotLessThan => OperatorPrecedence.Range, + BinaryOperator.LessThanDotDot => OperatorPrecedence.Range, + BinaryOperator.LessThanDotDotLessThan => OperatorPrecedence.Range, + + BinaryOperator.EqualsEquals => OperatorPrecedence.Relational, + BinaryOperator.NotEqual => OperatorPrecedence.Relational, + BinaryOperator.LessThan => OperatorPrecedence.Relational, + BinaryOperator.LessThanOrEqual => OperatorPrecedence.Relational, + BinaryOperator.GreaterThan => OperatorPrecedence.Relational, + BinaryOperator.GreaterThanOrEqual => OperatorPrecedence.Relational, + + _ => throw ExhaustiveMatch.Failed(Operator), + }; + + public override string ToString() + { + return $"{LeftOperand.ToGroupedString(ExpressionPrecedence)} {Operator.ToSymbolString()} {RightOperand.ToGroupedString(ExpressionPrecedence)}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/BlockExpression.cs b/Compiler.Semantics/AST/Tree/BlockExpression.cs new file mode 100644 index 00000000..a25048a5 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/BlockExpression.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class BlockExpression : Expression, IBlockExpression + { + public FixedList Statements { get; } + + public BlockExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + FixedList statements) + : base(span, dataType, semantics) + { + Statements = statements; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + if (Statements.Any()) return $"{{ {Statements.Count} Statements }} : {DataType}"; + + return $"{{ }} : {DataType}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/Body.cs b/Compiler.Semantics/AST/Tree/Body.cs new file mode 100644 index 00000000..0d9ad748 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/Body.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class Body : AbstractSyntax, IBody + { + public FixedList Statements { get; } + FixedList IBodyOrBlock.Statements => Statements.ToFixedList(); + + public Body(TextSpan span, FixedList statements) + : base(span) + { + Statements = statements; + } + + public override string ToString() + { + return "{ … }"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/BoolLiteralExpression.cs b/Compiler.Semantics/AST/Tree/BoolLiteralExpression.cs new file mode 100644 index 00000000..e0a6a903 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/BoolLiteralExpression.cs @@ -0,0 +1,30 @@ +using System.Globalization; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class BoolLiteralExpression : LiteralExpression, IBoolLiteralExpression + { + public bool Value { get; } + + public BoolLiteralExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + bool value) + : base(span, dataType, semantics) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return Value.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/Compiler.Semantics/AST/Tree/BorrowExpression.cs b/Compiler.Semantics/AST/Tree/BorrowExpression.cs new file mode 100644 index 00000000..964e225e --- /dev/null +++ b/Compiler.Semantics/AST/Tree/BorrowExpression.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class BorrowExpression : Expression, IBorrowExpression + { + public BindingSymbol ReferencedSymbol { get; } + public IExpression Referent { get; } + + public BorrowExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + BindingSymbol referencedSymbol, + IExpression referent) + : base(span, dataType, semantics) + { + ReferencedSymbol = referencedSymbol; + Referent = referent; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return $"mut {Referent}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/BreakExpression.cs b/Compiler.Semantics/AST/Tree/BreakExpression.cs new file mode 100644 index 00000000..261f3be0 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/BreakExpression.cs @@ -0,0 +1,31 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class BreakExpression : Expression, IBreakExpression + { + public IExpression? Value { get; } + + public BreakExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression? value) + : base(span, dataType, semantics) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => + Value != null ? OperatorPrecedence.Min : OperatorPrecedence.Primary; + + public override string ToString() + { + if (Value != null) return $"break {Value}"; + return "break"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ClassDeclaration.cs b/Compiler.Semantics/AST/Tree/ClassDeclaration.cs new file mode 100644 index 00000000..e1c886ee --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ClassDeclaration.cs @@ -0,0 +1,34 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ClassDeclaration : Declaration, IClassDeclaration + { + public new ObjectTypeSymbol Symbol { get; } + public FixedList Members { get; } + public ConstructorSymbol? DefaultConstructorSymbol { get; } + + public ClassDeclaration( + CodeFile file, + TextSpan span, + ObjectTypeSymbol symbol, + TextSpan nameSpan, + ConstructorSymbol? defaultConstructorSymbol, + Func> buildMembers) + : base(file, span, symbol, nameSpan) + { + Symbol = symbol; + DefaultConstructorSymbol = defaultConstructorSymbol; + Members = buildMembers(this); + } + + public override string ToString() + { + return $"class {Symbol.ContainingSymbol}.{Symbol.Name} {{ … }}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ConcreteMethodDeclaration.cs b/Compiler.Semantics/AST/Tree/ConcreteMethodDeclaration.cs new file mode 100644 index 00000000..26283b02 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ConcreteMethodDeclaration.cs @@ -0,0 +1,42 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; +using MoreLinq.Extensions; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ConcreteMethodDeclaration : InvocableDeclaration, IConcreteMethodDeclaration + { + public IClassDeclaration DeclaringClass { get; } + public new MethodSymbol Symbol { get; } + public ISelfParameter SelfParameter { get; } + public new FixedList Parameters { get; } + public IBody Body { get; } + + public ConcreteMethodDeclaration( + CodeFile file, + TextSpan span, + IClassDeclaration declaringClass, + MethodSymbol symbol, + TextSpan nameSpan, + ISelfParameter selfParameter, + FixedList parameters, + IBody body) + : base(file, span, symbol, nameSpan, + parameters.ToFixedList()) + { + Symbol = symbol; + Parameters = parameters; + SelfParameter = selfParameter; + Body = body; + DeclaringClass = declaringClass; + } + + public override string ToString() + { + var returnType = Symbol.ReturnDataType != null ? " -> " + Symbol.ReturnDataType : ""; + return $"fn {Symbol.ContainingSymbol}::{Symbol.Name}({string.Join(", ", Parameters.Prepend(SelfParameter))}){returnType} {Body}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ConstructorDeclaration.cs b/Compiler.Semantics/AST/Tree/ConstructorDeclaration.cs new file mode 100644 index 00000000..a3b5019d --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ConstructorDeclaration.cs @@ -0,0 +1,38 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ConstructorDeclaration : InvocableDeclaration, IConstructorDeclaration + { + public IClassDeclaration DeclaringClass { get; } + public new ConstructorSymbol Symbol { get; } + public ISelfParameter ImplicitSelfParameter { get; } + public IBody Body { get; } + + public ConstructorDeclaration( + CodeFile file, + TextSpan span, + IClassDeclaration declaringClass, + ConstructorSymbol symbol, + TextSpan nameSpan, + ISelfParameter implicitSelfParameter, + FixedList parameters, + IBody body) + : base(file, span, symbol, nameSpan, parameters) + { + Symbol = symbol; + ImplicitSelfParameter = implicitSelfParameter; + Body = body; + DeclaringClass = declaringClass; + } + + public override string ToString() + { + var name = Symbol.Name is null ? $" {Symbol.Name}" : ""; + return $"{Symbol.ContainingSymbol}::new{name}({string.Join(", ", Parameters)})"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/Declaration.cs b/Compiler.Semantics/AST/Tree/Declaration.cs new file mode 100644 index 00000000..f54365f9 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/Declaration.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal abstract class Declaration : AbstractSyntax, IDeclaration + { + public CodeFile File { get; } + public Symbol Symbol { get; } + public TextSpan NameSpan { get; } + + protected Declaration(CodeFile file, TextSpan span, Symbol symbol, TextSpan nameSpan) + : base(span) + { + Symbol = symbol; + NameSpan = nameSpan; + File = file; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/Expression.cs b/Compiler.Semantics/AST/Tree/Expression.cs new file mode 100644 index 00000000..f35936da --- /dev/null +++ b/Compiler.Semantics/AST/Tree/Expression.cs @@ -0,0 +1,27 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal abstract class Expression : AbstractSyntax, IExpression + { + public DataType DataType { get; } + public ExpressionSemantics Semantics { get; } + + protected Expression(TextSpan span, DataType dataType, ExpressionSemantics semantics) + : base(span) + { + DataType = dataType; + Semantics = semantics; + } + + protected abstract OperatorPrecedence ExpressionPrecedence { get; } + + public string ToGroupedString(OperatorPrecedence surroundingPrecedence) + { + return surroundingPrecedence > ExpressionPrecedence ? $"({this})" : ToString(); + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ExpressionStatement.cs b/Compiler.Semantics/AST/Tree/ExpressionStatement.cs new file mode 100644 index 00000000..a763551e --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ExpressionStatement.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ExpressionStatement : Statement, IExpressionStatement + { + public IExpression Expression { [DebuggerStepThrough] get; } + + public ExpressionStatement(TextSpan span, IExpression expression) + : base(span) + { + Expression = expression; + } + + public override string ToString() + { + return Expression + ";"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/FieldAccessExpression.cs b/Compiler.Semantics/AST/Tree/FieldAccessExpression.cs new file mode 100644 index 00000000..2d9f4bf3 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/FieldAccessExpression.cs @@ -0,0 +1,37 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class FieldAccessExpression : Expression, IFieldAccessExpression + { + public IExpression Context { get; } + public AccessOperator AccessOperator { get; } + public FieldSymbol ReferencedSymbol { get; } + + public FieldAccessExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression context, + AccessOperator accessOperator, + FieldSymbol referencedSymbol) + : base(span, dataType, semantics) + { + Context = context; + AccessOperator = accessOperator; + ReferencedSymbol = referencedSymbol; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"{Context.ToGroupedString(ExpressionPrecedence)}{AccessOperator.ToSymbolString()}{ReferencedSymbol.Name}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/FieldDeclaration.cs b/Compiler.Semantics/AST/Tree/FieldDeclaration.cs new file mode 100644 index 00000000..b2f112f0 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/FieldDeclaration.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class FieldDeclaration : Declaration, IFieldDeclaration + { + public IClassDeclaration DeclaringClass { get; } + public new FieldSymbol Symbol { get; } + BindingSymbol IBinding.Symbol => Symbol; + + public FieldDeclaration( + CodeFile file, + TextSpan span, + IClassDeclaration declaringClass, + FieldSymbol symbol, + TextSpan nameSpan) + : base(file, span, symbol, nameSpan) + { + Symbol = symbol; + DeclaringClass = declaringClass; + } + + public override string ToString() + { + return Symbol + ";"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/FieldParameter.cs b/Compiler.Semantics/AST/Tree/FieldParameter.cs new file mode 100644 index 00000000..b1f0b0a9 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/FieldParameter.cs @@ -0,0 +1,28 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class FieldParameter : Parameter, IFieldParameter + { + public FieldSymbol ReferencedSymbol { get; } + public IExpression? DefaultValue { get; } + + public FieldParameter( + TextSpan span, + FieldSymbol referencedSymbol, + IExpression? defaultValue) + : base(span, false) + { + ReferencedSymbol = referencedSymbol; + DefaultValue = defaultValue; + } + + public override string ToString() + { + var defaultValue = DefaultValue != null ? " = " + DefaultValue : ""; + return $".{ReferencedSymbol.Name}{defaultValue}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ForeachExpression.cs b/Compiler.Semantics/AST/Tree/ForeachExpression.cs new file mode 100644 index 00000000..57b2804f --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ForeachExpression.cs @@ -0,0 +1,42 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ForeachExpression : Expression, IForeachExpression + { + public VariableSymbol Symbol { get; } + BindingSymbol IBinding.Symbol => Symbol; + NamedBindingSymbol ILocalBinding.Symbol => Symbol; + public IExpression InExpression { get; } + public IBlockExpression Block { get; } + public Promise VariableIsLiveAfterAssignment { get; } = new Promise(); + + public ForeachExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + VariableSymbol symbol, + IExpression inExpression, + IBlockExpression block) + : base(span, dataType, semantics) + { + Symbol = symbol; + InExpression = inExpression; + Block = block; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + var binding = Symbol.IsMutableBinding ? "var " : ""; + var declarationNumber = Symbol.DeclarationNumber is null ? "" : "#" + Symbol.DeclarationNumber; + return $"foreach {binding}{Symbol.Name}{declarationNumber}: {Symbol.DataType} in {InExpression} {Block}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/FunctionDeclaration.cs b/Compiler.Semantics/AST/Tree/FunctionDeclaration.cs new file mode 100644 index 00000000..ef5e4fb2 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/FunctionDeclaration.cs @@ -0,0 +1,35 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class FunctionDeclaration : InvocableDeclaration, IFunctionDeclaration + { + public new FunctionSymbol Symbol { get; } + public new FixedList Parameters { get; } + public IBody Body { get; } + + public FunctionDeclaration( + CodeFile file, + TextSpan span, + FunctionSymbol symbol, + TextSpan nameSpan, + FixedList parameters, + IBody body) + : base(file, span, symbol, nameSpan, parameters.ToFixedList()) + { + Symbol = symbol; + Parameters = parameters; + Body = body; + } + + public override string ToString() + { + var returnType = Symbol.ReturnDataType != DataType.Void ? " -> " + Symbol.ReturnDataType : ""; + return $"fn {Symbol.ContainingSymbol}.{Symbol.Name}({string.Join(", ", Parameters)}){returnType} {Body}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/FunctionInvocationExpression.cs b/Compiler.Semantics/AST/Tree/FunctionInvocationExpression.cs new file mode 100644 index 00000000..f0bb873b --- /dev/null +++ b/Compiler.Semantics/AST/Tree/FunctionInvocationExpression.cs @@ -0,0 +1,35 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class FunctionInvocationExpression : Expression, IFunctionInvocationExpression + { + public FunctionSymbol ReferencedSymbol { get; } + InvocableSymbol IInvocationExpression.ReferencedSymbol => ReferencedSymbol; + public FixedList Arguments { get; } + + public FunctionInvocationExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + FunctionSymbol referencedSymbol, + FixedList arguments) + : base(span, dataType, semantics) + { + ReferencedSymbol = referencedSymbol; + Arguments = arguments; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"{ReferencedSymbol.ContainingSymbol}.{ReferencedSymbol.Name}({string.Join(", ", Arguments)})"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/IfExpression.cs b/Compiler.Semantics/AST/Tree/IfExpression.cs new file mode 100644 index 00000000..5d6ea377 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/IfExpression.cs @@ -0,0 +1,36 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class IfExpression : Expression, IIfExpression + { + public IExpression Condition { get; } + public IBlockOrResult ThenBlock { get; } + public IElseClause? ElseClause { get; } + + public IfExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression condition, + IBlockOrResult thenBlock, + IElseClause? elseClause) + : base(span, dataType, semantics) + { + Condition = condition; + ThenBlock = thenBlock; + ElseClause = elseClause; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + if (ElseClause != null) return $"if {Condition} {ThenBlock} else {ElseClause}"; + return $"if {Condition} {ThenBlock}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ImplicitConversionExpression.cs b/Compiler.Semantics/AST/Tree/ImplicitConversionExpression.cs new file mode 100644 index 00000000..92f100f2 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ImplicitConversionExpression.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal abstract class ImplicitConversionExpression : Expression, IImplicitConversionExpression + { + public IExpression Expression { get; } + + protected ImplicitConversionExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression expression) + : base(span, dataType, semantics) + { + Expression = expression; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + } +} diff --git a/Compiler.Semantics/AST/Tree/ImplicitImmutabilityConversion.cs b/Compiler.Semantics/AST/Tree/ImplicitImmutabilityConversion.cs new file mode 100644 index 00000000..a1b43a1d --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ImplicitImmutabilityConversion.cs @@ -0,0 +1,28 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ImplicitImmutabilityConversion : ImplicitConversionExpression, IImplicitImmutabilityConversionExpression + { + public ObjectType ConvertToType { get; } + + public ImplicitImmutabilityConversion( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression expression, + ObjectType convertToType) + : base(span, dataType, semantics, expression) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{Expression.ToGroupedString(OperatorPrecedence.Min)} ⟦as ⟦immutable⟧⟧"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ImplicitNoneConversionExpression.cs b/Compiler.Semantics/AST/Tree/ImplicitNoneConversionExpression.cs new file mode 100644 index 00000000..ca917dce --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ImplicitNoneConversionExpression.cs @@ -0,0 +1,28 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ImplicitNoneConversionExpression : ImplicitConversionExpression, IImplicitNoneConversionExpression + { + public OptionalType ConvertToType { get; } + + public ImplicitNoneConversionExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression expression, + OptionalType convertToType) + : base(span, dataType, semantics, expression) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{Expression.ToGroupedString(OperatorPrecedence.Min)} ⟦as {ConvertToType}⟧"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ImplicitNumericConversionExpression.cs b/Compiler.Semantics/AST/Tree/ImplicitNumericConversionExpression.cs new file mode 100644 index 00000000..5b0a8533 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ImplicitNumericConversionExpression.cs @@ -0,0 +1,27 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ImplicitNumericConversionExpression : ImplicitConversionExpression, IImplicitNumericConversionExpression + { + public NumericType ConvertToType { get; } + + public ImplicitNumericConversionExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression expression, + NumericType convertToType) + : base(span, dataType, semantics, expression) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{{Expression.ToGroupedString(OperatorPrecedence.Min)}} ⟦as {ConvertToType}⟧"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ImplicitOptionalConversionExpression.cs b/Compiler.Semantics/AST/Tree/ImplicitOptionalConversionExpression.cs new file mode 100644 index 00000000..081021fc --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ImplicitOptionalConversionExpression.cs @@ -0,0 +1,28 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ImplicitOptionalConversionExpression : ImplicitConversionExpression, IImplicitOptionalConversionExpression + { + public OptionalType ConvertToType { get; } + + public ImplicitOptionalConversionExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression expression, + OptionalType convertToType) + : base(span, dataType, semantics, expression) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{Expression.ToGroupedString(OperatorPrecedence.Min)} ⟦as {ConvertToType}⟧"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/IntegerLiteralExpression.cs b/Compiler.Semantics/AST/Tree/IntegerLiteralExpression.cs new file mode 100644 index 00000000..c42b8d89 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/IntegerLiteralExpression.cs @@ -0,0 +1,31 @@ +using System.Globalization; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class IntegerLiteralExpression : LiteralExpression, IIntegerLiteralExpression + { + public BigInteger Value { get; } + + public IntegerLiteralExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + BigInteger value) + : base(span, dataType, semantics) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return Value.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/Compiler.Semantics/AST/Tree/InvocableDeclaration.cs b/Compiler.Semantics/AST/Tree/InvocableDeclaration.cs new file mode 100644 index 00000000..b0c3b1d2 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/InvocableDeclaration.cs @@ -0,0 +1,25 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal abstract class InvocableDeclaration : Declaration, IInvocableDeclaration + { + public new InvocableSymbol Symbol { get; } + public FixedList Parameters { get; } + + protected InvocableDeclaration( + CodeFile file, + TextSpan span, + InvocableSymbol symbol, + TextSpan nameSpan, + FixedList parameters) + : base(file, span, symbol, nameSpan) + { + Symbol = symbol; + Parameters = parameters; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/LiteralExpression.cs b/Compiler.Semantics/AST/Tree/LiteralExpression.cs new file mode 100644 index 00000000..696c1bfa --- /dev/null +++ b/Compiler.Semantics/AST/Tree/LiteralExpression.cs @@ -0,0 +1,15 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal abstract class LiteralExpression : Expression, ILiteralExpression + { + protected LiteralExpression(TextSpan span, DataType dataType, ExpressionSemantics semantics) + : base(span, dataType, semantics) { } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + } +} diff --git a/Compiler.Semantics/AST/Tree/LoopExpression.cs b/Compiler.Semantics/AST/Tree/LoopExpression.cs new file mode 100644 index 00000000..07caa51c --- /dev/null +++ b/Compiler.Semantics/AST/Tree/LoopExpression.cs @@ -0,0 +1,29 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class LoopExpression : Expression, ILoopExpression + { + public IBlockExpression Block { get; } + + public LoopExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IBlockExpression block) + : base(span, dataType, semantics) + { + Block = block; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"loop {Block}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/MethodInvocationExpression.cs b/Compiler.Semantics/AST/Tree/MethodInvocationExpression.cs new file mode 100644 index 00000000..04d9da2b --- /dev/null +++ b/Compiler.Semantics/AST/Tree/MethodInvocationExpression.cs @@ -0,0 +1,38 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class MethodInvocationExpression : Expression, IMethodInvocationExpression + { + public IExpression Context { get; } + public MethodSymbol ReferencedSymbol { get; } + InvocableSymbol IInvocationExpression.ReferencedSymbol => ReferencedSymbol; + public FixedList Arguments { get; } + + public MethodInvocationExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression context, + MethodSymbol referencedSymbol, + FixedList arguments) + : base(span, dataType, semantics) + { + Context = context; + ReferencedSymbol = referencedSymbol; + Arguments = arguments; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"{Context.ToGroupedString(ExpressionPrecedence)}.{ReferencedSymbol.Name}({string.Join(", ", Arguments)})"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/MoveExpression.cs b/Compiler.Semantics/AST/Tree/MoveExpression.cs new file mode 100644 index 00000000..bb92bac0 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/MoveExpression.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class MoveExpression : Expression, IMoveExpression + { + public BindingSymbol ReferencedSymbol { get; } + public IExpression Referent { get; } + + public MoveExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + BindingSymbol referencedSymbol, + IExpression referent) + : base(span, dataType, semantics) + { + ReferencedSymbol = referencedSymbol; + Referent = referent; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return $"move {Referent}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/NameExpression.cs b/Compiler.Semantics/AST/Tree/NameExpression.cs new file mode 100644 index 00000000..60bf0b85 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/NameExpression.cs @@ -0,0 +1,32 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class NameExpression : Expression, INameExpression + { + public NamedBindingSymbol ReferencedSymbol { get; } + public Promise VariableIsLiveAfter { get; } = new Promise(); + + public NameExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + NamedBindingSymbol referencedSymbol) + : base(span, dataType, semantics) + { + ReferencedSymbol = referencedSymbol; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return ReferencedSymbol.Name.ToString(); + } + } +} diff --git a/Compiler.Semantics/AST/Tree/NamedParameter.cs b/Compiler.Semantics/AST/Tree/NamedParameter.cs new file mode 100644 index 00000000..fa2f4250 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/NamedParameter.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class NamedParameter : Parameter, INamedParameter + { + public VariableSymbol Symbol { get; } + BindingSymbol IBinding.Symbol => Symbol; + NamedBindingSymbol ILocalBinding.Symbol => Symbol; + public IExpression? DefaultValue { get; } + + public NamedParameter( + TextSpan span, + VariableSymbol symbol, + bool unused, + IExpression? defaultValue) + : base(span, unused) + { + Symbol = symbol; + DefaultValue = defaultValue; + } + + public override string ToString() + { + var mutable = Symbol.IsMutableBinding ? "var " : ""; + var defaultValue = DefaultValue != null ? " = " + DefaultValue : ""; + var declarationNumber = Symbol.DeclarationNumber is null ? "" : "#" + Symbol.DeclarationNumber; + return $"{mutable}{Symbol.Name}{declarationNumber}: {Symbol.DataType}{defaultValue}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/NewObjectExpression.cs b/Compiler.Semantics/AST/Tree/NewObjectExpression.cs new file mode 100644 index 00000000..8f337b2a --- /dev/null +++ b/Compiler.Semantics/AST/Tree/NewObjectExpression.cs @@ -0,0 +1,35 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class NewObjectExpression : Expression, INewObjectExpression + { + public ConstructorSymbol ReferencedSymbol { get; } + public FixedList Arguments { get; } + + public NewObjectExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + ConstructorSymbol referencedSymbol, + FixedList arguments) + : base(span, dataType, semantics) + { + ReferencedSymbol = referencedSymbol; + Arguments = arguments; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + var name = ReferencedSymbol.Name != null ? "." + ReferencedSymbol.Name : ""; + return $"new {ReferencedSymbol.ContainingSymbol}{name}({string.Join(", ", Arguments)})"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/NextExpression.cs b/Compiler.Semantics/AST/Tree/NextExpression.cs new file mode 100644 index 00000000..9a4f9a7d --- /dev/null +++ b/Compiler.Semantics/AST/Tree/NextExpression.cs @@ -0,0 +1,20 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class NextExpression : Expression, INextExpression + { + public NextExpression(TextSpan span, DataType dataType, ExpressionSemantics semantics) + : base(span, dataType, semantics) { } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return "next"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/NoneLiteralExpression.cs b/Compiler.Semantics/AST/Tree/NoneLiteralExpression.cs new file mode 100644 index 00000000..f929edd3 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/NoneLiteralExpression.cs @@ -0,0 +1,17 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class NoneLiteralExpression : LiteralExpression, INoneLiteralExpression + { + public NoneLiteralExpression(TextSpan span, DataType dataType, ExpressionSemantics semantics) + : base(span, dataType, semantics) { } + + public override string ToString() + { + return "none"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/Package.cs b/Compiler.Semantics/AST/Tree/Package.cs new file mode 100644 index 00000000..4576221d --- /dev/null +++ b/Compiler.Semantics/AST/Tree/Package.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Primitives; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class Package + { + public FixedList AllDeclarations { get; } + public FixedList NonMemberDeclarations { get; } + public FixedSymbolTree SymbolTree { get; } + public SymbolForest SymbolTrees { get; } + public Diagnostics Diagnostics { get; } + public FixedDictionary References { get; } + public IEnumerable ReferencedPackages => References.Values; + + public Package( + FixedList nonMemberDeclarations, + FixedSymbolTree symbolTree, + Diagnostics diagnostics, + FixedDictionary references) + { + AllDeclarations = GetAllDeclarations(nonMemberDeclarations).ToFixedList(); + NonMemberDeclarations = nonMemberDeclarations; + SymbolTree = symbolTree; + Diagnostics = diagnostics; + References = references; + SymbolTrees = new SymbolForest(Primitive.SymbolTree, ReferencedPackages.Select(p => p.SymbolTree).Append(SymbolTree)); + } + + private static IEnumerable GetAllDeclarations( + IEnumerable nonMemberDeclarations) + { + var declarations = new Queue(); + declarations.EnqueueRange(nonMemberDeclarations); + while (declarations.TryDequeue(out var declaration)) + { + yield return declaration; + if (declaration is IClassDeclaration syn) + declarations.EnqueueRange(syn.Members); + } + } + } +} diff --git a/Compiler.Semantics/AST/Tree/Parameter.cs b/Compiler.Semantics/AST/Tree/Parameter.cs new file mode 100644 index 00000000..1dd67eae --- /dev/null +++ b/Compiler.Semantics/AST/Tree/Parameter.cs @@ -0,0 +1,16 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal abstract class Parameter : AbstractSyntax, IParameter + { + public bool Unused { get; } + + protected Parameter(TextSpan span, bool unused) + : base(span) + { + Unused = unused; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ResultStatement.cs b/Compiler.Semantics/AST/Tree/ResultStatement.cs new file mode 100644 index 00000000..35669667 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ResultStatement.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ResultStatement : Statement, IResultStatement + { + public IExpression Expression { get; } + + public ResultStatement(TextSpan span, IExpression expression) + : base(span) + { + Expression = expression; + } + + public override string ToString() + { + return $"=> {Expression};"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ReturnExpression.cs b/Compiler.Semantics/AST/Tree/ReturnExpression.cs new file mode 100644 index 00000000..34edefc1 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ReturnExpression.cs @@ -0,0 +1,29 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ReturnExpression : Expression, IReturnExpression + { + public IExpression? Value { get; } + + public ReturnExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression? value) + : base(span, dataType, semantics) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return Value is null ? "return" : $"return {Value}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/SelfExpression.cs b/Compiler.Semantics/AST/Tree/SelfExpression.cs new file mode 100644 index 00000000..747b36ac --- /dev/null +++ b/Compiler.Semantics/AST/Tree/SelfExpression.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class SelfExpression : Expression, ISelfExpression + { + public SelfParameterSymbol ReferencedSymbol { get; } + public bool IsImplicit { get; } + + public SelfExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + SelfParameterSymbol referencedSymbol, + bool isImplicit) + : base(span, dataType, semantics) + { + ReferencedSymbol = referencedSymbol; + IsImplicit = isImplicit; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return IsImplicit ? "⟦self⟧" : "self"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/SelfParameter.cs b/Compiler.Semantics/AST/Tree/SelfParameter.cs new file mode 100644 index 00000000..87fb91ba --- /dev/null +++ b/Compiler.Semantics/AST/Tree/SelfParameter.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class SelfParameter : Parameter, ISelfParameter + { + public SelfParameterSymbol Symbol { get; } + BindingSymbol IBinding.Symbol => Symbol; + public SelfParameter(TextSpan span, SelfParameterSymbol symbol, bool unused) + : base(span, unused) + { + Symbol = symbol; + } + + public override string ToString() + { + var value = "self"; + if (Symbol.IsMutableBinding) value = "mut " + value; + return value; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/ShareExpression.cs b/Compiler.Semantics/AST/Tree/ShareExpression.cs new file mode 100644 index 00000000..929cc074 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/ShareExpression.cs @@ -0,0 +1,33 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class ShareExpression : Expression, IShareExpression + { + public BindingSymbol ReferencedSymbol { get; } + public IExpression Referent { get; } + + public ShareExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + BindingSymbol referencedSymbol, + IExpression referent) + : base(span, dataType, semantics) + { + ReferencedSymbol = referencedSymbol; + Referent = referent; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return $"⟦share⟧ {Referent}"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/Statement.cs b/Compiler.Semantics/AST/Tree/Statement.cs new file mode 100644 index 00000000..5f3017b8 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/Statement.cs @@ -0,0 +1,11 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal abstract class Statement : AbstractSyntax, IStatement + { + protected Statement(TextSpan span) + : base(span) { } + } +} diff --git a/Compiler.Semantics/AST/Tree/StringLiteralExpression.cs b/Compiler.Semantics/AST/Tree/StringLiteralExpression.cs new file mode 100644 index 00000000..2938fb26 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/StringLiteralExpression.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class StringLiteralExpression : LiteralExpression, IStringLiteralExpression + { + public string Value { get; } + + public StringLiteralExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + string value) + : base(span, dataType, semantics) + { + Value = value; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"\"{Value.Escape()}\""; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/UnaryOperatorExpression.cs b/Compiler.Semantics/AST/Tree/UnaryOperatorExpression.cs new file mode 100644 index 00000000..07fd2bd4 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/UnaryOperatorExpression.cs @@ -0,0 +1,42 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class UnaryOperatorExpression : Expression, IUnaryOperatorExpression + { + public UnaryOperatorFixity Fixity { get; } + public UnaryOperator Operator { get; } + public IExpression Operand { get; } + + public UnaryOperatorExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + UnaryOperatorFixity fixity, + UnaryOperator @operator, + IExpression operand) + : base(span, dataType, semantics) + { + Fixity = fixity; + Operator = @operator; + Operand = operand; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Unary; + + public override string ToString() + { + return Fixity switch + { + UnaryOperatorFixity.Prefix => $"{Operator.ToSymbolString()}{Operand.ToGroupedString(ExpressionPrecedence)}", + UnaryOperatorFixity.Postfix => $"{Operand.ToGroupedString(ExpressionPrecedence)}{Operator.ToSymbolString()}", + _ => throw ExhaustiveMatch.Failed(Fixity) + }; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/UnsafeExpression.cs b/Compiler.Semantics/AST/Tree/UnsafeExpression.cs new file mode 100644 index 00000000..a410d6a0 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/UnsafeExpression.cs @@ -0,0 +1,29 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class UnsafeExpression : Expression, IUnsafeExpression + { + public IExpression Expression { get; } + + public UnsafeExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression expression) + : base(span, dataType, semantics) + { + Expression = expression; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Primary; + + public override string ToString() + { + return $"unsafe ({Expression})"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/VariableDeclarationStatement.cs b/Compiler.Semantics/AST/Tree/VariableDeclarationStatement.cs new file mode 100644 index 00000000..14cbfc2d --- /dev/null +++ b/Compiler.Semantics/AST/Tree/VariableDeclarationStatement.cs @@ -0,0 +1,37 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class VariableDeclarationStatement : Statement, IVariableDeclarationStatement + { + public TextSpan NameSpan { get; } + public VariableSymbol Symbol { get; } + BindingSymbol IBinding.Symbol => Symbol; + NamedBindingSymbol ILocalBinding.Symbol => Symbol; + public IExpression? Initializer { get; } + public Promise VariableIsLiveAfter { get; } = new Promise(); + + public VariableDeclarationStatement( + TextSpan span, + TextSpan nameSpan, + VariableSymbol symbol, + IExpression? initializer) + : base(span) + { + NameSpan = nameSpan; + Symbol = symbol; + Initializer = initializer; + } + + public override string ToString() + { + var binding = Symbol.IsMutableBinding ? "var" : "let"; + var declarationNumber = Symbol.DeclarationNumber is null ? "" : "#" + Symbol.DeclarationNumber; + var initializer = Initializer != null ? " = " + Initializer : ""; + return $"{binding} {Symbol.Name}{declarationNumber}: {Symbol.DataType}{initializer};"; + } + } +} diff --git a/Compiler.Semantics/AST/Tree/WhileExpression.cs b/Compiler.Semantics/AST/Tree/WhileExpression.cs new file mode 100644 index 00000000..65a37c43 --- /dev/null +++ b/Compiler.Semantics/AST/Tree/WhileExpression.cs @@ -0,0 +1,32 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree +{ + internal class WhileExpression : Expression, IWhileExpression + { + public IExpression Condition { get; } + public IBlockExpression Block { get; } + + public WhileExpression( + TextSpan span, + DataType dataType, + ExpressionSemantics semantics, + IExpression condition, + IBlockExpression block) + : base(span, dataType, semantics) + { + Condition = condition; + Block = block; + } + + protected override OperatorPrecedence ExpressionPrecedence => OperatorPrecedence.Min; + + public override string ToString() + { + return $"while {Condition} {Block}"; + } + } +} diff --git a/Compiler.Semantics/Basic/BasicAnalyzer.cs b/Compiler.Semantics/Basic/BasicAnalyzer.cs new file mode 100644 index 00000000..7bb48838 --- /dev/null +++ b/Compiler.Semantics/Basic/BasicAnalyzer.cs @@ -0,0 +1,107 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic +{ + /// + /// The basic analyzer does name binding, type checking and constant folding. + /// This class handles declarations and delegates expressions, types etc. to + /// other classes. + /// + /// All basic analysis uses specific terminology to distinguish different + /// aspects of type checking. (The entry method `Check` is an exception. It + /// is named to match other analyzers but performs a resolve.) + /// + /// Terminology: + /// + /// * Resolve - includes type inference and checking + /// * Check - check something has an expected type + /// * Infer - infer what type something has + /// * Evaluate - determine the type for some type syntax + /// + public class BasicAnalyzer + { + private readonly SymbolTreeBuilder symbolTreeBuilder; + private readonly SymbolForest symbolTrees; + private readonly ObjectTypeSymbol? stringSymbol; + private readonly Diagnostics diagnostics; + + private BasicAnalyzer( + SymbolTreeBuilder symbolTreeBuilder, + SymbolForest symbolTrees, + ObjectTypeSymbol? stringSymbol, + Diagnostics diagnostics) + { + this.symbolTreeBuilder = symbolTreeBuilder; + this.symbolTrees = symbolTrees; + this.stringSymbol = stringSymbol; + this.diagnostics = diagnostics; + } + + public static void Check(PackageSyntax package, ObjectTypeSymbol? stringSymbol) + { + var analyzer = new BasicAnalyzer(package.SymbolTree, package.SymbolTrees, stringSymbol, package.Diagnostics); + analyzer.Check(package.AllEntityDeclarations); + } + + private void Check(FixedSet entities) + { + foreach (var entity in entities) + ResolveBodyTypes(entity); + } + + private void ResolveBodyTypes(IEntityDeclarationSyntax declaration) + { + switch (declaration) + { + default: + throw ExhaustiveMatch.Failed(declaration); + case IFunctionDeclarationSyntax function: + { + var resolver = new BasicBodyAnalyzer(function, symbolTreeBuilder, symbolTrees, stringSymbol, diagnostics, + function.Symbol.Result.ReturnDataType); + resolver.ResolveTypes(function.Body); + break; + } + case IAssociatedFunctionDeclarationSyntax associatedFunction: + { + var resolver = new BasicBodyAnalyzer(associatedFunction, symbolTreeBuilder, symbolTrees, + stringSymbol, diagnostics, + associatedFunction.Symbol.Result.ReturnDataType); + resolver.ResolveTypes(associatedFunction.Body); + break; + } + case IConcreteMethodDeclarationSyntax method: + { + var resolver = new BasicBodyAnalyzer(method, symbolTreeBuilder, + symbolTrees, stringSymbol, diagnostics, method.Symbol.Result.ReturnDataType); + resolver.ResolveTypes(method.Body); + break; + } + case IAbstractMethodDeclarationSyntax _: + // has no body, so nothing to resolve + break; + case IFieldDeclarationSyntax field: + if (field.Initializer != null) + { + var resolver = new BasicBodyAnalyzer(field, symbolTreeBuilder, symbolTrees, stringSymbol, diagnostics); + resolver.CheckType(ref field.Initializer, field.Symbol.Result.DataType); + } + break; + case IConstructorDeclarationSyntax constructor: + { + var resolver = new BasicBodyAnalyzer(constructor, symbolTreeBuilder, symbolTrees, stringSymbol, diagnostics, constructor.ImplicitSelfParameter.Symbol.Result.DataType); + resolver.ResolveTypes(constructor.Body); + break; + } + case IClassDeclarationSyntax _: + // body of class is processed as separate items + break; + } + } + } +} diff --git a/Compiler.Semantics/Basic/BasicBodyAnalyzer.cs b/Compiler.Semantics/Basic/BasicBodyAnalyzer.cs new file mode 100644 index 00000000..3bb3ebc6 --- /dev/null +++ b/Compiler.Semantics/Basic/BasicBodyAnalyzer.cs @@ -0,0 +1,1233 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.InferredSyntax; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Errors; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Types; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic +{ + /// + /// Do basic analysis of bodies. + /// + /// + /// Type checking doesn't concern anything with reachability. It only deals + /// with types and reference capabilities. + /// + public class BasicBodyAnalyzer + { + private readonly CodeFile file; + private readonly Symbol containingSymbol; + private readonly SymbolTreeBuilder symbolTreeBuilder; + private readonly SymbolForest symbolTrees; + private readonly ObjectTypeSymbol? stringSymbol; + private readonly Diagnostics diagnostics; + private readonly DataType? returnType; + private readonly TypeResolver typeResolver; + + public BasicBodyAnalyzer( + IEntityDeclarationSyntax containingDeclaration, + SymbolTreeBuilder symbolTreeBuilder, + SymbolForest symbolTrees, + ObjectTypeSymbol? stringSymbol, + Diagnostics diagnostics, + DataType? returnType = null) + { + file = containingDeclaration.File; + containingSymbol = containingDeclaration.Symbol.Result; + this.symbolTreeBuilder = symbolTreeBuilder; + this.stringSymbol = stringSymbol; + this.diagnostics = diagnostics; + this.symbolTrees = symbolTrees; + this.returnType = returnType; + typeResolver = new TypeResolver(file, diagnostics); + } + + public void ResolveTypes(IBodySyntax body) + { + foreach (var statement in body.Statements) + switch (statement) + { + default: + throw ExhaustiveMatch.Failed(statement); + case IVariableDeclarationStatementSyntax variableDeclaration: + ResolveTypes(variableDeclaration); + break; + case IExpressionStatementSyntax expressionStatement: + InferType(ref expressionStatement.Expression); + break; + } + } + + private void ResolveTypes(IStatementSyntax statement) + { + switch (statement) + { + default: + throw ExhaustiveMatch.Failed(statement); + case IVariableDeclarationStatementSyntax variableDeclaration: + ResolveTypes(variableDeclaration); + break; + case IExpressionStatementSyntax expressionStatement: + InferType(ref expressionStatement.Expression); + break; + case IResultStatementSyntax resultStatement: + InferType(ref resultStatement.Expression); + break; + } + } + + private void ResolveTypes(IVariableDeclarationStatementSyntax variableDeclaration) + { + DataType type; + if (variableDeclaration.Type != null) + { + type = typeResolver.Evaluate(variableDeclaration.Type); + CheckType(ref variableDeclaration.Initializer, type); + } + else if (variableDeclaration.Initializer != null) + type = InferDeclarationType(ref variableDeclaration.Initializer, variableDeclaration.InferMutableType); + else + { + diagnostics.Add(TypeError.NotImplemented(file, variableDeclaration.NameSpan, + "Inference of local variable types not implemented")); + type = DataType.Unknown; + } + + if (variableDeclaration.Initializer != null) + { + var initializerType = variableDeclaration.Initializer.DataType ?? throw new InvalidOperationException("Initializer type should be determined"); + + if (!type.IsAssignableFrom(initializerType)) + diagnostics.Add(TypeError.CannotConvert(file, variableDeclaration.Initializer, initializerType, type)); + } + + var symbol = new VariableSymbol((InvocableSymbol)containingSymbol, variableDeclaration.Name, + variableDeclaration.DeclarationNumber.Result, variableDeclaration.IsMutableBinding, type); + variableDeclaration.Symbol.Fulfill(symbol); + symbolTreeBuilder.Add(symbol); + } + + /// + /// Infer the type of a variable declaration from an expression + /// + private DataType InferDeclarationType([NotNull] ref IExpressionSyntax expression, bool inferMutableType) + { + var type = InferType(ref expression); + if (!type.IsKnown) return DataType.Unknown; + type = type.ToNonConstantType(); + type = InsertImplicitConversionIfNeeded(ref expression, type); + + switch (expression) + { + case IMoveExpressionSyntax _: + // If we are explicitly moving then take the actual type + return type; + case IMutateExpressionSyntax _: + { + // If we are explicitly borrowing or moving then take the actual type + if (!(type is ReferenceType referenceType)) + throw new NotImplementedException("Compile error: can't borrow non reference type"); + + throw new NotImplementedException("Mutate expression declaration type"); + //return referenceType.To(ReferenceCapability.Borrowed); + } + default: + { + // We assume immutability on variables unless explicitly stated + if (!inferMutableType) return type.ToReadOnly(); + if (type is ReferenceType referenceType) + { + if (!referenceType.IsMutable) + throw new NotImplementedException("Compile error: can't infer a mutable type"); + + return type; + } + + throw new NotImplementedException("Compile error: can't infer mutability for non reference type"); + } + } + } + + public void CheckType([NotNull] ref IExpressionSyntax? expression, DataType expectedType) + { + if (expression is null) return; + InferType(ref expression); + var actualType = InsertImplicitConversionIfNeeded(ref expression, expectedType); + if (!expectedType.IsAssignableFrom(actualType)) + diagnostics.Add(TypeError.CannotConvert(file, expression, actualType, expectedType)); + } + + /// + /// Create an implicit conversion if allowed and needed + /// + private static DataType InsertImplicitConversionIfNeeded( + ref IExpressionSyntax expression, + DataType expectedType) + { + switch (expectedExpressionType: expectedType, Type: expression.DataType) + { + case (OptionalType targetType, OptionalType expressionType) + when expressionType.Referent is NeverType: + expression = new ImplicitNoneConversionExpression(expression, targetType); + break; + case (OptionalType targetType, /* non-optional type */ _): + // If needed, convert the type to the referent type of the optional type + var type = InsertImplicitConversionIfNeeded(ref expression, targetType.Referent); + if (targetType.Referent.IsAssignableFrom(type)) + expression = new ImplicitOptionalConversionExpression(expression, targetType); + break; + case (FixedSizeIntegerType targetType, FixedSizeIntegerType expressionType): + if (targetType.Bits > expressionType.Bits && (!expressionType.IsSigned || targetType.IsSigned)) + expression = new ImplicitNumericConversionExpression(expression, targetType); + break; + case (FixedSizeIntegerType targetType, IntegerConstantType expressionType): + { + var requireSigned = expressionType.Value < 0; + var bits = expressionType.Value.GetByteCount(!targetType.IsSigned) * 8; + if (targetType.Bits >= bits && (!requireSigned || targetType.IsSigned)) + expression = new ImplicitNumericConversionExpression(expression, targetType); + } + break; + case (PointerSizedIntegerType targetType, IntegerConstantType expressionType): + { + var requireSigned = expressionType.Value < 0; + if (!requireSigned || targetType.IsSigned) + expression = new ImplicitNumericConversionExpression(expression, targetType); + } + break; + case (ObjectType targetType, ObjectType expressionType) + when targetType.IsReadOnly && expressionType.IsMutable: + // TODO if source type is explicitly mutable, issue warning about using `mut` in immutable context + expression = new ImplicitImmutabilityConversionExpression(expression, expressionType.ToReadOnly()); + break; + } + + return expression.DataType!; + } + + /// + /// Infer the type of an expression and assign that type to the expression. + /// + /// A reference to the repression. This is a reference so that the + /// expression can be replaced because the parser can't always correctly determine + /// what kind of expression it is without type information. + /// Whether implicit share expressions should be inserted around + /// bare variable references. + private DataType InferType([NotNull] ref IExpressionSyntax? expression, bool implicitShare = true) + { + switch (expression) + { + default: + throw ExhaustiveMatch.Failed(expression); + case null: + expression = null!; // Trick compiler into allowing it to stay null + return DataType.Unknown; + case IShareExpressionSyntax _: + throw new InvalidOperationException("Share expressions should not be in the AST during basic analysis"); + case IMoveExpressionSyntax exp: + switch (exp.Referent) + { + case INameExpressionSyntax nameExpression: + nameExpression.Semantics = ExpressionSemantics.Acquire; + var type = InferType(ref exp.Referent, false); + switch (type) + { + case ReferenceType referenceType: + if (!referenceType.IsMovable) + { + diagnostics.Add(TypeError.CannotMoveValue(file, exp)); + type = DataType.Unknown; + } + break; + default: + throw new NotImplementedException("Non-moveable type can't be moved"); + } + + exp.ReferencedSymbol.Fulfill(nameExpression.ReferencedSymbol.Result); + exp.Semantics = ExpressionSemantics.Acquire; + return exp.DataType = type; + case IMutateExpressionSyntax _: + throw new NotImplementedException("Raise error about `move mut` expression"); + case IMoveExpressionSyntax _: + throw new NotImplementedException("Raise error about `move move` expression"); + default: + throw new NotImplementedException("Tried to move out of expression type that isn't implemented"); + } + case IMutateExpressionSyntax exp: + switch (exp.Referent) + { + case INameExpressionSyntax nameExpression: + { + nameExpression.Semantics = ExpressionSemantics.Borrow; + var type = InferType(ref exp.Referent, false); + switch (type) + { + case ReferenceType referenceType: + if (!referenceType.IsMutable) + { + diagnostics.Add(TypeError.ExpressionCantBeMutable(file, exp.Referent)); + type = DataType.Unknown; + } + else + throw new NotImplementedException("Mutate expression reference capability"); + //type = referenceType.To(ReferenceCapability.Borrowed); + + break; + default: + throw new NotImplementedException("Non-mutable type can't be borrowed mutably"); + } + + exp.ReferencedSymbol.Fulfill(nameExpression.ReferencedSymbol.Result); + return exp.DataType = type; + } + case IMutateExpressionSyntax _: + throw new NotImplementedException("Raise error about `mut mut` expression"); + case IMoveExpressionSyntax _: + throw new NotImplementedException("Raise error about `mut move` expression"); + default: + throw new NotImplementedException("Tried mutate expression type that isn't implemented"); + } + case IReturnExpressionSyntax exp: + { + if (exp.Value != null) + { + var expectedReturnType = returnType ?? throw new InvalidOperationException("Return statement in constructor"); + InferType(ref exp.Value, false); + // If we return ownership, there can be an implicit move + // otherwise there could be an implicit share or borrow + InsertImplicitActionIfNeeded(ref exp.Value, expectedReturnType, implicitBorrowAllowed: false); + var actualType = InsertImplicitConversionIfNeeded(ref exp.Value, expectedReturnType); + if (!expectedReturnType.IsAssignableFrom(actualType)) + diagnostics.Add(TypeError.CannotConvert(file, exp.Value, actualType, expectedReturnType)); + } + else if (returnType == DataType.Never) + diagnostics.Add(TypeError.CantReturnFromNeverFunction(file, exp.Span)); + else if (returnType != DataType.Void) + diagnostics.Add(TypeError.ReturnExpressionMustHaveValue(file, exp.Span, returnType ?? DataType.Unknown)); + + return exp.DataType = DataType.Never; + } + case IIntegerLiteralExpressionSyntax exp: + return exp.DataType = new IntegerConstantType(exp.Value); + case IStringLiteralExpressionSyntax exp: + return exp.DataType = stringSymbol?.DeclaresDataType ?? (DataType)DataType.Unknown; + case IBoolLiteralExpressionSyntax exp: + return exp.DataType = exp.Value ? DataType.True : DataType.False; + case IBinaryOperatorExpressionSyntax binaryOperatorExpression: + { + var leftType = InferType(ref binaryOperatorExpression.LeftOperand); + var @operator = binaryOperatorExpression.Operator; + var rightType = InferType(ref binaryOperatorExpression.RightOperand); + + // If either is unknown, then we can't know whether there is a a problem. + // Note that the operator could be overloaded + if (leftType == DataType.Unknown || rightType == DataType.Unknown) + return binaryOperatorExpression.DataType = DataType.Unknown; + + bool compatible; + switch (@operator) + { + case BinaryOperator.Plus: + case BinaryOperator.Minus: + case BinaryOperator.Asterisk: + case BinaryOperator.Slash: + compatible = NumericOperatorTypesAreCompatible(ref binaryOperatorExpression.LeftOperand, ref binaryOperatorExpression.RightOperand); + binaryOperatorExpression.DataType = compatible ? leftType : DataType.Unknown; + binaryOperatorExpression.Semantics = ExpressionSemantics.Copy; + break; + case BinaryOperator.EqualsEquals: + case BinaryOperator.NotEqual: + case BinaryOperator.LessThan: + case BinaryOperator.LessThanOrEqual: + case BinaryOperator.GreaterThan: + case BinaryOperator.GreaterThanOrEqual: + compatible = (leftType == DataType.Bool && rightType == DataType.Bool) + || NumericOperatorTypesAreCompatible(ref binaryOperatorExpression.LeftOperand, ref binaryOperatorExpression.RightOperand) + /*|| OperatorOverloadDefined(@operator, binaryOperatorExpression.LeftOperand, ref binaryOperatorExpression.RightOperand)*/; + binaryOperatorExpression.DataType = DataType.Bool; + binaryOperatorExpression.Semantics = ExpressionSemantics.Copy; + break; + case BinaryOperator.And: + case BinaryOperator.Or: + compatible = leftType == DataType.Bool && rightType == DataType.Bool; + binaryOperatorExpression.DataType = DataType.Bool; + binaryOperatorExpression.Semantics = ExpressionSemantics.Copy; + break; + case BinaryOperator.DotDot: + case BinaryOperator.LessThanDotDot: + case BinaryOperator.DotDotLessThan: + case BinaryOperator.LessThanDotDotLessThan: + throw new NotImplementedException("Type analysis of range operators"); + default: + throw ExhaustiveMatch.Failed(@operator); + } + if (!compatible) + diagnostics.Add(TypeError.OperatorCannotBeAppliedToOperandsOfType(file, + binaryOperatorExpression.Span, @operator, leftType, rightType)); + + return binaryOperatorExpression.DataType; + } + case INameExpressionSyntax exp: + { + var type = InferNameType(exp); + // In many contexts, variable names are implicitly shared + if (implicitShare) + type = InsertImplicitShareIfNeeded(ref expression, type); + + // TODO do a more complete generation of expression semantics + if (exp.Semantics is null) + switch (type.Semantics) + { + case TypeSemantics.Copy: + exp.Semantics = ExpressionSemantics.Copy; + break; + case TypeSemantics.Move: + exp.Semantics = ExpressionSemantics.Move; + break; + } + + return type; + } + case IUnaryOperatorExpressionSyntax exp: + { + var @operator = exp.Operator; + switch (@operator) + { + default: + throw ExhaustiveMatch.Failed(@operator); + case UnaryOperator.Not: + CheckType(ref exp.Operand, DataType.Bool); + exp.DataType = DataType.Bool; + break; + case UnaryOperator.Minus: + case UnaryOperator.Plus: + var operandType = InferType(ref exp.Operand); + switch (operandType) + { + case IntegerConstantType integerType: + exp.DataType = integerType; + break; + case FixedSizeIntegerType sizedIntegerType: + exp.DataType = sizedIntegerType; + break; + case UnknownType _: + exp.DataType = DataType.Unknown; + break; + default: + diagnostics.Add(TypeError.OperatorCannotBeAppliedToOperandOfType(file, + exp.Span, @operator, operandType)); + exp.DataType = DataType.Unknown; + break; + } + break; + } + + return exp.DataType; + } + case INewObjectExpressionSyntax exp: + { + var argumentTypes = exp.Arguments.Select(argument => InferType(ref argument.Expression)).ToFixedList(); + // TODO handle named constructors here + var constructingType = typeResolver.Evaluate(exp.Type); + if (!constructingType.IsKnown) + { + diagnostics.Add(NameBindingError.CouldNotBindConstructor(file, exp.Span)); + exp.ReferencedSymbol.Fulfill(null); + return exp.DataType = DataType.Unknown; + } + + // TODO handle null typesymbol + var typeSymbol = exp.Type.ReferencedSymbol.Result ?? throw new InvalidOperationException(); + var classType = (ObjectType)constructingType; + ObjectType constructedType; + var constructorSymbols = symbolTreeBuilder.Children(typeSymbol).OfType().ToFixedList(); + constructorSymbols = ResolveOverload(constructorSymbols, argumentTypes); + switch (constructorSymbols.Count) + { + case 0: + diagnostics.Add(NameBindingError.CouldNotBindConstructor(file, exp.Span)); + exp.ReferencedSymbol.Fulfill(null); + constructedType = classType.ToConstructorReturn(); + break; + case 1: + var constructorSymbol = constructorSymbols.Single(); + exp.ReferencedSymbol.Fulfill(constructorSymbol); + foreach (var (arg, parameterDataType) in exp.Arguments.Zip(constructorSymbol.ParameterDataTypes)) + { + InsertImplicitConversionIfNeeded(ref arg.Expression, parameterDataType); + CheckArgumentTypeCompatibility(parameterDataType, arg.Expression); + } + constructedType = constructorSymbol.ReturnDataType; + break; + default: + diagnostics.Add(NameBindingError.AmbiguousConstructorCall(file, exp.Span)); + exp.ReferencedSymbol.Fulfill(null); + constructedType = classType.ToConstructorReturn(); + break; + } + + + return exp.DataType = constructedType; + } + case IForeachExpressionSyntax exp: + { + var declaredType = typeResolver.Evaluate(exp.Type); + var expressionType = CheckForeachInType(declaredType, ref exp.InExpression); + var variableType = declaredType ?? expressionType; + var symbol = new VariableSymbol((InvocableSymbol)containingSymbol, exp.VariableName, + exp.DeclarationNumber.Result, exp.IsMutableBinding, variableType); + exp.Symbol.Fulfill(symbol); + symbolTreeBuilder.Add(symbol); + + // TODO check the break types + InferBlockType(exp.Block); + // TODO assign correct type to the expression + exp.Semantics = ExpressionSemantics.Void; + return exp.DataType = DataType.Void; + } + case IWhileExpressionSyntax exp: + { + CheckType(ref exp.Condition, DataType.Bool); + InferBlockType(exp.Block); + // TODO assign correct type to the expression + exp.Semantics = ExpressionSemantics.Void; + return exp.DataType = DataType.Void; + } + case ILoopExpressionSyntax exp: + InferBlockType(exp.Block); + // TODO assign correct type to the expression + exp.Semantics = ExpressionSemantics.Void; + return exp.DataType = DataType.Void; + case IQualifiedInvocationExpressionSyntax exp: + return InferMethodInvocationType(exp, ref expression); + case IUnqualifiedInvocationExpressionSyntax exp: + return InferFunctionInvocationType(exp); + case IUnsafeExpressionSyntax exp: + { + exp.DataType = InferType(ref exp.Expression); + exp.Semantics = exp.Expression.Semantics.Assigned(); + return exp.DataType; + } + case IIfExpressionSyntax exp: + CheckType(ref exp.Condition, DataType.Bool); + InferBlockType(exp.ThenBlock); + switch (exp.ElseClause) + { + default: + throw ExhaustiveMatch.Failed(exp.ElseClause); + case null: + break; + case IIfExpressionSyntax _: + case IBlockExpressionSyntax _: + var elseExpression = (IExpressionSyntax)exp.ElseClause; + InferType(ref elseExpression); + //ifExpression.ElseClause = elseExpression; + break; + case IResultStatementSyntax resultStatement: + InferType(ref resultStatement.Expression); + break; + } + // TODO assign a type to the expression + exp.Semantics = ExpressionSemantics.Void; + return exp.DataType = DataType.Void; + case IQualifiedNameExpressionSyntax exp: + { + // Don't wrap the self expression in a share expression for field access + var isSelfField = exp.Context is ISelfExpressionSyntax; + var contextType = InferType(ref exp.Context, !isSelfField); + var member = exp.Field; + var contextSymbol = LookupSymbolForType(contextType); + if (contextSymbol is null) + { + member.ReferencedSymbol.Fulfill(null); + member.DataType = DataType.Unknown; + exp.Semantics ??= ExpressionSemantics.Copy; + return exp.DataType = DataType.Unknown; + } + // TODO Deal with no context symbol + var memberSymbols = symbolTreeBuilder.Children(contextSymbol!).OfType() + .Where(s => s.Name == member.Name).ToFixedList(); + var type = AssignReferencedSymbolAndType(member, memberSymbols); + // In many contexts, variable names are implicitly shared + if (implicitShare) type = InsertImplicitShareIfNeeded(ref expression, type); + + if (exp.Semantics is null) + switch (type.Semantics) + { + case TypeSemantics.Copy: + exp.Semantics = ExpressionSemantics.Copy; + break; + case TypeSemantics.Move: + exp.Semantics = ExpressionSemantics.Move; + break; + } + return exp.DataType = type; + } + case IBreakExpressionSyntax exp: + InferType(ref exp.Value); + return exp.DataType = DataType.Never; + case INextExpressionSyntax exp: + return exp.DataType = DataType.Never; + case IAssignmentExpressionSyntax exp: + { + var left = InferAssignmentTargetType(ref exp.LeftOperand); + InferType(ref exp.RightOperand); + InsertImplicitConversionIfNeeded(ref exp.RightOperand, left); + var right = exp.RightOperand.DataType ?? throw new InvalidOperationException(); + if (!left.IsAssignableFrom(right)) + diagnostics.Add(TypeError.CannotConvert(file, + exp.RightOperand, right, left)); + exp.Semantics = ExpressionSemantics.Void; + return exp.DataType = DataType.Void; + } + case ISelfExpressionSyntax exp: + { + var type = InferSelfType(exp); + if (implicitShare) + type = InsertImplicitShareIfNeeded(ref expression, type); + + if (exp.Semantics is null) + { + if (type is ReferenceType referenceType) + exp.Semantics = referenceType.IsMutable + ? ExpressionSemantics.Borrow : ExpressionSemantics.Share; + else + throw new NotImplementedException("Could not assign semantics to `self` expression"); + } + return type; + } + case INoneLiteralExpressionSyntax exp: + return exp.DataType = DataType.None; + case IImplicitConversionExpressionSyntax _: + throw new Exception("ImplicitConversionExpressions are inserted by BasicExpressionAnalyzer. They should not be present in the AST yet."); + case IBlockExpressionSyntax blockSyntax: + return InferBlockType(blockSyntax); + } + } + + private static DataType InsertImplicitShareIfNeeded([NotNull] ref IExpressionSyntax expression, DataType type) + { + // Value types aren't shared + if (!(type is ReferenceType referenceType)) return type; + + BindingSymbol? referencedSymbol; + switch (expression) + { + case INameExpressionSyntax exp: + exp.Semantics = ExpressionSemantics.Share; + referencedSymbol = exp.ReferencedSymbol.Result; + break; + case ISelfExpressionSyntax exp: + referencedSymbol = exp.ReferencedSymbol.Result; + break; + case IQualifiedNameExpressionSyntax exp: + exp.Field.Semantics = ExpressionSemantics.Share; + exp.Semantics = ExpressionSemantics.Share; + referencedSymbol = exp.ReferencedSymbol.Result; + break; + default: + // implicit share isn't needed around other expressions + return type; + } + + type = referenceType.To(ReferenceCapability.Shared); + + expression = new ImplicitShareExpressionSyntax(expression, type, referencedSymbol); + + return type; + } + + private static void InsertImplicitMutateIfNeeded([NotNull] ref IExpressionSyntax expression, DataType type) + { + // Value types aren't shared + if (!(type is ReferenceType referenceType)) return; + + BindingSymbol? referencedSymbol; + switch (expression) + { + case INameExpressionSyntax exp: + exp.Semantics = ExpressionSemantics.Borrow; + referencedSymbol = exp.ReferencedSymbol.Result; + break; + case ISelfExpressionSyntax exp: + referencedSymbol = exp.ReferencedSymbol.Result; + break; + default: + // implicit borrow isn't needed around other expressions + return; + } + + throw new NotImplementedException("Implicit mutate reference capability"); + //type = referenceType.To(ReferenceCapability.Borrowed); + + //expression = new ImplicitMutateExpressionSyntax(expression, type, referencedSymbol); + } + + private static void InsertImplicitMoveIfNeeded([NotNull] ref IExpressionSyntax expression, DataType type) + { + // Value types aren't moved + if (!(type is ReferenceType referenceType) + // Neither are non-moveable types + || !referenceType.IsMovable) + return; + + if (!(expression is INameExpressionSyntax name)) + // Implicit move not needed + return; + + var referencedSymbol = name.ReferencedSymbol.Result; + expression = new ImplicitMoveSyntax(expression, type, referencedSymbol); + name.Semantics = ExpressionSemantics.Acquire; + expression.Semantics = ExpressionSemantics.Acquire; + } + + private static void InsertImplicitActionIfNeeded([NotNull] ref IExpressionSyntax expression, DataType toType, bool implicitBorrowAllowed) + { + var fromType = expression.DataType.Assigned(); + if (!(fromType is ReferenceType from) || !(toType is ReferenceType to)) return; + + if (@from.IsMovable && to.IsMovable) + InsertImplicitMoveIfNeeded(ref expression, to); + else if (@from.IsReadOnly || to.IsReadOnly) + InsertImplicitShareIfNeeded(ref expression, to.ToReadOnly()); + else if (implicitBorrowAllowed) + InsertImplicitMutateIfNeeded(ref expression, to); + } + + private DataType InferAssignmentTargetType([NotNull] ref IAssignableExpressionSyntax expression) + { + switch (expression) + { + default: + throw ExhaustiveMatch.Failed(expression); + case IQualifiedNameExpressionSyntax exp: + // Don't wrap the self expression in a share expression for field access + var isSelfField = exp.Context is ISelfExpressionSyntax; + var contextType = InferType(ref exp.Context, !isSelfField); + var member = exp.Field; + var contextSymbol = LookupSymbolForType(contextType); + // TODO Deal with no context symbol + var memberSymbols = symbolTreeBuilder.Children(contextSymbol!).OfType().Where(s => s.Name == member.Name).ToFixedList(); + var type = AssignReferencedSymbolAndType(member, memberSymbols); + exp.Field.Semantics ??= ExpressionSemantics.CreateReference; + exp.Semantics = exp.Field.Semantics.Assigned(); + return exp.DataType = type; + case INameExpressionSyntax exp: + exp.Semantics = ExpressionSemantics.CreateReference; + return InferNameType(exp); + } + } + + private DataType InferMethodInvocationType( + IQualifiedInvocationExpressionSyntax qualifiedInvocation, + ref IExpressionSyntax expression) + { + // This could actually be any of the following since the parser can't distinguish them: + // * Associated function invocation + // * Namespaced function invocation + // * Method invocation + // First we need to distinguish those. + var contextName = MethodContextAsName(qualifiedInvocation.Context); + if (contextName != null) + { + var contextSymbols = qualifiedInvocation.ContainingLexicalScope.Lookup(contextName.Segments[0]).Select(p => p.Result); + foreach (var name in contextName.Segments.Skip(1)) + contextSymbols = contextSymbols.SelectMany(c => symbolTrees.Children(c).Where(s => s.Name == name)); + + var functionSymbols = contextSymbols + .SelectMany(c => symbolTrees + .Children(c).OfType() + .Where(s => s.Name == qualifiedInvocation.InvokedName)) + .ToFixedSet(); + if (functionSymbols.Any()) + { + // It is a namespaced or associated function invocation, modify the tree + var functionInvocation = new FunctionInvocationExpressionSyntax( + qualifiedInvocation.Span, + contextName, + qualifiedInvocation.InvokedName, + qualifiedInvocation.InvokedNameSpan, + qualifiedInvocation.Arguments, + functionSymbols); + + expression = functionInvocation; + return InferFunctionInvocationType(functionInvocation); + } + } + + var argumentTypes = qualifiedInvocation.Arguments.Select(argument => InferType(ref argument.Expression)).ToFixedList(); + var contextType = InferType(ref qualifiedInvocation.Context, false); + // If it is unknown, we already reported an error + if (contextType == DataType.Unknown) + { + qualifiedInvocation.Semantics = ExpressionSemantics.Never; + return qualifiedInvocation.DataType = DataType.Unknown; + }; + + var contextSymbol = LookupSymbolForType(contextType); + var methodSymbols = symbolTrees.Children(contextSymbol!).OfType() + .Where(s => s.Name == qualifiedInvocation.InvokedName).ToFixedList(); + methodSymbols = ResolveMethodOverload(contextType, methodSymbols, argumentTypes); + + switch (methodSymbols.Count) + { + case 0: + diagnostics.Add(NameBindingError.CouldNotBindMethod(file, qualifiedInvocation.Span)); + qualifiedInvocation.ReferencedSymbol.Fulfill(null); + qualifiedInvocation.DataType = DataType.Unknown; + break; + case 1: + var methodSymbol = methodSymbols.Single(); + qualifiedInvocation.ReferencedSymbol.Fulfill(methodSymbol); + + var selfParamType = methodSymbol.SelfDataType; + InsertImplicitActionIfNeeded(ref qualifiedInvocation.Context, selfParamType, implicitBorrowAllowed: true); + + InsertImplicitConversionIfNeeded(ref qualifiedInvocation.Context, selfParamType); + CheckArgumentTypeCompatibility(selfParamType, qualifiedInvocation.Context); + + foreach (var (arg, type) in qualifiedInvocation.Arguments + .Zip(methodSymbol.ParameterDataTypes)) + { + InsertImplicitConversionIfNeeded(ref arg.Expression, type); + CheckArgumentTypeCompatibility(type, arg.Expression); + } + + qualifiedInvocation.DataType = methodSymbol.ReturnDataType; + AssignInvocationSemantics(qualifiedInvocation, methodSymbol.ReturnDataType); + break; + default: + diagnostics.Add(NameBindingError.AmbiguousMethodCall(file, qualifiedInvocation.Span)); + qualifiedInvocation.ReferencedSymbol.Fulfill(null); + qualifiedInvocation.DataType = DataType.Unknown; + break; + } + + return qualifiedInvocation.DataType; + } + + /// + /// Used on the target of a method invocation to see if it is could be the name of a namespace or class + /// + /// A name if the expression is a qualified name, otherwise null + private static NamespaceName? MethodContextAsName(IExpressionSyntax expression) + { + return expression switch + { + IQualifiedNameExpressionSyntax memberAccess => + // if implicit self + memberAccess.Context is null + ? null + : MethodContextAsName(memberAccess.Context)?.Qualify(memberAccess.Field.Name!), + INameExpressionSyntax nameExpression => nameExpression.Name!, + _ => null + }; + } + + private DataType InferFunctionInvocationType( + IUnqualifiedInvocationExpressionSyntax unqualifiedInvocationExpression) + { + var argumentTypes = unqualifiedInvocationExpression.Arguments.Select(argument => InferType(ref argument.Expression)).ToFixedList(); + var functionSymbols = unqualifiedInvocationExpression.LookupInContainingScope().Select(s => s.Result).ToFixedList(); + functionSymbols = ResolveOverload(functionSymbols, argumentTypes); + switch (functionSymbols.Count) + { + case 0: + diagnostics.Add(NameBindingError.CouldNotBindFunction(file, unqualifiedInvocationExpression.Span)); + unqualifiedInvocationExpression.ReferencedSymbol.Fulfill(null); + unqualifiedInvocationExpression.DataType = DataType.Unknown; + unqualifiedInvocationExpression.Semantics = ExpressionSemantics.Never; + break; + case 1: + var functionSymbol = functionSymbols.Single(); + unqualifiedInvocationExpression.ReferencedSymbol.Fulfill(functionSymbol); + foreach (var (arg, parameterDataType) in unqualifiedInvocationExpression.Arguments.Zip(functionSymbol.ParameterDataTypes)) + { + InsertImplicitConversionIfNeeded(ref arg.Expression, parameterDataType); + CheckArgumentTypeCompatibility(parameterDataType, arg.Expression); + } + + unqualifiedInvocationExpression.DataType = functionSymbol.ReturnDataType; + AssignInvocationSemantics(unqualifiedInvocationExpression, functionSymbol.ReturnDataType); + break; + default: + diagnostics.Add(NameBindingError.AmbiguousFunctionCall(file, unqualifiedInvocationExpression.Span)); + unqualifiedInvocationExpression.ReferencedSymbol.Fulfill(null); + unqualifiedInvocationExpression.DataType = DataType.Unknown; + unqualifiedInvocationExpression.Semantics = ExpressionSemantics.Never; + break; + } + return unqualifiedInvocationExpression.DataType; + } + + private static void AssignInvocationSemantics( + IInvocationExpressionSyntax invocationExpression, + DataType type) + { + switch (type.Semantics) + { + default: + throw ExhaustiveMatch.Failed(type.Semantics); + case TypeSemantics.Void: + invocationExpression.Semantics = ExpressionSemantics.Void; + break; + case TypeSemantics.Move: + invocationExpression.Semantics = ExpressionSemantics.Move; + break; + case TypeSemantics.Copy: + invocationExpression.Semantics = ExpressionSemantics.Copy; + break; + case TypeSemantics.Never: + invocationExpression.Semantics = ExpressionSemantics.Never; + break; + case TypeSemantics.Reference: + var referenceType = (ReferenceType)type; + throw new NotImplementedException("AssignInvocationSemantics for reference types"); + //if (referenceType.ReferenceCapability.CanBeAcquired()) + // invocationExpression.Semantics = ExpressionSemantics.Acquire; + //else if (referenceType.IsMutable) + // invocationExpression.Semantics = ExpressionSemantics.Borrow; + //else + // invocationExpression.Semantics = ExpressionSemantics.Share; + //break; + } + } + + private DataType InferBlockType(IBlockOrResultSyntax blockOrResult) + { + switch (blockOrResult) + { + default: + throw ExhaustiveMatch.Failed(blockOrResult); + case IBlockExpressionSyntax block: + foreach (var statement in block.Statements) + ResolveTypes(statement); + + block.Semantics = ExpressionSemantics.Void; + return block.DataType = DataType.Void; // TODO assign the correct type to the block + case IResultStatementSyntax result: + InferType(ref result.Expression); + return result.Expression.DataType!; + } + } + + public DataType InferNameType(INameExpressionSyntax nameExpression) + { + if (nameExpression.Name is null) + { + // Name unknown, no error + nameExpression.ReferencedSymbol.Fulfill(null); + return nameExpression.DataType = DataType.Unknown; + } + + DataType? type; + var symbols = nameExpression.LookupInContainingScope().ToFixedList(); + switch (symbols.Count) + { + case 0: + diagnostics.Add(NameBindingError.CouldNotBindName(file, nameExpression.Span)); + nameExpression.ReferencedSymbol.Fulfill(null); + type = DataType.Unknown; + break; + case 1: + var symbol = symbols.Single().Result; + // TODO fulfill the ReferencedSymbol once we have a correct symbol + nameExpression.ReferencedSymbol.Fulfill(symbol); + type = symbol.DataType; + break; + default: + diagnostics.Add(NameBindingError.AmbiguousName(file, nameExpression.Span)); + nameExpression.ReferencedSymbol.Fulfill(null); + type = DataType.Unknown; + break; + } + + return nameExpression.DataType = type!; + } + + private DataType InferSelfType(ISelfExpressionSyntax selfExpression) + { + DataType type; + switch (containingSymbol) + { + default: + throw ExhaustiveMatch.Failed(containingSymbol); + case MethodSymbol _: + case ConstructorSymbol _: + var symbols = symbolTreeBuilder.Children(containingSymbol).OfType().ToList(); + switch (symbols.Count) + { + case 0: + diagnostics.Add(NameBindingError.CouldNotBindName(file, selfExpression.Span)); + type = DataType.Unknown; + selfExpression.ReferencedSymbol.Fulfill(null); + break; + case 1: + var symbol = symbols.Single(); + type = symbol.DataType; + selfExpression.ReferencedSymbol.Fulfill(symbol); + break; + default: + diagnostics.Add(NameBindingError.AmbiguousName(file, selfExpression.Span)); + type = DataType.Unknown; + selfExpression.ReferencedSymbol.Fulfill(null); + break; + } + break; + case FunctionSymbol _: + diagnostics.Add(selfExpression.IsImplicit + ? SemanticError.ImplicitSelfOutsideMethod(file, selfExpression.Span) + : SemanticError.SelfOutsideMethod(file, selfExpression.Span)); + type = DataType.Unknown; + selfExpression.ReferencedSymbol.Fulfill(null); + break; + case NamespaceOrPackageSymbol _: + case BindingSymbol _: + case TypeSymbol _: + throw new InvalidOperationException("Invalid containing symbol for body"); + } + + return selfExpression.DataType = type; + } + + /// + /// Eventually, a `foreach` `in` expression will just be a regular expression. However, at the + /// moment, there isn't enough of the language to implement range expressions. So this + /// check handles range expressions in the specific case of `foreach` only. It marks them + /// as having the same type as the range endpoints. + /// + private DataType CheckForeachInType(DataType? declaredType, ref IExpressionSyntax inExpression) + { + switch (inExpression) + { + case IBinaryOperatorExpressionSyntax binaryExpression + when binaryExpression.Operator == BinaryOperator.DotDot + || binaryExpression.Operator == BinaryOperator.LessThanDotDot + || binaryExpression.Operator == BinaryOperator.DotDotLessThan + || binaryExpression.Operator == BinaryOperator.LessThanDotDotLessThan: + var leftType = InferType(ref binaryExpression.LeftOperand); + InferType(ref binaryExpression.RightOperand); + if (declaredType != null) + { + leftType = InsertImplicitConversionIfNeeded(ref binaryExpression.LeftOperand, declaredType); + InsertImplicitConversionIfNeeded(ref binaryExpression.RightOperand, declaredType); + } + + inExpression.Semantics = ExpressionSemantics.Copy; // Treat ranges as structs + return inExpression.DataType = leftType; + default: + return InferType(ref inExpression); + } + } + + private void CheckArgumentTypeCompatibility(DataType type, IExpressionSyntax arg) + { + var fromType = arg.DataType ?? throw new ArgumentException("argument must have a type"); + if (!type.IsAssignableFrom(fromType)) + diagnostics.Add(TypeError.CannotConvert(file, arg, fromType, type)); + } + + private DataType AssignReferencedSymbolAndType( + INameExpressionSyntax exp, + FixedList matchingSymbols) + { + switch (matchingSymbols.Count) + { + case 0: + diagnostics.Add(NameBindingError.CouldNotBindMember(file, exp.Span)); + exp.Semantics = ExpressionSemantics.Never; + exp.ReferencedSymbol.Fulfill(null); + return exp.DataType = DataType.Unknown; + case 1: + var memberSymbol = matchingSymbols.Single(); + switch (memberSymbol.DataType.Semantics) + { + default: + throw ExhaustiveMatch.Failed(memberSymbol.DataType.Semantics); + case TypeSemantics.Copy: + exp.Semantics = ExpressionSemantics.Copy; + break; + case TypeSemantics.Never: + exp.Semantics = ExpressionSemantics.Never; + break; + case TypeSemantics.Reference: + // Needs to be assigned based on share/borrow expression + break; + case TypeSemantics.Move: + throw new InvalidOperationException("Can't move out of field"); + case TypeSemantics.Void: + throw new InvalidOperationException("Can't assign semantics to void field"); + } + + exp.ReferencedSymbol.Fulfill(memberSymbol); + return exp.DataType = memberSymbol.DataType; + default: + diagnostics.Add(NameBindingError.AmbiguousName(file, exp.Span)); + exp.Semantics = ExpressionSemantics.Never; + exp.ReferencedSymbol.Fulfill(null); + return exp.DataType = DataType.Unknown; + } + } + + private static bool NumericOperatorTypesAreCompatible( + ref IExpressionSyntax leftOperand, + ref IExpressionSyntax rightOperand) + { + var leftType = leftOperand.DataType; + switch (leftType) + { + default: + // In theory we could just make the default false, but this + // way we are forced to note exactly which types this doesn't work on. + throw ExhaustiveMatch.Failed(leftType); + case IntegerConstantType _: + // TODO may need to promote based on size + throw new NotImplementedException(); + //return !IsIntegerType(rightType); + case PointerSizedIntegerType integerType: + // TODO this isn't right we might need to convert either of them + InsertImplicitConversionIfNeeded(ref rightOperand, integerType); + return rightOperand.DataType is PointerSizedIntegerType; + case FixedSizeIntegerType integerType: + // TODO this isn't right we might need to convert either of them + InsertImplicitConversionIfNeeded(ref rightOperand, integerType); + return rightOperand.DataType is FixedSizeIntegerType; + case OptionalType _: + throw new NotImplementedException("Trying to do math on optional type"); + case NeverType _: + case UnknownType _: + return true; + case ReferenceType _: + case BoolType _: + case VoidType _: // This might need a special error message + return false; + } + } + + //private bool OperatorOverloadDefined(BinaryOperator @operator, ExpressionSyntax leftOperand, ref ExpressionSyntax rightOperand) + //{ + // // all other operators are not yet implemented + // if (@operator != BinaryOperator.EqualsEquals) + // return false; + + // if (!(leftOperand.Type is UserObjectType userObjectType)) + // return false; + // var equalityOperators = userObjectType.Symbol.Lookup(SpecialName.OperatorEquals); + // if (equalityOperators.Count != 1) + // return false; + // var equalityOperator = equalityOperators.Single(); + // if (!(equalityOperator.Type is FunctionType functionType) || functionType.Arity != 2) + // return false; + // InsertImplicitConversionIfNeeded(ref rightOperand, functionType.ParameterTypes[1]); + // return IsAssignableFrom(functionType.ParameterTypes[1], rightOperand.Type); + + //} + + // Re-expose type analyzer to BasicAnalyzer + public DataType EvaluateType(ITypeSyntax typeSyntax) + { + return typeResolver.Evaluate(typeSyntax); + } + + //private void InferExpressionTypeInInvocation(ExpressionSyntax callee, FixedList argumentTypes) + //{ + // switch (callee) + // { + // case NameSyntax identifierName: + // { + // var symbols = identifierName.LookupInContainingScope(); + // symbols = ResolveOverload(symbols, null, argumentTypes); + // AssignReferencedSymbolAndType(identifierName, symbols); + // } + // break; + // case MemberAccessExpressionSyntax memberAccess: + // { + // var left = InferExpressionType(ref memberAccess.Expression); + // var containingSymbol = GetSymbolForType(left); + // var symbols = containingSymbol.Lookup(memberAccess.Member.Name); + // symbols = ResolveOverload(symbols, left, argumentTypes); + // memberAccess.Type = AssignReferencedSymbolAndType(memberAccess.Member, symbols); + // } + // break; + // default: + // throw new NotImplementedException(); + // } + //} + + private static FixedList ResolveOverload( + FixedList symbols, + FixedList argumentTypes) + where TSymbol : InvocableSymbol + { + // Filter down to symbols that could possible match + symbols = symbols.Where(s => + { + if (s.Arity != argumentTypes.Count) return false; + // TODO check compatibility over argument types + return true; + }).ToFixedList(); + // TODO Select most specific match + return symbols; + } + + private static FixedList ResolveMethodOverload( + DataType selfType, + FixedList symbols, + FixedList argumentTypes) + { + // Filter down to symbols that could possible match + symbols = symbols.Where(s => + { + if (s.Arity != argumentTypes.Count) return false; + // TODO check compatibility of self type + _ = selfType; + // TODO check compatibility over argument types + + return true; + }).ToFixedList(); + + // TODO Select most specific match + return symbols; + } + + private TypeSymbol? LookupSymbolForType(DataType dataType) + { + return dataType switch + { + UnknownType _ => null, + ObjectType objectType => LookupSymbolForType(objectType), + IntegerType integerType => symbolTrees.PrimitiveSymbolTree + .GlobalSymbols + .OfType() + .Single(s => s.DeclaresDataType == integerType), + _ => throw new NotImplementedException( + $"{nameof(LookupSymbolForType)} not implemented for {dataType.GetType().Name}") + }; + } + + private TypeSymbol? LookupSymbolForType(ObjectType objectType) + { + + var contextSymbols = symbolTrees.Packages.SafeCast(); + foreach (var name in objectType.ContainingNamespace.Segments) + { + contextSymbols = contextSymbols.SelectMany(c => symbolTrees.Children(c)) + .Where(s => s.Name == name); + } + + return contextSymbols.SelectMany(c => symbolTrees.Children(c)).OfType() + .Single(s => s.Name == objectType.Name); + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitConversionExpression.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitConversionExpression.cs new file mode 100644 index 00000000..523cf69f --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitConversionExpression.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + /// + /// The implicit conversions do not have "Syntax" in their name, because + /// there is no corresponding syntax. They are never generated by the parser, + /// but are inserted during type checking. + /// + internal abstract class ImplicitConversionExpression : ImplicitExpressionSyntax, IImplicitConversionExpressionSyntax + { + public IExpressionSyntax Expression { [DebuggerHidden] get; } + DataType IImplicitConversionExpressionSyntax.DataType { [DebuggerHidden] get => DataType!; } + + protected ImplicitConversionExpression( + TextSpan span, + DataType convertToType, + IExpressionSyntax expression, + ExpressionSemantics semantics) + : base(convertToType, span, semantics) + { + Expression = expression; + } + + public abstract override string ToString(); + + public string ToGroupedString(OperatorPrecedence surroundingPrecedence) + { + return ToString(); + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitExpressionSyntax.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitExpressionSyntax.cs new file mode 100644 index 00000000..48089f86 --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitExpressionSyntax.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + internal abstract class ImplicitExpressionSyntax + { + [SuppressMessage("Microsoft.Performance", "CA1822: Mark members as static", + Justification = "This IS instance data!")] + public TextSpan Span { [DebuggerStepThrough] get; } + + private readonly DataType dataType; + [DisallowNull] + public DataType? DataType + { + [DebuggerStepThrough] + get => dataType; + // Type is always set by the constructor, so it can't be set again + set => throw new InvalidOperationException("Can't set type repeatedly"); + } + + private ExpressionSemantics? semantics; + [DisallowNull] + public ExpressionSemantics? Semantics + { + [DebuggerStepThrough] + get => semantics; + set + { + if (semantics != null) + throw new InvalidOperationException("Can't set semantics repeatedly"); + semantics = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + protected ImplicitExpressionSyntax(DataType dataType, TextSpan span, ExpressionSemantics? semantics = null) + { + Span = span; + this.dataType = dataType ?? throw new ArgumentNullException(nameof(dataType)); + this.semantics = semantics; + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitImmutabilityConversionExpression.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitImmutabilityConversionExpression.cs new file mode 100644 index 00000000..b7f234e7 --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitImmutabilityConversionExpression.cs @@ -0,0 +1,25 @@ +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + // TODO No error is reported if IImplicitImmutabilityConversionExpression is missing + internal class ImplicitImmutabilityConversionExpression : ImplicitConversionExpression, IImplicitImmutabilityConversionExpressionSyntax + { + public ObjectType ConvertToType { get; } + + public ImplicitImmutabilityConversionExpression( + IExpressionSyntax expression, + ObjectType convertToType) + : base(expression.Span, convertToType, expression, expression.Semantics.Assigned()) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{Expression.ToGroupedString(OperatorPrecedence.Min)} ⟦as ⟦immutable⟧⟧"; + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitMoveSyntax.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitMoveSyntax.cs new file mode 100644 index 00000000..095c75bd --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitMoveSyntax.cs @@ -0,0 +1,41 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + internal class ImplicitMoveSyntax : ImplicitExpressionSyntax, IMoveExpressionSyntax + { + [SuppressMessage("Style", "IDE0044:Add readonly modifier", + Justification = "Can't be readonly because a reference to it is exposed")] + private IExpressionSyntax referent; + public ref IExpressionSyntax Referent + { + [DebuggerStepThrough] + get => ref referent; + } + + public Promise ReferencedSymbol { get; } + + public ImplicitMoveSyntax(IExpressionSyntax referent, DataType type, BindingSymbol? referencedSymbol) + : base(type, referent.Span) + { + this.referent = referent; + ReferencedSymbol = Promise.ForValue(referencedSymbol); + } + + public override string ToString() + { + return $"⟦move⟧ {Referent}"; + } + + public string ToGroupedString(OperatorPrecedence surroundingPrecedence) + { + return $"({ToString()})"; + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitMutateExpressionSyntax.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitMutateExpressionSyntax.cs new file mode 100644 index 00000000..4cb716da --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitMutateExpressionSyntax.cs @@ -0,0 +1,42 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + internal class ImplicitMutateExpressionSyntax : ImplicitExpressionSyntax, IMutateExpressionSyntax + { + [SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = + "Can't be readonly because a reference to it is exposed")] + private IExpressionSyntax referent; + + public ref IExpressionSyntax Referent + { + [DebuggerStepThrough] + get => ref referent; + } + public Promise ReferencedSymbol { get; } + + public ImplicitMutateExpressionSyntax(IExpressionSyntax referent, DataType type, BindingSymbol? referencedSymbol) + : base(type, referent.Span, ExpressionSemantics.Borrow) + { + this.referent = referent; + ReferencedSymbol = Promise.ForValue(referencedSymbol); + } + + public override string ToString() + { + return $"⟦borrow⟧ {Referent}"; + } + + public string ToGroupedString(OperatorPrecedence surroundingPrecedence) + { + return $"({this})"; + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitNoneConversionExpression.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitNoneConversionExpression.cs new file mode 100644 index 00000000..23970ac3 --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitNoneConversionExpression.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + /// + /// An implicit conversion from `none` of type `never?` to some other optional + /// type. For example, a conversion to `int?` + /// + internal class ImplicitNoneConversionExpression : ImplicitConversionExpression, IImplicitNoneConversionExpressionSyntax + { + public OptionalType ConvertToType { get; } + + public ImplicitNoneConversionExpression( + IExpressionSyntax expression, + OptionalType convertToType) + // We can always copy the `none` literal + : base(expression.Span, convertToType, expression, ExpressionSemantics.Copy) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{Expression.ToGroupedString(OperatorPrecedence.Min)} ⟦as {ConvertToType}⟧"; + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitNumericConversionExpression.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitNumericConversionExpression.cs new file mode 100644 index 00000000..a79422bc --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitNumericConversionExpression.cs @@ -0,0 +1,25 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + internal class ImplicitNumericConversionExpression : ImplicitConversionExpression, IImplicitNumericConversionExpressionSyntax + { + public NumericType ConvertToType { get; } + + public ImplicitNumericConversionExpression( + IExpressionSyntax expression, + NumericType convertToType) + : base(expression.Span, convertToType, expression, ExpressionSemantics.Copy) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{Expression.ToGroupedString(OperatorPrecedence.Min)} ⟦as {ConvertToType}⟧"; + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitOptionalConversionExpression.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitOptionalConversionExpression.cs new file mode 100644 index 00000000..023d4cee --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitOptionalConversionExpression.cs @@ -0,0 +1,27 @@ +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + /// + /// An implicit conversion from `T` to `T?` + /// + internal class ImplicitOptionalConversionExpression : ImplicitConversionExpression, IImplicitOptionalConversionExpressionSyntax + { + public OptionalType ConvertToType { get; } + + public ImplicitOptionalConversionExpression( + IExpressionSyntax expression, + OptionalType convertToType) + : base(expression.Span, convertToType, expression, expression.Semantics.Assigned()) + { + ConvertToType = convertToType; + } + + public override string ToString() + { + return $"{Expression.ToGroupedString(OperatorPrecedence.Min)} ⟦as {ConvertToType}⟧"; + } + } +} diff --git a/Compiler.Semantics/Basic/ImplicitOperations/ImplicitShareExpressionSyntax.cs b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitShareExpressionSyntax.cs new file mode 100644 index 00000000..30f910fb --- /dev/null +++ b/Compiler.Semantics/Basic/ImplicitOperations/ImplicitShareExpressionSyntax.cs @@ -0,0 +1,41 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.ImplicitOperations +{ + internal class ImplicitShareExpressionSyntax : ImplicitExpressionSyntax, IShareExpressionSyntax + { + [SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = + "Can't be readonly because a reference to it is exposed")] + private IExpressionSyntax referent; + public ref IExpressionSyntax Referent + { + [DebuggerStepThrough] + get => ref referent; + } + public Promise ReferencedSymbol { get; } + + public ImplicitShareExpressionSyntax(IExpressionSyntax referent, DataType type, BindingSymbol? referencedSymbol) + : base(type, referent.Span, ExpressionSemantics.Share) + { + this.referent = referent; + ReferencedSymbol = Promise.ForValue(referencedSymbol); + } + + public override string ToString() + { + return $"⟦share⟧ {Referent}"; + } + + public string ToGroupedString(OperatorPrecedence surroundingPrecedence) + { + return $"({ToString()})"; + } + } +} diff --git a/Compiler.Semantics/Basic/InferredSyntax/FunctionInvocationExpressionSyntax.cs b/Compiler.Semantics/Basic/InferredSyntax/FunctionInvocationExpressionSyntax.cs new file mode 100644 index 00000000..8bae9b01 --- /dev/null +++ b/Compiler.Semantics/Basic/InferredSyntax/FunctionInvocationExpressionSyntax.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Basic.InferredSyntax +{ + internal class FunctionInvocationExpressionSyntax : IUnqualifiedInvocationExpressionSyntax + { + private readonly FixedSet possibleReferents; + public LexicalScope ContainingLexicalScope + { + [DebuggerStepThrough] + get => throw new NotSupportedException($"{nameof(ContainingLexicalScope)} not supported on inferred {nameof(FunctionInvocationExpressionSyntax)}"); + [DebuggerStepThrough] + set => throw new NotSupportedException($"{nameof(ContainingLexicalScope)} not supported on inferred {nameof(FunctionInvocationExpressionSyntax)}"); + } + public TextSpan Span { get; } + public NamespaceName Namespace { get; } + public Name InvokedName { get; } + public TextSpan InvokedNameSpan { get; } + public FixedList Arguments { get; } + public Promise ReferencedSymbol { get; } = new Promise(); + IPromise IInvocationExpressionSyntax.ReferencedSymbol => ReferencedSymbol; + + private DataType? dataType; + [DisallowNull] + public DataType? DataType + { + get => dataType; + set + { + if (dataType != null) + throw new InvalidOperationException("Can't set type repeatedly"); + dataType = value ?? throw new ArgumentNullException(nameof(DataType), "Can't set type to null"); + } + } + + private ExpressionSemantics? valueSemantics; + + [DisallowNull] + public ExpressionSemantics? Semantics + { + [DebuggerStepThrough] + get => valueSemantics; + set + { + if (valueSemantics != null) + throw new InvalidOperationException("Can't set semantics repeatedly"); + valueSemantics = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + public FunctionInvocationExpressionSyntax( + TextSpan span, + NamespaceName ns, + Name invokedName, + TextSpan invokedNameSpan, + FixedList arguments, + FixedSet possibleReferents) + { + this.possibleReferents = possibleReferents; + InvokedName = invokedName; + InvokedNameSpan = invokedNameSpan; + Span = span; + Namespace = ns; + Arguments = arguments; + } + + public IEnumerable> LookupInContainingScope() + { + return possibleReferents.Select(AcyclicPromise.ForValue); + } + + public override string ToString() + { + return Namespace == NamespaceName.Global + ? $"{InvokedName}({string.Join(", ", Arguments)})" + : $"{Namespace}.{InvokedName}({string.Join(", ", Arguments)})"; + } + + public string ToGroupedString(OperatorPrecedence surroundingPrecedence) + { + return ToString(); + } + } +} diff --git a/Compiler.Semantics/Compiler.Semantics.csproj b/Compiler.Semantics/Compiler.Semantics.csproj new file mode 100644 index 00000000..27253318 --- /dev/null +++ b/Compiler.Semantics/Compiler.Semantics.csproj @@ -0,0 +1,41 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Semantics + Azoth.Tools.Bootstrap.Compiler.Semantics + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/Compiler.Semantics/DataFlow/BackwardDataFlowAnalyzer.cs b/Compiler.Semantics/DataFlow/BackwardDataFlowAnalyzer.cs new file mode 100644 index 00000000..e8d8340e --- /dev/null +++ b/Compiler.Semantics/DataFlow/BackwardDataFlowAnalyzer.cs @@ -0,0 +1,74 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.AST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + internal class BackwardDataFlowAnalyzer : AbstractSyntaxWalker + where TState : class + { + private readonly IBackwardDataFlowAnalyzer strategy; + private readonly ISymbolTree symbolTree; + private readonly Diagnostics diagnostics; + + public BackwardDataFlowAnalyzer( + IBackwardDataFlowAnalyzer strategy, + ISymbolTree symbolTree, + Diagnostics diagnostics) + { + this.strategy = strategy; + this.symbolTree = symbolTree; + this.diagnostics = diagnostics; + } + + private IBackwardDataFlowAnalysis? checker; + private TState? currentState; + + public void Check(IExecutableDeclaration syntax) + { + Walk(syntax, false); + } + + protected override void WalkNonNull(IAbstractSyntax syntax, bool isLValue) + { + // TODO this doesn't handle loops correctly + switch (syntax) + { + case IConcreteInvocableDeclaration exp: + checker = strategy.BeginAnalysis(exp, symbolTree, diagnostics); + currentState = checker.StartState(); + break; + case IAssignmentExpression exp: + WalkNonNull(exp.RightOperand, false); + WalkNonNull(exp.LeftOperand, true); + currentState = checker!.Assignment(exp, currentState!); + return; + case INameExpression exp: + if (isLValue) return; // ignore + currentState = checker!.IdentifierName(exp, currentState!); + return; + case IVariableDeclarationStatement exp: + currentState = checker!.VariableDeclaration(exp, currentState!); + WalkChildrenInReverse(exp, false); + return; + case IForeachExpression exp: + WalkNonNull(exp.Block, isLValue); + currentState = checker!.VariableDeclaration(exp, currentState!); + WalkNonNull(exp.InExpression, isLValue); + return; + case IFieldAccessExpression exp: + WalkNonNull(exp.Context, isLValue); + // Don't walk the field name, it shouldn't be treated as a variable + return; + case ITypeSyntax _: + return; + case IDeclarationSyntax _: + throw new InvalidOperationException($"Analyze data flow of declaration of type {syntax.GetType().Name}"); + } + WalkChildrenInReverse(syntax, isLValue); + } + } +} diff --git a/Compiler.Semantics/DataFlow/DataFlowAnalysis.cs b/Compiler.Semantics/DataFlow/DataFlowAnalysis.cs new file mode 100644 index 00000000..827a3b88 --- /dev/null +++ b/Compiler.Semantics/DataFlow/DataFlowAnalysis.cs @@ -0,0 +1,56 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + /// + /// An abstract data flow analysis. The specific analysis performed is + /// determined by the data flow analysis strategy. + /// + /// Notes: + /// + /// The Roslyn compiler calculates data flow by visiting through the tree in + /// control flow order. It stores state only at labels and before loops. If + /// it does a join on a backward edge that changes state, it makes a note of + /// this and then makes a full pass through the function again, visiting in + /// control flow order again. + /// + /// The Rust MIR proposal actually suggests checking definite assignment on + /// the MIR. That seems like it might make it difficult to match a spec though. + /// + /// Given that Azoth will never have goto or arbitrary switch statements, + /// it should be possible to do data flow analysis in a very top down way. + /// I.e. proceed through the control flow and repeat things like loops until + /// they stabilize. The only question might be with nested loops whether it + /// makes sense to stabilize an inner loop before repeating an outer loop + /// or not. + /// + public static class DataFlowAnalysis + { + public static void Check( + IForwardDataFlowAnalyzer strategy, + FixedSet declarations, + ISymbolTree symbolTree, + Diagnostics diagnostics) + where TState : class + { + var dataFlowAnalyzer = new ForwardDataFlowAnalyzer(strategy, symbolTree, diagnostics); + foreach (var invocableDeclaration in declarations) + dataFlowAnalyzer.Check(invocableDeclaration); + } + + public static void Check( + IBackwardDataFlowAnalyzer strategy, + FixedSet declarations, + ISymbolTree symbolTree, + Diagnostics diagnostics) + where TState : class + { + var dataFlowAnalyzer = new BackwardDataFlowAnalyzer(strategy, symbolTree, diagnostics); + foreach (var invocableDeclaration in declarations) + dataFlowAnalyzer.Check(invocableDeclaration); + } + } +} diff --git a/Compiler.Semantics/DataFlow/ForwardDataFlowAnalyzer.cs b/Compiler.Semantics/DataFlow/ForwardDataFlowAnalyzer.cs new file mode 100644 index 00000000..c2858da0 --- /dev/null +++ b/Compiler.Semantics/DataFlow/ForwardDataFlowAnalyzer.cs @@ -0,0 +1,75 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.AST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + internal class ForwardDataFlowAnalyzer : AbstractSyntaxWalker + where TState : class + { + private readonly IForwardDataFlowAnalyzer strategy; + private readonly ISymbolTree symbolTree; + private readonly Diagnostics diagnostics; + + public ForwardDataFlowAnalyzer( + IForwardDataFlowAnalyzer strategy, + ISymbolTree symbolTree, + Diagnostics diagnostics) + { + this.strategy = strategy; + this.symbolTree = symbolTree; + this.diagnostics = diagnostics; + } + + private IForwardDataFlowAnalysis? checker; + private TState? currentState; + + public void Check(IExecutableDeclaration syntax) + { + Walk(syntax, false); + } + + protected override void WalkNonNull(IAbstractSyntax syntax, bool isLValue) + { + // TODO this doesn't handle loops correctly + switch (syntax) + { + case IConcreteInvocableDeclaration exp: + checker = strategy.BeginAnalysis(exp, symbolTree, diagnostics); + currentState = checker.StartState(); + break; + case IFieldDeclaration exp: + checker = strategy.BeginAnalysis(exp, symbolTree, diagnostics); + currentState = checker.StartState(); + break; + case IAssignmentExpression exp: + WalkNonNull(exp.LeftOperand, true); + WalkNonNull(exp.RightOperand, false); + currentState = checker!.Assignment(exp, currentState!); + return; + case INameExpression exp: + if (isLValue) return; // ignore + currentState = checker!.IdentifierName(exp, currentState!); + return; + case IVariableDeclarationStatement exp: + WalkChildren(exp, false); + currentState = checker!.VariableDeclaration(exp, currentState!); + return; + case IForeachExpression exp: + WalkNonNull(exp.InExpression, isLValue); + currentState = checker!.VariableDeclaration(exp, currentState!); + WalkNonNull(exp.Block, isLValue); + return; + case IFieldAccessExpression exp: + WalkNonNull(exp.Context, isLValue); + // Don't walk the field name, it shouldn't be treated as a variable + return; + case IDeclaration _: + throw new InvalidOperationException($"Analyze data flow of declaration of type {syntax.GetType().Name}"); + } + WalkChildren(syntax, false); + } + } +} diff --git a/Compiler.Semantics/DataFlow/IBackwardDataFlowAnalysis.cs b/Compiler.Semantics/DataFlow/IBackwardDataFlowAnalysis.cs new file mode 100644 index 00000000..f0087ca0 --- /dev/null +++ b/Compiler.Semantics/DataFlow/IBackwardDataFlowAnalysis.cs @@ -0,0 +1,13 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + public interface IBackwardDataFlowAnalysis + { + TState StartState(); + TState Assignment(IAssignmentExpression assignmentExpression, TState state); + TState IdentifierName(INameExpression nameExpression, TState state); + TState VariableDeclaration(IVariableDeclarationStatement variableDeclaration, TState state); + TState VariableDeclaration(IForeachExpression foreachExpression, TState state); + } +} diff --git a/Compiler.Semantics/DataFlow/IBackwardDataFlowAnalyzer.cs b/Compiler.Semantics/DataFlow/IBackwardDataFlowAnalyzer.cs new file mode 100644 index 00000000..72013d7e --- /dev/null +++ b/Compiler.Semantics/DataFlow/IBackwardDataFlowAnalyzer.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + /// + /// A factory for . This is used + /// to start a new data flow analysis. + /// + public interface IBackwardDataFlowAnalyzer + { + IBackwardDataFlowAnalysis BeginAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics diagnostics); + } +} diff --git a/Compiler.Semantics/DataFlow/IForwardDataFlowAnalysis.cs b/Compiler.Semantics/DataFlow/IForwardDataFlowAnalysis.cs new file mode 100644 index 00000000..6a0ed9ef --- /dev/null +++ b/Compiler.Semantics/DataFlow/IForwardDataFlowAnalysis.cs @@ -0,0 +1,13 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + public interface IForwardDataFlowAnalysis + { + TState StartState(); + TState Assignment(IAssignmentExpression assignmentExpression, TState state); + TState IdentifierName(INameExpression nameExpression, TState state); + TState VariableDeclaration(IVariableDeclarationStatement variableDeclaration, TState state); + TState VariableDeclaration(IForeachExpression foreachExpression, TState state); + } +} diff --git a/Compiler.Semantics/DataFlow/IForwardDataFlowAnalyzer.cs b/Compiler.Semantics/DataFlow/IForwardDataFlowAnalyzer.cs new file mode 100644 index 00000000..8b98a2b6 --- /dev/null +++ b/Compiler.Semantics/DataFlow/IForwardDataFlowAnalyzer.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + /// + /// A factory for . This is used + /// to start a new data flow analysis. + /// + public interface IForwardDataFlowAnalyzer + { + IForwardDataFlowAnalysis BeginAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics diagnostics); + } +} diff --git a/Compiler.Semantics/DataFlow/VariableFlags.cs b/Compiler.Semantics/DataFlow/VariableFlags.cs new file mode 100644 index 00000000..a96a366d --- /dev/null +++ b/Compiler.Semantics/DataFlow/VariableFlags.cs @@ -0,0 +1,62 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow +{ + public class VariableFlags + { + private readonly FixedDictionary symbolMap; + private readonly BitArray flags; + + public VariableFlags(IExecutableDeclaration declaration, ISymbolTree symbolTree, bool defaultValue) + { + var invocableSymbol = declaration.Symbol; + symbolMap = symbolTree.Children(invocableSymbol).Cast().Enumerate().ToFixedDictionary(); + flags = new BitArray(symbolMap.Count, defaultValue); + } + + public VariableFlags( + FixedDictionary symbolMap, + BitArray flags) + { + this.symbolMap = symbolMap; + this.flags = flags; + } + + /// + /// Returns the state for the variable or null if the symbol isn't a + /// variable. + /// + [SuppressMessage("Design", "CA1043:Use Integral Or String Argument For Indexers", Justification = "Symbols are like immutable strings")] + public bool? this[BindingSymbol symbol] => symbolMap.TryGetValue(symbol, out var i) ? (bool?)flags[i] : null; + + public VariableFlags Set(BindingSymbol symbol, bool value) + { + // TODO if setting to the current value, don't need to clone + var newFlags = Clone(); + newFlags.flags[symbolMap[symbol]] = value; + return newFlags; + } + + public VariableFlags Set(IEnumerable symbols, bool value) + { + // TODO if setting to the current value, don't need to clone + var newFlags = Clone(); + foreach (var symbol in symbols) + newFlags.flags[symbolMap[symbol]] = value; + + return newFlags; + } + + private VariableFlags Clone() + { + return new VariableFlags(symbolMap, (BitArray)flags.Clone()); + } + } +} diff --git a/Compiler.Semantics/DeclarationNumbers/DeclarationNumberAssigner.cs b/Compiler.Semantics/DeclarationNumbers/DeclarationNumberAssigner.cs new file mode 100644 index 00000000..81a54d3a --- /dev/null +++ b/Compiler.Semantics/DeclarationNumbers/DeclarationNumberAssigner.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.DeclarationNumbers +{ + public class DeclarationNumberAssigner : SyntaxWalker + { + private readonly Dictionary> lastDeclaration = new Dictionary>(); + private DeclarationNumberAssigner() { } + + public static void AssignIn(IEnumerable entities) + { + foreach (var entity in entities) + { + var assigner = new DeclarationNumberAssigner(); + assigner.WalkNonNull(entity); + assigner.AssignSingleDeclarationsNull(); + } + } + + protected override void WalkNonNull(ISyntax syntax) + { + switch (syntax) + { + case IClassDeclarationSyntax _: + // Skip, will see members separately + return; + case INamedParameterSyntax syn: + ProcessDeclaration(syn.Name, syn.DeclarationNumber); + break; + case IVariableDeclarationStatementSyntax syn: + ProcessDeclaration(syn.Name, syn.DeclarationNumber); + break; + case IForeachExpressionSyntax syn: + ProcessDeclaration(syn.VariableName, syn.DeclarationNumber); + break; + } + WalkChildren(syntax); + } + + private void ProcessDeclaration(Name name, Promise declarationNumber) + { + if (lastDeclaration.TryGetValue(name, out var previousDeclarationNumber)) + { + if (!previousDeclarationNumber.IsFulfilled) + // There is at least two declarations, start counting from 1 + previousDeclarationNumber.Fulfill(1); + declarationNumber.Fulfill(previousDeclarationNumber.Result + 1); + lastDeclaration[name] = declarationNumber; + } + else + lastDeclaration.Add(name, declarationNumber); + } + + private void AssignSingleDeclarationsNull() + { + var unfulfilledDeclarationNumbers = lastDeclaration.Values + .Where(declarationNumber => !declarationNumber.IsFulfilled); + foreach (var declarationNumber in unfulfilledDeclarationNumbers) + // Only a single declaration, don't apply unique numbers + declarationNumber.Fulfill(null); + } + } +} diff --git a/Compiler.Semantics/Errors/BorrowError.cs b/Compiler.Semantics/Errors/BorrowError.cs new file mode 100644 index 00000000..842bc784 --- /dev/null +++ b/Compiler.Semantics/Errors/BorrowError.cs @@ -0,0 +1,63 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Errors +{ + /// + /// Error Code Ranges: + /// 1001-1999: Lexical Errors + /// 2001-2999: Parsing Errors + /// 3001-3999: Type Errors + /// 4001-4999: Borrow Checking Errors + /// 5001-5999: Name Binding Errors + /// 6001-6999: Other Semantic Errors + /// + public static class BorrowError + { + public static Diagnostic SharedValueDoesNotLiveLongEnough( + CodeFile file, + TextSpan span, + Name? variable) + { + var msg = variable is null ? "Shared value does not live long enough" + : $"Value shared by `{variable}` does not live long enough"; + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 4001, msg); + } + + public static Diagnostic CantBorrowWhileShared(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 4002, + $"Can't borrow a reference while it is shared by other references."); + } + + public static Diagnostic CantBorrowWhileBorrowed(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 4003, + $"Can't borrow from reference while it is borrowed."); + } + + public static Diagnostic CantShareWhileBorrowed(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 4004, + $"Can't share a reference while it is borrowed."); + } + + public static Diagnostic CantMoveIntoArgumentWhileShared(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 4005, + $"Can't move ownership into argument while it is shared."); + } + + public static Diagnostic CantBorrowFromThisReference(CodeFile file, in TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 4006, + $"Can't borrow from this reference."); + } + + public static Diagnostic ValueDoesNotLiveLongEnough(CodeFile file, in TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 4007, + $"Value does not live long enough."); + } + } +} diff --git a/Compiler.Semantics/Errors/NameBindingError.cs b/Compiler.Semantics/Errors/NameBindingError.cs new file mode 100644 index 00000000..22e9f761 --- /dev/null +++ b/Compiler.Semantics/Errors/NameBindingError.cs @@ -0,0 +1,84 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Errors +{ + /// + /// Error Code Ranges: + /// 1001-1999: Lexical Errors + /// 2001-2999: Parsing Errors + /// 3001-3999: Type Errors + /// 4001-4999: Borrow Checking Errors + /// 5001-5999: Name Binding Errors + /// 6001-6999: Other Semantic Errors + /// + public static class NameBindingError + { + public static Diagnostic CouldNotBindName(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5001, + $"The name `{file.Code[span]}` is not defined in this scope."); + } + + public static Diagnostic AmbiguousName(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5002, + $"The name `{file.Code[span]}` is ambiguous."); + } + + public static Diagnostic CouldNotBindMember(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5003, + $"Could not find member `{file.Code[span]}` on object."); + } + + public static Diagnostic CouldNotBindConstructor(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5004, + $"Type doesn't have a constructor with this name and number of arguments."); + } + + public static Diagnostic AmbiguousConstructorCall(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5005, + $"Constructor call is ambiguous."); + } + + public static Diagnostic CouldNotBindFunction(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5006, + $"Could not find function with this name and number of arguments."); + } + + public static Diagnostic AmbiguousFunctionCall(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5007, + $"Function call is ambiguous."); + } + + public static Diagnostic CouldNotBindMethod(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5008, + $"Could not find method with this name and number of arguments."); + } + + public static Diagnostic AmbiguousMethodCall(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5009, + $"Method call is ambiguous."); + } + + // TODO add check for this back + public static Diagnostic UsingNonExistentNamespace(CodeFile file, TextSpan span, NamespaceName ns) + { + return new Diagnostic(file, span, DiagnosticLevel.CompilationError, DiagnosticPhase.Analysis, 5010, + $"Using directive refers to namespace `{ns}` which does not exist"); + } + + public static Diagnostic CouldNotBindParameterName(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 5008, + $"Could not find parameter with the name `{file.Code[span]}`."); + } + } +} diff --git a/Compiler.Semantics/Errors/SemanticError.cs b/Compiler.Semantics/Errors/SemanticError.cs new file mode 100644 index 00000000..6b0120a0 --- /dev/null +++ b/Compiler.Semantics/Errors/SemanticError.cs @@ -0,0 +1,72 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Errors +{ + /// + /// Error Code Ranges: + /// 1001-1999: Lexical Errors + /// 2001-2999: Parsing Errors + /// 3001-3999: Type Errors + /// 4001-4999: Borrow Checking Errors + /// 5001-5999: Name Binding Errors + /// 6001-6999: Other Semantic Errors + /// + public static class SemanticError + { + public static Diagnostic CantRebindMutableBinding(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6001, + $"Variable binding can't rebind previous mutable variable binding"); + } + + public static Diagnostic CantRebindAsMutableBinding(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6002, + $"Mutable variable binding can't rebind previous variable binding"); + } + + public static Diagnostic CantShadow(CodeFile file, TextSpan bindingSpan, TextSpan useSpan) + { + // TODO that use span needs converted to a line and column + return new Diagnostic(file, bindingSpan, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6003, + $"Variable binding can't shadow. Shadowed binding used at {useSpan}"); + } + + public static Diagnostic VariableMayAlreadyBeAssigned(CodeFile file, TextSpan span, Name name) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6004, + $"Variable `{name}` declared with `let` may already be assigned"); + } + + public static Diagnostic VariableMayNotHaveBeenAssigned(CodeFile file, TextSpan span, Name name) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6005, + $"Variable `{name}` may not have been assigned before use"); + } + + public static Diagnostic UseOfPossiblyMovedValue(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6006, + "Use of possibly moved value."); + } + + public static Diagnostic ImplicitSelfOutsideMethod(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6007, + "Can't use implicit self reference outside of a method or constructor"); + } + + public static Diagnostic SelfOutsideMethod(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6008, + "Can't use `self` outside of a method or constructor"); + } + + public static Diagnostic NoStringTypeDefined(CodeFile file) + { + return new Diagnostic(file, new TextSpan(0, 0), DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 6009, + "Could not find a `String` type. A `String` type must be defined in the global namespace."); + } + } +} diff --git a/Compiler.Semantics/Errors/TypeError.cs b/Compiler.Semantics/Errors/TypeError.cs new file mode 100644 index 00000000..8b739149 --- /dev/null +++ b/Compiler.Semantics/Errors/TypeError.cs @@ -0,0 +1,112 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Types; +using UnaryOperator = Azoth.Tools.Bootstrap.Compiler.Core.Operators.UnaryOperator; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Errors +{ + /// + /// Error Code Ranges: + /// 1001-1999: Lexical Errors + /// 2001-2999: Parsing Errors + /// 3001-3999: Type Errors + /// 4001-4999: Borrow Checking Errors + /// 5001-5999: Name Binding Errors + /// 6001-6999: Other Semantic Errors + /// + public static class TypeError + { + public static Diagnostic NotImplemented(CodeFile file, TextSpan span, string message) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3000, message); + } + + public static Diagnostic OperatorCannotBeAppliedToOperandsOfType( + CodeFile file, + TextSpan span, + BinaryOperator @operator, + DataType leftOperandType, + DataType rightOperandType) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3001, + $"Operator `{@operator.ToSymbolString()}` cannot be applied to operands of type `{leftOperandType}` and `{rightOperandType}`."); + } + + public static Diagnostic OperatorCannotBeAppliedToOperandOfType( + CodeFile file, + TextSpan span, + UnaryOperator @operator, + DataType operandType) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3002, + $"Operator `{@operator}` cannot be applied to operand of type `{operandType}`."); + } + + public static Diagnostic MustBeATypeExpression(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3003, + "Expression must be of type `type` (i.e. it must evaluate to a type)"); + } + + public static Diagnostic NameRefersToFunctionNotType(CodeFile file, TextSpan span, string name) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3004, + $"The name `{name}` refers to a function not a type."); + } + + public static Diagnostic MustBeABoolExpression(CodeFile file, TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3005, + "Expression must be of type `bool`"); + } + + public static Diagnostic CircularDefinition(CodeFile file, TextSpan span, IClassDeclarationSyntax @class) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3006, + $"Declaration of type `{@class.ContainingNamespaceName}.{@class.Name}` is part of a circular definition"); + } + + public static Diagnostic CannotConvert(CodeFile file, ISyntax expression, DataType ofType, DataType toType) + { + return new Diagnostic(file, expression.Span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3007, + $"Cannot convert expression `{file.Code[expression.Span]}` of type `{ofType}` to type `{toType}`"); + } + + public static Diagnostic MustBeInvocable(CodeFile file, IExpressionSyntax expression) + { + return new Diagnostic(file, expression.Span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3008, + $"Expression must be of callable type to be invoked `{file.Code[expression.Span]}`"); + } + + public static Diagnostic CannotMoveValue(CodeFile file, IMoveExpressionSyntax expression) + { + return new Diagnostic(file, expression.Span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3009, + $"Cannot move value `{file.Code[expression.Referent.Span]}`"); + } + + public static Diagnostic TypeDeclaredImmutable(CodeFile file, IExpressionSyntax expression) + { + return new Diagnostic(file, expression.Span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3010, + $"Type can't be made mutable because it was declared immutable `{file.Code[expression.Span]}`"); + } + + public static Diagnostic ExpressionCantBeMutable(CodeFile file, IExpressionSyntax expression) + { + return new Diagnostic(file, expression.Span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3011, + $"Expression can't be made mutable `{file.Code[expression.Span]}`"); + } + + public static Diagnostic ReturnExpressionMustHaveValue(CodeFile file, in TextSpan span, DataType returnType) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3012, + $"The return expression must return a value of type `{returnType}`"); + } + + public static Diagnostic CantReturnFromNeverFunction(CodeFile file, in TextSpan span) + { + return new Diagnostic(file, span, DiagnosticLevel.FatalCompilationError, DiagnosticPhase.Analysis, 3013, + $"Functions that never return can't contain return statements"); + } + } +} diff --git a/Compiler.Semantics/ILGen/BlockBuilder.cs b/Compiler.Semantics/ILGen/BlockBuilder.cs new file mode 100644 index 00000000..9040b59d --- /dev/null +++ b/Compiler.Semantics/ILGen/BlockBuilder.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.ILGen +{ + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + internal class BlockBuilder + { + public int Number { get; } + private readonly List instructions = new List(); + public IReadOnlyList Instructions => instructions; + public bool IsTerminated => Terminator != null; + [DisallowNull] + public TerminatorInstruction? Terminator { get; private set; } + + public BlockBuilder(in int number) + { + Number = number; + } + + public void Add(Instruction instruction) + { + instructions.Add(instruction); + } + + public void End(TerminatorInstruction instruction) + { + if (Terminator != null) + throw new InvalidOperationException("Can't set terminator instruction more than once"); + Terminator = instruction; + } + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => $"BB #{Number}, Instructions={Instructions.Count + (IsTerminated ? 1 : 0)}, Terminated={IsTerminated}"; + } +} diff --git a/Compiler.Semantics/ILGen/ControlFlowGraphBuilder.cs b/Compiler.Semantics/ILGen/ControlFlowGraphBuilder.cs new file mode 100644 index 00000000..1ab02c35 --- /dev/null +++ b/Compiler.Semantics/ILGen/ControlFlowGraphBuilder.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.ILGen +{ + /// + /// Builder pattern for control flow graphs used during control flow graph fabrication + /// + internal class ControlFlowGraphBuilder + { + private readonly CodeFile file; + private readonly List variables = new List(); + public VariableDeclaration SelfVariable => variables.SingleOrDefault(v => v.Variable == Variable.Self); + private readonly List blockBuilders = new List(); + + public ControlFlowGraphBuilder(CodeFile file) + { + this.file = file; + } + + public VariableDeclaration AddVariable(bool mutableBinding, DataType type, Scope scope, BindingSymbol? symbol = null) + { + var variable = new VariableDeclaration(false, mutableBinding, type.ToNonConstantType(), + new Variable(variables.Count), scope, symbol); + variables.Add(variable); + return variable; + } + + public VariableDeclaration AddParameter(bool mutableBinding, DataType type, Scope scope, BindingSymbol symbol) + { + var variable = new VariableDeclaration(true, mutableBinding, type.ToNonConstantType(), + new Variable(variables.Count), scope, symbol); + variables.Add(variable); + return variable; + } + + public VariableDeclaration AddSelfParameter(SelfParameterSymbol symbol) + { + Requires.That("variableNumber", variables.Count == 0, "Self parameter must have variable number 0"); + var variable = new VariableDeclaration(true, false, symbol.DataType, Variable.Self, null, symbol); + variables.Add(variable); + return variable; + } + + public VariableDeclaration Let(DataType type, Scope scope) + { + return AddVariable(false, type, scope); + } + + public VariableDeclaration Var(DataType type, Scope scope) + { + return AddVariable(true, type, scope); + } + + public VariableDeclaration VariableFor(BindingSymbol symbol) + { + return variables.Single(v => v.Symbol == symbol); + } + + public BlockBuilder NewBlock() + { + var block = new BlockBuilder(blockBuilders.Count); + blockBuilders.Add(block); + return block; + } + + /// Used to create a new block for the entry into a loop if needed. + /// Doesn't create a new block if the current block is empty. + public BlockBuilder NewEntryBlock(BlockBuilder currentBlock, TextSpan span, Scope scope) + { + if (!currentBlock.IsTerminated && !currentBlock.Instructions.Any()) return currentBlock; + var entryBlock = NewBlock(); + currentBlock.End(new GotoInstruction(entryBlock.Number, span, scope)); + return entryBlock; + } + + public ControlFlowGraph Build() + { + // We assume that the first block is the entry block + var blocks = new List(); + foreach (var block in blockBuilders) + { + var terminator = block.Terminator ?? throw new InvalidOperationException(); + blocks.Add(new Block(block.Number, block.Instructions.ToFixedList(), terminator)); + } + + return new ControlFlowGraph(file, variables, blocks); + } + + [SuppressMessage("Design", "CA1043:Use Integral Or String Argument For Indexers", Justification = + "Variable is a value type, essentially a strongly type integer")] + public VariableDeclaration this[Variable variable] + { + get + { + var declaration = variables[variable.Number]; + if (declaration.Variable != variable) + throw new InvalidOperationException("Declaration variable isn't this variable"); + return declaration; + } + } + } +} diff --git a/Compiler.Semantics/ILGen/ControlFlowGraphFabrication.cs b/Compiler.Semantics/ILGen/ControlFlowGraphFabrication.cs new file mode 100644 index 00000000..54debd9f --- /dev/null +++ b/Compiler.Semantics/ILGen/ControlFlowGraphFabrication.cs @@ -0,0 +1,779 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core.Operators; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Operands; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Places; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; +using static Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions.BooleanLogicOperator; +using static Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions.CompareInstructionOperator; +using static Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.Instructions.NumericInstructionOperator; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.ILGen +{ + /// + /// The fabrication of a single control flow graph from a single callable AST node + /// + /// + /// The control flow graph can only be constructed from a completely valid AST. + /// That is, there must be no fatal compiler errors. + /// + public class ControlFlowGraphFabrication + { + private readonly IConcreteInvocableDeclaration invocable; + private readonly SelfParameterSymbol? selfParameter; + private readonly DataType returnType; + private readonly ControlFlowGraphBuilder graph; + + /// + /// The block we are currently adding statements to. Thus, after control flow statements this + /// is the block the control flow exits (to/from?). + /// + private BlockBuilder? currentBlock; + + /// + /// Actions are registered here to connect break statements to the loop exit block + /// + private List> addBreaks = new List>(); + + /// + /// The block that a `next` statement should go to + /// + private BlockBuilder? continueToBlock; + + /// + /// The next available scope number + /// + //private Scope nextScope; + private readonly Stack scopes = new Stack(); + private Scope CurrentScope => scopes.Peek(); + + public ControlFlowGraphFabrication(IConcreteInvocableDeclaration invocable) + { + this.invocable = invocable; + graph = new ControlFlowGraphBuilder(invocable.File); + // We start in the outer scope and need that on the stack + var scope = Scope.Outer; + scopes.Push(scope); + //nextScope = scope.Next(); + switch (invocable) + { + default: + throw ExhaustiveMatch.Failed(invocable); + case IConcreteMethodDeclaration method: + selfParameter = method.SelfParameter.Symbol; + returnType = method.Symbol.ReturnDataType.Known(); + break; + case IConstructorDeclaration constructor: + selfParameter = constructor.ImplicitSelfParameter.Symbol; + returnType = DataType.Void; // the body should `return;` + break; + case IAssociatedFunctionDeclaration associatedFunction: + returnType = associatedFunction.Symbol.ReturnDataType.Known(); + break; + case IFunctionDeclaration function: + returnType = function.Symbol.ReturnDataType.Known(); + break; + } + + // TODO really use return type + _ = returnType; + } + + public ControlFlowGraph CreateGraph() + { + // Temp Variable for return + if (selfParameter != null) graph.AddSelfParameter(selfParameter); + + foreach (var parameter in invocable.Parameters.Where(p => !p.Unused).OfType()) + graph.AddParameter(parameter.Symbol.IsMutableBinding, parameter.Symbol.DataType, CurrentScope, parameter.Symbol); + + currentBlock = graph.NewBlock(); + foreach (var statement in invocable.Body.Statements) + Convert(statement); + + // Generate the implicit return statement + if (currentBlock != null && !currentBlock.IsTerminated) + { + var span = invocable.Span.AtEnd(); + //EndScope(span); + currentBlock.End(new ReturnVoidInstruction(span, Scope.Outer)); + } + + return graph.Build(); + } + + + private void Convert(IStatement statement) + { + switch (statement) + { + default: + throw ExhaustiveMatch.Failed(statement); + case IVariableDeclarationStatement variableDeclaration: + { + var variable = graph.AddVariable(variableDeclaration.Symbol.IsMutableBinding, + variableDeclaration.Symbol.DataType, + CurrentScope, variableDeclaration.Symbol); + if (variableDeclaration.Initializer != null) + { + ConvertIntoPlace(variableDeclaration.Initializer, + variable.Place(variableDeclaration.Initializer.Span)); + } + } + break; + case IExpressionStatement expressionStatement: + { + var expression = expressionStatement.Expression; + if (!expression.DataType.Assigned().IsKnown) + throw new ArgumentException("Expression must have a known type", nameof(statement)); + + Convert(expression); + } + break; + case IResultStatement resultStatement: + { + var expression = resultStatement.Expression; + if (!expression.DataType.Assigned().IsKnown) + throw new ArgumentException("Expression must have a known type", nameof(statement)); + + Convert(expression); + } + break; + } + } + + /// + /// Convert without expecting any result value + /// + private void Convert(IBlockOrResult blockOrResult) + { + switch (blockOrResult) + { + default: + throw ExhaustiveMatch.Failed(blockOrResult); + case IBlockExpression exp: + Convert((IExpression)exp); + break; + case IResultStatement statement: + Convert((IStatement)statement); + break; + } + } + + private void Convert(IElseClause elseClause) + { + switch (elseClause) + { + default: + throw ExhaustiveMatch.Failed(elseClause); + case IBlockOrResult blockOrResult: + Convert(blockOrResult); + break; + case IIfExpression exp: + Convert((IExpression)exp); + break; + } + } + + /// + /// Convert an expression without expecting any result value + /// + private void Convert(IExpression expression) + { + switch (expression) + { + default: + throw ExhaustiveMatch.Failed(expression); + case INewObjectExpression _: + case IFieldAccessExpression _: + throw new NotImplementedException($"Convert({expression.GetType().Name}) Not Implemented."); + case IBorrowExpression exp: + Convert(exp.Referent); + break; + case IShareExpression exp: + Convert(exp.Referent); + break; + case IMoveExpression exp: + Convert(exp.Referent); + break; + case IImplicitNumericConversionExpression exp: + Convert(exp.Expression); + break; + case IImplicitOptionalConversionExpression exp: + Convert(exp.Expression); + break; + case IImplicitImmutabilityConversionExpression exp: + Convert(exp.Expression); + break; + case ILoopExpression exp: + { + var loopEntry = graph.NewEntryBlock(currentBlock!, exp.Block.Span.AtStart(), CurrentScope); + currentBlock = loopEntry; + continueToBlock = loopEntry; + var loopExit = ConvertLoopBody(exp.Block, exitRequired: false); + // If it always breaks, there isn't a current block + currentBlock?.End(new GotoInstruction(loopEntry.Number, exp.Block.Span.AtEnd(), CurrentScope)); + currentBlock = loopExit; + } + break; + case IWhileExpression whileExpression: + { + // There is a block for the condition, it then goes either to + // the body or the after block. + var conditionBlock = graph.NewEntryBlock(currentBlock!, + whileExpression.Condition.Span.AtStart(), CurrentScope); + currentBlock = conditionBlock; + var condition = ConvertToOperand(whileExpression.Condition); + var loopEntry = graph.NewBlock(); + continueToBlock = conditionBlock; + currentBlock = loopEntry; + var loopExit = ConvertLoopBody(whileExpression.Block); + // Generate if branch now that loop exit is known + conditionBlock.End(new IfInstruction(condition, loopEntry.Number, loopExit!.Number, + whileExpression.Condition.Span, CurrentScope)); + // If it always breaks, there isn't a current block + currentBlock?.End(new GotoInstruction(conditionBlock.Number, + whileExpression.Block.Span.AtEnd(), CurrentScope)); + currentBlock = loopExit; + } + break; + case IForeachExpression exp: + { + // For now, we support only range syntax `foreach x: T in z..y`. Range operators + // aren't yet supported by the rest of the language, so they must be directly + // translated here. The for each loop is basically desugared into: + // var x: T = z; + // let temp = y; + // loop + // { + // + // x += 1; + // if x > temp => break; + // } + if (!(exp.InExpression is IBinaryOperatorExpression inExpression) + || (inExpression.Operator != BinaryOperator.DotDot + && inExpression.Operator != BinaryOperator.LessThanDotDot + && inExpression.Operator != BinaryOperator.DotDotLessThan + && inExpression.Operator != BinaryOperator.LessThanDotDotLessThan)) + throw new NotImplementedException( + "`foreach in` non-range expression not implemented"); + + var startExpression = inExpression.LeftOperand; + var endExpression = inExpression.RightOperand; + + var variableType = (IntegerType)exp.Symbol.DataType; + var loopVariable = graph.AddVariable(exp.Symbol.IsMutableBinding, + variableType, CurrentScope, exp.Symbol); + var loopVariablePlace = loopVariable.Place(exp.Span); + var loopVariableReference = loopVariable.Reference(exp.Span); + + var includeStart = inExpression.Operator == BinaryOperator.DotDot + || inExpression.Operator == BinaryOperator.DotDotLessThan; + var includeEnd = inExpression.Operator == BinaryOperator.DotDot + || inExpression.Operator == BinaryOperator.LessThanDotDot; + + // Load up the constant 1 + var one = graph.Let(variableType, CurrentScope); + currentBlock!.Add(new LoadIntegerInstruction(one.Place(exp.Span), 1, variableType, + exp.Span, CurrentScope)); + + // emit var x: T = z (+1); + ConvertIntoPlace(startExpression, loopVariablePlace); + if (!includeStart) + { + var addSpan = inExpression.LeftOperand.Span.AtEnd(); + currentBlock!.Add(new NumericInstruction(loopVariablePlace, Add, variableType, + loopVariableReference, one.Reference(addSpan), CurrentScope)); + } + + // let temp = y; + var endValue = ConvertToOperand(endExpression); + + // Emit block + var loopEntry = graph.NewEntryBlock(currentBlock, + exp.Block.Span.AtStart(), CurrentScope); + currentBlock = loopEntry; + var conditionBlock = continueToBlock = graph.NewBlock(); + // TODO this generates the exit block too soon if the break condition is non-trivial + var loopExit = ConvertLoopBody(exp.Block)!; + // If it always breaks, there isn't a current block + currentBlock?.End(new GotoInstruction(conditionBlock.Number, + exp.Block.Span.AtEnd(), CurrentScope)); + currentBlock = conditionBlock; + // emit x += 1; + currentBlock.Add(new NumericInstruction(loopVariablePlace, Add, variableType, loopVariableReference, one.Reference(exp.Span), CurrentScope)); + + // emit if x (>)|(>=) temp => break; + var breakOperator = includeEnd ? GreaterThan : GreaterThanOrEqual; + var condition = graph.AddVariable(true, DataType.Bool, CurrentScope); + currentBlock.Add(new CompareInstruction(condition.Place(exp.Span), + breakOperator, variableType, loopVariableReference, endValue, CurrentScope)); + + currentBlock.End(new IfInstruction(condition.Reference(exp.Span), loopExit.Number, loopEntry.Number, + exp.Span, CurrentScope)); + currentBlock = loopExit; + } + break; + case IBreakExpression exp: + { + // TODO Do we need `ExitScope(exp.Span.AtEnd());` ? + // capture the current block for use in the lambda + var breakingBlock = currentBlock!; + addBreaks.Add(loopExit => breakingBlock.End(new GotoInstruction(loopExit.Number, exp.Span, CurrentScope))); + currentBlock = null; + } + break; + case INextExpression exp: + { + // TODO Do we need `ExitScope(nextExpression.Span.AtEnd());` ? + currentBlock!.End(new GotoInstruction(continueToBlock?.Number ?? throw new InvalidOperationException(), + exp.Span, CurrentScope)); + currentBlock = null; + } + break; + case IIfExpression exp: + { + var containingBlock = currentBlock!; + var condition = ConvertToOperand(exp.Condition); + var thenEntry = graph.NewBlock(); + currentBlock = thenEntry; + Convert(exp.ThenBlock); + var thenExit = currentBlock; + BlockBuilder elseEntry; + BlockBuilder? exit = null; + if (exp.ElseClause is null) + { + elseEntry = exit = graph.NewBlock(); + thenExit?.End(new GotoInstruction(exit.Number, exp.ThenBlock.Span.AtEnd(), CurrentScope)); + } + else + { + elseEntry = graph.NewBlock(); + currentBlock = elseEntry; + Convert(exp.ElseClause); + var elseExit = currentBlock; + if (thenExit != null || elseExit != null) + { + exit = graph.NewBlock(); + thenExit?.End(new GotoInstruction(exit.Number, exp.ThenBlock.Span.AtEnd(), CurrentScope)); + elseExit?.End(new GotoInstruction(exit.Number, exp.ElseClause.Span.AtEnd(), CurrentScope)); + } + } + + containingBlock.End(new IfInstruction(condition, thenEntry.Number, elseEntry.Number, exp.Condition.Span, CurrentScope)); + currentBlock = exit; + } + break; + case IMethodInvocationExpression exp: + { + var method = exp.ReferencedSymbol; + var target = ConvertToOperand(exp.Context); + var args = exp.Arguments.Select(ConvertToOperand).ToFixedList(); + currentBlock!.Add(new CallVirtualInstruction(target, method, args, exp.Span, CurrentScope)); + } + break; + case IAssignmentExpression exp: + { + var leftOperand = exp.LeftOperand; + var assignInto = ConvertToPlace(leftOperand); + NumericInstructionOperator? op = exp.Operator switch + { + AssignmentOperator.Simple => null, + AssignmentOperator.Plus => Add, + AssignmentOperator.Minus => Subtract, + AssignmentOperator.Asterisk => Multiply, + AssignmentOperator.Slash => Divide, + _ => throw ExhaustiveMatch.Failed(exp.Operator), + }; + if (op is null) + ConvertIntoPlace(exp.RightOperand, assignInto); + else + { + var rhs = ConvertToOperand(exp.RightOperand); + currentBlock!.Add(new NumericInstruction(assignInto, op.Value, (NumericType)leftOperand.DataType.Known(), + assignInto.ToOperand(leftOperand.Span), rhs, CurrentScope)); + } + } + break; + case IUnsafeExpression exp: + Convert(exp.Expression); + break; + case IBlockExpression exp: + foreach (var statement in exp.Statements) + Convert(statement); + break; + case IFunctionInvocationExpression exp: + { + var functionName = exp.ReferencedSymbol; + var args = exp.Arguments.Select(ConvertToOperand).ToFixedList(); + currentBlock!.Add(CallInstruction.ForFunction(functionName, args, exp.Span, CurrentScope)); + } + break; + case IReturnExpression exp: + { + if (exp.Value is null) + currentBlock!.End(new ReturnVoidInstruction(exp.Span, CurrentScope)); + else + { + var returnValue = ConvertToOperand(exp.Value); + currentBlock!.End(new ReturnValueInstruction(returnValue, exp.Span, CurrentScope)); + } + + // There is no exit from a return block, hence null for exit block + currentBlock = null; + } + break; + case INameExpression _: + case IBinaryOperatorExpression _: + case IUnaryOperatorExpression _: + case IBoolLiteralExpression _: + case IStringLiteralExpression _: + case ISelfExpression _: + case INoneLiteralExpression _: + case IIntegerLiteralExpression _: + case IImplicitNoneConversionExpression _: + // These operation have no side effects, so if the result isn't needed, there is nothing to do + break; + + } + } + + /// + /// Convert the body of a loop. Ensures break statements are handled correctly. + /// + private BlockBuilder? ConvertLoopBody(IBlockExpression body, bool exitRequired = true) + { + var oldAddBreaks = addBreaks; + addBreaks = new List>(); + Convert((IExpression)body); + BlockBuilder? loopExit = null; + // If this kind of loop requires an exit or if there is a break, create an exit block + if (exitRequired || addBreaks.Any()) + { + loopExit = graph.NewBlock(); + foreach (var addBreak in addBreaks) addBreak(loopExit); + addBreaks = oldAddBreaks; + } + return loopExit; + } + + /// + /// Convert an expression that yields a value and assign that value into . + /// + private void ConvertIntoPlace(IExpression expression, Place resultPlace) + { + switch (expression) + { + default: + throw ExhaustiveMatch.Failed(expression); + case ILoopExpression _: + case IWhileExpression _: + case IForeachExpression _: + case IReturnExpression _: + case IBreakExpression _: + case INextExpression _: + case IIfExpression _: + case IUnsafeExpression _: + case IBlockExpression _: + case INoneLiteralExpression _: + throw new NotImplementedException($"ConvertIntoPlace({expression.GetType().Name}, Place) Not Implemented."); + case ISelfExpression exp: + { + // This occurs when the source code contains a simple assignment like `x = self` + var symbol = exp.ReferencedSymbol; + var variable = graph.VariableFor(symbol).Reference(exp.Span); + currentBlock!.Add(new AssignmentInstruction(resultPlace, variable, exp.Span, CurrentScope)); + } + break; + case IImplicitOptionalConversionExpression exp: + { + var operand = ConvertToOperand(exp.Expression); + currentBlock!.Add(new SomeInstruction(resultPlace, exp.ConvertToType, operand, exp.Span, CurrentScope)); + } + break; + case IAssignmentExpression exp: + throw new NotImplementedException("Assignments don't have a result"); + case INameExpression exp: + { + // This occurs when the source code contains a simple assignment like `x = y` + var symbol = exp.ReferencedSymbol; + var variable = graph.VariableFor(symbol).Reference(exp.Span); + currentBlock!.Add(new AssignmentInstruction(resultPlace, variable, exp.Span, CurrentScope)); + } + break; + case IBorrowExpression exp: + ConvertIntoPlace(exp.Referent, resultPlace); + break; + case IShareExpression exp: + ConvertIntoPlace(exp.Referent, resultPlace); + break; + case IMoveExpression exp: + ConvertIntoPlace(exp.Referent, resultPlace); + break; + case IImplicitImmutabilityConversionExpression exp: + ConvertIntoPlace(exp.Expression, resultPlace); + break; + case IBinaryOperatorExpression exp: + { + var resultType = exp.DataType.Assigned().Known(); + var operandType = exp.LeftOperand.DataType.Assigned().Known(); + var leftOperand = ConvertToOperand(exp.LeftOperand); + var rightOperand = ConvertToOperand(exp.RightOperand); + switch (exp.Operator) + { + default: + throw ExhaustiveMatch.Failed(expression); + case BinaryOperator.DotDot: + case BinaryOperator.LessThanDotDot: + case BinaryOperator.DotDotLessThan: + case BinaryOperator.LessThanDotDotLessThan: + throw new NotImplementedException("Range operator control flow not implemented"); + #region Logical Operators + case BinaryOperator.And: + // TODO handle calls to overloaded operators + // TODO handle short circuiting if needed + currentBlock!.Add(new BooleanLogicInstruction(resultPlace, And, + leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.Or: + // TODO handle calls to overloaded operators + // TODO handle short circuiting if needed + currentBlock!.Add(new BooleanLogicInstruction(resultPlace, Or, + leftOperand, rightOperand, CurrentScope)); + break; + #endregion + #region Binary Math Operators + case BinaryOperator.Plus: + currentBlock!.Add(new NumericInstruction(resultPlace, Add, + (NumericType)resultType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.Minus: + currentBlock!.Add(new NumericInstruction(resultPlace, Subtract, + (NumericType)resultType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.Asterisk: + currentBlock!.Add(new NumericInstruction(resultPlace, Multiply, + (NumericType)resultType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.Slash: + currentBlock!.Add(new NumericInstruction(resultPlace, Divide, + (NumericType)resultType, leftOperand, rightOperand, CurrentScope)); + break; + #endregion + #region Comparisons + case BinaryOperator.EqualsEquals: + currentBlock!.Add(new CompareInstruction(resultPlace, Equal, + (NumericType)operandType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.NotEqual: + currentBlock!.Add(new CompareInstruction(resultPlace, NotEqual, + (NumericType)operandType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.LessThan: + currentBlock!.Add(new CompareInstruction(resultPlace, LessThan, + (NumericType)operandType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.LessThanOrEqual: + currentBlock!.Add(new CompareInstruction(resultPlace, LessThanOrEqual, + (NumericType)operandType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.GreaterThan: + currentBlock!.Add(new CompareInstruction(resultPlace, GreaterThan, + (NumericType)operandType, leftOperand, rightOperand, CurrentScope)); + break; + case BinaryOperator.GreaterThanOrEqual: + currentBlock!.Add(new CompareInstruction(resultPlace, GreaterThanOrEqual, + (NumericType)operandType, leftOperand, rightOperand, CurrentScope)); + break; + #endregion Comparisons + } + } + break; + case IUnaryOperatorExpression exp: + { + var type = exp.DataType.Assigned().Known(); + var operand = ConvertToOperand(exp.Operand); + switch (exp.Operator) + { + default: + throw ExhaustiveMatch.Failed(expression); + case UnaryOperator.Not: + case UnaryOperator.Plus: // TODO don't even allow unary plus in IL or AST, it is a noop + throw new NotImplementedException($"ConvertToOperand({expression.GetType().Name}, Place) Not Implemented for {exp.Operator}."); + case UnaryOperator.Minus: + currentBlock!.Add(new NegateInstruction(resultPlace, (NumericType)type, operand, exp.Span, CurrentScope)); + break; + } + } + break; + case IFieldAccessExpression exp: + { + var context = ConvertToOperand(exp.Context); + var field = exp.ReferencedSymbol; + currentBlock!.Add(new FieldAccessInstruction(resultPlace, context, field, exp.Span, CurrentScope)); + } + break; + case IFunctionInvocationExpression exp: + { + var functionName = exp.ReferencedSymbol; + var args = exp.Arguments.Select(ConvertToOperand).ToFixedList(); + currentBlock!.Add(CallInstruction.ForFunction(resultPlace, functionName, args, exp.Span, CurrentScope)); + } + break; + case IMethodInvocationExpression exp: + { + var methodName = exp.ReferencedSymbol; + var target = ConvertToOperand(exp.Context); + var args = exp.Arguments.Select(ConvertToOperand).ToFixedList(); + if (exp.Context.DataType is ReferenceType) + currentBlock!.Add(new CallVirtualInstruction(resultPlace, target, methodName, args, exp.Span, CurrentScope)); + else + currentBlock!.Add(CallInstruction.ForMethod(resultPlace, target, methodName, args, exp.Span, CurrentScope)); + } + break; + case INewObjectExpression exp: + { + var constructor = exp.ReferencedSymbol; + var args = exp.Arguments.Select(ConvertToOperand).ToFixedList(); + var constructedType = (ObjectType)exp.DataType.Known(); + currentBlock!.Add(new NewObjectInstruction(resultPlace, constructor, constructedType, args, exp.Span, CurrentScope)); + } + break; + case IStringLiteralExpression exp: + currentBlock!.Add(new LoadStringInstruction(resultPlace, exp.Value, exp.Span, CurrentScope)); + break; + case IBoolLiteralExpression exp: + currentBlock!.Add(new LoadBoolInstruction(resultPlace, exp.Value, exp.Span, CurrentScope)); + break; + case IImplicitNumericConversionExpression exp: + { + if (exp.Expression.DataType.Assigned().Known() is IntegerConstantType constantType) + currentBlock!.Add(new LoadIntegerInstruction(resultPlace, constantType.Value, + (IntegerType)exp.DataType.Assigned().Known(), + exp.Span, CurrentScope)); + else + currentBlock!.Add(new ConvertInstruction(resultPlace, ConvertToOperand(exp.Expression), + (NumericType)exp.Expression.DataType.Assigned().Known(), exp.ConvertToType, + exp.Span, CurrentScope)); + } + break; + case IIntegerLiteralExpression exp: + throw new InvalidOperationException( + "Integer literals should have an implicit conversion around them"); + case IImplicitNoneConversionExpression exp: + currentBlock!.Add(new LoadNoneInstruction(resultPlace, exp.ConvertToType, exp.Span, CurrentScope)); + break; + } + } + + /// + /// Convert an expression that yields a value into an operand for another instruction + /// + private Operand ConvertToOperand(IExpression expression) + { + switch (expression) + { + default: + throw ExhaustiveMatch.Failed(expression); + case ISelfExpression exp: + return graph.SelfVariable.Reference(exp.Span); + case INameExpression exp: + { + var symbol = exp.ReferencedSymbol; + return graph.VariableFor(symbol).Reference(exp.Span); + } + case IBorrowExpression exp: + return ConvertToOperand(exp.Referent); + case IShareExpression exp: + return ConvertToOperand(exp.Referent); + case IMoveExpression exp: + return ConvertToOperand(exp.Referent); + case IAssignmentExpression _: + case IBinaryOperatorExpression _: + case IUnaryOperatorExpression _: + case IFieldAccessExpression _: + case IMethodInvocationExpression _: + case INewObjectExpression _: + case IImplicitNumericConversionExpression _: + case IIntegerLiteralExpression _: + case IStringLiteralExpression _: + case IBoolLiteralExpression _: + case INoneLiteralExpression _: + case IImplicitImmutabilityConversionExpression _: + case IImplicitOptionalConversionExpression _: + case IUnsafeExpression _: + case IBlockExpression _: + case IImplicitNoneConversionExpression _: + case IBreakExpression _: + case INextExpression _: + case IReturnExpression _: + case IIfExpression _: + case IFunctionInvocationExpression _: + case IForeachExpression _: + case ILoopExpression _: + case IWhileExpression _: + { + var tempVar = graph.Let(expression.DataType.Assigned().Known(), CurrentScope); + ConvertIntoPlace(expression, tempVar.Place(expression.Span)); + return tempVar.Reference(expression.Span); + } + } + } + + /// + /// Convert an expression to a place. Used for LValues + /// + private Place ConvertToPlace(IExpression expression) + { + switch (expression) + { + default: + //throw ExhaustiveMatch.Failed(expression); + throw new NotImplementedException($"ConvertToPlaceWithoutSideEffects({expression.GetType().Name}) Not Implemented."); + case IFieldAccessExpression exp: + { + var context = ConvertToOperand(exp.Context); + var field = exp.ReferencedSymbol; + return new FieldPlace(context, field, exp.Span); + } + case ISelfExpression exp: + return new VariablePlace(Variable.Self, exp.Span); + case INameExpression exp: + { + var symbol = exp.ReferencedSymbol; + return graph.VariableFor(symbol).Place(exp.Span); + } + } + } + + //private void EnterNewScope() + //{ + // scopes.Push(nextScope); + // nextScope = nextScope.Next(); + //} + + ///// + ///// An exit point for the current scope that doesn't end it. For example, a break statement + ///// + //private void ExitScope(TextSpan span) + //{ + // currentBlock?.AddExitScope(span, CurrentScope); + //} + + //private void EndScope(TextSpan span) + //{ + // // In some cases we will have left the current block by a terminator, + // // so we don't need to emit an exit statement. + // currentBlock?.AddExitScope(span, CurrentScope); + // scopes.Pop(); + //} + } +} diff --git a/Compiler.Semantics/ILGen/DeclarationBuilder.cs b/Compiler.Semantics/ILGen/DeclarationBuilder.cs new file mode 100644 index 00000000..6db48246 --- /dev/null +++ b/Compiler.Semantics/ILGen/DeclarationBuilder.cs @@ -0,0 +1,171 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG.TerminatorInstructions; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.ILGen +{ + public class DeclarationBuilder + { + private readonly ILFactory ilFactory; + private readonly Dictionary declarationsIL = new Dictionary(); + + public DeclarationBuilder(ILFactory ilFactory) + { + this.ilFactory = ilFactory; + } + + public IEnumerable AllDeclarations => declarationsIL.Values; + + public void Build(IEnumerable declarations, ISymbolTree symbolTree) + { + foreach (var declaration in declarations) + Build(declaration, symbolTree); + } + + private FixedList BuildList( + IEnumerable memberDeclarations, + ISymbolTree symbolTree) + { + return memberDeclarations.Select(e => Build(e, symbolTree)).ToFixedList(); + } + + private DeclarationIL Build(IDeclaration declaration, ISymbolTree symbolTree) + { + if (declarationsIL.TryGetValue(declaration.Symbol, out var declarationIL)) + return declarationIL; + + switch (declaration) + { + default: + throw ExhaustiveMatch.Failed(declaration); + case IFunctionDeclaration function: + { + var il = ilFactory.CreateGraph(function); + declarationIL = new FunctionIL(false, false, function.Symbol, BuildParameters(function.Parameters), il); + break; + } + case IAssociatedFunctionDeclaration associatedFunction: + { + var il = ilFactory.CreateGraph(associatedFunction); + declarationIL = new FunctionIL(false, true, associatedFunction.Symbol, BuildParameters(associatedFunction.Parameters), il); + break; + } + case IConcreteMethodDeclaration method: + { + var il = ilFactory.CreateGraph(method); + declarationIL = new MethodDeclarationIL(method.Symbol, BuildParameter(method.SelfParameter), BuildParameters(method.Parameters), il); + break; + } + case IAbstractMethodDeclaration method: + { + declarationIL = new MethodDeclarationIL(method.Symbol, BuildParameter(method.SelfParameter), BuildParameters(method.Parameters), null); + break; + } + case IConstructorDeclaration constructor: + { + var il = ilFactory.CreateGraph(constructor); + var parameters = BuildConstructorParameters(constructor); + var fieldInitializations = BuildFieldInitializations(constructor); + declarationIL = new ConstructorIL(constructor.Symbol, parameters, fieldInitializations, il); + break; + } + case IFieldDeclaration fieldDeclaration: + declarationIL = new FieldIL(fieldDeclaration.Symbol); + break; + case IClassDeclaration classDeclaration: + declarationIL = new ClassIL(classDeclaration.Symbol, + BuildClassMembers(classDeclaration, symbolTree)); + break; + } + declarationsIL.Add(declaration.Symbol, declarationIL); + return declarationIL; + } + + private FixedList BuildClassMembers( + IClassDeclaration classDeclaration, + ISymbolTree symbolTree) + { + var members = BuildList(classDeclaration.Members, symbolTree); + var defaultConstructor = BuildDefaultConstructor(classDeclaration, symbolTree); + if (!(defaultConstructor is null)) + members = members.Append(defaultConstructor).ToFixedList(); + return members.ToFixedList(); + } + + private DeclarationIL? BuildDefaultConstructor( + IClassDeclaration classDeclaration, + ISymbolTree symbolTree) + { + var constructorSymbol = classDeclaration.DefaultConstructorSymbol; + if (constructorSymbol is null) return null; + + if (declarationsIL.TryGetValue(constructorSymbol, out var declaration)) + return declaration; + + var selfParameterSymbol = symbolTree.Children(constructorSymbol).OfType().Single(); + var selfParameter = new SelfParameterIL(selfParameterSymbol); + var parameters = selfParameter.Yield().ToFixedList(); + + var graph = new ControlFlowGraphBuilder(classDeclaration.File); + graph.AddSelfParameter(selfParameterSymbol); + var block = graph.NewBlock(); + block.End(new ReturnVoidInstruction(classDeclaration.NameSpan, Scope.Outer)); + + //var il = new ControlFlowGraphBuilder(classDeclaration.File); + //il.AddSelfParameter(selfType); + //var block = il.NewBlock(); + //block.End(classDeclaration.NameSpan, Scope.Outer); + + var defaultConstructor = new ConstructorIL(// TODO how to get a name + constructorSymbol, + parameters, FixedList.Empty, graph.Build()); + + //defaultConstructor.ControlFlowOld.InsertedDeletes = new InsertedDeletes(); + declarationsIL.Add(constructorSymbol, defaultConstructor); + return defaultConstructor; + } + + private static FixedList BuildParameters(IEnumerable parameters) + { + return parameters.Select(BuildParameter).ToFixedList(); + } + + private static FixedList BuildConstructorParameters(IConstructorDeclaration constructorDeclaration) + { + var selfParameterSymbol = constructorDeclaration.ImplicitSelfParameter.Symbol; + var selfParameter = new SelfParameterIL(selfParameterSymbol); + return selfParameter.Yield().Concat(constructorDeclaration.Parameters.Select(BuildParameter)).ToFixedList(); + } + + private static ParameterIL BuildParameter(IParameter parameter) + { + return parameter switch + { + INamedParameter namedParameter => new NamedParameterIL(namedParameter.Symbol), + ISelfParameter selfParameter => new SelfParameterIL(selfParameter.Symbol), + IFieldParameter fieldParameter => new FieldParameterIL(fieldParameter.ReferencedSymbol), + _ => throw ExhaustiveMatch.Failed(parameter) + }; + } + + private static FixedList BuildFieldInitializations( + IConstructorDeclaration constructorDeclaration) + { + return constructorDeclaration.Parameters.OfType() + .Select(BuildFieldInitialization) + .ToFixedList(); + } + + private static FieldInitializationIL BuildFieldInitialization(IFieldParameter parameter) + { + return new FieldInitializationIL(parameter.ReferencedSymbol); + } + } +} diff --git a/Compiler.Semantics/ILGen/IlFactory.cs b/Compiler.Semantics/ILGen/IlFactory.cs new file mode 100644 index 00000000..eff6a29c --- /dev/null +++ b/Compiler.Semantics/ILGen/IlFactory.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage.CFG; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.ILGen +{ + public class ILFactory + { + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public ControlFlowGraph CreateGraph(IConcreteInvocableDeclaration invocableDeclaration) + { + // TODO build control flow graphs for field initializers + + var fabrication = new ControlFlowGraphFabrication(invocableDeclaration); + return fabrication.CreateGraph(); + } + } +} diff --git a/Compiler.Semantics/LexicalScopes/LexicalScopesBuilder.cs b/Compiler.Semantics/LexicalScopes/LexicalScopesBuilder.cs new file mode 100644 index 00000000..8c29624d --- /dev/null +++ b/Compiler.Semantics/LexicalScopes/LexicalScopesBuilder.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Primitives; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.LexicalScopes +{ + public class LexicalScopesBuilder + { + [SuppressMessage("Performance", "CA1822:Mark members as static", + Justification = "OO")] + public void BuildFor(PackageSyntax package) + { + var declarationSymbols = GetAllNonMemberDeclarationSymbols(package); + var namespaces = BuildNamespaces(declarationSymbols); + var packagesScope = BuildPackagesScope(package); + var globalScope = BuildGlobalScope(packagesScope, namespaces[NamespaceName.Global]); + + foreach (var compilationUnit in package.CompilationUnits) + { + var builder = new LexicalScopesBuilderWalker(globalScope, namespaces); + builder.BuildFor(compilationUnit, globalScope); + } + } + + private static FixedList GetAllNonMemberDeclarationSymbols(PackageSyntax package) + { + var primitiveSymbols = Primitive.SymbolTree.Symbols + .Where(s => s.ContainingSymbol is null) + .Select(s => new NonMemberSymbol(s)); + + var packageNamespaces = package.SymbolTree.Symbols + .OfType() + .Select(s => new NonMemberSymbol(s)); + + var packageSymbols = package.AllEntityDeclarations + .OfType() + .Select(d => new NonMemberSymbol(d)); + + // TODO it might be better to go to the declarations and get their symbols (once that is implemented) + var referencedSymbols = package.ReferencedPackages + .SelectMany(p => p.SymbolTree.Symbols) + .Concat(Intrinsic.SymbolTree.Symbols) + .Where(s => s.ContainingSymbol is NamespaceOrPackageSymbol) + .Select(s => new NonMemberSymbol(s)); + return primitiveSymbols + .Concat(packageNamespaces) + .Concat(packageSymbols) + .Concat(referencedSymbols) + .ToFixedList(); + } + + private static FixedDictionary BuildNamespaces( + FixedList declarationSymbols) + { + var namespaces = declarationSymbols.SelectMany(s => s.ContainingNamespace.NamespaceNames()).Distinct(); + var nsSymbols = new List(); + foreach (var ns in namespaces) + { + var symbols = declarationSymbols.Where(s => s.ContainingNamespace == ns).ToList(); + var nestedSymbols = declarationSymbols.Where(s => s.ContainingNamespace.IsNestedIn(ns)).ToList(); + + nsSymbols.Add(new Namespace( + ns, + ToDictionary(symbols), + ToDictionary(nestedSymbols), + ToDictionary(symbols.Where(s => s.InCurrentPackage)), + ToDictionary(nestedSymbols.Where(s => s.InCurrentPackage)))); + } + + return nsSymbols.ToFixedDictionary(ns => ns.Name); + } + private static PackagesScope BuildPackagesScope(PackageSyntax package) + { + var packageAliases = package.References + .ToDictionary(p => p.Key, p => p.Value.Symbol) + .ToFixedDictionary(); + return new PackagesScope(package.Symbol, packageAliases); + } + + private static NestedScope BuildGlobalScope( + PackagesScope packagesScope, + Namespace globalNamespace) + { + var allPackagesGlobalScope = NestedScope.CreateGlobal(packagesScope, globalNamespace.Symbols, globalNamespace.NestedSymbols); + + return allPackagesGlobalScope; + } + + private static FixedDictionary>> ToDictionary( + IEnumerable symbols) + { + return symbols.GroupBy(s => s.Name, s => s.Symbol) + .ToFixedDictionary(e => e.Key, e => e.ToFixedSet()); + } + } +} diff --git a/Compiler.Semantics/LexicalScopes/LexicalScopesBuilderWalker.cs b/Compiler.Semantics/LexicalScopes/LexicalScopesBuilderWalker.cs new file mode 100644 index 00000000..fab3d1b6 --- /dev/null +++ b/Compiler.Semantics/LexicalScopes/LexicalScopesBuilderWalker.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.LexicalScopes +{ + internal class LexicalScopesBuilderWalker : SyntaxWalker + { + private readonly NestedScope globalScope; + private readonly FixedDictionary namespaces; + + public LexicalScopesBuilderWalker( + NestedScope globalScope, + FixedDictionary namespaces) + { + this.globalScope = globalScope; + this.namespaces = namespaces; + } + + public void BuildFor(ICompilationUnitSyntax compilationUnit, LexicalScope containingScope) + { + Walk(compilationUnit, containingScope); + } + + protected override void WalkNonNull(ISyntax syntax, LexicalScope containingScope) + { + if (syntax is IHasContainingLexicalScope hasContainingLexicalScope) + hasContainingLexicalScope.ContainingLexicalScope = containingScope; + + switch (syntax) + { + case ICompilationUnitSyntax syn: + containingScope = BuildNamespaceScopes(NamespaceName.Global, syn.ImplicitNamespaceName, containingScope); + containingScope = BuildUsingDirectivesScope(syn.UsingDirectives, containingScope); + break; + case INamespaceDeclarationSyntax syn: + if (syn.IsGlobalQualified) + { + containingScope = globalScope; + containingScope = BuildNamespaceScopes(NamespaceName.Global, syn.FullName, containingScope); + } + else + containingScope = BuildNamespaceScopes(syn.ContainingNamespaceName, syn.DeclaredNames, containingScope); + + containingScope = BuildUsingDirectivesScope(syn.UsingDirectives, containingScope); + break; + case IClassDeclarationSyntax syn: + containingScope = BuildClassScope(syn, containingScope); + break; + case IFunctionDeclarationSyntax function: + foreach (var parameter in function.Parameters) + Walk(parameter, containingScope); + Walk(function.ReturnType, containingScope); + containingScope = BuildBodyScope(function.Parameters, containingScope); + Walk(function.Body, containingScope); + return; + case IAssociatedFunctionDeclarationSyntax function: + foreach (var parameter in function.Parameters) + Walk(parameter, containingScope); + Walk(function.ReturnType, containingScope); + containingScope = BuildBodyScope(function.Parameters, containingScope); + Walk(function.Body, containingScope); + return; + case IConcreteMethodDeclarationSyntax concreteMethod: + Walk(concreteMethod.SelfParameter, containingScope); + foreach (var parameter in concreteMethod.Parameters) + Walk(parameter, containingScope); + Walk(concreteMethod.ReturnType, containingScope); + containingScope = BuildBodyScope(concreteMethod.Parameters, containingScope); + Walk(concreteMethod.Body, containingScope); + return; + case IConstructorDeclarationSyntax constructor: + Walk(constructor.ImplicitSelfParameter, containingScope); + foreach (var parameter in constructor.Parameters) + Walk(parameter, containingScope); + containingScope = BuildBodyScope(constructor.Parameters, containingScope); + Walk(constructor.Body, containingScope); + return; + case IBodyOrBlockSyntax bodyOrBlock: + foreach (var statement in bodyOrBlock.Statements) + { + Walk(statement, containingScope); + // Each variable declaration effectively starts a new scope after it, this + // ensures a lookup returns the last declaration + if (statement is IVariableDeclarationStatementSyntax variableDeclaration) + containingScope = BuildVariableScope(containingScope, variableDeclaration.Name, variableDeclaration.Symbol); + } + return; + case IForeachExpressionSyntax foreachExpression: + Walk(foreachExpression.Type, containingScope); + Walk(foreachExpression.InExpression, containingScope); + containingScope = BuildVariableScope(containingScope, foreachExpression.VariableName, foreachExpression.Symbol); + Walk(foreachExpression.Block, containingScope); + return; + } + + WalkChildren(syntax, containingScope); + } + + private LexicalScope BuildNamespaceScopes( + NamespaceName containingNamespaceName, + NamespaceName declaredNamespaceNames, + LexicalScope containingScope) + { + foreach (var name in declaredNamespaceNames.NamespaceNames()) + { + var fullNamespaceName = containingNamespaceName.Qualify(name); + // Skip the global namespace because we already have the global lexical scopes + if (fullNamespaceName == NamespaceName.Global) continue; + containingScope = BuildNamespaceScope(fullNamespaceName, containingScope); + } + + return containingScope; + } + + private LexicalScope BuildNamespaceScope(NamespaceName nsName, LexicalScope containingScope) + { + var ns = namespaces[nsName]; + return NestedScope.Create(containingScope, ns.SymbolsInPackage, ns.NestedSymbolsInPackage); + } + + private LexicalScope BuildUsingDirectivesScope( + FixedList usingDirectives, + LexicalScope containingScope) + { + if (!usingDirectives.Any()) return containingScope; + + var importedSymbols = new Dictionary>>(); + foreach (var usingDirective in usingDirectives) + { + if (!namespaces.TryGetValue(usingDirective.Name, out var ns)) + { + // TODO diagnostics.Add(NameBindingError.UsingNonExistentNamespace(file, usingDirective.Span, usingDirective.Name)); + continue; + } + + foreach (var (name, additionalSymbols) in ns.Symbols) + { + if (importedSymbols.TryGetValue(name, out var symbols)) + symbols.AddRange(additionalSymbols); + else + importedSymbols.Add(name, additionalSymbols.ToHashSet()); + } + } + + var symbolsInScope = importedSymbols.ToFixedDictionary(e => e.Key, e => e.Value.ToFixedSet()); + return NestedScope.Create(containingScope, symbolsInScope); + } + + private static LexicalScope BuildClassScope( + IClassDeclarationSyntax @class, + LexicalScope containingScope) + { + // Only "static" names are in scope. Other names must use `self.` + var symbols = @class.Members.OfType() + .GroupBy(m => m.Name, m => m.Symbol) + .ToFixedDictionary(e => (TypeName)e.Key, e => e.ToFixedSet>()); + + return NestedScope.Create(containingScope, symbols); + } + + private static LexicalScope BuildBodyScope( + IEnumerable parameters, + LexicalScope containingScope) + { + var symbols = parameters.OfType() + .GroupBy(p => p.Name, p => p.Symbol) + .ToFixedDictionary(e => (TypeName)e.Key, e => e.ToFixedSet>()); + return NestedScope.Create(containingScope, symbols); + } + + private static LexicalScope BuildVariableScope( + LexicalScope containingScope, + Name name, + IPromise symbol) + { + var symbols = new Dictionary>>() + { + { name, symbol.Yield().ToFixedSet>() } + }.ToFixedDictionary(); + return NestedScope.Create(containingScope, symbols); + } + } +} diff --git a/Compiler.Semantics/LexicalScopes/Namespace.cs b/Compiler.Semantics/LexicalScopes/Namespace.cs new file mode 100644 index 00000000..fbbab82a --- /dev/null +++ b/Compiler.Semantics/LexicalScopes/Namespace.cs @@ -0,0 +1,30 @@ +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.LexicalScopes +{ + internal class Namespace + { + public NamespaceName Name { get; } + public FixedDictionary>> Symbols { get; } + public FixedDictionary>> NestedSymbols { get; } + public FixedDictionary>> SymbolsInPackage { get; } + public FixedDictionary>> NestedSymbolsInPackage { get; } + + public Namespace( + NamespaceName name, + FixedDictionary>> symbols, + FixedDictionary>> nestedSymbols, + FixedDictionary>> symbolsInPackage, + FixedDictionary>> nestedSymbolsInPackage) + { + Name = name; + Symbols = symbols; + NestedSymbols = nestedSymbols; + SymbolsInPackage = symbolsInPackage; + NestedSymbolsInPackage = nestedSymbolsInPackage; + } + } +} diff --git a/Compiler.Semantics/LexicalScopes/NonMemberSymbol.cs b/Compiler.Semantics/LexicalScopes/NonMemberSymbol.cs new file mode 100644 index 00000000..0aa13066 --- /dev/null +++ b/Compiler.Semantics/LexicalScopes/NonMemberSymbol.cs @@ -0,0 +1,36 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.LexicalScopes +{ + internal class NonMemberSymbol + { + public bool InCurrentPackage { get; } + public NamespaceName ContainingNamespace { get; } + public TypeName Name { get; } + public IPromise Symbol { get; } + + public NonMemberSymbol(INonMemberEntityDeclarationSyntax declaration) + { + InCurrentPackage = true; + ContainingNamespace = declaration.ContainingNamespaceName; + Name = declaration.Name; + Symbol = declaration.Symbol; + } + + public NonMemberSymbol(Symbol symbol) + { + if (!(symbol.ContainingSymbol is NamespaceOrPackageSymbol + || symbol.ContainingSymbol is null)) + throw new ArgumentException("Symbol must be for a non-member declaration", nameof(symbol)); + var containingSymbol = symbol.ContainingSymbol as NamespaceOrPackageSymbol; + InCurrentPackage = false; + ContainingNamespace = containingSymbol?.NamespaceName ?? NamespaceName.Global; + Name = symbol.Name ?? throw new ArgumentException("Symbol must have a name", nameof(symbol)); + Symbol = AcyclicPromise.ForValue(symbol); + } + } +} diff --git a/Compiler.Semantics/Liveness/LivenessAnalysis.cs b/Compiler.Semantics/Liveness/LivenessAnalysis.cs new file mode 100644 index 00000000..9a981c09 --- /dev/null +++ b/Compiler.Semantics/Liveness/LivenessAnalysis.cs @@ -0,0 +1,80 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Liveness +{ + public class LivenessAnalysis : IBackwardDataFlowAnalysis + { + private readonly IExecutableDeclaration declaration; + private readonly ISymbolTree symbolTree; + + public LivenessAnalysis(IExecutableDeclaration declaration, ISymbolTree symbolTree) + { + this.declaration = declaration; + this.symbolTree = symbolTree; + } + + public VariableFlags StartState() + { + return new VariableFlags(declaration, symbolTree, false); + } + + public VariableFlags Assignment( + IAssignmentExpression assignmentExpression, + VariableFlags liveVariables) + { + switch (assignmentExpression.LeftOperand) + { + case INameExpression identifier: + var symbol = identifier.ReferencedSymbol; + var isLifeAfter = liveVariables[symbol] + ?? throw new Exception($"No liveness data for variable {symbol}"); + identifier.VariableIsLiveAfter.Fulfill(isLifeAfter); + return liveVariables.Set(symbol, false); + case IFieldAccessExpression _: + return liveVariables; + default: + throw new NotImplementedException("Complex assignments not yet implemented"); + } + } + + public VariableFlags IdentifierName( + INameExpression nameExpression, + VariableFlags liveVariables) + { + SetLiveness(nameExpression.ReferencedSymbol, nameExpression.VariableIsLiveAfter, liveVariables); + return liveVariables.Set(nameExpression.ReferencedSymbol, true); + } + + public VariableFlags VariableDeclaration( + IVariableDeclarationStatement variableDeclaration, + VariableFlags liveVariables) + { + SetLiveness(variableDeclaration.Symbol, variableDeclaration.VariableIsLiveAfter, liveVariables); + return liveVariables.Set(variableDeclaration.Symbol, false); + } + + public VariableFlags VariableDeclaration( + IForeachExpression foreachExpression, + VariableFlags liveVariables) + { + SetLiveness(foreachExpression.Symbol, foreachExpression.VariableIsLiveAfterAssignment, liveVariables); + return liveVariables.Set(foreachExpression.Symbol, false); + } + + private static void SetLiveness( + NamedBindingSymbol symbol, + Promise promise, + VariableFlags liveVariables) + { + var isLiveAfter = liveVariables[symbol] + ?? throw new Exception($"No liveness data for variable {symbol}"); + + promise.Fulfill(isLiveAfter); + } + } +} diff --git a/Compiler.Semantics/Liveness/LivenessAnalyzer.cs b/Compiler.Semantics/Liveness/LivenessAnalyzer.cs new file mode 100644 index 00000000..ed65e1e8 --- /dev/null +++ b/Compiler.Semantics/Liveness/LivenessAnalyzer.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Liveness +{ + public class LivenessAnalyzer : IBackwardDataFlowAnalyzer + { + #region Singleton + public static readonly LivenessAnalyzer Instance = new LivenessAnalyzer(); + + private LivenessAnalyzer() { } + #endregion + + public IBackwardDataFlowAnalysis BeginAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics _) + { + return new LivenessAnalysis(declaration, symbolTree); + } + } +} diff --git a/Compiler.Semantics/Liveness/OldLivenessAnalyzer.cs b/Compiler.Semantics/Liveness/OldLivenessAnalyzer.cs new file mode 100644 index 00000000..1b434440 --- /dev/null +++ b/Compiler.Semantics/Liveness/OldLivenessAnalyzer.cs @@ -0,0 +1,157 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Liveness +{ + ///// + ///// Compute where variables are live and return a structure of that + ///// + //public static class LivenessAnalyzer + //{ + // public static FixedDictionary Check( + // FixedList functions, + // bool saveLivenessAnalysis) + // { + // throw new NotImplementedException(); + // //var analyses = new Dictionary(); + // //foreach (var function in functions) + // //{ + // // var controlFlow = function.ControlFlowOld; + // // if (controlFlow is null) continue; + // // var liveness = ComputeLiveness(controlFlow); + // // if (liveness != null) + // // { + // // analyses.Add(function, liveness); + // // if (saveLivenessAnalysis) + // // controlFlow.LiveVariables = liveness; + // // } + // //} + + // //return analyses.ToFixedDictionary(); + // } + + // /// + // /// Perform a backwards data flow analysis to determine where each variable is live or dead + // /// + // private static LiveVariables ComputeLiveness(ControlFlowGraphOld controlFlow) + // { + // var blocksQueue = new Queue(); + // // Not just exit blocks because sometimes the exit blocks don't change vars and don't pull in other blocks + // blocksQueue.EnqueueRange(controlFlow.BasicBlocks); + // var liveVariables = new LiveVariables(controlFlow); + // var numberOfVariables = controlFlow.VariableDeclarations.Count; + + // while (blocksQueue.TryDequeue(out var block)) + // { + // var oldLiveBeforeBlock = new BitArray(liveVariables.Before(block.Statements[0])); + + // var liveAfterBlock = new BitArray(numberOfVariables); + // foreach (var successor in controlFlow.Edges.From(block)) + // liveAfterBlock.Or(liveVariables.Before(successor.Statements[0])); + + // var liveAfterStatement = liveAfterBlock; + + // foreach (var statement in block.Statements.Reverse()) + // { + // var liveSet = liveVariables.Before(statement); + // liveSet.Or(liveAfterStatement); + // switch (statement) + // { + // default: + // throw ExhaustiveMatch.Failed(statement); + // case AssignmentStatement assignment: + // KillVariables(liveSet, assignment.Place); + // EnlivenVariables(liveSet, assignment.Value); + // break; + // case ActionStatement action: + // EnlivenVariables(liveSet, action.Value); + // break; + // case DeleteStatement delete: + // liveSet[delete.Place.CoreVariable().Number] = true; + // break; + // case IfStatement _: + // case GotoStatement _: + // // We already or'ed together the live variables from successor blocks + // break; + // case ReturnStatement _: + // // As a sanity check, no variables should be live after return + // if (liveAfterStatement.Bits().AnyTrue()) + // throw new InvalidOperationException("Variables live after return statement"); + // // But the result variable might be live before it + // if (controlFlow.ReturnVariable.TypeIsNotEmpty) + // liveSet[Variable.Result.Number] = true; + // break; + // case ExitScopeStatement _: + // // TODO use end scope statement to track liminal state (see BorrowChecker) + // break; + // } + + // // For the next statement + // liveAfterStatement = liveSet; + // } + + // if (!oldLiveBeforeBlock.ValuesEqual(liveVariables.Before(block.Statements[0]))) + // foreach (var basicBlock in controlFlow.Edges.To(block) + // .Where(fromBlock => !blocksQueue.Contains(fromBlock)).ToList()) + // blocksQueue.Enqueue(basicBlock); + // } + // return liveVariables; + // } + + // private static void KillVariables(BitArray variables, IPlace place) + // { + // switch (place) + // { + // default: + // throw ExhaustiveMatch.Failed(place); + // case VariableReference variableReference: + // variables[variableReference.Variable.Number] = false; + // break; + // case FieldAccess fieldAccess: + // EnlivenVariables(variables, fieldAccess.Expression); + // break; + // } + // } + // private static void EnlivenVariables(BitArray variables, IValue value) + // { + // switch (value) + // { + // default: + // throw ExhaustiveMatch.Failed(value); + // case ConstructorCall constructorCall: + // foreach (var argument in constructorCall.Arguments) + // EnlivenVariables(variables, argument); + // break; + // case UnaryOperation unaryOperation: + // EnlivenVariables(variables, unaryOperation.Operand); + // break; + // case BinaryOperation binaryOperation: + // EnlivenVariables(variables, binaryOperation.LeftOperand); + // EnlivenVariables(variables, binaryOperation.RightOperand); + // break; + // case IPlace place: + // variables[place.CoreVariable().Number] = true; + // break; + // case FunctionCall functionCall: + // if (functionCall.Self != null) + // EnlivenVariables(variables, functionCall.Self); + // foreach (var argument in functionCall.Arguments) + // EnlivenVariables(variables, argument); + // break; + // case VirtualFunctionCall virtualFunctionCall: + // EnlivenVariables(variables, virtualFunctionCall.Self); + // foreach (var argument in virtualFunctionCall.Arguments) + // EnlivenVariables(variables, argument); + // break; + // case ConstructSome constructSome: + // EnlivenVariables(variables, constructSome.Value); + // break; + // case Constant _: + // // No variables + // break; + // case DeclaredValue _: + // throw new NotImplementedException(); + // case Conversion conversion: + // EnlivenVariables(variables, conversion.Operand); + // break; + // } + // } + //} +} diff --git a/Compiler.Semantics/README.md b/Compiler.Semantics/README.md new file mode 100644 index 00000000..72b0f398 --- /dev/null +++ b/Compiler.Semantics/README.md @@ -0,0 +1,35 @@ +# Azoth.Tools.Bootstrap.Compiler.Semantics + +There have been several approaches to the semantic analysis portion of the compiler. + +## Original Approach + +At first, it was thought that this compiler should be designed to plan for reimplementation in Azoth. Doing so imposed a number of constraints: + +1. Avoid Reference Cycles - borrow checking and reference counting in Azoth make reference cycles problematic. In particular, there was concern about links across the tree. To avoid those, the plan had been to store them as name or data type objects that could be used to look up the relevant nodes in a symbol tree. +2. Incremental Compilation - prevented the use of references to parent nodes even though these might be ok in Azoth because it was expected nodes would be shared between different versions of the tree. Also, it pushed for immutable data structures for all phases of the compiler. It was not planned that this compiler would actually implement incremental compilation though. +3. Arbitrary Compile Time Code Execution - means that there could be arbitrarily complex connections between which parts of the semantic tree must be evaluated before other parts. For example, the ability to declare the return type of a function to be the result of a meta-function and the ability of pure functions to be used in constant and generic argument contexts means that some functions will have to be fully analyzed and interpreted before other functions can even be typed. +4. Parallel Compilation - it must be possible to break the work into chunks while not producing undo contention. +5. Strong Typing - to avoid bugs involving missing values or values of incorrect types. + +Trying to support all of these while creating a compiler that could be rapidly developed and changed was challenging. C# seemed to make everything very verbose. A series of approaches were tried. Eventually a system allowing for a fairly direct expression of attribute grammars over the syntax tree was adopted. However, this makes strong typing challenging. Worse than that, because everything is computed on demand it makes order of execution and debugging very confusing. To simplify the implementation of the borrow checker an intermediate language (IL) was introduced. This was helpful, however, it was added as a later phase to semantic analysis which then became an issue because it couldn't be used for compile time code execution because it could only be generated after all semantic analysis was complete. + +## Current Plan + +Of the above constraints, all but arbitrary compile time code execution and strong typing have been dropped. It is hoped this will make development quicker, easier and more testable. To make things clear and easy, the plan to to have a series of clearly defined steps. Of the below list, only steps 2 through 4 are part of semantic the semantic analysis in this project. Steps 1 and 5 are included for context. + +1. Lexing and Parsing produce a concrete syntax tree. Each compilation unit can be done in parallel. +2. Build a simplified tree of declarations and members for the package. (Consider including simplified statements and expressions too) +3. Build lexical scopes (i.e. name tables) for name resolution. Each compilation unit can be done in parallel. +4. Analyze the semantics. This must mix name binding, type checking, IL generation and compile time code execution because they are interdependent. To simplify this as much as possible, this will be single threaded and will be free to mutate the tree as needed. All mutator method/properties will be `internal` to other phases can't mutate the tree. The `Lazy` type may or may not be used to make cycle detection easier. +5. Emit C code from the generated IL. + +### Semantic/IL Tree + +The current plan is that the tree generated for the semantic analysis and IL generation would be something roughly consistent with what could be used in an IL representation of an Azoth package. However, it may include additional data or optional data structures that wouldn't be used if parsing and generating IL. In order to try to keep this structure as simple as possible, it is planned the following abstractions relative to the concrete syntax trees will be made: + +* Unify partial classes +* Don't represent namespaces hierarchically, each declaration is fully qualified +* Don't include parenthesized expressions +* Unify the different kinds of blocks +* All loops as `loop`? diff --git a/Compiler.Semantics/SemanticAnalyzer.cs b/Compiler.Semantics/SemanticAnalyzer.cs new file mode 100644 index 00000000..477b2660 --- /dev/null +++ b/Compiler.Semantics/SemanticAnalyzer.cs @@ -0,0 +1,144 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Semantics.AST; +using Azoth.Tools.Bootstrap.Compiler.Semantics.AST.Tree; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Basic; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DeclarationNumbers; +using Azoth.Tools.Bootstrap.Compiler.Semantics.ILGen; +using Azoth.Tools.Bootstrap.Compiler.Semantics.LexicalScopes; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Liveness; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Symbols.Entities; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Symbols.Namespaces; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Validation; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.BindingMutability; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.DefiniteAssignment; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Moves; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Shadowing; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics +{ + public class SemanticAnalyzer + { + /// + /// Whether to store the liveness analysis for each function and method. + /// Default Value: false + /// + public bool SaveLivenessAnalysis { get; set; } = false; + + /// + /// Whether to store the reachability graphs for each function and method. + /// Default Value: false + /// + public bool SaveReachabilityGraphs { get; set; } = false; + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "OO")] + public PackageIL Check(PackageSyntax packageSyntax) + { + // If there are errors from the lex and parse phase, don't continue on + packageSyntax.Diagnostics.ThrowIfFatalErrors(); + + NamespaceSymbolBuilder.BuildNamespaceSymbols(packageSyntax); + + // Build up lexical scopes down to the declaration level + new LexicalScopesBuilder().BuildFor(packageSyntax); + + // Check the semantics of the package + var packageAbstractSyntax = CheckSemantics(packageSyntax); + + // If there are errors from the semantics phase, don't continue on + packageAbstractSyntax.Diagnostics.ThrowIfFatalErrors(); + + // -------------------------------------------------- + // This is where the representation transitions to IR + var declarationsIL = BuildIL(packageAbstractSyntax); + // -------------------------------------------------- + + // If there are errors from the previous phase, don't continue on + // TODO can the BuildIL() step introduce errors? + packageAbstractSyntax.Diagnostics.ThrowIfFatalErrors(); + + var entryPointIL = DetermineEntryPoint(declarationsIL, packageAbstractSyntax.Diagnostics); + + var references = packageSyntax.ReferencedPackages.ToFixedSet(); + return new PackageIL(packageAbstractSyntax.SymbolTree, packageAbstractSyntax.Diagnostics.Build(), references, declarationsIL, entryPointIL); + } + + private static Package CheckSemantics(PackageSyntax packageSyntax) + { + DeclarationNumberAssigner.AssignIn(packageSyntax.AllEntityDeclarations); + + // Resolve symbols for the entities + EntitySymbolBuilder.BuildFor(packageSyntax); + + var stringSymbol = packageSyntax.SymbolTrees + .GlobalSymbols + .OfType() + .SingleOrDefault(s => s.Name == "String"); + + // Basic Analysis includes: Name Binding, Type Checking, Constant Folding + BasicAnalyzer.Check(packageSyntax, stringSymbol); + + // If there are errors from the basic analysis phase, don't continue on + packageSyntax.Diagnostics.ThrowIfFatalErrors(); + +#if DEBUG + new SymbolValidator(packageSyntax.SymbolTree).Validate(packageSyntax.AllEntityDeclarations); + new TypeFulfillmentValidator().Validate(packageSyntax.AllEntityDeclarations); + new TypeKnownValidator().Validate(packageSyntax.AllEntityDeclarations); + new ExpressionSemanticsValidator().Validate(packageSyntax.AllEntityDeclarations); +#endif + + var package = new ASTBuilder().BuildPackage(packageSyntax); + + // From this point forward, analysis focuses on executable declarations (i.e. invocables and field initializers) + var executableDeclarations = package.AllDeclarations.OfType().ToFixedSet(); + + ShadowChecker.Check(executableDeclarations, package.Diagnostics); + + DataFlowAnalysis.Check(DefiniteAssignmentAnalyzer.Instance, executableDeclarations, package.SymbolTree, package.Diagnostics); + + DataFlowAnalysis.Check(BindingMutabilityAnalyzer.Instance, executableDeclarations, package.SymbolTree, package.Diagnostics); + + DataFlowAnalysis.Check(UseOfMovedValueAnalyzer.Instance, executableDeclarations, package.SymbolTree, package.Diagnostics); + + // TODO use DataFlowAnalysis to check for unused variables and report use of variables starting with `_` + + // Compute variable liveness needed by reachability analyzer + DataFlowAnalysis.Check(LivenessAnalyzer.Instance, executableDeclarations, package.SymbolTree, package.Diagnostics); + + // TODO remove live variables if SaveLivenessAnalysis is false + + return package; + } + + private static FixedList BuildIL(Package package) + { + var ilFactory = new ILFactory(); + var declarationBuilder = new DeclarationBuilder(ilFactory); + declarationBuilder.Build(package.AllDeclarations, package.SymbolTree); + return declarationBuilder.AllDeclarations.ToFixedList(); + } + + private static FunctionIL DetermineEntryPoint( + FixedList declarations, + Diagnostics diagnostics) + { + var mainFunctions = declarations.OfType() + .Where(f => f.Symbol.Name == "main") + .ToList(); + + // TODO warn on and remove main functions that don't have correct parameters or types + _ = diagnostics; + // TODO compiler error on multiple main functions + + return mainFunctions.SingleOrDefault(); + } + } +} diff --git a/Compiler.Semantics/Symbols/Entities/EntitySymbolBuilder.cs b/Compiler.Semantics/Symbols/Entities/EntitySymbolBuilder.cs new file mode 100644 index 00000000..0888da40 --- /dev/null +++ b/Compiler.Semantics/Symbols/Entities/EntitySymbolBuilder.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Errors; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Types; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Symbols.Entities +{ + public class EntitySymbolBuilder + { + private readonly Diagnostics diagnostics; + private readonly SymbolTreeBuilder symbolTree; + + private EntitySymbolBuilder(Diagnostics diagnostics, SymbolTreeBuilder symbolTree) + { + this.diagnostics = diagnostics; + this.symbolTree = symbolTree; + } + + public static void BuildFor(PackageSyntax package) + { + var builder = new EntitySymbolBuilder(package.Diagnostics, package.SymbolTree); + builder.Build(package.AllEntityDeclarations); + } + + private void Build(FixedSet entities) + { + // Process all classes first because they may be referenced by functions etc. + foreach (var @class in entities.OfType()) + BuildClassSymbol(@class); + + // Now resolve all other symbols (class declarations will already have symbols and won't be processed again) + foreach (var entity in entities) + BuildEntitySymbol(entity); + } + + /// + /// If the type has not been resolved, this resolves it. This function + /// also watches for type cycles and reports an error. + /// + private void BuildEntitySymbol(IEntityDeclarationSyntax entity) + { + switch (entity) + { + default: + throw ExhaustiveMatch.Failed(entity); + case IMethodDeclarationSyntax method: + BuildMethodSymbol(method); + break; + case IConstructorDeclarationSyntax constructor: + BuildConstructorSymbol(constructor); + break; + case IAssociatedFunctionDeclarationSyntax associatedFunction: + BuildAssociatedFunctionSymbol(associatedFunction); + break; + case IFieldDeclarationSyntax field: + BuildFieldSymbol(field); + break; + case IFunctionDeclarationSyntax syn: + BuildFunctionSymbol(syn); + break; + case IClassDeclarationSyntax syn: + BuildClassSymbol(syn); + break; + } + } + + private void BuildMethodSymbol(IMethodDeclarationSyntax method) + { + method.Symbol.BeginFulfilling(); + var declaringClassSymbol = method.DeclaringClass.Symbol.Result; + var resolver = new TypeResolver(method.File, diagnostics); + var selfParameterType = ResolveSelfParameterType(method.SelfParameter, method.DeclaringClass); + var parameterTypes = ResolveParameterTypes(resolver, method.Parameters, method.DeclaringClass); + var returnType = ResolveReturnType(method.ReturnType, resolver); + var symbol = new MethodSymbol(declaringClassSymbol, method.Name, selfParameterType, parameterTypes, returnType); + method.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + BuildSelParameterSymbol(symbol, method.SelfParameter, selfParameterType); + BuildParameterSymbols(symbol, method.Parameters, parameterTypes); + } + + private void BuildConstructorSymbol(IConstructorDeclarationSyntax constructor) + { + constructor.Symbol.BeginFulfilling(); + var selfParameterType = ResolveSelfParameterType(constructor.ImplicitSelfParameter, constructor.DeclaringClass); + var resolver = new TypeResolver(constructor.File, diagnostics); + var parameterTypes = ResolveParameterTypes(resolver, constructor.Parameters, constructor.DeclaringClass); + var declaringClassSymbol = constructor.DeclaringClass.Symbol.Result; + var symbol = new ConstructorSymbol(declaringClassSymbol, constructor.Name, parameterTypes); + constructor.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + BuildSelParameterSymbol(symbol, constructor.ImplicitSelfParameter, selfParameterType); + BuildParameterSymbols(symbol, constructor.Parameters, parameterTypes); + } + + private void BuildAssociatedFunctionSymbol(IAssociatedFunctionDeclarationSyntax associatedFunction) + { + associatedFunction.Symbol.BeginFulfilling(); + var resolver = new TypeResolver(associatedFunction.File, diagnostics); + var parameterTypes = ResolveParameterTypes(resolver, associatedFunction.Parameters, null); + var returnType = ResolveReturnType(associatedFunction.ReturnType, resolver); + var declaringClassSymbol = associatedFunction.DeclaringClass.Symbol.Result; + var symbol = new FunctionSymbol(declaringClassSymbol, associatedFunction.Name, parameterTypes, returnType); + associatedFunction.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + BuildParameterSymbols(symbol, associatedFunction.Parameters, parameterTypes); + } + + private FieldSymbol BuildFieldSymbol(IFieldDeclarationSyntax field) + { + if (field.Symbol.IsFulfilled) + return field.Symbol.Result; + + field.Symbol.BeginFulfilling(); + var resolver = new TypeResolver(field.File, diagnostics); + var type = resolver.Evaluate(field.Type); + var symbol = new FieldSymbol(field.DeclaringClass.Symbol.Result, field.Name, field.IsMutableBinding, type); + field.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + return symbol; + } + + private void BuildFunctionSymbol(IFunctionDeclarationSyntax function) + { + function.Symbol.BeginFulfilling(); + var resolver = new TypeResolver(function.File, diagnostics); + var parameterTypes = ResolveParameterTypes(resolver, function.Parameters, null); + var returnType = ResolveReturnType(function.ReturnType, resolver); + var symbol = new FunctionSymbol(function.ContainingNamespaceSymbol, function.Name, parameterTypes, returnType); + function.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + BuildParameterSymbols(symbol, function.Parameters, parameterTypes); + } + + private void BuildClassSymbol(IClassDeclarationSyntax @class) + { + if (!@class.Symbol.TryBeginFulfilling(AddCircularDefinitionError)) return; + + bool mutable = !(@class.MutableModifier is null); + var classType = new ObjectType(@class.ContainingNamespaceName, @class.Name, mutable, + ReferenceCapability.Shared); + + var symbol = new ObjectTypeSymbol(@class.ContainingNamespaceSymbol!, classType); + @class.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + @class.CreateDefaultConstructor(symbolTree); + + void AddCircularDefinitionError() + { + // TODO use something better than Name here which is an old name + diagnostics.Add(TypeError.CircularDefinition(@class.File, @class.NameSpan, @class)); + } + } + + private FixedList ResolveParameterTypes( + TypeResolver resolver, + IEnumerable parameters, + IClassDeclarationSyntax? declaringClass) + { + var types = new List(); + foreach (var parameter in parameters) + switch (parameter) + { + default: + throw ExhaustiveMatch.Failed(parameter); + case INamedParameterSyntax namedParameter: + { + var type = resolver.Evaluate(namedParameter.Type); + types.Add(type); + } + break; + case IFieldParameterSyntax fieldParameter: + { + var field = (declaringClass ?? throw new InvalidOperationException("Field parameter outside of class declaration")) + .Members.OfType() + .SingleOrDefault(f => f.Name == fieldParameter.Name); + if (field is null) + { + types.Add(DataType.Unknown); + fieldParameter.ReferencedSymbol.Fulfill(null); + // TODO report an error + throw new NotImplementedException(); + } + else + { + var fieldSymbol = BuildFieldSymbol(field); + fieldParameter.ReferencedSymbol.Fulfill(fieldSymbol); + types.Add(fieldSymbol.DataType); + } + } + break; + } + + return types.ToFixedList(); + } + + private void BuildParameterSymbols( + InvocableSymbol containingSymbol, + IEnumerable parameters, + IEnumerable types) + { + foreach (var (param, type) in parameters.Zip(types)) + { + switch (param) + { + default: + throw ExhaustiveMatch.Failed(param); + case INamedParameterSyntax namedParam: + { + var symbol = new VariableSymbol(containingSymbol, namedParam.Name, + namedParam.DeclarationNumber.Result, namedParam.IsMutableBinding, type); + namedParam.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + } + break; + case IFieldParameterSyntax _: + // Referenced field already assigned + break; + } + } + } + + private static ObjectType ResolveSelfParameterType( + ISelfParameterSyntax selfParameter, + IClassDeclarationSyntax declaringClass) + { + var selfType = declaringClass.Symbol.Result.DeclaresDataType; + if (selfParameter.MutableSelf) + selfType = selfType.ToConstructorSelf(); + return selfType; + } + + private void BuildSelParameterSymbol( + InvocableSymbol containingSymbol, + ISelfParameterSyntax param, + DataType type) + { + var symbol = new SelfParameterSymbol(containingSymbol, type); + param.Symbol.Fulfill(symbol); + symbolTree.Add(symbol); + } + + private static DataType ResolveReturnType( + ITypeSyntax? returnTypeSyntax, TypeResolver resolver) + { + var returnType = returnTypeSyntax != null + ? resolver.Evaluate(returnTypeSyntax) : DataType.Void; + return returnType; + } + } +} diff --git a/Compiler.Semantics/Symbols/Namespaces/NamespaceSymbolBuilder.cs b/Compiler.Semantics/Symbols/Namespaces/NamespaceSymbolBuilder.cs new file mode 100644 index 00000000..b924b102 --- /dev/null +++ b/Compiler.Semantics/Symbols/Namespaces/NamespaceSymbolBuilder.cs @@ -0,0 +1,75 @@ +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Symbols.Namespaces +{ + public class NamespaceSymbolBuilder : SyntaxWalker + { + private readonly SymbolTreeBuilder treeBuilder; + + private NamespaceSymbolBuilder(SymbolTreeBuilder treeBuilder) + { + this.treeBuilder = treeBuilder; + } + + public static void BuildNamespaceSymbols(PackageSyntax package) + { + var builder = new NamespaceSymbolBuilder(package.SymbolTree); + foreach (var compilationUnit in package.CompilationUnits) + builder.Walk(compilationUnit, package.Symbol); + } + + protected override void WalkNonNull(ISyntax syntax, NamespaceOrPackageSymbol containingSymbol) + { + switch (syntax) + { + case ICompilationUnitSyntax syn: + { + var sym = BuildNamespaceSymbol(containingSymbol, syn.ImplicitNamespaceName); + WalkChildren(syn, sym); + } + break; + case INamespaceDeclarationSyntax syn: + { + syn.ContainingNamespaceSymbol = containingSymbol; + // TODO correctly handle Global qualifier + var sym = BuildNamespaceSymbol(containingSymbol, syn.DeclaredNames); + syn.Symbol.Fulfill(sym); + WalkChildren(syn, sym); + } + break; + case INonMemberEntityDeclarationSyntax syn: + syn.ContainingNamespaceSymbol = containingSymbol; + break; + default: + // do nothing + return; + } + } + + private NamespaceOrPackageSymbol BuildNamespaceSymbol( + NamespaceOrPackageSymbol containingSymbol, + NamespaceName namespaces) + { + foreach (var nsName in namespaces.Segments) + { + var nsSymbol = treeBuilder.Children(containingSymbol) + .OfType() + .SingleOrDefault(c => c.Name == nsName); + if (nsSymbol is null) + { + nsSymbol = new NamespaceSymbol(containingSymbol, nsName); + treeBuilder.Add(nsSymbol); + } + + containingSymbol = nsSymbol; + } + + return containingSymbol; + } + } +} diff --git a/Compiler.Semantics/Types/TypeResolver.cs b/Compiler.Semantics/Types/TypeResolver.cs new file mode 100644 index 00000000..5c8e60a4 --- /dev/null +++ b/Compiler.Semantics/Types/TypeResolver.cs @@ -0,0 +1,80 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Errors; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Types +{ + /// + /// Analyzes an to evaluate which type it refers to. + /// + public class TypeResolver + { + private readonly CodeFile file; + private readonly Diagnostics diagnostics; + + public TypeResolver(CodeFile file, Diagnostics diagnostics) + { + this.file = file; + this.diagnostics = diagnostics; + } + + [return: NotNullIfNotNull("typeSyntax")] + public DataType? Evaluate(ITypeSyntax? typeSyntax) + { + switch (typeSyntax) + { + default: + throw ExhaustiveMatch.Failed(typeSyntax); + case null: + return null; + case ITypeNameSyntax typeName: + { + var symbolPromises = typeName.LookupInContainingScope().ToFixedList(); + switch (symbolPromises.Count) + { + case 0: + diagnostics.Add(NameBindingError.CouldNotBindName(file, typeName.Span)); + typeName.ReferencedSymbol.Fulfill(null); + typeName.NamedType = DataType.Unknown; + break; + case 1: + var symbol = symbolPromises.Single().Result; + typeName.ReferencedSymbol.Fulfill(symbol); + typeName.NamedType = symbol.DeclaresDataType; + break; + default: + diagnostics.Add(NameBindingError.AmbiguousName(file, typeName.Span)); + typeName.ReferencedSymbol.Fulfill(null); + typeName.NamedType = DataType.Unknown; + break; + } + break; + } + case ICapabilityTypeSyntax referenceCapability: + { + var type = Evaluate(referenceCapability.ReferentType); + if (type == DataType.Unknown) + return DataType.Unknown; + if (type is ReferenceType referenceType) + referenceCapability.NamedType = referenceType.To(referenceCapability.Capability); + else + referenceCapability.NamedType = DataType.Unknown; + break; + } + case IOptionalTypeSyntax optionalType: + { + var referent = Evaluate(optionalType.Referent); + return optionalType.NamedType = new OptionalType(referent); + } + } + + return typeSyntax.NamedType ?? throw new InvalidOperationException(); + } + } +} diff --git a/Compiler.Semantics/Validation/ExpressionSemanticsValidator.cs b/Compiler.Semantics/Validation/ExpressionSemanticsValidator.cs new file mode 100644 index 00000000..2cb86fe8 --- /dev/null +++ b/Compiler.Semantics/Validation/ExpressionSemanticsValidator.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Validation +{ + /// + /// Validates all expressions have been assigned expression semantics + /// + public class ExpressionSemanticsValidator : SyntaxWalker + { + public void Validate(IEnumerable entityDeclarations) + { + foreach (var declaration in entityDeclarations) + WalkNonNull(declaration); + } + + protected override void WalkNonNull(ISyntax syntax) + { + switch (syntax) + { + case IClassDeclarationSyntax _: + // Don't recur into body, we will see those as separate members + return; + case IExpressionSyntax expression: + WalkChildren(expression); + expression.Semantics.Assigned(); + return; + case ITypeSyntax _: + return; + } + + WalkChildren(syntax); + } + } +} diff --git a/Compiler.Semantics/Validation/SymbolValidator.cs b/Compiler.Semantics/Validation/SymbolValidator.cs new file mode 100644 index 00000000..2980745c --- /dev/null +++ b/Compiler.Semantics/Validation/SymbolValidator.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Validation +{ + public class SymbolValidator : SyntaxWalker + { + private readonly ISymbolTree symbolTree; + + public SymbolValidator(ISymbolTree symbolTree) + { + this.symbolTree = symbolTree; + } + + public void Validate(IEnumerable entityDeclaration) + { + foreach (var declaration in entityDeclaration) + WalkNonNull(declaration); + } + + protected override void WalkNonNull(ISyntax syntax) + { + switch (syntax) + { + case IClassDeclarationSyntax syn: + CheckSymbol(syn, syn.Symbol); + // Don't recur into body, we will see those as separate members + return; + case IFieldDeclarationSyntax syn: + CheckSymbol(syn, syn.Symbol); + break; + case IEntityDeclarationSyntax syn: + CheckSymbol(syn, syn.Symbol); + break; + case INamedParameterSyntax syn: + CheckSymbol(syn, syn.Symbol); + break; + case IFieldParameterSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case ISelfParameterSyntax syn: + CheckSymbol(syn, syn.Symbol); + break; + case IVariableDeclarationStatementSyntax syn: + CheckSymbol(syn, syn.Symbol); + break; + case IDeclarationSyntax syn: + CheckSymbol(syn, syn.Symbol); + break; + case INameExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case ISelfExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case ITypeNameSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case IMutateExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case IShareExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case IMoveExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case INewObjectExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case IUnqualifiedInvocationExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + case IQualifiedInvocationExpressionSyntax syn: + CheckReferencedSymbol(syn, syn.ReferencedSymbol); + break; + } + + WalkChildren(syntax); + } + + private void CheckSymbol(ISyntax syntax, IPromise promise) + { + if (!promise.IsFulfilled) + throw new Exception($"Syntax doesn't have a symbol '{syntax}'"); + + if (promise.Result is null) + throw new Exception($"Syntax has unknown symbol '{syntax}'"); + + if (!symbolTree.Contains(promise.Result)) + throw new Exception($"Symbol isn't in the symbol tree '{promise.Result}'"); + } + + private static void CheckReferencedSymbol(ISyntax syntax, IPromise promise) + { + if (!promise.IsFulfilled) + throw new Exception($"Syntax doesn't have a referenced symbol '{syntax}'"); + + if (promise.Result is null) + throw new Exception($"Syntax has unknown referenced symbol '{syntax}'"); + } + } +} diff --git a/Compiler.Semantics/Validation/TypeFulfillmentValidator.cs b/Compiler.Semantics/Validation/TypeFulfillmentValidator.cs new file mode 100644 index 00000000..1cf88f86 --- /dev/null +++ b/Compiler.Semantics/Validation/TypeFulfillmentValidator.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Validation +{ + /// + /// Validates that all types are fulfilled. That is that everything as an + /// assigned type, even if that type is Unknown. + /// + public class TypeFulfillmentValidator : SyntaxWalker + { + public void Validate(IEnumerable entityDeclarations) + { + foreach (var declaration in entityDeclarations) + WalkNonNull(declaration); + } + + protected override void WalkNonNull(ISyntax syntax) + { + switch (syntax) + { + case IClassDeclarationSyntax _: + // Don't recur into body, we will see those as separate members + return; + case ITypeSyntax type: + WalkChildren(type); + type.NamedType.Assigned(); + return; + case IForeachExpressionSyntax foreachExpression: + WalkChildren(foreachExpression); + foreachExpression.DataType.Assigned(); + return; + case IExpressionSyntax expression: + WalkChildren(expression); + expression.DataType.Assigned(); + return; + } + + WalkChildren(syntax); + } + } +} diff --git a/Compiler.Semantics/Validation/TypeKnownValidator.cs b/Compiler.Semantics/Validation/TypeKnownValidator.cs new file mode 100644 index 00000000..2d459948 --- /dev/null +++ b/Compiler.Semantics/Validation/TypeKnownValidator.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.CST; +using Azoth.Tools.Bootstrap.Compiler.CST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Validation +{ + /// + /// Validates that all types are known. + /// + public class TypeKnownValidator : SyntaxWalker + { + public void Validate(IEnumerable entityDeclarations) + { + foreach (var declaration in entityDeclarations) + WalkNonNull(declaration); + } + + protected override void WalkNonNull(ISyntax syntax) + { + switch (syntax) + { + case IClassDeclarationSyntax classDeclaration: + classDeclaration.Symbol.Result.DeclaresDataType.Known(); + // Don't recur into body, we will see those as separate members + return; + case IConstructorDeclarationSyntax constructorDeclaration: + WalkChildren(constructorDeclaration); + constructorDeclaration.ImplicitSelfParameter.Symbol.Result.DataType.Known(); + constructorDeclaration.Symbol.Result.ReturnDataType.Known(); + return; + case IMethodDeclarationSyntax methodDeclaration: + WalkChildren(methodDeclaration); + methodDeclaration.Symbol.Result.ReturnDataType.Known(); + return; + case IFunctionDeclarationSyntax functionDeclaration: + WalkChildren(functionDeclaration); + functionDeclaration.Symbol.Result.ReturnDataType.Known(); + return; + case IAssociatedFunctionDeclarationSyntax associatedFunctionDeclaration: + WalkChildren(associatedFunctionDeclaration); + associatedFunctionDeclaration.Symbol.Result.ReturnDataType.Known(); + return; + case IParameterSyntax parameter: + WalkChildren(parameter); + parameter.DataType.Known(); + return; + case IFieldDeclarationSyntax fieldDeclaration: + WalkChildren(fieldDeclaration); + fieldDeclaration.Symbol.Result.DataType.Known(); + return; + case ITypeSyntax type: + WalkChildren(type); + type.NamedType.Known(); + return; + case IVariableDeclarationStatementSyntax variableDeclaration: + WalkChildren(variableDeclaration); + variableDeclaration.Symbol.Result.DataType.Known(); + return; + case IForeachExpressionSyntax foreachExpression: + WalkChildren(foreachExpression); + foreachExpression.DataType.Known(); + foreachExpression.Symbol.Result.DataType.Known(); + return; + case IExpressionSyntax expression: + WalkChildren(expression); + expression.DataType.Known(); + return; + } + + WalkChildren(syntax); + } + } +} diff --git a/Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalysis.cs b/Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalysis.cs new file mode 100644 index 00000000..f91a0a28 --- /dev/null +++ b/Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalysis.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Errors; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.BindingMutability +{ + /// + /// Uses a data flow analysis of variables that are definitely unassigned to + /// determine if binding mutability is violated. + /// + public class BindingMutabilityAnalysis : IForwardDataFlowAnalysis + { + private readonly IExecutableDeclaration declaration; + private readonly ISymbolTree symbolTree; + private readonly CodeFile file; + private readonly Diagnostics diagnostics; + + public BindingMutabilityAnalysis(IExecutableDeclaration declaration, ISymbolTree symbolTree, Diagnostics diagnostics) + { + this.declaration = declaration; + this.symbolTree = symbolTree; + file = declaration.File; + this.diagnostics = diagnostics; + } + + public VariableFlags StartState() + { + // All variables start definitely unassigned + var definitelyUnassigned = new VariableFlags(declaration, symbolTree, true); + if (declaration is IInvocableDeclaration invocable) + { + // All parameters are assigned + var namedParameters = invocable.Parameters.OfType(); + var parameterSymbols = namedParameters.Select(p => p.Symbol); + definitelyUnassigned = definitelyUnassigned.Set(parameterSymbols, false); + } + return definitelyUnassigned; + } + + public VariableFlags Assignment( + IAssignmentExpression assignmentExpression, + VariableFlags definitelyUnassigned) + { + switch (assignmentExpression.LeftOperand) + { + case INameExpression identifier: + var symbol = identifier.ReferencedSymbol; + if (!symbol.IsMutableBinding && definitelyUnassigned[symbol] == false) + diagnostics.Add(SemanticError.VariableMayAlreadyBeAssigned(file, identifier.Span, identifier.ReferencedSymbol.Name)); + return definitelyUnassigned.Set(symbol, false); + case IFieldAccessExpression _: + return definitelyUnassigned; + default: + throw new NotImplementedException("Complex assignments not yet implemented"); + } + } + + public VariableFlags IdentifierName( + INameExpression nameExpression, + VariableFlags definitelyUnassigned) + { + return definitelyUnassigned; + } + + public VariableFlags VariableDeclaration( + IVariableDeclarationStatement variableDeclaration, + VariableFlags definitelyUnassigned) + { + if (variableDeclaration.Initializer is null) + return definitelyUnassigned; + return definitelyUnassigned.Set(variableDeclaration.Symbol, false); + } + + public VariableFlags VariableDeclaration( + IForeachExpression foreachExpression, + VariableFlags definitelyUnassigned) + { + return definitelyUnassigned.Set(foreachExpression.Symbol, false); + } + } +} diff --git a/Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalyzer.cs b/Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalyzer.cs new file mode 100644 index 00000000..6055bd3e --- /dev/null +++ b/Compiler.Semantics/Variables/BindingMutability/BindingMutabilityAnalyzer.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.BindingMutability +{ + public class BindingMutabilityAnalyzer : IForwardDataFlowAnalyzer + { + #region Singleton + public static readonly BindingMutabilityAnalyzer Instance = new BindingMutabilityAnalyzer(); + + private BindingMutabilityAnalyzer() { } + #endregion + + public IForwardDataFlowAnalysis BeginAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics diagnostics) + { + return new BindingMutabilityAnalysis(declaration, symbolTree, diagnostics); + } + } +} diff --git a/Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalysis.cs b/Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalysis.cs new file mode 100644 index 00000000..79904e5a --- /dev/null +++ b/Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalysis.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Errors; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.DefiniteAssignment +{ + // TODO check definite assignment of fields in constructors + internal class DefiniteAssignmentAnalysis : IForwardDataFlowAnalysis + { + private readonly IExecutableDeclaration declaration; + private readonly ISymbolTree symbolTree; + private readonly CodeFile file; + private readonly Diagnostics diagnostics; + + public DefiniteAssignmentAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics diagnostics) + { + this.declaration = declaration; + this.symbolTree = symbolTree; + this.file = declaration.File; + this.diagnostics = diagnostics; + } + + public VariableFlags StartState() + { + var definitelyAssigned = new VariableFlags(declaration, symbolTree, false); + if (declaration is IInvocableDeclaration invocable) + { + // All parameters are assigned + var namedParameters = invocable.Parameters.OfType(); + var parameterSymbols = namedParameters.Select(p => p.Symbol); + definitelyAssigned = definitelyAssigned.Set(parameterSymbols, true); + } + return definitelyAssigned; + } + + public VariableFlags Assignment( + IAssignmentExpression assignmentExpression, + VariableFlags definitelyAssigned) + { + return assignmentExpression.LeftOperand switch + { + INameExpression identifier => + definitelyAssigned.Set(identifier.ReferencedSymbol, true), + IFieldAccessExpression _ => definitelyAssigned, + _ => throw new NotImplementedException("Complex assignments not yet implemented") + }; + } + + public VariableFlags IdentifierName( + INameExpression nameExpression, + VariableFlags definitelyAssigned) + { + if (definitelyAssigned[nameExpression.ReferencedSymbol] == false) + diagnostics.Add(SemanticError.VariableMayNotHaveBeenAssigned(file, + nameExpression.Span, nameExpression.ReferencedSymbol.Name)); + + return definitelyAssigned; + } + + public VariableFlags VariableDeclaration( + IVariableDeclarationStatement variableDeclaration, + VariableFlags definitelyAssigned) + { + if (variableDeclaration.Initializer is null) + return definitelyAssigned; + return definitelyAssigned.Set(variableDeclaration.Symbol, true); + } + + public VariableFlags VariableDeclaration( + IForeachExpression foreachExpression, + VariableFlags definitelyAssigned) + { + return definitelyAssigned.Set(foreachExpression.Symbol, true); + } + } +} diff --git a/Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalyzer.cs b/Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalyzer.cs new file mode 100644 index 00000000..6e89bdb6 --- /dev/null +++ b/Compiler.Semantics/Variables/DefiniteAssignment/DefiniteAssignmentAnalyzer.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.DefiniteAssignment +{ + public class DefiniteAssignmentAnalyzer : IForwardDataFlowAnalyzer + { + #region Singleton + public static readonly DefiniteAssignmentAnalyzer Instance = new DefiniteAssignmentAnalyzer(); + + private DefiniteAssignmentAnalyzer() { } + #endregion + + public IForwardDataFlowAnalysis BeginAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics diagnostics) + { + return new DefiniteAssignmentAnalysis(declaration, symbolTree, diagnostics); + } + } +} diff --git a/Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalysis.cs b/Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalysis.cs new file mode 100644 index 00000000..59902fcf --- /dev/null +++ b/Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalysis.cs @@ -0,0 +1,106 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Errors; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Moves +{ + /// + /// Uses a data flow analysis of variables that may have their value moved + /// out of them to check for use of possibly moved value. + /// + /// The variable flags used by this checker indicate that a variable may have + /// its value moved. Variables not yet declared or assigned vacuously haven't + /// been moved from. + /// + public class UseOfMovedValueAnalysis : IForwardDataFlowAnalysis + { + private readonly IExecutableDeclaration declaration; + private readonly ISymbolTree symbolTree; + private readonly CodeFile file; + private readonly Diagnostics diagnostics; + + public UseOfMovedValueAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics diagnostics) + { + this.declaration = declaration; + this.symbolTree = symbolTree; + file = declaration.File; + this.diagnostics = diagnostics; + } + + public VariableFlags StartState() + { + // All variables start without possibly having their values moved out of them + return new VariableFlags(declaration, symbolTree, false); + } + + public VariableFlags Assignment( + IAssignmentExpression assignmentExpression, + VariableFlags possiblyMoved) + { + switch (assignmentExpression.LeftOperand) + { + case INameExpression identifierName: + // We are assigning into this variable so it definitely has a value now + var symbol = identifierName.ReferencedSymbol; + return possiblyMoved.Set(symbol, false); + case IFieldAccessExpression _: + return possiblyMoved; + default: + throw new NotImplementedException("Complex assignments not yet implemented"); + } + } + + public VariableFlags IdentifierName( + INameExpression nameExpression, + VariableFlags possiblyMoved) + { + var symbol = nameExpression.ReferencedSymbol; + if (possiblyMoved[symbol] == true) + diagnostics.Add(SemanticError.UseOfPossiblyMovedValue(file, nameExpression.Span)); + + var valueSemantics = nameExpression.Semantics; + // TODO this isn't correct, but for now fields don't have proper move, borrow handling + //?? nameExpression.Type.Assigned().OldValueSemantics; + switch (valueSemantics) + { + case ExpressionSemantics.Move: + case ExpressionSemantics.Acquire: + return possiblyMoved.Set(symbol, true); + case ExpressionSemantics.Copy: + case ExpressionSemantics.Borrow: + case ExpressionSemantics.Share: + case ExpressionSemantics.Void: + case ExpressionSemantics.Never: + case ExpressionSemantics.CreateReference: + // If it were move or copy, that would have been set to the ExpressionSemantics + // Not moving value + return possiblyMoved; + default: + throw ExhaustiveMatch.Failed(valueSemantics); + } + } + + public VariableFlags VariableDeclaration( + IVariableDeclarationStatement variableDeclaration, + VariableFlags possiblyMoved) + { + // No affect on state since it should already be false + return possiblyMoved; + } + + public VariableFlags VariableDeclaration( + IForeachExpression foreachExpression, + VariableFlags possiblyMoved) + { + // No affect on state since it should already be false + return possiblyMoved; + } + } +} diff --git a/Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalyzer.cs b/Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalyzer.cs new file mode 100644 index 00000000..19be5ab9 --- /dev/null +++ b/Compiler.Semantics/Variables/Moves/UseOfMovedValueAnalyzer.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.DataFlow; +using Azoth.Tools.Bootstrap.Compiler.Symbols.Trees; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Moves +{ + public class UseOfMovedValueAnalyzer : IForwardDataFlowAnalyzer + { + #region Singleton + public static readonly UseOfMovedValueAnalyzer Instance = new UseOfMovedValueAnalyzer(); + + private UseOfMovedValueAnalyzer() { } + #endregion + + public IForwardDataFlowAnalysis BeginAnalysis( + IExecutableDeclaration declaration, + ISymbolTree symbolTree, + Diagnostics diagnostics) + { + return new UseOfMovedValueAnalysis(declaration, symbolTree, diagnostics); + } + } +} diff --git a/Compiler.Semantics/Variables/Shadowing/BindingScope.cs b/Compiler.Semantics/Variables/Shadowing/BindingScope.cs new file mode 100644 index 00000000..212c8b10 --- /dev/null +++ b/Compiler.Semantics/Variables/Shadowing/BindingScope.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Shadowing +{ + public abstract class BindingScope + { + public bool Lookup(Name name, [NotNullWhen(true)] out VariableBinding? binding) + { + return LookupWithoutNumber(name, out binding); + } + + protected abstract bool LookupWithoutNumber(Name name, [NotNullWhen(true)] out VariableBinding? binding); + + /// + /// Indicates that some nested scope declared a variable binding. + /// + protected internal abstract void NestedBindingDeclared(VariableBinding binding); + } +} diff --git a/Compiler.Semantics/Variables/Shadowing/EmptyBindingScope.cs b/Compiler.Semantics/Variables/Shadowing/EmptyBindingScope.cs new file mode 100644 index 00000000..5ca6522a --- /dev/null +++ b/Compiler.Semantics/Variables/Shadowing/EmptyBindingScope.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Shadowing +{ + public class EmptyBindingScope : BindingScope + { + #region Singleton + public static readonly BindingScope Instance = new EmptyBindingScope(); + + private EmptyBindingScope() { } + #endregion + + protected override bool LookupWithoutNumber(Name name, [NotNullWhen(true)] out VariableBinding? binding) + { + binding = null; + return false; + } + + protected internal override void NestedBindingDeclared(VariableBinding binding) + { + // Empty scope has no bindings, so nested bindings don't matter + } + } +} diff --git a/Compiler.Semantics/Variables/Shadowing/ShadowChecker.cs b/Compiler.Semantics/Variables/Shadowing/ShadowChecker.cs new file mode 100644 index 00000000..29ecd240 --- /dev/null +++ b/Compiler.Semantics/Variables/Shadowing/ShadowChecker.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.AST.Walkers; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Semantics.Errors; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Shadowing +{ + /// + /// Enforces rules disallowing local variable shadowing + /// + internal class ShadowChecker : AbstractSyntaxWalker + { + private readonly CodeFile file; + private readonly Diagnostics diagnostics; + + private ShadowChecker(CodeFile file, Diagnostics diagnostics) + { + this.file = file; + this.diagnostics = diagnostics; + } + + public static void Check(IEnumerable declarations, Diagnostics diagnostics) + { + foreach (var declaration in declarations) + new ShadowChecker(declaration.File, diagnostics).Walk(declaration, EmptyBindingScope.Instance); + } + + protected override void WalkNonNull(IAbstractSyntax syntax, BindingScope bindingScope) + { + switch (syntax) + { + case IConcreteInvocableDeclaration syn: + foreach (var parameter in syn.Parameters.OfType()) + bindingScope = new VariableBindingScope(bindingScope, parameter); + break; + case IFieldDeclaration syn: + WalkChildren(syn, bindingScope); + break; + case IBodyOrBlock syn: + foreach (var statement in syn.Statements) + { + WalkNonNull(statement, bindingScope); + // Each variable declaration establishes a new binding scope + if (statement is IVariableDeclarationStatement variableDeclaration) + bindingScope = new VariableBindingScope(bindingScope, variableDeclaration); + } + return; + case IVariableDeclarationStatement syn: + { + WalkChildren(syn, bindingScope); + if (!bindingScope.Lookup(syn.Symbol.Name, out var binding)) return; + if (binding.MutableBinding) + diagnostics.Add(SemanticError.CantRebindMutableBinding(file, syn.NameSpan)); + else if (syn.Symbol.IsMutableBinding) + diagnostics.Add(SemanticError.CantRebindAsMutableBinding(file, syn.NameSpan)); + return; + } + case INameExpression syn: + { + // This checks for cases where a variable was shadowed, but then used later + if (!bindingScope.Lookup(syn.ReferencedSymbol.Name, out var binding)) return; + if (binding.WasShadowedBy.Any()) + diagnostics.Add(SemanticError.CantShadow(file, binding.WasShadowedBy[^1].NameSpan, syn.Span)); + return; + } + case IDeclaration _: + throw new InvalidOperationException($"Can't shadow check declaration of type {syntax.GetType().Name}"); + } + + WalkChildren(syntax, bindingScope); + } + } +} diff --git a/Compiler.Semantics/Variables/Shadowing/VariableBinding.cs b/Compiler.Semantics/Variables/Shadowing/VariableBinding.cs new file mode 100644 index 00000000..e235023f --- /dev/null +++ b/Compiler.Semantics/Variables/Shadowing/VariableBinding.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Shadowing +{ + public class VariableBinding + { + public bool MutableBinding { get; } + public Name Name { get; } + public TextSpan NameSpan { get; } + public IReadOnlyList WasShadowedBy => wasShadowedBy; + private readonly List wasShadowedBy = new List(); + + public VariableBinding(INamedParameter parameter) + { + MutableBinding = parameter.Symbol.IsMutableBinding; + Name = parameter.Symbol.Name; + NameSpan = parameter.Span; + } + + public VariableBinding(IVariableDeclarationStatement variableDeclaration) + { + MutableBinding = variableDeclaration.Symbol.IsMutableBinding; + Name = variableDeclaration.Symbol.Name; + NameSpan = variableDeclaration.NameSpan; + } + + public void NestedBindingDeclared(VariableBinding binding) + { + if (Name == binding.Name) + wasShadowedBy.Add(binding); + } + } +} diff --git a/Compiler.Semantics/Variables/Shadowing/VariableBindingScope.cs b/Compiler.Semantics/Variables/Shadowing/VariableBindingScope.cs new file mode 100644 index 00000000..e30b6cab --- /dev/null +++ b/Compiler.Semantics/Variables/Shadowing/VariableBindingScope.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Compiler.AST; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Semantics.Variables.Shadowing +{ + public class VariableBindingScope : BindingScope + { + public BindingScope ContainingScope { get; } + public VariableBinding VariableBinding { get; } + + public VariableBindingScope( + BindingScope containingScope, + INamedParameter parameter) + { + ContainingScope = containingScope; + VariableBinding = new VariableBinding(parameter); + ContainingScope.NestedBindingDeclared(VariableBinding); + } + + public VariableBindingScope(BindingScope containingScope, + IVariableDeclarationStatement variableDeclaration) + { + ContainingScope = containingScope; + VariableBinding = new VariableBinding(variableDeclaration); + ContainingScope.NestedBindingDeclared(VariableBinding); + } + + protected override bool LookupWithoutNumber(Name name, [NotNullWhen(true)] out VariableBinding? binding) + { + if (VariableBinding.Name != name) + return ContainingScope.Lookup(name, out binding); + + binding = VariableBinding; + return true; + } + + protected internal override void NestedBindingDeclared(VariableBinding binding) + { + VariableBinding.NestedBindingDeclared(binding); + ContainingScope.NestedBindingDeclared(binding); + } + } +} diff --git a/Compiler.Symbols/BindingSymbol.cs b/Compiler.Symbols/BindingSymbol.cs new file mode 100644 index 00000000..801a2c1a --- /dev/null +++ b/Compiler.Symbols/BindingSymbol.cs @@ -0,0 +1,26 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + [Closed( + typeof(NamedBindingSymbol), + typeof(SelfParameterSymbol))] + public abstract class BindingSymbol : Symbol + { + public override PackageSymbol? Package { get; } + public new Name? Name { get; } + public bool IsMutableBinding { get; } + public DataType DataType { get; } + + protected BindingSymbol(Symbol containingSymbol, Name? name, bool isMutableBinding, DataType dataType) + : base(containingSymbol, name) + { + Package = containingSymbol.Package; + Name = name; + IsMutableBinding = isMutableBinding; + DataType = dataType; + } + } +} diff --git a/Compiler.Symbols/Compiler.Symbols.csproj b/Compiler.Symbols/Compiler.Symbols.csproj new file mode 100644 index 00000000..7ad6618f --- /dev/null +++ b/Compiler.Symbols/Compiler.Symbols.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Symbols + Azoth.Tools.Bootstrap.Compiler.Symbols + enable + + + + DEBUG;TRACE + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Compiler.Symbols/ConstructorSymbol.cs b/Compiler.Symbols/ConstructorSymbol.cs new file mode 100644 index 00000000..4eeef1a2 --- /dev/null +++ b/Compiler.Symbols/ConstructorSymbol.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + public sealed class ConstructorSymbol : InvocableSymbol + { + public new ObjectTypeSymbol ContainingSymbol { get; } + public new ObjectType ReturnDataType { get; } + + public ConstructorSymbol( + ObjectTypeSymbol containingSymbol, + Name? name, + FixedList parameterDataTypes) + : base(containingSymbol, name, parameterDataTypes, + containingSymbol.DeclaresDataType.ToConstructorReturn()) + { + ContainingSymbol = containingSymbol; + ReturnDataType = containingSymbol.DeclaresDataType.ToConstructorReturn(); + } + + public static ConstructorSymbol CreateDefault(ObjectTypeSymbol containingSymbol) + { + return new ConstructorSymbol(containingSymbol, null, FixedList.Empty); + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is ConstructorSymbol otherConstructor + && ContainingSymbol == otherConstructor.ContainingSymbol + && Name == otherConstructor.Name + && ParameterDataTypes.SequenceEqual(otherConstructor.ParameterDataTypes); + } + + public override int GetHashCode() + { + return HashCode.Combine(ContainingSymbol, Name, ParameterDataTypes); + } + + public override string ToILString() + { + var name = Name is null ? $" {Name}" : ""; + return $"{ContainingSymbol}::new{name}({string.Join(", ", ParameterDataTypes)})"; + } + } +} diff --git a/Compiler.Symbols/FieldSymbol.cs b/Compiler.Symbols/FieldSymbol.cs new file mode 100644 index 00000000..a0d27c71 --- /dev/null +++ b/Compiler.Symbols/FieldSymbol.cs @@ -0,0 +1,43 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + public sealed class FieldSymbol : NamedBindingSymbol + { + public new TypeSymbol ContainingSymbol { get; } + + public FieldSymbol( + TypeSymbol containingSymbol, + Name name, + bool isMutableBinding, + DataType dataType) + : base(containingSymbol, name, isMutableBinding, dataType) + { + ContainingSymbol = containingSymbol; + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is FieldSymbol otherField + && ContainingSymbol == otherField.ContainingSymbol + && Name == otherField.Name + && IsMutableBinding == otherField.IsMutableBinding + && DataType == otherField.DataType; + } + + public override int GetHashCode() + { + return HashCode.Combine(ContainingSymbol, Name, IsMutableBinding, DataType); + } + + public override string ToILString() + { + var mutable = IsMutableBinding ? "var" : "let"; + return $"{ContainingSymbol}::{mutable} {Name}: {DataType}"; + } + } +} diff --git a/Compiler.Symbols/FunctionOrMethodSymbol.cs b/Compiler.Symbols/FunctionOrMethodSymbol.cs new file mode 100644 index 00000000..ae0a8776 --- /dev/null +++ b/Compiler.Symbols/FunctionOrMethodSymbol.cs @@ -0,0 +1,25 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + [Closed( + typeof(FunctionSymbol), + typeof(MethodSymbol))] + public abstract class FunctionOrMethodSymbol : InvocableSymbol + { + public new Name Name { get; } + + protected FunctionOrMethodSymbol( + Symbol containingSymbol, + Name name, + FixedList parameterDataTypes, + DataType returnDataType) + : base(containingSymbol, name, parameterDataTypes, returnDataType) + { + Name = name; + } + } +} diff --git a/Compiler.Symbols/FunctionSymbol.cs b/Compiler.Symbols/FunctionSymbol.cs new file mode 100644 index 00000000..e910a202 --- /dev/null +++ b/Compiler.Symbols/FunctionSymbol.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + /// + /// A symbol for a function + /// + public sealed class FunctionSymbol : FunctionOrMethodSymbol + { + public new Name Name { get; } + + public FunctionSymbol( + Symbol containingSymbol, + Name name, + FixedList parameterDataTypes, + DataType returnDataType) + : base(containingSymbol, name, parameterDataTypes, returnDataType) + { + Name = name; + } + + public FunctionSymbol( + Symbol containingSymbol, + Name name, + FixedList parameterDataTypes) + : this(containingSymbol, name, parameterDataTypes, DataType.Void) + { + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is FunctionSymbol otherFunction + && ContainingSymbol == otherFunction.ContainingSymbol + && Name == otherFunction.Name + && ParameterDataTypes.SequenceEqual(otherFunction.ParameterDataTypes) + && ReturnDataType == otherFunction.ReturnDataType; + } + + public override int GetHashCode() + { + return HashCode.Combine(ContainingSymbol, Name, ParameterDataTypes, ReturnDataType); + } + + public override string ToILString() + { + return $"{ContainingSymbol}.{Name}({string.Join(", ", ParameterDataTypes)}) -> {ReturnDataType}"; + } + } +} diff --git a/Compiler.Symbols/InternalsVisibleTo.cs b/Compiler.Symbols/InternalsVisibleTo.cs new file mode 100644 index 00000000..66f2f800 --- /dev/null +++ b/Compiler.Symbols/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols")] diff --git a/Compiler.Symbols/InvocableSymbol.cs b/Compiler.Symbols/InvocableSymbol.cs new file mode 100644 index 00000000..ade4f941 --- /dev/null +++ b/Compiler.Symbols/InvocableSymbol.cs @@ -0,0 +1,32 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + [Closed( + typeof(FunctionOrMethodSymbol), + typeof(ConstructorSymbol))] + public abstract class InvocableSymbol : Symbol + { + public new Symbol ContainingSymbol { get; } + public new Name? Name { get; } + public FixedList ParameterDataTypes { get; } + public int Arity => ParameterDataTypes.Count; + public DataType ReturnDataType { get; } + + protected InvocableSymbol( + Symbol containingSymbol, + Name? name, + FixedList parameterDataTypes, + DataType returnDataType) + : base(containingSymbol, name) + { + ContainingSymbol = containingSymbol; + Name = name; + ParameterDataTypes = parameterDataTypes; + ReturnDataType = returnDataType; + } + } +} diff --git a/Compiler.Symbols/MethodSymbol.cs b/Compiler.Symbols/MethodSymbol.cs new file mode 100644 index 00000000..72251f0d --- /dev/null +++ b/Compiler.Symbols/MethodSymbol.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + public sealed class MethodSymbol : FunctionOrMethodSymbol + { + public new TypeSymbol ContainingSymbol { get; } + public new Name Name { get; } + public DataType SelfDataType { get; } + + public MethodSymbol( + TypeSymbol containingSymbol, + Name name, + DataType selfDataType, + FixedList parameterDataTypes, + DataType returnDataType) + : base(containingSymbol, name, parameterDataTypes, returnDataType) + { + ContainingSymbol = containingSymbol; + Name = name; + SelfDataType = selfDataType; + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is MethodSymbol otherMethod + && ContainingSymbol == otherMethod.ContainingSymbol + && Name == otherMethod.Name + && SelfDataType == otherMethod.SelfDataType + && ParameterDataTypes.SequenceEqual(otherMethod.ParameterDataTypes) + && ReturnDataType == otherMethod.ReturnDataType; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, SelfDataType, ParameterDataTypes, ReturnDataType); + } + + public override string ToILString() + { + return $"{ContainingSymbol}::{Name}({string.Join(", ", ParameterDataTypes)}) -> {ReturnDataType}"; + } + } +} diff --git a/Compiler.Symbols/NamedBindingSymbol.cs b/Compiler.Symbols/NamedBindingSymbol.cs new file mode 100644 index 00000000..6cddfa9a --- /dev/null +++ b/Compiler.Symbols/NamedBindingSymbol.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + [Closed( + typeof(VariableSymbol), + typeof(FieldSymbol))] + public abstract class NamedBindingSymbol : BindingSymbol + { + public new Name Name { get; } + + protected NamedBindingSymbol( + Symbol containingSymbol, + Name name, + bool isMutableBinding, + DataType dataType) + : base(containingSymbol, name, isMutableBinding, dataType) + { + Name = name; + } + } +} diff --git a/Compiler.Symbols/NamespaceOrPackageSymbol.cs b/Compiler.Symbols/NamespaceOrPackageSymbol.cs new file mode 100644 index 00000000..3f2d52ab --- /dev/null +++ b/Compiler.Symbols/NamespaceOrPackageSymbol.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + [Closed( + typeof(NamespaceSymbol), + typeof(PackageSymbol))] + public abstract class NamespaceOrPackageSymbol : Symbol + { + public NamespaceName NamespaceName { get; } + public new Name Name { get; } + + protected NamespaceOrPackageSymbol(NamespaceOrPackageSymbol? containingSymbol, Name name) + : base(containingSymbol, name) + { + NamespaceName = containingSymbol is null + ? NamespaceName.Global + : containingSymbol.NamespaceName.Qualify(name); + Name = name; + } + } +} diff --git a/Compiler.Symbols/NamespaceSymbol.cs b/Compiler.Symbols/NamespaceSymbol.cs new file mode 100644 index 00000000..9fe3c85f --- /dev/null +++ b/Compiler.Symbols/NamespaceSymbol.cs @@ -0,0 +1,40 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + /// + /// While namespaces in syntax declarations are repeated across files, and + /// IL doesn't even directly represent namespaces, for symbols, a namespace + /// is the container of all the names in it. There is one symbol per namespace. + /// + public sealed class NamespaceSymbol : NamespaceOrPackageSymbol + { + public new NamespaceOrPackageSymbol ContainingSymbol { get; } + + public NamespaceSymbol(NamespaceOrPackageSymbol containingSymbol, Name name) + : base(containingSymbol, name) + { + ContainingSymbol = containingSymbol; + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is NamespaceSymbol otherNamespace + && ContainingSymbol.Equals(otherNamespace.ContainingSymbol) + && Name == otherNamespace.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(ContainingSymbol, Name); + } + + public override string ToILString() + { + return $"{ContainingSymbol}.{Name}"; + } + } +} diff --git a/Compiler.Symbols/ObjectTypeSymbol.cs b/Compiler.Symbols/ObjectTypeSymbol.cs new file mode 100644 index 00000000..16c7a206 --- /dev/null +++ b/Compiler.Symbols/ObjectTypeSymbol.cs @@ -0,0 +1,50 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + /// + /// A symbol for a type declaration (i.e. a class) + /// + public sealed class ObjectTypeSymbol : TypeSymbol + { + public override PackageSymbol? Package { get; } + public new NamespaceOrPackageSymbol ContainingSymbol { get; } + public new TypeName Name { get; } + public new ObjectType DeclaresDataType { get; } + + public ObjectTypeSymbol( + NamespaceOrPackageSymbol containingSymbol, + ObjectType declaresDataType) + : base(containingSymbol, declaresDataType.Name, declaresDataType) + { + // TODO check the declared type is in the containing namespace and package + Package = containingSymbol.Package; + ContainingSymbol = containingSymbol; + Name = declaresDataType.Name; + DeclaresDataType = declaresDataType; + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is ObjectTypeSymbol otherType + && ContainingSymbol == otherType.ContainingSymbol + && Name == otherType.Name + && DeclaresDataType == otherType.DeclaresDataType; + } + + public override int GetHashCode() + { + return HashCode.Combine(ContainingSymbol, Name, DeclaresDataType); + } + + public override string ToILString() + { + // TODO include generics + return $"{ContainingSymbol}.{Name}"; + } + } +} diff --git a/Compiler.Symbols/PackageSymbol.cs b/Compiler.Symbols/PackageSymbol.cs new file mode 100644 index 00000000..8a933fff --- /dev/null +++ b/Compiler.Symbols/PackageSymbol.cs @@ -0,0 +1,37 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + /// + /// A symbol a package + /// + /// + /// A package alias has no affect on the symbol. It is still the same package. + /// + public class PackageSymbol : NamespaceOrPackageSymbol + { + public override PackageSymbol Package => this; + + public PackageSymbol(Name name) + : base(null, name) { } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is PackageSymbol otherNamespace + && Name == otherNamespace.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name); + } + + public override string ToILString() + { + return $"<{Name}>::"; + } + } +} diff --git a/Compiler.Symbols/PrimitiveTypeSymbol.cs b/Compiler.Symbols/PrimitiveTypeSymbol.cs new file mode 100644 index 00000000..a480a29f --- /dev/null +++ b/Compiler.Symbols/PrimitiveTypeSymbol.cs @@ -0,0 +1,42 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + public class PrimitiveTypeSymbol : TypeSymbol + { + public new SpecialTypeName Name { get; } + + public PrimitiveTypeSymbol(SimpleType declaresDataType) + : base(null, declaresDataType.Name, declaresDataType) + { + Name = declaresDataType.Name; + } + + public PrimitiveTypeSymbol(EmptyType declaresDataType) + : base(null, declaresDataType.Name, declaresDataType) + { + Name = declaresDataType.Name; + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is PrimitiveTypeSymbol otherType + && Name == otherType.Name + && DeclaresDataType == otherType.DeclaresDataType; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, DeclaresDataType); + } + + public override string ToILString() + { + return Name.ToString(); + } + } +} diff --git a/Compiler.Symbols/README.md b/Compiler.Symbols/README.md new file mode 100644 index 00000000..482cad4e --- /dev/null +++ b/Compiler.Symbols/README.md @@ -0,0 +1,3 @@ +# Azoth.Tools.Bootstrap.Compiler.Symbols + +A symbol is the pairing of a full name with a type and other data about the symbol. For example, function symbols include symbols for the parameters and a return type. diff --git a/Compiler.Symbols/SelfParameterSymbol.cs b/Compiler.Symbols/SelfParameterSymbol.cs new file mode 100644 index 00000000..dece7bf2 --- /dev/null +++ b/Compiler.Symbols/SelfParameterSymbol.cs @@ -0,0 +1,40 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + public class SelfParameterSymbol : BindingSymbol + { + public new InvocableSymbol ContainingSymbol { get; } + + public SelfParameterSymbol(InvocableSymbol containingSymbol, DataType dataType) + : base(containingSymbol, null, false, dataType) + { + if (containingSymbol is FunctionSymbol) + throw new ArgumentException("Function can't have self parameter", nameof(containingSymbol)); + + ContainingSymbol = containingSymbol; + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is VariableSymbol otherVariable + && ContainingSymbol == otherVariable.ContainingSymbol + && Name == otherVariable.Name + && IsMutableBinding == otherVariable.IsMutableBinding + && DataType == otherVariable.DataType; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, IsMutableBinding, DataType); + } + + public override string ToILString() + { + return $"{ContainingSymbol} {{self: {DataType}}}"; + } + } +} diff --git a/Compiler.Symbols/Symbol.cs b/Compiler.Symbols/Symbol.cs new file mode 100644 index 00000000..3c3494ca --- /dev/null +++ b/Compiler.Symbols/Symbol.cs @@ -0,0 +1,62 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Names; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + [Closed( + typeof(NamespaceOrPackageSymbol), + typeof(TypeSymbol), + typeof(InvocableSymbol), + typeof(BindingSymbol))] + [DebuggerDisplay("{" + nameof(ToILString) + "(),nq}")] + public abstract class Symbol : IEquatable + { + public virtual PackageSymbol? Package { get; } + public Symbol? ContainingSymbol { get; } + public TypeName? Name { get; } + public bool IsGlobal => ContainingSymbol == Package; + + protected Symbol(Symbol? containingSymbol, TypeName? name) + { + // Note: constructor can't be `private protected` so `Symbol` can be mocked in unit tests + Package = containingSymbol?.Package; + ContainingSymbol = containingSymbol; + Name = name; + } + + #region Equality + public abstract bool Equals(Symbol? other); + + public abstract override int GetHashCode(); + + public sealed override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((Symbol)obj); + } + + public static bool operator ==(Symbol? symbol1, Symbol? symbol2) + { + return Equals(symbol1, symbol2); + } + + public static bool operator !=(Symbol? symbol1, Symbol? symbol2) + { + return !(symbol1 == symbol2); + } + #endregion + + [Obsolete("Use ToILString() instead")] +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + public sealed override string ToString() +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member + { + return ToILString(); + } + + public abstract string ToILString(); + } +} diff --git a/Compiler.Symbols/Trees/FixedSymbolTree.cs b/Compiler.Symbols/Trees/FixedSymbolTree.cs new file mode 100644 index 00000000..52e7fdc5 --- /dev/null +++ b/Compiler.Symbols/Trees/FixedSymbolTree.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols.Trees +{ + /// + /// A symbol tree for a specific package + /// + public class FixedSymbolTree : ISymbolTree + { + public PackageSymbol Package { get; } + private readonly FixedDictionary> symbolChildren; + public IEnumerable Symbols => symbolChildren.Keys; + + public FixedSymbolTree(PackageSymbol package, FixedDictionary> symbolChildren) + { + if (symbolChildren.Keys.Any(s => s.Package != package)) + throw new ArgumentException("Children must be for this package", nameof(symbolChildren)); + Package = package; + this.symbolChildren = symbolChildren; + } + + public bool Contains(Symbol symbol) + { + return symbolChildren.ContainsKey(symbol); + } + + public IEnumerable Children(Symbol symbol) + { + if (symbol.Package != Package) + throw new ArgumentException("Symbol must be for the package of this tree", nameof(symbol)); + + return symbolChildren.TryGetValue(symbol, out var children) + ? children : FixedSet.Empty; + } + } +} diff --git a/Compiler.Symbols/Trees/ISymbolTree.cs b/Compiler.Symbols/Trees/ISymbolTree.cs new file mode 100644 index 00000000..5f2873c2 --- /dev/null +++ b/Compiler.Symbols/Trees/ISymbolTree.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols.Trees +{ + /// + /// A symbol tree is a immutable collection of symbols that answers the question: + /// For any given symbol, what are its child symbols. + /// + /// Each symbol tree + /// + public interface ISymbolTree + { + PackageSymbol? Package { get; } + IEnumerable Symbols { get; } + bool Contains(Symbol symbol); + IEnumerable Children(Symbol symbol); + } +} diff --git a/Compiler.Symbols/Trees/PrimitiveSymbolTree.cs b/Compiler.Symbols/Trees/PrimitiveSymbolTree.cs new file mode 100644 index 00000000..9848f0fd --- /dev/null +++ b/Compiler.Symbols/Trees/PrimitiveSymbolTree.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols.Trees +{ + public sealed class PrimitiveSymbolTree : ISymbolTree + { + PackageSymbol? ISymbolTree.Package => null; + public FixedSet GlobalSymbols { get; } + private readonly FixedDictionary> symbolChildren; + public IEnumerable Symbols => symbolChildren.Keys; + + public PrimitiveSymbolTree(FixedDictionary> symbolChildren) + { + this.symbolChildren = symbolChildren; + GlobalSymbols = symbolChildren.Keys.Where(s => s.ContainingSymbol is null).ToFixedSet(); + } + + public bool Contains(Symbol symbol) + { + return symbolChildren.ContainsKey(symbol); + } + + public IEnumerable Children(Symbol symbol) + { + if (!(symbol.Package is null)) + throw new ArgumentException("Symbol must be primitive", nameof(symbol)); + + return symbolChildren.TryGetValue(symbol, out var children) + ? children : FixedSet.Empty; + } + } +} diff --git a/Compiler.Symbols/Trees/SymbolForest.cs b/Compiler.Symbols/Trees/SymbolForest.cs new file mode 100644 index 00000000..0e534174 --- /dev/null +++ b/Compiler.Symbols/Trees/SymbolForest.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols.Trees +{ + public class SymbolForest + { + public PrimitiveSymbolTree PrimitiveSymbolTree { get; } + private readonly FixedDictionary packageTrees; + public IEnumerable Packages => packageTrees.Keys; + + public IEnumerable GlobalSymbols => PrimitiveSymbolTree.GlobalSymbols + .Concat(Packages.SelectMany(Children)); + public IEnumerable Symbols => packageTrees.Values.SelectMany(t => t.Symbols); + + public SymbolForest(PrimitiveSymbolTree primitiveSymbolTree, SymbolTreeBuilder symbolTreeBuilder, IEnumerable packageTrees) + { + if (symbolTreeBuilder.Package is null) + throw new ArgumentException("Can't be builder for primitive symbols", nameof(symbolTreeBuilder)); + PrimitiveSymbolTree = primitiveSymbolTree; + this.packageTrees = packageTrees.Append(symbolTreeBuilder).ToFixedDictionary(t => t.Package!); + } + + public SymbolForest(PrimitiveSymbolTree primitiveSymbolTree, IEnumerable packageTrees) + { + PrimitiveSymbolTree = primitiveSymbolTree; + this.packageTrees = packageTrees.ToFixedDictionary(t => t.Package!, t => (ISymbolTree)t); + } + + public IEnumerable Children(Symbol symbol) + { + if (symbol.Package is null) + return PrimitiveSymbolTree.Children(symbol); + + if (!packageTrees.TryGetValue(symbol.Package, out var tree)) + throw new ArgumentException("Symbol must be for one of the packages in this tree", nameof(symbol)); + + return tree.Children(symbol); + } + } +} diff --git a/Compiler.Symbols/Trees/SymbolTreeBuilder.cs b/Compiler.Symbols/Trees/SymbolTreeBuilder.cs new file mode 100644 index 00000000..38c5ad41 --- /dev/null +++ b/Compiler.Symbols/Trees/SymbolTreeBuilder.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols.Trees +{ + /// + /// Builder for a + /// + public class SymbolTreeBuilder : ISymbolTree + { + public PackageSymbol? Package { get; } + private readonly IDictionary> symbolChildren = new Dictionary>(); + public IEnumerable Symbols => symbolChildren.Keys; + + public SymbolTreeBuilder() + { + Package = null; + } + + public SymbolTreeBuilder(PackageSymbol package) + { + Package = package; + symbolChildren.Add(package, new HashSet()); + } + + public bool Contains(Symbol symbol) + { + return symbolChildren.ContainsKey(symbol); + } + + public IEnumerable Children(Symbol symbol) + { + if (symbol.Package != Package) + throw new ArgumentException("Symbol must be for the package of this tree", nameof(symbol)); + + return symbolChildren.TryGetValue(symbol, out var children) + ? children : Enumerable.Empty(); + } + + public void Add(Symbol symbol) + { + if (symbol.Package != Package) + throw new ArgumentException("Symbol must be for the package of this tree", nameof(symbol)); + + GetOrAdd(symbol); + } + + private ISet GetOrAdd(Symbol symbol) + { + if (!symbolChildren.TryGetValue(symbol, out var children)) + { + // Add to parent's children + if (!(symbol.ContainingSymbol is null)) + GetOrAdd(symbol.ContainingSymbol).Add(symbol); + children = new HashSet(); + symbolChildren.Add(symbol, children); + } + return children; + } + + public FixedSymbolTree Build() + { + if (Package is null) + throw new InvalidOperationException($"Can't build {nameof(FixedSymbolTree)} without a package"); + return new FixedSymbolTree(Package, symbolChildren.ToFixedDictionary(e => e.Key, e => e.Value.ToFixedSet())); + } + + public PrimitiveSymbolTree BuildPrimitives() + { + if (!(Package is null)) + throw new InvalidOperationException($"Can't build {nameof(PrimitiveSymbolTree)} WITH a package"); + return new PrimitiveSymbolTree(symbolChildren.ToFixedDictionary(e => e.Key, e => e.Value.ToFixedSet())); + } + } +} diff --git a/Compiler.Symbols/TypeSymbol.cs b/Compiler.Symbols/TypeSymbol.cs new file mode 100644 index 00000000..c87248ca --- /dev/null +++ b/Compiler.Symbols/TypeSymbol.cs @@ -0,0 +1,24 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + [Closed( + typeof(PrimitiveTypeSymbol), + typeof(ObjectTypeSymbol))] + public abstract class TypeSymbol : Symbol + { + public new NamespaceOrPackageSymbol? ContainingSymbol { get; } + public new TypeName Name { get; } + public DataType DeclaresDataType { get; } + + protected TypeSymbol(NamespaceOrPackageSymbol? containingSymbol, TypeName name, DataType declaresDataType) + : base(containingSymbol, name) + { + ContainingSymbol = containingSymbol; + Name = name; + DeclaresDataType = declaresDataType; + } + } +} diff --git a/Compiler.Symbols/VariableSymbol.cs b/Compiler.Symbols/VariableSymbol.cs new file mode 100644 index 00000000..f4b4cb62 --- /dev/null +++ b/Compiler.Symbols/VariableSymbol.cs @@ -0,0 +1,51 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Compiler.Symbols +{ + /// + /// A symbol for a variable or parameter. Both of which are bindings using `let` or `var` + /// + public sealed class VariableSymbol : NamedBindingSymbol + { + public new InvocableSymbol ContainingSymbol { get; } + public int? DeclarationNumber { get; } + + public VariableSymbol( + InvocableSymbol containingSymbol, + Name name, + int? declarationNumber, + bool isMutableBinding, + DataType dataType) + : base(containingSymbol, name, isMutableBinding, dataType) + { + ContainingSymbol = containingSymbol; + DeclarationNumber = declarationNumber; + } + + public override bool Equals(Symbol? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is VariableSymbol otherVariable + && ContainingSymbol == otherVariable.ContainingSymbol + && Name == otherVariable.Name + && DeclarationNumber == otherVariable.DeclarationNumber + && IsMutableBinding == otherVariable.IsMutableBinding + && DataType == otherVariable.DataType; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, DeclarationNumber, IsMutableBinding, DataType); + } + + public override string ToILString() + { + var mutable = IsMutableBinding ? "var" : "let"; + var declarationNumber = DeclarationNumber is null ? "" : "#" + DeclarationNumber; + return $"{ContainingSymbol} {{{mutable} {Name}{declarationNumber}: {DataType}}}"; + } + } +} diff --git a/Compiler.Tokens/BareIdentifierToken.cs b/Compiler.Tokens/BareIdentifierToken.cs new file mode 100644 index 00000000..98744738 --- /dev/null +++ b/Compiler.Tokens/BareIdentifierToken.cs @@ -0,0 +1,20 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal class BareIdentifierToken : IdentifierToken, IBareIdentifierToken + { + public BareIdentifierToken(TextSpan span, string value) + : base(span, value) + { + } + } + + public static partial class TokenFactory + { + public static IIdentifierToken BareIdentifier(TextSpan span, string value) + { + return new BareIdentifierToken(span, value); + } + } +} diff --git a/Compiler.Tokens/Compiler.Tokens.csproj b/Compiler.Tokens/Compiler.Tokens.csproj new file mode 100644 index 00000000..7d995285 --- /dev/null +++ b/Compiler.Tokens/Compiler.Tokens.csproj @@ -0,0 +1,58 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Tokens + latest + Azoth.Tools.Bootstrap.Compiler.Tokens + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + KeywordTokens.tt + True + True + + + Tokens.tt + True + True + + + + + + KeywordTokens.cs + TextTemplatingFileGenerator + + + Tokens.cs + TextTemplatingFileGenerator + + + + + + + + diff --git a/Compiler.Tokens/EscapedIdentifierToken.cs b/Compiler.Tokens/EscapedIdentifierToken.cs new file mode 100644 index 00000000..9d02115b --- /dev/null +++ b/Compiler.Tokens/EscapedIdentifierToken.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal class EscapedIdentifierToken : IdentifierToken, IEscapedIdentifierToken + { + public EscapedIdentifierToken(TextSpan span, string value) + : base(span, value) + { + } + } + + public static partial class TokenFactory + { + + public static IEscapedIdentifierToken EscapedIdentifier(TextSpan span, string value) + { + return new EscapedIdentifierToken(span, value); + } + } +} diff --git a/Compiler.Tokens/FalseKeywordToken.cs b/Compiler.Tokens/FalseKeywordToken.cs new file mode 100644 index 00000000..cdf79e3b --- /dev/null +++ b/Compiler.Tokens/FalseKeywordToken.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal partial class FalseKeywordToken + { + public bool Value => false; + } +} diff --git a/Compiler.Tokens/IAccessModifierToken.cs b/Compiler.Tokens/IAccessModifierToken.cs new file mode 100644 index 00000000..962ec28d --- /dev/null +++ b/Compiler.Tokens/IAccessModifierToken.cs @@ -0,0 +1,13 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IPublishedKeywordToken), + typeof(IPublicKeywordToken) + )] + public partial interface IAccessModifierToken : IKeywordToken { } + + public partial interface IPublishedKeywordToken : IAccessModifierToken { } + public partial interface IPublicKeywordToken : IAccessModifierToken { } +} diff --git a/Compiler.Tokens/IAccessOperatorToken.cs b/Compiler.Tokens/IAccessOperatorToken.cs new file mode 100644 index 00000000..73eca598 --- /dev/null +++ b/Compiler.Tokens/IAccessOperatorToken.cs @@ -0,0 +1,12 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IDotToken), + typeof(IQuestionDotToken))] + public interface IAccessOperatorToken : IOperatorToken { } + + public partial interface IDotToken : IAccessOperatorToken { } + public partial interface IQuestionDotToken : IAccessOperatorToken { } +} diff --git a/Compiler.Tokens/IAssignmentToken.cs b/Compiler.Tokens/IAssignmentToken.cs new file mode 100644 index 00000000..f99df122 --- /dev/null +++ b/Compiler.Tokens/IAssignmentToken.cs @@ -0,0 +1,18 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IEqualsToken), + typeof(IPlusEqualsToken), + typeof(IMinusEqualsToken), + typeof(IAsteriskEqualsToken), + typeof(ISlashEqualsToken))] + public interface IAssignmentToken : IOperatorToken { } + + public partial interface IEqualsToken : IAssignmentToken { } + public partial interface IPlusEqualsToken : IAssignmentToken { } + public partial interface IMinusEqualsToken : IAssignmentToken { } + public partial interface IAsteriskEqualsToken : IAssignmentToken { } + public partial interface ISlashEqualsToken : IAssignmentToken { } +} diff --git a/Compiler.Tokens/IBareIdentifierToken.cs b/Compiler.Tokens/IBareIdentifierToken.cs new file mode 100644 index 00000000..2d8f6afd --- /dev/null +++ b/Compiler.Tokens/IBareIdentifierToken.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + /// + /// A bare identifier is one that isn't escaped + /// + public interface IBareIdentifierToken : IIdentifierToken + { + } +} diff --git a/Compiler.Tokens/IBinaryOperatorToken.cs b/Compiler.Tokens/IBinaryOperatorToken.cs new file mode 100644 index 00000000..a919c41b --- /dev/null +++ b/Compiler.Tokens/IBinaryOperatorToken.cs @@ -0,0 +1,44 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IDotDotToken), + typeof(ILessThanDotDotToken), + typeof(IDotDotLessThanToken), + typeof(ILessThanDotDotLessThanToken), + typeof(IPlusToken), + typeof(IMinusToken), + typeof(IAsteriskToken), + typeof(ISlashToken), + typeof(IEqualsEqualsToken), + typeof(INotEqualToken), + typeof(IGreaterThanToken), + typeof(IGreaterThanOrEqualToken), + typeof(ILessThanToken), + typeof(ILessThanOrEqualToken), + typeof(IQuestionQuestionToken), + typeof(IAndKeywordToken), + typeof(IOrKeywordToken))] + public interface IBinaryOperatorToken : IEssentialToken + { + } + + public partial interface IDotDotToken : IBinaryOperatorToken { } + public partial interface ILessThanDotDotToken : IBinaryOperatorToken { } + public partial interface IDotDotLessThanToken : IBinaryOperatorToken { } + public partial interface ILessThanDotDotLessThanToken : IBinaryOperatorToken { } + public partial interface IPlusToken : IBinaryOperatorToken { } + public partial interface IMinusToken : IBinaryOperatorToken { } + public partial interface IAsteriskToken : IBinaryOperatorToken { } + public partial interface ISlashToken : IBinaryOperatorToken { } + public partial interface IEqualsEqualsToken : IBinaryOperatorToken { } + public partial interface INotEqualToken : IBinaryOperatorToken { } + public partial interface IGreaterThanToken : IBinaryOperatorToken { } + public partial interface IGreaterThanOrEqualToken : IBinaryOperatorToken { } + public partial interface ILessThanToken : IBinaryOperatorToken { } + public partial interface ILessThanOrEqualToken : IBinaryOperatorToken { } + public partial interface IQuestionQuestionToken : IBinaryOperatorToken { } + public partial interface IAndKeywordToken : IBinaryOperatorToken { } + public partial interface IOrKeywordToken : IBinaryOperatorToken { } +} diff --git a/Compiler.Tokens/IBindingToken.cs b/Compiler.Tokens/IBindingToken.cs new file mode 100644 index 00000000..56e44b36 --- /dev/null +++ b/Compiler.Tokens/IBindingToken.cs @@ -0,0 +1,12 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(ILetKeywordToken), + typeof(IVarKeywordToken))] + public interface IBindingToken : IKeywordToken { } + + public partial interface ILetKeywordToken : IBindingToken { } + public partial interface IVarKeywordToken : IBindingToken { } +} diff --git a/Compiler.Tokens/IBooleanLiteralToken.cs b/Compiler.Tokens/IBooleanLiteralToken.cs new file mode 100644 index 00000000..465496f8 --- /dev/null +++ b/Compiler.Tokens/IBooleanLiteralToken.cs @@ -0,0 +1,15 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(ITrueKeywordToken), + typeof(IFalseKeywordToken))] + public partial interface IBooleanLiteralToken : IKeywordToken + { + bool Value { get; } + } + + public partial interface ITrueKeywordToken : IBooleanLiteralToken { } + public partial interface IFalseKeywordToken : IBooleanLiteralToken { } +} diff --git a/Compiler.Tokens/ICapabilityToken.cs b/Compiler.Tokens/ICapabilityToken.cs new file mode 100644 index 00000000..4e990a3f --- /dev/null +++ b/Compiler.Tokens/ICapabilityToken.cs @@ -0,0 +1,18 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IIsolatedKeywordToken), + typeof(ISharedKeywordToken), + typeof(IConstKeywordToken), + typeof(IMutableKeywordToken), + typeof(IIdKeywordToken))] + public interface ICapabilityToken : IKeywordToken { } + + public partial interface IIsolatedKeywordToken : ICapabilityToken { } + public partial interface ISharedKeywordToken : ICapabilityToken { } + public partial interface IConstKeywordToken : ICapabilityToken { } + public partial interface IMutableKeywordToken : ICapabilityToken { } + public partial interface IIdKeywordToken : ICapabilityToken { } +} diff --git a/Compiler.Tokens/IEscapedIdentifierToken.cs b/Compiler.Tokens/IEscapedIdentifierToken.cs new file mode 100644 index 00000000..473ebe4e --- /dev/null +++ b/Compiler.Tokens/IEscapedIdentifierToken.cs @@ -0,0 +1,4 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public interface IEscapedIdentifierToken : IIdentifierToken { } +} diff --git a/Compiler.Tokens/IEssentialToken.cs b/Compiler.Tokens/IEssentialToken.cs new file mode 100644 index 00000000..b3edd034 --- /dev/null +++ b/Compiler.Tokens/IEssentialToken.cs @@ -0,0 +1,21 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + /// + /// A token that isn't trivia + /// + [Closed( + typeof(IStringLiteralToken), + typeof(IKeywordToken), + typeof(IIdentifierOrUnderscoreToken), + typeof(IOperatorToken), + typeof(ILiteralToken), + typeof(IPrimitiveTypeToken), + typeof(IBinaryOperatorToken), + typeof(IPunctuationToken), + typeof(IEndOfFileToken))] + public partial interface IEssentialToken : IToken + { + } +} diff --git a/Compiler.Tokens/IIdentifierOrUnderscoreToken.cs b/Compiler.Tokens/IIdentifierOrUnderscoreToken.cs new file mode 100644 index 00000000..38832e87 --- /dev/null +++ b/Compiler.Tokens/IIdentifierOrUnderscoreToken.cs @@ -0,0 +1,19 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IIdentifierToken))] + public interface IIdentifierOrUnderscoreToken : IEssentialToken + { + string Value { get; } + } + + public partial interface IIdentifierToken : IIdentifierOrUnderscoreToken { } + //public partial interface IUnderscoreKeywordToken : IIdentifierOrUnderscoreToken { } + + //internal partial class UnderscoreKeywordToken + //{ + // public string Value => "_"; + //} +} diff --git a/Compiler.Tokens/IIntegerLiteralToken.cs b/Compiler.Tokens/IIntegerLiteralToken.cs new file mode 100644 index 00000000..a6a1bd2a --- /dev/null +++ b/Compiler.Tokens/IIntegerLiteralToken.cs @@ -0,0 +1,9 @@ +using System.Numerics; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public partial interface IIntegerLiteralToken : ILiteralToken + { + BigInteger Value { get; } + } +} diff --git a/Compiler.Tokens/IKeywordToken.cs b/Compiler.Tokens/IKeywordToken.cs new file mode 100644 index 00000000..68d9326e --- /dev/null +++ b/Compiler.Tokens/IKeywordToken.cs @@ -0,0 +1,14 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IMemberNameToken), + typeof(IBindingToken), + typeof(IModiferToken), + typeof(IAccessModifierToken), + typeof(ITypeKindKeywordToken), + typeof(IBooleanLiteralToken), + typeof(ICapabilityToken))] + public partial interface IKeywordToken : IEssentialToken { } +} diff --git a/Compiler.Tokens/ILiteralToken.cs b/Compiler.Tokens/ILiteralToken.cs new file mode 100644 index 00000000..0aab0a43 --- /dev/null +++ b/Compiler.Tokens/ILiteralToken.cs @@ -0,0 +1,19 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IBooleanLiteralToken), + typeof(IIntegerLiteralToken), + typeof(IStringLiteralToken), + typeof(INoneKeywordToken) + //typeof(IUninitializedKeywordToken) + )] + public interface ILiteralToken : IEssentialToken { } + + public partial interface IBooleanLiteralToken : ILiteralToken { } + public partial interface IIntegerLiteralToken : ILiteralToken { } + public partial interface IStringLiteralToken : ILiteralToken { } + public partial interface INoneKeywordToken : ILiteralToken { } + //public partial interface IUninitializedKeywordToken : ILiteralToken { } +} diff --git a/Compiler.Tokens/IMemberNameToken.cs b/Compiler.Tokens/IMemberNameToken.cs new file mode 100644 index 00000000..82837fd8 --- /dev/null +++ b/Compiler.Tokens/IMemberNameToken.cs @@ -0,0 +1,20 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IIdentifierToken), + typeof(INewKeywordToken) + //typeof(IInitKeywordToken) + //typeof(IDeleteKeywordToken) + )] + public interface IMemberNameToken : IKeywordToken { } + + [Closed( + typeof(IBareIdentifierToken), + typeof(IEscapedIdentifierToken))] + public partial interface IIdentifierToken : IMemberNameToken { } + public partial interface INewKeywordToken : IMemberNameToken { } + //public partial interface IInitKeywordToken : IMemberNameToken { } + //public partial interface IDeleteKeywordToken : IMemberNameToken { } +} diff --git a/Compiler.Tokens/IModiferToken.cs b/Compiler.Tokens/IModiferToken.cs new file mode 100644 index 00000000..098e8dfe --- /dev/null +++ b/Compiler.Tokens/IModiferToken.cs @@ -0,0 +1,19 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IAccessModifierToken), + typeof(IMutableKeywordToken), + typeof(IMoveKeywordToken), + typeof(ISafeKeywordToken), + typeof(IUnsafeKeywordToken) + )] + public partial interface IModiferToken : IKeywordToken { } + + public partial interface IAccessModifierToken : IModiferToken { } + public partial interface IMutableKeywordToken : IModiferToken { } + public partial interface IMoveKeywordToken : IModiferToken { } + public partial interface ISafeKeywordToken : IModiferToken { } + public partial interface IUnsafeKeywordToken : IModiferToken { } +} diff --git a/Compiler.Tokens/IOperatorToken.cs b/Compiler.Tokens/IOperatorToken.cs new file mode 100644 index 00000000..55e1a597 --- /dev/null +++ b/Compiler.Tokens/IOperatorToken.cs @@ -0,0 +1,66 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IAccessOperatorToken), + typeof(IDotToken), + typeof(IDotDotToken), + typeof(ILessThanDotDotToken), + typeof(IDotDotLessThanToken), + typeof(ILessThanDotDotLessThanToken), + typeof(IPlusToken), + typeof(IMinusToken), + typeof(IAsteriskToken), + typeof(ISlashToken), + typeof(IEqualsToken), + typeof(IEqualsEqualsToken), + typeof(INotEqualToken), + typeof(IGreaterThanToken), + typeof(IGreaterThanOrEqualToken), + typeof(ILessThanToken), + typeof(ILessThanOrEqualToken), + typeof(IPlusEqualsToken), + typeof(IMinusEqualsToken), + typeof(IAsteriskEqualsToken), + typeof(ISlashEqualsToken), + typeof(IQuestionToken), + typeof(IQuestionQuestionToken), + typeof(IQuestionDotToken), + typeof(ILessThanColonToken), + typeof(IRightDoubleArrowToken), + typeof(IAndKeywordToken), + typeof(IOrKeywordToken), + typeof(INotKeywordToken), + typeof(IAssignmentToken))] + public interface IOperatorToken : IEssentialToken { } + + public partial interface IDotToken : IOperatorToken { } + public partial interface IDotDotToken : IOperatorToken { } + public partial interface ILessThanDotDotToken : IOperatorToken { } + public partial interface IDotDotLessThanToken : IOperatorToken { } + public partial interface ILessThanDotDotLessThanToken : IOperatorToken { } + public partial interface IPlusToken : IOperatorToken { } + public partial interface IMinusToken : IOperatorToken { } + public partial interface IAsteriskToken : IOperatorToken { } + public partial interface ISlashToken : IOperatorToken { } + public partial interface IEqualsToken : IOperatorToken { } + public partial interface IEqualsEqualsToken : IOperatorToken { } + public partial interface INotEqualToken : IOperatorToken { } + public partial interface IGreaterThanToken : IOperatorToken { } + public partial interface IGreaterThanOrEqualToken : IOperatorToken { } + public partial interface ILessThanToken : IOperatorToken { } + public partial interface ILessThanOrEqualToken : IOperatorToken { } + public partial interface IPlusEqualsToken : IOperatorToken { } + public partial interface IMinusEqualsToken : IOperatorToken { } + public partial interface IAsteriskEqualsToken : IOperatorToken { } + public partial interface ISlashEqualsToken : IOperatorToken { } + public partial interface IQuestionToken : IOperatorToken { } + public partial interface IQuestionQuestionToken : IOperatorToken { } + public partial interface IQuestionDotToken : IOperatorToken { } + public partial interface ILessThanColonToken : IOperatorToken { } + public partial interface IRightDoubleArrowToken : IOperatorToken { } + public partial interface IAndKeywordToken : IOperatorToken { } + public partial interface IOrKeywordToken : IOperatorToken { } + public partial interface INotKeywordToken : IOperatorToken { } +} diff --git a/Compiler.Tokens/IPrimitiveTypeToken.cs b/Compiler.Tokens/IPrimitiveTypeToken.cs new file mode 100644 index 00000000..ebbf4e7b --- /dev/null +++ b/Compiler.Tokens/IPrimitiveTypeToken.cs @@ -0,0 +1,46 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IVoidKeywordToken), + typeof(INeverKeywordToken), + typeof(IBoolKeywordToken), + typeof(IAnyKeywordToken), + //typeof(ITypeKeywordToken), + //typeof(IInt8KeywordToken), + typeof(IByteKeywordToken), + //typeof(IInt16KeywordToken), + //typeof(IUInt16KeywordToken), + typeof(IIntKeywordToken), + typeof(IUIntKeywordToken), + //typeof(IInt64KeywordToken), + //typeof(IInt64KeywordToken), + //typeof(IUInt64KeywordToken), + typeof(ISizeKeywordToken), + typeof(IOffsetKeywordToken) + //typeof(IFloat32KeywordToken), + //typeof(IFloat64KeywordToken) + )] + public interface IPrimitiveTypeToken : IEssentialToken { } + + public partial interface IVoidKeywordToken : IPrimitiveTypeToken { } + public partial interface INeverKeywordToken : IPrimitiveTypeToken { } + public partial interface IBoolKeywordToken : IPrimitiveTypeToken { } + public partial interface IAnyKeywordToken : IPrimitiveTypeToken { } + //public partial interface ITypeKeywordToken : IPrimitiveTypeToken { } + + //public partial interface IInt8KeywordToken : IPrimitiveTypeToken { } + public partial interface IByteKeywordToken : IPrimitiveTypeToken { } + //public partial interface IInt16KeywordToken : IPrimitiveTypeToken { } + //public partial interface IUInt16KeywordToken : IPrimitiveTypeToken { } + public partial interface IIntKeywordToken : IPrimitiveTypeToken { } + public partial interface IUIntKeywordToken : IPrimitiveTypeToken { } + //public partial interface IInt64KeywordToken : IPrimitiveTypeToken { } + //public partial interface IUInt64KeywordToken : IPrimitiveTypeToken { } + public partial interface ISizeKeywordToken : IPrimitiveTypeToken { } + public partial interface IOffsetKeywordToken : IPrimitiveTypeToken { } + + //public partial interface IFloat32KeywordToken : IPrimitiveTypeToken { } + //public partial interface IFloat64KeywordToken : IPrimitiveTypeToken { } +} diff --git a/Compiler.Tokens/IPunctuationToken.cs b/Compiler.Tokens/IPunctuationToken.cs new file mode 100644 index 00000000..8314d74a --- /dev/null +++ b/Compiler.Tokens/IPunctuationToken.cs @@ -0,0 +1,26 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(IOpenBraceToken), + typeof(ICloseBraceToken), + typeof(IOpenParenToken), + typeof(ICloseParenToken), + typeof(ISemicolonToken), + typeof(IColonToken), + typeof(IColonColonDotToken), + typeof(ICommaToken))] + public interface IPunctuationToken : IEssentialToken + { + } + + public partial interface IOpenBraceToken : IPunctuationToken { } + public partial interface ICloseBraceToken : IPunctuationToken { } + public partial interface IOpenParenToken : IPunctuationToken { } + public partial interface ICloseParenToken : IPunctuationToken { } + public partial interface ISemicolonToken : IPunctuationToken { } + public partial interface IColonToken : IPunctuationToken { } + public partial interface IColonColonDotToken : IPunctuationToken { } + public partial interface ICommaToken : IPunctuationToken { } +} diff --git a/Compiler.Tokens/IStringLiteralToken.cs b/Compiler.Tokens/IStringLiteralToken.cs new file mode 100644 index 00000000..0350c3ae --- /dev/null +++ b/Compiler.Tokens/IStringLiteralToken.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public partial interface IStringLiteralToken : IEssentialToken + { + string Value { get; } + } +} diff --git a/Compiler.Tokens/IToken.cs b/Compiler.Tokens/IToken.cs new file mode 100644 index 00000000..51dd3cff --- /dev/null +++ b/Compiler.Tokens/IToken.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + /// + /// A non-missing token + /// + [Closed( + typeof(IEssentialToken), + typeof(ITriviaToken))] + public partial interface IToken + { + TextSpan Span { get; } + + string Text(CodeText code); + } +} diff --git a/Compiler.Tokens/ITriviaToken.cs b/Compiler.Tokens/ITriviaToken.cs new file mode 100644 index 00000000..e7af7937 --- /dev/null +++ b/Compiler.Tokens/ITriviaToken.cs @@ -0,0 +1,52 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + [Closed( + typeof(ICommentToken), + typeof(IWhitespaceToken), + typeof(IUnexpectedToken))] + public interface ITriviaToken : IToken { } + + public interface ICommentToken : ITriviaToken { } + public interface IWhitespaceToken : ITriviaToken { } + public interface IUnexpectedToken : ITriviaToken { } + + + internal class WhitespaceToken : Token, IWhitespaceToken + { + public WhitespaceToken(TextSpan span) + : base(span) { } + } + + internal class CommentToken : Token, ICommentToken + { + public CommentToken(TextSpan span) + : base(span) { } + } + + internal class UnexpectedToken : Token, IUnexpectedToken + { + public UnexpectedToken(TextSpan span) + : base(span) { } + } + + public static partial class TokenFactory + { + public static IWhitespaceToken Whitespace(TextSpan span) + { + return new WhitespaceToken(span); + } + + public static ICommentToken Comment(TextSpan span) + { + return new CommentToken(span); + } + + public static IUnexpectedToken Unexpected(TextSpan span) + { + return new UnexpectedToken(span); + } + } +} diff --git a/Compiler.Tokens/ITypeKindKeywordToken.cs b/Compiler.Tokens/ITypeKindKeywordToken.cs new file mode 100644 index 00000000..70c33bd5 --- /dev/null +++ b/Compiler.Tokens/ITypeKindKeywordToken.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public interface ITypeKindKeywordToken : IKeywordToken { } + + public partial interface IClassKeywordToken : ITypeKindKeywordToken { } + //public partial interface IStructKeywordToken : ITypeKindKeywordToken { } +} diff --git a/Compiler.Tokens/IdentifierToken.cs b/Compiler.Tokens/IdentifierToken.cs new file mode 100644 index 00000000..078d664d --- /dev/null +++ b/Compiler.Tokens/IdentifierToken.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal abstract class IdentifierToken : Token + { + public string Value { get; } + + protected IdentifierToken(TextSpan span, string value) + : base(span) + { + Value = value; + } + + // Helpful for debugging + public override string ToString() + { + return Value; + } + } +} diff --git a/Compiler.Tokens/IntegerLiteralToken.cs b/Compiler.Tokens/IntegerLiteralToken.cs new file mode 100644 index 00000000..b264bb75 --- /dev/null +++ b/Compiler.Tokens/IntegerLiteralToken.cs @@ -0,0 +1,30 @@ +using System.Globalization; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal class IntegerLiteralToken : Token, IIntegerLiteralToken + { + public BigInteger Value { get; } + + public IntegerLiteralToken(TextSpan span, BigInteger value) + : base(span) + { + Value = value; + } + + // Helpful for debugging + + public override string ToString() => Value.ToString(CultureInfo.InvariantCulture); + } + + public static partial class TokenFactory + { + + public static IIntegerLiteralToken IntegerLiteral(TextSpan span, BigInteger value) + { + return new IntegerLiteralToken(span, value); + } + } +} diff --git a/Compiler.Tokens/KeywordTokens.cs b/Compiler.Tokens/KeywordTokens.cs new file mode 100644 index 00000000..074e4997 --- /dev/null +++ b/Compiler.Tokens/KeywordTokens.cs @@ -0,0 +1,712 @@ +using System; +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public static partial class TokenTypes + { + private static readonly IReadOnlyList Keyword = new List() + { + typeof(PublishedKeywordToken), + typeof(PublicKeywordToken), + typeof(LetKeywordToken), + typeof(VarKeywordToken), + typeof(VoidKeywordToken), + typeof(IntKeywordToken), + typeof(UIntKeywordToken), + typeof(ByteKeywordToken), + typeof(SizeKeywordToken), + typeof(OffsetKeywordToken), + typeof(BoolKeywordToken), + typeof(NeverKeywordToken), + typeof(ReturnKeywordToken), + typeof(ClassKeywordToken), + typeof(FunctionKeywordToken), + typeof(NewKeywordToken), + typeof(IsolatedKeywordToken), + typeof(TransitionKeywordToken), + typeof(SharedKeywordToken), + typeof(ConstKeywordToken), + typeof(IdKeywordToken), + typeof(LentKeywordToken), + typeof(NamespaceKeywordToken), + typeof(UsingKeywordToken), + typeof(ForeachKeywordToken), + typeof(InKeywordToken), + typeof(IfKeywordToken), + typeof(ElseKeywordToken), + typeof(UnsafeKeywordToken), + typeof(SafeKeywordToken), + typeof(SelfKeywordToken), + typeof(MutableKeywordToken), + typeof(NoneKeywordToken), + typeof(MoveKeywordToken), + typeof(CopyKeywordToken), + typeof(LoopKeywordToken), + typeof(WhileKeywordToken), + typeof(BreakKeywordToken), + typeof(NextKeywordToken), + typeof(AnyKeywordToken), + typeof(TrueKeywordToken), + typeof(FalseKeywordToken), + typeof(AsKeywordToken), + typeof(AndKeywordToken), + typeof(OrKeywordToken), + typeof(NotKeywordToken), + }.AsReadOnly(); + } + + public static partial class TokenFactory + { + public static IPublishedKeywordToken PublishedKeyword(TextSpan span) + { + return new PublishedKeywordToken(span); + } + public static IPublicKeywordToken PublicKeyword(TextSpan span) + { + return new PublicKeywordToken(span); + } + public static ILetKeywordToken LetKeyword(TextSpan span) + { + return new LetKeywordToken(span); + } + public static IVarKeywordToken VarKeyword(TextSpan span) + { + return new VarKeywordToken(span); + } + public static IVoidKeywordToken VoidKeyword(TextSpan span) + { + return new VoidKeywordToken(span); + } + public static IIntKeywordToken IntKeyword(TextSpan span) + { + return new IntKeywordToken(span); + } + public static IUIntKeywordToken UIntKeyword(TextSpan span) + { + return new UIntKeywordToken(span); + } + public static IByteKeywordToken ByteKeyword(TextSpan span) + { + return new ByteKeywordToken(span); + } + public static ISizeKeywordToken SizeKeyword(TextSpan span) + { + return new SizeKeywordToken(span); + } + public static IOffsetKeywordToken OffsetKeyword(TextSpan span) + { + return new OffsetKeywordToken(span); + } + public static IBoolKeywordToken BoolKeyword(TextSpan span) + { + return new BoolKeywordToken(span); + } + public static INeverKeywordToken NeverKeyword(TextSpan span) + { + return new NeverKeywordToken(span); + } + public static IReturnKeywordToken ReturnKeyword(TextSpan span) + { + return new ReturnKeywordToken(span); + } + public static IClassKeywordToken ClassKeyword(TextSpan span) + { + return new ClassKeywordToken(span); + } + public static IFunctionKeywordToken FunctionKeyword(TextSpan span) + { + return new FunctionKeywordToken(span); + } + public static INewKeywordToken NewKeyword(TextSpan span) + { + return new NewKeywordToken(span); + } + public static IIsolatedKeywordToken IsolatedKeyword(TextSpan span) + { + return new IsolatedKeywordToken(span); + } + public static ITransitionKeywordToken TransitionKeyword(TextSpan span) + { + return new TransitionKeywordToken(span); + } + public static ISharedKeywordToken SharedKeyword(TextSpan span) + { + return new SharedKeywordToken(span); + } + public static IConstKeywordToken ConstKeyword(TextSpan span) + { + return new ConstKeywordToken(span); + } + public static IIdKeywordToken IdKeyword(TextSpan span) + { + return new IdKeywordToken(span); + } + public static ILentKeywordToken LentKeyword(TextSpan span) + { + return new LentKeywordToken(span); + } + public static INamespaceKeywordToken NamespaceKeyword(TextSpan span) + { + return new NamespaceKeywordToken(span); + } + public static IUsingKeywordToken UsingKeyword(TextSpan span) + { + return new UsingKeywordToken(span); + } + public static IForeachKeywordToken ForeachKeyword(TextSpan span) + { + return new ForeachKeywordToken(span); + } + public static IInKeywordToken InKeyword(TextSpan span) + { + return new InKeywordToken(span); + } + public static IIfKeywordToken IfKeyword(TextSpan span) + { + return new IfKeywordToken(span); + } + public static IElseKeywordToken ElseKeyword(TextSpan span) + { + return new ElseKeywordToken(span); + } + public static IUnsafeKeywordToken UnsafeKeyword(TextSpan span) + { + return new UnsafeKeywordToken(span); + } + public static ISafeKeywordToken SafeKeyword(TextSpan span) + { + return new SafeKeywordToken(span); + } + public static ISelfKeywordToken SelfKeyword(TextSpan span) + { + return new SelfKeywordToken(span); + } + public static IMutableKeywordToken MutableKeyword(TextSpan span) + { + return new MutableKeywordToken(span); + } + public static INoneKeywordToken NoneKeyword(TextSpan span) + { + return new NoneKeywordToken(span); + } + public static IMoveKeywordToken MoveKeyword(TextSpan span) + { + return new MoveKeywordToken(span); + } + public static ICopyKeywordToken CopyKeyword(TextSpan span) + { + return new CopyKeywordToken(span); + } + public static ILoopKeywordToken LoopKeyword(TextSpan span) + { + return new LoopKeywordToken(span); + } + public static IWhileKeywordToken WhileKeyword(TextSpan span) + { + return new WhileKeywordToken(span); + } + public static IBreakKeywordToken BreakKeyword(TextSpan span) + { + return new BreakKeywordToken(span); + } + public static INextKeywordToken NextKeyword(TextSpan span) + { + return new NextKeywordToken(span); + } + public static IAnyKeywordToken AnyKeyword(TextSpan span) + { + return new AnyKeywordToken(span); + } + public static ITrueKeywordToken TrueKeyword(TextSpan span) + { + return new TrueKeywordToken(span); + } + public static IFalseKeywordToken FalseKeyword(TextSpan span) + { + return new FalseKeywordToken(span); + } + public static IAsKeywordToken AsKeyword(TextSpan span) + { + return new AsKeywordToken(span); + } + public static IAndKeywordToken AndKeyword(TextSpan span) + { + return new AndKeywordToken(span); + } + public static IOrKeywordToken OrKeyword(TextSpan span) + { + return new OrKeywordToken(span); + } + public static INotKeywordToken NotKeyword(TextSpan span) + { + return new NotKeywordToken(span); + } + } + + [Closed( + typeof(IPublishedKeywordToken), + typeof(IPublicKeywordToken), + typeof(ILetKeywordToken), + typeof(IVarKeywordToken), + typeof(IVoidKeywordToken), + typeof(IIntKeywordToken), + typeof(IUIntKeywordToken), + typeof(IByteKeywordToken), + typeof(ISizeKeywordToken), + typeof(IOffsetKeywordToken), + typeof(IBoolKeywordToken), + typeof(INeverKeywordToken), + typeof(IReturnKeywordToken), + typeof(IClassKeywordToken), + typeof(IFunctionKeywordToken), + typeof(INewKeywordToken), + typeof(IIsolatedKeywordToken), + typeof(ITransitionKeywordToken), + typeof(ISharedKeywordToken), + typeof(IConstKeywordToken), + typeof(IIdKeywordToken), + typeof(ILentKeywordToken), + typeof(INamespaceKeywordToken), + typeof(IUsingKeywordToken), + typeof(IForeachKeywordToken), + typeof(IInKeywordToken), + typeof(IIfKeywordToken), + typeof(IElseKeywordToken), + typeof(IUnsafeKeywordToken), + typeof(ISafeKeywordToken), + typeof(ISelfKeywordToken), + typeof(IMutableKeywordToken), + typeof(INoneKeywordToken), + typeof(IMoveKeywordToken), + typeof(ICopyKeywordToken), + typeof(ILoopKeywordToken), + typeof(IWhileKeywordToken), + typeof(IBreakKeywordToken), + typeof(INextKeywordToken), + typeof(IAnyKeywordToken), + typeof(ITrueKeywordToken), + typeof(IFalseKeywordToken), + typeof(IAsKeywordToken), + typeof(IAndKeywordToken), + typeof(IOrKeywordToken), + typeof(INotKeywordToken))] + public partial interface IKeywordToken : IToken { } + + + public partial interface IPublishedKeywordToken : IKeywordToken { } + internal partial class PublishedKeywordToken : Token, IPublishedKeywordToken + { + public PublishedKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IPublicKeywordToken : IKeywordToken { } + internal partial class PublicKeywordToken : Token, IPublicKeywordToken + { + public PublicKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILetKeywordToken : IKeywordToken { } + internal partial class LetKeywordToken : Token, ILetKeywordToken + { + public LetKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IVarKeywordToken : IKeywordToken { } + internal partial class VarKeywordToken : Token, IVarKeywordToken + { + public VarKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IVoidKeywordToken : IKeywordToken { } + internal partial class VoidKeywordToken : Token, IVoidKeywordToken + { + public VoidKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IIntKeywordToken : IKeywordToken { } + internal partial class IntKeywordToken : Token, IIntKeywordToken + { + public IntKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IUIntKeywordToken : IKeywordToken { } + internal partial class UIntKeywordToken : Token, IUIntKeywordToken + { + public UIntKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IByteKeywordToken : IKeywordToken { } + internal partial class ByteKeywordToken : Token, IByteKeywordToken + { + public ByteKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ISizeKeywordToken : IKeywordToken { } + internal partial class SizeKeywordToken : Token, ISizeKeywordToken + { + public SizeKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IOffsetKeywordToken : IKeywordToken { } + internal partial class OffsetKeywordToken : Token, IOffsetKeywordToken + { + public OffsetKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IBoolKeywordToken : IKeywordToken { } + internal partial class BoolKeywordToken : Token, IBoolKeywordToken + { + public BoolKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface INeverKeywordToken : IKeywordToken { } + internal partial class NeverKeywordToken : Token, INeverKeywordToken + { + public NeverKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IReturnKeywordToken : IKeywordToken { } + internal partial class ReturnKeywordToken : Token, IReturnKeywordToken + { + public ReturnKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IClassKeywordToken : IKeywordToken { } + internal partial class ClassKeywordToken : Token, IClassKeywordToken + { + public ClassKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IFunctionKeywordToken : IKeywordToken { } + internal partial class FunctionKeywordToken : Token, IFunctionKeywordToken + { + public FunctionKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface INewKeywordToken : IKeywordToken { } + internal partial class NewKeywordToken : Token, INewKeywordToken + { + public NewKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IIsolatedKeywordToken : IKeywordToken { } + internal partial class IsolatedKeywordToken : Token, IIsolatedKeywordToken + { + public IsolatedKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ITransitionKeywordToken : IKeywordToken { } + internal partial class TransitionKeywordToken : Token, ITransitionKeywordToken + { + public TransitionKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ISharedKeywordToken : IKeywordToken { } + internal partial class SharedKeywordToken : Token, ISharedKeywordToken + { + public SharedKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IConstKeywordToken : IKeywordToken { } + internal partial class ConstKeywordToken : Token, IConstKeywordToken + { + public ConstKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IIdKeywordToken : IKeywordToken { } + internal partial class IdKeywordToken : Token, IIdKeywordToken + { + public IdKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILentKeywordToken : IKeywordToken { } + internal partial class LentKeywordToken : Token, ILentKeywordToken + { + public LentKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface INamespaceKeywordToken : IKeywordToken { } + internal partial class NamespaceKeywordToken : Token, INamespaceKeywordToken + { + public NamespaceKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IUsingKeywordToken : IKeywordToken { } + internal partial class UsingKeywordToken : Token, IUsingKeywordToken + { + public UsingKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IForeachKeywordToken : IKeywordToken { } + internal partial class ForeachKeywordToken : Token, IForeachKeywordToken + { + public ForeachKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IInKeywordToken : IKeywordToken { } + internal partial class InKeywordToken : Token, IInKeywordToken + { + public InKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IIfKeywordToken : IKeywordToken { } + internal partial class IfKeywordToken : Token, IIfKeywordToken + { + public IfKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IElseKeywordToken : IKeywordToken { } + internal partial class ElseKeywordToken : Token, IElseKeywordToken + { + public ElseKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IUnsafeKeywordToken : IKeywordToken { } + internal partial class UnsafeKeywordToken : Token, IUnsafeKeywordToken + { + public UnsafeKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ISafeKeywordToken : IKeywordToken { } + internal partial class SafeKeywordToken : Token, ISafeKeywordToken + { + public SafeKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ISelfKeywordToken : IKeywordToken { } + internal partial class SelfKeywordToken : Token, ISelfKeywordToken + { + public SelfKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IMutableKeywordToken : IKeywordToken { } + internal partial class MutableKeywordToken : Token, IMutableKeywordToken + { + public MutableKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface INoneKeywordToken : IKeywordToken { } + internal partial class NoneKeywordToken : Token, INoneKeywordToken + { + public NoneKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IMoveKeywordToken : IKeywordToken { } + internal partial class MoveKeywordToken : Token, IMoveKeywordToken + { + public MoveKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ICopyKeywordToken : IKeywordToken { } + internal partial class CopyKeywordToken : Token, ICopyKeywordToken + { + public CopyKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILoopKeywordToken : IKeywordToken { } + internal partial class LoopKeywordToken : Token, ILoopKeywordToken + { + public LoopKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IWhileKeywordToken : IKeywordToken { } + internal partial class WhileKeywordToken : Token, IWhileKeywordToken + { + public WhileKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IBreakKeywordToken : IKeywordToken { } + internal partial class BreakKeywordToken : Token, IBreakKeywordToken + { + public BreakKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface INextKeywordToken : IKeywordToken { } + internal partial class NextKeywordToken : Token, INextKeywordToken + { + public NextKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IAnyKeywordToken : IKeywordToken { } + internal partial class AnyKeywordToken : Token, IAnyKeywordToken + { + public AnyKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ITrueKeywordToken : IKeywordToken { } + internal partial class TrueKeywordToken : Token, ITrueKeywordToken + { + public TrueKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IFalseKeywordToken : IKeywordToken { } + internal partial class FalseKeywordToken : Token, IFalseKeywordToken + { + public FalseKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IAsKeywordToken : IKeywordToken { } + internal partial class AsKeywordToken : Token, IAsKeywordToken + { + public AsKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IAndKeywordToken : IKeywordToken { } + internal partial class AndKeywordToken : Token, IAndKeywordToken + { + public AndKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IOrKeywordToken : IKeywordToken { } + internal partial class OrKeywordToken : Token, IOrKeywordToken + { + public OrKeywordToken(TextSpan span) + : base(span) + { + } + } + + public partial interface INotKeywordToken : IKeywordToken { } + internal partial class NotKeywordToken : Token, INotKeywordToken + { + public NotKeywordToken(TextSpan span) + : base(span) + { + } + } +} diff --git a/Compiler.Tokens/KeywordTokens.tt b/Compiler.Tokens/KeywordTokens.tt new file mode 100644 index 00000000..a0ab44eb --- /dev/null +++ b/Compiler.Tokens/KeywordTokens.tt @@ -0,0 +1,137 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +<# + var keywords = new List() + { + "Published", + "Public", + //"Protected", + "Let", + "Var", + "Void", + "Int", + //"Int8", + //"Int16", + //"Int64", + "UInt", + //"UInt16", + //"UInt64", + "Byte", + "Size", + "Offset", + "Bool", + "Never", + "Return", + "Class", + "Function", + "New", + //"Init", + "Isolated", + "Transition", + "Shared", + "Const", + "Id", + "Lent", + "Namespace", + "Using", + "Foreach", + "In", + "If", + "Else", + //"Struct", + //"Enum", + "Unsafe", + "Safe", + "Self", + //"SelfType", + //"Base", + //"Type", + "Mutable", + //"Params", + //"May", + //"No", + //"Throw", + //"Ref", + //"Abstract", + //"Get", + //"Set", + //"Requires", + //"Ensures", + //"Invariant", + //"Where", + //"Uninitialized", + "None", + //"Operator", + //"Implicit", + //"Explicit", + "Move", + "Copy", + //"Match", + "Loop", + "While", + "Break", + "Next", + //"Override", + "Any", + "True", + "False", + "As", + "And", + "Or", + "Not", + //"Trait", + //"Float32", + //"Float64", + //"Underscore", + //"External", + }.AsReadOnly(); +#> +using System; +using System.Collections.Generic; +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public static partial class TokenTypes + { + private static readonly IReadOnlyList Keyword = new List() + { +<# foreach(var keyword in keywords) {#> + typeof(<#=keyword#>KeywordToken), +<#}#> + }.AsReadOnly(); + } + + public static partial class TokenFactory + { +<# foreach(var keyword in keywords) {#> + public static I<#=keyword#>KeywordToken <#=keyword#>Keyword(TextSpan span) + { + return new <#=keyword#>KeywordToken(span); + } +<#}#> + } + + [Closed( +<# for (int i = 0; i < keywords.Count; i++) {#> + typeof(I<#=keywords[i]#>KeywordToken)<# if(i == keywords.Count-1) {#>)]<#} else {#>,<#}#> + +<#}#> public partial interface IKeywordToken : IToken { } + +<# foreach(var keyword in keywords) {#> + + public partial interface I<#=keyword#>KeywordToken : IKeywordToken { } + internal partial class <#=keyword#>KeywordToken : Token, I<#=keyword#>KeywordToken + { + public <#=keyword#>KeywordToken(TextSpan span) + : base(span) + { + } + } +<#}#> +} diff --git a/Compiler.Tokens/OperatorPrecedence.cs b/Compiler.Tokens/OperatorPrecedence.cs new file mode 100644 index 00000000..c2632035 --- /dev/null +++ b/Compiler.Tokens/OperatorPrecedence.cs @@ -0,0 +1,19 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public enum OperatorPrecedence + { + Min = Assignment, // The minimum precedence + AboveAssignment = Coalesce, + Assignment = 1, // `=` `+=` `-=` + Coalesce, // `??` + LogicalOr, // `or` + LogicalAnd, // `and` + Equality, // `==` `≠` + Relational, // `<` `<=` `>` `>=` `<:` + Range, // `..` `..<` + Additive, // `+` `-` + Multiplicative, // `*` `/` + Unary, // `+` `-` `not` `?` + Primary // `f()` `.` `[]` + } +} diff --git a/Compiler.Tokens/README.md b/Compiler.Tokens/README.md new file mode 100644 index 00000000..fcb34192 --- /dev/null +++ b/Compiler.Tokens/README.md @@ -0,0 +1,19 @@ +# Azoth.Tools.Bootstrap.Compiler.Tokens + +## Naming Conventions + +There are a number of reasons that token naming is challenging: + +* Operators can be overloaded so that `*` may not mean multiply for example. +* Operators and keywords are used in multiple contexts where they have different meanings. For example, `-` could be subtract or negate. +* Characters have multiple names. For example, `#` is called "number sign", "hash sign", or "pound sign", or even "octothorpe." + +So while the temptation to name tokens after what they mean is strong, the policy adopted by the Roslyn project makes more sense. Tokens are named after their characters. All keyword tokens are simply the keyword followed by "KeywordToken". All tokens composed of symbols are just the concatenation of the names of the individual characters followed by "Token". For characters, the Unicode official name is generally used. Filler words like "sign" are dropped unless they are needed to disambiguate from another token. However, occasionally, the unicode character names are very strange and a more common name is used. + +Some tokens in Azoth have multiple acceptable character sequences. For example, both "`->`" (U+002D, U+003E) and "`→`" (U+2192) are acceptable. In these cases, the token is named after the shorter character the longer sequence is meant to be evocative of. + +## Software Architecture + +Ideally tokens would be enums (like Azoth enum structs). That would maximize performance and minimize memory footprint. Since C# enums are not flexible enough, this can't be done. Instead switching on type is used. + +Because the different tokens can be grouped many different ways into complex hierarchies, it became necessary to use interfaces to represent this. In time, the mix of classes and interfaces became confusing and limiting. For that reason, the tokens have been fully encapsulated in this project with only their interfaces exposed. This allows the equivalent of the Azoth ability to implement the interface of a class. diff --git a/Compiler.Tokens/StringLiteralToken.cs b/Compiler.Tokens/StringLiteralToken.cs new file mode 100644 index 00000000..10479f0b --- /dev/null +++ b/Compiler.Tokens/StringLiteralToken.cs @@ -0,0 +1,31 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal class StringLiteralToken : Token, IStringLiteralToken + { + public string Value { get; } + + public StringLiteralToken(TextSpan span, string value) + : base(span) + { + Value = value; + } + + // Helpful for debugging + public override string ToString() + { + return '"' + Value.Escape() + '"'; + } + } + + public static partial class TokenFactory + { + + public static IStringLiteralToken StringLiteral(TextSpan span, string value) + { + return new StringLiteralToken(span, value); + } + } +} diff --git a/Compiler.Tokens/Token.cs b/Compiler.Tokens/Token.cs new file mode 100644 index 00000000..b991ed7b --- /dev/null +++ b/Compiler.Tokens/Token.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal abstract class Token + { + public TextSpan Span { get; } + + protected Token(TextSpan span) + { + Span = span; + } + public string Text(CodeText code) + { + return Span.GetText(code.Text); + } + } +} diff --git a/Compiler.Tokens/TokenTypes.Keywords.cs b/Compiler.Tokens/TokenTypes.Keywords.cs new file mode 100644 index 00000000..dd05df1b --- /dev/null +++ b/Compiler.Tokens/TokenTypes.Keywords.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public static partial class TokenTypes + { + // Must be before KeywordFactories because it is used in the construction of it + private static readonly int KeywordTokenLength = "KeywordToken".Length; + + public static readonly FixedDictionary> KeywordFactories = + BuildKeywordFactories(); + + public static readonly IReadOnlyCollection Keywords = KeywordFactories.Keys.ToHashSet(); + + private static FixedDictionary> BuildKeywordFactories() + { + var factories = new Dictionary>(); + + foreach (var tokenType in Keyword) + { + string keyword; + var tokenTypeName = tokenType.Name; + switch (tokenTypeName) + { + // Some exceptions to the normal rule + case "FunctionKeywordToken": + keyword = "fn"; + break; + case "SelfTypeKeywordToken": + keyword = "Self"; + break; + case "IsolatedKeywordToken": + keyword = "iso"; + break; + case "TransitionKeywordToken": + keyword = "trn"; + break; + case "MutableKeywordToken": + keyword = "mut"; + break; + case "AnyKeywordToken": + keyword = "Any"; + break; + case "TypeKeywordToken": + keyword = "Type"; + break; + case "UnderscoreKeywordToken": + keyword = "_"; + break; + default: +#pragma warning disable CA1308 // Normalize strings to uppercase. Reason: this is not a normalization + keyword = tokenTypeName + .Substring(0, tokenTypeName.Length - KeywordTokenLength) + .ToLower(CultureInfo.InvariantCulture); +#pragma warning restore CA1308 // Normalize strings to uppercase + break; + } + var factory = CompileFactory(tokenType); + factories.Add(keyword, factory); + } + return factories.ToFixedDictionary(); + } + + private static Func CompileFactory(Type tokenType) + where T : IToken + { + var spanParam = Expression.Parameter(typeof(TextSpan), "span"); + var newExpression = Expression.New(tokenType.GetConstructor(new[] { typeof(TextSpan) }), spanParam); + var factory = + Expression.Lambda>( + newExpression, spanParam); + return factory.Compile(); + } + } +} diff --git a/Compiler.Tokens/TokenTypes.ReservedWord.cs b/Compiler.Tokens/TokenTypes.ReservedWord.cs new file mode 100644 index 00000000..8013d36e --- /dev/null +++ b/Compiler.Tokens/TokenTypes.ReservedWord.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public partial class TokenTypes + { + private static readonly Regex ReservedTypeNamePattern = new Regex(@"^(((u?int|float|decimal)\d*)|(u?fixed(\d+\.\d+)?)|(real(\.\d+)?))$", + RegexOptions.Compiled| RegexOptions.ExplicitCapture); + + public static readonly IReadOnlyCollection ReservedWords = new HashSet() + { + // Keywords not yet implemented + "abstract", + "base", + "delete", + "ensures", + "enum", + "explicit", + "external", + "float32", + "float64", + "forever", + "get", + "implicit", + "init", + "int8", + "int16", + "int64", + "invariant", + "match", + "may", + "Metatype", + "no", + "operator", + "override", + "params", + "protected", + "ref", + "requires", + "Self", + "set", + "struct", + "throw", + "Tuple", + "Type", + "uint16", + "uint64", + "uninitialized", + "where", + + // Reserved Words + "alias", // Planned Type Alias Feature + "case", // Useful for switch like constructs + "cast", // Casting + "checked", // Checked Operations + "const_cast", // Casting + "continue", // Useful for control flow + "default", // Useful for switch like constructs and default values + "defer", // Swift style "`defer`",statements + "do", // "`do while`",loop or Swift style "`do`",block + "dynamic_cast", // Casting + "extend", // Extensions + "extension", // Extensions + "fallthrough", // Useful for switch like constructs + "for", // Common Keyword + "from", // Common Keyword + "guard", // Swift style guard statements + "internal", // Common Keyword + "null", // Null value for pointers + "otherwise", // Loop Else + "package", // Qualify names with the current package (i.e. `package::.name`) + "partial", // Partial Classes + "private", // Common Keyword + "reinterpret_cast", // Casting + "repeat", // Swift style "`repeat {} while condition;`",loop + "replace", // Partial Classes + "select", // C# style query + "sizeof", "size_of", // Size of Operator + "switch", // Useful for switch like constructs + "symmetric", // Symmetric operators + "transmute", // Reinterpret Cast + "then", // Python style loop else + "type", // Type aliases and declarations + "unchecked", // Unchecked Operations + "unless", // Ruby style `if not` statement or `unless break` for Python style loop else + "when", // C# style exception filters + "xor", // Logical exclusive or operator + "yield", // Generators + }; + + /// + /// Whether the value is a reserved type name. Note, this returns + /// true for actual type names like `int32`. So values must be + /// checked for being a keyword before being checked with this function. + /// + public static bool IsReservedTypeName(string value) + { + return ReservedTypeNamePattern.IsMatch(value); + } + } +} diff --git a/Compiler.Tokens/Tokens.cs b/Compiler.Tokens/Tokens.cs new file mode 100644 index 00000000..9a04ed32 --- /dev/null +++ b/Compiler.Tokens/Tokens.cs @@ -0,0 +1,538 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public static partial class TokenFactory + { + public static IEndOfFileToken EndOfFile(TextSpan span) + { + return new EndOfFileToken(span); + } + + public static IOpenBraceToken OpenBrace(TextSpan span) + { + return new OpenBraceToken(span); + } + + public static ICloseBraceToken CloseBrace(TextSpan span) + { + return new CloseBraceToken(span); + } + + public static IOpenParenToken OpenParen(TextSpan span) + { + return new OpenParenToken(span); + } + + public static ICloseParenToken CloseParen(TextSpan span) + { + return new CloseParenToken(span); + } + + public static ISemicolonToken Semicolon(TextSpan span) + { + return new SemicolonToken(span); + } + + public static ICommaToken Comma(TextSpan span) + { + return new CommaToken(span); + } + + public static IColonToken Colon(TextSpan span) + { + return new ColonToken(span); + } + + public static IRightArrowToken RightArrow(TextSpan span) + { + return new RightArrowToken(span); + } + + public static IDotToken Dot(TextSpan span) + { + return new DotToken(span); + } + + public static IColonColonDotToken ColonColonDot(TextSpan span) + { + return new ColonColonDotToken(span); + } + + public static IDotDotToken DotDot(TextSpan span) + { + return new DotDotToken(span); + } + + public static ILessThanDotDotToken LessThanDotDot(TextSpan span) + { + return new LessThanDotDotToken(span); + } + + public static IDotDotLessThanToken DotDotLessThan(TextSpan span) + { + return new DotDotLessThanToken(span); + } + + public static ILessThanDotDotLessThanToken LessThanDotDotLessThan(TextSpan span) + { + return new LessThanDotDotLessThanToken(span); + } + + public static IPlusToken Plus(TextSpan span) + { + return new PlusToken(span); + } + + public static IMinusToken Minus(TextSpan span) + { + return new MinusToken(span); + } + + public static IAsteriskToken Asterisk(TextSpan span) + { + return new AsteriskToken(span); + } + + public static ISlashToken Slash(TextSpan span) + { + return new SlashToken(span); + } + + public static IEqualsToken Equals(TextSpan span) + { + return new EqualsToken(span); + } + + public static IEqualsEqualsToken EqualsEquals(TextSpan span) + { + return new EqualsEqualsToken(span); + } + + public static INotEqualToken NotEqual(TextSpan span) + { + return new NotEqualToken(span); + } + + public static IGreaterThanToken GreaterThan(TextSpan span) + { + return new GreaterThanToken(span); + } + + public static IGreaterThanOrEqualToken GreaterThanOrEqual(TextSpan span) + { + return new GreaterThanOrEqualToken(span); + } + + public static ILessThanToken LessThan(TextSpan span) + { + return new LessThanToken(span); + } + + public static ILessThanOrEqualToken LessThanOrEqual(TextSpan span) + { + return new LessThanOrEqualToken(span); + } + + public static IPlusEqualsToken PlusEquals(TextSpan span) + { + return new PlusEqualsToken(span); + } + + public static IMinusEqualsToken MinusEquals(TextSpan span) + { + return new MinusEqualsToken(span); + } + + public static IAsteriskEqualsToken AsteriskEquals(TextSpan span) + { + return new AsteriskEqualsToken(span); + } + + public static ISlashEqualsToken SlashEquals(TextSpan span) + { + return new SlashEqualsToken(span); + } + + public static IQuestionToken Question(TextSpan span) + { + return new QuestionToken(span); + } + + public static IQuestionQuestionToken QuestionQuestion(TextSpan span) + { + return new QuestionQuestionToken(span); + } + + public static IQuestionDotToken QuestionDot(TextSpan span) + { + return new QuestionDotToken(span); + } + + public static ILessThanColonToken LessThanColon(TextSpan span) + { + return new LessThanColonToken(span); + } + + public static IRightDoubleArrowToken RightDoubleArrow(TextSpan span) + { + return new RightDoubleArrowToken(span); + } + + } + + [Closed( + typeof(IEndOfFileToken), + typeof(IOpenBraceToken), + typeof(ICloseBraceToken), + typeof(IOpenParenToken), + typeof(ICloseParenToken), + typeof(ISemicolonToken), + typeof(ICommaToken), + typeof(IColonToken), + typeof(IRightArrowToken), + typeof(IDotToken), + typeof(IColonColonDotToken), + typeof(IDotDotToken), + typeof(ILessThanDotDotToken), + typeof(IDotDotLessThanToken), + typeof(ILessThanDotDotLessThanToken), + typeof(IPlusToken), + typeof(IMinusToken), + typeof(IAsteriskToken), + typeof(ISlashToken), + typeof(IEqualsToken), + typeof(IEqualsEqualsToken), + typeof(INotEqualToken), + typeof(IGreaterThanToken), + typeof(IGreaterThanOrEqualToken), + typeof(ILessThanToken), + typeof(ILessThanOrEqualToken), + typeof(IPlusEqualsToken), + typeof(IMinusEqualsToken), + typeof(IAsteriskEqualsToken), + typeof(ISlashEqualsToken), + typeof(IQuestionToken), + typeof(IQuestionQuestionToken), + typeof(IQuestionDotToken), + typeof(ILessThanColonToken), + typeof(IRightDoubleArrowToken))] + public partial interface IEssentialToken { } + + + public partial interface IEndOfFileToken : IEssentialToken { } + internal partial class EndOfFileToken : Token, IEndOfFileToken + { + public EndOfFileToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IOpenBraceToken : IEssentialToken { } + internal partial class OpenBraceToken : Token, IOpenBraceToken + { + public OpenBraceToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ICloseBraceToken : IEssentialToken { } + internal partial class CloseBraceToken : Token, ICloseBraceToken + { + public CloseBraceToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IOpenParenToken : IEssentialToken { } + internal partial class OpenParenToken : Token, IOpenParenToken + { + public OpenParenToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ICloseParenToken : IEssentialToken { } + internal partial class CloseParenToken : Token, ICloseParenToken + { + public CloseParenToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ISemicolonToken : IEssentialToken { } + internal partial class SemicolonToken : Token, ISemicolonToken + { + public SemicolonToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ICommaToken : IEssentialToken { } + internal partial class CommaToken : Token, ICommaToken + { + public CommaToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IColonToken : IEssentialToken { } + internal partial class ColonToken : Token, IColonToken + { + public ColonToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IRightArrowToken : IEssentialToken { } + internal partial class RightArrowToken : Token, IRightArrowToken + { + public RightArrowToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IDotToken : IEssentialToken { } + internal partial class DotToken : Token, IDotToken + { + public DotToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IColonColonDotToken : IEssentialToken { } + internal partial class ColonColonDotToken : Token, IColonColonDotToken + { + public ColonColonDotToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IDotDotToken : IEssentialToken { } + internal partial class DotDotToken : Token, IDotDotToken + { + public DotDotToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILessThanDotDotToken : IEssentialToken { } + internal partial class LessThanDotDotToken : Token, ILessThanDotDotToken + { + public LessThanDotDotToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IDotDotLessThanToken : IEssentialToken { } + internal partial class DotDotLessThanToken : Token, IDotDotLessThanToken + { + public DotDotLessThanToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILessThanDotDotLessThanToken : IEssentialToken { } + internal partial class LessThanDotDotLessThanToken : Token, ILessThanDotDotLessThanToken + { + public LessThanDotDotLessThanToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IPlusToken : IEssentialToken { } + internal partial class PlusToken : Token, IPlusToken + { + public PlusToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IMinusToken : IEssentialToken { } + internal partial class MinusToken : Token, IMinusToken + { + public MinusToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IAsteriskToken : IEssentialToken { } + internal partial class AsteriskToken : Token, IAsteriskToken + { + public AsteriskToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ISlashToken : IEssentialToken { } + internal partial class SlashToken : Token, ISlashToken + { + public SlashToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IEqualsToken : IEssentialToken { } + internal partial class EqualsToken : Token, IEqualsToken + { + public EqualsToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IEqualsEqualsToken : IEssentialToken { } + internal partial class EqualsEqualsToken : Token, IEqualsEqualsToken + { + public EqualsEqualsToken(TextSpan span) + : base(span) + { + } + } + + public partial interface INotEqualToken : IEssentialToken { } + internal partial class NotEqualToken : Token, INotEqualToken + { + public NotEqualToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IGreaterThanToken : IEssentialToken { } + internal partial class GreaterThanToken : Token, IGreaterThanToken + { + public GreaterThanToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IGreaterThanOrEqualToken : IEssentialToken { } + internal partial class GreaterThanOrEqualToken : Token, IGreaterThanOrEqualToken + { + public GreaterThanOrEqualToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILessThanToken : IEssentialToken { } + internal partial class LessThanToken : Token, ILessThanToken + { + public LessThanToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILessThanOrEqualToken : IEssentialToken { } + internal partial class LessThanOrEqualToken : Token, ILessThanOrEqualToken + { + public LessThanOrEqualToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IPlusEqualsToken : IEssentialToken { } + internal partial class PlusEqualsToken : Token, IPlusEqualsToken + { + public PlusEqualsToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IMinusEqualsToken : IEssentialToken { } + internal partial class MinusEqualsToken : Token, IMinusEqualsToken + { + public MinusEqualsToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IAsteriskEqualsToken : IEssentialToken { } + internal partial class AsteriskEqualsToken : Token, IAsteriskEqualsToken + { + public AsteriskEqualsToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ISlashEqualsToken : IEssentialToken { } + internal partial class SlashEqualsToken : Token, ISlashEqualsToken + { + public SlashEqualsToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IQuestionToken : IEssentialToken { } + internal partial class QuestionToken : Token, IQuestionToken + { + public QuestionToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IQuestionQuestionToken : IEssentialToken { } + internal partial class QuestionQuestionToken : Token, IQuestionQuestionToken + { + public QuestionQuestionToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IQuestionDotToken : IEssentialToken { } + internal partial class QuestionDotToken : Token, IQuestionDotToken + { + public QuestionDotToken(TextSpan span) + : base(span) + { + } + } + + public partial interface ILessThanColonToken : IEssentialToken { } + internal partial class LessThanColonToken : Token, ILessThanColonToken + { + public LessThanColonToken(TextSpan span) + : base(span) + { + } + } + + public partial interface IRightDoubleArrowToken : IEssentialToken { } + internal partial class RightDoubleArrowToken : Token, IRightDoubleArrowToken + { + public RightDoubleArrowToken(TextSpan span) + : base(span) + { + } + } +} diff --git a/Compiler.Tokens/Tokens.tt b/Compiler.Tokens/Tokens.tt new file mode 100644 index 00000000..124dfb21 --- /dev/null +++ b/Compiler.Tokens/Tokens.tt @@ -0,0 +1,88 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +<# + var tokens = new List() + { + "EndOfFile", + "OpenBrace", // `{` U+007B + "CloseBrace", // `}` U+007D + "OpenParen", // `(` U+0028 + "CloseParen", // `)` U+0029 + //"OpenBracket", // `[` U+005B + //"CloseBracket", // `]` U+005D + "Semicolon", // `;` U+003B + "Comma", // `",` U+002C + //"Pipe", // `|` U+007C + "Colon", // `:` U+003A + "RightArrow", // `→` U+2192, `->` U+002D, U+003E + //"Hash", // `#` U+0023 + //"HashHash", // `##` U+0023, U+0023 + "Dot", // `.` U+002E + "ColonColonDot", // `::.` U+003A, U+003A, U+002E + "DotDot", // `..` U+002E, U+002E + "LessThanDotDot", // `<..` U+003C, U+002E, U+002E + "DotDotLessThan", // `..<` U+002E, U+002E, U+003C + "LessThanDotDotLessThan", // `<..<` U+002E, U+002E, U+003C + //"AtSign", // `@` U+0040 (named at sign to distinguish it from the word "at") + //"Caret", // `^` U+005E + //"CaretDot", // `^.` U+005E, U+002E + "Plus", // `+` U+002B + "Minus", // `-` U+002D + "Asterisk", // `*` U+002A + "Slash", // `/` U+002F + "Equals", // `=` U+003D + "EqualsEquals", // `==` U+003D", U+003D + "NotEqual", // `≠` U+2260, `=/=` U+003D, U+002F, U+003D + "GreaterThan", // `>` U+003E + "GreaterThanOrEqual", // `≥` U+2265", `>=` U+003E", U+003D + "LessThan", // `<` U+003C + "LessThanOrEqual", // `≤` U+2264", `<=` U+003C", U+003D + "PlusEquals", // `+=` U+002B", U+003D + "MinusEquals", // `-=` U+002D", U+003D + "AsteriskEquals", // `*=` U+002A", U+003D + "SlashEquals", // `/=` U+002F", U+003D + "Question", // `?` U+003F + "QuestionQuestion", // `??` U+003F, U+003F + "QuestionDot", // `?.` U+003F, U+002E + "LessThanColon" , // `<:` U+003C, U+003A + "RightDoubleArrow", // `⇒` U+21D2, `=>` U+003D, U+003E + }.AsReadOnly(); +#> +using Azoth.Tools.Bootstrap.Compiler.Core; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + public static partial class TokenFactory + { +<# foreach(var token in tokens) {#> + public static I<#=token#>Token <#=token#>(TextSpan span) + { + return new <#=token#>Token(span); + } + +<#}#> + } + + [Closed( +<# for (int i = 0; i < tokens.Count; i++) {#> + typeof(I<#=tokens[i]#>Token)<# if(i == tokens.Count-1) {#>)]<#} else {#>,<#}#> + +<#}#> public partial interface IEssentialToken { } + +<# foreach(var token in tokens) {#> + + public partial interface I<#=token#>Token : IEssentialToken { } + internal partial class <#=token#>Token : Token, I<#=token#>Token + { + public <#=token#>Token(TextSpan span) + : base(span) + { + } + } +<#}#> +} diff --git a/Compiler.Tokens/TrueKeywordToken.cs b/Compiler.Tokens/TrueKeywordToken.cs new file mode 100644 index 00000000..bfac89a7 --- /dev/null +++ b/Compiler.Tokens/TrueKeywordToken.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Tokens +{ + internal partial class TrueKeywordToken + { + public bool Value => true; + } +} diff --git a/Compiler.Types/AnyType.cs b/Compiler.Types/AnyType.cs new file mode 100644 index 00000000..b753f96e --- /dev/null +++ b/Compiler.Types/AnyType.cs @@ -0,0 +1,54 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The universal type all reference types can be converted to. A top type + /// for reference and function types. + /// + /// + /// `Any` is "declared" mutable so that it can hold mutable references to + /// mutable types. + /// + public sealed class AnyType : ReferenceType + { + public AnyType(ReferenceCapability referenceCapability) + : base(true, referenceCapability) + { + } + + public override bool IsKnown => true; + + + + public override bool Equals(DataType? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is AnyType otherType + && ReferenceCapability == otherType.ReferenceCapability; + } + + public override int GetHashCode() + { + return HashCode.Combine(SpecialTypeName.Any, ReferenceCapability); + } + + protected internal override Self To_ReturnsSelf(ReferenceCapability referenceCapability) + { + return new AnyType(referenceCapability); + } + + public override string ToILString() + { + return $"{ReferenceCapability} Any"; + } + + public override string ToSourceCodeString() + { + return $"{ReferenceCapability} Any"; + } + } +} diff --git a/Compiler.Types/BoolConstantType.cs b/Compiler.Types/BoolConstantType.cs new file mode 100644 index 00000000..d9689d53 --- /dev/null +++ b/Compiler.Types/BoolConstantType.cs @@ -0,0 +1,36 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + public sealed class BoolConstantType : BoolType + { + internal new static readonly BoolConstantType True = new BoolConstantType(true); + internal new static readonly BoolConstantType False = new BoolConstantType(false); + + public override bool IsConstant => true; + + public bool Value { get; } + + private BoolConstantType(bool value) + : base(value ? SpecialTypeName.True : SpecialTypeName.False) + { + Value = value; + } + + public override DataType ToNonConstantType() + { + return Bool; + } + + public override string ToSourceCodeString() + { + throw new InvalidOperationException("Bool constant type has no source code representation"); + } + + public override string ToILString() + { + return $"const[{(Value ? "true" : "false")}]"; + } + } +} diff --git a/Compiler.Types/BoolType.cs b/Compiler.Types/BoolType.cs new file mode 100644 index 00000000..0655912a --- /dev/null +++ b/Compiler.Types/BoolType.cs @@ -0,0 +1,20 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + public class BoolType : SimpleType + { + #region Singleton + internal static readonly BoolType Instance = new BoolType(); + + private BoolType() + : base(SpecialTypeName.Bool) + { } + #endregion + + private protected BoolType(SpecialTypeName name) + : base(name) { } + + public override bool IsKnown => true; + } +} diff --git a/Compiler.Types/Compiler.Types.csproj b/Compiler.Types/Compiler.Types.csproj new file mode 100644 index 00000000..ca655f4f --- /dev/null +++ b/Compiler.Types/Compiler.Types.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.Types + Azoth.Tools.Bootstrap.Compiler.Types + enable + + + + DEBUG;TRACE + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Compiler.Types/DataType.cs b/Compiler.Types/DataType.cs new file mode 100644 index 00000000..673ae9ed --- /dev/null +++ b/Compiler.Types/DataType.cs @@ -0,0 +1,127 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The data type of a value in an Azoth program. This includes potentially + /// unresolved types like `UnknownType` or types containing unknown parts. + /// + [Closed( + typeof(ReferenceType), + typeof(ValueType), + typeof(EmptyType), + typeof(UnknownType))] + [DebuggerDisplay("{" + nameof(ToILString) + "(),nq}")] + public abstract class DataType : IEquatable + { + #region Standard Types + public static readonly UnknownType Unknown = UnknownType.Instance; + public static readonly VoidType Void = VoidType.Instance; + public static readonly NeverType Never = NeverType.Instance; + public static readonly BoolType Bool = BoolType.Instance; + public static readonly BoolConstantType True = BoolConstantType.True; + public static readonly BoolConstantType False = BoolConstantType.False; + public static readonly FixedSizeIntegerType Byte = FixedSizeIntegerType.Byte; +#pragma warning disable CA1720 + public static readonly FixedSizeIntegerType Int = FixedSizeIntegerType.Int; + public static readonly FixedSizeIntegerType UInt = FixedSizeIntegerType.UInt; +#pragma warning restore CA1720 + public static readonly PointerSizedIntegerType Size = PointerSizedIntegerType.Size; + public static readonly PointerSizedIntegerType Offset = PointerSizedIntegerType.Offset; + + /// + /// The value `none` has this type, which is `never?` + /// + public static readonly OptionalType None = new OptionalType(Never); + #endregion + + /// + /// The `never` and `void` types are the only empty types. This means + /// there are no values of either type. The `never` type is defined + /// as the type without values. The `void` type behaves more like a unit + /// type. However, its implementation is that it doesn't have a value + /// and represents the lack of that value. For example, that a function + /// doesn't return a value or that an argument is to be dropped. + /// + public virtual bool IsEmpty => false; + + public virtual bool IsConstant => false; + + /// + /// A known type is one that has no unknown parts + /// + public abstract bool IsKnown { get; } + + /// + /// The semantics of values of this type + /// + public abstract TypeSemantics Semantics { get; } + + private protected DataType() { } + + public virtual DataType ToNonConstantType() + { + return this; + } + + // TODO equality + + [Obsolete("Use ToSourceCodeString() or ToILString() instead")] +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + public sealed override string ToString() +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member + { + return ToILString(); + } + + /// + /// How this type would be written in source code + /// + public abstract string ToSourceCodeString(); + + /// + /// How this type would be written in IL + /// + public abstract string ToILString(); + + [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", + Justification = "Return self idiom")] + public static implicit operator Self(DataType type) + { + return new Self(type); + } + + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", + Justification = "Returns self idiom")] + protected internal virtual Self ToReadOnly_ReturnsSelf() + { + return this; + } + + #region Equality + public abstract bool Equals(DataType? other); + public abstract override int GetHashCode(); + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((DataType)obj); + } + + public static bool operator ==(DataType? left, DataType? right) + { + return Equals(left, right); + } + + public static bool operator !=(DataType? left, DataType? right) + { + return !Equals(left, right); + } + #endregion + } +} diff --git a/Compiler.Types/DataTypeExtensions.cs b/Compiler.Types/DataTypeExtensions.cs new file mode 100644 index 00000000..cf1e432a --- /dev/null +++ b/Compiler.Types/DataTypeExtensions.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using Azoth.Tools.Bootstrap.Compiler.Core.Promises; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + public static class DataTypeExtensions + { + /// + /// Returns the same type except with any mutability removed + /// + [DebuggerHidden] + public static T ToReadOnly(this T type) + where T : DataType + { + return type.ToReadOnly_ReturnsSelf().Cast(); + } + + /// + /// Tests whether a place of the target type could be assigned a value of the source type. + /// This does not account for implicit conversions, but does allow for borrowing + /// and sharing. It also allows for isolated upgrading to mutable. + /// + public static bool IsAssignableFrom(this DataType target, DataType source) + { + switch (target, source) + { + case (_, _) when target.Equals(source): + case (UnknownType _, _): + case (_, UnknownType _): + case (BoolType _, BoolConstantType _): + return true; + case (AnyType targetReference, ReferenceType sourceReference): + return targetReference.ReferenceCapability.IsAssignableFrom(sourceReference.ReferenceCapability); + case (ReferenceType _, AnyType _): + return false; + case (ObjectType targetReference, ObjectType sourceReference): + return targetReference.ReferenceCapability.IsAssignableFrom(sourceReference.ReferenceCapability) + && targetReference.Name == sourceReference.Name + && targetReference.ContainingNamespace == sourceReference.ContainingNamespace; + case (OptionalType targetOptional, OptionalType sourceOptional): + return IsAssignableFrom(targetOptional.Referent, sourceOptional.Referent); + default: + return false; + } + } + + /// + /// Validates that a type as been assigned. + /// + [DebuggerHidden] + public static DataType Assigned(this DataType? type) + { + return type ?? throw new InvalidOperationException("Type not assigned"); + } + + [DebuggerHidden] + public static DataType Known(this DataType? type) + { + if (!type.Assigned().IsKnown) throw new InvalidOperationException($"Type {type} not known"); + + return type!; + } + + [DebuggerHidden] + public static DataType Known(this IPromise promise) + { + var type = promise.Result; + if (!type.IsKnown) throw new InvalidOperationException($"Type {type} not known"); + + return type; + } + + public static ReferenceType? UnderlyingReferenceType(this DataType type) + { + return type switch + { + ReferenceType referenceType => referenceType, + OptionalType optionalType when optionalType.Referent is ReferenceType referenceType => referenceType, + _ => null + }; + } + } +} diff --git a/Compiler.Types/EmptyType.cs b/Compiler.Types/EmptyType.cs new file mode 100644 index 00000000..634af4e5 --- /dev/null +++ b/Compiler.Types/EmptyType.cs @@ -0,0 +1,48 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The two empty types are `never` and `void`. They are the only types with + /// no values. + /// + [Closed( + typeof(VoidType), + typeof(NeverType))] + public abstract class EmptyType : DataType + { + public SpecialTypeName Name { get; } + + public override bool IsEmpty => true; + + public override bool IsKnown => true; + + private protected EmptyType(SpecialTypeName name) + { + Name = name; + } + + public override string ToSourceCodeString() + { + return Name.ToString(); + } + + public override string ToILString() + { + return ToSourceCodeString(); + } + + public override bool Equals(DataType? other) + { + // Empty types are all fixed instances, so a reference equality suffices + return ReferenceEquals(this, other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Name); + } + } +} diff --git a/Compiler.Types/FixedSizeIntegerType.cs b/Compiler.Types/FixedSizeIntegerType.cs new file mode 100644 index 00000000..7679aa3d --- /dev/null +++ b/Compiler.Types/FixedSizeIntegerType.cs @@ -0,0 +1,30 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + public sealed class FixedSizeIntegerType : IntegerType + { + //internal new static readonly FixedSizeIntegerType Int8 = new FixedSizeIntegerType("int8", -8); + internal new static readonly FixedSizeIntegerType Byte = new FixedSizeIntegerType(SpecialTypeName.Byte, 8); + //internal new static readonly FixedSizeIntegerType Int16 = new FixedSizeIntegerType("int16", -16); + //internal new static readonly FixedSizeIntegerType UInt16 = new FixedSizeIntegerType("uint16", 16); +#pragma warning disable CA1720 + internal new static readonly FixedSizeIntegerType Int = new FixedSizeIntegerType(SpecialTypeName.Int, -32); + internal new static readonly FixedSizeIntegerType UInt = new FixedSizeIntegerType(SpecialTypeName.UInt, 32); +#pragma warning restore CA1720 + //internal new static readonly FixedSizeIntegerType Int64 = new FixedSizeIntegerType("int64", -64); + //internal new static readonly FixedSizeIntegerType UInt64 = new FixedSizeIntegerType("uint64", 64); + + public bool IsSigned { get; } + public int Bits { get; } + public override bool IsKnown => true; + + private FixedSizeIntegerType(SpecialTypeName name, int bits) + : base(name) + { + IsSigned = bits < 0; + Bits = Math.Abs(bits); + } + } +} diff --git a/Compiler.Types/IntegerConstantType.cs b/Compiler.Types/IntegerConstantType.cs new file mode 100644 index 00000000..2aa86b4e --- /dev/null +++ b/Compiler.Types/IntegerConstantType.cs @@ -0,0 +1,51 @@ +using System; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// This is the type of integer constants, it isn't possible to declare a + /// variable to have this type. + /// + public sealed class IntegerConstantType : IntegerType + { + public override bool IsConstant => true; + public BigInteger Value { get; } + public override bool IsKnown => true; + + public IntegerConstantType(BigInteger value) + : base(SpecialTypeName.ConstInt) + { + Value = value; + } + + public override DataType ToNonConstantType() + { + return Int; + } + + public override bool Equals(DataType? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is IntegerConstantType otherType + && Value == otherType.Value; + } + + public override int GetHashCode() + { + return HashCode.Combine(Value); + } + + public override string ToSourceCodeString() + { + throw new InvalidOperationException("Integer constant type has no source code representation"); + } + + public override string ToILString() + { + return $"const[{Value}]"; + } + } +} diff --git a/Compiler.Types/IntegerType.cs b/Compiler.Types/IntegerType.cs new file mode 100644 index 00000000..d9d318be --- /dev/null +++ b/Compiler.Types/IntegerType.cs @@ -0,0 +1,17 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + [Closed( + typeof(IntegerConstantType), + typeof(FixedSizeIntegerType), + typeof(PointerSizedIntegerType))] + public abstract class IntegerType : NumericType + { + private protected IntegerType(SpecialTypeName name) + : base(name) + { + } + } +} diff --git a/Compiler.Types/InternalsVisibleTo.cs b/Compiler.Types/InternalsVisibleTo.cs new file mode 100644 index 00000000..8e35a2ab --- /dev/null +++ b/Compiler.Types/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types")] diff --git a/Compiler.Types/Metatype.cs b/Compiler.Types/Metatype.cs new file mode 100644 index 00000000..91e9ca94 --- /dev/null +++ b/Compiler.Types/Metatype.cs @@ -0,0 +1,54 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// As in Swift, the metatype is the type of a type. That is the type of the + /// metaobject which has the class or associated functions on it. The metatype + /// of a class can be accessed using `Class_Name.type`. Metatypes are subtypes + /// of `Type`. + /// + /// This can be very confusing. Think of types as values. Then `Class_Name` + /// refers to an object whose type is `Class_Name.type`. Note that for any + /// any type `T`, `T.type.type == Type`. + /// + /// see https://docs.swift.org/swift-book/ReferenceManual/Types.html#grammar_metatype-type + /// + //public class Metatype : ObjectType + //{ + // // The type this is a metatype for. Named instance because it represents + // // the single object instance that is of this metatype. + // public DataType Instance { get; } + // public override bool IsKnown => Instance.IsKnown; + + // public Metatype(ObjectType instanceType) + // : base(new QualifiedName(instanceType.Name, SpecialName.Type), + // false, Mutability.Immutable, + // Lifetime.Forever) + // { + // Instance = instanceType; + // } + + // public Metatype(SimpleType instanceType) + // : base(new QualifiedName(instanceType.Name, SpecialName.Type), + // false, Mutability.Immutable, + // Lifetime.Forever) + // { + // Instance = instanceType; + // } + + // protected internal override Self AsImmutableReturnsSelf() + // { + // throw new System.NotImplementedException(); + // } + + // protected internal override Self WithLifetimeReturnsSelf(Lifetime lifetime) + // { + // throw new System.NotImplementedException(); + // } + + // public override string ToString() + // { + // // For these types we don't need parens, for others we might + // return $"{Instance}.Type"; + // } + //} +} diff --git a/Compiler.Types/NeverType.cs b/Compiler.Types/NeverType.cs new file mode 100644 index 00000000..b3d8f342 --- /dev/null +++ b/Compiler.Types/NeverType.cs @@ -0,0 +1,26 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The never or bottom type. That is a type with no values. A function + /// with return type `never` must never return by normal means. It always + /// either throws an exception, abandons the process or doesn't terminate. + /// Because it is the bottom type, it is assignment compatible to all types + /// this makes it particularly useful as the result type of expressions like + /// `throw`, `return` and `break` which never produce a result. It is also + /// used as the type of a `loop` statement with no breaks in it. + /// + public sealed class NeverType : EmptyType + { + #region Singleton + internal static readonly NeverType Instance = new NeverType(); + + private NeverType() + : base(SpecialTypeName.Never) + { } + #endregion + + public override TypeSemantics Semantics => TypeSemantics.Never; + } +} diff --git a/Compiler.Types/NumericType.cs b/Compiler.Types/NumericType.cs new file mode 100644 index 00000000..2a703d8f --- /dev/null +++ b/Compiler.Types/NumericType.cs @@ -0,0 +1,16 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + [Closed( + //typeof(FloatingPointType), + typeof(IntegerType))] + public abstract class NumericType : SimpleType + { + private protected NumericType(SpecialTypeName name) + : base(name) + { + } + } +} diff --git a/Compiler.Types/ObjectType.cs b/Compiler.Types/ObjectType.cs new file mode 100644 index 00000000..a2e1896a --- /dev/null +++ b/Compiler.Types/ObjectType.cs @@ -0,0 +1,111 @@ +using System; +using System.Diagnostics; +using System.Text; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// Object types are the types created with class and trait declarations. An + /// object type may have generic parameters that may be filled with generic + /// arguments. An object type with generic parameters but no generic arguments + /// is an *unbound type*. One with generic arguments supplied for all + /// parameters is *a constructed type*. One with some but not all arguments + /// supplied is *partially constructed type*. + /// + /// + /// There will be two special object types `Type` and `Metatype` + /// + public sealed class ObjectType : ReferenceType + { + // TODO this needs a containing package + public NamespaceName ContainingNamespace { get; } + public TypeName Name { get; } + public override bool IsKnown { [DebuggerStepThrough] get => true; } + + // TODO referenceCapability needs to match declared mutable? + public ObjectType( + NamespaceName containingNamespace, + TypeName name, + bool declaredMutable, + ReferenceCapability referenceCapability) + : base(declaredMutable, referenceCapability) + { + ContainingNamespace = containingNamespace; + Name = name; + } + + /// + /// Use this type as a mutable type. Only allowed if the type is declared mutable + /// + public ObjectType ToMutable() + { + //Requires.That(nameof(DeclaredMutable), DeclaredMutable, "must be declared as a mutable type to use mutably"); + //return new ObjectType(ContainingNamespace, Name, DeclaredMutable, ReferenceCapability.ToMutable()); + throw new NotImplementedException(); + } + + /// + /// Make a mutable version of this type regardless of whether it was declared + /// mutable for use as the constructor parameter. + /// + public ObjectType ToConstructorSelf() + { + //return new ObjectType(ContainingNamespace, Name, DeclaredMutable, ReferenceCapability.Borrowed); + throw new NotImplementedException(); + } + + public ObjectType ToConstructorReturn() + { + var constructedType = this.To(ReferenceCapability.Isolated); + if (constructedType.DeclaredMutable) constructedType = constructedType.ToMutable(); + return constructedType; + } + + public override string ToSourceCodeString() + { + var builder = new StringBuilder(); + builder.Append(ReferenceCapability); + builder.Append(' '); + builder.Append(ContainingNamespace); + if (ContainingNamespace != NamespaceName.Global) builder.Append('.'); + builder.Append(Name); + return builder.ToString(); + } + + public override string ToILString() + { + var builder = new StringBuilder(); + builder.Append(ReferenceCapability); + builder.Append(' '); + builder.Append(ContainingNamespace); + if (ContainingNamespace != NamespaceName.Global) builder.Append('.'); + builder.Append(Name); + return builder.ToString(); + } + + #region Equality + public override bool Equals(DataType? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is ObjectType otherType + && ContainingNamespace == otherType.ContainingNamespace + && Name == otherType.Name + && DeclaredMutable == otherType.DeclaredMutable + && ReferenceCapability == otherType.ReferenceCapability; + } + + public override int GetHashCode() + { + return HashCode.Combine(ContainingNamespace, Name, DeclaredMutable, ReferenceCapability); + } + #endregion + + protected internal override Self To_ReturnsSelf(ReferenceCapability referenceCapability) + { + return new ObjectType(ContainingNamespace, Name, DeclaredMutable, referenceCapability); + } + } +} diff --git a/Compiler.Types/OptionalType.cs b/Compiler.Types/OptionalType.cs new file mode 100644 index 00000000..09caf444 --- /dev/null +++ b/Compiler.Types/OptionalType.cs @@ -0,0 +1,55 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The type `T?` is an optional type. Optional types either have the value + /// `none` or a value of their referent type. The referent type may be a value + /// type or a reference type. Optional types themselves are always immutable. + /// However the referent type may be mutable or immutable. Effectively, optional + /// types are like an immutable struct type `Optional[T]`. However, the value + /// semantics are strange. They depend on the referent type. + /// + public sealed class OptionalType : ValueType + { + public DataType Referent { get; } + + public override bool IsKnown { get; } + + public override TypeSemantics Semantics => + Referent.Semantics == TypeSemantics.Never + ? TypeSemantics.Copy : Referent.Semantics; + + public OptionalType(DataType referent) + { + if (referent is VoidType) + throw new ArgumentException("Cannot create `void?` type", nameof(referent)); + Referent = referent; + IsKnown = referent.IsKnown; + } + + public override string ToSourceCodeString() + { + return $"({Referent.ToSourceCodeString()})?"; + } + + public override string ToILString() + { + return $"({Referent.ToILString()})?"; + } + + public override bool Equals(DataType? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other is OptionalType otherType + && Equals(Referent, otherType.Referent); + } + + public override int GetHashCode() + { + // Use the type to give a different hashcode than just the referent + return HashCode.Combine(typeof(OptionalType), Referent); + } + } +} diff --git a/Compiler.Types/PointerSizedIntegerType.cs b/Compiler.Types/PointerSizedIntegerType.cs new file mode 100644 index 00000000..41696459 --- /dev/null +++ b/Compiler.Types/PointerSizedIntegerType.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// Integer types whose exact bit length is architecture dependent and whose + /// length matches that of pointers + /// + public sealed class PointerSizedIntegerType : IntegerType + { + internal new static readonly PointerSizedIntegerType Size = new PointerSizedIntegerType(SpecialTypeName.Size, false); + internal new static readonly PointerSizedIntegerType Offset = new PointerSizedIntegerType(SpecialTypeName.Offset, true); + + public bool IsSigned { get; } + public override bool IsKnown => true; + + private PointerSizedIntegerType(SpecialTypeName name, bool signed) + : base(name) + { + IsSigned = signed; + } + } +} diff --git a/Compiler.Types/README.md b/Compiler.Types/README.md new file mode 100644 index 00000000..6ee42373 --- /dev/null +++ b/Compiler.Types/README.md @@ -0,0 +1,12 @@ +# Azoth.Tools.Bootstrap.Compiler.Types + +Types name the possible types in a program. Many types correspond directly to some name in the program. However, other types are constructed out of those types. For example, by substituting in type arguments. Types may be either *open* or *closed*. An open type is one which has at least one unspecific type parameter. A closed type has all of its type parameters fully specified. + +## Constant Types and Values + +In the Azoth compiler, constant values of expressions are expressed as special types. There are several reasons for this: + +1. It ensures strong typing by coupling the constant value to the type rather than keeping a separate constant value for an expression which must then be cast to the correct type based on the type of the expression. +2. Integer constants must have their own type anyway because they have arbitrary size. +3. It is in line with the planned ability to have values as generic parameters. Thus parameterized types on values. It may one day make sense to expose this in the language. That is `bool[true]` could be the type of a boolean known to be true at compile time. Likewise, `int[0]` the type of an integer known to be zero at compile time. One possible use for this is in a units of measure library where `m^3` could be handled by overloading the `^` operator on `int[1]`, `int[2]`, `int[3]` etc. +4. It makes supporting operations like supporting concatenation of constant strings very easy. diff --git a/Compiler.Types/ReferenceCapability.cs b/Compiler.Types/ReferenceCapability.cs new file mode 100644 index 00000000..4903fcaf --- /dev/null +++ b/Compiler.Types/ReferenceCapability.cs @@ -0,0 +1,115 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + public class ReferenceCapability + { + /// + /// A reference that owns the object and there are *no* references from + /// the subtree out to other non-constant objects. Isolated references + /// are implicitly mutable if the type is mutable. + /// + public static readonly ReferenceCapability Isolated + = new ReferenceCapability("iso", allowsWrite: true); + + public static readonly ReferenceCapability LentIsolated + = new ReferenceCapability("lent iso", allowsWrite: true, isLent: true); + + public static readonly ReferenceCapability Transition + = new ReferenceCapability("trn", allowsWrite: true, allowsReadAliases: true); + + public static readonly ReferenceCapability LentTransition + = new ReferenceCapability("lent trn", allowsWrite: true, allowsReadAliases: true, isLent: true); + /// + /// A reference that can be aliased and has write access to the type + /// + public static readonly ReferenceCapability SharedMutable + = new ReferenceCapability("shared mut", allowsWrite: true, allowsWriteAliases: true, allowsReadAliases: true); + + public static readonly ReferenceCapability LentMutable + = new ReferenceCapability("lent mut", allowsWrite: true, allowsWriteAliases: true, allowsReadAliases: true, isLent: true); + + /// + /// A reference has read-only access and there are no references that + /// can mutate this object. + /// + public static readonly ReferenceCapability Const + = new ReferenceCapability("const", allowsReadAliases: true); + + public static readonly ReferenceCapability LentConst + = new ReferenceCapability("lent const", allowsReadAliases: true, isLent: true); + + /// + /// A reference that can be aliased and has read-only access to the type. + /// It may by mutable through another reference. + /// + public static readonly ReferenceCapability Shared + = new ReferenceCapability("shared", allowsWriteAliases: true, allowsReadAliases: true); + + public static readonly ReferenceCapability Lent = + new ReferenceCapability("lent", allowsWriteAliases: true, allowsReadAliases: true, isLent: true); + + /// + /// A reference that can be used to identify an object but not read or + /// write to it. + /// + public static readonly ReferenceCapability Identity + = new ReferenceCapability("id", allowsWriteAliases: true, allowsReadAliases: true, allowsRead: false); + + private readonly string value; + /// + /// Whether this kind of reference allows mutating the referenced object through this reference + /// + public bool AllowsWrite { get; } + /// + /// Whether this kind of reference permits other writable aliases to the object to exist + /// + public bool AllowsWriteAliases { get; } + /// + /// Whether this kind of reference allows reading the value of an object through this reference + /// + public bool AllowsRead { get; } + /// + /// Whether this kind of reference permits other readable aliases to the object to exist + /// + public bool AllowsReadAliases { get; } + /// + /// Whether this reference is "lent" meaning that it can not be stored on the heap except + /// in objects that are wholly lent. + /// + public bool IsLent { get; } + + public bool IsMovable => AllowsWrite && !AllowsWriteAliases; + + + private ReferenceCapability(string value, bool allowsWrite = false, bool allowsWriteAliases = false, bool allowsRead = true, bool allowsReadAliases = false, bool isLent = false) + { + AllowsWrite = allowsWrite; + AllowsWriteAliases = allowsWriteAliases; + AllowsRead = allowsRead; + AllowsReadAliases = allowsReadAliases; + IsLent = isLent; + this.value = value; + } + + public bool IsAssignableFrom(ReferenceCapability from) + { + // Everything can be assigned to `id` (needed because lent <: id) + if (this == Identity) return true; + + // Can't go from lent to non-lent + if (!IsLent && from.IsLent) return false; + + // Can gain permissions + if (AllowsWrite && !from.AllowsWrite) return false; + if (!AllowsWriteAliases && from.AllowsWriteAliases) return false; + if (AllowsRead && !from.AllowsRead) return false; + if (!AllowsReadAliases && from.AllowsReadAliases) return false; + + return true; + } + + public override string ToString() + { + return value; + } + } +} diff --git a/Compiler.Types/ReferenceCapabilityExtensions.cs b/Compiler.Types/ReferenceCapabilityExtensions.cs new file mode 100644 index 00000000..4a0aee69 --- /dev/null +++ b/Compiler.Types/ReferenceCapabilityExtensions.cs @@ -0,0 +1,183 @@ +using System; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + //public static class ReferenceCapabilityExtensions + //{ + // public static bool IsMutable(this ReferenceCapability referenceCapability) + // { + // switch (referenceCapability) + // { + // default: + // throw ExhaustiveMatch.Failed(referenceCapability); + // case ReferenceCapability.Isolated: + // case ReferenceCapability.Mutable: + // return true; + // case ReferenceCapability.ReadOnly: + // case ReferenceCapability.Const: + // case ReferenceCapability.Identity: + // return false; + // } + // } + + // public static bool CanBeAcquired(this ReferenceCapability referenceCapability) + // { + // switch (referenceCapability) + // { + // default: + // throw ExhaustiveMatch.Failed(referenceCapability); + // case ReferenceCapability.OwnedMutable: + // case ReferenceCapability.Owned: + // case ReferenceCapability.IsolatedMutable: + // case ReferenceCapability.Isolated: + // case ReferenceCapability.HeldMutable: + // case ReferenceCapability.Held: + // return true; + // case ReferenceCapability.Borrowed: + // case ReferenceCapability.Shared: + // case ReferenceCapability.Identity: + // return false; + // } + // } + + // public static bool IsAssignableFrom(this ReferenceCapability target, ReferenceCapability source) + // { + // switch (target, source) + // { + // default: + // //throw ExhaustiveMatch.Failed((target, source)); + // throw new NotImplementedException(); + // case (ReferenceCapability.Identity, _): + // case (_, _) when target == source: + // return true; + // case (/* Not Identity */ _, ReferenceCapability.Identity): + // return false; + // // Anything that isn't identity can be shared + // case (ReferenceCapability.Shared, _): + // // Mutable things can be borrowed + // case (ReferenceCapability.Borrowed, ReferenceCapability.OwnedMutable): + // case (ReferenceCapability.Borrowed, ReferenceCapability.IsolatedMutable): + // case (ReferenceCapability.Borrowed, ReferenceCapability.HeldMutable): + // // Held can accept borrow/share or ownership + // case (ReferenceCapability.HeldMutable, ReferenceCapability.Borrowed): + // case (ReferenceCapability.HeldMutable, ReferenceCapability.OwnedMutable): + // case (ReferenceCapability.HeldMutable, ReferenceCapability.IsolatedMutable): + // case (ReferenceCapability.Held, ReferenceCapability.Shared): + // case (ReferenceCapability.Held, ReferenceCapability.Owned): + // case (ReferenceCapability.Held, ReferenceCapability.Isolated): + // case (ReferenceCapability.Held, ReferenceCapability.Borrowed): + // case (ReferenceCapability.Held, ReferenceCapability.OwnedMutable): + // case (ReferenceCapability.Held, ReferenceCapability.IsolatedMutable): + // // Mutable things can be weakened to read-only + // case (ReferenceCapability.Owned, ReferenceCapability.OwnedMutable): + // case (ReferenceCapability.Isolated, ReferenceCapability.IsolatedMutable): + // case (ReferenceCapability.Held, ReferenceCapability.HeldMutable): + // // Isolated can be weakened to owned + // case (ReferenceCapability.Owned, ReferenceCapability.Isolated): + // case (ReferenceCapability.OwnedMutable, ReferenceCapability.IsolatedMutable): + // // Isolated allows recovering mutability + // case (ReferenceCapability.IsolatedMutable, ReferenceCapability.Isolated): + // return true; + // // Can't borrow from read-only + // case (ReferenceCapability.Borrowed, ReferenceCapability.Owned): + // case (ReferenceCapability.Borrowed, ReferenceCapability.Isolated): + // case (ReferenceCapability.Borrowed, ReferenceCapability.Held): + // case (ReferenceCapability.Borrowed, ReferenceCapability.Shared): + // // All other conversions to ownership disallowed + // case (ReferenceCapability.Owned, _): + // case (ReferenceCapability.OwnedMutable, _): + // case (ReferenceCapability.IsolatedMutable, _): + // case (ReferenceCapability.Isolated, _): + + // case (ReferenceCapability.HeldMutable, _): + // return false; + // } + // } + + // /// + // /// Convert to the equivalent reference capability that is mutable. + // /// + // public static ReferenceCapability ToMutable(this ReferenceCapability referenceCapability) + // { + // switch (referenceCapability) + // { + // default: + // throw ExhaustiveMatch.Failed(referenceCapability); + // case ReferenceCapability.OwnedMutable: + // case ReferenceCapability.IsolatedMutable: + // case ReferenceCapability.HeldMutable: + // case ReferenceCapability.Borrowed: + // return referenceCapability; + // case ReferenceCapability.Owned: + // return ReferenceCapability.OwnedMutable; + // case ReferenceCapability.Isolated: + // return ReferenceCapability.IsolatedMutable; + // case ReferenceCapability.Held: + // return ReferenceCapability.HeldMutable; + // case ReferenceCapability.Shared: + // return ReferenceCapability.Borrowed; + // case ReferenceCapability.Identity: + // return ReferenceCapability.Identity; + // } + // } + // /// + // /// Convert to the equivalent reference capability that is read-only. + // /// + // public static ReferenceCapability ToReadOnly(this ReferenceCapability referenceCapability) + // { + // switch (referenceCapability) + // { + // default: + // throw ExhaustiveMatch.Failed(referenceCapability); + // case ReferenceCapability.OwnedMutable: + // return ReferenceCapability.Owned; + // case ReferenceCapability.IsolatedMutable: + // return ReferenceCapability.Isolated; + // case ReferenceCapability.HeldMutable: + // return ReferenceCapability.Held; + // case ReferenceCapability.Borrowed: + // return ReferenceCapability.Shared; + // case ReferenceCapability.Owned: + // case ReferenceCapability.Isolated: + // case ReferenceCapability.Held: + // case ReferenceCapability.Shared: + // case ReferenceCapability.Identity: + // return referenceCapability; + // } + // } + // public static string ToSourceCodeString(this ReferenceCapability referenceCapability) + // { + // return referenceCapability switch + // { + // ReferenceCapability.Isolated => "iso", + // ReferenceCapability.IsolatedMutable => "iso mut", + // ReferenceCapability.Owned => "owned", + // ReferenceCapability.OwnedMutable => "owned mut", + // ReferenceCapability.Held => "held", + // ReferenceCapability.HeldMutable => "held mut", + // ReferenceCapability.Shared => "", + // ReferenceCapability.Borrowed => "mut", + // ReferenceCapability.Identity => "id", + // _ => throw ExhaustiveMatch.Failed(referenceCapability) + // }; + // } + + // public static string ToILString(this ReferenceCapability referenceCapability) + // { + // return referenceCapability switch + // { + // ReferenceCapability.Isolated => "iso", + // ReferenceCapability.IsolatedMutable => "iso mut", + // ReferenceCapability.Owned => "owned", + // ReferenceCapability.OwnedMutable => "owned mut", + // ReferenceCapability.Held => "held", + // ReferenceCapability.HeldMutable => "held mut", + // ReferenceCapability.Shared => "shared", + // ReferenceCapability.Borrowed => "mut", + // ReferenceCapability.Identity => "id", + // _ => throw ExhaustiveMatch.Failed(referenceCapability) + // }; + // } + //} +} diff --git a/Compiler.Types/ReferenceType.cs b/Compiler.Types/ReferenceType.cs new file mode 100644 index 00000000..625e9757 --- /dev/null +++ b/Compiler.Types/ReferenceType.cs @@ -0,0 +1,44 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Azoth.Tools.Bootstrap.Framework; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + [Closed( + typeof(ObjectType), + typeof(AnyType))] + public abstract class ReferenceType : DataType + { + public ReferenceCapability ReferenceCapability { get; } + public bool IsReadOnly => !DeclaredMutable && !ReferenceCapability.AllowsWrite; + public bool IsMutable => DeclaredMutable && ReferenceCapability.AllowsWrite; + public bool IsMovable => ReferenceCapability.IsMovable; + + public override TypeSemantics Semantics => TypeSemantics.Reference; + + /// + /// Whether this type was declared `mut class` or just `class`. Types + /// not declared mutably are always immutable. + /// + public bool DeclaredMutable { get; } + + // TODO clarify this + + private protected ReferenceType(bool declaredMutable, ReferenceCapability referenceCapability) + { + ReferenceCapability = referenceCapability; + DeclaredMutable = declaredMutable; + } + + protected internal sealed override Self ToReadOnly_ReturnsSelf() + { + //return To_ReturnsSelf(ReferenceCapability.ToReadOnly()); + throw new NotImplementedException(); + } + + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", + Justification = "Returns self idiom")] + protected internal abstract Self To_ReturnsSelf(ReferenceCapability referenceCapability); + } +} diff --git a/Compiler.Types/ReferenceTypeExtensions.cs b/Compiler.Types/ReferenceTypeExtensions.cs new file mode 100644 index 00000000..acca63d4 --- /dev/null +++ b/Compiler.Types/ReferenceTypeExtensions.cs @@ -0,0 +1,14 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + public static class ReferenceTypeExtensions + { + /// + /// Return the same type except with the given reference capability + /// + public static T To(this T type, ReferenceCapability referenceCapability) + where T : ReferenceType + { + return type.To_ReturnsSelf(referenceCapability).Cast(); + } + } +} diff --git a/Compiler.Types/SimpleType.cs b/Compiler.Types/SimpleType.cs new file mode 100644 index 00000000..f21a9ab0 --- /dev/null +++ b/Compiler.Types/SimpleType.cs @@ -0,0 +1,42 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + [Closed( + typeof(BoolType), + typeof(NumericType))] + public abstract class SimpleType : ValueType + { + public SpecialTypeName Name { get; } + + public override TypeSemantics Semantics => TypeSemantics.Copy; + + private protected SimpleType(SpecialTypeName name) + { + Name = name; + } + + public override string ToSourceCodeString() + { + return Name.ToString(); + } + + public override string ToILString() + { + return ToSourceCodeString(); + } + + public override bool Equals(DataType? other) + { + // Most simple types are fixed instances, so a reference comparision suffices + return ReferenceEquals(this, other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Name); + } + } +} diff --git a/Compiler.Types/StringConstantType.cs b/Compiler.Types/StringConstantType.cs new file mode 100644 index 00000000..4e2158c8 --- /dev/null +++ b/Compiler.Types/StringConstantType.cs @@ -0,0 +1,4 @@ +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + // TODO implement StringConstantType +} diff --git a/Compiler.Types/TypeSemantics.cs b/Compiler.Types/TypeSemantics.cs new file mode 100644 index 00000000..918fa7b1 --- /dev/null +++ b/Compiler.Types/TypeSemantics.cs @@ -0,0 +1,70 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The semantics of values of a type + /// + [SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names", + Justification = "Name not plural")] + public enum TypeSemantics + { + /// + /// Types with never value semantics have no values, but they are assignable + /// to all types. That is, they are a true bottom type. + /// + /// + /// Only and have this + /// semantics + /// + Never = -1, + + /// + /// Void value semantics means values of this type can never be used, + /// assigned to variables, or passed because there are no values of this type. + /// + /// + /// Only has this semantics + /// + Void = 0, + + /// + /// For types with move semantics, any time the value is used, it is moved + /// from the source to the destination. + /// + /// + /// Currently, all move types are . However, logically, + /// there is no reason other types including references couldn't have this + /// semantics. For example, a reference type representing a file could + /// have move semantics so there is only ever one reference to it. + /// + Move = 1, + + /// + /// Values of types with copy semantics are copied each time they are used. + /// + /// + /// For , copy semantics means a bitwise copy. + /// However, for other value types, copy semantics could mean calling the + /// copy constructor. Currently, all copy types are . + /// However, logically, there is no reason other types including reference + /// types couldn't be copy types. For example, a big integer class could + /// be a copy type so each instance is independent and the class behaves + /// like a value type. + /// + Copy, + + /// + /// Types with reference semantics represent references to values where + /// more than one reference may exist. When using a reference value, + /// the reference is copied, but refers to the same object. Reference + /// capabilities and reachability checks are applied to references + /// to prevent issues such as dangling references or shared mutability. + /// + /// + /// Variable references may be a distinct category of value semantics + /// from this. + /// + Reference, + } +} diff --git a/Compiler.Types/UnknownType.cs b/Compiler.Types/UnknownType.cs new file mode 100644 index 00000000..9e240e75 --- /dev/null +++ b/Compiler.Types/UnknownType.cs @@ -0,0 +1,47 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The type of expressions and values whose type could not be determined or + /// was somehow invalid. The unknown type can't be directly used in code. + /// No well typed program contains any value of the unknown type. + /// + public sealed class UnknownType : DataType + { + #region Singleton + internal static readonly UnknownType Instance = new UnknownType(); + + private UnknownType() { } + #endregion + + public override bool IsKnown => false; + + /// + /// Like `never` values of type unknown can be assigned to any value. + /// It acts like a bottom type in this respect. + /// + public override TypeSemantics Semantics => TypeSemantics.Never; + + public override string ToSourceCodeString() + { + throw new InvalidOperationException("Unknown type has no source code representation"); + } + + public override string ToILString() + { + return "⧼unknown⧽"; + } + + public override bool Equals(DataType? other) + { + // The unknown type is a singleton, so reference equality suffices + return ReferenceEquals(this, other); + } + + public override int GetHashCode() + { + return HashCode.Combine(typeof(UnknownType)); + } + } +} diff --git a/Compiler.Types/ValueType.cs b/Compiler.Types/ValueType.cs new file mode 100644 index 00000000..b00c3feb --- /dev/null +++ b/Compiler.Types/ValueType.cs @@ -0,0 +1,12 @@ +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + [Closed( + typeof(SimpleType), + typeof(OptionalType))] + public abstract class ValueType : DataType + { + private protected ValueType() { } + } +} diff --git a/Compiler.Types/VoidType.cs b/Compiler.Types/VoidType.cs new file mode 100644 index 00000000..e1cfd91d --- /dev/null +++ b/Compiler.Types/VoidType.cs @@ -0,0 +1,26 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; + +namespace Azoth.Tools.Bootstrap.Compiler.Types +{ + /// + /// The void type behaves similar to a unit type. However it represents the + /// lack of a value. For example, a function returning `void` doesn't return + /// a value. A parameter of type `void` is dropped from the parameter list. + /// + public class VoidType : EmptyType + { + #region Singleton + internal static readonly VoidType Instance = new VoidType(); + + private VoidType() + : base(SpecialTypeName.Void) + { } + #endregion + + public override bool IsEmpty => true; + + public override bool IsKnown => true; + + public override TypeSemantics Semantics => TypeSemantics.Void; + } +} diff --git a/Framework/BitArrayExtensions.cs b/Framework/BitArrayExtensions.cs new file mode 100644 index 00000000..45ec3a1a --- /dev/null +++ b/Framework/BitArrayExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class BitArrayExtensions + { + public static bool ValuesEqual(this BitArray left, BitArray right) + { + if (left.Count != right.Count) throw new InvalidOperationException(); + for (int i = 0; i < left.Count; i++) + if (left[i] != right[i]) + return false; + + return true; + } + + public static IEnumerable TrueIndexes(this BitArray array) + { + for (var i = 0; i < array.Length; i++) + if (array[i]) + yield return i; + } + + public static IEnumerable FalseIndexes(this BitArray array) + { + for (var i = 0; i < array.Length; i++) + if (!array[i]) + yield return i; + } + + public static IEnumerable Bits(this BitArray array) + { + for (var i = 0; i < array.Count; i++) yield return array[i]; + } + } +} diff --git a/Framework/CollectionDebugView.cs b/Framework/CollectionDebugView.cs new file mode 100644 index 00000000..5dd49003 --- /dev/null +++ b/Framework/CollectionDebugView.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Framework +{ + /// + /// This class is used in place of the FixedList[T] class by the debugger for + /// display purposes. It just exposes the collection items as an array at the root. + /// + /// + public sealed class CollectionDebugView + where T : class + { + private readonly IEnumerable collection; + + public CollectionDebugView(IEnumerable collection) + { + this.collection = collection ?? throw new ArgumentNullException(nameof(collection)); + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + [SuppressMessage("Performance", "CA1819:Properties should not return arrays")] + public T[] Items => collection.ToArray(); + } +} diff --git a/Framework/DictionaryDebugView.cs b/Framework/DictionaryDebugView.cs new file mode 100644 index 00000000..59e77266 --- /dev/null +++ b/Framework/DictionaryDebugView.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public sealed class DictionaryDebugView + where K : notnull + { + private readonly IReadOnlyDictionary dictionary; + + public DictionaryDebugView(IReadOnlyDictionary dictionary) + { + this.dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary)); + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + [SuppressMessage("Performance", "CA1819:Properties should not return arrays")] + public KeyValuePair[] Items => dictionary.ToArray(); + } +} diff --git a/Framework/DictionaryExtensions.cs b/Framework/DictionaryExtensions.cs new file mode 100644 index 00000000..5a7551a3 --- /dev/null +++ b/Framework/DictionaryExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class DictionaryExtensions + { + public static FixedDictionary ToFixedDictionary( + this IDictionary dictionary) + where TKey : notnull + { + return new FixedDictionary(dictionary); + } + + public static FixedDictionary ToFixedDictionary( + this IEnumerable source, + Func keySelector, + Func valueSelector) + where TKey : notnull + { + return new FixedDictionary(source.ToDictionary(keySelector, valueSelector)); + } + + public static FixedDictionary ToFixedDictionary( + this IEnumerable<(TKey Key, TValue Value)> source) + where TKey : notnull + { + return new FixedDictionary(source.ToDictionary(s => s.Key, s => s.Value)); + } + + public static FixedDictionary ToFixedDictionary( + this IEnumerable source, + Func keySelector) + where TKey : notnull + { + return new FixedDictionary(source.ToDictionary(keySelector)); + } + } +} diff --git a/Framework/EnumerableExtensions.cs b/Framework/EnumerableExtensions.cs new file mode 100644 index 00000000..363d6c71 --- /dev/null +++ b/Framework/EnumerableExtensions.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class EnumerableExtensions + { + [DebuggerStepThrough] + public static IEnumerable Yield(this T value) + { + yield return value; + } + + [DebuggerStepThrough] + public static IEnumerable YieldValue(this T? value) + where T : class + { + if (value != null) yield return value; + } + + [DebuggerStepThrough] + public static IEnumerable YieldValue(this T? value) + where T : struct + { + if (value != null) yield return value.Value; + } + + [DebuggerStepThrough] + public static FixedList ToFixedList(this IEnumerable values) + { + return new FixedList(values); + } + + [DebuggerStepThrough] + public static FixedSet ToFixedSet(this IEnumerable values) + { + return new FixedSet(values); + } + + [DebuggerStepThrough] + public static IEnumerable CrossJoin( + this IEnumerable first, + IEnumerable second, + Func resultSelector) + { + return first.SelectMany(_ => second, resultSelector); + } + + [DebuggerStepThrough] + public static IEnumerable<(TFirst, TSecond)> CrossJoin( + this IEnumerable first, + IEnumerable second) + { + return first.SelectMany(_ => second, (f, s) => (f, s)); + } + + [DebuggerStepThrough] + public static IEnumerable<(T Value, int Index)> Enumerate(this IEnumerable source) + { + return source.Select((v, i) => (v, i)); + } + + [DebuggerStepThrough] + public static Queue ToQueue(this IEnumerable source) + { + return new Queue(source); + } + + [DebuggerStepThrough] + public static IEnumerable SelectMany(this IEnumerable> source) + { + return source.SelectMany(items => items); + } + + [DebuggerStepThrough] + public static IEnumerable Except(this IEnumerable source, T value) + { + return source.Except(value.Yield()); + } + + /// + /// Performs an implicit cast. This is useful when C# is having trouble getting the correct type. + /// + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable SafeCast(this IEnumerable source) + { + // When the source implements multiple IEnumerable and the next + // Linq function takes IEnumerable (not generic) this shim + // is needed to force the call to the correct GetEnumerator(). + // A Linq function that takes IEnumerable is OfType() + return new ImplicitCastEnumerable(source); + } + + private class ImplicitCastEnumerable : IEnumerable + { + private readonly IEnumerable source; + + public ImplicitCastEnumerable(IEnumerable source) + { + this.source = source; + } + + public IEnumerator GetEnumerator() + { + return source.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return source.GetEnumerator(); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool AnyTrue(this IEnumerable values) + { + return values.Any(v => v); + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable WhereNotNull(this IEnumerable values) + where T : class + { + return values.Where(v => !(v is null))!; + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable WhereNotNull(this IEnumerable values) + where T : struct + { + return values.Where(v => !(v is null)).Select(v => v!.Value); + } + } +} diff --git a/Framework/FixedDictionary.cs b/Framework/FixedDictionary.cs new file mode 100644 index 00000000..1ced4058 --- /dev/null +++ b/Framework/FixedDictionary.cs @@ -0,0 +1,72 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Azoth.Tools.Bootstrap.Framework +{ + [DebuggerDisplay("Count = {" + nameof(Count) + "}")] + [DebuggerTypeProxy(typeof(DictionaryDebugView<,>))] + public class FixedDictionary : IReadOnlyDictionary + where TKey : notnull + { + public static readonly FixedDictionary Empty = new FixedDictionary(new Dictionary()); + + private readonly IReadOnlyDictionary items; + + [DebuggerStepThrough] + public FixedDictionary(IDictionary items) + { + this.items = new ReadOnlyDictionary(new Dictionary(items)); + } + + [DebuggerStepThrough] + public IEnumerator> GetEnumerator() + { + return items.GetEnumerator(); + } + + [DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + + public int Count + { + [DebuggerStepThrough] + get => items.Count; + } + + [DebuggerStepThrough] + public bool ContainsKey(TKey key) + { + return items.ContainsKey(key); + } + + [DebuggerStepThrough] + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + return items.TryGetValue(key, out value); + } + + public TValue this[TKey key] + { + [DebuggerStepThrough] + get => items[key]; + } + + public IEnumerable Keys + { + [DebuggerStepThrough] + get => items.Keys; + } + + public IEnumerable Values + { + [DebuggerStepThrough] + get => items.Values; + } + } +} diff --git a/Framework/FixedList.cs b/Framework/FixedList.cs new file mode 100644 index 00000000..c3e32422 --- /dev/null +++ b/Framework/FixedList.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Framework +{ + // These attributes make it so FixedList is displayed nicely in the debugger similar to List + [DebuggerDisplay("Count = {" + nameof(Count) + "}")] + [DebuggerTypeProxy(typeof(CollectionDebugView<>))] + public class FixedList : IReadOnlyList, IEquatable> + { + public static readonly FixedList Empty = new FixedList(Enumerable.Empty()); + + private readonly IReadOnlyList items; + + [DebuggerStepThrough] + public FixedList(IEnumerable items) + { + this.items = items.ToList().AsReadOnly(); + } + + [DebuggerStepThrough] + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + [DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + + public int Count + { + [DebuggerStepThrough] get => items.Count; + } + + public T this[int index] + { + [DebuggerStepThrough] get => items[index]; + } + + #region Equality + public override bool Equals(object? obj) + { + return Equals(obj as FixedList); + } + + public bool Equals(FixedList? other) + { + return other != null && Count == other.Count && items.SequenceEqual(other.items); + } + + public override int GetHashCode() + { + HashCode hash = new HashCode(); + foreach (var item in items) hash.Add(item); + return hash.ToHashCode(); + } + #endregion + } +} diff --git a/Framework/FixedSet.cs b/Framework/FixedSet.cs new file mode 100644 index 00000000..23a025e1 --- /dev/null +++ b/Framework/FixedSet.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Framework +{ + // These attributes make it so FixedList is displayed nicely in the debugger similar to Set + [DebuggerDisplay("Count = {" + nameof(Count) + "}")] + [DebuggerTypeProxy(typeof(CollectionDebugView<>))] + public class FixedSet : IReadOnlyCollection, IEquatable> + { + public static readonly FixedSet Empty = new FixedSet(Enumerable.Empty()); + + // There is no IReadOnlySet interface, must use ISet + private readonly ISet items; + + [DebuggerStepThrough] + public FixedSet(IEnumerable items) + { + this.items = items.ToHashSet(); + } + + [DebuggerStepThrough] + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + [DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + + public int Count => items.Count; + + #region Equality + public override bool Equals(object? obj) + { + return Equals(obj as FixedSet); + } + + public bool Equals(FixedSet? other) + { + return other != null && Count == other.Count && items.SetEquals(other.items); + } + + public override int GetHashCode() + { + HashCode hash = new HashCode(); + // Order the hash codes so there is a consistent hash order + foreach (var item in items.OrderBy(i => i?.GetHashCode())) + hash.Add(item); + return hash.ToHashCode(); + } + #endregion + } +} diff --git a/Framework/Framework.csproj b/Framework/Framework.csproj new file mode 100644 index 00000000..8b0063c4 --- /dev/null +++ b/Framework/Framework.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Framework + Azoth.Tools.Bootstrap.Framework + 8.0 + enable + + + + + + + + DEBUG;TRACE + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Framework/Generator.cs b/Framework/Generator.cs new file mode 100644 index 00000000..9f92aad7 --- /dev/null +++ b/Framework/Generator.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Framework +{ + /// + /// Generates an infinite by repeatedly calling + /// a generator function + /// + public class Generator : IEnumerable + { + private readonly Func generator; + + public Generator(Func generator) + { + this.generator = generator; + } + + public IEnumerator GetEnumerator() + { + for (; ; ) + yield return generator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Framework/QueueExtensions.cs b/Framework/QueueExtensions.cs new file mode 100644 index 00000000..8add6249 --- /dev/null +++ b/Framework/QueueExtensions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class QueueExtensions + { + public static void EnqueueRange(this Queue queue, IEnumerable items) + { + foreach (var item in items) + queue.Enqueue(item); + } + } +} diff --git a/Framework/Requires.cs b/Framework/Requires.cs new file mode 100644 index 00000000..3955de0e --- /dev/null +++ b/Framework/Requires.cs @@ -0,0 +1,45 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; + +namespace Azoth.Tools.Bootstrap.Framework +{ + /// + /// Note: The parameters for the parameter names are intentionally named + /// `parameter` rather than `name` so that VS autocomplete won't try to + /// complete to `name:` when you type `nameof...` + /// + public static class Requires + { + [DebuggerHidden] + public static void Positive(string parameter, int value) + { + if (value < 0) + throw new ArgumentOutOfRangeException(parameter, value, "Must be greater than or equal to zero"); + } + + [DebuggerHidden] + public static void InString(string inString, string parameter, int value) + { + // Start is allowed to be equal to length to allow for a zero length span after the last character + if (value < 0 || value >= inString.Length) + throw new ArgumentOutOfRangeException(parameter, value, $"Value not in string of length {inString.Length}"); + } + + [DebuggerHidden] + public static void ValidEnum(string parameter, E value) + where E : Enum + { + if (!Enum.IsDefined(typeof(E), value)) + throw new InvalidEnumArgumentException(parameter, Convert.ToInt32(value, CultureInfo.InvariantCulture), typeof(E)); + } + + [DebuggerHidden] + public static void That(string parameter, bool condition, string message) + { + if (!condition) + throw new ArgumentException(message, parameter); + } + } +} diff --git a/Framework/Self.cs b/Framework/Self.cs new file mode 100644 index 00000000..35bc274f --- /dev/null +++ b/Framework/Self.cs @@ -0,0 +1,57 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Framework +{ + /// + /// This struct is used as a marker type for the returns `Self` type idiom. + /// In this idiom, a `protected internal` method is declared on a class with + /// return type `Self` and name ending in `ReturnsSelf`. For convenience, + /// the base type may want to implement an implicit conversion to `Self`. + /// A generic extension method is then used to re-expose the `ReturnsSelf` + /// method as a method that actually has the same return type as the object + /// it is called on. Note, it is up to implementors of the `ReturnsSelf` + /// methods to actually return an object of the same type as the current + /// class. + /// + public struct Self : IEquatable + { + private readonly object self; + + public Self(object self) + { + this.self = self; + } + + public T Cast() + { + return (T)self; + } + + public override bool Equals(object? obj) + { + if (obj is Self other) + return ReferenceEquals(other.self, self); + return false; + } + + public bool Equals(Self other) + { + return ReferenceEquals(other.self, self); + } + + public override int GetHashCode() + { + return self?.GetHashCode() ?? 0; + } + + public static bool operator ==(Self left, Self right) + { + return left.Equals(right); + } + + public static bool operator !=(Self left, Self right) + { + return !(left==right); + } + } +} diff --git a/Framework/SetExtensions.cs b/Framework/SetExtensions.cs new file mode 100644 index 00000000..22422af5 --- /dev/null +++ b/Framework/SetExtensions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class SetExtensions + { + public static void AddRange(this ISet set, IEnumerable values) + { + foreach (var value in values) set.Add(value); + } + } +} diff --git a/Framework/StackExtensions.cs b/Framework/StackExtensions.cs new file mode 100644 index 00000000..c0afad41 --- /dev/null +++ b/Framework/StackExtensions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class StackExtensions + { + public static void PushRange(this Stack stack, IEnumerable items) + { + foreach (var item in items) + stack.Push(item); + } + } +} diff --git a/Framework/StringExtensions.cs b/Framework/StringExtensions.cs new file mode 100644 index 00000000..4d72e54d --- /dev/null +++ b/Framework/StringExtensions.cs @@ -0,0 +1,89 @@ +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class StringExtensions + { + private static readonly Regex LineEndings = new Regex(@"\r\n|\n\r|\n|\r", RegexOptions.Compiled); + + public static string Repeat(this string input, int count) + { + if (string.IsNullOrEmpty(input) || count == 0) + return string.Empty; + + return new StringBuilder(input.Length * count) + .Insert(0, input, count) + .ToString(); + } + + public static string NormalizeLineEndings(this string input, string lineEnding) + { + return LineEndings.Replace(input, lineEnding); + } + + public static string Escape(this string input) + { + var escaped = new StringBuilder(input.Length + 2); + foreach (var c in input) + { + switch (c) + { + case '\'': + escaped.Append(@"\'"); + break; + case '\"': + escaped.Append("\\\""); + break; + case '\\': + escaped.Append(@"\\"); + break; + case '\0': + escaped.Append(@"\0"); + break; + case '\a': + escaped.Append(@"\a"); + break; + case '\b': + escaped.Append(@"\b"); + break; + case '\f': + escaped.Append(@"\f"); + break; + case '\n': + escaped.Append(@"\n"); + break; + case '\r': + escaped.Append(@"\r"); + break; + case '\t': + escaped.Append(@"\t"); + break; + case '\v': + escaped.Append(@"\v"); + break; + default: + if (char.GetUnicodeCategory(c) != UnicodeCategory.Control) + escaped.Append(c); + else + { + escaped.Append(@"\u("); + escaped.Append(((ushort)c).ToString("x", CultureInfo.InvariantCulture)); + escaped.Append(@")"); + } + + break; + } + } + return escaped.ToString(); + } + + public static FixedList SplitOrEmpty(this string value, params char[] separators) + { + if (string.IsNullOrEmpty(value)) + return FixedList.Empty; + return value.Split(separators).ToFixedList(); + } + } +} diff --git a/Framework/TypeExtensions.cs b/Framework/TypeExtensions.cs new file mode 100644 index 00000000..f803ca46 --- /dev/null +++ b/Framework/TypeExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Framework +{ + public static class TypeExtensions + { + public static string GetFriendlyName(this Type type) + { + if (type.IsGenericParameter || !type.IsGenericType) + return type.Name; + + var name = type.Name; + var index = name.IndexOf("`", StringComparison.Ordinal); + name = name.Substring(0, index); + var genericArguments = string.Join(',', type.GetGenericArguments().Select(GetFriendlyName)); + return $"{name}<{genericArguments}>"; + } + + public static IEnumerable GetAllSubtypes(this Type type) + { + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where(t => type.IsAssignableFrom(t) && t != type) + .ToArray(); + } + + public static bool HasCustomAttribute(this Type type) + { + return type.GetCustomAttributes(true).OfType().Any(); + } + } +} diff --git a/Framework/TypeSet.cs b/Framework/TypeSet.cs new file mode 100644 index 00000000..c8cbad07 --- /dev/null +++ b/Framework/TypeSet.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Framework +{ + /// + /// A set of types + /// + /// The type all types in the set must inherit from + public class TypeSet + { + private readonly ISet types = new HashSet(); + + public bool Add() + where T : TBase + { + return types.Add(typeof(T)); + } + + public bool Contains() + where T : TBase + { + return types.Contains(typeof(T)); + } + + public bool Contains(Type type) + { + return types.Contains(type); + } + } +} diff --git a/Framework/UnreachableCodeException.cs b/Framework/UnreachableCodeException.cs new file mode 100644 index 00000000..a2e0621d --- /dev/null +++ b/Framework/UnreachableCodeException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace Azoth.Tools.Bootstrap.Framework +{ + [Serializable] + public class UnreachableCodeException : Exception + { + public UnreachableCodeException() + { + } + + public UnreachableCodeException(string message) + : base(message) + { + + } + + protected UnreachableCodeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/Framework/Void.cs b/Framework/Void.cs new file mode 100644 index 00000000..b3b5b663 --- /dev/null +++ b/Framework/Void.cs @@ -0,0 +1,6 @@ +namespace Azoth.Tools.Bootstrap.Framework +{ + public struct Void + { + } +} diff --git a/IL/IL.csproj b/IL/IL.csproj new file mode 100644 index 00000000..fab24562 --- /dev/null +++ b/IL/IL.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.IL + Azoth.Tools.Bootstrap.IL + enable + + + + DEBUG;TRACE + true + + + diff --git a/IL/Instruction.cs b/IL/Instruction.cs new file mode 100644 index 00000000..5b4c5745 --- /dev/null +++ b/IL/Instruction.cs @@ -0,0 +1,12 @@ +namespace Azoth.Tools.Bootstrap.IL +{ + public readonly struct Instruction + { + private readonly int value; + + public Instruction(OpCode opCode, int operand1, int operand2) + { + value = (int)opCode; + } + } +} diff --git a/IL/OpCode.cs b/IL/OpCode.cs new file mode 100644 index 00000000..18eb492e --- /dev/null +++ b/IL/OpCode.cs @@ -0,0 +1,7 @@ +namespace Azoth.Tools.Bootstrap.IL +{ + public enum OpCode : byte + { + + } +} diff --git a/Interpreter/Interpreter.csproj b/Interpreter/Interpreter.csproj new file mode 100644 index 00000000..d407374e --- /dev/null +++ b/Interpreter/Interpreter.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Interpreter + Azoth.Tools.Bootstrap.Interpreter + enable + + + + DEBUG;TRACE + true + + + diff --git a/Interpreter/MemoryLayout/AzothObject.cs b/Interpreter/MemoryLayout/AzothObject.cs new file mode 100644 index 00000000..1ba0b25e --- /dev/null +++ b/Interpreter/MemoryLayout/AzothObject.cs @@ -0,0 +1,18 @@ +using System; + +namespace Azoth.Tools.Bootstrap.Interpreter.MemoryLayout +{ + public readonly struct AzothObject + { + private readonly AzothObjectSlot[] slots; + + public AzothObject(int dataLength, int slotCount) + { + slots = new AzothObjectSlot[slotCount + 1]; + slots[0].Data = dataLength == 0 ? Array.Empty() : new byte[dataLength]; + } + + public byte[] Data => slots[0].Data; + public Span Slots => new Span(slots, 1, slots.Length - 1); + } +} diff --git a/Interpreter/MemoryLayout/AzothObjectSlot.cs b/Interpreter/MemoryLayout/AzothObjectSlot.cs new file mode 100644 index 00000000..9a8d9007 --- /dev/null +++ b/Interpreter/MemoryLayout/AzothObjectSlot.cs @@ -0,0 +1,13 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Azoth.Tools.Bootstrap.Interpreter.MemoryLayout +{ + [StructLayout(LayoutKind.Explicit)] + public struct AzothObjectSlot + { + [FieldOffset(0)] public AzothReference Reference; + [FieldOffset(0)] public BigInteger Int; + [FieldOffset(0)] public byte[] Data; + } +} diff --git a/Interpreter/MemoryLayout/AzothReference.cs b/Interpreter/MemoryLayout/AzothReference.cs new file mode 100644 index 00000000..fdd8ad2b --- /dev/null +++ b/Interpreter/MemoryLayout/AzothReference.cs @@ -0,0 +1,14 @@ +namespace Azoth.Tools.Bootstrap.Interpreter.MemoryLayout +{ + public readonly struct AzothReference + { + public readonly AzothObject Object; + public readonly VTableRef VTableRef; + + public AzothReference(AzothObject o, VTableRef vTable) + { + Object = o; + VTableRef = vTable; + } + } +} diff --git a/Interpreter/MemoryLayout/AzothValue.cs b/Interpreter/MemoryLayout/AzothValue.cs new file mode 100644 index 00000000..c5549626 --- /dev/null +++ b/Interpreter/MemoryLayout/AzothValue.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Azoth.Tools.Bootstrap.Interpreter.MemoryLayout +{ + [StructLayout(LayoutKind.Explicit)] + public struct AzothValue + { + [FieldOffset(0)] public int I32; + [FieldOffset(0)] public uint U32; + [FieldOffset(0)] public long I64; + [FieldOffset(0)] public ulong U64; + [FieldOffset(0)] public float F32; + [FieldOffset(0)] public double F64; + } +} diff --git a/Interpreter/MemoryLayout/VTableRef.cs b/Interpreter/MemoryLayout/VTableRef.cs new file mode 100644 index 00000000..2cd4abb1 --- /dev/null +++ b/Interpreter/MemoryLayout/VTableRef.cs @@ -0,0 +1,12 @@ +namespace Azoth.Tools.Bootstrap.Interpreter.MemoryLayout +{ + public readonly struct VTableRef + { + private readonly int index; + + public VTableRef(int index) + { + this.index = index; + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..70d4adeb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Jeff Walker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..541fab50 --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# Azoth Bootstrap Compiler + +A bootstrap compiler for the [Azoth language](http://azoth-lang.org). That is, a compiler for a subset of the language that will be used to write the Azoth compiler in Azoth. The compiler currently compiles a small subset of the Azoth language to C. + +## Project Status: Alpha Active + +The compiler is under active development. It is in a very early stage, and there are likely issues and limitations. APIs are subject to frequent breaking changes. + +## Current Plan + +The current plan is to get a basic object-oriented subset of the language working. The inspiration for this is the feature set of Java 1.0. While Java 1.0 was very limiting, it demonstrates a viable language for general purpose development. This subset of the language should provide experience with the viability of developing with compile-time memory management. It will also provide a platform for experimenting with how to best support language features like asynchronous programming, class extension, closures, exceptions, and effect types. + +The basic object-oriented subset is planned to include a minimal set of features to support programming. The list below should be taken as a general guideline. Some very basic language features have been omitted. For features that are listed, often only a very basic version of them will be supported. At the end of the list are some features that may be necessary to include, but will be omitted if possible. + +The compiler will be simplified to include code only for these features. No attempt will be made to structure code for features that aren't currently present. For example, function types will not be used internally because function types aren't supported in this version of the language. Additionally, the compiler will be multi-phase with errors in any phase preventing subsequent phases from running. It is expected the phases will be lexing and parsing; name binding and type checking; and borrow checking. + +* [x] Stand-alone Functions - no generics +* [x] Simple Types - `int`, `uint`, `size`, `offset` +* [x] Simple Class Declarations - no base class, no generics (see below for supported members) +* [x] Fields - no initializers +* [x] Constructors +* [x] Basic Methods - no generics +* [ ] Optional Types +* [x] Basic Control Flow - `if`, `while`, `loop` `break`, `next` +* [ ] Conversions and Casts - `as`, `as!`, `as?` (not including unboxing) +* [ ] Namespaces +* [ ] Strings +* [ ] `foreach n in 1..100` - basic loop iteration may need to be hard-coded because the final version will depend on features not yet available. +* [ ] Basic Arrays of `Any` - may need to be implemented as intrinsics +* [ ] Wrapper Types - `Int`, `UInt`, `Size`, `Offset` to enable simple types in arrays +* [ ] Basic Traits? - no generics, classes directly implement like interfaces +* [ ] `foreach` in Iterator? +* [ ] `string` as primitive type + +These features should allow a number of basic programs and katas to be implemented. Note that the lack of generics will mean that future versions won't be backwards compatible with the standard library of this version. + +As this subset is developed, the plan will be to: + +* Make the *Azoth source* code as correct as possible given all current ideas on the design of the language with the exception of what isn't possible due to missing features (for example, generics). +* Ensure any language features used are in the language reference. +* If needed, parts of what will be the standard library can be created as compiler intrinsics at first, but they should be replaced with correct standard library code when possible. + +### Excluded Language Features + +The following features will not be implemented by this compiler even though they are described in the [language reference](https://github.com/azoth/azoth.language.reference/blob/master/src/book.md). + +* [Loop Labels](https://github.com/azoth/azoth.language.reference/blob/master/src/loop-expressions.md#loop-labels) +* [Document Comment Validation](https://github.com/azoth/azoth.language.reference/blob/master/src/documentation-comments.md#documentation-comments) +* Full Type Inference: Variable types can be inferred from the type of their initializer only. + +### Features Planned for After v1.0 + +A number of language features are planned for the future but not yet included in the language. For quick reference, SOME of those features are: + +* [Global and Package Qualifiers](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-qualifier.md) +* [Additional Types](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-types.md) + * [128-bit Integer Types](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-types.md#128-bit-integer-types) + * [128-bit Floating Point Type](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-types.md#128-bit-floating-point-type) + * [Fixed Point Types](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-types.md#fixed-point-types) + * [Decimal Types](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-types.md#decimal-types) + * [Real Types](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-types.md#real-types) +* [Aliases](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-aliases.md) +* [Additional Expressions](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-expressions.md) + * [Multiline String Literals](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-expressions.md#multiline-string-literals) +* [Operator Features](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-operators.md) +* [Compile-time Function Execution](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-ctfe.md) +* [Language-Oriented Programming](https://github.com/azoth/azoth.language.reference/blob/master/src/planned-lop.md) + +The full list can be found in the [language reference](https://github.com/azoth/azoth.language.reference/blob/master/src/book.md) planned features section. + +## Cleanup Tasks + +None Currently + +## Conventions + +* Unit tests are in projects named `Tests.Unit.*`. This way, it is not inconsistent when further namespaces are nested inside them. If `Tests` were at the end of the name, then many namespaces would have it at the end, while nested ones would have it in the middle. This also allows conformance and integration tests to be grouped with them by placing them all under the `Tests` namespace. +* Namespace hierarchies are kept fairly flat. This is to avoid issues of needing too many `using` statements and moving types between them. A namespace should contain all classes that represent the same "kind" of entity without much regard to sub-kinds. Originally, this was not the case. Types were separated into many sub-namespaces, but this ended up being more trouble than it was worth. In practice, one still just used the go to type functionality to find types. + +## Line Count + +A line count can be obtained from Visual Studio 2019 using the "Code Metrics" feature. An alternate approximation can be found in other versions of Visual Studio by running "Find All" with regular expressions across all `*.cs` files using the regex `^.*[^\s{}].*.$`. This matches all lines that aren't blank or only curly braces. Note that this does match lines that are only comments and includes a few generated code files. diff --git a/Tests.Asserts/DiagnosticAsserts.cs b/Tests.Asserts/DiagnosticAsserts.cs new file mode 100644 index 00000000..279ad59a --- /dev/null +++ b/Tests.Asserts/DiagnosticAsserts.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Xunit +{ + public partial class Assert + { + public static void Diagnostic( + Diagnostic diagnostic, + DiagnosticPhase phase, + int errorCode, + int start, + int length) + { + NotNull(diagnostic); + Equal(phase, diagnostic.Phase); + Equal(errorCode, diagnostic.ErrorCode); + True(start == diagnostic.Span.Start, + $"Expected diagnostic start {start}, was {diagnostic.Span.Start}"); + True(length == diagnostic.Span.Length, + $"Expected diagnostic length {length}, was {diagnostic.Span.Length}"); + } + } +} diff --git a/Tests.Asserts/GlobalSuppressions.cs b/Tests.Asserts/GlobalSuppressions.cs new file mode 100644 index 00000000..b25541ce --- /dev/null +++ b/Tests.Asserts/GlobalSuppressions.cs @@ -0,0 +1,13 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "External Code", Scope = "namespaceanddescendants", Target = "Xunit.Sdk")] +[assembly: SuppressMessage("Style", "IDE0018:Inline variable declaration", Justification = "External Code", Scope = "namespaceanddescendants", Target = "Xunit.Sdk")] +[assembly: SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "", Scope = "member", Target = "~M:Xunit.Assert.Contains``1(``0,System.Collections.Generic.IEnumerable{``0})")] +[assembly: SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "", Scope = "member", Target = "~M:Xunit.Assert.DoesNotContain``1(``0,System.Collections.Generic.IEnumerable{``0})")] +[assembly: SuppressMessage("Style", "IDE0034:Simplify 'default' expression", Justification = "", Scope = "member", Target = "~M:Xunit.Assert.Single``1(System.Collections.Generic.IEnumerable{``0},System.Predicate{``0})~``0")] +[assembly: SuppressMessage("Style", "IDE0020:Use pattern matching", Justification = "", Scope = "member", Target = "~M:Xunit.Sdk.ArgumentFormatter.Format(System.Object,System.Int32)~System.String")] diff --git a/Tests.Asserts/README.md b/Tests.Asserts/README.md new file mode 100644 index 00000000..7722e630 --- /dev/null +++ b/Tests.Asserts/README.md @@ -0,0 +1,3 @@ +# Azoth.Tools.Bootstrap.Tests.Asserts + +The currently officially recommended way of defining custom asserts for xUnit is to reference the `xunit.assert.source` NuGet package which brings the source code of the asserts class into your project as a partial class. However, that code doesn't compile with treat all warnings as errors. Nor is it written with the new C# nullable references. Given that the custom asserts are in a partial class, it must be in the same project and be subject to the same compilation options. As a result, this project can't be configured the way all the other projects are. diff --git a/Tests.Asserts/Tests.Asserts.csproj b/Tests.Asserts/Tests.Asserts.csproj new file mode 100644 index 00000000..2aa7b423 --- /dev/null +++ b/Tests.Asserts/Tests.Asserts.csproj @@ -0,0 +1,36 @@ + + + + netcoreapp3.0 + + false + + disable + + Azoth.Tools.Bootstrap.Tests.Asserts + + Xunit + + + + DEBUG;TRACE + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Tests.Asserts/TypeAsserts.cs b/Tests.Asserts/TypeAsserts.cs new file mode 100644 index 00000000..fe3d3460 --- /dev/null +++ b/Tests.Asserts/TypeAsserts.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xunit +{ + public partial class Assert + { + public static T OfType(object @object) + { + return IsAssignableFrom(@object); + } + + public static void OfType(Type expectedType, object @object) + { + IsAssignableFrom(expectedType, @object); + } + } +} diff --git a/Tests.Compiler.Conformance/ConformanceTests.cs b/Tests.Compiler.Conformance/ConformanceTests.cs new file mode 100644 index 00000000..1174ce51 --- /dev/null +++ b/Tests.Compiler.Conformance/ConformanceTests.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Azoth.Tools.Bootstrap.Compiler.API; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Emit.C; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Framework; +using Azoth.Tools.Bootstrap.Tests.Conformance.Helpers; +using Azoth.Tools.Bootstrap.Tests.Unit.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace Azoth.Tools.Bootstrap.Tests.Conformance +{ + [Trait("Category", "Conformance")] + public class ConformanceTests : IClassFixture + { + private readonly ITestOutputHelper testOutput; + + public ConformanceTests(ITestOutputHelper testOutput) + { + this.testOutput = testOutput; + } + + [Fact] + public void Can_load_all_conformance_test_cases() + { + Assert.NotEmpty(GetConformanceTestCases()); + } + + private static readonly Regex ExitCodePattern = new Regex(@"//[ \t]*exit code: (?\d+)", RegexOptions.Compiled); + private const string ExpectedOutputFileFormat = @"//[ \t]*{0} file: (?[a-zA-Z0-9_.]+)"; + private const string ExpectedOutputFormat = @"\/\*[ \t]*{0}:\r?\n(?(\*+[^/]|[^*])*)\*\/"; + private static readonly Regex ExpectCompileErrorsPattern = new Regex(@"//[ \t]*compile: errors", RegexOptions.Compiled); + private static readonly Regex ErrorPattern = new Regex(@"//[ \t]*ERROR([ \t].*)?", RegexOptions.Compiled | RegexOptions.Multiline); + + [Theory] + [MemberData(nameof(GetConformanceTestCases))] + public void Test_cases(TestCase testCase) + { + // Setup + var codeFile = CodeFile.Load(testCase.FullCodePath); + var code = codeFile.Code.Text; + var compiler = new AzothCompiler() + { + SaveLivenessAnalysis = true, + SaveReachabilityGraphs = true, + }; + var references = new Dictionary(); + + // Reference Standard Library + var stdLibPackage = CompileStdLib(compiler); + references.Add("azoth.stdlib", stdLibPackage); + + try + { + // Analyze + var package = compiler.CompilePackage("testPackage", codeFile.Yield(), + references.ToFixedDictionary()); + + // Check for compiler errors + Assert.NotNull(package.Diagnostics); + var diagnostics = package.Diagnostics; + var errorDiagnostics = CheckErrorsExpected(testCase, codeFile, code, diagnostics); + + // Disassemble + var ilAssembler = new ILAssembler(); + testOutput.WriteLine(ilAssembler.Disassemble(package)); + + // We got only expected errors, but need to not go on to emit code + if (errorDiagnostics.Any()) + return; + + // Emit Code + var codePath = Path.ChangeExtension(testCase.FullCodePath, "c"); + EmitCode(package, stdLibPackage, codePath); + + // Compile Code to Executable + var exePath = CompileToExecutable(codePath); + + // Execute and check results + var process = Execute(exePath); + + process.WaitForExit(); + var stdout = process.StandardOutput.ReadToEnd(); + testOutput.WriteLine("stdout:"); + testOutput.WriteLine(stdout); + Assert.Equal(ExpectedOutput(code, "stdout", testCase.FullCodePath), stdout); + var stderr = process.StandardError.ReadToEnd(); + testOutput.WriteLine("stderr:"); + testOutput.WriteLine(stderr); + Assert.Equal(ExpectedOutput(code, "stderr", testCase.FullCodePath), stderr); + Assert.Equal(ExpectedExitCode(code), process.ExitCode); + } + catch (FatalCompilationErrorException ex) + { + var diagnostics = ex.Diagnostics; + CheckErrorsExpected(testCase, codeFile, code, diagnostics); + } + } + + private PackageIL CompileStdLib(AzothCompiler compiler) + { + try + { + var sourceDir = Path.Combine(SolutionDirectory.Get(), @"stdlib\src"); + var sourcePaths = + Directory.EnumerateFiles(sourceDir, "*.ad", SearchOption.AllDirectories); + var rootNamespace = FixedList.Empty; + var codeFiles = sourcePaths.Select(p => LoadCode(p, sourceDir, rootNamespace)) + .ToList(); + return compiler.CompilePackage("azoth.stdlib", codeFiles, + FixedDictionary.Empty); + } + catch (FatalCompilationErrorException ex) + { + testOutput.WriteLine("Std Lib Compiler Errors:"); + foreach (var diagnostic in ex.Diagnostics) + { + testOutput.WriteLine( + $"{diagnostic.File.Reference}:{diagnostic.StartPosition.Line}:{diagnostic.StartPosition.Column} {diagnostic.Level} {diagnostic.ErrorCode}"); + testOutput.WriteLine(diagnostic.Message); + } + Assert.True(false, "Compilation errors in standard library"); + throw new UnreachableCodeException(); + } + } + + private static CodeFile LoadCode( + string path, + string sourceDir, + FixedList rootNamespace) + { + var relativeDirectory = Path.GetDirectoryName(Path.GetRelativePath(sourceDir, path)) ?? throw new InvalidOperationException(); + var ns = rootNamespace.Concat(relativeDirectory.SplitOrEmpty(Path.DirectorySeparatorChar)).ToFixedList(); + return CodeFile.Load(path, ns); + } + + private List CheckErrorsExpected( + TestCase testCase, + CodeFile codeFile, + string code, + FixedList diagnostics) + { + // Check for compiler errors + var expectCompileErrors = ExpectCompileErrors(code); + var expectedCompileErrorLines = ExpectedCompileErrorLines(codeFile, code); + + if (diagnostics.Any()) + { + testOutput.WriteLine("Compiler Errors:"); + foreach (var diagnostic in diagnostics) + { + testOutput.WriteLine( + $"{testCase.RelativeCodePath}:{diagnostic.StartPosition.Line}:{diagnostic.StartPosition.Column} {diagnostic.Level} {diagnostic.ErrorCode}"); + testOutput.WriteLine(diagnostic.Message); + } + + testOutput.WriteLine(""); + } + + var errorDiagnostics = diagnostics + .Where(d => d.Level >= DiagnosticLevel.CompilationError).ToList(); + + if (expectedCompileErrorLines.Any()) + { + foreach (var expectedCompileErrorLine in expectedCompileErrorLines) + { + // Assert a single error on the given line + var errorsOnLine = errorDiagnostics.Count(e => + e.StartPosition.Line == expectedCompileErrorLine); + Assert.True(errorsOnLine == 1, + $"Expected one error on line {expectedCompileErrorLine}, found {errorsOnLine}"); + } + } + + if (expectCompileErrors) + Assert.True(errorDiagnostics.Any(), "Expected compilation errors and there were none"); + else + foreach (var error in errorDiagnostics) + { + var errorLine = error.StartPosition.Line; + if (expectedCompileErrorLines.All(line => line != errorLine)) + Assert.True(false, $"Unexpected error on line {error.StartPosition.Line}"); + } + + return errorDiagnostics; + } + + private static bool ExpectCompileErrors(string code) + { + return ExpectCompileErrorsPattern.IsMatch(code); + } + + private static List ExpectedCompileErrorLines(CodeFile codeFile, string code) + { + return ErrorPattern.Matches(code) + .Select(match => codeFile.Code.Lines.LineIndexContainingOffset(match.Index) + 1) + .ToList(); + } + + private static void EmitCode(PackageIL package, PackageIL stdLibPackage, string path) + { + var codeEmitter = new CodeEmitter(); + codeEmitter.Emit(package); + codeEmitter.Emit(stdLibPackage); + File.WriteAllText(path, codeEmitter.GetEmittedCode(), Encoding.UTF8); + } + + private string CompileToExecutable(string codePath) + { + var compiler = new CLangCompiler(); + var sourceFiles = new[] { codePath, RuntimeLibraryFixture.GetRuntimeLibraryPath() }; + var headerSearchPaths = new[] { RuntimeLibraryFixture.GetRuntimeDirectory() }; + var outputPath = Path.ChangeExtension(codePath, "exe"); + var exitCode = compiler.Compile(new CompilerOutputAdapter(testOutput), sourceFiles, headerSearchPaths, outputPath); + Assert.True(exitCode == 0, $"clang exited with {exitCode}"); + return outputPath; + } + + private static Process Execute(string executable) + { + var startInfo = new ProcessStartInfo(executable) + { + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false, + }; + + return Process.Start(startInfo); + } + + private static int ExpectedExitCode(string code) + { + var exitCode = ExitCodePattern.Match(code).Groups["exitCode"]?.Captures.SingleOrDefault()?.Value ?? "0"; + return int.Parse(exitCode, CultureInfo.InvariantCulture); + } + + private static string ExpectedOutput( + string code, + string channel, + string testCasePath) + { + // First check if there is a file for the expected output + var match = Regex.Match(code, string.Format(CultureInfo.InvariantCulture, ExpectedOutputFileFormat, channel)); + var path = match.Groups["file"]?.Captures.SingleOrDefault()?.Value; + if (path != null) + { + var testCaseDirectory = Path.GetDirectoryName(testCasePath) ?? throw new InvalidOperationException(); + path = Path.Combine(testCaseDirectory, path); + return File.ReadAllText(path); + } + + // Then look for inline expected output + match = Regex.Match(code, string.Format(CultureInfo.InvariantCulture, ExpectedOutputFormat, channel)); + return match.Groups["output"]?.Captures.SingleOrDefault()?.Value ?? ""; + } + + public static TheoryData GetConformanceTestCases() + { + var testCases = new TheoryData(); + var testsDirectory = Path.Combine(SolutionDirectory.Get(), "tests"); + var adFiles = Directory.EnumerateFiles(testsDirectory, "*.ad", SearchOption.AllDirectories); + var azFiles = Directory.EnumerateFiles(testsDirectory, "*.az", SearchOption.AllDirectories); + foreach (var fullPath in adFiles.Concat(azFiles)) + { + var relativePath = Path.GetRelativePath(testsDirectory, fullPath); + testCases.Add(new TestCase(fullPath, relativePath)); + } + return testCases; + } + } +} diff --git a/Tests.Compiler.Conformance/GlobalSuppressions.cs b/Tests.Compiler.Conformance/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Compiler.Conformance/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Compiler.Conformance/Helpers/CompilerOutputAdapter.cs b/Tests.Compiler.Conformance/Helpers/CompilerOutputAdapter.cs new file mode 100644 index 00000000..3a946a20 --- /dev/null +++ b/Tests.Compiler.Conformance/Helpers/CompilerOutputAdapter.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.Emit.C; +using Xunit.Abstractions; + +namespace Azoth.Tools.Bootstrap.Tests.Conformance.Helpers +{ + public class CompilerOutputAdapter : ICompilerOutput + { + private readonly ITestOutputHelper testOutput; + + public CompilerOutputAdapter(ITestOutputHelper testOutput) + { + this.testOutput = testOutput; + } + + public void WriteLine(string message) + { + if (message != null) + testOutput.WriteLine(message); + } + } +} diff --git a/Tests.Compiler.Conformance/Helpers/RuntimeLibraryFixture.cs b/Tests.Compiler.Conformance/Helpers/RuntimeLibraryFixture.cs new file mode 100644 index 00000000..56a17a28 --- /dev/null +++ b/Tests.Compiler.Conformance/Helpers/RuntimeLibraryFixture.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; +using Azoth.Tools.Bootstrap.Compiler.Emit.C; +using Azoth.Tools.Bootstrap.Tests.Unit.Helpers; + +namespace Azoth.Tools.Bootstrap.Tests.Conformance.Helpers +{ + [SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", Justification = "Instantiated by XUnit using reflection")] + public class RuntimeLibraryFixture + { + public RuntimeLibraryFixture() + { + Directory.CreateDirectory(GetRuntimeDirectory()); + + File.WriteAllText(GetRuntimeLibraryPath(), CodeEmitter.RuntimeLibraryCode, Encoding.UTF8); + + File.WriteAllText(GetRuntimeLibraryHeaderPath(), CodeEmitter.RuntimeLibraryHeader, Encoding.UTF8); + } + + public static string GetRuntimeDirectory() + { + return Path.Combine(SolutionDirectory.Get(), "tests", "runtime"); + } + public static string GetRuntimeLibraryPath() + { + return Path.Combine(GetRuntimeDirectory(), CodeEmitter.RuntimeLibraryCodeFileName); + + } + public static string GetRuntimeLibraryHeaderPath() + { + return Path.Combine(GetRuntimeDirectory(), CodeEmitter.RuntimeLibraryHeaderFileName); + } + } +} diff --git a/Tests.Compiler.Conformance/Helpers/TestCase.cs b/Tests.Compiler.Conformance/Helpers/TestCase.cs new file mode 100644 index 00000000..b69506ad --- /dev/null +++ b/Tests.Compiler.Conformance/Helpers/TestCase.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Xunit.Abstractions; + +namespace Azoth.Tools.Bootstrap.Tests.Conformance.Helpers +{ + /// + /// An abstract test case. The visual studio runner serializes tests cases + /// and doesn't always reload the list of the test cases when running. By + /// storing the paths to the test files instead of the actual we avoid issues + /// where the test case has changed but visual studio doesn't pick up the change. + /// + public class TestCase : IXunitSerializable + { + public string FullCodePath { get; private set; } + public string RelativeCodePath { get; private set; } + + private readonly Lazy code; + public string Code => code.Value; + + [Obsolete("Required by IXunitSerializable", true)] + public TestCase() + { + FullCodePath = ""; + RelativeCodePath = ""; + code = new Lazy(LoadCode); + } + + public TestCase(string fullCodePath, string relativeCodePath) + { + FullCodePath = fullCodePath; + RelativeCodePath = relativeCodePath; + code = new Lazy(LoadCode); + } + + protected string LoadCode() => File.ReadAllText(FullCodePath, CodeFile.Encoding); + + public override string ToString() + { + var pathWithoutExtension = Path.ChangeExtension(RelativeCodePath, null); + return pathWithoutExtension + .Replace(Path.DirectorySeparatorChar, '.') + .Replace(Path.AltDirectorySeparatorChar, '.'); + } + + public virtual void Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(FullCodePath), FullCodePath); + info.AddValue(nameof(RelativeCodePath), RelativeCodePath); + } + public virtual void Deserialize(IXunitSerializationInfo info) + { + FullCodePath = info.GetValue(nameof(FullCodePath)); + RelativeCodePath = info.GetValue(nameof(RelativeCodePath)); + } + } +} diff --git a/Tests.Compiler.Conformance/Tests.Compiler.Conformance.csproj b/Tests.Compiler.Conformance/Tests.Compiler.Conformance.csproj new file mode 100644 index 00000000..27501649 --- /dev/null +++ b/Tests.Compiler.Conformance/Tests.Compiler.Conformance.csproj @@ -0,0 +1,54 @@ + + + + netcoreapp3.0 + + false + + Azoth.Tools.Bootstrap.Tests.Conformance + + Azoth.Tools.Bootstrap.Tests.Conformance + + 8.0 + enable + + + + true + + + + + DEBUG;TRACE + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + + + diff --git a/Tests.Unit.Compiler.CodeGen/GlobalSuppressions.cs b/Tests.Unit.Compiler.CodeGen/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Unit.Compiler.CodeGen/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Unit.Compiler.CodeGen/ParserTests.cs b/Tests.Unit.Compiler.CodeGen/ParserTests.cs new file mode 100644 index 00000000..73407b27 --- /dev/null +++ b/Tests.Unit.Compiler.CodeGen/ParserTests.cs @@ -0,0 +1,440 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.CodeGen; +using Azoth.Tools.Bootstrap.Compiler.CodeGen.Config; +using Azoth.Tools.Bootstrap.Framework; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.CodeGen +{ + [Trait("Category", "CodeGen")] + public class ParserTests + { + #region Options + [Fact] + public void DefaultsNamespaceToNull() + { + const string grammar = ""; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Null(config.Namespace); + } + + [Fact] + public void DefaultsBaseTypeToNull() + { + const string grammar = ""; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Null(config.BaseType); + } + + [Fact] + public void DefaultsPrefixToEmptyString() + { + const string grammar = ""; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal("", config.Prefix); + } + + [Fact] + public void DefaultsSuffixToEmptyString() + { + const string grammar = ""; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal("", config.Suffix); + } + + [Fact] + public void DefaultsListTypeToList() + { + const string grammar = ""; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal("List", config.ListType); + } + + [Fact] + public void ParsesNamespace() + { + const string grammar = "◊namespace Foo.Bar.Baz;"; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal("Foo.Bar.Baz", config.Namespace); + } + + [Fact] + public void ParsesBaseType() + { + const string grammar = "◊base MyBase;"; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal(Symbol("MyBase"), config.BaseType); + } + + [Fact] + public void ParsesQuotedBaseType() + { + const string grammar = "◊base 'MyBase';"; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal(QuotedSymbol("MyBase"), config.BaseType); + } + + [Fact] + public void ParsesPrefix() + { + const string grammar = "◊prefix MyPrefix;"; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal("MyPrefix", config.Prefix); + } + + [Fact] + public void ParsesSuffix() + { + const string grammar = "◊suffix MySuffix;"; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal("MySuffix", config.Suffix); + } + + [Fact] + public void ParsesListType() + { + const string grammar = "◊list MyList;"; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal("MyList", config.ListType); + } + + [Fact] + public void ParsesUsingNamespaces() + { + const string grammar = "◊using Foo.Bar;\r◊using Foo.Bar.Baz;"; + + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Equal(FixedList("Foo.Bar", "Foo.Bar.Baz"), config.UsingNamespaces); + } + #endregion + + #region Rules + [Fact] + public void ParsesSimpleNonterminalRule() + { + const string grammar = "SubType;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Equal(Symbol("SubType"), rule.Nonterminal); + Assert.Empty(rule.Parents); + Assert.Empty(rule.Properties); + } + + [Fact] + public void ParsesSimpleQuotedNonterminalRule() + { + const string grammar = "'IMyFullTypeName';"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Equal(QuotedSymbol("IMyFullTypeName"), rule.Nonterminal); + Assert.Empty(rule.Parents); + Assert.Empty(rule.Properties); + } + + [Fact] + public void ParsesSimpleNonterminalRuleWithDefaultBaseType() + { + const string grammar = "◊base MyBase;\nSubType;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Equal(Symbol("SubType"), rule.Nonterminal); + var expectedParents = FixedList(Symbol("MyBase")); + Assert.Equal(expectedParents, rule.Parents); + Assert.Empty(rule.Properties); + } + + [Fact] + public void ParsesBaseTypeRule() + { + const string grammar = "◊base MyBase;\nMyBase;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Equal(Symbol("MyBase"), rule.Nonterminal); + Assert.Empty(rule.Parents); + Assert.Empty(rule.Properties); + } + + [Fact] + public void ParsesSingleInheritanceRule() + { + const string grammar = "SubType: BaseType;"; + + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Equal(Symbol("SubType"), rule.Nonterminal); + Assert.Single(rule.Parents, Symbol("BaseType")); + Assert.Empty(rule.Properties); + } + + [Fact] + public void ParsesMultipleInheritanceRule() + { + const string grammar = "SubType: BaseType1, BaseType2;"; + + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Equal(Symbol("SubType"), rule.Nonterminal); + var expectedParents = FixedList(Symbol("BaseType1"), Symbol("BaseType2")); + Assert.Equal(expectedParents, rule.Parents); + Assert.Empty(rule.Properties); + } + + [Fact] + public void ParseQuotedInheritanceRule() + { + const string grammar = "SubType: 'BaseType1', BaseType2;"; + + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Equal(new GrammarSymbol("SubType"), rule.Nonterminal); + var expectedParents = FixedList(QuotedSymbol("BaseType1"), Symbol("BaseType2")); + Assert.Equal(expectedParents, rule.Parents); + Assert.Empty(rule.Properties); + } + + [Fact] + public void ParseErrorTooManyEqualSigns() + { + const string grammar = "NonTerminal = Foo = Bar;"; + + var ex = Assert.Throws(() => Parser.ReadGrammarConfig(grammar)); + + Assert.Equal("Too many equal signs on line: 'NonTerminal = Foo = Bar'", ex.Message); + } + + [Fact] + public void ParseErrorTooManyColonsInDeclaration() + { + const string grammar = "SubType: BaseType: What = Foo;"; + + var ex = Assert.Throws(() => Parser.ReadGrammarConfig(grammar)); + + Assert.Equal("Too many colons in declaration: 'SubType: BaseType: What '", ex.Message); + } + #endregion + + #region Comments + [Fact] + public void Ignores_line_comments() + { + const string grammar = "// A comment"; + var config = Parser.ReadGrammarConfig(grammar); + + Assert.Empty(config.Rules); + } + #endregion + + #region Properties + [Fact] + public void ParsesSimpleProperty() + { + const string grammar = "MyNonterminal = MyProperty;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(Type(Symbol("MyProperty")), property.Type); + } + + [Fact] + public void ParsesSimpleOptionalProperty() + { + const string grammar = "MyNonterminal = MyProperty?;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(OptionalType(Symbol("MyProperty")), property.Type); + } + + [Fact] + public void ParsesSimpleReferenceProperty() + { + const string grammar = "MyNonterminal = &MyProperty;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(RefType(Symbol("MyProperty")), property.Type); + } + + [Fact] + public void ParsesTypedProperty() + { + const string grammar = "MyNonterminal = MyProperty:MyType;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(Type(Symbol("MyType")), property.Type); + } + + [Fact] + public void ParsesQuotedTypedProperty() + { + const string grammar = "MyNonterminal = MyProperty:'MyType';"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(Type(QuotedSymbol("MyType")), property.Type); + } + + [Fact] + public void ParsesListTypedProperty() + { + const string grammar = "MyNonterminal = MyProperty:MyType*;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(ListType(Symbol("MyType")), property.Type); + } + + [Fact] + public void ParsesOptionalTypedProperty() + { + const string grammar = "MyNonterminal = MyProperty:MyType?;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(OptionalType(Symbol("MyType")), property.Type); + } + + [Fact] + public void ParsesListOfOptionalTypedProperty() + { + const string grammar = "MyNonterminal = MyProperty:MyType?*;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(new GrammarType(Symbol("MyType"), false, true, true), property.Type); + } + + [Fact] + public void ParsesReferenceTypedProperty() + { + const string grammar = "MyNonterminal = &MyProperty:MyType;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + var property = Assert.Single(rule.Properties); + Assert.Equal("MyProperty", property.Name); + Assert.Equal(RefType(Symbol("MyType")), property.Type); + } + + [Fact] + public void ParseErrorTooManyColonsInDefinition() + { + const string grammar = "MyNonterminal = MyProperty:MyType:What;"; + + var ex = Assert.Throws(() => Parser.ReadGrammarConfig(grammar)); + + Assert.Equal("Too many colons in definition: 'MyProperty:MyType:What'", ex.Message); + } + + [Fact] + public void ParsesMultipleProperties() + { + const string grammar = "MyNonterminal = MyProperty1:MyType1 MyProperty2:MyType2*;"; + var config = Parser.ReadGrammarConfig(grammar); + + var rule = Assert.Single(config.Rules); + Assert.Collection(rule.Properties, p1 => + { + Assert.Equal("MyProperty1", p1.Name); + Assert.Equal(Type(Symbol("MyType1")), p1.Type); + }, p2 => + { + Assert.Equal("MyProperty2", p2.Name); + Assert.Equal(ListType(Symbol("MyType2")), p2.Type); + }); + } + + [Fact] + public void ParseErrorDuplicateProperties() + { + const string grammar = "MyNonterminal = Something Something:'Blah';"; + + var ex = Assert.Throws(() => Parser.ReadGrammarConfig(grammar)); + + Assert.Equal("Rule for MyNonterminal contains duplicate property definitions", ex.Message); + } + #endregion + + private static GrammarSymbol Symbol(string text) + { + return new GrammarSymbol(text); + } + + private static GrammarSymbol QuotedSymbol(string text) + { + return new GrammarSymbol(text, true); + } + + private static GrammarType Type(GrammarSymbol symbol) + { + return new GrammarType(symbol, false, false, false); + } + + private static GrammarType OptionalType(GrammarSymbol symbol) + { + return new GrammarType(symbol, false, true, false); + } + + private static GrammarType ListType(GrammarSymbol symbol) + { + return new GrammarType(symbol, false, false, true); + } + + private static GrammarType RefType(GrammarSymbol symbol) + { + return new GrammarType(symbol, true, false, false); + } + + private static FixedList FixedList(params T[] values) + { + return new FixedList(values); + } + } +} diff --git a/Tests.Unit.Compiler.CodeGen/Tests.Unit.Compiler.CodeGen.csproj b/Tests.Unit.Compiler.CodeGen/Tests.Unit.Compiler.CodeGen.csproj new file mode 100644 index 00000000..2fc61614 --- /dev/null +++ b/Tests.Unit.Compiler.CodeGen/Tests.Unit.Compiler.CodeGen.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp3.1 + + false + + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.CodeGen + + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.CodeGen + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Tests.Unit.Compiler.Core/Tests.Unit.Compiler.Core.csproj b/Tests.Unit.Compiler.Core/Tests.Unit.Compiler.Core.csproj new file mode 100644 index 00000000..d7454923 --- /dev/null +++ b/Tests.Unit.Compiler.Core/Tests.Unit.Compiler.Core.csproj @@ -0,0 +1,37 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Core + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Core + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + diff --git a/Tests.Unit.Compiler.Core/TextLinesTests.cs b/Tests.Unit.Compiler.Core/TextLinesTests.cs new file mode 100644 index 00000000..c3115294 --- /dev/null +++ b/Tests.Unit.Compiler.Core/TextLinesTests.cs @@ -0,0 +1,69 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Core +{ + [Trait("Category", "Core")] + public class TextLinesTests + { + public const string OneLine = "Line0"; + public const string TwoLines = "Line0\r\nLine1"; + public const string NewlineCarriageReturn = "Line0\n\rLine1"; + public const string AllLineEndings = "Line0\rLine1\r\nLine2\nLine3\x0Bline4\fline5\x85line6\u2028line7\u2029line8"; + public const string EndsInNewline = "Line0\n"; + + [Theory] + [InlineData(OneLine, 0, 0)] + [InlineData(OneLine, 3, 0)] + [InlineData(OneLine, 4, 0)] // Last char + [InlineData(OneLine, 5, 0)] // End + [InlineData(TwoLines, 0, 0)] + [InlineData(TwoLines, 4, 0)] // Last char of line 0 + [InlineData(TwoLines, 5, 0)] // \r + [InlineData(TwoLines, 6, 0)] // \n + [InlineData(TwoLines, 7, 1)] + [InlineData(TwoLines, 11, 1)] // Last char of line 1 + [InlineData(TwoLines, 12, 1)] // End + public void LineIndexContainingOffsetInText(string text, int charOffset, int expectedLine) + { + var lines = new TextLines(text); + + var line = lines.LineIndexContainingOffset(charOffset); + + Assert.Equal(expectedLine, line); + } + + [Theory] + [InlineData(OneLine)] + [InlineData(TwoLines)] + [InlineData(NewlineCarriageReturn)] + [InlineData(AllLineEndings)] + [InlineData(EndsInNewline)] + public void LineIndexContainingOffsetOutsideOfText(string text) + { + var lines = new TextLines(text); + + Assert.Throws(() => lines.LineIndexContainingOffset(-5)); + Assert.Throws(() => lines.LineIndexContainingOffset(-1)); + Assert.Throws(() => lines.LineIndexContainingOffset(text.Length + 1)); + Assert.Throws(() => lines.LineIndexContainingOffset(text.Length + 42)); + } + + + [Theory] + [InlineData(OneLine, 1)] + [InlineData(TwoLines, 2)] + [InlineData(NewlineCarriageReturn, 3)] + [InlineData(AllLineEndings, 9)] + [InlineData(EndsInNewline, 2)] + public void LineCount(string text, int expectedLineCount) + { + var lines = new TextLines(text); + + var lineCount = lines.Count; + + Assert.Equal(expectedLineCount, lineCount); + } + } +} diff --git a/Tests.Unit.Compiler.Emit.C/GlobalSuppressions.cs b/Tests.Unit.Compiler.Emit.C/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Unit.Compiler.Emit.C/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Unit.Compiler.Emit.C/NameManglerTests.cs b/Tests.Unit.Compiler.Emit.C/NameManglerTests.cs new file mode 100644 index 00000000..57855937 --- /dev/null +++ b/Tests.Unit.Compiler.Emit.C/NameManglerTests.cs @@ -0,0 +1,33 @@ +using System.Text; +using Azoth.Tools.Bootstrap.Compiler.Emit.C; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.C +{ + [Trait("Category", "CCodeGen")] + public class NameManglerTests + { + [Theory] + // Basic Multilingual Plane + [InlineData("x", "x")] + [InlineData("My_Class", "My_Class")] + [InlineData("class", "class")] // Keywords aren't special + [InlineData("_059amzAMZ", "_059amzAMZ")] // Basic ASCII range + [InlineData("Hello World", "Hello_World")] + //[InlineData("do_it!", "do_itµ21ǂ")] // Encode other chars + //[InlineData("ᵢₐ˽·´µǂ", "µ1D62ǂµ2090ǂµ2FDǂµB7ǂµB4ǂµB5ǂµ1C2ǂ")] // Encode chars used for escaping + //[InlineData("\xF8\u1681\uD7FF\uF900\uFFFD", "\xF8\u1681\uD7FF\uF900\uFFFD")] // Already Valid + //[InlineData("\x0F\xAB\u1680", "µFǂµABǂµ1680ǂ")] // Not Valid, all lengths in BMP + // Supplementary Planes + //[InlineData("\U00010000\U0003FFFD\U0009F1FD", "\U00010000\U0003FFFD\U0009F1FD")] // Already Valid + //[InlineData("\U0001FFFE\U000F1234\U0010FFFF", "µ1FFFEǂµF1234ǂµ10FFFFǂ")] // Not Valid, all lengths + public void Mangle_part(string name, string expectedMangledName) + { + var builder = new StringBuilder(); + + NameMangler.ManglePart(name, builder); + + Assert.Equal(expectedMangledName, builder.ToString()); + } + } +} diff --git a/Tests.Unit.Compiler.Emit.C/ResourcesTests.cs b/Tests.Unit.Compiler.Emit.C/ResourcesTests.cs new file mode 100644 index 00000000..1b3ba5a9 --- /dev/null +++ b/Tests.Unit.Compiler.Emit.C/ResourcesTests.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Azoth.Tools.Bootstrap.Compiler.Emit.C; +using Azoth.Tools.Bootstrap.Tests.Unit.Helpers; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.C +{ + [Trait("Category", "CCodeGen")] + public class ResourcesTests + { + private readonly IReadOnlyList byteOrderMarkUtf8 = Encoding.UTF8.GetPreamble().ToList().AsReadOnly(); + + /// + /// In order for .NET to read the resources files as UTF-8 they must have a BOM. Otherwise, + /// they are read with some other encoding and all non-ascii characters are messed up. + /// + [Theory] + [InlineData(CodeEmitter.RuntimeLibraryCodeFileName)] + [InlineData(CodeEmitter.RuntimeLibraryHeaderFileName)] + public void Resources_start_with_BOM(string filename) + { + var projectDir = Path.Combine(SolutionDirectory.Get(), "Compiler.Emit.C"); + + var codeFileBytes = File.ReadAllBytes(Path.Combine(projectDir, filename)); + AssertStartsWithUtf8Bom(codeFileBytes); + } + + private void AssertStartsWithUtf8Bom(byte[] bytes) + { + Assert.True(bytes.Length >= byteOrderMarkUtf8.Count, $"Length = {bytes.Length}"); + for (var i = 0; i < byteOrderMarkUtf8.Count; i++) + Assert.Equal(byteOrderMarkUtf8[i], bytes[i]); + } + } +} diff --git a/Tests.Unit.Compiler.Emit.C/Tests.Unit.Compiler.Emit.C.csproj b/Tests.Unit.Compiler.Emit.C/Tests.Unit.Compiler.Emit.C.csproj new file mode 100644 index 00000000..71a063c5 --- /dev/null +++ b/Tests.Unit.Compiler.Emit.C/Tests.Unit.Compiler.Emit.C.csproj @@ -0,0 +1,48 @@ + + + + netcoreapp3.0 + + false + + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Emit.C + + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.C + + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + diff --git a/Tests.Unit.Compiler.Lexing/Fakes/FakeParseContext.cs b/Tests.Unit.Compiler.Lexing/Fakes/FakeParseContext.cs new file mode 100644 index 00000000..92149c30 --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/Fakes/FakeParseContext.cs @@ -0,0 +1,13 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Tests.Unit.Fakes; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Fakes +{ + public static class FakeParseContext + { + public static ParseContext For(string text) + { + return new ParseContext(FakeCodeFile.For(text), new Diagnostics()); + } + } +} diff --git a/Tests.Unit.Compiler.Lexing/GlobalSuppressions.cs b/Tests.Unit.Compiler.Lexing/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Unit.Compiler.Lexing/Helpers/Arbitrary.cs b/Tests.Unit.Compiler.Lexing/Helpers/Arbitrary.cs new file mode 100644 index 00000000..ebf5a2e1 --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/Helpers/Arbitrary.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; +using Azoth.Tools.Bootstrap.Tests.Unit.Helpers; +using Fare; +using FsCheck; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Helpers +{ + public static class Arbitrary + { + public static Arbitrary PsuedoToken() + { + return Arb.From(GenPsuedoToken()); + } + + public static Arbitrary> PsuedoTokenList() + { + return Arb.From(GenPsuedoTokenList(), Arb.Shrink); + } + + private static Gen> GenPsuedoTokenList() + { + return Gen.Sized(size => GenPsuedoTokenList(size, size)); + } + + private static Gen> GenPsuedoTokenList(int size, int length) + { + Requires.Positive(nameof(size), size); + Requires.Positive(nameof(length), length); + if (length == 0) + return Gen.Fresh(() => new List()); + + return GenPsuedoTokenList(size, length - 1).Select(list => AppendPsuedoToken(size, list)); + } + + private static List AppendPsuedoToken( + int size, + List tokens) + { + var lastToken = tokens.LastOrDefault(); + // TODO this is a huge hack calling Sample() FIX IT! + var token = GenPsuedoToken().Where(t => + { + if (lastToken == null) + return true; + + return !SeparateTokens(lastToken, t); + + }).Sample(size, 1).Single(); + tokens.Add(token); + return tokens; + } + + private static bool SeparateTokens(PsuedoToken t1, PsuedoToken t2) + { + switch (t1.Text) + { + case ".": + case "^": + return t2.Text == "." || t2.Text == ".." || t2.Text == "..<"; + case "+": + case ">": + return t2.Text == "=" || t2.Text == "==" || t2.Text == "=/=" || t2.Text == "=>"; + case "*": + return t2.Text == "=" || t2.Text == "==" || t2.Text == "=/=" || t2.Text == "=>" + || t2.Text == "*="; + case "<": + return t2.Text == "=" || t2.Text == "==" || t2.Text == "=/=" || t2.Text == "=>" + || t2.Text == ":" || t2.Text == "::." + || t2.Text == ".." || t2.Text == "..<"; + case "-": + return t2.Text == "=" || t2.Text == "==" || t2.Text == "=/=" || t2.Text == "=>" + || t2.Text == ">" || t2.Text == ">="; + case "/": + return t2.Text == "=" || t2.Text == "==" || t2.Text == "=/=" || t2.Text == "=>" + || t2.Text == "*" || t2.Text == "*=" + || t2.Text == "/" || t2.Text == "/=" + || t2.TokenType == typeof(ICommentToken); + case "=": + return t2.Text == "=" || t2.Text == "==" || t2.Text == "=/=" || t2.Text == "=>" || t2.Text == "/=" + || t2.Text == ">" || t2.Text == ">="; + case "?": + return t2.Text == "." || t2.Text == ".." || t2.Text == "..<" + || t2.Text == "?" || t2.Text == "?." || t2.Text == "??"; + case "..": + case "<..": + return t2.Text == "<" || t2.Text == "<=" || t2.Text == "<:" || t2.Text == "<.." || t2.Text == "<..<"; + case "#": + return t2.Text == "#" || t2.Text == "##"; + case ":": + // TODO actually ':',':' is fine. It is really the three token sequence ':',':','.' that is the problem + return t2.Text == ":"; + default: + if (typeof(IKeywordToken).IsAssignableFrom(t1.TokenType) + || typeof(IIdentifierToken).IsAssignableFrom(t1.TokenType)) + return typeof(IIdentifierToken).IsAssignableFrom(t2.TokenType) + || typeof(IKeywordToken).IsAssignableFrom(t2.TokenType) + || t2.TokenType == typeof(IIntegerLiteralToken); + else if (t1.TokenType == typeof(IIntegerLiteralToken)) + return t2.TokenType == typeof(IIntegerLiteralToken); + else if (t1.TokenType == typeof(IWhitespaceToken)) + return t2.TokenType == typeof(IWhitespaceToken); + else + return false; + } + } + + private static Gen GenPsuedoToken() + { + return Gen.Frequency( + GenSymbol().WithWeight(20), + GenWhitespace().WithWeight(10), + GenComment().WithWeight(5), + GenBareIdentifier().WithWeight(10), + GenEscapedIdentifier().WithWeight(5), + GenIntegerLiteral().WithWeight(5), + GenStringLiteral().WithWeight(5)); + } + + private static Gen GenSymbol() + { + return Gen.Elements(Symbols.AsEnumerable()) + .Select(item => new PsuedoToken(item.Value, item.Key)); + } + + private static Gen GenRegex(string pattern) + { + return Gen.Sized(size => + { + var xegar = new Xeger(pattern); + var count = size < 1 ? 1 : size; + return Gen.Elements(Enumerable.Range(1, count).Select(i => xegar.Generate())) + .Resize(count); + }); + } + + private static Gen GenWhitespace() + { + return GenRegex("[ \t\n\r]") + .Select(s => new PsuedoToken(typeof(IWhitespaceToken), s)); + } + + private static Gen GenComment() + { + // Covers both block comments and line comments + // For line comments, end in newline requires escape sequences + return GenRegex(@"(/\*(\**[^/])*\*/)|" + "(//.*[\r\n])") + .Select(s => new PsuedoToken(typeof(ICommentToken), s)); + } + + private static Gen GenBareIdentifier() + { + return GenRegex(@"[a-zA-Z_][a-zA-Z_0-9]*") + .Where(s => !Symbols.ContainsKey(s)) // don't emit keywords + .Select(s => new PsuedoToken(typeof(IBareIdentifierToken), s, s)); + } + + private static Gen GenEscapedIdentifier() + { + return GenRegex(@"\\[a-zA-Z_0-9]+") + .Where(s => !Symbols.ContainsKey(s)) // don't emit keywords + .Select(s => new PsuedoToken(typeof(IEscapedIdentifierToken), s, s.Substring(1))); + } + + private static Gen GenIntegerLiteral() + { + return GenRegex(@"0|[1-9][0-9]*") + .Select(s => new PsuedoToken(typeof(IIntegerLiteralToken), s, BigInteger.Parse(s, CultureInfo.InvariantCulture))); + } + + private static Gen GenStringLiteral() + { + // @"""([^\\]|\\(r|n|0|t|'|""|\\|u\([0-9a-fA-F]{1,6}\)))*""" + return GenRegex(@"\""([^\\""]|\\(r|n|0|t|\'|\""))*\""") + .Select(s => + { + var value = s[1..^1] + .Replace(@"\\", @"\b", StringComparison.Ordinal) // Swap out backslash escape to not mess up others + .Replace(@"\r", "\r", StringComparison.Ordinal) + .Replace(@"\n", "\n", StringComparison.Ordinal) + .Replace(@"\0", "\0", StringComparison.Ordinal) + .Replace(@"\t", "\t", StringComparison.Ordinal) + .Replace(@"\'", "\'", StringComparison.Ordinal) + .Replace(@"\""", "\"", StringComparison.Ordinal) + .Replace(@"\b", "\\", StringComparison.Ordinal); + + return new PsuedoToken(typeof(IStringLiteralToken), s, value); + }); + } + + public static readonly FixedDictionary Symbols = new Dictionary() + { + { "{", typeof(IOpenBraceToken) }, + { "}", typeof(ICloseBraceToken) }, + { "(", typeof(IOpenParenToken) }, + { ")", typeof(ICloseParenToken) }, + //{ "[", typeof(IOpenBracketToken) }, + //{ "]", typeof(ICloseBracketToken) }, + { ";", typeof(ISemicolonToken) }, + { ",", typeof(ICommaToken) }, + { ".", typeof(IDotToken) }, + { "::.", typeof(IColonColonDotToken) }, + { "..", typeof(IDotDotToken) }, + { "<..", typeof(ILessThanDotDotToken) }, + { "..<", typeof(IDotDotLessThanToken) }, + { "<..<", typeof(ILessThanDotDotLessThanToken) }, + { ":", typeof(IColonToken) }, + { "<:", typeof(ILessThanColonToken) }, + { "?", typeof(IQuestionToken) }, + { "?.", typeof(IQuestionDotToken) }, + { "??", typeof(IQuestionQuestionToken) }, + //{ "|", typeof(IPipeToken) }, + { "→", typeof(IRightArrowToken) }, + { "->", typeof(IRightArrowToken) }, + //{ "@", typeof(IAtSignToken) }, + //{ "^", typeof(ICaretToken) }, + //{ "^.", typeof(ICaretDotToken) }, + { "+", typeof(IPlusToken) }, + { "-", typeof(IMinusToken) }, + { "*", typeof(IAsteriskToken) }, + { "/", typeof(ISlashToken) }, + { "=", typeof(IEqualsToken) }, + { "==", typeof(IEqualsEqualsToken) }, + { "≠", typeof(INotEqualToken) }, + { "=/=", typeof(INotEqualToken) }, + { ">", typeof(IGreaterThanToken) }, + { "≥", typeof(IGreaterThanOrEqualToken) }, + { ">=", typeof(IGreaterThanOrEqualToken) }, + { "<", typeof(ILessThanToken) }, + { "≤", typeof(ILessThanOrEqualToken) }, + { "<=", typeof(ILessThanOrEqualToken) }, + { "+=", typeof(IPlusEqualsToken) }, + { "-=", typeof(IMinusEqualsToken) }, + { "*=", typeof(IAsteriskEqualsToken) }, + { "/=", typeof(ISlashEqualsToken) }, + { "⇒", typeof(IRightDoubleArrowToken) }, + { "=>", typeof(IRightDoubleArrowToken) }, + //{ "#", typeof(IHashToken) }, + //{ "##", typeof(IHashHashToken) }, + { "published", typeof(IPublishedKeywordToken) }, + { "public", typeof(IPublicKeywordToken) }, + //{ "protected", typeof(IProtectedKeywordToken) }, + { "let", typeof(ILetKeywordToken) }, + { "var", typeof(IVarKeywordToken) }, + { "void", typeof(IVoidKeywordToken) }, + //{ "int8", typeof(IInt8KeywordToken) }, + //{ "int16", typeof(IInt16KeywordToken) }, + { "int", typeof(IIntKeywordToken) }, + //{ "int64", typeof(IInt64KeywordToken) }, + { "byte", typeof(IByteKeywordToken) }, + //{ "uint16", typeof(IUInt16KeywordToken) }, + { "uint", typeof(IUIntKeywordToken) }, + //{ "uint64", typeof(IUInt64KeywordToken) }, + { "bool", typeof(IBoolKeywordToken) }, + { "return", typeof(IReturnKeywordToken) }, + { "class", typeof(IClassKeywordToken) }, + { "new", typeof(INewKeywordToken) }, + //{ "delete", typeof(IDeleteKeywordToken) }, + { "namespace", typeof(INamespaceKeywordToken) }, + { "using", typeof(IUsingKeywordToken) }, + { "foreach", typeof(IForeachKeywordToken) }, + { "in", typeof(IInKeywordToken) }, + { "if", typeof(IIfKeywordToken) }, + { "else", typeof(IElseKeywordToken) }, + { "not", typeof(INotKeywordToken) }, + { "and", typeof(IAndKeywordToken) }, + { "or", typeof(IOrKeywordToken) }, + //{ "struct", typeof(IStructKeywordToken) }, + //{ "enum", typeof(IEnumKeywordToken) }, + { "size", typeof(ISizeKeywordToken) }, + { "unsafe", typeof(IUnsafeKeywordToken) }, + { "safe", typeof(ISafeKeywordToken) }, + //{ "base", typeof(IBaseKeywordToken) }, + { "fn", typeof(IFunctionKeywordToken) }, + //{ "Self", typeof(ISelfTypeKeywordToken) }, + //{ "init", typeof(IInitKeywordToken) }, + { "iso", typeof(IIsolatedKeywordToken) }, + { "shared", typeof(ISharedKeywordToken) }, + { "const", typeof(IConstKeywordToken)}, + { "id", typeof(IIdKeywordToken) }, + { "self", typeof(ISelfKeywordToken) }, + //{ "Type", typeof(ITypeKeywordToken) }, + { "true", typeof(ITrueKeywordToken) }, + { "false", typeof(IFalseKeywordToken) }, + { "mut", typeof(IMutableKeywordToken) }, + //{ "params", typeof(IParamsKeywordToken) }, + //{ "may", typeof(IMayKeywordToken) }, + //{ "no", typeof(INoKeywordToken) }, + //{ "throw", typeof(IThrowKeywordToken) }, + //{ "ref", typeof(IRefKeywordToken) }, + //{ "abstract", typeof(IAbstractKeywordToken) }, + //{ "get", typeof(IGetKeywordToken) }, + //{ "set", typeof(ISetKeywordToken) }, + //{ "requires", typeof(IRequiresKeywordToken) }, + //{ "ensures", typeof(IEnsuresKeywordToken) }, + //{ "invariant", typeof(IInvariantKeywordToken) }, + //{ "where", typeof(IWhereKeywordToken) }, + //{ "uninitialized", typeof(IUninitializedKeywordToken) }, + { "none", typeof(INoneKeywordToken) }, + //{ "operator", typeof(IOperatorKeywordToken) }, + //{ "implicit", typeof(IImplicitKeywordToken) }, + //{ "explicit", typeof(IExplicitKeywordToken) }, + { "move", typeof(IMoveKeywordToken) }, + { "copy", typeof(ICopyKeywordToken) }, + //{ "match", typeof(IMatchKeywordToken) }, + { "loop", typeof(ILoopKeywordToken) }, + { "while", typeof(IWhileKeywordToken) }, + { "break", typeof(IBreakKeywordToken) }, + { "next", typeof(INextKeywordToken) }, + //{ "override", typeof(IOverrideKeywordToken) }, + { "as", typeof(IAsKeywordToken) }, + { "Any", typeof(IAnyKeywordToken) }, + { "never", typeof(INeverKeywordToken) }, + //{ "float", typeof(IFloatKeywordToken) }, + //{ "float32", typeof(IFloat32KeywordToken) }, + { "offset", typeof(IOffsetKeywordToken) }, + //{ "_", typeof(IUnderscoreKeywordToken) }, + //{ "external", typeof(IExternalKeywordToken) }, + }.ToFixedDictionary(); + } +} diff --git a/Tests.Unit.Compiler.Lexing/Helpers/AssertExtensions.cs b/Tests.Unit.Compiler.Lexing/Helpers/AssertExtensions.cs new file mode 100644 index 00000000..225d33e4 --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/Helpers/AssertExtensions.cs @@ -0,0 +1,60 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Helpers +{ + // TODO use xUnit assert extensions + public static class AssertExtensions + { + public static void AssertIdentifier( + this IToken token, + int expectedStart, + int expectedLength, + string expectedValue) + { + Assert.NotNull(token); + var identifier = Assert.OfType(token); + Assert.True(expectedStart == identifier.Span.Start, $"Expected token start {expectedStart}, was {identifier.Span.Start}"); + Assert.True(expectedLength == identifier.Span.Length, $"Expected token length {expectedLength}, was {identifier.Span.Length}"); + Assert.Equal(expectedValue, identifier.Value); + } + + public static void AssertStringLiteral( + this IToken token, + int expectedStart, + int expectedLength, + string expectedValue) + { + Assert.NotNull(token); + var identifier = Assert.OfType(token); + Assert.True(expectedStart == identifier.Span.Start, $"Expected token start {expectedStart}, was {identifier.Span.Start}"); + Assert.True(expectedLength == identifier.Span.Length, $"Expected token length {expectedLength}, was {identifier.Span.Length}"); + Assert.Equal(expectedValue, identifier.Value); + } + + public static void AssertIs( + this IToken token, + int expectedStart, + int expectedLength) + where T : IToken + { + Assert.NotNull(token); + Assert.OfType(token); + Assert.True(expectedStart == token.Span.Start, $"Expected token start {expectedStart}, was {token.Span.Start}"); + Assert.True(expectedLength == token.Span.Length, $"Expected token length {expectedLength}, was {token.Span.Length}"); + } + + public static void AssertError(this Diagnostic diagnostic, int errorCode, int start, int length) + { + Assert.NotNull(diagnostic); + Assert.Equal(DiagnosticLevel.CompilationError, diagnostic.Level); + AssertLexingDiagnostic(diagnostic, errorCode, start, length); + } + + public static void AssertLexingDiagnostic(this Diagnostic diagnostic, int errorCode, int start, int length) + { + Assert.Diagnostic(diagnostic, DiagnosticPhase.Lexing, errorCode, start, length); + } + } +} diff --git a/Tests.Unit.Compiler.Lexing/Helpers/DebugFormatExtensions.cs b/Tests.Unit.Compiler.Lexing/Helpers/DebugFormatExtensions.cs new file mode 100644 index 00000000..e478f839 --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/Helpers/DebugFormatExtensions.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Helpers +{ + public static class DebugFormatExtensions + { + public static string DebugFormat(this IEnumerable diagnostics) + { + return string.Join(", ", + diagnostics.Select(d => + $"{d.ErrorCode}@{d.StartPosition.Line}:{d.StartPosition.Column}")); + } + + public static string DebugFormat(this IEnumerable tokens) + { + return string.Join(", ", tokens); + } + } +} diff --git a/Tests.Unit.Compiler.Lexing/Helpers/LexResult.cs b/Tests.Unit.Compiler.Lexing/Helpers/LexResult.cs new file mode 100644 index 00000000..193efe09 --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/Helpers/LexResult.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Helpers +{ + public class LexResult + { + public CodeFile File { get; } + public FixedList Tokens { get; } + public FixedList Diagnostics { get; } + + public LexResult(ITokenIterator iterator) + { + var tokens = new List(); + do + { + tokens.Add(iterator.Current); + } while (iterator.Next()); + + File = iterator.Context.File; + Tokens = tokens.ToFixedList(); + Diagnostics = iterator.Context.Diagnostics.Build(); + } + + public IToken AssertSingleToken() + { + Assert.True(2 == Tokens.Count, $"Expected token count {1}, was {Tokens.Count - 1} (excluding EOF)"); + var eof = Assert.IsAssignableFrom(Tokens[^1]); + Assert.Equal(new TextSpan(Tokens[^2].Span.End, 0), eof.Span); + return Tokens[0]; + } + + public void AssertTokens(int expectedCount) + { + Assert.Equal(expectedCount + 1, Tokens.Count); + } + + public void AssertNoDiagnostics() + { + Assert.Empty(Diagnostics); + } + + public Diagnostic AssertSingleDiagnostic() + { + return Assert.Single(Diagnostics); + } + + public void AssertDiagnostics(int expectedCount) + { + Assert.Equal(expectedCount, Diagnostics.Count); + } + + public string TokensToString() + { + return string.Concat(Tokens.Select(t => t.Text(File.Code))); + } + + public FixedList ToPsuedoTokens() + { + return Tokens.Select(t => PsuedoToken.For(t, File.Code)).ToFixedList(); + } + } +} diff --git a/Tests.Unit.Compiler.Lexing/Helpers/PsuedoToken.cs b/Tests.Unit.Compiler.Lexing/Helpers/PsuedoToken.cs new file mode 100644 index 00000000..0caa0b34 --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/Helpers/PsuedoToken.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Helpers +{ + public class PsuedoToken + { + public Type TokenType { get; } + + public string Text { get; } + public object? Value { get; } + + public PsuedoToken(Type tokenType, string text, object? value = null) + { + TokenType = tokenType; + Text = text; + Value = value; + } + + public static PsuedoToken EndOfFile() + { + return new PsuedoToken(typeof(IEndOfFileToken), ""); + } + + public static PsuedoToken For(IToken token, CodeText code) + { + var tokenType = token.GetType(); + var text = token.Text(code); + return token switch + { + IIdentifierToken identifier => new PsuedoToken(tokenType, text, identifier.Value), + IStringLiteralToken stringLiteral => new PsuedoToken(tokenType, text, stringLiteral.Value), + IIntegerLiteralToken integerLiteral => new PsuedoToken(tokenType, text, integerLiteral.Value), + _ => new PsuedoToken(tokenType, text) + }; + } + + public override bool Equals(object? obj) + { + if (obj is PsuedoToken token && + (TokenType == token.TokenType + || TokenType.IsAssignableFrom(token.TokenType) + || token.TokenType.IsAssignableFrom(TokenType)) && + Text == token.Text) + { + if (Value is IReadOnlyList diagnostics + && token.Value is IReadOnlyList otherDiagnostics) + { + // TODO this zip looks wrong, shouldn't it be comparing something rather than always returning false? + return diagnostics.Zip(otherDiagnostics, (d1, d2) => false).All(i => i); + } + return EqualityComparer.Default.Equals(Value, token.Value); + } + return false; + } + + public override int GetHashCode() + { + return HashCode.Combine(TokenType, Text, Value); + } + + public override string ToString() + { + var textValue = string.IsNullOrEmpty(Text) ? "" : $":„{Text.Escape()}„"; + return Value switch + { + null => $"{TokenType.Name}{textValue}", + string s => $"{TokenType.Name}{textValue} 【{s.Escape()}】", + BigInteger i => $"{TokenType.Name}{textValue} {i}", + IReadOnlyList diagnostics => $"{TokenType.Name}{textValue} [{diagnostics.DebugFormat()}]", + _ => $"{TokenType.Name}{textValue} InvalidValue={Value}" + }; + } + } +} diff --git a/Tests.Unit.Compiler.Lexing/LexerTests.cs b/Tests.Unit.Compiler.Lexing/LexerTests.cs new file mode 100644 index 00000000..82071e9c --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/LexerTests.cs @@ -0,0 +1,338 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Lexing; +using Azoth.Tools.Bootstrap.Compiler.Tokens; +using Azoth.Tools.Bootstrap.Framework; +using Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Fakes; +using Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing.Helpers; +using Azoth.Tools.Bootstrap.Tests.Unit.Helpers; +using FsCheck; +using FsCheck.Xunit; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing +{ + [Trait("Category", "Lexing")] + public class LexerTests + { + private static LexResult Lex(string text) + { + var lexer = new Lexer(); + var context = FakeParseContext.For(text); + return new LexResult(lexer.Lex(context)); + } + + [Theory] + [InlineData("hello", "hello")] + [InlineData(@"\class", "class")] + [InlineData(@"\""Hello World!""", "Hello World!", Skip = "Escaped String Identifiers Not Implemented")] + public void Identifier_value(string identifier, string value) + { + var result = Lex(identifier); + var token = result.AssertSingleToken(); + token.AssertIdentifier(0, identifier.Length, value); + result.AssertNoDiagnostics(); + } + + [Theory] + // Keywords Not Yet Implemented + [InlineData("ensures")] + [InlineData("implicit")] + [InlineData("invariant")] + [InlineData("params")] + [InlineData("ref")] + [InlineData("throw")] + [InlineData("where")] + // Actual Reserved Words + [InlineData("alias")] + [InlineData("partial")] + [InlineData("xor")] + public void Reserved_words(string reservedWord) + { + // TODO this error should only be reported at the declaration site, not the use site + var result = Lex(reservedWord); + var token = result.AssertSingleToken(); + token.AssertIdentifier(0, reservedWord.Length, reservedWord); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1006, 0, reservedWord.Length); + } + + [Fact] + public void Continue_as_next() + { + // TODO this error should be contextual so that it reports reserved word when used as a keyword, but this when used as `next` + const string word = "continue"; + var result = Lex(word); + var token = result.AssertSingleToken(); + token.AssertIdentifier(0, word.Length, word); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1007, 0, word.Length); + } + + [Theory] + // Keywords + [InlineData("class")] + [InlineData("int")] + // Keywords not yet implemented + [InlineData("invariant")] + [InlineData("ensures")] + [InlineData("params")] + // Reserved words + [InlineData("alias")] + [InlineData("partial")] + [InlineData("xor")] + // Reserved type names + [InlineData("int12")] + [InlineData("uint96")] + [InlineData("float42")] + [InlineData("fixed")] + [InlineData("fixed8.8", Skip = "Not implemented, doesn't lex as identifier")] + [InlineData("ufixed8.24", Skip = "Not implemented, doesn't lex as identifier")] + [InlineData("decimal")] + [InlineData("decimal32")] + [InlineData("real")] + [InlineData("real.45", Skip = "Doesn't lex as identifier")] + // Start with digits + [InlineData("0")] + [InlineData("1")] + [InlineData("9")] + [InlineData("42_answer")] + public void Escaped_identifiers(string identifier) + { + var result = Lex("\\"+identifier); + var token = result.AssertSingleToken(); + token.AssertIdentifier(0, identifier.Length+1, identifier); + result.AssertNoDiagnostics(); + } + + [Theory] + [InlineData("hello")] + [InlineData("x")] + [InlineData("foo")] + public void Escaped_identifier_not_reserved(string identifier) + { + var result = Lex("\\" + identifier); + var token = result.AssertSingleToken(); + token.AssertIdentifier(0, identifier.Length + 1, identifier); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1008, 0, identifier.Length + 1); + } + + [Theory] + [InlineData(@"""Hello World!""", "Hello World!")] + [InlineData(@""" \r \n \0 \t \"" \' """, " \r \n \0 \t \" ' ")] // basic escape sequences + [InlineData(@"""\u(2660)""", "\u2660")] // basic unicode escape sequences + [InlineData(@""" \u(FFFF) \u(a) """, " \uFFFF \u000A ")] + [InlineData(@""" \u(10FFFF) """, " \uDBFF\uDFFF ")] // Surrogate Pair + public void String_literal_value(string literal, string value) + { + var result = Lex(literal); + var token = result.AssertSingleToken(); + token.AssertStringLiteral(0, literal.Length, value); + result.AssertNoDiagnostics(); + } + + [Theory] + [MemberData(nameof(Symbols_lex_TheoryData))] + public void Symbols_lex(string symbol, Type tokenType) + { + var result = Lex(symbol); + var token = result.AssertSingleToken(); + Assert.OfType(tokenType, token); + Assert.Equal(new TextSpan(0, symbol.Length), token.Span); + result.AssertNoDiagnostics(); + } + + public static IEnumerable Symbols_lex_TheoryData() + { + return Arbitrary.Symbols.Select(item => new object[] { item.Key, item.Value }); + } + + [Fact] + public void Error_for_c_style_not_equals_operator() + { + var result = Lex("x!=y"); + result.AssertTokens(3); + result.Tokens[0].AssertIdentifier(0, 1, "x"); + result.Tokens[1].AssertIs(1, 2); + result.Tokens[2].AssertIdentifier(3, 1, "y"); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1004, 1, 2); + } + + [Theory] + [InlineData(@"/*c*/")] + [InlineData(@"/**/")] + [InlineData(@"/***/")] + [InlineData(@"/*/**/")] + [InlineData(@"/* *c */")] + [InlineData(@"//c")] + [InlineData(@"//\r")] + [InlineData(@"// foo")] + public void Comments(string comment) + { + var result = Lex(comment); + var token = result.AssertSingleToken(); + token.AssertIs(0, comment.Length); + result.AssertNoDiagnostics(); + } + + [Theory] + [InlineData(@"/*")] + [InlineData(@"/*/")] + public void End_of_file_in_block_comment(string comment) + { + var result = Lex(comment); + var token = result.AssertSingleToken(); + token.AssertIs(0, comment.Length); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1001, 0, comment.Length); + } + + [Fact] + public void End_of_file_in_line_comment() + { + var result = Lex("// hello"); + var token = result.AssertSingleToken(); + token.AssertIs(0, 8); + result.AssertNoDiagnostics(); + } + + [Fact] + public void End_of_file_in_string() + { + var result = Lex(@"""Hello"); + var token = result.AssertSingleToken(); + token.AssertStringLiteral(0, 6, "Hello"); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1002, 0, 6); + } + + [Theory] + [InlineData(@"""\", "")] + [InlineData(@"""\a""", "a")] + [InlineData(@"""\m""", "m")] + [InlineData(@"""\x""", "x")] + // Unicode escapes could be stopped with EOF, End of string or invalid char + // at each of the different states + [InlineData(@"""\u""", "u")] + [InlineData(@"""\u", "u")] + [InlineData(@"""\u|""", "u|")] + [InlineData(@"""\u(", "u(")] + [InlineData(@"""\u(""", "u(")] + [InlineData(@"""\u(|""", "u(|")] + [InlineData(@"""\u(1f", "\u001f")] + [InlineData(@"""\u(1f""", "\u001f")] + [InlineData(@"""\u(1f|""", "\u001f|")] + [InlineData(@"""\u()""", "u()")] + [InlineData(@"""\u(110000)""", "u(110000)")] // 1 too high + // TODO can't put use a surrogate pair as a unicode escape, they must be unicode scalars + public void Invalid_escape_sequence(string literal, string expectedValue) + { + var result = Lex(literal); + var token = result.AssertSingleToken(); + token.AssertStringLiteral(0, literal.Length, expectedValue); + var completeString = literal.EndsWith("\"", StringComparison.Ordinal); + var expectedDiagnosticCount = completeString ? 1 : 2; + result.AssertDiagnostics(expectedDiagnosticCount); + var diagnostics = result.Diagnostics; + + var expectedLength = literal.Length; + if (literal.Contains('|', StringComparison.Ordinal)) expectedLength -= 1; + if (completeString) + diagnostics[0].AssertError(1003, 1, expectedLength - 2); + else + { + diagnostics[0].AssertError(1002, 0, literal.Length); + diagnostics[1].AssertError(1003, 1, expectedLength - 1); + } + } + + [Theory] + [InlineData(" % ")] + [InlineData(" ~ ")] + [InlineData(" ◊ ")] + [InlineData(" \u0007 ")] // Bell Character + public void Unexpected_character(string text) + { + var result = Lex(text); + result.AssertTokens(3); + result.Tokens[0].AssertIs(0, 1); + result.Tokens[1].AssertIs(1, 1); + result.Tokens[2].AssertIs(2, 1); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1005, 1, 1); + Assert.True(diagnostic.Message.Contains(text[1], StringComparison.Ordinal), "Doesn't contain character"); + } + + [Theory] + // Not implemented + [InlineData("[")] + [InlineData("]")] + [InlineData("#")] + [InlineData("#(")] + [InlineData("#[")] + [InlineData("#{")] + [InlineData("|")] + [InlineData("&")] + [InlineData("@")] + [InlineData("^")] + [InlineData("^.")] + // Reserved + [InlineData("$")] + [InlineData("`")] + [InlineData("##")] + public void Reserved_operator(string text) + { + var result = Lex(text); + var token = result.AssertSingleToken(); + token.AssertIs(0, text.Length); + var diagnostic = result.AssertSingleDiagnostic(); + diagnostic.AssertError(1009, 0, text.Length); + } + + [Property(MaxTest = 10_000)] + public Property Tokens_concatenate_to_input() + { + return Prop.ForAll>(input => + { + var inputString = input.NotNull(nameof(input)); + var result = Lex(inputString); + return inputString == result.TokensToString(); + }); + } + + [Property(MaxTest = 1_000)] + public Property Token_lexes() + { + return Prop.ForAll(Arbitrary.PsuedoToken(), token => + { + var result = Lex(token.Text); + var outputAsPsuedoTokens = result.ToPsuedoTokens(); + var expectedPsuedoTokens = token.Yield().Append(PsuedoToken.EndOfFile()).ToList(); + return expectedPsuedoTokens.SequenceEqual(outputAsPsuedoTokens) + .Label($"Actual: {outputAsPsuedoTokens.DebugFormat()}") + .Label($"Expected: {expectedPsuedoTokens.DebugFormat()}") + .Collect(token.TokenType.Name); + }); + } + + [Property(MaxTest = 200)] + public Property Valid_token_sequence_lexes_back_to_itself() + { + return Prop.ForAll(Arbitrary.PsuedoTokenList(), tokens => + { + var input = string.Concat(tokens.Select(t => t.Text)); + var result = Lex(input); + var outputAsPsuedoTokens = result.ToPsuedoTokens(); + var expectedPsuedoTokens = tokens.Append(PsuedoToken.EndOfFile()).ToFixedList(); + return expectedPsuedoTokens.SequenceEqual(outputAsPsuedoTokens) + .Label($"Text: „{input.Escape()}„") + .Label($"Actual: {outputAsPsuedoTokens.DebugFormat()}") + .Label($"Expected: {expectedPsuedoTokens.DebugFormat()}"); + }); + } + } +} diff --git a/Tests.Unit.Compiler.Lexing/Tests.Unit.Compiler.Lexing.csproj b/Tests.Unit.Compiler.Lexing/Tests.Unit.Compiler.Lexing.csproj new file mode 100644 index 00000000..8f0f5d09 --- /dev/null +++ b/Tests.Unit.Compiler.Lexing/Tests.Unit.Compiler.Lexing.csproj @@ -0,0 +1,50 @@ + + + + netcoreapp3.0 + + false + + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing + + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Lexing + + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + diff --git a/Tests.Unit.Compiler.Names/GlobalSuppressions.cs b/Tests.Unit.Compiler.Names/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Unit.Compiler.Names/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Unit.Compiler.Names/NamespaceNameTests.cs b/Tests.Unit.Compiler.Names/NamespaceNameTests.cs new file mode 100644 index 00000000..9f53a45a --- /dev/null +++ b/Tests.Unit.Compiler.Names/NamespaceNameTests.cs @@ -0,0 +1,136 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Names +{ + [Trait("Category", "Names")] + public class NamespaceNameTests + { + [Fact] + public void Name_qualified_with_global_namespace_is_unchanged() + { + var ns = Namespace("foo", "bar", "baz"); + + var qualified = NamespaceName.Global.Qualify(ns); + + Assert.Equal(ns, qualified); + } + + [Fact] + public void Qualified_name_combines_segments() + { + var ns1 = Namespace("foo", "bar", "baz"); + var ns2 = Namespace("qux", "quux", "quuz"); + + var qualified = ns1.Qualify(ns2); + + Assert.Equal(Namespace("foo", "bar", "baz", "qux", "quux", "quuz"), qualified); + } + + [Fact] + public void Can_qualify_empty_namespace() + { + var ns = Namespace("foo", "bar", "baz"); + + var qualifiedEmpty = ns.Qualify(Namespace()); + + Assert.Equal(ns, qualifiedEmpty); + } + + [Fact] + public void Namespace_names_of_typical_namespace() + { + var ns = Namespace("foo", "bar", "baz"); + + var names = ns.NamespaceNames(); + + var expected = new[] + { + NamespaceName.Global, + Namespace("foo"), + Namespace("foo", "bar"), + Namespace("foo", "bar", "baz") + }; + Assert.Equal(expected, names); + } + + [Fact] + public void Namespace_names_of_global_namespace() + { + var ns = Namespace(); + + var names = ns.NamespaceNames(); + + var expected = new[] + { + NamespaceName.Global + }; + Assert.Equal(expected, names); + } + + [Fact] + public void Namespace_not_nested_in_itself() + { + var ns = Namespace("foo", "bar", "baz"); + + var isNested = ns.IsNestedIn(ns); + + Assert.False(isNested); + } + + [Fact] + public void Namespace_nested_in_global_namespace() + { + var ns = Namespace("foo", "bar", "baz"); + + var isNested = ns.IsNestedIn(NamespaceName.Global); + + Assert.True(isNested); + } + + [Fact] + public void Namespace_nested_in_containing_namespace() + { + var ns = Namespace("foo", "bar", "baz"); + + var isNested = ns.IsNestedIn(Namespace("foo", "bar")); + + Assert.True(isNested); + } + + [Fact] + public void Namespace_nested_in_containing_namespace_parent() + { + var ns = Namespace("foo", "bar", "baz"); + + var isNested = ns.IsNestedIn(Namespace("foo")); + + Assert.True(isNested); + } + + [Fact] + public void Namespace_not_nested_in_child() + { + var ns = Namespace("foo", "bar", "baz"); + + var isNested = ns.IsNestedIn(ns.Qualify("biff")); + + Assert.False(isNested); + } + + [Fact] + public void Equal_namespaces_have_same_hash_code() + { + var ns1 = Namespace("foo", "bar", "baz"); + var ns2 = Namespace("foo", "bar", "baz"); + + Assert.Equal(ns1, ns2); + Assert.Equal(ns1.GetHashCode(), ns2.GetHashCode()); + } + + private static NamespaceName Namespace(params string[] segments) + { + return new NamespaceName(segments); + } + } +} diff --git a/Tests.Unit.Compiler.Names/Tests.Unit.Compiler.Names.csproj b/Tests.Unit.Compiler.Names/Tests.Unit.Compiler.Names.csproj new file mode 100644 index 00000000..1859c55a --- /dev/null +++ b/Tests.Unit.Compiler.Names/Tests.Unit.Compiler.Names.csproj @@ -0,0 +1,39 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Names + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Names + enable + + + + DEBUG;TRACE + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Tests.Unit.Compiler.Symbols/ConstructorTests.cs b/Tests.Unit.Compiler.Symbols/ConstructorTests.cs new file mode 100644 index 00000000..a013cee7 --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/ConstructorTests.cs @@ -0,0 +1,21 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class ConstructorTests : SymbolTestFixture + { + [Fact] + public void Default_constructor_has_correct_properties() + { + var type = Type(); + var defaultConstructor = ConstructorSymbol.CreateDefault(type); + + Assert.Equal(type, defaultConstructor.ContainingSymbol); + Assert.Null(defaultConstructor.Name); + Assert.Empty(defaultConstructor.ParameterDataTypes); + Assert.Equal(0, defaultConstructor.Arity); + } + } +} diff --git a/Tests.Unit.Compiler.Symbols/FunctionSymbolTests.cs b/Tests.Unit.Compiler.Symbols/FunctionSymbolTests.cs new file mode 100644 index 00000000..3c8df91d --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/FunctionSymbolTests.cs @@ -0,0 +1,66 @@ +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class FunctionSymbolTests : SymbolTestFixture + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(20)] + public void Arity_is_number_of_parameters(int expectedParameters) + { + var funcA = Func("A", @params: Params(expectedParameters)); + + Assert.Equal(expectedParameters, funcA.Arity); + } + + [Fact] + public void Functions_with_same_parameters_and_return_type_are_equal() + { + var parameters = Params(DataType("T1"), DataType("T2")); + var funcA = Func("A", @params: parameters, @return: DataType("T3")); + var funcACopy = Func(funcA); + + Assert.Equal(funcA, funcACopy); + } + + [Fact] + public void Functions_with_different_parameters_are_not_equal() + { + var parameters1 = Params(DataType("T1"), DataType("T2")); + var funcA1 = Func("A", @params: parameters1); + var parameters2 = Params(DataType("T1"), DataType("T3")); + var funcA2 = Func(funcA1, @params: parameters2); + + Assert.NotEqual(funcA1, funcA2); + } + + [Fact] + public void Functions_with_different_return_types_are_not_equal() + { + var funcA1 = Func("A", @return: DataType("T1")); + var funcA2 = Func(funcA1, @return: DataType("T2")); + + Assert.NotEqual(funcA1, funcA2); + } + + [Fact] + public void Is_not_equal_to_equivalent_method() + { + // Note that methods should really have different names than functions, + // but, just in case, we need to check method vs. function in equality. + var ns = Namespace(); + var parameters = Params(DataType("T1"), DataType("T2")); + var funcA = Func("A", ns, parameters, DataType("T3")); + var selfDataType = DataType("Class"); + var selfType = Type(ns, selfDataType); + var methodA = Method("A", selfType, selfDataType, parameters, DataType("T3")); + + // Note: assert false used to ensure which object Equals is called on + Assert.False(funcA.Equals(methodA)); + } + } +} diff --git a/Tests.Unit.Compiler.Symbols/GlobalSuppressions.cs b/Tests.Unit.Compiler.Symbols/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Unit.Compiler.Symbols/MethodSymbolTests.cs b/Tests.Unit.Compiler.Symbols/MethodSymbolTests.cs new file mode 100644 index 00000000..fb06e2e4 --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/MethodSymbolTests.cs @@ -0,0 +1,64 @@ +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class MethodSymbolTests : SymbolTestFixture + { + [Fact] + public void Methods_with_same_name_parameters_and_return_type_are_equal() + { + var methodA = Method("A"); + var methodACopy = Method(methodA); + + Assert.Equal(methodA, methodACopy); + } + + [Fact] + public void Methods_with_different_self_parameters_are_not_equal() + { + var selfType1 = DataType("T1"); + var methodA1 = Method("A", self: selfType1); + var selfType2 = DataType("T2"); + var methodA2 = Method(methodA1, self: selfType2); + + Assert.NotEqual(methodA1, methodA2); + } + + [Fact] + public void Methods_with_different_parameters_are_not_equal() + { + var parameters1 = Params(DataType("T1"), DataType("T2")); + var methodA1 = Method("A", @params: parameters1); + var parameters2 = Params(DataType("T1"), DataType("T3")); + var methodA2 = Method(methodA1, @params: parameters2); + + Assert.NotEqual(methodA1, methodA2); + } + + [Fact] + public void Methods_with_different_return_types_are_not_equal() + { + var methodA1 = Method("A", @return: DataType("T1")); + var methodA2 = Method(methodA1, @return: DataType("T2")); + + Assert.NotEqual(methodA1, methodA2); + } + + [Fact] + public void Is_not_equal_to_equivalent_function() + { + // Note that methods should really have different names than functions, + // but, just in case, we need to check method vs. function in equality. + var ns = Namespace(); + var parameters = Params(DataType("T1"), DataType("T2")); + var selfDataType = DataType("Class"); + var selfType = Type(ns, selfDataType); + var method = Method("A", selfType, selfDataType, parameters, DataType("T3")); + var func = Func("A", ns, parameters, DataType("T3")); + + // Note: assert false used to ensure which object Equals is called on + Assert.False(method.Equals(func)); + } + } +} diff --git a/Tests.Unit.Compiler.Symbols/NamespaceSymbolTests.cs b/Tests.Unit.Compiler.Symbols/NamespaceSymbolTests.cs new file mode 100644 index 00000000..233364ea --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/NamespaceSymbolTests.cs @@ -0,0 +1,39 @@ +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class NamespaceSymbolTests : SymbolTestFixture + { + [Fact] + public void Namespaces_with_same_name_and_containing_symbol_are_equal() + { + var container = Package("my.something_package"); + var nsFoo1 = Namespace("foo", container); + var nsFoo2 = Namespace("foo", container); + + Assert.Equal(nsFoo1, nsFoo2); + } + + [Fact] + public void Namespaces_with_different_name_are_not_equal() + { + var container = Package("my.something_package"); + var nsFoo = Namespace("foo", container); + var nsBar = Namespace("bar", container); + + Assert.NotEqual(nsFoo, nsBar); + } + + [Fact] + public void Namespaces_with_different_containing_symbols_are_not_equal() + { + var container1 = Package("my.something_package"); + var ns1 = Namespace("foo", container1); + var container2 = Package("my.other_package"); + var ns2 = Namespace("foo", container2); + + Assert.NotEqual(ns1, ns2); + } + } +} diff --git a/Tests.Unit.Compiler.Symbols/PackageSymbolTests.cs b/Tests.Unit.Compiler.Symbols/PackageSymbolTests.cs new file mode 100644 index 00000000..caac4367 --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/PackageSymbolTests.cs @@ -0,0 +1,17 @@ +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class PackageSymbolTests : SymbolTestFixture + { + [Fact] + public void Packages_with_same_name_are_equal() + { + var p1 = Package("some.package.name"); + var p2 = Package("some.package.name"); + + Assert.Equal(p1, p2); + } + } +} diff --git a/Tests.Unit.Compiler.Symbols/SymbolTests.cs b/Tests.Unit.Compiler.Symbols/SymbolTests.cs new file mode 100644 index 00000000..938d0c50 --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/SymbolTests.cs @@ -0,0 +1,32 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Moq; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class SymbolTests : SymbolTestFixture + { + [Fact] + public void Symbol_not_in_namespace_is_global() + { + var symbol = FakeSymbol(null, Name("My_Class")); + + Assert.True(symbol.IsGlobal); + } + + [Fact] + public void Symbol_in_namespace_is_not_global() + { + var symbol = FakeSymbol(Namespace(), Name("My_Class")); + + Assert.False(symbol.IsGlobal); + } + + private static Symbol FakeSymbol(NamespaceOrPackageSymbol? containing, Name name) + { + return new Mock(MockBehavior.Default, containing, name).Object; + } + } +} diff --git a/Tests.Unit.Compiler.Symbols/Tests.Unit.Compiler.Symbols.csproj b/Tests.Unit.Compiler.Symbols/Tests.Unit.Compiler.Symbols.csproj new file mode 100644 index 00000000..a7c0a895 --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/Tests.Unit.Compiler.Symbols.csproj @@ -0,0 +1,41 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols + enable + + + + DEBUG;TRACE + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/Tests.Unit.Compiler.Symbols/TypeSymbolTests.cs b/Tests.Unit.Compiler.Symbols/TypeSymbolTests.cs new file mode 100644 index 00000000..0c4c0962 --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/TypeSymbolTests.cs @@ -0,0 +1,44 @@ +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class TypeSymbolTests : SymbolTestFixture + { + [Fact] + public void With_same_name_and_type_are_equal() + { + var container = Package("my.package"); + var type = DataType("T1"); + var sym1 = Type(container, type); + var sym2 = Type(container, type); + + Assert.Equal(sym1, sym2); + } + + [Fact] + public void With_different_name_are_not_equal() + { + var container = Package("my.package"); + var type1 = DataType("My_Class1"); + var sym1 = Type(container, type1); + var type2 = DataType("My_Class2"); + var sym2 = Type(container, type2); + + Assert.NotEqual(sym1, sym2); + } + + [Fact] + public void With_different_type_are_not_equal() + { + var container = new PackageSymbol(Name("my.package")); + var type1 = DataType("T1"); + var sym1 = Type(container, type1); + var type2 = DataType("T2"); + var sym2 = Type(container, type2); + + Assert.NotEqual(sym1, sym2); + } + } +} diff --git a/Tests.Unit.Compiler.Symbols/VariableSymbolTests.cs b/Tests.Unit.Compiler.Symbols/VariableSymbolTests.cs new file mode 100644 index 00000000..b5f95f84 --- /dev/null +++ b/Tests.Unit.Compiler.Symbols/VariableSymbolTests.cs @@ -0,0 +1,49 @@ +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Symbols +{ + [Trait("Category", "Symbols")] + public class VariableSymbolTests : SymbolTestFixture + { + [Fact] + public void Has_properties_constructed_with() + { + var func = Func(); + var dataType = DataType(); + var symbol = Variable("foo", func, 42, true, dataType); + + Assert.Equal(func, symbol.ContainingSymbol); + Assert.Equal(Name("foo"), symbol.Name); + Assert.Equal(42, symbol.DeclarationNumber); + Assert.True(symbol.IsMutableBinding); + Assert.Equal(dataType, symbol.DataType); + } + + [Fact] + public void Variables_with_same_name_mutability_and_type_are_equal() + { + var varA = Variable("a"); + var varACopy = Variable(varA); + + Assert.Equal(varA, varACopy); + } + + [Fact] + public void Variables_with_different_mutability_are_not_equal() + { + var varA1 = Variable("a", mut: true); + var varA2 = Variable(varA1, mut: false); + + Assert.NotEqual(varA1, varA2); + } + + [Fact] + public void Variables_with_different_types_are_not_equal() + { + var varA1 = Variable("a", type: DataType("T1")); + var varA2 = Variable(varA1, type: DataType("T2")); + + Assert.NotEqual(varA1, varA2); + } + } +} diff --git a/Tests.Unit.Compiler.Types/AnyTypeTests.cs b/Tests.Unit.Compiler.Types/AnyTypeTests.cs new file mode 100644 index 00000000..6eb2cecb --- /dev/null +++ b/Tests.Unit.Compiler.Types/AnyTypeTests.cs @@ -0,0 +1,126 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; +using static Azoth.Tools.Bootstrap.Compiler.Types.ReferenceCapability; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class AnyTypeTests + { + [Fact] + public void Is_reference_type() + { + var type = new AnyType(Isolated); + + Assert.OfType(type); + } + + [Fact] + public void Is_not_constant() + { + var type = new AnyType(Isolated); + + Assert.False(type.IsConstant); + } + + [Fact] + public void Is_known_type() + { + var type = new AnyType(Isolated); + + Assert.True(type.IsKnown); + } + + [Fact] + public void Is_not_empty_type() + { + var type = new AnyType(Isolated); + + Assert.False(type.IsEmpty); + } + + [Fact] + public void Is_declared_mutable() + { + var type = new AnyType(Isolated); + + Assert.True(type.DeclaredMutable); + } + + [Fact] + public void Has_reference_semantics() + { + var type = new AnyType(Isolated); + + Assert.Equal(TypeSemantics.Reference, type.Semantics); + } + + //[Theory] + //[InlineData(Isolated, "iso Any")] + //[InlineData(OwnedMutable, "owned mut Any")] + //[InlineData(Borrowed, "mut Any")] + //[InlineData(Shared, "Any")] + //public void ToSourceCodeString_includes_reference_capability(ReferenceCapability capability, string expected) + //{ + // var type = new AnyType(capability); + + // Assert.Equal(expected, type.ToSourceCodeString()); + //} + + //[Theory] + //[InlineData(Isolated, "iso Any")] + //[InlineData(OwnedMutable, "owned mut Any")] + //[InlineData(Borrowed, "mut Any")] + //[InlineData(Shared, "shared Any")] + //public void ToILString_includes_reference_capability(ReferenceCapability capability, string expected) + //{ + // var type = new AnyType(capability); + + // Assert.Equal(expected, type.ToILString()); + //} + + //[Theory] + //[InlineData(Isolated)] + //[InlineData(OwnedMutable)] + //[InlineData(Borrowed)] + //[InlineData(Shared)] + //public void Has_reference_capability_constructed_with(ReferenceCapability capability) + //{ + // var type = new AnyType(capability); + + // Assert.Equal(capability, type.ReferenceCapability); + //} + + //[Theory] + //[InlineData(Isolated)] + //[InlineData(OwnedMutable)] + //[InlineData(Borrowed)] + //[InlineData(Shared)] + //public void Can_convert_to_reference_capability(ReferenceCapability capability) + //{ + // var type = new AnyType(Isolated); + + // var converted = type.To(capability); + + // Assert.Equal(capability, converted.ReferenceCapability); + //} + + [Fact] + public void Any_types_with_same_reference_capability_are_equal() + { + var type1 = new AnyType(Shared); + var type2 = new AnyType(Shared); + + Assert.Equal(type1, type2); + } + + [Fact] + public void Any_types_with_different_reference_capabilities_are_not_equal() + { + var type1 = new AnyType(Shared); + var type2 = new AnyType(SharedMutable); + + Assert.NotEqual(type1, type2); + } + } +} diff --git a/Tests.Unit.Compiler.Types/BoolConstantTypeTests.cs b/Tests.Unit.Compiler.Types/BoolConstantTypeTests.cs new file mode 100644 index 00000000..f8e5c871 --- /dev/null +++ b/Tests.Unit.Compiler.Types/BoolConstantTypeTests.cs @@ -0,0 +1,83 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class BoolConstantTypeTests + { + [Fact] + public void Is_a_boolean_type() + { + var type = BoolConstantType.True; + + Assert.OfType(type); + } + + [Fact] + public void Is_constant() + { + var type = BoolConstantType.True; + + Assert.True(type.IsConstant); + } + + [Fact] + public void True_type_has_true_value() + { + var type = BoolConstantType.True; + + Assert.True(type.Value); + } + + [Fact] + public void False_type_has_false_value() + { + var type = BoolConstantType.False; + + Assert.False(type.Value); + } + + [Fact] + public void True_has_special_name_and_ToString() + { + var type = BoolConstantType.True; + + Assert.Equal(SpecialTypeName.True, type.Name); + Assert.Equal("const[true]", type.ToString()); + } + + [Fact] + public void False_has_special_name_and_ToString() + { + var type = BoolConstantType.False; + + Assert.Equal(SpecialTypeName.False, type.Name); + Assert.Equal("const[false]", type.ToString()); + } + + [Fact] + public void Converts_to_non_constant_bool_type() + { + var type = BoolConstantType.False; + + var nonConstant = type.ToNonConstantType(); + + Assert.Same(DataType.Bool, nonConstant); + } + + [Fact] + public void Bool_constant_types_with_same_value_are_equal() + { + Assert.Equal(BoolConstantType.True, BoolConstantType.True); + Assert.Equal(BoolConstantType.False, BoolConstantType.False); + } + + [Fact] + public void Any_types_with_different_reference_capabilities_are_not_equal() + { + Assert.NotEqual(BoolConstantType.True, BoolConstantType.False); + } + } +} diff --git a/Tests.Unit.Compiler.Types/BoolTypeTests.cs b/Tests.Unit.Compiler.Types/BoolTypeTests.cs new file mode 100644 index 00000000..2ea6b6ec --- /dev/null +++ b/Tests.Unit.Compiler.Types/BoolTypeTests.cs @@ -0,0 +1,90 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class BoolTypeTests + { + [Fact] + public void Is_simple_value_type() + { + var type = BoolType.Instance; + + Assert.OfType(type); + Assert.OfType(type); + } + + [Fact] + public void Is_not_constant() + { + var type = BoolType.Instance; + + Assert.False(type.IsConstant); + } + + [Fact] + public void Is_known_type() + { + var type = BoolType.Instance; + + Assert.True(type.IsKnown); + } + + [Fact] + public void Is_not_empty_type() + { + var type = BoolType.Instance; + + Assert.False(type.IsEmpty); + } + + [Fact] + public void Has_copy_semantics() + { + var type = BoolType.Instance; + + Assert.Equal(TypeSemantics.Copy, type.Semantics); + } + + [Fact] + public void Has_special_name_bool() + { + var type = BoolType.Instance; + + Assert.Equal(SpecialTypeName.Bool, type.Name); + } + + [Fact] + public void Has_proper_ToString() + { + var type = BoolType.Instance; + + Assert.Equal("bool", type.ToString()); + } + + [Fact] + public void Convert_to_read_only_has_no_effect() + { + var type = BoolType.Instance; + + var @readonly = type.ToReadOnly(); + + Assert.Equal(type, @readonly); + } + + [Fact] + public void Bool_type_equal_to_itself() + { + Assert.Equal(BoolType.Instance, BoolType.Instance); + } + + [Fact] + public void Bool_type_not_equal_to_bool_constant_type() + { + Assert.NotEqual(BoolType.Instance, DataType.True); + Assert.NotEqual(BoolType.Instance, DataType.True); + } + } +} diff --git a/Tests.Unit.Compiler.Types/DataTypeExtensionsTests.cs b/Tests.Unit.Compiler.Types/DataTypeExtensionsTests.cs new file mode 100644 index 00000000..03076e45 --- /dev/null +++ b/Tests.Unit.Compiler.Types/DataTypeExtensionsTests.cs @@ -0,0 +1,81 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; +using static Azoth.Tools.Bootstrap.Compiler.Types.ReferenceCapability; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class DataTypeExtensionsTests + { + [Fact] + public void Bool_constant_types_is_assignable_to_bool_type() + { + var trueAssignable = DataType.Bool.IsAssignableFrom(DataType.True); + var falseAssignable = DataType.Bool.IsAssignableFrom(DataType.False); + + Assert.True(trueAssignable, $"{DataType.True} not assignable to {DataType.Bool}"); + Assert.True(falseAssignable, $"{DataType.False} not assignable to {DataType.Bool}"); + } + + /// + /// A numeric conversion is required + /// + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(int.MaxValue)] + [InlineData((long)int.MaxValue+1)] + [InlineData(-1)] + [InlineData(int.MinValue)] + [InlineData((long)int.MinValue-1)] + public void Integer_constant_types_not_assignable_to_int32(long value) + { + var constType = new IntegerConstantType(value); + + var assignable = DataType.Int.IsAssignableFrom(constType); + + Assert.False(assignable); + } + + [Fact] + public void Underlying_reference_type_of_reference_type_is_itself() + { + var referenceType = new ObjectType("Foo", "Bar", true, SharedMutable); + + var underlyingType = referenceType.UnderlyingReferenceType(); + + Assert.Same(referenceType, underlyingType); + } + + [Fact] + public void Underlying_reference_type_of_optional_reference_type_is_reference_type() + { + var referenceType = new ObjectType("Foo", "Bar", true, SharedMutable); + var optionalType = new OptionalType(referenceType); + + var underlyingType = optionalType.UnderlyingReferenceType(); + + Assert.Same(referenceType, underlyingType); + } + + [Fact] + public void No_underlying_reference_type_for_optional_value_type() + { + var optionalType = new OptionalType(DataType.Bool); + + var underlyingType = optionalType.UnderlyingReferenceType(); + + Assert.Null(underlyingType); + } + + [Fact] + public void No_underlying_reference_type_for_value_type() + { + var valueType = DataType.Int; + + var underlyingType = valueType.UnderlyingReferenceType(); + + Assert.Null(underlyingType); + } + } +} diff --git a/Tests.Unit.Compiler.Types/DataTypeTests.cs b/Tests.Unit.Compiler.Types/DataTypeTests.cs new file mode 100644 index 00000000..9746dfde --- /dev/null +++ b/Tests.Unit.Compiler.Types/DataTypeTests.cs @@ -0,0 +1,18 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class DataTypeTests + { + [Fact] + public void None_type_is_optional_never() + { + var none = DataType.None; + + Assert.OfType(none); + Assert.Equal(NeverType.Instance, none.Referent); + } + } +} diff --git a/Tests.Unit.Compiler.Types/GlobalSuppressions.cs b/Tests.Unit.Compiler.Types/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Unit.Compiler.Types/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Unit.Compiler.Types/IntegerConstantTypeTests.cs b/Tests.Unit.Compiler.Types/IntegerConstantTypeTests.cs new file mode 100644 index 00000000..548116e8 --- /dev/null +++ b/Tests.Unit.Compiler.Types/IntegerConstantTypeTests.cs @@ -0,0 +1,92 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class IntegerConstantTypeTests + { + [Fact] + public void Is_integer_numeric_simple_value_type() + { + var type = new IntegerConstantType(1); + + Assert.OfType(type); + Assert.OfType(type); + Assert.OfType(type); + Assert.OfType(type); + } + + [Fact] + public void Is_constant() + { + var type = new IntegerConstantType(1); + + Assert.True(type.IsConstant); + } + + [Fact] + public void Is_known_type() + { + var type = new IntegerConstantType(1); + + Assert.True(type.IsKnown); + } + + [Fact] + public void Is_not_empty_type() + { + var type = new IntegerConstantType(1); + + Assert.False(type.IsEmpty); + } + + [Fact] + public void Has_copy_semantics() + { + var type = new IntegerConstantType(1); + + Assert.Equal(TypeSemantics.Copy, type.Semantics); + } + + [Theory] + [InlineData(-23)] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(42)] + [InlineData(234_324_234_325)] + public void Has_integer_value(long value) + { + var type = new IntegerConstantType(value); + + Assert.Equal(value, type.Value); + } + + [Fact] + public void Converts_to_non_constant_int_type() + { + // TODO larger values need to convert to larger integer types + + var type = new IntegerConstantType(42); + + var nonConstant = type.ToNonConstantType(); + + Assert.Same(DataType.Int, nonConstant); + } + + [Theory] + [InlineData(-234234)] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(234234524)] + public void Integer_constant_types_with_same_value_are_equal(int value) + { + var type1 = new IntegerConstantType(value); + var type2 = new IntegerConstantType(value); + + Assert.Equal(type1, type2); + } + } +} diff --git a/Tests.Unit.Compiler.Types/NeverTypeTests.cs b/Tests.Unit.Compiler.Types/NeverTypeTests.cs new file mode 100644 index 00000000..aa7798cc --- /dev/null +++ b/Tests.Unit.Compiler.Types/NeverTypeTests.cs @@ -0,0 +1,74 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class NeverTypeTests + { + [Fact] + public void Is_empty_data_type() + { + var type = NeverType.Instance; + + Assert.OfType(type); + } + + [Fact] + public void Is_known_type() + { + var type = NeverType.Instance; + + Assert.True(type.IsKnown); + } + + [Fact] + public void Is_empty_type() + { + var type = NeverType.Instance; + + Assert.True(type.IsEmpty); + } + + [Fact] + public void Never_has_never_semantics() + { + var type = NeverType.Instance; + + Assert.Equal(TypeSemantics.Never, type.Semantics); + } + + [Fact] + public void Has_special_name_never() + { + var type = NeverType.Instance; + + Assert.Equal(SpecialTypeName.Never, type.Name); + } + + [Fact] + public void Has_proper_ToString() + { + var type = NeverType.Instance; + + Assert.Equal("never", type.ToString()); + } + + [Fact] + public void Convert_to_read_only_has_no_effect() + { + var type = NeverType.Instance; + + var @readonly = type.ToReadOnly(); + + Assert.Equal(type, @readonly); + } + + [Fact] + public void Is_equal_to_itself() + { + Assert.Equal(NeverType.Instance, NeverType.Instance); + } + } +} diff --git a/Tests.Unit.Compiler.Types/ObjectTypeTests.cs b/Tests.Unit.Compiler.Types/ObjectTypeTests.cs new file mode 100644 index 00000000..afeb3b88 --- /dev/null +++ b/Tests.Unit.Compiler.Types/ObjectTypeTests.cs @@ -0,0 +1,37 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; +using static Azoth.Tools.Bootstrap.Compiler.Types.ReferenceCapability; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class ObjectTypeTests + { + [Fact] + public void Has_reference_semantics() + { + var type = new ObjectType("Foo", "Bar", true, Isolated); + + Assert.Equal(TypeSemantics.Reference, type.Semantics); + } + + [Fact] + public void Convert_to_non_constant_type_is_same_type() + { + var type = new ObjectType("Foo", "Bar", true, Isolated); + + var nonConstant = type.ToNonConstantType(); + + Assert.Same(type, nonConstant); + } + + [Fact] + public void With_same_name_mutability_and_reference_capability_are_equal() + { + var type1 = new ObjectType("Foo", "Bar", true, Isolated); + var type2 = new ObjectType("Foo", "Bar", true, Isolated); + + Assert.Equal(type1, type2); + } + } +} diff --git a/Tests.Unit.Compiler.Types/OptionalTypeTests.cs b/Tests.Unit.Compiler.Types/OptionalTypeTests.cs new file mode 100644 index 00000000..48d818e5 --- /dev/null +++ b/Tests.Unit.Compiler.Types/OptionalTypeTests.cs @@ -0,0 +1,81 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; +using static Azoth.Tools.Bootstrap.Compiler.Types.ReferenceCapability; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class OptionalTypeTests + { + [Fact] + public void Optional_reference_has_reference_semantics() + { + var optionalAny = new OptionalType(new AnyType(Isolated)); + + Assert.Equal(TypeSemantics.Reference, optionalAny.Semantics); + } + + [Fact] + public void Optional_copy_type_has_copy_semantics() + { + var optionalBool = new OptionalType(DataType.Bool); + + Assert.Equal(TypeSemantics.Copy, optionalBool.Semantics); + } + + /// + /// The type `never?` has only one value, `none`. That value can be + /// freely copied into any optional type, hence `never?` has copy + /// semantics. + /// + [Fact] + public void Optional_never_type_has_copy_semantics() + { + var optionalNever = new OptionalType(DataType.Never); + + Assert.Equal(TypeSemantics.Copy, optionalNever.Semantics); + } + + /// + /// The type `⧼unknown⧽?` is assignable into any optional type. Note though + /// that it isn't assignable into non-optional types so it can't have + /// never semantics. We assume it has the most lenient semantics possible + /// for it, which is copy. + /// + [Fact] + public void Optional_unknown_type_has_copy_semantics() + { + var optionalNever = new OptionalType(DataType.Unknown); + + Assert.Equal(TypeSemantics.Copy, optionalNever.Semantics); + } + + [Fact(Skip = "There are no move types yet (and they can't be faked)")] + public void Optional_move_type_has_move_semantics() + { + throw new NotImplementedException(); + } + + [Fact] + public void Cannot_have_optional_void_type() + { + Assert.Throws(() => new OptionalType(DataType.Void)); + } + + [Fact] + public void With_equal_referent_are_equal() + { + var type1 = new OptionalType(new ObjectType(Namespace("foo", "bar"), "Baz", true, SharedMutable)); + var type2 = new OptionalType(new ObjectType(Namespace("foo", "bar"), "Baz", true, SharedMutable)); + + Assert.Equal(type1, type2); + } + + private static NamespaceName Namespace(params string[] segments) + { + return new NamespaceName(segments); + } + } +} diff --git a/Tests.Unit.Compiler.Types/ReferenceCapabilityAssignmentTestCase.cs b/Tests.Unit.Compiler.Types/ReferenceCapabilityAssignmentTestCase.cs new file mode 100644 index 00000000..fb15a335 --- /dev/null +++ b/Tests.Unit.Compiler.Types/ReferenceCapabilityAssignmentTestCase.cs @@ -0,0 +1,23 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + public class ReferenceCapabilityAssignmentTestCase + { + public ReferenceCapability From { get; } + public ReferenceCapability To { get; } + public bool Assignable { get; } + + public ReferenceCapabilityAssignmentTestCase(ReferenceCapability @from, ReferenceCapability to, bool assignable) + { + From = @from; + To = to; + Assignable = assignable; + } + + public override string ToString() + { + return Assignable ? $"{From} <: {To}" : $"{From} ≮: {To}"; + } + } +} diff --git a/Tests.Unit.Compiler.Types/ReferenceCapabilityTests.cs b/Tests.Unit.Compiler.Types/ReferenceCapabilityTests.cs new file mode 100644 index 00000000..f1dbb714 --- /dev/null +++ b/Tests.Unit.Compiler.Types/ReferenceCapabilityTests.cs @@ -0,0 +1,49 @@ +using Xunit; +using static Azoth.Tools.Bootstrap.Compiler.Types.ReferenceCapability; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class ReferenceCapabilityTests + { + [Theory] + [MemberData(nameof(AssignableFromData))] + public void AssignableFrom(ReferenceCapabilityAssignmentTestCase row) + { + var isAssignable = row.To.IsAssignableFrom(row.From); + + Assert.Equal(row.Assignable, isAssignable); + } + + public static TheoryData AssignableFromData() + { + var data = new TheoryData + { + // From something to the lent version + new ReferenceCapabilityAssignmentTestCase(Isolated, LentIsolated, true), + new ReferenceCapabilityAssignmentTestCase(Transition, LentTransition, true), + new ReferenceCapabilityAssignmentTestCase(SharedMutable, LentMutable, true), + new ReferenceCapabilityAssignmentTestCase(Const, LentConst, true), + new ReferenceCapabilityAssignmentTestCase(Shared, Lent, true), + + // Up the standard hierarchy + new ReferenceCapabilityAssignmentTestCase(Isolated, Transition, true), + new ReferenceCapabilityAssignmentTestCase(Transition, SharedMutable, true), + new ReferenceCapabilityAssignmentTestCase(Transition, Const, true), + new ReferenceCapabilityAssignmentTestCase(SharedMutable, Shared, true), + new ReferenceCapabilityAssignmentTestCase(Const, Shared, true), + new ReferenceCapabilityAssignmentTestCase(Shared, Identity, true), + + // Up the lent hierarchy + new ReferenceCapabilityAssignmentTestCase(LentIsolated, LentTransition, true), + new ReferenceCapabilityAssignmentTestCase(LentTransition, LentMutable, true), + new ReferenceCapabilityAssignmentTestCase(LentTransition, LentConst, true), + new ReferenceCapabilityAssignmentTestCase(LentMutable, Lent, true), + new ReferenceCapabilityAssignmentTestCase(LentConst, Lent, true), + new ReferenceCapabilityAssignmentTestCase(Lent, Identity, true), + }; + // All all transitive conversions? + return data; + } + } +} diff --git a/Tests.Unit.Compiler.Types/SizedIntegerTypeTests.cs b/Tests.Unit.Compiler.Types/SizedIntegerTypeTests.cs new file mode 100644 index 00000000..3574f0ea --- /dev/null +++ b/Tests.Unit.Compiler.Types/SizedIntegerTypeTests.cs @@ -0,0 +1,44 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class SizedIntegerTypeTests + { + [Fact] + public void Byte_has_8_bits() + { + var type = FixedSizeIntegerType.Byte; + + Assert.Equal(8, type.Bits); + } + + [Fact] + public void Byte_is_unsigned() + { + var type = FixedSizeIntegerType.Byte; + + Assert.False(type.IsSigned); + } + + [Fact] + public void Int_has_copy_semantics() + { + var type = FixedSizeIntegerType.Int; + + Assert.Equal(TypeSemantics.Copy, type.Semantics); + } + + [Fact] + public void Types_equal_to_themselves_and_not_others() + { + Assert.Equal(FixedSizeIntegerType.Int, FixedSizeIntegerType.Int); + Assert.Equal(FixedSizeIntegerType.UInt, FixedSizeIntegerType.UInt); + Assert.Equal(FixedSizeIntegerType.Byte, FixedSizeIntegerType.Byte); + + Assert.NotEqual(FixedSizeIntegerType.Int, FixedSizeIntegerType.UInt); + Assert.NotEqual(FixedSizeIntegerType.Int, FixedSizeIntegerType.Byte); + } + } +} diff --git a/Tests.Unit.Compiler.Types/Tests.Unit.Compiler.Types.csproj b/Tests.Unit.Compiler.Types/Tests.Unit.Compiler.Types.csproj new file mode 100644 index 00000000..5002f93e --- /dev/null +++ b/Tests.Unit.Compiler.Types/Tests.Unit.Compiler.Types.csproj @@ -0,0 +1,39 @@ + + + + netcoreapp3.0 + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types + Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types + enable + + + + TRACE;DEBUG + true + + + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Tests.Unit.Compiler.Types/UnknownTypeTests.cs b/Tests.Unit.Compiler.Types/UnknownTypeTests.cs new file mode 100644 index 00000000..68bc0ce0 --- /dev/null +++ b/Tests.Unit.Compiler.Types/UnknownTypeTests.cs @@ -0,0 +1,42 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class UnknownTypeTests + { + [Fact] + public void Unknown_is_NOT_a_known_type() + { + var type = UnknownType.Instance; + + Assert.False(type.IsKnown); + } + + [Fact] + public void Is_not_constant() + { + var type = UnknownType.Instance; + + Assert.False(type.IsConstant); + } + + /// + /// It has never semantics because it is assignable to anything + /// + [Fact] + public void Unknown_has_never_semantics() + { + var type = UnknownType.Instance; + + Assert.Equal(TypeSemantics.Never, type.Semantics); + } + + [Fact] + public void Equal_to_itself() + { + Assert.Equal(UnknownType.Instance, UnknownType.Instance); + } + } +} diff --git a/Tests.Unit.Compiler.Types/UnsizedIntegerTypeTests.cs b/Tests.Unit.Compiler.Types/UnsizedIntegerTypeTests.cs new file mode 100644 index 00000000..90344ead --- /dev/null +++ b/Tests.Unit.Compiler.Types/UnsizedIntegerTypeTests.cs @@ -0,0 +1,66 @@ +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class UnsizedIntegerTypeTests + { + [Fact] + public void Size_is_unsigned() + { + var type = PointerSizedIntegerType.Size; + + Assert.False(type.IsSigned); + } + + [Fact] + public void Size_is_known_type() + { + var type = PointerSizedIntegerType.Size; + + Assert.True(type.IsKnown); + } + + [Fact] + public void Size_has_copy_semantics() + { + var type = PointerSizedIntegerType.Size; + + Assert.Equal(TypeSemantics.Copy, type.Semantics); + } + + [Fact] + public void Offset_is_signed() + { + var type = PointerSizedIntegerType.Offset; + + Assert.True(type.IsSigned); + } + + [Fact] + public void Offset_is_known_type() + { + var type = PointerSizedIntegerType.Offset; + + Assert.True(type.IsKnown); + } + + [Fact] + public void Offset_has_copy_semantics() + { + var type = PointerSizedIntegerType.Offset; + + Assert.Equal(TypeSemantics.Copy, type.Semantics); + } + + [Fact] + public void Types_equal_to_themselves_and_not_others() + { + Assert.Equal(PointerSizedIntegerType.Size, PointerSizedIntegerType.Size); + Assert.Equal(PointerSizedIntegerType.Offset, PointerSizedIntegerType.Offset); + + Assert.NotEqual(PointerSizedIntegerType.Size, PointerSizedIntegerType.Offset); + } + } +} diff --git a/Tests.Unit.Compiler.Types/VoidTypeTests.cs b/Tests.Unit.Compiler.Types/VoidTypeTests.cs new file mode 100644 index 00000000..7e6feb26 --- /dev/null +++ b/Tests.Unit.Compiler.Types/VoidTypeTests.cs @@ -0,0 +1,75 @@ +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Compiler.Types +{ + [Trait("Category", "Types")] + public class VoidTypeTests + { + [Fact] + public void Is_empty_data_type() + { + var type = VoidType.Instance; + + Assert.OfType(type); + } + + [Fact] + public void Is_known_type() + { + var type = VoidType.Instance; + + Assert.True(type.IsKnown); + } + + [Fact] + public void Is_empty_type() + { + var type = VoidType.Instance; + + Assert.True(type.IsEmpty); + } + + [Fact] + public void Void_has_void_semantics() + { + var type = VoidType.Instance; + + Assert.Equal(TypeSemantics.Void, type.Semantics); + } + + [Fact] + public void Has_special_name_never() + { + var type = VoidType.Instance; + + Assert.Equal(SpecialTypeName.Void, type.Name); + } + + [Fact] + public void Has_proper_ToString() + { + var type = VoidType.Instance; + + Assert.Equal("void", type.ToString()); + } + + [Fact] + public void Convert_to_read_only_has_no_effect() + { + var type = NeverType.Instance; + + var @readonly = type.ToReadOnly(); + + Assert.Equal(type, @readonly); + } + + + [Fact] + public void Equal_to_itself() + { + Assert.Equal(NeverType.Instance, NeverType.Instance); + } + } +} diff --git a/Tests.Unit.Framework/EnumerableExtensionTests.cs b/Tests.Unit.Framework/EnumerableExtensionTests.cs new file mode 100644 index 00000000..13abc064 --- /dev/null +++ b/Tests.Unit.Framework/EnumerableExtensionTests.cs @@ -0,0 +1,30 @@ +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Framework +{ + [Trait("Category", "Framework")] + public class EnumerableExtensionTests + { + [Fact] + public void CrossJoinCreatesEveryCombination() + { + var items1 = new[] { 1, 2, 3 }; + var items2 = new[] { "1", "2", "3" }; + var expected = new[] + { + (1, "1"), + (1, "2"), + (1, "3"), + (2, "1"), + (2, "2"), + (2, "3"), + (3, "1"), + (3, "2"), + (3, "3"), + }.ToHashSet(); + Assert.Equal(expected, items1.CrossJoin(items2).ToHashSet()); + } + } +} diff --git a/Tests.Unit.Framework/FixedListTests.cs b/Tests.Unit.Framework/FixedListTests.cs new file mode 100644 index 00000000..01d26842 --- /dev/null +++ b/Tests.Unit.Framework/FixedListTests.cs @@ -0,0 +1,19 @@ +using Azoth.Tools.Bootstrap.Framework; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Framework +{ + [Trait("Category", "Framework")] + public class FixedListTests + { + [Fact] + public void Equal_fixed_lists_have_same_hash_code() + { + var l1 = new[] { "foo", "bar", "baz" }.ToFixedList(); + var l2 = new[] { "foo", "bar", "baz" }.ToFixedList(); + + Assert.Equal(l1, l2); + Assert.Equal(l1.GetHashCode(), l2.GetHashCode()); + } + } +} diff --git a/Tests.Unit.Framework/FixedSetTests.cs b/Tests.Unit.Framework/FixedSetTests.cs new file mode 100644 index 00000000..3c425fc7 --- /dev/null +++ b/Tests.Unit.Framework/FixedSetTests.cs @@ -0,0 +1,19 @@ +using Azoth.Tools.Bootstrap.Framework; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Framework +{ + [Trait("Category", "Framework")] + public class FixedSetTests + { + [Fact] + public void Equal_fixed_sets_have_same_hash_code() + { + var set1 = new[] { "foo", "bar", "baz" }.ToFixedSet(); + var set2 = new[] { "foo", "bar", "baz" }.ToFixedSet(); + + Assert.Equal(set1, set2); + Assert.Equal(set1.GetHashCode(), set2.GetHashCode()); + } + } +} diff --git a/Tests.Unit.Framework/GlobalSuppressions.cs b/Tests.Unit.Framework/GlobalSuppressions.cs new file mode 100644 index 00000000..95c3683c --- /dev/null +++ b/Tests.Unit.Framework/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Unit test naming")] diff --git a/Tests.Unit.Framework/Tests.Unit.Framework.csproj b/Tests.Unit.Framework/Tests.Unit.Framework.csproj new file mode 100644 index 00000000..ca6d47ae --- /dev/null +++ b/Tests.Unit.Framework/Tests.Unit.Framework.csproj @@ -0,0 +1,50 @@ + + + + netcoreapp3.0 + + false + + Azoth.Tools.Bootstrap.Tests.Unit.Framework + + Azoth.Tools.Bootstrap.Tests.Unit.Framework + + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + diff --git a/Tests.Unit.Framework/YieldTests.cs b/Tests.Unit.Framework/YieldTests.cs new file mode 100644 index 00000000..6396c08d --- /dev/null +++ b/Tests.Unit.Framework/YieldTests.cs @@ -0,0 +1,51 @@ +using Azoth.Tools.Bootstrap.Framework; +using Xunit; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Framework +{ + [Trait("Category", "Framework")] + public class YieldTests + { + [Fact] + public void YieldIsSingleItem() + { + var x = new object(); + Assert.Single(x.Yield(), x); + } + + [Fact] + public void YieldOfNullIsSingleItem() + { + object? x = null; + Assert.Single(x.Yield(), x); + } + + [Fact] + public void YieldValueOfClassIsSingleItem() + { + var x = new object(); + Assert.Single(x.YieldValue(), x); + } + + [Fact] + public void YieldValueOfNullClassIsEmpty() + { + object? x = null; + Assert.Empty(x.YieldValue()); + } + + [Fact] + public void YieldValueOfValueTypeIsSingleItem() + { + int? x = 42; + Assert.Single(x.YieldValue(), x); + } + + [Fact] + public void YieldValueOfNullValueTypeIsEmpty() + { + int? x = null; + Assert.Empty(x.YieldValue()); + } + } +} diff --git a/Tests.Unit/CSharpNameResolutionTests.cs b/Tests.Unit/CSharpNameResolutionTests.cs new file mode 100644 index 00000000..1bdc5a3d --- /dev/null +++ b/Tests.Unit/CSharpNameResolutionTests.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Azoth.Tools.Bootstrap.Tests.Unit +{ + namespace OuterNamespace + { + public class TestClass1 { } + public class TestClass1 { } + + namespace InnerNamespace + { + public class TestClass1 + { + } + + public static class Tester + { + [SuppressMessage("Style", "IDE0059:Unnecessary assignment of a value", Justification = "")] + public static void TestMethod() + { + TestClass1 t1 = new TestClass1(); + TestClass1 t2 = new TestClass1(); + } + } + } + } +} diff --git a/Tests.Unit/DotNetFrameworkTests.cs b/Tests.Unit/DotNetFrameworkTests.cs new file mode 100644 index 00000000..4ad4b1bd --- /dev/null +++ b/Tests.Unit/DotNetFrameworkTests.cs @@ -0,0 +1,125 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Azoth.Tools.Bootstrap.Tests.Unit +{ + [Trait("Category", "Characterization")] + public class DotNetFrameworkTests + { + private readonly ITestOutputHelper output; + + public DotNetFrameworkTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void ClosureTypes() + { + var lambda1 = GetMethodInfo(x => 1); + var lambda2 = GetMethodInfo(x => 2); + output.WriteLine(lambda1.Name); + + output.WriteLine(lambda2.Name); + Assert.NotEqual(lambda1, lambda2); + } + + private static MethodInfo GetMethodInfo(Func f) + { + return f.Method; + } + + [Fact] + public void LazyCycleThrowsInvalidOperation() + { + Lazy? l2 = null; + var l1 = new Lazy(() => l2!.Value); + l2 = new Lazy(() => l1.Value); + Assert.Throws(() => l1.Value); + } + + [Fact] + public async Task TaskSelfReferenceDeadlocks() + { + var task = Task.FromResult(1); + task = GetValueAsync(() => task); + + var delayTask = Task.Delay((int)TimeSpan.FromSeconds(2).TotalMilliseconds); + var completedTask = await Task.WhenAny(task, delayTask).ConfigureAwait(false); + Assert.Equal(delayTask, completedTask); + } + + [Fact] + public async Task TaskCycleDeadlocks() + { + var t2 = Task.FromResult(2); + var t1 = GetValueAsync(() => t2); + t2 = GetValueAsync(() => t1); + + var delayTask = Task.Delay((int)TimeSpan.FromSeconds(2).TotalMilliseconds); + var completedTask = await Task.WhenAny(t1, delayTask).ConfigureAwait(false); + Assert.Equal(delayTask, completedTask); + } + + private static async Task GetValueAsync(Func> task) + { + await Task.Yield(); + return await task().ConfigureAwait(false); + } + + private string testField = "old"; + + [Fact] + public void CanPassFieldByRef() + { + TakeByRef(ref testField); + } + + private static void TakeByRef(ref string x) + { + x = "new"; + } + + [Fact] + public void CanPassArrayElementByRef() + { + var strings = new string[] { "old1", "old2" }; + TakeByRef(ref strings[0]); + } + + [Fact] + public void BecomeNonNull() + { + var x = new Ref("Hello!"); + + var z = x.HasValue(); + Assert.Equal("Hello!", z.Value); + } + } + + public class Ref + where T : class? + { + public T Value { get; } + + public Ref(T value) + { + Value = value; + } + + } + + public static class RefExtensions + { + public static Ref HasValue(this Ref value) + where T : class + { + if (value.Value is null) + throw new InvalidOperationException(); + return value!; + } + } +} diff --git a/Tests.Unit/Fakes/FakeCodeFile.cs b/Tests.Unit/Fakes/FakeCodeFile.cs new file mode 100644 index 00000000..fe21a511 --- /dev/null +++ b/Tests.Unit/Fakes/FakeCodeFile.cs @@ -0,0 +1,13 @@ +using Azoth.Tools.Bootstrap.Compiler.Core; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Fakes +{ + public static class FakeCodeFile + { + + public static CodeFile For(string text) + { + return new CodeFile(FakeCodeReference.Instance, new CodeText(text)); + } + } +} diff --git a/Tests.Unit/Fakes/FakeCodeReference.cs b/Tests.Unit/Fakes/FakeCodeReference.cs new file mode 100644 index 00000000..7eef182a --- /dev/null +++ b/Tests.Unit/Fakes/FakeCodeReference.cs @@ -0,0 +1,23 @@ +using System; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Fakes +{ + internal class FakeCodeReference : CodeReference + { + #region Singleton + + public static readonly FakeCodeReference Instance = new FakeCodeReference(); + + private FakeCodeReference() + : base(FixedList.Empty) + { } + #endregion + + public override string ToString() + { + throw new InvalidOperationException("Fake doesn't support ToString()"); + } + } +} diff --git a/Tests.Unit/Helpers/GeneratorExtensions.cs b/Tests.Unit/Helpers/GeneratorExtensions.cs new file mode 100644 index 00000000..627d49e3 --- /dev/null +++ b/Tests.Unit/Helpers/GeneratorExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.CompilerServices; +using FsCheck; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Helpers +{ + public static class GeneratorExtensions + { + public static WeightAndValue> WithWeight( + this Gen generator, + int weight) + { + return new WeightAndValue>(weight, generator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + public static T NotNull(this NonNull value, string paramName) + where T : class + { + return (value ?? throw new ArgumentNullException(paramName)).Get; + } + } +} diff --git a/Tests.Unit/Helpers/SolutionDirectory.cs b/Tests.Unit/Helpers/SolutionDirectory.cs new file mode 100644 index 00000000..601a9a19 --- /dev/null +++ b/Tests.Unit/Helpers/SolutionDirectory.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using System.Linq; + +namespace Azoth.Tools.Bootstrap.Tests.Unit.Helpers +{ + public static class SolutionDirectory + { + public static string Get() + { + var directory = Directory.GetCurrentDirectory() ?? throw new InvalidOperationException("Could not get current directory"); + while (directory != null && !Directory.GetFiles(directory, "*.sln", SearchOption.TopDirectoryOnly).Any()) + { + directory = Path.GetDirectoryName(directory) ?? throw new InvalidOperationException("Null directory name"); + } + return directory ?? throw new InvalidOperationException("Compiler is confused, this can't be null"); + } + } +} diff --git a/Tests.Unit/README.md b/Tests.Unit/README.md new file mode 100644 index 00000000..e1694a90 --- /dev/null +++ b/Tests.Unit/README.md @@ -0,0 +1,3 @@ +# Azoth.Tools.Bootstrap.Tests.Unit + +This is a place for code to make writing tests easier. diff --git a/Tests.Unit/SymbolTestFixture.cs b/Tests.Unit/SymbolTestFixture.cs new file mode 100644 index 00000000..bc6a4165 --- /dev/null +++ b/Tests.Unit/SymbolTestFixture.cs @@ -0,0 +1,163 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Compiler.Symbols; +using Azoth.Tools.Bootstrap.Compiler.Types; +using Azoth.Tools.Bootstrap.Framework; + +namespace Azoth.Tools.Bootstrap.Tests.Unit +{ + public abstract class SymbolTestFixture + { + private int unique; + + protected NamespaceSymbol Namespace(string? name = null, NamespaceOrPackageSymbol? ns = null) + { + return new NamespaceSymbol(ns ?? Package(), Name(name) ?? DefaultName("namespace")); + } + + protected PackageSymbol Package(string? name = null) + { + return new PackageSymbol(Name(name) ?? DefaultName("package")); + } + + protected Name DefaultName(string prefix) + { + return new Name($"⧫{prefix}_{++unique}"); + } + + [return: NotNullIfNotNull("name")] + protected static Name? Name(string? name = null) + { + return name is null ? null : new Name(name); + } + + protected FixedList Params(int? count = null) + { + return Enumerable.Range(1, count ?? ++unique).Select(n => Compiler.Types.DataType.Int).ToFixedList(); + } + + protected static FixedList Params(DataType param, params DataType[] @params) + { + return @params.Prepend(param).ToFixedList(); + } + + protected FunctionSymbol Func(string? name = null, NamespaceOrPackageSymbol? ns = null, FixedList? @params = null, DataType? @return = null) + { + return new FunctionSymbol( + ns ?? Namespace(), + Name(name) ?? DefaultName("func"), + @params ?? Params(), + @return ?? DataType()); + } + + protected static FunctionSymbol Func( + FunctionSymbol mother, + string? name = null, + NamespaceOrPackageSymbol? ns = null, + FixedList? @params = null, + DataType? @return = null) + { + return new FunctionSymbol( + ns ?? mother.ContainingSymbol, + Name(name) ?? mother.Name, + @params ?? mother.ParameterDataTypes, + @return ?? mother.ReturnDataType); + } + + protected MethodSymbol Method( + string? name = null, + ObjectTypeSymbol? containing = null, + DataType? self = null, + FixedList? @params = null, + DataType? @return = null) + { + containing ??= Type(); + return new MethodSymbol( + containing, + Name(name) ?? DefaultName("method"), + self ?? containing.DeclaresDataType, + @params ?? Params(), + @return ?? DataType()); + } + + protected static MethodSymbol Method( + MethodSymbol mother, + string? name = null, + ObjectTypeSymbol? containing = null, + DataType? self = null, + FixedList? @params = null, + DataType? @return = null) + { + return new MethodSymbol( + containing ?? mother.ContainingSymbol, + Name(name) ?? mother.Name, + self ?? mother.SelfDataType, + @params ?? mother.ParameterDataTypes, + @return ?? mother.ReturnDataType); + } + + protected ObjectType DataType( + string? name = null, + NamespaceName? containingNamespace = null, + bool? declaredMutable = null, + ReferenceCapability? referenceCapability = null) + { + var finalName = Name(name) ?? DefaultName("DataType"); + return new ObjectType( + containingNamespace ?? NamespaceName.Global, + finalName.Text, + declaredMutable ?? false, + referenceCapability ?? ReferenceCapability.Isolated); + } + + protected ObjectTypeSymbol Type( + NamespaceOrPackageSymbol? ns = null, + ObjectType? dataType = null) + { + return new ObjectTypeSymbol( + ns ?? Package(), + dataType ?? DataType()); + } + + protected VariableSymbol Variable( + string? name = null, + InvocableSymbol? containing = null, + int? declaration = null, + bool? mut = null, + DataType? type = null) + { + return new VariableSymbol( + containing ?? Func(), + Name(name) ?? DefaultName("variable"), + declaration ?? ++unique, + mut ?? true, + type ?? DataType()); + } + + protected static VariableSymbol Variable( + VariableSymbol mother, + string? name = null, + InvocableSymbol? containing = null, + int? declaration = null, + bool? mut = null, + DataType? type = null) + { + return new VariableSymbol( + containing ?? mother.ContainingSymbol, + Name(name) ?? mother.Name, + declaration ?? mother.DeclarationNumber, + mut ?? mother.IsMutableBinding, + type ?? mother.DataType); + } + + protected SelfParameterSymbol SelfParam( + InvocableSymbol? containing = null, + DataType? type = null) + { + return new SelfParameterSymbol( + containing ?? Method(), + type ?? DataType()); + } + } +} diff --git a/Tests.Unit/Tests.Unit.csproj b/Tests.Unit/Tests.Unit.csproj new file mode 100644 index 00000000..8b104156 --- /dev/null +++ b/Tests.Unit/Tests.Unit.csproj @@ -0,0 +1,56 @@ + + + + netcoreapp3.0 + + false + + Azoth.Tools.Bootstrap.Tests.Unit + + Azoth.Tools.Bootstrap.Tests.Unit + + 8.0 + enable + + + + DEBUG;TRACE + true + + + + + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + ..\..\..\..\Users\jwalker\.nuget\packages\fscheck\2.11.0\lib\netstandard1.6\FsCheck.dll + + + + diff --git a/azlab/Build/Project.cs b/azlab/Build/Project.cs new file mode 100644 index 00000000..5453b193 --- /dev/null +++ b/azlab/Build/Project.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azoth.Tools.Bootstrap.Framework; +using Azoth.Tools.Bootstrap.Lab.Config; + +namespace Azoth.Tools.Bootstrap.Lab.Build +{ + internal class Project + { + public string Path { get; } + public FixedList RootNamespace { get; } + public string Name { get; } + public FixedList Authors { get; } + public ProjectTemplate Template { get; } + public FixedList References { get; } + + public Project( + ProjectConfig file, + IEnumerable references) + { + Path = System.IO.Path.GetDirectoryName(file.FullPath) ?? throw new InvalidOperationException("Null directory name"); + Name = file.Name ?? throw new InvalidOperationException(); + RootNamespace = (file.RootNamespace ?? "").SplitOrEmpty('.'); + Authors = (file.Authors ?? throw new InvalidOperationException()) + .Select(a => a ?? throw new InvalidOperationException()).ToFixedList(); + Template = file.Template ?? throw new InvalidOperationException(); + References = references.ToFixedList(); + } + } +} diff --git a/azlab/Build/ProjectReference.cs b/azlab/Build/ProjectReference.cs new file mode 100644 index 00000000..4cf7f344 --- /dev/null +++ b/azlab/Build/ProjectReference.cs @@ -0,0 +1,16 @@ +namespace Azoth.Tools.Bootstrap.Lab.Build +{ + internal class ProjectReference + { + public string Name { get; } + public Project Project { get; } + public bool Trusted { get; } + + public ProjectReference(string name, Project project, bool trusted) + { + Name = name; + Project = project; + Trusted = trusted; + } + } +} diff --git a/azlab/Build/ProjectSet.cs b/azlab/Build/ProjectSet.cs new file mode 100644 index 00000000..c27225c5 --- /dev/null +++ b/azlab/Build/ProjectSet.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Azoth.Tools.Bootstrap.Compiler.API; +using Azoth.Tools.Bootstrap.Compiler.Core; +using Azoth.Tools.Bootstrap.Compiler.Emit.C; +using Azoth.Tools.Bootstrap.Compiler.IntermediateLanguage; +using Azoth.Tools.Bootstrap.Compiler.Names; +using Azoth.Tools.Bootstrap.Framework; +using Azoth.Tools.Bootstrap.Lab.Config; +using ExhaustiveMatching; + +namespace Azoth.Tools.Bootstrap.Lab.Build +{ + /// + /// Represents and determines an order to build packages in + /// + internal class ProjectSet : IEnumerable + { + private readonly Dictionary projects = new Dictionary(); + + public void AddAll(ProjectConfigSet configs) + { + foreach (var config in configs) + GetOrAdd(config, configs); + } + + private Project GetOrAdd(ProjectConfig config, ProjectConfigSet configs) + { + var projectDir = Path.GetDirectoryName(config.FullPath) ?? throw new InvalidOperationException("Null directory name"); + if (projects.TryGetValue(projectDir, out var existingProject)) + return existingProject; + + // Add a placeholder to prevent cycles (safe because we will replace it below + projects.Add(projectDir, null!); + var dependencies = config.Dependencies.Select(d => + { + var (name, config) = d; + var dependencyConfig = configs[name]; + var dependencyProject = GetOrAdd(dependencyConfig, configs); + return new ProjectReference(name, dependencyProject, config?.Trusted ?? throw new InvalidOperationException()); + }).ToList(); + + var project = new Project(config, dependencies); + projects[projectDir] = project; + return project; + } + + public IEnumerator GetEnumerator() + { + return projects.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public async Task Build(TaskScheduler taskScheduler, bool verbose) + { + _ = verbose; // verbose parameter will be needed in the future + var taskFactory = new TaskFactory(taskScheduler); + var projectBuilds = new Dictionary>(); + + var projectBuildsSource = new TaskCompletionSource>>(); + var projectBuildsTask = projectBuildsSource.Task; + + // Sort projects to detect cycles and so we can assume the tasks already exist + var sortedProjects = TopologicalSort(); + var compiler = new AzothCompiler(); + var consoleLock = new object(); + foreach (var project in sortedProjects) + { +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler (created with task factory built with task scheduler) + var buildTask = taskFactory.StartNew(() => + Build(compiler, project, projectBuildsTask, consoleLock)) +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler + .Unwrap(); // Needed because StartNew doesn't work intuitively with Async methods + if (!projectBuilds.TryAdd(project, buildTask)) + throw new Exception("Project added to build set twice"); + } + projectBuildsSource.SetResult(projectBuilds.ToFixedDictionary()); + + await Task.WhenAll(projectBuilds.Values).ConfigureAwait(false); + } + + private static async Task Build( + AzothCompiler compiler, + Project project, + Task>> projectBuildsTask, + object consoleLock) + { + var projectBuilds = await projectBuildsTask.ConfigureAwait(false); + var sourceDir = Path.Combine(project.Path, "src"); + var sourcePaths = Directory.EnumerateFiles(sourceDir, "*.ad", SearchOption.AllDirectories); + // Wait for the references, unfortunately, this requires an ugly loop. + var referenceTasks = project.References.ToDictionary(r => r.Name, r => projectBuilds[r.Project]); + var references = new Dictionary(); + foreach (var referenceTask in referenceTasks) + { + var package = await referenceTask.Value.ConfigureAwait(false); + if (!(package is null)) + references.Add(referenceTask.Key, package); + } + + lock (consoleLock) + { + Console.WriteLine($@"Compiling {project.Name} ({project.Path})..."); + } + var codeFiles = sourcePaths.Select(p => LoadCode(p, sourceDir, project.RootNamespace)).ToList(); + try + { + var package = compiler.CompilePackage(project.Name, codeFiles, references.ToFixedDictionary()); + // TODO switch to the async version of the compiler + //var codeFiles = sourcePaths.Select(p => new CodePath(p)).ToList(); + //var references = project.References.ToDictionary(r => r.Name, r => projectBuilds[r.Project]); + //var package = await compiler.CompilePackageAsync(project.Name, codeFiles, references); + + if (OutputDiagnostics(project, package.Diagnostics, consoleLock)) + return package; + + var cacheDir = PrepareCacheDir(project); + var codePath = EmitCode(project, package, cacheDir); + var success = CompileCode(project, cacheDir, codePath, consoleLock); + + lock (consoleLock) + { + Console.WriteLine(success + ? $"Build SUCCEEDED {project.Name} ({project.Path})" + : $"Build FAILED {project.Name} ({project.Path})"); + } + + return package; + } + catch (FatalCompilationErrorException ex) + { + OutputDiagnostics(project, ex.Diagnostics, consoleLock); + return null; + } + } + + private static CodeFile LoadCode( + string path, + string sourceDir, + FixedList rootNamespace) + { + var relativeDirectory = Path.GetDirectoryName(Path.GetRelativePath(sourceDir, path)) ?? throw new InvalidOperationException("Null directory name"); + var ns = rootNamespace.Concat(relativeDirectory.SplitOrEmpty(Path.DirectorySeparatorChar)).ToFixedList(); + return CodeFile.Load(path, ns); + } + + private static string PrepareCacheDir(Project project) + { + var cacheDir = Path.Combine(project.Path, ".forge-cache"); + Directory.CreateDirectory(cacheDir); // Ensure the cache directory exists + + // Clear the cache directory? + var dir = new DirectoryInfo(cacheDir); + foreach (var file in dir.EnumerateFiles()) + file.Delete(); + foreach (var subDirectory in dir.EnumerateDirectories()) + subDirectory.Delete(true); + + return cacheDir; + } + + private static bool OutputDiagnostics(Project project, FixedList diagnostics, object consoleLock) + { + if (!diagnostics.Any()) + return false; + lock (consoleLock) + { + Console.WriteLine($@"Build FAILED {project.Name} ({project.Path})"); + foreach (var group in diagnostics.GroupBy(d => d.File)) + { + var fileDiagnostics = @group.ToList(); + foreach (var diagnostic in fileDiagnostics.Take(10)) + { + Console.WriteLine( + $@"{diagnostic.File.Reference}:{diagnostic.StartPosition.Line}:{diagnostic.StartPosition.Column} {diagnostic.Level} {diagnostic.ErrorCode}"); + Console.WriteLine(@" " + diagnostic.Message); + } + + if (fileDiagnostics.Count > 10) + { + Console.WriteLine($"{@group.Key.Reference}"); + Console.WriteLine( + $" {fileDiagnostics.Skip(10).Count(d => d.Level >= DiagnosticLevel.CompilationError)} more errors not shown."); + Console.WriteLine( + $" {fileDiagnostics.Skip(10).Count(d => d.Level == DiagnosticLevel.Warning)} more warnings not shown."); + } + } + } + + return true; + } + + private static string EmitCode(Project project, PackageIL package, string cacheDir) + { + var emittedPackages = new HashSet(); + var packagesToEmit = new Queue(); + packagesToEmit.Enqueue(package); + + var codeEmitter = new CodeEmitter(); + while (packagesToEmit.TryDequeue(out var currentPackage)) + { + if (!emittedPackages.Contains(currentPackage)) + { + codeEmitter.Emit(currentPackage); + emittedPackages.Add(currentPackage); + packagesToEmit.EnqueueRange(currentPackage.References); + } + } + + string outputPath; + switch (project.Template) + { + case ProjectTemplate.App: + { + outputPath = Path.Combine(cacheDir, "program.c"); + } + break; + case ProjectTemplate.Lib: + { + outputPath = Path.Combine(cacheDir, "lib.c"); + } + break; + default: + throw ExhaustiveMatch.Failed(project.Template); + } + + File.WriteAllText(outputPath, codeEmitter.GetEmittedCode(), Encoding.UTF8); + + return outputPath; + } + + private static bool CompileCode( + Project project, + string cacheDir, + string codePath, + object consoleLock) + { + var compiler = new CLangCompiler(); + + var runtimeLibrarySourcePath = System.IO.Path.Combine(cacheDir, CodeEmitter.RuntimeLibraryCodeFileName); + File.WriteAllText(runtimeLibrarySourcePath, CodeEmitter.RuntimeLibraryCode, Encoding.UTF8); + var runtimeLibraryHeaderPath = System.IO.Path.Combine(cacheDir, CodeEmitter.RuntimeLibraryHeaderFileName); + File.WriteAllText(runtimeLibraryHeaderPath, CodeEmitter.RuntimeLibraryHeader, Encoding.UTF8); + + var sourceFiles = new[] { codePath, runtimeLibrarySourcePath }; + var headerSearchPaths = new[] { cacheDir }; + string outputPath = project.Template switch + { + ProjectTemplate.App => Path.ChangeExtension(codePath, "exe"), + ProjectTemplate.Lib => Path.ChangeExtension(codePath, "dll"), + _ => throw ExhaustiveMatch.Failed(project.Template) + }; + + lock (consoleLock) + { + Console.WriteLine($"CLang Compiling {project.Name} ({project.Path})..."); + var exitCode = compiler.Compile(ConsoleCompilerOutput.Instance, sourceFiles, + headerSearchPaths, outputPath); + + return exitCode == 0; + } + } + + private List TopologicalSort() + { + var projectAlive = projects.Values.ToDictionary(p => p, p => SortState.Alive); + var sorted = new List(projects.Count); + foreach (var project in projects.Values) + TopologicalSortVisit(project, projectAlive, sorted); + + return sorted; + } + + private static void TopologicalSortVisit(Project project, Dictionary state, List sorted) + { + switch (state[project]) + { + case SortState.Dead: // Already visited + return; + + case SortState.Undead:// Cycle + throw new Exception("Dependency Cycle"); + + case SortState.Alive: + state[project] = SortState.Undead; + foreach (var referencedProject in project.References.Select(r => r.Project)) + TopologicalSortVisit(referencedProject, state, sorted); + state[project] = SortState.Dead; + sorted.Add(project); + return; + + default: + throw ExhaustiveMatch.Failed(state[project]); + } + } + + private enum SortState + { + Alive, + Undead, + Dead, + } + } +} diff --git a/azlab/CommandOptionExtensions.cs b/azlab/CommandOptionExtensions.cs new file mode 100644 index 00000000..2420f0ff --- /dev/null +++ b/azlab/CommandOptionExtensions.cs @@ -0,0 +1,24 @@ +using McMaster.Extensions.CommandLineUtils; + +namespace Azoth.Tools.Bootstrap.Lab +{ + public static class CommandOptionExtensions + { + public static T? OptionalValue(this CommandOption option) + where T : struct + { + // Trying to use just `default` leads to 0 for int + return option.HasValue() ? option.ParsedValue : default(T?); + } + + /// For cases are supported + /// * "--option" returns true + /// * "--option=true" returns true + /// * "--option=false" returns false + /// * "" returns null + public static bool? OptionalValue(this CommandOption option) + { + return option.HasValue() ? (option.Value() is null || option.ParsedValue) : default(bool?); + } + } +} diff --git a/azlab/Config/ProjectConfig.cs b/azlab/Config/ProjectConfig.cs new file mode 100644 index 00000000..20f9eb18 --- /dev/null +++ b/azlab/Config/ProjectConfig.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Newtonsoft.Json; + +namespace Azoth.Tools.Bootstrap.Lab.Config +{ + [SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes", + Justification = "Instantiated by JSON converter")] + internal class ProjectConfig + { + public const string FileName = "azlab-project.vson"; + + [JsonIgnore] + public string? FullPath { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("root_namespace")] + public string? RootNamespace { get; set; } + + [JsonProperty("authors")] + public List? Authors { get; set; } + + [JsonProperty("template")] + public ProjectTemplate? Template { get; set; } + + [JsonProperty("dependencies")] + public Dictionary? Dependencies { get; set; } = new Dictionary(); + + public static ProjectConfig Load(string path) + { + var extension = Path.GetExtension(path); + string projectFilePath; + if (Directory.Exists(path)) + { + projectFilePath = Path.Combine(path, FileName); + } + else if (extension == "vson") + { + projectFilePath = path; + } + else + { + throw new Exception($"Unexpected project file extension '.{extension}'"); + } + + projectFilePath = Path.GetFullPath(projectFilePath); + + using var file = new JsonTextReader(File.OpenText(projectFilePath)); + var serializer = new JsonSerializer(); + var projectFile = serializer.Deserialize(file) ?? throw new NullReferenceException(); + projectFile.FullPath = projectFilePath; + return projectFile; + } + } +} diff --git a/azlab/Config/ProjectConfigSet.cs b/azlab/Config/ProjectConfigSet.cs new file mode 100644 index 00000000..5e8b0e3e --- /dev/null +++ b/azlab/Config/ProjectConfigSet.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Azoth.Tools.Bootstrap.Lab.Config +{ + /// + /// A set of project configs. Note that this assumes the name used to refer to a project + /// matches the name in the project. + /// + internal class ProjectConfigSet : IEnumerable + { + private readonly Dictionary configs = new Dictionary(); + + public ProjectConfig Load(string packagePath) + { + var config = ProjectConfig.Load(packagePath); + if (configs.TryGetValue(config.FullPath ?? throw new InvalidOperationException(), out var existingConfig)) + return existingConfig; + + configs.Add(config.Name ?? throw new InvalidOperationException(), config); + foreach (var dependency in config.Dependencies ?? throw new InvalidOperationException()) + Load(dependency.Value?.Path ?? throw new InvalidOperationException()); + + return config; + } + + public ProjectConfig this[string name] => configs[name]; + + public IEnumerator GetEnumerator() + { + return configs.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/azlab/Config/ProjectDependencyConfig.cs b/azlab/Config/ProjectDependencyConfig.cs new file mode 100644 index 00000000..f85d907d --- /dev/null +++ b/azlab/Config/ProjectDependencyConfig.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Azoth.Tools.Bootstrap.Lab.Config +{ + public class ProjectDependencyConfig + { + [JsonProperty("path")] + public string? Path { get; set; } + + [JsonProperty("trusted")] + public bool? Trusted { get; set; } + } +} diff --git a/azlab/Config/ProjectTemplate.cs b/azlab/Config/ProjectTemplate.cs new file mode 100644 index 00000000..a42be9bb --- /dev/null +++ b/azlab/Config/ProjectTemplate.cs @@ -0,0 +1,9 @@ +namespace Azoth.Tools.Bootstrap.Lab.Config +{ + internal enum ProjectTemplate + { + App, + Lib, + //Web, // TODO add web template + } +} diff --git a/azlab/Program.cs b/azlab/Program.cs new file mode 100644 index 00000000..7d9372d3 --- /dev/null +++ b/azlab/Program.cs @@ -0,0 +1,88 @@ +using System.Threading.Tasks; +using Azoth.Tools.Bootstrap.Lab.Build; +using Azoth.Tools.Bootstrap.Lab.Config; +using McMaster.Extensions.CommandLineUtils; + +namespace Azoth.Tools.Bootstrap.Lab +{ + public static class Program + { +#if DEBUG + public const bool DefaultAllowParallel = false; +#else + public const bool DefaultAllowParallel = true; +#endif + + public static int Main(string[] args) + { + using var app = new CommandLineApplication() + { + Name = "azlab", + Description = "Azoth's package manager and build tool" + }; + + app.HelpOption(); + + var allowParallelOption = app.Option("--allow-parallel", "Allow parallel processing", CommandOptionType.SingleOrNoValue, true); + var maxConcurrencyOption = app.Option("--max-concurrency", + "The max number of tasks to run concurrently", + CommandOptionType.SingleValue, true); + + + app.Command("build", cmd => + { + cmd.Description = "Compile a package and all of its dependencies"; + var packageOption = cmd.Option("-p|--package ", + "Package to build", CommandOptionType.SingleValue); + var verboseOption = cmd.Option("-v|--verbose", "Use verbose output", + CommandOptionType.NoValue); + cmd.OnExecuteAsync(async (cancellationToken) => + { + var allowParallel = allowParallelOption.OptionalValue() ?? DefaultAllowParallel; + var maxConcurrency = maxConcurrencyOption.OptionalValue(); + var verbose = verboseOption.HasValue(); + var packagePath = packageOption.Value() ?? "."; + + await BuildAsync(packagePath, verbose, allowParallel, maxConcurrency).ConfigureAwait(false); + }); + }); + + app.OnExecute(() => + { + app.ShowHelp(); + return 1; + }); + + return app.Execute(args); + } + + private static Task BuildAsync( + string packagePath, + bool verbose, + bool allowParallel, + int? maxConcurrency) + { + var configs = new ProjectConfigSet(); + configs.Load(packagePath); + var projectSet = new ProjectSet(); + projectSet.AddAll(configs); + var taskScheduler = NewTaskScheduler( + allowParallel, + maxConcurrency); + return projectSet.Build(taskScheduler, verbose); + } + + private static TaskScheduler NewTaskScheduler(bool allowParallel, int? maxConcurrency) + { + ConcurrentExclusiveSchedulerPair taskSchedulerPair; + if (maxConcurrency is int concurrency) + taskSchedulerPair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, concurrency); + else + taskSchedulerPair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default); + + return allowParallel + ? taskSchedulerPair.ConcurrentScheduler + : taskSchedulerPair.ExclusiveScheduler; + } + } +} diff --git a/azlab/azlab.csproj b/azlab/azlab.csproj new file mode 100644 index 00000000..0f2e55a3 --- /dev/null +++ b/azlab/azlab.csproj @@ -0,0 +1,38 @@ + + + + Exe + netcoreapp3.0 + Azoth.Tools.Bootstrap.Lab + 8.0 + enable + azlab + + + + true + + + + + DEBUG;TRACE + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/azoth.language.tests b/azoth.language.tests new file mode 160000 index 00000000..f2d29022 --- /dev/null +++ b/azoth.language.tests @@ -0,0 +1 @@ +Subproject commit f2d29022bb2862015bb4348b31d3c0936481778e diff --git a/azothc/Program.cs b/azothc/Program.cs new file mode 100644 index 00000000..2c107706 --- /dev/null +++ b/azothc/Program.cs @@ -0,0 +1,31 @@ +using System; +using McMaster.Extensions.CommandLineUtils; + +namespace Azoth.Tools.Bootstrap.Compiler.CLI +{ + public static class Program + { + public static int Main(string[] args) + { + using var app = new CommandLineApplication(); + + app.HelpOption(); + + var inputFilesArgument = app.Argument("...", "Input Code Files", multipleValues: true).IsRequired(); + var outputOption = app.Option("-o|--output ", "Output Code File", CommandOptionType.SingleValue).IsRequired(); + var verboseOption = app.Option("-v|--verbose", "Verbose Logging", CommandOptionType.NoValue); + + app.OnExecute(() => + { + var inputFiles = string.Join(';', inputFilesArgument.Values); + var outputFile = outputOption.Value(); + var verbose = verboseOption.HasValue(); // Strange since we didn't pass a value + Console.WriteLine("Input Files=" + inputFiles); + Console.WriteLine("Output File=" + outputFile); + Console.WriteLine("Verbose=" + verbose); + }); + + return app.Execute(args); + } + } +} diff --git a/azothc/Properties/PublishProfiles/Win-x64.pubxml b/azothc/Properties/PublishProfiles/Win-x64.pubxml new file mode 100644 index 00000000..17e5fb67 --- /dev/null +++ b/azothc/Properties/PublishProfiles/Win-x64.pubxml @@ -0,0 +1,16 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp2.0 + bin\publish\win-x64 + win-x64 + true + <_IsPortable>false + + \ No newline at end of file diff --git a/azothc/azothc.csproj b/azothc/azothc.csproj new file mode 100644 index 00000000..8e3d865f --- /dev/null +++ b/azothc/azothc.csproj @@ -0,0 +1,35 @@ + + + + Exe + netcoreapp3.0 + Azoth.Tools.Bootstrap.Compiler.CLI + + 8.0 + enable + azothc + + + + DEBUG;TRACE + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/docs/CoreFeatures.md b/docs/CoreFeatures.md new file mode 100644 index 00000000..659429ce --- /dev/null +++ b/docs/CoreFeatures.md @@ -0,0 +1,35 @@ +# Core Features + +This is a list of core features that need to be implemented or at least accounted for in the design of the compiler. + +* Borrow Checking (lifetimes) +* Rebinding of local variables (for `let` and in separate scopes for `var`) +* Binding mutability checking (i.e. `let` vs `var`) + * Local variables + * Parameters + * Self Parameter + * Global Variables +* Mutability + * Reference Types (on class and type declaration) + * Value Types +* Type Checking +* Overload Functions on Arity +* Overload Functions on Type +* Runtime Code Execution +* Generic Types +* Associated Functions +* Generic Types over Values (i.e. fixed length arrays) +* Definite assignment +* Destructor value access rules +* Exception types +* Effect Typing +* Async/await +* Inheritance +* Interface implementation +* Closures (esp. given lifetimes) +* Pseudo references +* Optional Types +* Void type as type parameter +* Unsafe code +* Type Inference +* Constant Evaluation diff --git a/docs/Implemented.md b/docs/Implemented.md new file mode 100644 index 00000000..41ac5317 --- /dev/null +++ b/docs/Implemented.md @@ -0,0 +1 @@ +# Features Implemented diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 00000000..aed3b547 --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1 @@ +.forge-cache/ diff --git a/runtime/azlab-project.vson b/runtime/azlab-project.vson new file mode 100644 index 00000000..eb5a4aa0 --- /dev/null +++ b/runtime/azlab-project.vson @@ -0,0 +1,5 @@ +{ + "name": "azoth.tools.bootstrap.compiler.runtime", + "authors": ["Jeff Walker Code Ranger"], + "template": "lib" +} diff --git a/runtime/src/Optional.az b/runtime/src/Optional.az new file mode 100644 index 00000000..3821e1a1 --- /dev/null +++ b/runtime/src/Optional.az @@ -0,0 +1,7 @@ +namespace azoth.tools.compiler.runtime; + +public enum struct Optional[T] +{ + Some(T), + None +} diff --git a/runtime/src/String.az b/runtime/src/String.az new file mode 100644 index 00000000..c0d897d7 --- /dev/null +++ b/runtime/src/String.az @@ -0,0 +1,27 @@ +namespace azoth.tools.compiler.runtime; + +/// String is a good example where treating the pseudo reference lifetime as just +/// $self wouldn't make sense. For example, a string literal would have a "static" +/// lifetime even though the struct referencing it would have a shorter lifetime. +/// Thus the lifetime needs to be a separate lifetime parameter. Thus the new +/// syntax of `ref struct` and the lifetime `$ref`. +public ref struct String +{ + public let count: size; + protected let data: @byte; + + // Unsafe because it can violate the invariants that `data` point to a buffer + // of at least `count` bytes and that those bytes be valid UTF-8 + public unsafe init(count: size, data: @byte) + { + self.count = count; + self.data = data; + } + + // TODO the string needs to delete the memory if it is owned, but we don't + // have the allocate/free functions in the runtime? + // public safe delete() + // { + + // } +}