Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #149 : REPL sitio web #176

Merged
merged 10 commits into from
Sep 17, 2024
4 changes: 2 additions & 2 deletions scripts/download-libs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ const libsToDownload = [
libs: [
{
filename: "p5.js",
url: "http://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.js",
url: "http://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js",
},
{
filename: "p5.sound.js",
url: "http://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/addons/p5.sound.js",
url: "http://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/addons/p5.sound.min.js",
},
{
filename: "socket.io.esm.min.js",
Expand Down
89 changes: 12 additions & 77 deletions src/commands/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import http from 'http'
import logger from 'loglevel'
import { CompleterResult, Interface, createInterface as Repl } from 'readline'
import { Server, Socket } from 'socket.io'
import { Entity, Environment, Evaluation, Import, link, notEmpty, Package, Reference, Sentence, WollokException, parse, TO_STRING_METHOD, RuntimeObject, linkSentenceInNode, Interpreter, ParseError, WRENatives as natives } from 'wollok-ts'
import { getDataDiagram } from '../services/diagram-generator'
import { buildEnvironmentForProject, failureDescription, getFQN, publicPath, successDescription, valueDescription, validateEnvironment, handleError, ENTER, serverError, stackTrace, replIcon } from '../utils'
import { Entity, Environment, Evaluation, Interpreter, Package, REPL, interprete, link, WRENatives as natives } from 'wollok-ts'
import { logger as fileLogger } from '../logger'
import { getDataDiagram } from '../services/diagram-generator'
import { TimeMeasurer } from '../time-measurer'

export const REPL = 'REPL'
import { ENTER, buildEnvironmentForProject, failureDescription, getFQN, handleError, publicPath, replIcon, serverError, sanitizeStackTrace, successDescription, validateEnvironment, valueDescription } from '../utils'

// TODO:
// - autocomplete piola
Expand Down Expand Up @@ -44,7 +42,7 @@ export async function replFn(autoImportPath: string | undefined, options: Option
logger.info(`${replIcon} Initializing Wollok REPL ${autoImportPath ? `for file ${valueDescription(autoImportPath)} ` : ''}on ${valueDescription(options.project)}`)

let interpreter = await initializeInterpreter(autoImportPath, options)
const autoImportName = autoImportPath && replNode(interpreter.evaluation.environment).name
const autoImportName = autoImportPath && interpreter.evaluation.environment.replNode().name
const repl = Repl({
input: process.stdin,
output: process.stdout,
Expand Down Expand Up @@ -92,7 +90,7 @@ export async function replFn(autoImportPath: string | undefined, options: Option
if (line.startsWith(':')) commandHandler.parse(line.split(' '), { from: 'user' })
else {
history.push(line)
console.log(interprete(interpreter, line))
console.log(interpreteLine(interpreter, line))
dynamicDiagramClient.onReload(interpreter)
}
}
Expand All @@ -103,6 +101,11 @@ export async function replFn(autoImportPath: string | undefined, options: Option
return repl
}

export function interpreteLine(interpreter: Interpreter, line: string): string {
const { errored, result, error } = interprete(interpreter, line)
return errored ? failureDescription(result, error) : successDescription(result)
}

export async function initializeInterpreter(autoImportPath: string | undefined, { project, skipValidations }: Options): Promise<Interpreter> {
let environment: Environment
const timeMeasurer = new TimeMeasurer()
Expand All @@ -128,7 +131,7 @@ export async function initializeInterpreter(autoImportPath: string | undefined,
return new Interpreter(Evaluation.build(environment, natives))
} catch (error: any) {
handleError(error)
fileLogger.info({ message: `${replIcon} REPL execution - build failed for ${project}`, timeElapsed: timeMeasurer.elapsedTime(), ok: false, error: stackTrace(error) })
fileLogger.info({ message: `${replIcon} REPL execution - build failed for ${project}`, timeElapsed: timeMeasurer.elapsedTime(), ok: false, error: sanitizeStackTrace(error) })
return process.exit(12)
}
}
Expand Down Expand Up @@ -190,61 +193,6 @@ function defineCommands(autoImportPath: string | undefined, options: Options, re
// EVALUATION
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════

// TODO WOLLOK-TS: check if we should decouple the function
export function interprete(interpreter: Interpreter, line: string): string {
try {
const sentenceOrImport = parse.Import.or(parse.Variable).or(parse.Assignment).or(parse.Expression).tryParse(line)
const error = [sentenceOrImport, ...sentenceOrImport.descendants].flatMap(_ => _.problems ?? []).find(_ => _.level === 'error')
if (error) throw error

if (sentenceOrImport.is(Sentence)) {
const environment = interpreter.evaluation.environment
linkSentenceInNode(sentenceOrImport, replNode(environment))
const unlinkedNode = [sentenceOrImport, ...sentenceOrImport.descendants].find(_ => _.is(Reference) && !_.target)

if (unlinkedNode) {
if (unlinkedNode.is(Reference)) {
if (!interpreter.evaluation.currentFrame.get(unlinkedNode.name))
return failureDescription(`Unknown reference ${valueDescription(unlinkedNode.name)}`)
} else return failureDescription(`Unknown reference at ${unlinkedNode.sourceInfo}`)
}

const result = interpreter.exec(sentenceOrImport)
const stringResult = result
? showInnerValue(interpreter, result)
: ''
return successDescription(stringResult)
}

if (sentenceOrImport.is(Import)) {
if (!interpreter.evaluation.environment.getNodeOrUndefinedByFQN(sentenceOrImport.entity.name))
throw new Error(
`Unknown reference ${valueDescription(sentenceOrImport.entity.name)}`
)

newImport(sentenceOrImport, interpreter.evaluation.environment)

return successDescription('')
}

return successDescription('')
} catch (error: any) {
return (
error.type === 'ParsimmonError' ? failureDescription(`Syntax error:\n${error.message.split('\n').filter(notEmpty).slice(1).join('\n')}`) :
error instanceof WollokException ? failureDescription('Evaluation Error!', error) :
error instanceof ParseError ? failureDescription(`Syntax Error at offset ${error.sourceMap.start.offset}: ${line.slice(error.sourceMap.start.offset, error.sourceMap.end.offset)}`) :
failureDescription('Uh-oh... Unexpected TypeScript Error!', error)
)
}
}

function showInnerValue(interpreter: Interpreter, obj: RuntimeObject) {
if (obj!.innerValue === null) return 'null'
return typeof obj.innerValue === 'string'
? `"${obj.innerValue}"`
: interpreter.send(TO_STRING_METHOD, obj)!.innerString!
}

async function autocomplete(input: string): Promise<CompleterResult> {
const completions = ['fafafa', 'fefefe', 'fofofo']
const hits = completions.filter((c) => c.startsWith(input))
Expand Down Expand Up @@ -302,17 +250,4 @@ export async function initializeClient(options: Options, repl: Interface, interp
app,
server,
}
}

// TODO WOLLOK-TS: migrate it? Maybe it could be part of Environment
// Environment.newImportFor(baseNode, importNode)
function newImport(importNode: Import, environment: Environment) {
const node = replNode(environment)
const imported = node.scope.resolve<Package | Entity>(importNode.entity.name)!
if (imported.is(Package)) {
return node.scope.include(imported.scope)
}
return node.scope.register([imported.name!, imported])
}

export const replNode = (environment: Environment): Package => environment.getNodeByFQN<Package>(REPL)
}
6 changes: 3 additions & 3 deletions src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import logger from 'loglevel'
import { join, relative } from 'path'
import { Server, Socket } from 'socket.io'
import { Asset, SoundState, VisualState, boardState, buildKeyPressEvent, queueEvent, soundState, visualState } from 'wollok-game-web/dist/utils'
import { Environment, GAME_MODULE, Interpreter, Name, Package, RuntimeObject, WollokException, interpret, link, WRENatives as natives, parse } from 'wollok-ts'
import { Environment, GAME_MODULE, Name, Package, RuntimeObject, WollokException, interpret, link, WRENatives as natives, parse, Interpreter } from 'wollok-ts'
import { logger as fileLogger } from '../logger'
import { getDataDiagram } from '../services/diagram-generator'
import { ENTER, buildEnvironmentForProject, buildEnvironmentIcon, failureDescription, folderIcon, gameIcon, handleError, isValidAsset, isValidImage, isValidSound, programIcon, publicPath, readPackageProperties, serverError, stackTrace, successDescription, validateEnvironment, valueDescription } from '../utils'
import { ENTER, buildEnvironmentForProject, buildEnvironmentIcon, failureDescription, folderIcon, gameIcon, handleError, isValidAsset, isValidImage, isValidSound, programIcon, publicPath, readPackageProperties, serverError, sanitizeStackTrace, successDescription, validateEnvironment, valueDescription } from '../utils'
import { TimeMeasurer } from './../time-measurer'

const { time, timeEnd } = console
Expand Down Expand Up @@ -74,7 +74,7 @@ export default async function (programFQN: Name, options: Options): Promise<void
}
} catch (error: any) {
handleError(error)
fileLogger.info({ message: `${game ? gameIcon : programIcon} ${game ? 'Game' : 'Program'} executed ${programFQN} on ${project}`, timeElapsed: timeMeasurer.elapsedTime(), ok: false, error: stackTrace(error) })
fileLogger.info({ message: `${game ? gameIcon : programIcon} ${game ? 'Game' : 'Program'} executed ${programFQN} on ${project}`, timeElapsed: timeMeasurer.elapsedTime(), ok: false, error: sanitizeStackTrace(error) })
if (!game) { process.exit(21) }
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { bold, red } from 'chalk'
import { time, timeEnd } from 'console'
import logger from 'loglevel'
import { Entity, Environment, Node, Test, is, match, when, WRENatives as natives, interpret, Describe } from 'wollok-ts'
import { buildEnvironmentForProject, failureDescription, successDescription, valueDescription, validateEnvironment, handleError, ENTER, stackTrace, buildEnvironmentIcon, testIcon } from '../utils'
import { buildEnvironmentForProject, failureDescription, successDescription, valueDescription, validateEnvironment, handleError, ENTER, sanitizeStackTrace, buildEnvironmentIcon, testIcon } from '../utils'
import { logger as fileLogger } from '../logger'
import { TimeMeasurer } from '../time-measurer'
import { Package } from 'wollok-ts'
Expand Down Expand Up @@ -139,7 +139,7 @@ export default async function (filter: string | undefined, options: Options): Pr

const failuresForLogging = failures.map(([test, error]) => ({
test: test.fullyQualifiedName,
error: stackTrace(error),
error: sanitizeStackTrace(error),
}))
fileLogger.info({ message: `${testIcon} Test runner executed ${filter ? `matching ${filter} ` : ''}on ${project}`, result: { ok: successes, failed: failures.length }, failures: failuresForLogging, timeElapsed: timeMeasurer.elapsedTime() })

Expand Down
Loading
Loading