Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
50 changes: 50 additions & 0 deletions .github/workflows/build-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Build and deploy runner

on:
push:
branches:
- main

jobs:
build:
name: Build runner
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Enable corepack
run: corepack enable

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build
run: yarn run build --all || yarn build

- name: Upload artifacts
id: deployment
uses: actions/upload-pages-artifact@v3
with:
path: dist/

deploy:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
needs: build
name: Deploy runner
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
2 changes: 1 addition & 1 deletion src/compiler/symbol-table.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { UnannType } from '../ast/types/classes'
import { ImportDeclaration } from '../ast/types/packages-and-modules'
import { METHOD_FLAGS } from '../ClassFile/types/methods'
import {
generateClassAccessFlags,
generateFieldAccessFlags,
Expand All @@ -12,7 +13,6 @@ import {
SymbolRedeclarationError
} from './error'
import { libraries } from './import/libs'
import { METHOD_FLAGS } from '../ClassFile/types/methods'

export const typeMap = new Map([
['byte', 'B'],
Expand Down
51 changes: 51 additions & 0 deletions src/conductor/JavaEvaluator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import setupJVM from '../jvm/index'
import BasicEvaluator, { IRunnerPlugin } from './runner'

/**
* Minimal Java conductor evaluator stub.
* Currently this evaluator is a placeholder that demonstrates how to
* integrate with the local JVM runner. It expects class file bytes
* encoded as a base64 string when used via conductor channels.
*/
export class JavaEvaluator extends BasicEvaluator {
constructor(conductor: IRunnerPlugin) {
super(conductor)
}

async evaluateChunk(_chunk: string): Promise<void> {
this.conductor.sendOutput('JavaEvaluator: evaluateChunk not supported; use evaluateFile with a .class file encoded as base64')
}

async evaluateFile(fileName: string, fileContent: string): Promise<void> {
try {
if (fileName.endsWith('.class')) {
// Expect class file content as base64 to allow conductor transport via JSON
Buffer.from(fileContent, 'base64')
// NOTE: we intentionally do not parse the class bytes here in the stub
// to avoid coupling the evaluator to the full classfile parser. A future
// implementation may call `parseBin` and then load into the JVM.
// create a JVM and run the Main class if present (not implemented in this stub)
setupJVM({
callbacks: {
readFileSync: (path: string) => {
throw new Error('readFileSync not available in conductor JavaEvaluator')
},
stdout: (m: string) => this.conductor.sendOutput(m),
stderr: (m: string) => this.conductor.sendOutput(`ERR: ${m}`)
}
})
Comment thread
kjw142857 marked this conversation as resolved.
Outdated
// Register a readFileSync that returns our in-memory class when requested
// The JVM runner currently expects classes on disk; for now we run using a minimal approach
this.conductor.sendOutput('JavaEvaluator: running class via in-memory runner is not yet implemented')
this.conductor.sendResult('')
return
}

this.conductor.sendOutput('JavaEvaluator: unsupported file type')
} catch (err) {
this.conductor.sendError(`${err instanceof Error ? err.message : String(err)}`)
}
}
}

export default JavaEvaluator
44 changes: 44 additions & 0 deletions src/conductor/__tests__/JavaEvaluator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { JavaEvaluator } from '../JavaEvaluator'
import { IRunnerPlugin } from '../runner'

class MockConductor implements IRunnerPlugin {
outputs: string[] = []
results: string[] = []
errors: string[] = []
statuses: Array<{ s: string; a: boolean }> = []
sendOutput(message: string): void {
this.outputs.push(message)
}
sendResult(result: string): void {
this.results.push(result)
}
sendError(error: string): void {
this.errors.push(error)
}
updateStatus(status: any, isActive: boolean): void {
this.statuses.push({ s: String(status), a: isActive })
}
}

test('evaluateFile with unsupported file emits message', async () => {
const mock = new MockConductor()
const ev = new JavaEvaluator(mock)
await ev.evaluateFile('hello.txt', 'console')
expect(mock.outputs.length).toBeGreaterThan(0)
expect(mock.outputs[0]).toMatch(/unsupported file type/)
expect(mock.errors.length).toBe(0)
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a test-case where the .class works? Is the PR still in progress? For now, I'm making it a draft till some functionality is supported

test('evaluateFile with .class base64 emits placeholder run message and result', async () => {
const mock = new MockConductor()
const ev = new JavaEvaluator(mock)
// Use a minimal dummy classfile header (CAFEBABE) as base64; the evaluator
// stub only inspects bytes and currently does not run them.
const buf = Buffer.from([0xca, 0xfe, 0xba, 0xbe])
const b64 = buf.toString('base64')
await ev.evaluateFile('Main.class', b64)
// JavaEvaluator currently emits a placeholder message then a result string
expect(mock.outputs.some(o => /running class via in-memory runner is not yet implemented/.test(o))).toBe(true)
// sendResult is called with an empty string in the stub
expect(mock.results.length).toBeGreaterThanOrEqual(1)
})
5 changes: 5 additions & 0 deletions src/conductor/evaluator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// `__EVALUATOR__` is replaced at build time by bundlers in other projects.
// We declare it here for type compatibility when bundle replacement is used.
declare const __EVALUATOR__: any

export default __EVALUATOR__
2 changes: 2 additions & 0 deletions src/conductor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { JavaEvaluator } from './JavaEvaluator'
export { default as BasicEvaluator } from './runner'
5 changes: 5 additions & 0 deletions src/conductor/initialise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { initialise } from './runner'
// @ts-expect-error — __EVALUATOR__ is replaced at build time by bundlers
import { __EVALUATOR__ } from './index'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of importing __EVALUATOR__ from ./index (which does not export it) and using @ts-expect-error, you can import it directly from ./evaluator where it is declared and exported as default. This provides full type safety and avoids compilation/bundling workarounds.

Suggested change
// @ts-expect-error — __EVALUATOR__ is replaced at build time by bundlers
import { __EVALUATOR__ } from './index'
import __EVALUATOR__ from './evaluator'


initialise(__EVALUATOR__)
30 changes: 30 additions & 0 deletions src/conductor/runner.ts
Comment thread
kjw142857 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export type RunnerStatus = 'IDLE' | 'RUNNING' | 'ERROR' | 'FINISHED'

export interface IRunnerPlugin {
sendOutput(message: string): void
sendResult(result: string): void
sendError(error: string): void
updateStatus(status: RunnerStatus, isActive: boolean): void
}

export abstract class BasicEvaluator {
protected conductor: IRunnerPlugin
constructor(conductor: IRunnerPlugin) {
this.conductor = conductor
}

abstract evaluateChunk(chunk: string): Promise<void>

async evaluateFile(fileName: string, fileContent: string): Promise<void> {
// default: treat file content as a single chunk
await this.evaluateChunk(fileContent)
}
}

export function initialise(_evaluator: any) {
// This is a minimal shim for initialise used in bundling entrypoints.
// Real conductor runner initialisation is out of scope for this repo.
Comment thread
kjw142857 marked this conversation as resolved.
Outdated
return
}

export default BasicEvaluator
2 changes: 1 addition & 1 deletion src/jvm/exception-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class ExceptionTable {
}

insert(from: number, to: number, target: number, type: ClassData): void {
var entry = new Entry(from, to, target, type)
const entry = new Entry(from, to, target, type)
Comment thread
kjw142857 marked this conversation as resolved.
Outdated
this.entries.push(entry)
}
}
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,10 @@ concat-map@0.0.1:
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==

"conductor@https://github.com/source-academy/conductor.git#0.3.0":
version "0.3.0"
resolved "https://github.com/source-academy/conductor.git#56e4a421005b909d0fef55ba8119a736b9bd1e0c"

convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
Expand Down
Loading