Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(compiler) Add -c flag for "compile and assemble only" #455

Merged
merged 1 commit into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Perlang.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perlang.Tests.Architecture"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perlang.Compiler", "src\Perlang.Compiler\Perlang.Compiler.csproj", "{7E7CF7BF-F792-49C9-BB16-67FE856D8D51}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{47B4A37F-6999-4510-9815-418E03D7B39E}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solution Items", "{47B4A37F-6999-4510-9815-418E03D7B39E}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
Expand Down
3 changes: 3 additions & 0 deletions release-notes/v0.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- `BigInt::pow`: Detect negative exponents and throw an exception [[#450][450]]
- Implement initial native `ASCIIString` class [[#451][451]]
- Wrap `ASCIIString` in `std::shared_ptr<T>` [[#453][453]]
- Add `-c` flag for "compile and assemble only" [[#455][455]]

### Changed
#### Data types
Expand All @@ -27,3 +28,5 @@
[449]: https://github.com/perlang-org/perlang/pull/449
[450]: https://github.com/perlang-org/perlang/pull/450
[451]: https://github.com/perlang-org/perlang/pull/451
[453]: https://github.com/perlang-org/perlang/pull/453
[455]: https://github.com/perlang-org/perlang/pull/455
69 changes: 68 additions & 1 deletion src/Perlang.ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console)
{
var versionOption = new Option<bool>(new[] { "--version", "-v" }, "Show version information");
var detailedVersionOption = new Option<bool>("-V", "Show detailed version information");
var compileAndAssembleOnlyOption = new Option<bool>("-c", "Compile and assemble to a .o file, but do not produce and execute an executable");
var printOption = new Option<string>("-p", "Parse a single-line script and output a human-readable version of the AST") { ArgumentHelpName = "script" };
var noWarnAsErrorOption = new Option<string>("-Wno-error", "Treats specified warning as a warning instead of an error.") { ArgumentHelpName = "error" };

Expand All @@ -143,6 +144,7 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console)
{
versionOption,
detailedVersionOption,
compileAndAssembleOnlyOption,
printOption,
noWarnAsErrorOption
};
Expand Down Expand Up @@ -200,9 +202,32 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console)
// TODO: Tried to fix this using some logic in the rootCommand.AddValidator() lambda, but I
// TODO: couldn't get it working. Since this is going to be rewritten in Perlang at some point
// TODO: anyway, let's not spend too much time thinking about it.
Console.Error.WriteLine("One of the -p <script> or <script-name> arguments must be provided");
Console.Error.WriteLine("ERROR: One of the -p <script> or <script-name> arguments must be provided");
return Task.FromResult(1);
}
else if (parseResult.HasOption(compileAndAssembleOnlyOption))
{
string scriptName = parseResult.GetValueForArgument(scriptNameArgument);

// Console.Out.WriteLine(parseResult.Tokens);
// Console.Out.WriteLine(parseResult.GetValueForArgument(scriptArguments));
//
// if (parseResult.Tokens.Count != 1)
// {
// Console.Error.WriteLine("ERROR: When the -c option is used, no script arguments can be provided since the Perlang script/program will not get executed");
// return Task.FromResult(1);
// }
Copy link
Collaborator Author

@perlun perlun Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh, did I just (well, yesterday) merge in commented-out code? 🙈 Will go away in #456 for sure.


var program = new Program(
replMode: false,
standardOutputHandler: console.WriteStdoutLine,
disabledWarningsAsErrors: disabledWarningsAsErrorsList,
experimentalCompilation: PerlangMode.ExperimentalCompilation
);

int result = program.CompileAndAssembleFile(scriptName);
return Task.FromResult(result);
}
else
{
string scriptName = parseResult.GetValueForArgument(scriptNameArgument);
Expand Down Expand Up @@ -239,6 +264,14 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console)
})
};

rootCommand.AddValidator(result =>
{
if (result.HasOption(compileAndAssembleOnlyOption) && result.HasOption(printOption))
{
result.ErrorMessage = "Error: the -c and -p options are mutually exclusive";
}
});

rootCommand.AddArgument(scriptNameArgument);
rootCommand.AddArgument(scriptArguments);

Expand Down Expand Up @@ -320,6 +353,35 @@ private int RunFile(string path)
return (int)ExitCodes.SUCCESS;
}

private int CompileAndAssembleFile(string path)
{
if (!File.Exists(path))
{
standardErrorHandler(Lang.String.from($"Error: File {path} not found"));
return (int)ExitCodes.FILE_NOT_FOUND;
}

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

CompileAndAssemble(source, path, CompilerWarning);

// Indicate an error in the exit code.
if (hadError)
{
return (int)ExitCodes.ERROR;
}

// Should never happen, but _if_ it does, it's surely better that we return with non-zero rather than falsely
// imply success.
if (hadRuntimeError)
{
return (int)ExitCodes.RUNTIME_ERROR;
}

return (int)ExitCodes.SUCCESS;
}

internal int Run(string source, CompilerWarningHandler compilerWarningHandler)
{
object? result = interpreter.Eval(source, ScanError, ParseError, NameResolutionError, ValidationError, ValidationError, compilerWarningHandler);
Expand All @@ -337,6 +399,11 @@ private void CompileAndRun(string source, string path, CompilerWarningHandler co
compiler.CompileAndRun(source, path, CompilerFlags.None, ScanError, ParseError, NameResolutionError, ValidationError, ValidationError, compilerWarningHandler);
}

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

private void ParseAndPrint(string source)
{
string? result = interpreter.Parse(source, ScanError, ParseError);
Expand Down
62 changes: 57 additions & 5 deletions src/Perlang.Interpreter/Compiler/PerlangCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,47 @@ private void RegisterGlobalClasses()
return executablePath;
}

/// <summary>
/// Compiles and assembles the given Perlang program to a .o file (ELF object file).
/// </summary>
/// <param name="source">The Perlang program to compile.</param>
/// <param name="path">The path to the source file (used for generating error messages).</param>
/// <param name="compilerFlags">One or more <see cref="CompilerFlags"/> to use.</param>
/// <param name="scanErrorHandler">A handler for scanner errors.</param>
/// <param name="parseErrorHandler">A handler for parse errors.</param>
/// <param name="nameResolutionErrorHandler">A handler for resolve errors.</param>
/// <param name="typeValidationErrorHandler">A handler for type validation errors.</param>
/// <param name="immutabilityValidationErrorHandler">A handler for immutability validation errors.</param>
/// <param name="compilerWarningHandler">A handler for compiler warnings.</param>
/// <returns>The path to the compiled .o file. Note that this will be non-null even on unsuccessful
/// compilation.</returns>
public string? CompileAndAssemble(
string source,
string path,
CompilerFlags compilerFlags,
ScanErrorHandler scanErrorHandler,
ParseErrorHandler parseErrorHandler,
NameResolutionErrorHandler nameResolutionErrorHandler,
ValidationErrorHandler typeValidationErrorHandler,
ValidationErrorHandler immutabilityValidationErrorHandler,
CompilerWarningHandler compilerWarningHandler)
{
string? executablePath = Compile(
source,
path,
compilerFlags,
scanErrorHandler,
parseErrorHandler,
nameResolutionErrorHandler,
typeValidationErrorHandler,
immutabilityValidationErrorHandler,
compilerWarningHandler,
compileAndAssembleOnly: true
);

return executablePath;
}

/// <summary>
/// Compiles the given Perlang program to an executable.
/// </summary>
Expand All @@ -284,6 +325,8 @@ private void RegisterGlobalClasses()
/// <param name="typeValidationErrorHandler">A handler for type validation errors.</param>
/// <param name="immutabilityValidationErrorHandler">A handler for immutability validation errors.</param>
/// <param name="compilerWarningHandler">A handler for compiler warnings.</param>
/// <param name="compileAndAssembleOnly">A flag which enables functionality similar to `gcc -c`; compile and assemble
/// the given program, but do not generate an executable (and inherently do not execute it).</param>
/// <returns>The path to the generated executable file, or `null` if compilation failed.</returns>
private string? Compile(
string source,
Expand All @@ -294,15 +337,16 @@ private void RegisterGlobalClasses()
NameResolutionErrorHandler nameResolutionErrorHandler,
ValidationErrorHandler typeValidationErrorHandler,
ValidationErrorHandler immutabilityValidationErrorHandler,
CompilerWarningHandler compilerWarningHandler)
CompilerWarningHandler compilerWarningHandler,
bool compileAndAssembleOnly = false)
{
string targetCppFile = Path.ChangeExtension(path, ".cc");

#if _WINDOWS
// clang is very unlikely to have been available on Windows anyway, but why not...
string targetExecutable = Path.ChangeExtension(targetCppFile, "exe");
string targetExecutable = Path.ChangeExtension(targetCppFile, compileAndAssembleOnly ? "obj" : "exe");
#else
string targetExecutable = Path.ChangeExtension(targetCppFile, null);
string targetExecutable = Path.ChangeExtension(targetCppFile, compileAndAssembleOnly ? "o" : null);
#endif

// TODO: Check the creation time of *all* dependencies here, including the stdlib (both .so/.dll and .h files
Expand Down Expand Up @@ -453,6 +497,13 @@ private void RegisterGlobalClasses()

try
{
if (compileAndAssembleOnly && methods["main"].MethodBody.Length == 0)
{
// We generate a default "main" method elsewhere. In -c mode, we want to remove this method if it's empty,
// to avoid conflicts with the "real" main method of the program the .o file is being linked into.
methods.Remove("main");
}

// The AST traverser writes its output to the transpiledSource field.
Compile(statements);

Expand Down Expand Up @@ -539,6 +590,7 @@ private void RegisterGlobalClasses()
"--std=c++17",

"-I", Path.Combine(stdlibPath, "include"),
compileAndAssembleOnly ? "-c" : "",
"-o", targetExecutable,

// Useful while debugging
Expand Down Expand Up @@ -604,10 +656,10 @@ private void RegisterGlobalClasses()
targetCppFile,

// TODO: Support Windows static libraries as well
Path.Combine(stdlibPath, "lib/libstdlib.a"),
compileAndAssembleOnly ? "" : Path.Combine(stdlibPath, "lib/libstdlib.a"),

// Needed by the Perlang stdlib
"-lm"
compileAndAssembleOnly ? "" : "-lm"
},
RedirectStandardOutput = true,
RedirectStandardError = true
Expand Down
Loading