literate-elm
Version:
Runs literate Elm code blocks and calculates Elm expressions
319 lines • 13.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runProgram = void 0;
const elm_string_representation_1 = require("elm-string-representation");
const fs_extra_1 = require("fs-extra");
const lodash_1 = __importDefault(require("lodash"));
const object_hash_1 = __importDefault(require("object-hash"));
const path_1 = require("path");
const auxFiles = __importStar(require("./shared/auxFiles"));
const tools_1 = require("./shared/tools");
const programTimeout = 20000;
const outputSymbolName = "literateElmOutputSymbol";
const chunkifyProgram = (program) => {
const chunks = [];
lodash_1.default.forEach(program.codeNodes, (codeNode, i) => {
chunks.push({
type: "codeNode",
ref: codeNode,
text: `-------- literate-elm code ${i}\n${codeNode.text}`,
});
});
const expressionNodesGroupedByText = lodash_1.default.groupBy(program.expressionNodes, (expressionNode) => expressionNode.text);
const orderedExpressionTexts = lodash_1.default.sortBy(lodash_1.default.keys(expressionNodesGroupedByText));
chunks.push({
type: "auxiliary",
text: `-------- literate-elm output
${outputSymbolName} : String
${outputSymbolName} =
Json.Encode.encode 0 <|
Json.Encode.object
[`,
});
lodash_1.default.forEach(orderedExpressionTexts, (text, i) => {
chunks.push({
type: "expressionText",
ref: expressionNodesGroupedByText[text],
text: `-------- literate-elm output expression ${text}
${i > 0 ? "," : " "} ("${text.replace(/"/g, '\\"')}", Json.Encode.string <| Debug.toString <| ${text})`,
});
});
chunks.push({
type: "auxiliary",
text: `-------- literate-elm output end\n ]\n`,
});
// only import Json.Encode if not done so in user code
const containsJsonEncodeImport = lodash_1.default.some(chunks, (codeChunk) => `\n${codeChunk.text}`.match(/\n\s*import\s+Json\.Encode/));
if (!containsJsonEncodeImport) {
chunks.unshift({
type: "auxiliary",
text: `import Json.Encode`,
});
}
const programName = `Program${(0, object_hash_1.default)(chunks.map((chunk) => chunk.text))}`;
chunks.unshift({
type: "auxiliary",
text: `module ${programName} exposing (..)`,
});
// measure vertical offset for each code chunk to map errors later
let offsetY = 0;
lodash_1.default.forEach(chunks, (chunk) => {
const lineCount = (chunk.text.match(/\n/g) || []).length + 1;
chunk.offsetY = offsetY;
offsetY += lineCount;
});
return {
name: programName,
chunks,
maxFileIndex: Math.max(...lodash_1.default.map([...program.codeNodes, ...program.expressionNodes], (node) => node.fileIndex || 0)),
};
};
const runChunkifiedProgram = (chunkifiedProgram, workingDirectory, keepElmFiles = false) => __awaiter(void 0, void 0, void 0, function* () {
const programPath = (0, path_1.resolve)(workingDirectory, `${chunkifiedProgram.name}.elm`);
const codeToRun = chunkifiedProgram.chunks.map(({ text }) => text).join("\n");
try {
let runElmResult;
yield (0, fs_extra_1.writeFile)(programPath, codeToRun, "utf8");
try {
runElmResult = yield (0, tools_1.runElm)(workingDirectory, programPath, outputSymbolName);
}
catch (e) {
const linesInStderr = (e.message || "").split("\n");
let parsedErrorOutput;
lodash_1.default.findLast(linesInStderr, (line) => {
try {
parsedErrorOutput = JSON.parse(line);
return true;
}
catch (_a) {
return false;
}
});
if (parsedErrorOutput && lodash_1.default.isArray(parsedErrorOutput.errors)) {
return {
status: "failed",
errors: parsedErrorOutput.errors,
};
}
else if (parsedErrorOutput) {
return {
status: "failed",
errors: [parsedErrorOutput],
};
}
else {
throw e;
}
}
return {
status: "succeeded",
errors: [],
evaluatedExpressionTextMap: JSON.parse(runElmResult.output || "{}"),
debugLog: runElmResult.debugLog,
};
}
catch (e) {
// some messages need to be patched to avoid confusing output
const message = (e.message || "")
.replace(/^Compilation failed\n/, "")
.replace(/\n{2,}/, "\n");
const indexOfFirstNewline = message.indexOf("\n");
const overview = indexOfFirstNewline !== -1
? message.substring(0, indexOfFirstNewline)
: message;
const details = indexOfFirstNewline !== -1
? message.substring(indexOfFirstNewline + 1)
: "";
return {
status: "failed",
errors: [
{
overview,
details,
region: {
start: { line: 1, column: 1 },
end: { line: 1, column: 1 },
},
},
],
};
}
finally {
if (!keepElmFiles) {
yield (0, fs_extra_1.remove)(programPath);
}
}
});
const getErrorMessageText = (error) => {
if (error.title === "UNKNOWN IMPORT") {
// Unknown imports form a special case. It is not reasonable to suggest looking into elm.json and source-directories
// as this is causing confusion in the literate-elm environment.
const failedImport = lodash_1.default.get(error, ["message", 1, "string"]);
if (failedImport) {
return `Could not ${failedImport}. Please make sure you have specified all dependencies on third-party Elm modules.`;
}
}
if (lodash_1.default.isArray(error.message)) {
const text = error.message
.map((chunk) => {
if (chunk.string) {
// remove underlines with ^^^^
if (chunk.string === "^".repeat(chunk.string.length)) {
return "__REMOVED_UNDERNLINE__";
}
return chunk.string;
}
return chunk;
})
.join("");
return text
.replace(/\s*__REMOVED_UNDERNLINE__\s*/g, "\n")
.replace(/\n\d+\|/g, "\n"); // remove line numbers in listings
}
return `${error.overview || error}`;
};
const convertErrorsToMessages = (chunkifiedProgram, errors) => {
const result = [];
lodash_1.default.map(lodash_1.default.get(errors, [0, "problems"], errors), (error) => {
const currentChunk = chunkifiedProgram.chunks[lodash_1.default.findIndex(chunkifiedProgram.chunks, (chunk) => (chunk.offsetY || 0) + 1 >
lodash_1.default.get(error, ["region", "start", "line"], 0)) - 1];
if (currentChunk && currentChunk.type === "codeNode") {
const position = {
start: {
line: lodash_1.default.get(error, ["region", "start", "line"], 0) -
(currentChunk.offsetY || 0) -
1 +
currentChunk.ref.position.start.line,
column: lodash_1.default.get(error, ["region", "start", "column"], 0) -
1 +
currentChunk.ref.position.start.column,
},
end: {
line: lodash_1.default.get(error, ["region", "end", "line"], 0) -
(currentChunk.offsetY || 0) -
1 +
currentChunk.ref.position.start.line,
column: lodash_1.default.get(error, ["region", "end", "column"], 0) -
1 +
currentChunk.ref.position.start.column,
},
};
result.push({
text: getErrorMessageText(error),
position,
severity: "error",
fileIndex: currentChunk.ref.fileIndex || 0,
node: currentChunk.ref,
});
}
else if (currentChunk && currentChunk.type === "expressionText") {
const messageText = getErrorMessageText(error);
const expressionNodes = currentChunk.ref;
lodash_1.default.forEach(expressionNodes, (expressionNode) => {
result.push({
text: messageText,
position: expressionNode.position,
fileIndex: expressionNode.fileIndex || 0,
severity: "error",
node: expressionNode,
});
});
}
else {
result.push({
text: getErrorMessageText(error),
severity: "error",
position: {
start: { line: 1, column: 1 },
end: { line: 1, column: 1 },
},
fileIndex: chunkifiedProgram.maxFileIndex,
node: null,
});
}
});
return result;
};
const runProgram = (program) => __awaiter(void 0, void 0, void 0, function* () {
const chunkifiedProgram = chunkifyProgram(program);
const programBasePath = (0, path_1.resolve)(program.environment.workingDirectory, chunkifiedProgram.name);
yield auxFiles.touch(programBasePath);
const programResultPath = `${programBasePath}.result.json`;
let cachedResult;
try {
yield auxFiles.ensureUnlocked(programBasePath, programTimeout);
cachedResult = (yield (0, fs_extra_1.readJson)(programResultPath));
}
catch (e) {
yield auxFiles.lock(programBasePath);
cachedResult = yield runChunkifiedProgram(chunkifiedProgram, program.environment.workingDirectory);
yield (0, fs_extra_1.writeFile)(programResultPath, JSON.stringify(cachedResult), "utf8");
yield auxFiles.unlock(programBasePath);
}
if (cachedResult && cachedResult.status === "succeeded") {
const evaluatedExpressions = program.expressionNodes.map((expressionNode) => {
const valueStringRepresentation = (cachedResult.evaluatedExpressionTextMap &&
cachedResult.evaluatedExpressionTextMap[expressionNode.text]) ||
"";
let value;
try {
value = (0, elm_string_representation_1.parseUsingCache)(valueStringRepresentation);
}
catch (e) {
value = e;
}
return {
node: expressionNode,
value,
valueStringRepresentation,
};
});
return {
program,
status: "succeeded",
evaluatedExpressions,
messages: convertErrorsToMessages(chunkifiedProgram, (cachedResult && cachedResult.errors) || []),
debugLog: cachedResult.debugLog instanceof Array ? cachedResult.debugLog : [],
};
}
else {
return {
program,
status: "failed",
messages: convertErrorsToMessages(chunkifiedProgram, (cachedResult && cachedResult.errors) || []),
};
}
});
exports.runProgram = runProgram;
//# sourceMappingURL=runProgram.js.map