Skip to content
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: 2 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ The CLI tools share a two-level application class hierarchy and a declarative op
6. `AfterExecute` — reporting hooks
7. `ShutdownSingletons` — cleanup in reverse order

CLI bytecode paths that need parse artifacts use `TGocciaCLISourcePipelineResult` (`Goccia.CLI.SourcePipelineResult.pas`) around the shared `TGocciaSourcePipeline.Parse` result. The helper stays in `source/app/`: it applies CLI warning display, transfers AST/source-map/generated-line ownership for compilation and coverage, and leaves bytecode compilation to each caller. `Goccia.CLI.SourceMaps.pas` owns the shared CLI source-map file output policy used by the Script Loader and Bundler.

**Tool mapping:**

| Tool | Base Class | Overrides |
Expand Down
22 changes: 19 additions & 3 deletions scripts/test-cli-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1268,11 +1268,26 @@ console.log("TestRunner: --output=compact-json omits build, memory, stdout, stde
const smSrc = join(tmp, "sm.jsx");
writeFileSync(smSrc, jsxSource);
await $`${BUNDLER} ${smSrc} --source-map`.quiet();
const smMap = join(tmp, "sm.jsx.map");
const smMap = join(tmp, "sm.map");
if (!existsSync(join(tmp, "sm.gbc"))) throw new Error("--source-map: .gbc should exist");
if (!existsSync(smMap)) throw new Error("--source-map: .map should exist");
if (existsSync(join(tmp, "sm.jsx.map"))) throw new Error("--source-map should not write map beside source extension");
assertValidSourceMap(smMap);

console.log("Bundler: --source-map defaults beside custom --output...");
const smOutSrc = join(tmp, "sm-output.jsx");
const smOutDir = join(tmp, "maps");
const smOutGbc = join(smOutDir, "out.gbc");
const smOutMap = join(smOutDir, "out.map");
mkdirSync(smOutDir, { recursive: true });
writeFileSync(smOutSrc, jsxSource);
await $`${BUNDLER} ${smOutSrc} --output=${smOutGbc} --source-map`.quiet();
if (!existsSync(smOutGbc)) throw new Error("--source-map with --output: .gbc should exist");
if (!existsSync(smOutMap)) throw new Error("--source-map with --output: .map should exist beside .gbc");
if (existsSync(join(tmp, "sm-output.map")) || existsSync(join(tmp, "sm-output.jsx.map")))
throw new Error("--source-map with --output should not write .map beside source");
assertValidSourceMap(smOutMap);

console.log("Bundler: --source-map=<custom path>...");
const smCustomSrc = join(tmp, "sm-custom.jsx");
const smCustomMap = join(tmp, "custom-output.map");
Expand All @@ -1285,14 +1300,15 @@ console.log("TestRunner: --output=compact-json omits build, memory, stdout, stde
const noSmSrc = join(tmp, "no-sm.jsx");
writeFileSync(noSmSrc, jsxSource);
await $`${BUNDLER} ${noSmSrc}`.quiet();
if (existsSync(join(tmp, "no-sm.map"))) throw new Error("No .map file should exist without --source-map");
if (existsSync(join(tmp, "no-sm.jsx.map"))) throw new Error("No .map file should exist without --source-map");

console.log("Bundler: stdin --source-map --output...");
const stdinSmOut = join(tmp, "stdin-sm.gbc");
await $`echo ${jsxSource} | ${BUNDLER} --source-map --output=${stdinSmOut}`.quiet();
const stdinSmMap = stdinSmOut + ".map";
const stdinSmMap = join(tmp, "stdin-sm.map");
if (!existsSync(stdinSmOut)) throw new Error("Stdin --source-map: .gbc should exist");
if (!existsSync(stdinSmMap)) throw new Error("Stdin --source-map: .gbc.map should exist");
if (!existsSync(stdinSmMap)) throw new Error("Stdin --source-map: .map should exist beside .gbc");
assertValidSourceMap(stdinSmMap);
} finally {
clean(tmp);
Expand Down
34 changes: 34 additions & 0 deletions source/app/Goccia.CLI.SourceMaps.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
unit Goccia.CLI.SourceMaps;

{$I Goccia.inc}

interface

uses
Goccia.SourceMap;

procedure WriteSourceMapIfAvailable(const ASourceMap: TGocciaSourceMap;
const AMapOutputPath, AGeneratedFileName, ASourceName: string;
const APrintMessage: Boolean);

implementation

uses
SysUtils;

procedure WriteSourceMapIfAvailable(const ASourceMap: TGocciaSourceMap;
const AMapOutputPath, AGeneratedFileName, ASourceName: string;
const APrintMessage: Boolean);
begin
if not Assigned(ASourceMap) then
Exit;

ASourceMap.FileName := ExtractFileName(AGeneratedFileName);
ASourceMap.SetSourcePath(0, ExtractFileName(ASourceName));
ASourceMap.SaveToFile(AMapOutputPath);

if APrintMessage then
WriteLn(SysUtils.Format(' Source map written to %s', [AMapOutputPath]));
end;

end.
125 changes: 125 additions & 0 deletions source/app/Goccia.CLI.SourcePipelineResult.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
unit Goccia.CLI.SourcePipelineResult;

{$I Goccia.inc}

interface

uses
Classes,

Goccia.AST.Node,
Goccia.SourceMap,
Goccia.SourcePipeline;

type
TGocciaCLISourcePipelineResult = class
private
FProgramNode: TGocciaProgram;
FSourceMap: TGocciaSourceMap;
FGeneratedSourceLines: TStringList;
FLexTimeNanoseconds: Int64;
FParseTimeNanoseconds: Int64;
public
destructor Destroy; override;

class function Parse(const ASource: TStringList; const AFileName: string;
const AOptions: TGocciaSourcePipelineOptions;
const ASuppressWarnings: Boolean): TGocciaCLISourcePipelineResult; static;

procedure RegisterCoverageSource(const AFileName: string);
function TakeSourceMap: TGocciaSourceMap;

property ProgramNode: TGocciaProgram read FProgramNode;
property SourceMap: TGocciaSourceMap read FSourceMap;
property LexTimeNanoseconds: Int64 read FLexTimeNanoseconds;
property ParseTimeNanoseconds: Int64 read FParseTimeNanoseconds;
end;

implementation

uses
SysUtils,

Goccia.Coverage,
Goccia.Threading;

procedure PrintPipelineWarnings(const AFileName: string;
const APipelineResult: TGocciaSourcePipelineResult;
const ASuppressWarnings: Boolean);
var
I: Integer;
Warning: TGocciaSourcePipelineWarning;
begin
if ASuppressWarnings or GIsWorkerThread then
Exit;

for I := 0 to APipelineResult.WarningCount - 1 do
begin
Warning := APipelineResult.Warnings[I];
WriteLn(SysUtils.Format('Warning: %s', [Warning.Message]));
if Warning.Suggestion <> '' then
WriteLn(SysUtils.Format(' Suggestion: %s', [Warning.Suggestion]));
WriteLn(SysUtils.Format(' --> %s:%d:%d',
[AFileName, Warning.Line, Warning.Column]));
end;
end;

destructor TGocciaCLISourcePipelineResult.Destroy;
begin
FGeneratedSourceLines.Free;
FSourceMap.Free;
FProgramNode.Free;
inherited Destroy;
end;

class function TGocciaCLISourcePipelineResult.Parse(const ASource: TStringList;
const AFileName: string; const AOptions: TGocciaSourcePipelineOptions;
const ASuppressWarnings: Boolean): TGocciaCLISourcePipelineResult;
var
PipelineResult: TGocciaSourcePipelineResult;
begin
Result := TGocciaCLISourcePipelineResult.Create;
try
PipelineResult := TGocciaSourcePipeline.Parse(ASource, AFileName,
AOptions);
try
Result.FLexTimeNanoseconds := PipelineResult.LexTimeNanoseconds;
Result.FParseTimeNanoseconds := PipelineResult.ParseTimeNanoseconds;
PrintPipelineWarnings(AFileName, PipelineResult, ASuppressWarnings);

Result.FProgramNode := PipelineResult.TakeProgramNode;
Result.FSourceMap := PipelineResult.TakeSourceMap;
Result.FGeneratedSourceLines := PipelineResult.TakeGeneratedSourceLines;
finally
PipelineResult.Free;
end;
except
Result.Free;
raise;
end;
end;

procedure TGocciaCLISourcePipelineResult.RegisterCoverageSource(
const AFileName: string);
begin
if not Assigned(TGocciaCoverageTracker.Instance) then
Exit;
if not TGocciaCoverageTracker.Instance.Enabled then
Exit;
if not Assigned(FGeneratedSourceLines) then
Exit;

TGocciaCoverageTracker.Instance.RegisterSourceFile(
AFileName, CountExecutableLines(FGeneratedSourceLines));
if Assigned(FSourceMap) then
TGocciaCoverageTracker.Instance.RegisterSourceMap(
AFileName, FSourceMap.Clone);
end;

function TGocciaCLISourcePipelineResult.TakeSourceMap: TGocciaSourceMap;
begin
Result := FSourceMap;
FSourceMap := nil;
end;

end.
40 changes: 20 additions & 20 deletions source/app/GocciaBenchmarkRunner.dpr
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ uses

Goccia.Application,
Goccia.Arguments.Collection,
Goccia.AST.Node,
Goccia.Benchmark.Reporter,
Goccia.Builtins.Benchmark,
Goccia.Bytecode.Module,
Goccia.CLI.Application,
Goccia.CLI.SourcePipelineResult,
Goccia.CLI.Options,
CLI.ConfigFile,
CLI.Options,
Expand Down Expand Up @@ -406,7 +406,7 @@ procedure TBenchmarkRunnerApp.CollectBenchmarkFileBytecode(
var
Source: TStringList;
PipelineOptions: TGocciaSourcePipelineOptions;
PipelineResult: TGocciaSourcePipelineResult;
SourcePipelineResult: TGocciaCLISourcePipelineResult;
Module: TGocciaCompiledModule;
Executor: TGocciaBytecodeExecutor;
Engine: TGocciaEngine;
Expand All @@ -417,7 +417,7 @@ var
LexTimeNanoseconds, ParseTimeNanoseconds: Int64;
begin
Source := nil;
PipelineResult := nil;
SourcePipelineResult := nil;
try
try
Source := SourceRegistry.Load(AFileName);
Expand All @@ -440,18 +440,18 @@ begin
PipelineOptions.Preprocessors := Engine.Preprocessors;
PipelineOptions.Compatibility := Engine.Compatibility;
PipelineOptions.SourceType := Engine.SourceType;
PipelineResult := TGocciaSourcePipeline.Parse(Source, AFileName,
PipelineOptions);
LexTimeNanoseconds := PipelineResult.LexTimeNanoseconds;
ParseTimeNanoseconds := PipelineResult.ParseTimeNanoseconds;
SourcePipelineResult := TGocciaCLISourcePipelineResult.Parse(Source, AFileName,
PipelineOptions, True);
LexTimeNanoseconds := SourcePipelineResult.LexTimeNanoseconds;
ParseTimeNanoseconds := SourcePipelineResult.ParseTimeNanoseconds;

CompileStart := GetNanoseconds;
try
Module := Engine.CompileModule(PipelineResult.ProgramNode);
Module := Engine.CompileModule(SourcePipelineResult.ProgramNode);
CompileEnd := GetNanoseconds;
finally
PipelineResult.Free;
PipelineResult := nil;
SourcePipelineResult.Free;
SourcePipelineResult := nil;
end;

ConfigureBenchmarkRuntime(Engine, AShowProgress, True);
Expand Down Expand Up @@ -521,7 +521,7 @@ begin
MakeErrorFileResult(AFileName, E.Message, AReporter);
end;
finally
PipelineResult.Free;
SourcePipelineResult.Free;
if Assigned(TGarbageCollector.Instance) then
TGarbageCollector.Instance.Collect;
Source.Free;
Expand Down Expand Up @@ -620,7 +620,7 @@ procedure TBenchmarkRunnerApp.CollectBenchmarkSourceBytecode(
const AReporter: TBenchmarkReporter; const AShowProgress: Boolean);
var
PipelineOptions: TGocciaSourcePipelineOptions;
PipelineResult: TGocciaSourcePipelineResult;
SourcePipelineResult: TGocciaCLISourcePipelineResult;
Module: TGocciaCompiledModule;
Executor: TGocciaBytecodeExecutor;
Engine: TGocciaEngine;
Expand All @@ -630,7 +630,7 @@ var
LexStart, CompileStart, CompileEnd, ExecEnd, BenchStart: Int64;
LexTimeNanoseconds, ParseTimeNanoseconds: Int64;
begin
PipelineResult := nil;
SourcePipelineResult := nil;
try
Executor := TGocciaBytecodeExecutor.Create;
try
Expand All @@ -640,18 +640,18 @@ begin
PipelineOptions.Preprocessors := Engine.Preprocessors;
PipelineOptions.Compatibility := Engine.Compatibility;
PipelineOptions.SourceType := Engine.SourceType;
PipelineResult := TGocciaSourcePipeline.Parse(ASource, AFileName,
PipelineOptions);
LexTimeNanoseconds := PipelineResult.LexTimeNanoseconds;
ParseTimeNanoseconds := PipelineResult.ParseTimeNanoseconds;
SourcePipelineResult := TGocciaCLISourcePipelineResult.Parse(ASource, AFileName,
PipelineOptions, True);
LexTimeNanoseconds := SourcePipelineResult.LexTimeNanoseconds;
ParseTimeNanoseconds := SourcePipelineResult.ParseTimeNanoseconds;

CompileStart := GetNanoseconds;
try
Module := Engine.CompileModule(PipelineResult.ProgramNode);
Module := Engine.CompileModule(SourcePipelineResult.ProgramNode);
CompileEnd := GetNanoseconds;
finally
PipelineResult.Free;
PipelineResult := nil;
SourcePipelineResult.Free;
SourcePipelineResult := nil;
end;

ConfigureBenchmarkRuntime(Engine, AShowProgress, True);
Expand Down
Loading
Loading