| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- /** Shared contract for the Pascal compiler service (tutorial section 27).
- *
- * The frontend never spawns the compiler directly: it calls this service
- * over RPC and the backend runs `fpc` asynchronously.
- * Other compilers can implement the same interface without changing the rest of the IDE.
- */
- export const PascalCompilerServicePath = '/services/pascal-compiler';
- export const PascalCompiler = Symbol('PascalCompiler');
- export type BuildSeverity = 'error' | 'warning' | 'info';
- export interface BuildDiagnostic {
- /** File path as reported by the compiler (relative to the project root). */
- file: string;
- /** Zero-based line and character. */
- line: number;
- character: number;
- message: string;
- severity: BuildSeverity;
- }
- export interface BuildResult {
- success: boolean;
- exitCode: number;
- /** Exact command line that was executed (for transparency). */
- command: string;
- /** Combined stdout/stderr of the compiler. */
- output: string;
- diagnostics: BuildDiagnostic[];
- /** Project source statistics and output size (present on success). */
- stats?: BuildStats;
- }
- /** Source census for the build report dialog. */
- export interface BuildStats {
- /** Number of `.pas`/`.pp` files under the project root. */
- files: number;
- /** Total lines across those files. */
- lines: number;
- /** Built executable size in bytes (-1 when unavailable). */
- outputSize: number;
- }
- /** Human-readable byte count (`45.2 KB`, `3.1 MB`). Pure and testable. */
- export function formatBytes(bytes: number): string {
- if (!Number.isFinite(bytes) || bytes < 0) return '—';
- if (bytes < 1024) return `${bytes} B`;
- const kb = bytes / 1024;
- if (kb < 1024) return `${kb.toFixed(1)} KB`;
- return `${(kb / 1024).toFixed(1)} MB`;
- }
- export type CompilerType = 'fpc' | 'blaise';
- export interface CompilerOptions {
- /** Compiler backend: "fpc" (default) or the Blaise compiler. */
- type?: CompilerType;
- /** Compiler executable, e.g. "fpc" or an absolute path. */
- path: string;
- options: string[];
- }
- export interface PascalProject {
- name: string;
- /** Absolute workspace folder containing pascal.json. */
- root: string;
- /** Main file, relative to root (e.g. "src/Main.pas"). */
- main: string;
- /** Built executable, relative to root. Defaults to bin/<name>. */
- output?: string;
- compiler: CompilerOptions;
- /** True when pascal.json sets an explicit compiler path. */
- compilerPathExplicit: boolean;
- }
- export interface CompilerInfo {
- path: string;
- /** First line of `fpc --version`. */
- version: string;
- }
- export interface PascalCompiler {
- /** Probe a compiler executable; undefined when it cannot be run. */
- detect(compilerPath?: string): Promise<CompilerInfo | undefined>;
- /** Compile (and link) the project main file. */
- build(project: PascalProject): Promise<BuildResult>;
- /** Remove build outputs (best-effort). */
- clean(project: PascalProject): Promise<void>;
- /** Build, then execute the produced program and capture its output. */
- run(project: PascalProject): Promise<BuildResult>;
- }
- /** Minimal `pascal.json` reader (tutorial sections 23/33). Pure and testable. */
- export function parseProjectFile(root: string, jsonText: string): PascalProject {
- const raw = JSON.parse(jsonText) as {
- name?: unknown; main?: unknown; output?: unknown;
- compiler?: { type?: unknown; path?: unknown; options?: unknown };
- };
- if (typeof raw.name !== 'string' || raw.name.length === 0) {
- throw new Error('pascal.json: missing "name".');
- }
- if (typeof raw.main !== 'string' || raw.main.length === 0) {
- throw new Error('pascal.json: missing "main".');
- }
- const compiler = raw.compiler ?? {};
- const type = compiler.type === 'blaise' ? 'blaise' as const : 'fpc' as const;
- const options = Array.isArray(compiler.options)
- ? compiler.options.filter((o): o is string => typeof o === 'string')
- : [];
- const explicitPath = typeof compiler.path === 'string' && compiler.path.length > 0;
- return {
- name: raw.name,
- root,
- main: raw.main,
- output: typeof raw.output === 'string' ? raw.output : undefined,
- compiler: {
- type,
- path: explicitPath ? (compiler.path as string) : (type === 'blaise' ? 'blaise' : 'fpc'),
- options,
- },
- compilerPathExplicit: explicitPath,
- };
- }
- /** Default executable path for a project (bin/<name> under root). */
- export function defaultOutput(project: PascalProject): string {
- return project.output ?? `bin/${project.name}`;
- }
- export interface ProvidedTask {
- label: string;
- group?: { kind: 'build' | 'rebuild'; isDefault?: boolean } | 'clean';
- command: string;
- args: string[];
- dependsOn?: string;
- }
- /** Pure task list for a project (tested headlessly; the Theia provider wraps these). */
- export function buildProvidedTasks(project: PascalProject): ProvidedTask[] {
- const out = defaultOutput(project);
- const buildArgs = project.compiler.type === 'blaise'
- ? ['--source', project.main, '--output', out, ...project.compiler.options]
- : ['-g', ...project.compiler.options, '-o' + out, project.main];
- return [
- { label: 'Pascal: Build', group: { kind: 'build', isDefault: true }, command: project.compiler.path, args: buildArgs },
- { label: 'Pascal: Clean', group: 'clean', command: 'rm', args: ['-f', out] },
- { label: 'Pascal: Rebuild', group: { kind: 'rebuild' }, command: project.compiler.path, args: buildArgs },
- { label: 'Pascal: Run', command: `./${out}`, args: [], dependsOn: 'Pascal: Build' },
- ];
- }
|