Skip to content

Commit

Permalink
fix: enhance error handling to support third-party tools (#509)
Browse files Browse the repository at this point in the history
- Replaced consoleLogger with the new Logger
- Removed jest mock where consoleLogger was used
- Add `TactErrorCollection` type
- Add `-q`/`--quiet` flags to suppress compiler log output

Co-authored-by: Novus Nota <[email protected]>
Co-authored-by: byakuren-hijiri <[email protected]>
Co-authored-by: Anton Trunov <[email protected]>
  • Loading branch information
4 people authored Jul 5, 2024
1 parent d8893ca commit 1d6dd06
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 214 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `-e` / `--eval` CLI flags to evaluate constant Tact expressions: PR [#462](https://github.com/tact-lang/tact/pull/462)
- `-q` / `--quiet` CLI flags to suppress compiler log output: PR [#509](https://github.com/tact-lang/tact/pull/509)

### Changed

Expand All @@ -35,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Error message for duplicate receiver definitions inherited from traits: PR [#519](https://github.com/tact-lang/tact/issues/519)
- Usage of `initOf` inside of `init()` does not cause error `135` anymore: PR [#521](https://github.com/tact-lang/tact/issues/521)
- Usage of `newAddress` with hash parts shorter than 64 hexadecimal digits does not cause constant evaluation error `Invalid address hash length` anymore: PR [#525](https://github.com/tact-lang/tact/pull/525)
- Introduced a streamlined error logger for compilation pipeline to support third-party tools: PR [#509](https://github.com/tact-lang/tact/pull/509)

## [1.4.0] - 2024-06-21

Expand Down
12 changes: 6 additions & 6 deletions bin/tact
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ meowModule.then(
Flags
-c, --config CONFIG Specify path to config file (tact.config.json)
-p, --project ...names Build only the specified project name(s) from the config file
-q, --quiet Suppress compiler log output
--with-decompilation Full compilation followed by decompilation of produced binary code
--func Output intermediate FunC code and exit
--check Perform syntax and type checking, then exit
Expand Down Expand Up @@ -51,14 +52,12 @@ meowModule.then(
);
},
},
eval: {
shortFlag: "e",
type: "string",
},
projects: { shortFlag: "p", type: "string", isMultiple: true },
quiet: { shortFlag: "q", type: "boolean", default: false },
withDecompilation: { type: "boolean", default: false },
func: { type: "boolean", default: false },
check: { type: "boolean", default: false },
eval: { shortFlag: "e", type: "string" },
version: { shortFlag: "v", type: "boolean" },
help: { shortFlag: "h", type: "boolean" },
},
Expand Down Expand Up @@ -164,10 +163,11 @@ meowModule.then(
configPath: cli.flags.config,
projectNames: cli.flags.projects ?? [],
additionalCliOptions: { mode },
suppressLog: cli.flags.quiet,
})
.then((success) => {
.then((response) => {
// https://nodejs.org/docs/v20.12.1/api/process.html#exit-codes
process.exit(success ? 0 : 30);
process.exit(response.ok ? 0 : 30);
});
},
);
137 changes: 75 additions & 62 deletions scripts/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,82 +5,95 @@ import { FuncCompilationResult, funcCompile } from "../src/func/funcCompile";
import path from "path";
import { glob } from "glob";
import { verify } from "../src/verify";
import { consoleLogger } from "../src/logger";
import { Logger } from "../src/logger";
import { __DANGER__disableVersionNumber } from "../src/pipeline/version";

// Read cases
void (async () => {
// Disable version number in packages
__DANGER__disableVersionNumber();

// Compile projects
if (!(await run({ configPath: __dirname + "/../tact.config.json" }))) {
console.error("Tact projects compilation failed");
process.exit(1);
}
const logger = new Logger();

// Verify projects
for (const pkgPath of glob.sync(
path.normalize(
path.resolve(__dirname, "..", "examples", "output", "*.pkg"),
),
)) {
const res = await verify({ pkg: fs.readFileSync(pkgPath, "utf-8") });
if (!res.ok) {
console.error("Failed to verify " + pkgPath + ": " + res.error);
process.exit(1);
try {
// Compile projects
const compileResult = await run({
configPath: path.join(__dirname, "..", "tact.config.json"),
});
if (!compileResult.ok) {
throw new Error("Tact projects compilation failed");
}
}

// Compile func files
for (const p of [{ path: __dirname + "/../func/" }]) {
const recs = fs.readdirSync(p.path);
for (const r of recs) {
if (!r.endsWith(".fc")) {
continue;
// Verify projects
for (const pkgPath of glob.sync(
path.normalize(
path.resolve(__dirname, "..", "examples", "output", "*.pkg"),
),
)) {
const res = await verify({
pkg: fs.readFileSync(pkgPath, "utf-8"),
});
if (!res.ok) {
throw new Error(`Failed to verify ${pkgPath}: ${res.error}`);
}
}

// Precompile
console.log("Processing " + p.path + r);
let c: FuncCompilationResult;
try {
const stdlibPath = path.resolve(
__dirname,
"..",
"stdlib",
"stdlib.fc",
);
const stdlib = fs.readFileSync(stdlibPath, "utf-8");
const code = fs.readFileSync(p.path + r, "utf-8");
c = await funcCompile({
entries: [stdlibPath, p.path + r],
sources: [
{
path: stdlibPath,
content: stdlib,
},
{
path: p.path + r,
content: code,
},
],
logger: consoleLogger,
});
if (!c.ok) {
console.error(c.log);
process.exit(1);
// Compile func files
for (const p of [{ path: path.join(__dirname, "..", "func") }]) {
const files = fs.readdirSync(p.path);
for (const file of files) {
if (!file.endsWith(".fc")) {
continue;
}
} catch (e) {
console.error(e);
console.error("Failed");
process.exit(1);
}
fs.writeFileSync(p.path + r + ".fift", c.fift!);
fs.writeFileSync(p.path + r + ".cell", c.output!);

// Cell -> Fift decompiler
const source = decompileAll({ src: c.output! });
fs.writeFileSync(p.path + r + ".rev.fift", source);
// Precompile
const funcFileFullPath = path.join(p.path, file);
logger.info(`Processing ${funcFileFullPath}`);
let c: FuncCompilationResult;
try {
const stdlibPath = path.resolve(
__dirname,
"..",
"stdlib",
"stdlib.fc",
);
const stdlib = fs.readFileSync(stdlibPath, "utf-8");
const code = fs.readFileSync(funcFileFullPath, "utf-8");
c = await funcCompile({
entries: [stdlibPath, funcFileFullPath],
sources: [
{
path: stdlibPath,
content: stdlib,
},
{
path: funcFileFullPath,
content: code,
},
],
logger: logger,
});
if (!c.ok) {
logger.error(c.log);
throw new Error(
`FunC compilation failed for ${funcFileFullPath}`,
);
}
} catch (e) {
logger.error(e as Error);
logger.error(`Failed for ${funcFileFullPath}`);
throw e;
}
fs.writeFileSync(funcFileFullPath + ".fift", c.fift!);
fs.writeFileSync(funcFileFullPath + ".cell", c.output!);

// Cell -> Fift decompiler
const source = decompileAll({ src: c.output! });
fs.writeFileSync(funcFileFullPath + ".rev.fift", source);
}
}
} catch (error) {
logger.error(error as Error);
process.exit(1);
}
})();
12 changes: 8 additions & 4 deletions src/browser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Config, verifyConfig } from "./config/parseConfig";
import { TactLogger } from "./logger";
import { Logger } from "./logger";
import { build } from "./pipeline/build";
import { createVirtualFileSystem } from "./vfs/createVirtualFileSystem";

export async function run(args: {
config: Config;
files: Record<string, string>;
logger?: TactLogger | null | undefined;
logger?: Logger;
}) {
// Verify config
const config = verifyConfig(args.config);
Expand All @@ -19,14 +19,18 @@ export async function run(args: {

// Compile
let success = true;
let errorCollection: Error[] = [];
for (const p of config.projects) {
const built = await build({
config: p,
project,
stdlib,
logger: args.logger,
});
success = success && built;
success = success && built.ok;
if (!built.ok) {
errorCollection = { ...errorCollection, ...built.error };
}
}
return success;
return { ok: success, error: errorCollection };
}
7 changes: 7 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,10 @@ export function idTextErr(
}
return `"${ident.text}"`;
}

export type TactErrorCollection =
| Error
| TactParseError
| TactCompilationError
| TactInternalCompilerError
| TactConstEvalError;
4 changes: 2 additions & 2 deletions src/func/funcCompile.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from "fs";
import path from "path";
import { consoleLogger } from "../logger";
import { Logger } from "../logger";
import { funcCompile } from "./funcCompile";
import files from "../imports/stdlib";

Expand All @@ -22,7 +22,7 @@ describe("funcCompile", () => {
},
{ path: "/small.fc", content: source },
],
logger: consoleLogger,
logger: new Logger(),
});
expect(res.ok).toBe(true);
});
Expand Down
7 changes: 3 additions & 4 deletions src/func/funcCompile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { TactLogger } from "../logger";
import { errorToString } from "../utils/errorToString";
import { Logger } from "../logger";

// Wasm Imports
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand Down Expand Up @@ -60,7 +59,7 @@ type CompileResult =
export async function funcCompile(args: {
entries: string[];
sources: { path: string; content: string }[];
logger: TactLogger;
logger: Logger;
}): Promise<FuncCompilationResult> {
// Parameters
const files: string[] = args.entries;
Expand Down Expand Up @@ -181,7 +180,7 @@ export async function funcCompile(args: {
}
}
} catch (e) {
args.logger.error(errorToString(e));
args.logger.error(e as Error);
throw Error("Unexpected compiler response");
} finally {
for (const i of allocatedFunctions) {
Expand Down
92 changes: 85 additions & 7 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,87 @@
export type TactLogger = {
log: (message: string) => void;
error: (message: string) => void;
};
export enum LogLevel {
/** Logging is turned off */
NONE,
/** Logs only error messages */
ERROR,
/** Logs warning and error messages */
WARN,
/** Logs informational, warning, and error messages */
INFO,
/** Logs debugging, informational, warning, and error messages */
DEBUG,
}

type messageType = string | Error;

export const consoleLogger: TactLogger = {
log: console.log,
error: console.error,
export interface LogMethods {
debug: (message: messageType) => void;
info: (message: messageType) => void;
warn: (message: messageType) => void;
error: (message: messageType) => void;
}

const logLevelToMethodName: { [key in LogLevel]: keyof LogMethods | null } = {
[LogLevel.NONE]: null,
[LogLevel.ERROR]: "error",
[LogLevel.WARN]: "warn",
[LogLevel.INFO]: "info",
[LogLevel.DEBUG]: "debug",
};

function getLoggingMethod(level: LogLevel): keyof LogMethods | null {
return logLevelToMethodName[level];
}

export class Logger {
private level: LogLevel;
private logMethods: LogMethods;

constructor(level: LogLevel = LogLevel.INFO) {
this.level = level;
this.logMethods = {
debug: console.log,
info: console.log,
warn: console.warn,
error: console.error,
};
}

protected log(level: LogLevel, message: messageType) {
if (this.level === LogLevel.NONE) {
return;
}

if (message instanceof Error) {
message = message.stack ?? message.message;
} else {
message = message.toString();
}

if (level > this.level) return;

const loggingMethod = getLoggingMethod(level);
if (!loggingMethod) return;

this.logMethods[loggingMethod](message);
}

debug(message: messageType) {
this.log(LogLevel.DEBUG, message);
}

info(message: messageType) {
this.log(LogLevel.INFO, message);
}

warn(message: messageType) {
this.log(LogLevel.WARN, message);
}

error(message: messageType) {
this.log(LogLevel.ERROR, message);
}

setLevel(level: LogLevel) {
this.level = level;
}
}
Loading

0 comments on commit 1d6dd06

Please sign in to comment.