Skip to content

Commit

Permalink
Implemented basic error recovery (still very experimental and in deve…
Browse files Browse the repository at this point in the history
…lopment)
  • Loading branch information
Luna-Klatzer committed Jul 28, 2022
1 parent 1e4cdda commit 79f94a4
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 106 deletions.
9 changes: 7 additions & 2 deletions kipper/cli/src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,17 @@ export default class Compile extends Command {
},
);

// If the compilation failed, abort
if (!result.success) {
return;
}

const out = await writeCompilationResult(result, file, flags["output-dir"], flags["encoding"] as KipperEncoding);
result.programCtx.logger.debug(`Generated file '${out}'.`);
logger.debug(`Generated file '${out}'.`);

// Finished!
const duration: number = (new Date().getTime() - startTime) / 1000;
await logger.info(`Done in ${duration}s.`);
logger.info(`Done in ${duration}s.`);
} catch (e) {
// In case the error is of type KipperError, exit the program, as the logger should have already handled the
// output of the error and traceback.
Expand Down
5 changes: 5 additions & 0 deletions kipper/cli/src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ export default class Run extends Command {
},
);

// If the compilation failed, abort
if (!result.success) {
return;
}

await writeCompilationResult(result, file, flags["output-dir"], flags["encoding"] as KipperEncoding);

// Execute the program
Expand Down
211 changes: 143 additions & 68 deletions kipper/core/src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import { CodePointCharStream, CommonTokenStream } from "antlr4ts";
import { KipperAntlrErrorListener } from "./antlr-error-listener";
import { KipperLexer, KipperParser, KipperParseStream } from "./parser";
import { KipperLexer, KipperParser, KipperParseStream, ParseData } from "./parser";
import { KipperLogger, LogLevel } from "../logger";
import { KipperProgramContext } from "./program-ctx";
import { type BuiltInFunction, kipperInternalBuiltIns, kipperRuntimeBuiltIns } from "./runtime-built-ins";
Expand All @@ -17,7 +17,7 @@ import { defaultOptimisationOptions, OptimisationOptions } from "./optimiser";
import type { KipperError } from "../errors";

/**
* Compilation Configuration for a Kipper program. This interface will be wrapped using {@link EvaluatedCompileOptions}
* Compilation Configuration for a Kipper program. This interface will be wrapped using {@link EvaluatedCompileConfig}
* if it's passed to {@link KipperCompiler.compile}.
* @since 0.1.0
*/
Expand All @@ -29,34 +29,58 @@ export interface CompileConfig {
* All built-in functions defined here must be implemented by the {@link target.builtInGenerator}.
*/
builtIns?: Array<BuiltInFunction>;

/**
* Extends the {@link builtIns} with the specified items. If {@link builtIns} is undefined, then it will simply extend
* the default array.
*
* All built-in functions defined here must be implemented by the {@link target.builtInGenerator}.
*/
extendBuiltIns?: Array<BuiltInFunction>;

/**
* The filename that should be used to represent the program.
* @since 0.2.0
*/
fileName?: string;

/**
* The translation languages for the compilation.
* @since 0.5.0
*/
target?: KipperCompileTarget;

/**
* Options for the {@link KipperOptimiser}.
* @since 0.8.0
*/
optimisationOptions?: OptimisationOptions;

/**
* If set to true, the compiler will check for warnings and add them to {@link KipperProgramContext.warnings} and
* {@link KipperCompileResult.warnings}.
* @since 0.9.0
*/
warnings?: boolean;

/**
* If set to true, the compiler will attempt to recover from compilation errors if they are encountered. This will
* lead to more errors being reported and allowing for bigger more detailed compiler logs, but can in rare cases
* lead to misleading errors that are caused as a result of another compilation errors.
*
* Generally though, it is considered a good practise to use compiler error recovery, which is why it is enabled per
* default.
* @since 0.10.0
*/
recover?: boolean;

/**
* Throws an error and cancels the compilation on the first error that is encountered.
*
* This per default overwrites {@link recover}.
* @since 0.10.0
*/
abortOnFirstError?: boolean;
}

/**
Expand All @@ -67,7 +91,7 @@ export interface CompileConfig {
* values will be processed and evaluated on construction, so that every option is not undefined.
* @since 0.1.0
*/
export class EvaluatedCompileOptions implements CompileConfig {
export class EvaluatedCompileConfig implements CompileConfig {
/**
* Original user-defined {@link CompileConfig}, which may not be overwritten anymore, as the compile-arguments
* were already processed using the {@link constructor} of this class.
Expand All @@ -80,11 +104,13 @@ export class EvaluatedCompileOptions implements CompileConfig {
*/
public static readonly defaults = {
builtIns: kipperRuntimeBuiltIns, // Default built-in globals
extendGlobals: [], // No globals
fileName: "anonymous-script", // Default name if no name is specified.
target: new TypeScriptTarget(), // Default target is TypeScript.
extendGlobals: [], // Use no custom globals per default
fileName: "anonymous-script", // Default name if no name is specified
target: new TypeScriptTarget(), // Default target is TypeScript
optimisationOptions: defaultOptimisationOptions,
warnings: true, // Always generate warnings by default.
warnings: true, // Always generate warnings by default
recover: true, // Always try to recover from compilation errors
abortOnFirstError: false, // This should never be enabled per default
};

/**
Expand Down Expand Up @@ -127,16 +153,37 @@ export class EvaluatedCompileOptions implements CompileConfig {
*/
public readonly warnings: boolean;

/**
* If set to true, the compiler will attempt to recover from compilation errors if they are encountered. This will
* lead to more errors being reported and allowing for bigger more detailed compiler logs, but can in rare cases
* lead to misleading errors that are caused as a result of another compilation errors.
*
* Generally though, it is considered a good practise to use compiler error recovery, which is why it is enabled per
* default.
* @since 0.10.0
*/
public readonly recover: boolean;

/**
* Throws an error and cancels the compilation on the first error that is encountered.
*
* This per default overwrites {@link recover}.
* @since 0.10.0
*/
public readonly abortOnFirstError: boolean;

constructor(options: CompileConfig) {
this.userOptions = options;

// Evaluate all config options
this.builtIns = options.builtIns ?? Object.values(EvaluatedCompileOptions.defaults.builtIns);
this.extendBuiltIns = options.extendBuiltIns ?? EvaluatedCompileOptions.defaults.extendGlobals;
this.fileName = options.fileName ?? EvaluatedCompileOptions.defaults.fileName;
this.target = options.target ?? EvaluatedCompileOptions.defaults.target;
this.optimisationOptions = options.optimisationOptions ?? EvaluatedCompileOptions.defaults.optimisationOptions;
this.warnings = options.warnings ?? EvaluatedCompileOptions.defaults.warnings;
this.builtIns = options.builtIns ?? Object.values(EvaluatedCompileConfig.defaults.builtIns);
this.extendBuiltIns = options.extendBuiltIns ?? EvaluatedCompileConfig.defaults.extendGlobals;
this.fileName = options.fileName ?? EvaluatedCompileConfig.defaults.fileName;
this.target = options.target ?? EvaluatedCompileConfig.defaults.target;
this.optimisationOptions = options.optimisationOptions ?? EvaluatedCompileConfig.defaults.optimisationOptions;
this.warnings = options.warnings ?? EvaluatedCompileConfig.defaults.warnings;
this.recover = options.recover ?? EvaluatedCompileConfig.defaults.recover;
this.abortOnFirstError = options.abortOnFirstError ?? EvaluatedCompileConfig.defaults.abortOnFirstError;
}
}

Expand All @@ -145,21 +192,10 @@ export class EvaluatedCompileOptions implements CompileConfig {
* @since 0.0.3
*/
export class KipperCompileResult {
/**
* The private field '_fileCtx' that actually stores the variable data,
* which is returned inside the {@link this.fileCtx}.
* @private
*/
private readonly _programCtx: KipperProgramContext;

/**
* The private field '_result' that actually stores the variable data,
* which is returned inside the {@link this.result}.
* @private
*/
private readonly _result: Array<Array<string>>;
public readonly _programCtx: KipperProgramContext;
public readonly _result: Array<TranslatedCodeLine> | undefined;

constructor(fileCtx: KipperProgramContext, result: Array<Array<string>>) {
constructor(fileCtx: KipperProgramContext, result: Array<TranslatedCodeLine> | undefined) {
this._programCtx = fileCtx;
this._result = result;
}
Expand All @@ -174,10 +210,18 @@ export class KipperCompileResult {
/**
* The result of the compilation in TypeScript form (every line is represented as an entry in the array).
*/
public get result(): Array<Array<string>> {
public get result(): Array<TranslatedCodeLine> | undefined {
return this._result;
}

/**
* Returns true, if the compilation was successful without errors.
* @since 0.10.0
*/
public get success(): boolean {
return Boolean(this.result);
}

/**
* The list of warnings that were raised during the compilation process.
*
Expand All @@ -189,11 +233,26 @@ export class KipperCompileResult {
return this.programCtx.warnings;
}

/**
* The list of errors that were raised during the compilation process.
*
* Errors are fatal errors, which are raised when the compiler encounters a situation that it considers to be
* problematic, which prevents it from compiling the program.
* @since 0.10.0
*/
public get errors(): Array<KipperError> {
return this.programCtx.errors;
}

/**
* Creates a string from the compiled code that can be written to a file in a human-readable way.
* @param lineEnding The line ending for each line of the file. Default line ending is LF ('\n').
*/
public write(lineEnding: string = "\n"): string {
if (this.result === undefined) {
throw Error("Can not generate code for a failed compilation");
}

return this.result.map((line: TranslatedCodeLine) => line.join("") + lineEnding).join("");
}
}
Expand Down Expand Up @@ -253,10 +312,7 @@ export class KipperCompiler {
* @returns The generated and parsed {@link CompilationUnitContext}.
* @throws KipperSyntaxError If a syntax exception was encountered while running.
*/
public async parse(
parseStream: KipperParseStream,
target: KipperCompileTarget = new TypeScriptTarget(),
): Promise<KipperProgramContext> {
public async parse(parseStream: KipperParseStream): Promise<ParseData> {
this._logger.info(`Parsing file content.`);

// Creating the char stream, based on the input
Expand All @@ -277,20 +333,46 @@ export class KipperCompiler {
parser.removeErrorListeners(); // removing all error listeners
parser.addErrorListener(errorListener); // adding our own error listener

// Parse the input, where `compilationUnit` is whatever entry point you defined
return (() => {
let result = parser.compilationUnit();
this._logger.debug(`Finished generation of parse tree.`);
return new KipperProgramContext(
parseStream,
result,
parser,
lexer,
this.logger,
target,
Object.values(kipperInternalBuiltIns),
);
})();
// Get the root parse item of the parse tree
const parseTree = parser.compilationUnit();

this._logger.debug(`Finished generation of parse tree.`);
return { parseStream, parseTree, parser, lexer };
}

/**
* Creates a new {@link KipperProgramContext} based on the passed {@link parseData} and {@link config configuration}.
* @param parseData The parsing data of the file.
* @param compilerOptions The compilation config.
* @since 0.10.0
*/
public async getProgramCtx(
parseData: ParseData,
compilerOptions: CompileConfig | EvaluatedCompileConfig,
): Promise<KipperProgramContext> {
const config: EvaluatedCompileConfig =
compilerOptions instanceof EvaluatedCompileConfig ? compilerOptions : new EvaluatedCompileConfig(compilerOptions);

// Creates a new program context using the parse data and compilation configuration
let programCtx = new KipperProgramContext(
parseData.parseStream,
parseData.parseTree,
parseData.parser,
parseData.lexer,
this.logger,
config.target,
Object.values(kipperInternalBuiltIns),
config,
);

// Register all available built-in functions
let globals = [...config.builtIns, ...config.extendBuiltIns];
if (globals.length > 0) {
programCtx.registerBuiltIns(globals);
}
this.logger.debug(`Registered ${globals.length} global function${globals.length === 1 ? "" : "s"}.`);

return programCtx;
}

/**
Expand All @@ -307,42 +389,34 @@ export class KipperCompiler {
*/
public async compile(
stream: string | KipperParseStream,
compilerOptions: CompileConfig = new EvaluatedCompileOptions({}),
compilerOptions: CompileConfig = new EvaluatedCompileConfig({}),
): Promise<KipperCompileResult> {
// Handle compiler options
const config: EvaluatedCompileOptions =
compilerOptions instanceof EvaluatedCompileOptions
? compilerOptions
: new EvaluatedCompileOptions(compilerOptions);

// Handle the input and format it
let inStream: KipperParseStream = await KipperCompiler._handleStreamInput(stream, compilerOptions.fileName);

// Log as the initialisation finished
this.logger.info(`Starting compilation for '${inStream.name}'.`);

try {
// The file context storing the metadata for the "virtual file"
const fileCtx: KipperProgramContext = await this.parse(inStream);

// If there are builtIns to register, register them
let globals = [...config.builtIns, ...config.extendBuiltIns];
if (globals.length > 0) {
fileCtx.registerBuiltIns(globals);
}
this.logger.debug(`Registered ${globals.length} global function${globals.length === 1 ? "" : "s"}.`);
// Initial parsing step -> New ctx instance for the program
const parseData = await this.parse(inStream);
const programCtx = await this.getProgramCtx(parseData, compilerOptions);

// Start compilation of the Kipper program
const code = await fileCtx.compileProgram(config.optimisationOptions);
const code = await programCtx.compileProgram();

// After the compilation is done, return the compilation result as an instance
this.logger.info(`Compilation finished successfully without errors.`);
return new KipperCompileResult(fileCtx, code);
if (programCtx.errors) {
let errs = programCtx.errors.length;
this.logger.fatal(`Encountered ${errs} error${errs === 1 ? "" : "s"} during compilation.`);
} else {
this.logger.info(`Compilation finished successfully without errors.`);
}

return new KipperCompileResult(programCtx, code);
} catch (e) {
// Report the failure of the compilation
this.logger.reportError(LogLevel.FATAL, `Failed to compile '${inStream.name}'.`);

// Re-throw error
this.logger.fatal(`Failed to compile '${inStream.name}'.`);
throw e;
}
}
Expand All @@ -359,6 +433,7 @@ export class KipperCompiler {
* @throws {KipperSyntaxError} If a syntax exception was encountered while running.
*/
public async syntaxAnalyse(stream: string | KipperParseStream): Promise<void> {
// TODO! Remove this function and replace it with a new compilation option 'noCodeGeneration'
let inStream: KipperParseStream = await KipperCompiler._handleStreamInput(stream);

this.logger.info(`Starting syntax check for '${inStream.name}'.`);
Expand Down
Loading

0 comments on commit 79f94a4

Please sign in to comment.