Skip to content

Commit

Permalink
(compiler) Add first steps towards experimental compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
perlun committed Oct 1, 2023
1 parent d66c6a4 commit 94e672a
Show file tree
Hide file tree
Showing 76 changed files with 2,365 additions and 721 deletions.
7 changes: 7 additions & 0 deletions Perlang.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perlang.Tests.Architecture", "src\Perlang.Tests.Architecture\Perlang.Tests.Architecture.csproj", "{E9D632CB-E520-4868-9E64-A4A70ACD713C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perlang.Compiler", "src\Perlang.Compiler\Perlang.Compiler.csproj", "{7E7CF7BF-F792-49C9-BB16-67FE856D8D51}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -65,6 +67,10 @@ Global
{E9D632CB-E520-4868-9E64-A4A70ACD713C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9D632CB-E520-4868-9E64-A4A70ACD713C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9D632CB-E520-4868-9E64-A4A70ACD713C}.Release|Any CPU.Build.0 = Release|Any CPU
{7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B338D29B-A85D-4650-9BFF-FBFE55258C58} = {B924E83B-6AFE-4ABF-B79D-444F92582892}
Expand All @@ -75,5 +81,6 @@ Global
{CD78DA11-CB31-4892-8F96-3B7FD7015B83} = {F13F8E4E-5231-4321-9722-1BFCC08E5BB5}
{1874E267-5D6F-4EA4-8938-F2A0D19F27B1} = {F13F8E4E-5231-4321-9722-1BFCC08E5BB5}
{E9D632CB-E520-4868-9E64-A4A70ACD713C} = {F13F8E4E-5231-4321-9722-1BFCC08E5BB5}
{7E7CF7BF-F792-49C9-BB16-67FE856D8D51} = {B924E83B-6AFE-4ABF-B79D-444F92582892}
EndGlobalSection
EndGlobal
3 changes: 1 addition & 2 deletions docs/examples/quickstart/hello_world.per
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
// hello_world.per
print "Hello World";
print "foobar"[0];
1 change: 1 addition & 0 deletions global.ruleset
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

<Rules AnalyzerId="SonarAnalyzer.CSharp" RuleNamespace="SonarAnalyzer.CSharp">
<Rule Id="S125" Action="None"/>
<Rule Id="S927" Action="None"/> <!-- All declarations of an object or function should use the same names and type qualifiers -->
<Rule Id="S1134" Action="None"/>
<Rule Id="S1135" Action="None"/>
<Rule Id="S3260" Action="None"/> <!-- Private classes or records which are not derived in the current assembly should be marked as 'sealed' -->
Expand Down
1 change: 1 addition & 0 deletions release-notes/v0.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- The REPL is also noticeably nicer to use now, particularly for those of us used to bash/Emacs-like keybindings. [[#354][354]], [[#356][356]]
- API docs on the website was broken since the v0.2.0 release for unknown reasons. [[#344][344]] has more details.
- Based on community feedback, the project README was also slightly improved. [[#371][371]]
- All changes below were made by @perlun

### Added
#### Data types
Expand Down
2 changes: 2 additions & 0 deletions release-notes/v0.4.0.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## [0.4.0] - Unreleased
- The fourth public release of Perlang.
- For more details on how to install Perlang on your own machine, see https://perlang.org/download/.
- All changes below were made by @perlun

### Added
#### Experimental compilation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Perlang.Compiler;

/// <summary>
/// Exception thrown for valid Perlang code, which is currently "not yet supported" in compiled mode.
///
/// This exception is thrown to make it possible for integration tests to skip tests for code which is known to not
/// yet work.
/// </summary>
public class NotImplementedInCompiledModeException : PerlangCompilerException
{
public NotImplementedInCompiledModeException(string message)
: base(message)
{
}
}
11 changes: 11 additions & 0 deletions src/Perlang.Common/Compiler/PerlangCompilerException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Perlang.Compiler;

public class PerlangCompilerException : Exception
{
public PerlangCompilerException(string message)
: base(message)
{
}
}
7 changes: 7 additions & 0 deletions src/Perlang.Common/Expr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,14 @@ public override string ToString() =>

public class Get : Expr, ITokenAware
{
/// <summary>
/// Gets the target object whose field/property/method is being accessed.
/// </summary>
public Expr Object { get; }

/// <summary>
/// Gets the name of the field/property/method that is being accessed.
/// </summary>
public Token Name { get; }

// TODO: Would be much nicer to have this be without setter, but there is no easy way to accomplish this,
Expand Down
38 changes: 38 additions & 0 deletions src/Perlang.Common/ITypeReference.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
#nullable enable

using System;
using System.Numerics;

namespace Perlang
{
public interface ITypeReference
{
Token? TypeSpecifier { get; }

/// <summary>
/// Gets or sets the CLR/.NET type that this <see cref="ITypeReference"/> refers to.
/// </summary>
// TODO: Remove setter to make this interface and class be fully immutable, for debuggability.
Type? ClrType { get; set; }

/// <summary>
/// Gets the C++ type that this <see cref="ITypeReference"/> refers to.
/// </summary>
string CppType { get; }

/// <summary>
/// Gets a value indicating whether the type reference contains an explicit type specifier or not. If this is
/// false, the user is perhaps intending for the type to be inferred from the program context.
Expand All @@ -27,5 +36,34 @@ public interface ITypeReference
/// Gets a value indicating whether this type reference refers to a `null` value.
/// </summary>
public bool IsNullObject => ClrType == typeof(NullObject);

bool IsValidNumberType =>
ClrType switch
{
null => false,
var t when t == typeof(SByte) => true,
var t when t == typeof(Int16) => true,
var t when t == typeof(Int32) => true,
var t when t == typeof(Int64) => true,
var t when t == typeof(Byte) => true,
var t when t == typeof(UInt16) => true,
var t when t == typeof(UInt32) => true,
var t when t == typeof(UInt64) => true,
var t when t == typeof(Single) => true, // i.e. float
var t when t == typeof(Double) => true,
var t when t == typeof(BigInteger) => true,
_ => false
};

bool IsStringType() =>
ClrType switch
{
null => false,

// Cannot use typeof(AsciiString) since Perlang.Common cannot depend on Perlang.Stdlib
var t when t.FullName == "Perlang.Lang.AsciiString" => true,

_ => false
};
}
}
25 changes: 25 additions & 0 deletions src/Perlang.Common/PerlangMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.IO;

namespace Perlang;

public static class PerlangMode
{
public static bool ExperimentalCompilation
{
get
{
string environmentVariable = Environment.GetEnvironmentVariable("PERLANG_EXPERIMENTAL_COMPILATION");

// The environment variable takes precedence, if set
if (Boolean.TryParse(environmentVariable, out bool flag))
{
return flag;
}

string homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);

return File.Exists(Path.Combine(homeDirectory, ".perlang_experimental_compilation"));
}
}
}
9 changes: 5 additions & 4 deletions src/Perlang.Common/Stmt.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;

Expand Down Expand Up @@ -99,9 +100,9 @@ public class If : Stmt
{
public Expr Condition { get; }
public Stmt ThenBranch { get; }
public Stmt ElseBranch { get; }
public Stmt? ElseBranch { get; }

public If(Expr condition, Stmt thenBranch, Stmt elseBranch)
public If(Expr condition, Stmt thenBranch, Stmt? elseBranch)
{
Condition = condition;
ThenBranch = thenBranch;
Expand Down Expand Up @@ -132,9 +133,9 @@ public override TR Accept<TR>(IVisitor<TR> visitor)
public class Return : Stmt
{
public Token Keyword { get; }
public Expr Value { get; }
public Expr? Value { get; }

public Return(Token keyword, Expr value)
public Return(Token keyword, Expr? value)
{
Keyword = keyword;
Value = value;
Expand Down
24 changes: 24 additions & 0 deletions src/Perlang.Common/TypeReference.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#nullable enable
using System;
using System.Numerics;
using Perlang.Compiler;

namespace Perlang
{
Expand Down Expand Up @@ -44,6 +46,28 @@ public Type? ClrType
}
}

public string CppType =>
clrType switch
{
// TODO: Add the other Perlang-supported types as well
var t when t == typeof(Int32) => "int32_t",
var t when t == typeof(UInt32) => "uint32_t",
var t when t == typeof(Int64) => "int64_t",
var t when t == typeof(UInt64) => "uint64_t",
var t when t == typeof(Single) => "float",
var t when t == typeof(Double) => "double",
var t when t == typeof(bool) => "bool",
var t when t == typeof(void) => "void",
var t when t == typeof(BigInteger) => throw new NotImplementedInCompiledModeException("BigInteger is not yet supported in compiled mode"),
null => throw new InvalidOperationException($"Internal error: ClrType was unexpectedly null"),

// TODO: Differentiate from these on the C++ level as well
var t when t.FullName == "Perlang.Lang.AsciiString" => "const char *",
var t when t.FullName == "Perlang.Lang.String" => "const char *",

_ => throw new NotImplementedInCompiledModeException($"Internal error: C++ type for {clrType} not defined")
};

/// <summary>
/// Initializes a new instance of the <see cref="TypeReference"/> class, for a given type specifier. The type
/// specifier can be null, in which case type inference will be attempted.
Expand Down
24 changes: 24 additions & 0 deletions src/Perlang.Compiler/Perlang.Compiler.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DocumentationFile>bin\Debug\net7.0\Perlang.Compiler.xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\Release\net7.0\Perlang.Compiler.xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Perlang.Common\Perlang.Common.csproj" />
<ProjectReference Include="..\Perlang.Parser\Perlang.Parser.csproj" />
</ItemGroup>

</Project>
5 changes: 5 additions & 0 deletions src/Perlang.Compiler/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Reflection;
using Perlang;

[assembly: AssemblyVersion(CommonConstants.Version)]
[assembly: AssemblyInformationalVersion(CommonConstants.InformationalVersion)]
1 change: 1 addition & 0 deletions src/Perlang.ConsoleApp/Perlang.ConsoleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Perlang.Compiler\Perlang.Compiler.csproj" />
<ProjectReference Include="..\Perlang.Interpreter\Perlang.Interpreter.csproj" />
</ItemGroup>

Expand Down
37 changes: 33 additions & 4 deletions src/Perlang.ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Mono.Terminal;
using Perlang.Internal;
using Perlang.Interpreter;
using Perlang.Interpreter.Compiler;
using Perlang.Interpreter.NameResolution;
using Perlang.Parser;
using ParseError = Perlang.Parser.ParseError;
Expand Down Expand Up @@ -64,6 +65,7 @@ internal enum ExitCodes
}

private readonly PerlangInterpreter interpreter;
private readonly PerlangCompiler compiler;

/// <summary>
/// Writes a Perlang native string to the standard output stream.
Expand All @@ -87,6 +89,11 @@ internal enum ExitCodes

private readonly HashSet<WarningType> disabledWarningsAsErrors;

/// <summary>
/// A flag which determines if (highly experimental) compilation to machine code is enabled or not.
/// </summary>
private readonly bool experimentalCompilation;

private bool hadError;
private bool hadRuntimeError;

Expand Down Expand Up @@ -234,7 +241,8 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console)
var program = new Program(
replMode: false,
standardOutputHandler: console.WriteStdoutLine,
disabledWarningsAsErrors: disabledWarningsAsErrorsList
disabledWarningsAsErrors: disabledWarningsAsErrorsList,
experimentalCompilation: PerlangMode.ExperimentalCompilation
);

result = program.RunFile(scriptName);
Expand All @@ -247,7 +255,8 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console)
replMode: false,
arguments: remainingArguments,
standardOutputHandler: console.WriteStdoutLine,
disabledWarningsAsErrors: disabledWarningsAsErrorsList
disabledWarningsAsErrors: disabledWarningsAsErrorsList,
experimentalCompilation: PerlangMode.ExperimentalCompilation
);

result = program.RunFile(scriptName);
Expand Down Expand Up @@ -290,13 +299,15 @@ internal Program(
Action<Lang.String> standardOutputHandler,
IEnumerable<string>? arguments = null,
IEnumerable<WarningType>? disabledWarningsAsErrors = null,
Action<RuntimeError>? runtimeErrorHandler = null)
Action<RuntimeError>? runtimeErrorHandler = null,
bool experimentalCompilation = false)
{
// TODO: Make these be separate handlers at some point, so the caller can separate between these types of
// TODO: output.
this.standardOutputHandler = standardOutputHandler;
this.standardErrorHandler = standardOutputHandler;
this.disabledWarningsAsErrors = (disabledWarningsAsErrors ?? Enumerable.Empty<WarningType>()).ToHashSet();
this.experimentalCompilation = experimentalCompilation;

// Convenience fields while we are migrating away from CLR strings to Perlang strings.
this.standardOutputHandlerFromClrString = s => this.standardOutputHandler(Lang.String.from(s));
Expand All @@ -309,6 +320,11 @@ internal Program(
arguments ?? new List<string>(),
replMode: replMode
);

compiler = new PerlangCompiler(
runtimeErrorHandler ?? RuntimeError,
this.standardOutputHandler
);
}

private int RunFile(string path)
Expand All @@ -320,8 +336,16 @@ private int RunFile(string path)
}

var bytes = File.ReadAllBytes(path);
string source = Encoding.UTF8.GetString(bytes);

Run(Encoding.UTF8.GetString(bytes), CompilerWarning);
if (experimentalCompilation)
{
CompileAndRun(source, path, CompilerWarning);
}
else
{
Run(source, CompilerWarning);
}

// Indicate an error in the exit code.
if (hadError)
Expand Down Expand Up @@ -426,6 +450,11 @@ internal int Run(string source, CompilerWarningHandler compilerWarningHandler)
return (int)ExitCodes.SUCCESS;
}

private void CompileAndRun(string source, string path, CompilerWarningHandler compilerWarningHandler)
{
compiler.CompileAndRun(source, path, ScanError, ParseError, NameResolutionError, ValidationError, ValidationError, compilerWarningHandler);
}

private void ParseAndPrint(string source)
{
string? result = interpreter.Parse(source, ScanError, ParseError);
Expand Down
Loading

0 comments on commit 94e672a

Please sign in to comment.