@jpetit/toolkit 3.0.23 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/assets/prompts/creators/create-solution.tpl.txt +10 -0
  2. package/assets/prompts/creators/create-statement.tpl.txt +21 -0
  3. package/assets/prompts/creators/create-translation.tpl.txt +5 -0
  4. package/assets/prompts/creators/private-test-cases.txt +6 -0
  5. package/assets/prompts/creators/sample-test-cases.txt +6 -0
  6. package/assets/prompts/creators/system-prompt.txt +2 -0
  7. package/assets/prompts/examples/statement-coda.tex +7 -0
  8. package/assets/prompts/examples/statement.tex +19 -0
  9. package/assets/prompts/generators/efficiency.md +41 -0
  10. package/assets/prompts/generators/hard.md +47 -0
  11. package/assets/prompts/generators/random.md +39 -0
  12. package/assets/prompts/proglangs/cc.md +3 -0
  13. package/assets/prompts/proglangs/py.md +40 -0
  14. package/lib/ai.ts +60 -4
  15. package/lib/cleaner.ts +24 -13
  16. package/lib/compilers/base.ts +70 -14
  17. package/lib/compilers/clojure.ts +21 -10
  18. package/lib/compilers/gcc.ts +4 -33
  19. package/lib/compilers/ghc.ts +4 -40
  20. package/lib/compilers/gxx.ts +4 -33
  21. package/lib/compilers/index.ts +9 -0
  22. package/lib/compilers/java.ts +105 -0
  23. package/lib/compilers/python3.ts +44 -37
  24. package/lib/compilers/run-clojure.ts +101 -0
  25. package/lib/compilers/run-haskell.ts +26 -22
  26. package/lib/compilers/run-python.ts +29 -35
  27. package/lib/compilers/rust.ts +39 -0
  28. package/lib/create-with-jutgeai.ts +407 -0
  29. package/lib/create-with-template.ts +55 -0
  30. package/lib/data.ts +6 -0
  31. package/lib/doctor.ts +86 -6
  32. package/lib/generate.ts +132 -290
  33. package/lib/helpers.ts +48 -0
  34. package/lib/inspector.ts +253 -0
  35. package/lib/jutge_api_client.ts +4631 -0
  36. package/lib/maker.ts +202 -289
  37. package/lib/settings.ts +26 -17
  38. package/lib/tui.ts +25 -15
  39. package/lib/types.ts +40 -5
  40. package/lib/upload.ts +216 -0
  41. package/lib/utils.ts +82 -14
  42. package/lib/versions.ts +46 -0
  43. package/package.json +50 -11
  44. package/toolkit/about.ts +43 -0
  45. package/toolkit/ai.ts +44 -18
  46. package/toolkit/check.ts +16 -0
  47. package/toolkit/clean.ts +16 -26
  48. package/toolkit/compilers.ts +4 -4
  49. package/toolkit/config.ts +91 -0
  50. package/toolkit/create.ts +30 -58
  51. package/toolkit/doctor.ts +15 -11
  52. package/toolkit/generate.ts +195 -98
  53. package/toolkit/index.ts +32 -21
  54. package/toolkit/make.ts +12 -48
  55. package/toolkit/upgrade.ts +9 -0
  56. package/toolkit/upload.ts +19 -0
  57. package/toolkit/create-jutge-ai.ts +0 -101
  58. package/toolkit/create-template.ts +0 -55
  59. package/toolkit/create-wizard.ts +0 -6
  60. package/toolkit/init.ts +0 -56
  61. package/toolkit/verify.ts +0 -19
@@ -1,9 +1,4 @@
1
- import { type Handler } from '../types'
2
- import tui from '../tui'
3
1
  import { Compiler } from './base'
4
- import { rm, exists } from 'fs/promises'
5
- import { join } from 'path'
6
- import { execa } from 'execa'
7
2
 
8
3
  export class GCC_Compiler extends Compiler {
9
4
  id(): string {
@@ -34,35 +29,11 @@ export class GCC_Compiler extends Compiler {
34
29
  return ''
35
30
  }
36
31
 
37
- extension(): string {
38
- return 'c'
32
+ tool(): string {
33
+ return 'gcc'
39
34
  }
40
35
 
41
- async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
42
- const exePath = sourcePath + '.exe'
43
- const fullExePath = join(directory, exePath)
44
- await rm(fullExePath, { force: true })
45
-
46
- if (handler.source_modifier === 'none') {
47
- tui.command(`gcc ${this.flags1()} ${sourcePath} -o ${exePath}`)
48
- await execa({
49
- reject: false,
50
- stderr: 'inherit',
51
- stdout: 'inherit',
52
- cwd: directory,
53
- })`gcc ${this.flags1().split(' ')} ${sourcePath} -o ${exePath}`
54
- } else if (handler.source_modifier === 'no_main') {
55
- tui.command(`gcc ${this.flags1()} ${sourcePath} main.c -o ${exePath}`)
56
- await execa({
57
- reject: false,
58
- stderr: 'inherit',
59
- stdout: 'inherit',
60
- cwd: directory,
61
- })`gcc ${this.flags1().split(' ')} ${sourcePath} main.c -o ${exePath}`
62
- } else {
63
- throw new Error(`Unknown source modifier: ${handler.source_modifier}`)
64
- }
65
-
66
- if (!(await exists(fullExePath))) throw new Error(`Compilation failed for ${sourcePath}`)
36
+ extension(): string {
37
+ return 'c'
67
38
  }
68
39
  }
@@ -1,9 +1,3 @@
1
- import { execa } from 'execa'
2
- import { exists, rm } from 'fs/promises'
3
- import { concatText } from '../utils'
4
- import { join } from 'path'
5
- import tui from '../tui'
6
- import { type Handler } from '../types'
7
1
  import { Compiler } from './base'
8
2
 
9
3
  export class GHC_Compiler extends Compiler {
@@ -35,41 +29,11 @@ export class GHC_Compiler extends Compiler {
35
29
  return '-O3'
36
30
  }
37
31
 
38
- extension(): string {
39
- return 'hs'
32
+ tool(): string {
33
+ return 'ghc'
40
34
  }
41
35
 
42
- async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
43
- const outputPath = sourcePath + '.exe'
44
- const fullOutputPath = join(directory, outputPath)
45
- await rm(fullOutputPath, { force: true })
46
-
47
- if (handler.source_modifier === 'none') {
48
- tui.command(`ghc ${this.flags1()} ${sourcePath} -o ${outputPath}`)
49
- await execa({
50
- reject: false,
51
- stderr: 'inherit',
52
- stdout: 'inherit',
53
- cwd: directory,
54
- })`ghc ${this.flags1().split(' ')} ${sourcePath} -o ${outputPath}`
55
- } else if (handler.source_modifier === 'no_main') {
56
- const wrapperPath = `wrapper-${sourcePath}`
57
-
58
- tui.command(`concat ${sourcePath} and main.hs into ${wrapperPath}`, { italic: true })
59
- await concatText([join(directory, sourcePath), join(directory, 'main.hs')], join(directory, wrapperPath))
60
-
61
- tui.command(`ghc ${this.flags1()} ${wrapperPath} -o ${outputPath}`)
62
-
63
- await execa({
64
- reject: false,
65
- stderr: 'inherit',
66
- stdout: 'inherit',
67
- cwd: directory,
68
- })`ghc ${this.flags1().split(' ')} ${wrapperPath} -o ${outputPath}`
69
- } else {
70
- throw new Error(`Unknown source modifier: ${handler.source_modifier}`)
71
- }
72
-
73
- if (!(await exists(fullOutputPath))) throw new Error(`Compilation failed for ${sourcePath}`)
36
+ extension(): string {
37
+ return 'hs'
74
38
  }
75
39
  }
@@ -1,8 +1,3 @@
1
- import { execa } from 'execa'
2
- import { exists, rm } from 'fs/promises'
3
- import { join } from 'path'
4
- import tui from '../tui'
5
- import { type Handler } from '../types'
6
1
  import { Compiler } from './base'
7
2
 
8
3
  export class GXX_Compiler extends Compiler {
@@ -34,35 +29,11 @@ export class GXX_Compiler extends Compiler {
34
29
  return '-std=c++17 -D_JUDGE_ -O2 -DNDEBUG -Wall -Wextra -Wno-sign-compare -Wshadow'
35
30
  }
36
31
 
37
- extension(): string {
38
- return 'cc'
32
+ tool(): string {
33
+ return 'g++'
39
34
  }
40
35
 
41
- async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
42
- const outputPath = sourcePath + '.exe'
43
- const fullOutputPath = join(directory, outputPath)
44
- await rm(fullOutputPath, { force: true })
45
-
46
- if (handler.source_modifier === 'none') {
47
- tui.command(`g++ ${this.flags1()} ${sourcePath} -o ${outputPath}`)
48
- await execa({
49
- reject: false,
50
- stderr: 'inherit',
51
- stdout: 'inherit',
52
- cwd: directory,
53
- })`g++ ${this.flags1().split(' ')} ${sourcePath} -o ${outputPath}`
54
- } else if (handler.source_modifier === 'no_main') {
55
- tui.command(`g++ ${this.flags1()} ${sourcePath} main.cc -o ${outputPath}`)
56
- await execa({
57
- reject: false,
58
- stderr: 'inherit',
59
- stdout: 'inherit',
60
- cwd: directory,
61
- })`g++ ${this.flags1().split(' ')} ${sourcePath} main.cc -o ${outputPath}`
62
- } else {
63
- throw new Error(`Unknown source modifier: ${handler.source_modifier}`)
64
- }
65
-
66
- if (!(await exists(fullOutputPath))) throw new Error(`Compilation failed for ${sourcePath}`)
36
+ extension(): string {
37
+ return 'cc'
67
38
  }
68
39
  }
@@ -5,8 +5,11 @@ import { GXX_Compiler } from './gxx'
5
5
  import { Python3_Compiler } from './python3'
6
6
  import { GHC_Compiler } from './ghc'
7
7
  import { Clojure_Compiler } from './clojure'
8
+ import { Java_Compiler } from './java'
9
+ import { Rust_Compiler } from './rust'
8
10
  import { RunPython_Compiler } from './run-python'
9
11
  import { RunHaskell_Compiler } from './run-haskell'
12
+ import { RunClojure_Compiler } from './run-clojure'
10
13
 
11
14
  const compilersRegistryById: Record<string, new () => Compiler> = {
12
15
  C: GCC_Compiler,
@@ -14,8 +17,12 @@ const compilersRegistryById: Record<string, new () => Compiler> = {
14
17
  Python3: Python3_Compiler,
15
18
  Haskell: GHC_Compiler,
16
19
  Clojure: Clojure_Compiler,
20
+ Java: Java_Compiler,
21
+ Rust: Rust_Compiler,
22
+
17
23
  RunPython: RunPython_Compiler,
18
24
  RunHaskell: RunHaskell_Compiler,
25
+ RunClojure: RunClojure_Compiler,
19
26
  }
20
27
 
21
28
  const compilersRegistryByExtension: Record<string, new () => Compiler> = {
@@ -24,6 +31,8 @@ const compilersRegistryByExtension: Record<string, new () => Compiler> = {
24
31
  py: Python3_Compiler,
25
32
  hs: GHC_Compiler,
26
33
  clj: Clojure_Compiler,
34
+ java: Java_Compiler,
35
+ rs: Rust_Compiler,
27
36
  }
28
37
 
29
38
  export function getCompilerById(id: string): Compiler {
@@ -0,0 +1,105 @@
1
+ import { execa } from 'execa'
2
+ import { join, parse } from 'path'
3
+ import tui from '../tui'
4
+ import type { Handler } from '../types'
5
+ import { nothing, toolkitPrefix } from '../utils'
6
+ import { Compiler } from './base'
7
+
8
+ export class Java_Compiler extends Compiler {
9
+ id(): string {
10
+ return 'Java'
11
+ }
12
+
13
+ name(): string {
14
+ return 'Java'
15
+ }
16
+
17
+ type(): string {
18
+ return 'vm'
19
+ }
20
+
21
+ language(): string {
22
+ return 'Java'
23
+ }
24
+
25
+ async version(): Promise<string> {
26
+ return await this.getVersion('javac --version', 0)
27
+ }
28
+
29
+ flags1(): string {
30
+ return ''
31
+ }
32
+
33
+ flags2(): string {
34
+ return ''
35
+ }
36
+
37
+ tool(): string {
38
+ return 'javac'
39
+ }
40
+
41
+ extension(): string {
42
+ return 'java'
43
+ }
44
+
45
+ override async compileNormal(handler: Handler, directory: string, sourcePath: string): Promise<string> {
46
+ const classPath = 'Main.class'
47
+
48
+ await this.rmInDir(directory, classPath)
49
+
50
+ tui.command(`${this.tool()} ${sourcePath}`)
51
+ await execa({
52
+ reject: false,
53
+ stderr: 'inherit',
54
+ stdout: 'inherit',
55
+ cwd: directory,
56
+ })`${this.tool()} ${sourcePath}`
57
+
58
+ return classPath
59
+ }
60
+
61
+ override async compileWithMain(handler: Handler, directory: string, sourcePath: string): Promise<string> {
62
+ const classPath = 'Main.class'
63
+
64
+ tui.command(`add main.${this.extension()} to ${sourcePath}`)
65
+ await this.concatText(directory, [`main.${this.extension()}`, sourcePath], sourcePath)
66
+
67
+ await this.rmInDir(directory, classPath)
68
+
69
+ tui.command(`${this.tool()} ${sourcePath}`)
70
+ await execa({
71
+ reject: false,
72
+ stderr: 'inherit',
73
+ stdout: 'inherit',
74
+ cwd: directory,
75
+ })`${this.tool()} ${sourcePath}`
76
+
77
+ return classPath
78
+ }
79
+
80
+ override async execute(
81
+ handler: Handler,
82
+ directory: string,
83
+ sourcePath: string,
84
+ inputPath: string,
85
+ outputPath: string,
86
+ ): Promise<void> {
87
+ const className = 'Main'
88
+ const classPath = className + '.class'
89
+
90
+ await this.rmInDir(directory, outputPath)
91
+ const input = await this.getInput(directory, inputPath)
92
+
93
+ tui.command(`java ${className} < ${inputPath} > ${outputPath}`)
94
+
95
+ const { exitCode } = await execa({
96
+ reject: false,
97
+ input,
98
+ stdout: { file: join(directory, outputPath) },
99
+ stderr: 'inherit',
100
+ cwd: directory,
101
+ })`java ${className}`
102
+
103
+ if (exitCode !== 0) throw new Error(`Execution failed for ${classPath}`)
104
+ }
105
+ }
@@ -1,9 +1,8 @@
1
1
  import { execa } from 'execa'
2
- import { exists, rm } from 'fs/promises'
3
2
  import { join } from 'path'
4
3
  import tui from '../tui'
5
4
  import type { Handler } from '../types'
6
- import { readText, writeText } from '../utils'
5
+ import { readText, toolkitPrefix, writeText } from '../utils'
7
6
  import { Compiler } from './base'
8
7
 
9
8
  export class Python3_Compiler extends Compiler {
@@ -35,40 +34,55 @@ export class Python3_Compiler extends Compiler {
35
34
  return ''
36
35
  }
37
36
 
37
+ tool(): string {
38
+ return 'python3'
39
+ }
40
+
38
41
  extension(): string {
39
42
  return 'py'
40
43
  }
41
44
 
42
- async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
43
- const wrapperPath = `wrapper-${sourcePath}`
44
- const fullWrapperPath = join(directory, wrapperPath)
45
- const fullSourcePath = join(directory, sourcePath)
46
- const source = await readText(fullSourcePath)
47
-
48
- tui.command(`wrap ${sourcePath} to ${wrapperPath}`, { italic: true })
49
- let wrapperSource: string
50
- if (handler.source_modifier === 'none') {
51
- wrapperSource = source
52
- .replace('import turtle', 'import turtle_pil as turtle')
53
- .replace('from turtle import', 'from turtle_pil import')
54
- } else if (handler.source_modifier === 'no_main' || handler.source_modifier === 'structs') {
55
- const main = await readText(join(directory, 'main.py'))
56
- wrapperSource = source + '\n\n\n' + main
57
- } else {
58
- throw new Error(`Unknown source modifier: ${handler.source_modifier as string}`)
45
+ override async compileNormal(handler: Handler, directory: string, sourcePath: string): Promise<string> {
46
+ const source = await readText(join(directory, sourcePath))
47
+
48
+ tui.command(`tweak ${sourcePath} for turtle compatibility`)
49
+ const modSource = source
50
+ .replace('import turtle', 'import turtle_pil as turtle')
51
+ .replace('from turtle import', 'from turtle_pil import')
52
+ await writeText(join(directory, sourcePath), modSource)
53
+
54
+ tui.command(`python3 -m py_compile ${sourcePath}`)
55
+ const { exitCode } = await execa({
56
+ reject: false,
57
+ stderr: 'inherit',
58
+ stdout: 'inherit',
59
+ cwd: directory,
60
+ })`python3 -m py_compile ${sourcePath}`
61
+
62
+ if (exitCode !== 0) {
63
+ throw new Error(`Compilation failed for ${sourcePath}`)
59
64
  }
60
- await writeText(fullWrapperPath, wrapperSource)
61
65
 
62
- tui.command(`python3 -m py_compile ${wrapperPath}`)
66
+ return sourcePath
67
+ }
68
+
69
+ override async compileWithMain(handler: Handler, directory: string, sourcePath: string): Promise<string> {
70
+ tui.command(`add main.${this.extension()} to ${sourcePath}`)
71
+ await this.concatText(directory, [sourcePath, `main.${this.extension()}`], sourcePath)
63
72
 
73
+ tui.command(`python3 -m py_compile ${sourcePath}`)
64
74
  const { exitCode } = await execa({
65
75
  reject: false,
66
76
  stderr: 'inherit',
67
77
  stdout: 'inherit',
68
78
  cwd: directory,
69
- })`python3 -m py_compile ${wrapperPath}`
79
+ })`python3 -m py_compile ${sourcePath}`
70
80
 
71
- if (exitCode !== 0) throw new Error(`Compilation failed for ${sourcePath}`)
81
+ if (exitCode !== 0) {
82
+ throw new Error(`Compilation failed for ${sourcePath}`)
83
+ }
84
+
85
+ return sourcePath
72
86
  }
73
87
 
74
88
  override async execute(
@@ -78,28 +92,21 @@ export class Python3_Compiler extends Compiler {
78
92
  inputPath: string,
79
93
  outputPath: string,
80
94
  ): Promise<void> {
81
- const wrapperPath = `wrapper-${sourcePath}`
82
- const fullWrapperPath = join(directory, wrapperPath)
83
-
84
- if (!(await exists(join(directory, wrapperPath)))) {
85
- throw new Error(`Executable file ${wrapperPath} does not exist in directory ${directory}`)
86
- }
95
+ const exePath = sourcePath
87
96
 
88
- const fullInputPath = join(directory, inputPath)
89
- const fullOutputPath = join(directory, outputPath)
90
- await rm(fullOutputPath, { force: true })
91
- const input = await readText(fullInputPath)
97
+ await this.rmInDir(directory, outputPath)
98
+ const input = await this.getInput(directory, inputPath)
92
99
 
93
- tui.command(`python3 ${wrapperPath} < ${inputPath} > ${outputPath}`)
100
+ tui.command(`python3 ${exePath} < ${inputPath} > ${outputPath}`)
94
101
 
95
102
  const { exitCode } = await execa({
96
103
  reject: false,
97
104
  input,
98
- stdout: { file: fullOutputPath },
105
+ stdout: { file: join(directory, outputPath) },
99
106
  stderr: 'inherit',
100
107
  cwd: directory,
101
- })`python3 ${wrapperPath}`
108
+ })`python3 ${exePath}`
102
109
 
103
- if (exitCode !== 0) throw new Error(`Execution failed for ${wrapperPath}`)
110
+ if (exitCode !== 0) throw new Error(`Execution failed for ${exePath}`)
104
111
  }
105
112
  }
@@ -0,0 +1,101 @@
1
+ import { execa } from 'execa'
2
+ import { join, parse } from 'path'
3
+ import tui from '../tui'
4
+ import type { Handler } from '../types'
5
+ import { nothing, readText, toolkitPrefix, writeText } from '../utils'
6
+ import { Compiler } from './base'
7
+
8
+ export class RunClojure_Compiler extends Compiler {
9
+ id(): string {
10
+ return 'RunClojure'
11
+ }
12
+
13
+ name(): string {
14
+ return 'RunClojure'
15
+ }
16
+
17
+ type(): string {
18
+ return 'interpreter'
19
+ }
20
+
21
+ language(): string {
22
+ return 'Clojure'
23
+ }
24
+
25
+ async version(): Promise<string> {
26
+ return await this.getVersion('clj --version', 0)
27
+ }
28
+
29
+ flags1(): string {
30
+ return ''
31
+ }
32
+
33
+ flags2(): string {
34
+ return ''
35
+ }
36
+
37
+ tool(): string {
38
+ return 'clj'
39
+ }
40
+
41
+ extension(): string {
42
+ return 'clj'
43
+ }
44
+
45
+ override async compileNormal(handler: Handler, directory: string, sourcePath: string): Promise<string> {
46
+ await nothing()
47
+
48
+ tui.warning(`No compilation available for Clojure`)
49
+
50
+ return sourcePath
51
+ }
52
+
53
+ override async compileWithMain(handler: Handler, directory: string, sourcePath: string): Promise<string> {
54
+ await nothing()
55
+ throw new Error('Method not implemented.')
56
+ }
57
+
58
+ override async execute(
59
+ handler: Handler,
60
+ directory: string,
61
+ sourcePath: string,
62
+ inputPath: string,
63
+ outputPath: string,
64
+ ): Promise<void> {
65
+ const newSourcePath = `${toolkitPrefix()}-${parse(sourcePath).name}-${parse(inputPath).name}.clj`
66
+
67
+ tui.command(`merge ${sourcePath} ${inputPath} > ${newSourcePath}`)
68
+ await this.mergeScripts(directory, sourcePath, inputPath, newSourcePath)
69
+
70
+ tui.command(`clj -M ${newSourcePath} > ${outputPath}`)
71
+
72
+ const { exitCode } = await execa({
73
+ reject: false,
74
+ stdout: { file: join(directory, outputPath) },
75
+ stderr: 'inherit',
76
+ cwd: directory,
77
+ })`clj -M ${newSourcePath}`
78
+
79
+ if (exitCode !== 0) throw new Error(`Execution failed for ${newSourcePath}`)
80
+ }
81
+
82
+ async mergeScripts(
83
+ directory: string,
84
+ scriptPath1: string,
85
+ scriptPath2: string,
86
+ outputScriptPath: string,
87
+ ): Promise<void> {
88
+ const script1 = await readText(join(directory, scriptPath1))
89
+ const script2 = await readText(join(directory, scriptPath2))
90
+ let mergedScript = script1
91
+ mergedScript += '\n\n\n'
92
+ for (const line of script2.split('\n')) {
93
+ if (line.trim() === '') {
94
+ mergedScript += '\n'
95
+ } else {
96
+ mergedScript += `(println ${line})\n`
97
+ }
98
+ }
99
+ await writeText(join(directory, outputScriptPath), mergedScript)
100
+ }
101
+ }
@@ -1,9 +1,8 @@
1
1
  import { execa } from 'execa'
2
- import { rm } from 'fs/promises'
3
- import { join } from 'path'
2
+ import { join, parse } from 'path'
4
3
  import tui from '../tui'
5
4
  import type { Handler } from '../types'
6
- import { readText, writeText } from '../utils'
5
+ import { nothing, readText, toolkitPrefix, writeText } from '../utils'
7
6
  import { Compiler } from './base'
8
7
 
9
8
  export class RunHaskell_Compiler extends Compiler {
@@ -16,7 +15,7 @@ export class RunHaskell_Compiler extends Compiler {
16
15
  }
17
16
 
18
17
  type(): string {
19
- return 'compiler'
18
+ return 'interpreter'
20
19
  }
21
20
 
22
21
  language(): string {
@@ -24,7 +23,7 @@ export class RunHaskell_Compiler extends Compiler {
24
23
  }
25
24
 
26
25
  async version(): Promise<string> {
27
- return await this.getVersion('ghc --version', 0)
26
+ return await this.getVersion('runhaskell --version', 0)
28
27
  }
29
28
 
30
29
  flags1(): string {
@@ -35,15 +34,15 @@ export class RunHaskell_Compiler extends Compiler {
35
34
  return ''
36
35
  }
37
36
 
37
+ tool(): string {
38
+ return 'ghc'
39
+ }
40
+
38
41
  extension(): string {
39
42
  return 'hs'
40
43
  }
41
44
 
42
- async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
43
- if (handler.source_modifier !== 'none') {
44
- throw new Error(`Unknown source modifier: ${handler.source_modifier as string}`)
45
- }
46
-
45
+ override async compileNormal(handler: Handler, directory: string, sourcePath: string): Promise<string> {
47
46
  // ghci -e ':q' solution.hs
48
47
  // This will load and typecheck the file, then immediately quit.
49
48
  // If there are compilation errors, they'll be shown. If it loads successfully and just exits, the code compiles.
@@ -58,7 +57,16 @@ export class RunHaskell_Compiler extends Compiler {
58
57
  cwd: directory,
59
58
  })`ghci -e :q ${sourcePath}`
60
59
 
61
- if (exitCode !== 0) throw new Error(`Compilation failed for ${sourcePath}`)
60
+ if (exitCode !== 0) {
61
+ throw new Error(`Compilation failed for ${sourcePath}`)
62
+ }
63
+
64
+ return sourcePath
65
+ }
66
+
67
+ override async compileWithMain(handler: Handler, directory: string, sourcePath: string): Promise<string> {
68
+ await nothing()
69
+ throw new Error('Method not implemented.')
62
70
  }
63
71
 
64
72
  override async execute(
@@ -68,25 +76,21 @@ export class RunHaskell_Compiler extends Compiler {
68
76
  inputPath: string,
69
77
  outputPath: string,
70
78
  ): Promise<void> {
71
- const mergedPath = `solution-${inputPath}.hs.exe`
72
-
73
- tui.command(`Merging solution.hs and ${inputPath} into ${mergedPath}`, { italic: true })
74
- await this.mergeScripts(directory, 'solution.hs', inputPath, mergedPath)
75
-
76
- const fullOutputPath = join(directory, outputPath)
79
+ const newSourcePath = `${toolkitPrefix()}-${parse(sourcePath).name}-${parse(inputPath).name}.hs`
77
80
 
78
- await rm(fullOutputPath, { force: true })
81
+ tui.command(`merge ${sourcePath} ${inputPath} > ${newSourcePath}`)
82
+ await this.mergeScripts(directory, sourcePath, inputPath, newSourcePath)
79
83
 
80
- tui.command(`runhaskell ${mergedPath} > ${outputPath}`)
84
+ tui.command(`runhaskell ${newSourcePath} > ${outputPath}`)
81
85
 
82
86
  const { exitCode } = await execa({
83
87
  reject: false,
84
- stdout: { file: fullOutputPath },
88
+ stdout: { file: join(directory, outputPath) },
85
89
  stderr: 'inherit',
86
90
  cwd: directory,
87
- })`runhaskell ${mergedPath}`
91
+ })`runhaskell ${newSourcePath}`
88
92
 
89
- if (exitCode !== 0) throw new Error(`Execution failed for ${mergedPath}`)
93
+ if (exitCode !== 0) throw new Error(`Execution failed for ${newSourcePath}`)
90
94
  }
91
95
 
92
96
  async mergeScripts(