Naive state management + syntax check works!
This commit is contained in:
parent
bdf12cafdb
commit
d8cc597eba
14 changed files with 501 additions and 60 deletions
19
Makefile
19
Makefile
|
|
@ -9,6 +9,7 @@ all: build
|
|||
.PHONY: public
|
||||
public: \
|
||||
public/openscad.js \
|
||||
public/openscad-worker-inlined.js \
|
||||
public/libraries/fonts.zip \
|
||||
public/libraries/NopSCADlib.zip \
|
||||
public/libraries/BOSL.zip \
|
||||
|
|
@ -25,17 +26,27 @@ public: \
|
|||
public/libraries/UB.scad.zip
|
||||
|
||||
clean:
|
||||
rm -rf libs build
|
||||
rm -rf public/libraries
|
||||
rm -fR libs build
|
||||
rm -fR public/openscad-worker-inlined.js
|
||||
rm -fR public/openscad.{js,wasm}
|
||||
rm -fR public/libraries
|
||||
rm -fR src/wasm
|
||||
|
||||
build: public
|
||||
npm run build
|
||||
|
||||
public/openscad.js:
|
||||
src/wasm/openscad.js:
|
||||
wget ${WASM_BUILD_URL} -O OpenSCAD-WebAssembly.zip
|
||||
( cd public && rm openscad.{js,wasm} && unzip ../OpenSCAD-WebAssembly.zip )
|
||||
mkdir -p src/wasm
|
||||
( cd src/wasm && rm -f openscad.{js,wasm} && unzip ../../OpenSCAD-WebAssembly.zip )
|
||||
rm OpenSCAD-WebAssembly.zip
|
||||
|
||||
public/openscad.js: src/wasm/openscad.js
|
||||
cp src/wasm/openscad.{js,wasm} public
|
||||
|
||||
public/openscad-worker-inlined.js: src/openscad-worker.ts src/wasm/openscad.js
|
||||
npx rollup -c
|
||||
|
||||
public/libraries/fonts.zip: libs/liberation
|
||||
mkdir -p public/libraries
|
||||
cp fonts.conf libs/liberation
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -14,8 +14,7 @@
|
|||
"monaco-editor": "^0.36.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "^5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
@ -44,7 +43,13 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"@types/filesystem": "^0.0.32",
|
||||
"@types/node": "^18.15.6"
|
||||
"@types/node": "^18.15.6",
|
||||
"rollup": "^2.79.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.2",
|
||||
"webpack-cli": "^5.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
rollup.config.js
Normal file
24
rollup.config.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// npx rollup -c
|
||||
// npx rollup ./src/openscad-worker.ts -o public/openscad-worker-inlined.js -f cjs
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
export default {
|
||||
input: 'src/openscad-worker.ts',
|
||||
output: {
|
||||
file: 'public/openscad-worker-inlined.js',
|
||||
format: 'es'
|
||||
},
|
||||
plugins: [
|
||||
typescript({
|
||||
// outDir: 'public2',
|
||||
rootDir: 'src',
|
||||
}),
|
||||
replace({
|
||||
'import.meta.url': JSON.stringify(process.env.dev
|
||||
? 'http://localhost:3000/'
|
||||
: 'https://ochafik.com/openscad/openscad-worker-inlined.js'),
|
||||
})
|
||||
|
||||
]
|
||||
};
|
||||
80
src/App.tsx
80
src/App.tsx
|
|
@ -1,46 +1,56 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {State} from './app-state'
|
||||
import Editor from '@monaco-editor/react';
|
||||
import {ModelContext, State} from './app-state'
|
||||
import Editor, { loader, Monaco } from '@monaco-editor/react';
|
||||
import './App.css';
|
||||
import openscadEditorOptions from './language/openscad-editor-options';
|
||||
import { writeStateInFragment } from './state';
|
||||
import { Model } from './model';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
let monacoInstance: Monaco
|
||||
loader.init().then(mi => monacoInstance = mi);
|
||||
|
||||
export function App({initialState}: {initialState: State}) {
|
||||
const [state, rawSetState] = useState(initialState);
|
||||
|
||||
const setState = (state: State) => {
|
||||
rawSetState(state);
|
||||
writeStateInFragment(state);
|
||||
};
|
||||
|
||||
const value = state.source.content;
|
||||
const setValue = (content?: string) => setState({...state, source: {...state.source, content: content ?? ''}});
|
||||
const [state, setState] = useState(initialState);
|
||||
const [editor, setEditor] = useState(null as monaco.editor.IStandaloneCodeEditor | null)
|
||||
const model = new Model(state, setState);
|
||||
|
||||
if (editor) {
|
||||
const editorModel = editor.getModel();
|
||||
if (editorModel && state.checkerRun) {
|
||||
monacoInstance.editor.setModelMarkers(editorModel, 'openscad', state.checkerRun.markers);
|
||||
}
|
||||
}
|
||||
|
||||
const source = model.source;
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src="logo.png" className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://openscad.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn OpenSCAD
|
||||
</a>
|
||||
<Editor
|
||||
className="openscad-editor"
|
||||
defaultLanguage="openscad"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
options={openscadEditorOptions}
|
||||
height="50vh"/>
|
||||
</header>
|
||||
</div>
|
||||
<ModelContext.Provider value={model}>
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src="logo.png" className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://openscad.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn OpenSCAD
|
||||
</a>
|
||||
<Editor
|
||||
className="openscad-editor"
|
||||
defaultLanguage="openscad"
|
||||
value={source}
|
||||
onChange={s => model.source = s ?? ''}
|
||||
onMount={e => setEditor(e)} // TODO: This looks a bit silly, does it trigger a re-render??
|
||||
options={openscadEditorOptions}
|
||||
height="50vh"/>
|
||||
</header>
|
||||
</div>
|
||||
</ModelContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
90
src/actions.ts
Normal file
90
src/actions.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { spawnOpenSCAD } from "./openscad-runner";
|
||||
import { joinMergedOutputs, parseMergedOutputs } from "./output-parser";
|
||||
import { AbortablePromise, turnIntoDelayableExecution } from './utils';
|
||||
|
||||
const syntaxDelay = 300;
|
||||
|
||||
type SyntaxCheckOutput = {logText: string, markers: monaco.editor.IMarkerData[]};
|
||||
export const checkSyntax = (source: string, callback: (out: SyntaxCheckOutput) => void) =>
|
||||
turnIntoDelayableExecution(syntaxDelay, () => {
|
||||
// const timestamp = Date.now();
|
||||
|
||||
const job = spawnOpenSCAD({
|
||||
inputs: [['input.scad', source + '\n']],
|
||||
args: ["input.scad", "-o", "out.ast"],
|
||||
});
|
||||
|
||||
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
|
||||
(async () => {
|
||||
try {
|
||||
const result = await job;
|
||||
// console.log(result);
|
||||
const logText = joinMergedOutputs(result.mergedOutputs);
|
||||
const markers = parseMergedOutputs(result.mergedOutputs);
|
||||
res({logText, markers});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
rej(e);
|
||||
}
|
||||
})()
|
||||
return () => job.kill();
|
||||
});
|
||||
}, callback);
|
||||
|
||||
var sourceFileName;
|
||||
// var editor;
|
||||
|
||||
var renderDelay = 1000;
|
||||
type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis?: number}
|
||||
|
||||
export const render = (source: string, features: string[], callback: (result: RenderOutput) => void) =>
|
||||
turnIntoDelayableExecution(renderDelay, () => {
|
||||
|
||||
const job = spawnOpenSCAD({
|
||||
// wasmMemory,
|
||||
inputs: [['input.scad', source]],
|
||||
args: [
|
||||
"input.scad",
|
||||
"-o", "out.stl",
|
||||
"--export-format=binstl",
|
||||
...features.map(f => `--enable=${f}`),
|
||||
],
|
||||
outputPaths: ['out.stl']
|
||||
});
|
||||
|
||||
return AbortablePromise<RenderOutput>((resolve, reject) => {
|
||||
(async () => {
|
||||
try {
|
||||
const result = await job;
|
||||
console.log(result);
|
||||
|
||||
const logText = joinMergedOutputs(result.mergedOutputs);
|
||||
const markers = parseMergedOutputs(result.mergedOutputs);
|
||||
|
||||
if (result.error) {
|
||||
reject(result.error);
|
||||
}
|
||||
|
||||
const [output] = result.outputs ?? [];
|
||||
if (!output) throw 'No output from runner!'
|
||||
const [filePath, content] = output;
|
||||
const filePathFragments = filePath.split('/');
|
||||
const fileName = filePathFragments[filePathFragments.length - 1];
|
||||
|
||||
// TODO: have the runner accept and return files.
|
||||
const blob = new Blob([content], { type: "application/octet-stream" });
|
||||
// console.log(new TextDecoder().decode(content));
|
||||
const stlFile = new File([blob], fileName);
|
||||
resolve({stlFile, logText, markers, elapsedMillis: result.elapsedMillis});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
reject(e);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => job.kill()
|
||||
});
|
||||
}, callback);
|
||||
|
|
@ -1,9 +1,18 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
import { createContext } from "react"
|
||||
import { Model } from "./model"
|
||||
|
||||
export interface State {
|
||||
source: {
|
||||
content: string
|
||||
params: {
|
||||
source: string,
|
||||
},
|
||||
checkerRun?: {
|
||||
logText: string,
|
||||
markers: monaco.editor.IMarkerData[]
|
||||
}
|
||||
output?: {
|
||||
path: string,
|
||||
timestamp: number,
|
||||
|
|
@ -12,3 +21,12 @@ export interface State {
|
|||
},
|
||||
};
|
||||
|
||||
export const ModelContext = createContext(new Model(
|
||||
{
|
||||
params: {
|
||||
source: ''
|
||||
}
|
||||
},
|
||||
() => { throw new Error('Not implemented'); }
|
||||
));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
// import FS from "browserfs/dist/node/core/FS";
|
||||
import { BrowserFS } from "./BrowserFS";
|
||||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { zipArchives } from "./zip-archives";
|
||||
|
||||
export interface FS {
|
||||
readdir(path: string, cb: (err: any, files: string[]) => void): void;
|
||||
symlink(target: string, source: string): void;
|
||||
}
|
||||
declare var BrowserFS: BrowserFSInterface
|
||||
|
||||
export type FSMounts = {
|
||||
[n: string]: {fs: string, options: {zipData: Buffer}}
|
||||
|
|
@ -43,12 +40,12 @@ export async function symlinkLibraries(archiveNames: string[], fs: FS, prefix='/
|
|||
};
|
||||
|
||||
await Promise.all(archiveNames.map(n => (async () => {
|
||||
if (!(n in zipArchives)) throw `Archive named ${n} invalid (valid ones: ${Object.keys(zipArchives).join(', ')})`;
|
||||
if (!(n in zipArchives)) throw new Error(`Archive named ${n} invalid (valid ones: ${Object.keys(zipArchives).join(', ')})`);
|
||||
const {symlinks} = (zipArchives)[n];
|
||||
if (symlinks) {
|
||||
for (const from in symlinks) {
|
||||
const to = symlinks[from];
|
||||
const target = to == '.' ? `${prefix}/${n}` : `${prefix}/${n}/${to}`;
|
||||
const target = to === '.' ? `${prefix}/${n}` : `${prefix}/${n}/${to}`;
|
||||
const source = from.startsWith('/') ? from : `${cwd}/${from}`;
|
||||
await createSymlink(target, source);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { State } from "./app-state";
|
||||
import { validateString } from "./utils";
|
||||
|
||||
export function writeStateInFragment(state: State) {
|
||||
window.location.hash = encodeURIComponent(JSON.stringify(state));
|
||||
}
|
||||
export function readStateFromFragment() {
|
||||
export function readStateFromFragment(): State | null {
|
||||
if (window.location.hash.startsWith('#') && window.location.hash.length > 1) {
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
|
||||
const state = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
|
||||
return {
|
||||
params: {
|
||||
source: validateString(state.params?.source),
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
|
|
@ -6,7 +8,7 @@ import reportWebVitals from './reportWebVitals';
|
|||
import { createEditorFS } from './filesystem';
|
||||
import { registerOpenSCADLanguage } from './language/openscad-register-language';
|
||||
import { zipArchives } from './zip-archives';
|
||||
import {readStateFromFragment} from './state'
|
||||
import {readStateFromFragment} from './fragment-state'
|
||||
import { State } from './app-state';
|
||||
|
||||
(async () => {
|
||||
|
|
@ -16,8 +18,8 @@ import { State } from './app-state';
|
|||
await registerOpenSCADLanguage(fs, workingDir, zipArchives);
|
||||
|
||||
const initialState = readStateFromFragment() ?? {
|
||||
source: {
|
||||
content: 'cube(1);\ntranslate([0.5, 0.5, 0.5])\n\tcube(1);',
|
||||
params: {
|
||||
source: 'cube(1);\ntranslate([0.5, 0.5, 0.5])\n\tcube(1);',
|
||||
}
|
||||
} as State;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import React from 'react';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
export default {
|
||||
|
|
|
|||
42
src/model.ts
Normal file
42
src/model.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { checkSyntax } from "./actions";
|
||||
import { State } from "./app-state";
|
||||
import { bubbleUpDeepMutations } from "./deep-mutate";
|
||||
import { writeStateInFragment } from "./fragment-state";
|
||||
|
||||
export class Model {
|
||||
constructor(private state: State, private setState_: (state: State) => void) {}
|
||||
|
||||
private setState(state: State) {
|
||||
this.state = state;
|
||||
this.setState_(state);
|
||||
writeStateInFragment(state);
|
||||
}
|
||||
|
||||
mutate(f: (state: State) => void) {
|
||||
const state = this.state;
|
||||
const mutated = bubbleUpDeepMutations(state, f);
|
||||
// No matter how deep the mutation happened, the top-level object's identity
|
||||
// will have changed iff the mutated values are different.
|
||||
if (mutated !== state) {
|
||||
this.setState(mutated);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get source(): string {
|
||||
return this.state.params.source;//.source, this.state.editor;//params.source.content;
|
||||
}
|
||||
|
||||
|
||||
set source(source) {
|
||||
if (this.mutate(s => s.params.source = source)) {
|
||||
checkSyntax(source, checkerRun => this.mutate(s => s.checkerRun = checkerRun))({now: false});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
64
src/openscad-runner.ts
Normal file
64
src/openscad-runner.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { MergedOutputs } from "./openscad-worker";
|
||||
import { AbortablePromise } from "./utils";
|
||||
|
||||
export function createWasmMemory({maximumMegabytes, maximumBytes}: {maximumMegabytes: number, maximumBytes: number}) {
|
||||
const pageSize = 64 * 1024; // 64KB
|
||||
if (!maximumBytes) {
|
||||
maximumBytes = maximumMegabytes * 1024 * 1024;
|
||||
}
|
||||
return new WebAssembly.Memory({
|
||||
initial: Math.floor(maximumBytes / 2 / pageSize),
|
||||
maximum: Math.floor(maximumBytes / pageSize),
|
||||
shared: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Output is {outputs: [name, content][], mergedOutputs: [{(stderr|stdout|error)?: string}], exitCode: number}
|
||||
export type OpenSCADInvocation = {
|
||||
wasmMemory?: WebAssembly.Memory,
|
||||
inputs?: [string, string][],
|
||||
args: string[],
|
||||
outputPaths?: string[],
|
||||
}
|
||||
export type OpenSCADInvocationResults = {
|
||||
exitCode: number,
|
||||
error?: string,
|
||||
outputs?: [string, string][],
|
||||
mergedOutputs: MergedOutputs,
|
||||
elapsedMillis?: number,
|
||||
}
|
||||
|
||||
export function spawnOpenSCAD({inputs, args, outputPaths}: OpenSCADInvocation): AbortablePromise<OpenSCADInvocationResults> {
|
||||
var worker: Worker | null;
|
||||
var rejection: (err: any) => void;
|
||||
|
||||
function terminate() {
|
||||
if (!worker) {
|
||||
return;
|
||||
}
|
||||
worker.terminate();
|
||||
worker = null;
|
||||
}
|
||||
|
||||
return AbortablePromise<OpenSCADInvocationResults>((resolve, reject) => {
|
||||
worker = new Worker('./openscad-worker-inlined.js');//, {type: "module"})
|
||||
// if (navigator.userAgent.indexOf(' Chrome/') < 0) {
|
||||
// worker = new Worker('./openscad-worker-firefox.js'); // {'type': 'module'}
|
||||
// } else {
|
||||
// worker = new Worker('./openscad-worker.js', {'type': 'module'});
|
||||
// }
|
||||
rejection = reject;
|
||||
worker.onmessage = (e: {data: OpenSCADInvocationResults}) => {
|
||||
resolve(e.data);
|
||||
terminate();
|
||||
}
|
||||
worker.postMessage({inputs, args, outputPaths})
|
||||
|
||||
return () => {
|
||||
// rejection({error: 'Terminated'});
|
||||
terminate();
|
||||
};
|
||||
});
|
||||
}
|
||||
106
src/openscad-worker.ts
Normal file
106
src/openscad-worker.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import OpenSCAD from "./wasm/openscad.js";
|
||||
|
||||
import { getBrowserFSLibrariesMounts, symlinkLibraries } from "./filesystem";
|
||||
import { OpenSCADInvocation, OpenSCADInvocationResults } from "./openscad-runner";
|
||||
import { zipArchives } from "./zip-archives";
|
||||
declare var BrowserFS: BrowserFSInterface
|
||||
|
||||
// importScripts("browserfs.min.js");
|
||||
importScripts("https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.min.js");
|
||||
|
||||
const allArchiveNames = Object.keys(zipArchives)
|
||||
const allZipMountsPromise = getBrowserFSLibrariesMounts(allArchiveNames);
|
||||
|
||||
export type MergedOutputs = {stdout?: string, stderr?: string, error?: string}[];
|
||||
|
||||
addEventListener('message', async (e) => {
|
||||
|
||||
const { inputs, args, outputPaths, wasmMemory } = e.data as OpenSCADInvocation;
|
||||
|
||||
const mergedOutputs: MergedOutputs = [];
|
||||
try {
|
||||
const instance = await OpenSCAD({
|
||||
wasmMemory,
|
||||
buffer: wasmMemory && wasmMemory.buffer,
|
||||
noInitialRun: true,
|
||||
'print': (text: string) => {
|
||||
// console.log('stdout: ' + text);
|
||||
mergedOutputs.push({ stdout: text })
|
||||
},
|
||||
'printErr': (text: string) => {
|
||||
// console.log('stderr: ' + text);
|
||||
mergedOutputs.push({ stderr: text })
|
||||
},
|
||||
});
|
||||
|
||||
// await browserFSInit;
|
||||
await new Promise(async (resolve, reject) => {
|
||||
BrowserFS.install(self);
|
||||
BrowserFS.configure({
|
||||
fs: "MountableFileSystem",
|
||||
options: {
|
||||
...await allZipMountsPromise,
|
||||
// "/": { fs: "InMemory" },
|
||||
}
|
||||
}, function (e) { if (e) reject(e); else resolve(null); });
|
||||
});
|
||||
|
||||
const archiveNames = allArchiveNames;
|
||||
|
||||
// instance.FS.mkdir('tmp');
|
||||
instance.FS.mkdir('/tmp/run');
|
||||
instance.FS.mkdir('/libraries');
|
||||
|
||||
// await browserFSInit;
|
||||
|
||||
// https://github.com/emscripten-core/emscripten/issues/10061
|
||||
const BFS = new BrowserFS.EmscriptenFS(
|
||||
instance.FS,
|
||||
instance.PATH ?? {
|
||||
join2: (a: string, b: string) => `${a}/${b}`,
|
||||
join: (...args: string[]) => args.join('/'),
|
||||
}, instance.ERRNO_CODES ?? {});
|
||||
instance.FS.mount(BFS, {root: '/'}, '/libraries');
|
||||
|
||||
await symlinkLibraries(archiveNames, instance.FS, '/libraries', '/tmp/run');
|
||||
|
||||
instance.FS.chdir('/tmp/run');
|
||||
|
||||
// console.log('.', instance.FS.readdir('.'));
|
||||
// console.log('fonts', instance.FS.readdir('fonts'));
|
||||
// console.log('BOSL2', instance.FS.readdir('BOSL2'));
|
||||
|
||||
if (inputs) {
|
||||
for (const [path, content] of inputs) {
|
||||
instance.FS.writeFile(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
console.debug('Calling main ', args)
|
||||
const start = performance.now();
|
||||
const exitCode = instance.callMain(args);
|
||||
const end = performance.now();
|
||||
|
||||
const result: OpenSCADInvocationResults = {
|
||||
outputs: outputPaths && await Promise.all(outputPaths.map((path: string) => [path, instance.FS.readFile(path)])),
|
||||
mergedOutputs,
|
||||
exitCode,
|
||||
elapsedMillis: end - start
|
||||
}
|
||||
|
||||
console.debug(result);
|
||||
|
||||
postMessage(result);
|
||||
} catch (e) {
|
||||
|
||||
console.error(e);
|
||||
const error = `${e}`;
|
||||
mergedOutputs.push({ error });
|
||||
postMessage({
|
||||
error,
|
||||
mergedOutputs,
|
||||
} as OpenSCADInvocationResults);
|
||||
}
|
||||
});
|
||||
68
src/output-parser.ts
Normal file
68
src/output-parser.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { MergedOutputs } from "./openscad-worker";
|
||||
|
||||
export function joinMergedOutputs(mergedOutputs: MergedOutputs) {
|
||||
let allLines = [];
|
||||
for (const {stderr, stdout, error} of mergedOutputs){
|
||||
allLines.push(stderr ?? stdout ?? `EXCEPTION: ${error}`);
|
||||
}
|
||||
|
||||
return allLines.join("\n");
|
||||
}
|
||||
|
||||
export function parseMergedOutputs(mergedOutputs: MergedOutputs): monaco.editor.IMarkerData[] {
|
||||
let unmatchedLines = [];
|
||||
|
||||
const markers = [];
|
||||
let warningCount = 0, errorCount = 0;
|
||||
const addError = (error: string, file: string, line: number) => {
|
||||
markers.push({
|
||||
startLineNumber: Number(line),
|
||||
startColumn: 1,
|
||||
endLineNumber: Number(line),
|
||||
endColumn: 1000,
|
||||
message: error,
|
||||
severity: monaco.MarkerSeverity.Error
|
||||
})
|
||||
}
|
||||
for (const {stderr, stdout, error} of mergedOutputs){
|
||||
if (stderr) {
|
||||
if (stderr.startsWith('ERROR:')) errorCount++;
|
||||
if (stderr.startsWith('WARNING:')) warningCount++;
|
||||
|
||||
let m = /^ERROR: Parser error in file "([^"]+)", line (\d+): (.*)$/.exec(stderr)
|
||||
if (m) {
|
||||
const [_, file, line, error] = m
|
||||
addError(error, file, Number(line));
|
||||
continue;
|
||||
}
|
||||
|
||||
m = /^ERROR: Parser error: (.*?) in file ([^",]+), line (\d+)$/.exec(stderr)
|
||||
if (m) {
|
||||
const [_, error, file, line] = m
|
||||
addError(error, file, Number(line));
|
||||
continue;
|
||||
}
|
||||
|
||||
m = /^WARNING: (.*?),? in file ([^,]+), line (\d+)\.?/.exec(stderr);
|
||||
if (m) {
|
||||
const [_, warning, file, line] = m
|
||||
markers.push({
|
||||
startLineNumber: Number(line),
|
||||
startColumn: 1,
|
||||
endLineNumber: Number(line),
|
||||
endColumn: 1000,
|
||||
message: warning,
|
||||
severity: monaco.MarkerSeverity.Warning
|
||||
})
|
||||
continue;
|
||||
}
|
||||
}
|
||||
unmatchedLines.push(stderr ?? stdout ?? `EXCEPTION: ${error}`);
|
||||
}
|
||||
if (errorCount || warningCount) unmatchedLines = [`${errorCount} errors, ${warningCount} warnings!`, '', ...unmatchedLines];
|
||||
|
||||
// logsElement.innerText = unmatchedLines.join("\n")
|
||||
|
||||
return markers;
|
||||
}
|
||||
Loading…
Reference in a new issue