Skip to content
This repository has been archived by the owner on Nov 4, 2023. It is now read-only.

Commit

Permalink
feat: stub in factorio entites
Browse files Browse the repository at this point in the history
  • Loading branch information
cdaringe committed Jan 18, 2021
1 parent 56185ae commit 7afe747
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 190 deletions.
12 changes: 12 additions & 0 deletions src/factorio-meta/LazyLoadedValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// https://lua-api.factorio.com/latest/LuaLazyLoadedValue.html

export type LuaLazyLoadedValue<T> = {
/** Is this object valid? */
readonly valid: boolean;
/** The class name of this object. */
readonly object_name: string;
/** Gets the value of this lazy loaded value. */
get: () => T;
/** All methods, and properties that this object supports. */
help: () => string;
};
8 changes: 8 additions & 0 deletions src/factorio-meta/libs/factorio-new-fns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** This function allows to log LocalisedStrings to the Factorio log file. This, in combination with serpent, makes debugging in the data stage easier, because it allows to simply inspect entire prototype tables. For example, this allows to see all properties of the sulfur item prototype: log(serpent.block(data.raw["item"]["sulfur"])) */
// @ts-ignore
declare const log: (ls: LocalisedString) => void;

/** This function allows printing LocalisedStrings to stdout without polluting the logfile. This is primarily useful for communicating with external tools that launch Factorio as a child process. */
declare const localised_print: typeof log;

declare const table_size: <T extends {}>(v: T) => number;
59 changes: 59 additions & 0 deletions src/factorio-meta/libs/serpent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
type SerpentOptions = {
/** indentation; triggers long multi-line output. */
indent: string;
/** provide stringified value in a comment (up to maxlevel of depth). */
comment: boolean | number;
/** sort keys. */
sortkeys: boolean | ((...args: unknown[]) => any);
/** force sparse encoding (no nil filling based on #t). */
sparse: boolean;
/** remove spaces. */
compact: boolean;
/** raise fatal error on non-serilizable values. */
fatal: boolean;
/** disable bytecode serialization for easy comparison. */
nocode: boolean;
/** disable checking numbers against undefined and huge values. */
nohuge: boolean;
/** specify max level up to which to expand nested tables. */
maxlevel: number;
/** specify max number of elements in a table. */
maxnum: number;
/** specify max length for all table elements. */
maxlength: number;
/** use __tostring metamethod when serializing tables (v0.29); set to false to disable and serialize the table as is, even when __tostring is present. */
metatostring: boolean;
/** specify format for numeric values as shortest possible round-trippable double (v0.30). Use "%.16g" for better readability and "%.17g" (the default value) to preserve floating point precision. */
numformat: string;
/** allows to specify a list of values to ignore (as keys). */
valignore: string[];
/** allows to specify the list of keys to be serialized. Any keys not in this list are not included in final output (as keys). */
keyallow: string[];
/** allows to specity the list of keys to ignore in serialization. */
keyignore: string[];
/** allows to specify a list of value types to ignore (as keys). */
valtypeignore: string[];
/** provide custom output for tables. */
custom: (opts: {
/** the name of the current element with '=' or an empty string in case of array index, */
tag: any;
/** an opening table bracket { and associated indentation and newline (if any), */
head: any;
/** table elements concatenated into a string using commas and indentation/newlines (if any), */
body: any;
/** a closing table bracket } and associated indentation and newline (if any), and */
tail: any;
/** the current level. */
level: any;
}) => any;
/** name; triggers full serialization with self-ref section. */
name: string;
};
declare const serpent: {
/** full serialization; sets name, compact and sparse options; */
dump(tbl: any, options?: SerpentOptions): string;
/** single line pretty printing, no self-ref section; sets sortkeys and comment options; */
line(tbl: any, options?: SerpentOptions): string;
/** multi-line indented pretty printing, no self-ref section; sets indent, sortkeys, and comment options. */
block(tbl: any, options?: SerpentOptions): string;
};
7 changes: 5 additions & 2 deletions src/factorio-meta/unmodeled-entities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* models that are know to _not_ be scrapeable from any of the class/concept/event
* html pages, but _do_ show up as referenced types in various signatures=--
* models that are know to _not_ have adequate type data from any of the class/concept/event
* html pages, but _do_ show up as referenced types in various signatures :/
*/
export const unmodeled = [
"blueprint entity",
Expand Down Expand Up @@ -32,4 +32,7 @@ export const unmodeled = [
"UnitSpawnDefinition",
"CircuitConnectionDefinition",
"CraftingQueueItem",
"action",
/** https://lua-api.factorio.com/latest/LuaForce.html#LuaForce.research_all_technologies */
"include_disabled_prototypes",
];
104 changes: 97 additions & 7 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { scrapeClassPage } from "./scrape/classes";
import { scrapeConcepts } from "./scrape/concepts";
import { scrapeDefines } from "./scrape/defines";
import { ofUrl } from "./scrape/dom";
import { resolve } from "path";
import { IDocument } from "happy-dom";
import { scrapeEvents } from "./scrape/events";

export const produce = async ({
urls,
Expand All @@ -13,12 +16,19 @@ export const produce = async ({
apiRoot: string;
};
}) => {
const classLinks = await enumerateClassUrlsFromUrl(urls.apiRoot);
const nonDedupedclassSchemas = await getClassesFromUrl(
const [rootDocument, eventsDocument] = await Promise.all([
ofUrl(urls.apiRoot),
ofUrl(`${urls.apiRoot}/events.html`),
]);
const classLinks = await enumerateClassUrlsFromUrl(
rootDocument,
urls.apiRoot
);
const nonDedupedClassSchemas = await getClassesFromUrl(
classLinks
).then((pageSchemas) => pageSchemas.flat());
const classSchemas = Object.values(
nonDedupedclassSchemas.reduce((byName, curr) => {
nonDedupedClassSchemas.reduce((byName, curr) => {
if (byName[curr.name]) {
if (JSON.stringify(curr) !== JSON.stringify(byName[curr.name])) {
throw new Error(
Expand All @@ -30,17 +40,98 @@ export const produce = async ({
return byName;
}, {} as Record<string, Cls>)
);
const [defines, concepts] = await Promise.all([
const [defines, concepts, events] = await Promise.all([
getDefinesFromUrl(urls.apiRoot),
getConceptsFromUrl(urls.apiRoot),
scrapeEvents(eventsDocument.body),
]);

const printed = [
`/** @noSelfInFile */`,

// defines
`/** defines */`,
`declare const defines: Defines;`,
printer.print(defines),

// concepts
`/** concepts */`,
...concepts.map(printer.print),

// classes
`/** classes */`,
// @todo consider applying a nominal typing hack to classes: https://github.com/andnp/SimplyTyped/blob/85fb9cdb7655ac921f38f6e21027dc27d76dcf80/src/types/utils.ts
...classSchemas.map(printer.print),
// @todo make global classes global, non-global classes ...not

// events
`/** events */`,
...events.map((evt) => {
evt.name =
evt.name
.split("_")
.map((chars) => {
const [first, ...rest] = chars.split("");
return [first.toLocaleUpperCase(), ...rest].join("");
})
.join("") + "Payload";
return printer.print(evt);
}),

/**
* expose global instances
* @see {https://lua-api.factorio.com/latest/ api}
*/
`/** globals */`,
...[
[
"game",
"LuaGameScript",
"This is the main object, through which most of the API is accessed. It is, however, not available inside handlers registered with LuaBootstrap::on_load.",
],
[
"script",
"LuaBootstrap",
"Provides an interface for registering event handlers.",
],
[
"remote",
"LuaRemote",
"Allows inter-mod communication by way of providing a repository of interfaces that is shared by all mods.",
],
[
"commands",
"LuaCommandProcessor",
"Allows registering custom commands for the in-game console accessible via the grave key.",
],
["settings", "LuaSettings", "Allows reading the current mod settings."],
[
"rcon",
"LuaRCON",
"Allows printing messages to the calling RCON instance if any.",
],
[
"rendering",
"LuaRendering",
"Allows rendering of geometric shapes, text and sprites in the game world.",
],
].map(
([symbol, type, description]) =>
`/** ${description} */\ndeclare const ${symbol}: ${type};`
),

// libs
// @todo add global libs https://lua-api.factorio.com/latest/Libraries.html
`/** libs */`,
...(await Promise.all([
fs.readFile(resolve(__dirname, "factorio-meta/libs/serpent.ts")),
fs.readFile(resolve(__dirname, "factorio-meta/libs/factorio-new-fns.ts")),
fs.readFile(resolve(__dirname, "factorio-meta/LazyLoadedValue.ts")),
])),

// hacks
// @todo parse real Filter objects (not correctly x-reffed by consuming type signatures in HTML)
`/** hacks */`,
`/** unimplented!\n * @see {https://lua-api.factorio.com/latest/Event-Filters.html Filters} */\ntype Filters = unknown;`,
].join("\n");
await fs.writeFile("factorio.schema.d.ts", printed);
};
Expand Down Expand Up @@ -75,8 +166,7 @@ const getClassesFromUrl = async (classLinks: { href: string }[]) =>
}
);

const enumerateClassUrlsFromUrl = async (url: string) => {
const document = await ofUrl(url);
const enumerateClassUrlsFromUrl = async (document: IDocument, url: string) => {
const classRootEls = document.getElementById("Classes");
const classEls =
classRootEls.nextElementSibling.nextElementSibling.nextElementSibling
Expand Down
9 changes: 8 additions & 1 deletion src/ir/of-lua.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Type,
union,
} from "./ir";
import * as printer from "./printer";

const description = "";

Expand Down Expand Up @@ -72,8 +73,14 @@ export const ofLua = (ltype_: string): Type => {
case "nil":
return nil({ description });
}

// factorio types
const [_, llv] = ltype.match(/LazyLoadedValue\s+\((.*)\)/) || [];
if (llv) {
return sym({ text: `LazyLoadedValue<${printer.print(ofLua(llv))}>` });
}
if (ltype.match(/defines\..+/)) {
return sym({ text: ltype });
return sym({ text: `typeof ${ltype}` });
}
if (unmodeled.includes(ltype)) {
return any({});
Expand Down
7 changes: 3 additions & 4 deletions src/ir/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ export const printInner = (t: Type): string => {
// description should be accessed by object literal
return print(t.type);
case "function":
return `(${t.parameters.map((p) => print(p)).join(", ")}) => ${print(
t.return
)}`;
const paramStrs = t.parameters.map(print);
return `(${paramStrs.join(", ")}) => ${print(t.return)}`;
case "literal":
return `${t.value}`;
case "map":
Expand Down Expand Up @@ -68,5 +67,5 @@ export const printInner = (t: Type): string => {

export const print = (t: Type): string => {
if (!t.description) return printInner(t);
return `/** ${t.description} */\n${printInner(t)}`;
return `\n/** ${t.description} */\n${printInner(t)}`;
};

0 comments on commit 7afe747

Please sign in to comment.