diff --git a/codecov.yml b/codecov.yml index 9f5cebfcf8..4bf885d5ac 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,3 +2,7 @@ fixes: # For now we only report website coverage, and since tests are run directly in # the /website folder we need to add this - "::website/" +ignore: + # Ignore webworkers since we can't test them + - "**/*.worker.ts" + - "**/*.worker.js" diff --git a/website/package.json b/website/package.json index 05c7da9df3..9242d0ca8d 100644 --- a/website/package.json +++ b/website/package.json @@ -120,7 +120,8 @@ "webpack": "5.15.0", "webpack-cli": "4.3.1", "webpack-dev-server": "3.11.2", - "webpack-merge": "5.7.3" + "webpack-merge": "5.7.3", + "worker-loader": "^3.0.8" }, "dependencies": { "@authenio/samlify-node-xmllint": "2.0.0", @@ -168,6 +169,8 @@ "reselect": "4.0.0", "samlify": "2.7.7", "searchkit": "2.4.1-alpha.5", + "sexpr-plus": "^7.0.0", + "smtlib-ext": "^1.0.0", "use-subscription": "1.5.1" }, "browserslist": [ diff --git a/website/src/actions/optimizer.ts b/website/src/actions/optimizer.ts new file mode 100644 index 0000000000..6c1823a646 --- /dev/null +++ b/website/src/actions/optimizer.ts @@ -0,0 +1,7 @@ +export const TOGGLE_OPTIMIZER_DISPLAY = 'TOGGLE_OPTIMIZER_DISPLAY' as const; +export function toggleOptimizerDisplay() { + return { + type: TOGGLE_OPTIMIZER_DISPLAY, + payload: null, + }; +} diff --git a/website/src/reducers/index.ts b/website/src/reducers/index.ts index 4fdd9f53e9..a800ed603d 100644 --- a/website/src/reducers/index.ts +++ b/website/src/reducers/index.ts @@ -8,6 +8,7 @@ import { Actions } from 'types/actions'; import requests from './requests'; import app from './app'; import createUndoReducer from './undoHistory'; +import optimizer from './optimizer'; // Persisted reducers import moduleBankReducer, { persistConfig as moduleBankPersistConfig } from './moduleBank'; @@ -41,6 +42,7 @@ export default function reducers(state: State = defaultState, action: Actions): requests: requests(state.requests, action), timetables: timetables(state.timetables, action), app: app(state.app, action), + optimizer: optimizer(state.optimizer, action), theme: theme(state.theme, action), settings: settings(state.settings, action), planner: planner(state.planner, action), diff --git a/website/src/reducers/optimizer.ts b/website/src/reducers/optimizer.ts new file mode 100644 index 0000000000..7fcb3d89ee --- /dev/null +++ b/website/src/reducers/optimizer.ts @@ -0,0 +1,22 @@ +import { OptimizerState } from 'types/reducers'; +import { Actions } from 'types/actions'; + +import { TOGGLE_OPTIMIZER_DISPLAY } from 'actions/optimizer'; + +export const defaultOptimizerState: OptimizerState = { + isOptimizerShown: false, +}; + +function optimizer(state: OptimizerState = defaultOptimizerState, action: Actions): OptimizerState { + switch (action.type) { + case TOGGLE_OPTIMIZER_DISPLAY: + return { + ...state, + isOptimizerShown: !state.isOptimizerShown, + }; + default: + return state; + } +} + +export default optimizer; diff --git a/website/src/types/optimizer.ts b/website/src/types/optimizer.ts new file mode 100644 index 0000000000..d08e66dc34 --- /dev/null +++ b/website/src/types/optimizer.ts @@ -0,0 +1,165 @@ +// Intermediate types used to contain timetable information relevant to the optimizer +import { mapValues, groupBy } from 'lodash'; +import { Module, RawLesson, StartTime, EndTime } from 'types/modules'; +import { SemTimetableConfig } from 'types/timetables'; + +// {module id}__{lesson type}__{lesson id} e.g., CS3203__Lecture__1 +export type UniqueLessonString = string; +export type Z3LessonID = number; + +/** + * Main input format into the optimizer layers + * */ +export type OptimizerInput = { + moduleInfo: ModuleInfoWithConstraints[]; + constraints: GlobalConstraints; +}; + +/** + * Callbacks to communicate with the caller of TimetableOptimizer + * */ +export interface OptimizerCallbacks { + onOptimizerInitialized(): void; + onSmtlib2InputCreated(s: string): void; + onSmtLib2ResultOutput(s: string): void; + onTimetableOutput(timetable: OptimizerOutput): void; +} + +/** + * Final timetable output to the optimizer caller + * */ +export type OptimizerOutput = { + isSat: boolean; + timetable: SemTimetableConfig; +}; + +/** + * Modules for optimizer to consider. + * required is a constraint indicating if the module can be dropped to fulfil other constraints + * */ +export type ModuleInfoWithConstraints = { + mod: Module; + required: boolean; + lessonsGrouped: LessonsByGroupsByClassNo; +}; + +// Mapping between lesson types -> classNo -> lessons. +// We have to take one classNo of each lessonType, so this indicates all the slots to be filled +// per classNo per lessonType +export type LessonsByGroupsByClassNo = { + [lessonType: string]: LessonsForLessonType; +}; + +export type LessonsForLessonType = { [classNo: string]: readonly RawLesson[] }; + +/* + * A list of times that are assigned to a particular owner (e.g., a lesson) + * Used throughout optimizer to indicate that a particular block of time should be reserved if a owner id is chosen + */ +export interface SlotConstraint { + startEndTimes: Array<[number, number]>; // Array of start and end times as integers + ownerId: number; // Numeric ID of owner, since we will encode this as an integer constraint + ownerString: string; // string representing the owner: user-interpretable, used for varnames +} + +/* + * Indicating that a varname (boolean selector) has a cost attached to it if it is chosen. + */ +export interface WorkloadCost { + varname: string; + cost: number; +} + +// User-selected constraints to pass to optimizer +export interface GlobalConstraints { + // Min/max number of MCs + whether the constraint is active + isWorkloadActive: boolean; + minWorkload: number; + maxWorkload: number; + // Find exactly N free days + whether the constraint is active + isFreeDayActive: boolean; + numRequiredFreeDays: number; + // Force these exact free days + whether the constraint is active + isSpecificFreeDaysActive: boolean; + specificFreeDays: Array; + // When lessons should start and end + whether the constraint is active + isTimeConstraintActive: boolean; + earliestLessonStartTime: StartTime; + latestLessonEndTime: EndTime; + // The hours where a lunch break should be allocated, + // how many half-hour slots to allocate, and whether the constraint is active + isLunchBreakActive: boolean; + lunchStart: StartTime; + lunchEnd: EndTime; + lunchHalfHours: number; + // Ask optimizer to compact timetable to leave as few gaps between lessons as possible + isPreferCompactTimetable: boolean; +} + +/** + * Defs for communicating between Optimizer <-> WebWorker <-> WASM wrapper + * */ +// Need to disable since there's an eslint bug with enums +// eslint-disable-next-line no-shadow +export enum Z3WorkerMessageKind { + // Request to init + INIT = 'INIT', + // Z3 initialized + INITIALIZED = 'INITIALIZED', + // Run the optimizer + OPTIMIZE = 'OPTIMIZE', + // Print output + PRINT = 'PRINT', + // Error + ERR = 'ERR', + // Z3 finished runnung + EXIT = 'EXIT', + // Z3 aborted + ABORT = 'ABORT', +} + +/** + * Message to be sent back and forth between a Z3 webworker and any callers + * */ +export interface Z3WorkerMessage { + kind: Z3WorkerMessageKind; + msg: string; +} + +// TODO Shouldn't be here +export const defaultConstraints: GlobalConstraints = { + isWorkloadActive: false, + minWorkload: 0, + maxWorkload: 30, + isFreeDayActive: false, + numRequiredFreeDays: 1, + isSpecificFreeDaysActive: false, + specificFreeDays: [], + earliestLessonStartTime: '0800', + latestLessonEndTime: '2200', + lunchStart: '1100', + lunchEnd: '1500', + lunchHalfHours: 2, + isLunchBreakActive: false, + isTimeConstraintActive: false, + isPreferCompactTimetable: false, +}; + +/** + * TODO move to utils + * Transforms a module's lessons into a mapping from + * lessonType ==> (classNo ==> list of lessons) + * The optimizer cares that a classNo contains all the slots that should be filled. + * */ +export function lessonByGroupsByClassNo(lessons: readonly RawLesson[]): LessonsByGroupsByClassNo { + const lessonByGroups: { [lessonType: string]: readonly RawLesson[] } = groupBy( + lessons, + (lesson) => lesson.lessonType, + ); + const lessonByGroupsByClassNumber = mapValues( + lessonByGroups, + (lessonsOfSamelessonType: readonly RawLesson[]) => + groupBy(lessonsOfSamelessonType, (lesson) => lesson.classNo), + ); + return lessonByGroupsByClassNumber; +} diff --git a/website/src/types/reducers.ts b/website/src/types/reducers.ts index d87e3a9b6c..2a89f1aed1 100644 --- a/website/src/types/reducers.ts +++ b/website/src/types/reducers.ts @@ -83,6 +83,11 @@ export type ThemeState = Readonly<{ showTitle: boolean; }>; +/* optimizer */ +export type OptimizerState = Readonly<{ + isOptimizerShown: boolean; +}>; + /* settings */ export type ModRegRoundKey = { type: RegPeriodType; name?: string }; diff --git a/website/src/types/state.ts b/website/src/types/state.ts index d4a381a8e4..a4a6d739cd 100644 --- a/website/src/types/state.ts +++ b/website/src/types/state.ts @@ -4,6 +4,7 @@ import { Requests, SettingsState, ThemeState, + OptimizerState, TimetablesState, UndoHistoryState, VenueBank, @@ -17,6 +18,7 @@ export type State = { timetables: TimetablesState; app: AppState; theme: ThemeState; + optimizer: OptimizerState; settings: SettingsState; planner: PlannerState; undoHistory: UndoHistoryState; diff --git a/website/src/types/vendor/sexpr-plus.d.ts b/website/src/types/vendor/sexpr-plus.d.ts new file mode 100644 index 0000000000..6b52ab3a6e --- /dev/null +++ b/website/src/types/vendor/sexpr-plus.d.ts @@ -0,0 +1,30 @@ +declare module 'sexpr-plus' { + // Parses string into expressions + export function parse(input: string): Expr; + // Location of start or end of an expression + export type ExprLocation = { + offset: number; + line: number; + column: number; + }; + // Location information for expressions + export type ExprLocations = { + start: ExprLocation; + end: ExprLocation; + }; + // Base output type from parsing + export type ExprNode = { + type: string; + content: Expr; + location: ExprLocations; + }; + export type Expr = string | ExprNode[]; + // try-catch with instanceof during parsing + export interface SyntaxError { + message: string; + expected: string; + found: string; + location: ExprLocations; + name: string; + } +} diff --git a/website/src/types/vendor/worker-loader.d.ts b/website/src/types/vendor/worker-loader.d.ts new file mode 100644 index 0000000000..3dac70cb58 --- /dev/null +++ b/website/src/types/vendor/worker-loader.d.ts @@ -0,0 +1,10 @@ +declare module '*.worker.ts' { + // You need to change `Worker`, if you specified a different value for the `workerType` option + class WebpackWorker extends Worker { + constructor(); + } + + // Uncomment this if you set the `esModule` option to `false` + // export = WebpackWorker; + export default WebpackWorker; +} diff --git a/website/src/utils/optimizer/__mocks__/converter.ts b/website/src/utils/optimizer/__mocks__/converter.ts new file mode 100644 index 0000000000..a83987b3e2 --- /dev/null +++ b/website/src/utils/optimizer/__mocks__/converter.ts @@ -0,0 +1,9 @@ +export const mockOnmessage = jest.fn(); +export const mockPostMessage = jest.fn(); + +const WebpackWorker = jest.fn().mockImplementation(() => ({ + onmessage: mockOnmessage, + postMessage: mockPostMessage, +})); + +export default WebpackWorker; diff --git a/website/src/utils/optimizer/__mocks__/z3WebWorker.worker.ts b/website/src/utils/optimizer/__mocks__/z3WebWorker.worker.ts new file mode 100644 index 0000000000..a83987b3e2 --- /dev/null +++ b/website/src/utils/optimizer/__mocks__/z3WebWorker.worker.ts @@ -0,0 +1,9 @@ +export const mockOnmessage = jest.fn(); +export const mockPostMessage = jest.fn(); + +const WebpackWorker = jest.fn().mockImplementation(() => ({ + onmessage: mockOnmessage, + postMessage: mockPostMessage, +})); + +export default WebpackWorker; diff --git a/website/src/utils/optimizer/constants.ts b/website/src/utils/optimizer/constants.ts new file mode 100644 index 0000000000..360d520751 --- /dev/null +++ b/website/src/utils/optimizer/constants.ts @@ -0,0 +1,22 @@ +import { invert } from 'lodash'; +import { WorkingDays } from 'types/modules'; + +export const DAYS = WorkingDays.length; // One week mon - sat +export const DAY_START_HOUR = 8; // This just needs to be earlier than the earliest lesson +export const DAY_END_HOUR = 22; // Similarly, later than the latest lesson. +export const HOURS_PER_DAY = DAY_END_HOUR - DAY_START_HOUR; // 14 8 am --> 10 pm. We save 24 - HOURS_PER_DAY variables per day. +export const HOURS_PER_WEEK = HOURS_PER_DAY * DAYS; +export const NUM_WEEKS = 13; + +export const DAY_IDXS: Record = { + monday: 0, + tuesday: 1, + wednesday: 2, + thursday: 3, + friday: 4, + saturday: 5, +}; +export const IDX_DAYS = invert(DAY_IDXS); + +// All possible week numbers we should encounter (except WeekRange) +export const ALL_WEEKS = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]]; diff --git a/website/src/utils/optimizer/converter.test.ts b/website/src/utils/optimizer/converter.test.ts new file mode 100644 index 0000000000..8113eb9ed1 --- /dev/null +++ b/website/src/utils/optimizer/converter.test.ts @@ -0,0 +1,356 @@ +import { OptimizerInputSmtlibConverter } from 'utils/optimizer/converter'; +import { Z3WeekSolver } from 'utils/optimizer/z3WeekSolver'; +import { Z3TimetableSolver } from 'utils/optimizer/z3TimetableSolver'; +import { + OptimizerInput, + ModuleInfoWithConstraints, + defaultConstraints, + lessonByGroupsByClassNo, + GlobalConstraints, + SlotConstraint, +} from 'types/optimizer'; +import { RawLesson } from 'types/modules'; +import { getModuleTimetable } from 'utils/modules'; +import { CS3216, GES1021, BFS1001 } from '__mocks__/modules'; + +// Directly mock the converter functions, allows us to track them +const mockAddWeeks = jest.fn(); +const mockGenerateSmtlib2String = jest.fn(); +jest.mock('utils/optimizer/z3WeekSolver', () => + // Works and lets you check for constructor calls: + ({ + // eslint-disable-next-line @typescript-eslint/no-empty-function + Z3WeekSolver: jest.fn().mockImplementation(() => ({ + addWeeks: mockAddWeeks, + generateSmtlib2String: mockGenerateSmtlib2String, + })), + }), +); + +const mockAddSlotConstraintsFulfilOnlyOne = jest.fn(); +const mockGenerateTimetableSolveSmtLib2String = jest.fn(); +const mockSetBooleanSelectorCosts = jest.fn(); +const mockAddSlotConstraintsFulfilExactlyN = jest.fn(); +const mockAddNegativevalueSlotConstraintToNConsecutive = jest.fn(); +jest.mock('utils/optimizer/z3TimetableSolver', () => + // Works and lets you check for constructor calls: + ({ + UNASSIGNED: -1, + // eslint-disable-next-line @typescript-eslint/no-empty-function + Z3TimetableSolver: jest.fn().mockImplementation(() => ({ + addSlotConstraintsFulfilOnlyOne: mockAddSlotConstraintsFulfilOnlyOne, + setBooleanSelectorCosts: mockSetBooleanSelectorCosts, + addSlotConstraintsFulfilExactlyN: mockAddSlotConstraintsFulfilExactlyN, + addNegativevalueSlotConstraintToNConsecutive: mockAddNegativevalueSlotConstraintToNConsecutive, + generateSmtlib2String: mockGenerateTimetableSolveSmtLib2String, + })), + }), +); + +beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Jest will fix this for us + Z3WeekSolver.mockClear(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Jest will fix this for us + Z3TimetableSolver.mockClear(); +}); + +const emptyModuleInfo: ModuleInfoWithConstraints[] = []; +const emptyOptimizerInput: OptimizerInput = { + moduleInfo: emptyModuleInfo, + constraints: defaultConstraints, +}; +// const modCS1010S: ModuleInfoWithConstraints = { +// mod: CS1010S, +// required: true, +// lessonsGrouped: lessonByGroupsByClassNo(getModuleTimetable(CS1010S, 1)), +// }; +const modCS3216: ModuleInfoWithConstraints = { + mod: CS3216, + required: true, + lessonsGrouped: lessonByGroupsByClassNo(getModuleTimetable(CS3216, 1)), +}; +const modGES1021: ModuleInfoWithConstraints = { + mod: GES1021, + required: true, + lessonsGrouped: lessonByGroupsByClassNo(getModuleTimetable(GES1021, 1)), +}; +const modBFS1001: ModuleInfoWithConstraints = { + mod: BFS1001, + required: true, + lessonsGrouped: lessonByGroupsByClassNo(getModuleTimetable(BFS1001, 1)), +}; + +const cs3216onlyOptimizerInput: OptimizerInput = { + moduleInfo: [modCS3216], + constraints: defaultConstraints, +}; +// const ges1021onlyOptimizerInput: OptimizerInput = { +// moduleInfo: [modGES1021], +// constraints: defaultConstraints, +// }; +const ges1021cs3216OptimizerInput: OptimizerInput = { + moduleInfo: [modCS3216, modGES1021], + constraints: defaultConstraints, +}; +const bfs1001OptimizerInput: OptimizerInput = { + moduleInfo: [modBFS1001], + constraints: defaultConstraints, +}; + +describe('constructor', () => { + it('fails when the start hour exceeds the end hour', () => { + const startHour = 5; + const endHour = 1; + expect( + () => new OptimizerInputSmtlibConverter(emptyOptimizerInput, 10, startHour, endHour), + ).toThrow(); + }); + it('fails when the start hour equals the end hour', () => { + const startHour = 5; + const endHour = 5; + expect( + () => new OptimizerInputSmtlibConverter(emptyOptimizerInput, 10, startHour, endHour), + ).toThrow(); + }); + + it('fails when the number of half-hour slots is <= 0', () => { + const halfHourSlots = -1; + expect( + () => new OptimizerInputSmtlibConverter(emptyOptimizerInput, halfHourSlots, 1, 10), + ).toThrow(); + }); + + it('creates and calls solver with the right time string values for valid hours configurations', () => { + // eslint-disable-next-line no-new + new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + expect(Z3TimetableSolver).toHaveBeenCalledWith(2, ['monday_0100_wk0', 'monday_0130_wk0']); + }); + + it('creates right owner id tables for a simple module', () => { + const conv = new OptimizerInputSmtlibConverter(cs3216onlyOptimizerInput, 2, 1, 3); + expect(conv.ownerIdToStringTable).toEqual({ '0': 'CS3216__LEC__1' }); + expect(conv.stringToOwnerIdTable).toEqual({ CS3216__LEC__1: 0 }); + }); + + it('creates right owner id tables for two modules', () => { + const conv = new OptimizerInputSmtlibConverter(ges1021cs3216OptimizerInput, 2, 1, 3); + expect(conv.ownerIdToStringTable).toEqual({ + '0': 'CS3216__LEC__1', + '1048576': 'GES1021__LEC__SL1', + }); + expect(conv.stringToOwnerIdTable).toEqual({ CS3216__LEC__1: 0, GES1021__LEC__SL1: 1048576 }); + }); +}); + +describe('generateWeekSolveSmtLib2String', () => { + it('errors if any lesson has a WeekRange instead of a list of week (NumericWeeks)', () => { + const lessonWithWeekRange: RawLesson = { + classNo: '01', + lessonType: 'Lecture', + day: 'Monday', + startTime: '1000', + endTime: '1200', + venue: 'placeholder', + weeks: { + start: 'placeholder', + end: 'placeholder', + weeks: [1, 2, 5, 8], + }, + }; + const modWithWeekRange: ModuleInfoWithConstraints = { + mod: GES1021, + required: true, + lessonsGrouped: { Lecture: { '01': [lessonWithWeekRange] } }, + }; + const modWithWeekRangeOptimizerInput: OptimizerInput = { + moduleInfo: [modWithWeekRange], + constraints: defaultConstraints, + }; + const conv = new OptimizerInputSmtlibConverter(modWithWeekRangeOptimizerInput, 2, 1, 3); + expect(() => conv.generateWeekSolveSmtLib2String()).toThrow(); + }); + + it('generate appropriate week range sets to pass to the week optimizer for normal use cases', () => { + // Has lessons with 2 unique week configs, weeks 1 - 6, and 7 - 13. We should only pass two into mockAddWeeks. + const conv = new OptimizerInputSmtlibConverter(bfs1001OptimizerInput, 2, 1, 3); + conv.generateWeekSolveSmtLib2String(); + expect(mockAddWeeks).toBeCalledTimes(2); + expect(mockAddWeeks).toBeCalledWith([1, 2, 3, 4, 5, 6], 'uniqueWeekId_0'); + expect(mockAddWeeks).toBeCalledWith([7, 8, 9, 10, 11, 12, 13], 'uniqueWeekId_1'); + }); +}); + +describe('updateZ3WeeksolveOutput', () => { + it('throws an error if the weeksolve result was unsat (not supposed to happen ever)', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + const testOutput = 'unsat'; + expect(() => conv.updateZ3WeeksolveOutput(testOutput)).toThrow(); + }); + it('throws an error if the weeksolve result was unsat with newline (not supposed to happen ever)', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + const testOutput = 'unsat\n'; + expect(() => conv.updateZ3WeeksolveOutput(testOutput)).toThrow(); + }); + it('updates the weeks to solve variable correctly if only the first week was chosen', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + const testOutput = 'sat\n((weeks_to_simulate #b1000000000000))\n'; + conv.updateZ3WeeksolveOutput(testOutput); + expect(conv.weeksToSimulate).toEqual( + new Set([1]), + ); + }); + it('updates the weeks to solve variable correctly if only the last (13th) week was chosen', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + const testOutput = 'sat\n((weeks_to_simulate #b0000000000001))\n'; + conv.updateZ3WeeksolveOutput(testOutput); + expect(conv.weeksToSimulate).toEqual( + new Set([13]), + ); + }); + it('updates the weeks to solve variable correctly some random weeks are chosen', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + const testOutput = 'sat\n((weeks_to_simulate #b0100001001001))\n'; + conv.updateZ3WeeksolveOutput(testOutput); + expect(conv.weeksToSimulate).toEqual( + new Set([2, 7, 10, 13]), + ); + }); +}); + +describe('hhmmToZ3Time', () => { + it('errors when the time is earlier than the lesson start time', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + expect(() => conv.hhmmToZ3Time('0000', 'Monday')).toThrow(); + expect(() => conv.hhmmToZ3Time('0030', 'Monday')).toThrow(); + }); + it('errors when the time is later than the lesson end time', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + expect(() => conv.hhmmToZ3Time('0400', 'Monday')).toThrow(); + expect(() => conv.hhmmToZ3Time('0430', 'Monday')).toThrow(); + }); + it('errors when the day of the week is wrong', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + expect(() => conv.hhmmToZ3Time('0200', 'funday')).toThrow(); + }); + it('produces the right time for a valid time range within Monday', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + let timeIdx = conv.hhmmToZ3Time('0100', 'Monday'); + expect(timeIdx).toEqual(0); + timeIdx = conv.hhmmToZ3Time('0130', 'Monday'); + expect(timeIdx).toEqual(1); + timeIdx = conv.hhmmToZ3Time('0200', 'Monday'); + expect(timeIdx).toEqual(2); + timeIdx = conv.hhmmToZ3Time('0230', 'Monday'); + expect(timeIdx).toEqual(3); + }); + it('produces the right time for a valid time range for another day of the week', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + let timeIdx = conv.hhmmToZ3Time('0100', 'Thursday'); + expect(timeIdx).toEqual(84); // Defined by the hours per day constant (22 - 08 = 14) * 2 (half hours) * 3 days + timeIdx = conv.hhmmToZ3Time('0130', 'Thursday'); + expect(timeIdx).toEqual(85); + timeIdx = conv.hhmmToZ3Time('0200', 'Thursday'); + expect(timeIdx).toEqual(86); + timeIdx = conv.hhmmToZ3Time('0230', 'Thursday'); + expect(timeIdx).toEqual(87); + }); + it('produces the right time for a valid time range for another day of the week for specified week (2)', () => { + const conv = new OptimizerInputSmtlibConverter(emptyOptimizerInput, 2, 1, 3); + let timeIdx = conv.hhmmToZ3Time('0100', 'Thursday', 2); + expect(timeIdx).toEqual(420); // Defined by the hours per day constant (22 - 08 = 14) * 2 (half hours) * 6 days per week * 2 weeks + 84 from previous test + timeIdx = conv.hhmmToZ3Time('0130', 'Thursday', 2); + expect(timeIdx).toEqual(421); + timeIdx = conv.hhmmToZ3Time('0200', 'Thursday', 2); + expect(timeIdx).toEqual(422); + timeIdx = conv.hhmmToZ3Time('0230', 'Thursday', 2); + expect(timeIdx).toEqual(423); + }); +}); + +const lunchBreakErrConstraintStart: GlobalConstraints = { + ...defaultConstraints, + lunchStart: '0000', +}; +const lunchBreakErrConstraintEnd: GlobalConstraints = { + ...defaultConstraints, + lunchStart: '0200', + lunchEnd: '0130', +}; +const lunchBreakShort: GlobalConstraints = { + ...defaultConstraints, + lunchStart: '0100', + lunchEnd: '0200', +}; +describe('generateLunchBreakSlotconstraints', () => { + it('errors when the lunch start / end times are impossible', () => { + let conv = new OptimizerInputSmtlibConverter( + { ...emptyOptimizerInput, constraints: lunchBreakErrConstraintStart }, + 2, + 1, + 3, + ); + expect(() => conv.generateLunchBreakSlotconstraints()).toThrow(); + conv = new OptimizerInputSmtlibConverter( + { ...emptyOptimizerInput, constraints: lunchBreakErrConstraintEnd }, + 2, + 1, + 3, + ); + expect(() => conv.generateLunchBreakSlotconstraints()).toThrow(); + }); + + it('generates the right output for minimal lunch break hours', () => { + const conv = new OptimizerInputSmtlibConverter( + { ...emptyOptimizerInput, constraints: lunchBreakShort }, + 2, + 1, + 3, + ); + conv.weeksToSimulate = new Set([1]); // at least add one week to simulate + const scs: SlotConstraint[] = conv.generateLunchBreakSlotconstraints(); + // Computed from the indices of all start times within the week + expect(scs).toEqual([ + { ownerId: -1, ownerString: 'UNASSIGNED', startEndTimes: [[0, 2]] }, + { ownerId: -1, ownerString: 'UNASSIGNED', startEndTimes: [[28, 30]] }, + { ownerId: -1, ownerString: 'UNASSIGNED', startEndTimes: [[56, 58]] }, + { ownerId: -1, ownerString: 'UNASSIGNED', startEndTimes: [[84, 86]] }, + { ownerId: -1, ownerString: 'UNASSIGNED', startEndTimes: [[112, 114]] }, + { ownerId: -1, ownerString: 'UNASSIGNED', startEndTimes: [[140, 142]] }, + ]); + }); +}); + +const lessonTimeErrConstraintStart: GlobalConstraints = { + ...defaultConstraints, + earliestLessonStartTime: '0000', +}; +const lessonTimeErrConstraintEnd: GlobalConstraints = { + ...defaultConstraints, + earliestLessonStartTime: '0200', + latestLessonEndTime: '0100', +}; +// const lessonTimeConstraint: GlobalConstraints = { +// ...defaultConstraints, +// earliestLessonStartTime: '0100', +// latestLessonEndTime: '0200', +// }; +describe('generateTimeconstraintSlotconstraint', () => { + it('errors when the lesson constraint start / end times are impossible', () => { + let conv = new OptimizerInputSmtlibConverter( + { ...emptyOptimizerInput, constraints: lessonTimeErrConstraintStart }, + 2, + 1, + 3, + ); + expect(() => conv.generateTimeconstraintSlotconstraint()).toThrow(); + conv = new OptimizerInputSmtlibConverter( + { ...emptyOptimizerInput, constraints: lessonTimeErrConstraintEnd }, + 2, + 1, + 3, + ); + expect(() => conv.generateTimeconstraintSlotconstraint()).toThrow(); + }); +}); diff --git a/website/src/utils/optimizer/converter.ts b/website/src/utils/optimizer/converter.ts new file mode 100644 index 0000000000..9409a61746 --- /dev/null +++ b/website/src/utils/optimizer/converter.ts @@ -0,0 +1,644 @@ +import { + OptimizerInput, + UniqueLessonString, + Z3LessonID, + ModuleInfoWithConstraints, + SlotConstraint, + OptimizerOutput, + LessonsForLessonType, + WorkloadCost, +} from 'types/optimizer'; +import { LessonTime, DayText, RawLesson, Weeks, NumericWeeks, isWeekRange } from 'types/modules'; +import { SemTimetableConfig } from 'types/timetables'; +import { Z3WeekSolver } from 'utils/optimizer/z3WeekSolver'; +import { LESSON_TYPE_ABBREV, LESSON_ABBREV_TYPE } from 'utils/timetables'; +import { + Z3TimetableSolver, + UNASSIGNED, + FREE, + TOOEARLY_LATE, +} from 'utils/optimizer/z3TimetableSolver'; +import { invert } from 'lodash'; +import { + DAYS, + DAY_IDXS, + HOURS_PER_DAY, + IDX_DAYS, + HOURS_PER_WEEK, + NUM_WEEKS, +} from 'utils/optimizer/constants'; +import { parse, Expr, ExprNode } from 'sexpr-plus'; +/** + * Converts to and from the high-level module/lesson input data to the optimizer + * and SMTLIB2 code used in Z3. + * It's a class as it has to maintain state between input and output to the optimizer. + * */ +export class OptimizerInputSmtlibConverter { + optimizerInput: OptimizerInput; + + z3tt: Z3TimetableSolver; + + // When to consider the start and end of the timetable simulation + startHour: number; // e.g., 8 for 8 am + + endHour: number; // e.g., 22 for 10 pm + + // These store the mapping between strings that we understand (module + lessontype + lessonid) and Z3 integers + stringToOwnerIdTable: Record; // string in both cases is {module id}__{lesson type}__{lesson id} + + ownerIdToStringTable: Record; + + weeksToSimulate: Set; // Each number in here is one week to simulate + + constructor( + optimizerInput: OptimizerInput, + totalHalfHourSlots: number, + dayStartHour: number, + dayEndHour: number, + ) { + if (dayStartHour >= dayEndHour) + throw new Error(`Unexpected: start hour ${dayStartHour} >= end hour ${dayEndHour}`); + if (totalHalfHourSlots <= 0) + throw new Error(`Unexpected: half hour slots ${totalHalfHourSlots} <= 0`); + + this.optimizerInput = optimizerInput; + this.startHour = dayStartHour; + this.endHour = dayEndHour; + this.stringToOwnerIdTable = {}; + this.ownerIdToStringTable = {}; + this.populateOwnerIdTables(); + + // User-parseable names for Z3 to name each variable representing (week, day, hour, minute) + const timeStrVals: Array = Array.from( + new Array(totalHalfHourSlots), + (_: number, i: number) => { + const [offset, day, week] = z3TimeToGenericTime(i); + const dayOfWeek = idxToDayStr(day); + const hour: number = Math.floor(offset / 2) + this.startHour; + const hourStr: string = hour < 10 ? `0${hour.toString()}` : hour.toString(); + const minuteStr: string = offset % 2 === 0 ? '00' : '30'; + return `${dayOfWeek}_${hourStr}${minuteStr}_wk${week.toString()}`; + }, + ); + + this.z3tt = new Z3TimetableSolver(totalHalfHourSlots, timeStrVals); + this.weeksToSimulate = new Set(); // 1-indexed weeks to simulate for the timetable + } + + /** + * Every lesson slot (unique combination of module - lessontype - lessonid) needs to have an integer representation to + * let the solver use integer constraints. Create the tables to transform between string and integer representations. + * */ + populateOwnerIdTables() { + this.optimizerInput.moduleInfo.forEach( + (modInfo: ModuleInfoWithConstraints, moduleidx: number) => { + Object.keys(modInfo.lessonsGrouped).forEach((lessonType: string, lessontypeidx: number) => { + const classNosOfLessontype: string[] = Object.keys(modInfo.lessonsGrouped[lessonType]); + classNosOfLessontype.forEach((lessonName: string, lessonidx: number) => { + const key = lessonInfoToZ3Varname(modInfo.mod.moduleCode, lessonType, lessonName); + // eslint-disable-next-line no-bitwise + this.stringToOwnerIdTable[key] = (moduleidx << 20) | (lessontypeidx << 10) | lessonidx; + }); + }); + }, + ); + this.ownerIdToStringTable = invert(this.stringToOwnerIdTable); + } + + /** + * STEP 1 of solve: + * Generate first-stage solver string for which weeks to simulate + * */ + generateWeekSolveSmtLib2String(): string { + const weekSolver: Z3WeekSolver = new Z3WeekSolver(NUM_WEEKS); + const uniqueWeeks: Set = new Set(); + + // Go through every lesson and generate all possible unique combinations of lesson weeks + this.optimizerInput.moduleInfo.forEach((modInfo: ModuleInfoWithConstraints) => { + Object.keys(modInfo.lessonsGrouped).forEach((lessonType: string) => { + const lessonsForLessontype: LessonsForLessonType = modInfo.lessonsGrouped[lessonType]; + Object.keys(lessonsForLessontype).forEach((classNo: string) => { + const lessons: readonly RawLesson[] = lessonsForLessontype[classNo]; + lessons + .map((l: RawLesson) => l.weeks) + .forEach((weeks: Weeks) => { + // This weird stringify + parse later is to only have unique combinations of weeks + // in the Set above. This makes solving the weeks problem easier. + // We can't put arrays in sets, so have to stringify. + if (isWeekRange(weeks)) { + // TODO: Error output + // console.error( + // `At least one lesson has a WeekRange (not just normal Week array) in module ${modInfo}`, + // ); + throw new Error( + `WeekRange unsupported: Mod ${modInfo.mod} has at least 1 lesson in lessons ${lessons} with WeekRange`, + ); + } else { + // No WeekRange, can treat as normal weeks + const weeksJson = JSON.stringify(weeks); + uniqueWeeks.add(weeksJson); + // console.log(weeksJson); + } + }); + }); + }); + }); + + // Add each unique week list to solver to generate solve string + Array.from(uniqueWeeks).forEach((uniqueWeek: string, idx: number) => { + // Reminder: this was necessary to keep the set of unique weeks as small as possible + const uniqueWeekArr = JSON.parse(uniqueWeek); + weekSolver.addWeeks(uniqueWeekArr, `uniqueWeekId_${idx}`); + }); + + return weekSolver.generateSmtlib2String(); + } + + /** + * STEP 2 of solve: + * After Z3 solves weeksolve, update the weeksToSimulate variable for + * the next solve stage. + * */ + updateZ3WeeksolveOutput(buffer: string) { + // General structure + // sat\n((weeks_to_simulate #b1000000000001))\n" + const lines = buffer.split('\n'); + if (lines[0] !== 'sat') + throw new Error( + 'Not SAT for week-solve before timetable solve - unexpected error, please report', + ); + + // Extract binary string + const line2 = lines[1]; + // Take part after first space, and part before first ) after that + const binstring = line2.split(' ')[1].split(')')[0]; + // Ignore "#b" in string + const binary = binstring.substring(2); + // Create list of weeks that we should simulate + binary.split('').forEach((c: string, idx: number) => { + if (c === '1') this.weeksToSimulate.add(idx + 1); + }); + // console.log(`WEEKS TO SIMULATE [${Array.from(this.weeksToSimulate).join(',')}]`); + } + + /** + * STEP 3: + * Generate the main SMTLIB2 string representing the timetable solve + * across all weeks to simulate. + * */ + generateTimetableSolveSmtLib2String(randomize = true): string { + // TODO make all these stages into separate functions + + // Add all the time constraints from each module + // Go through every lesson and generate all possible unique combinations of lesson weeks + this.optimizerInput.moduleInfo.forEach((modInfo: ModuleInfoWithConstraints) => { + Object.keys(modInfo.lessonsGrouped).forEach((lessonType: string) => { + const lessonsForLessontype: LessonsForLessonType = modInfo.lessonsGrouped[lessonType]; + const slotConstraints: Array = this.moduleLessonsToSlotconstraints( + modInfo.mod.moduleCode, + lessonsForLessontype, // TODO fix this function + ); + if (modInfo.required) { + this.z3tt.addSlotConstraintsFulfilOnlyOne(slotConstraints); + } else { + // Make these slot constraints depend on this module ID (creates a boolean selector based on the mod id) + this.z3tt.addSlotConstraintsFulfilOnlyOne(slotConstraints, modInfo.mod.moduleCode); + } + }); + }); + + // Workload constraints + if (this.optimizerInput.constraints.isWorkloadActive) { + // Non-compulsory modules make up the if-then-else + const optionalWorkloads: Array = this.optimizerInput.moduleInfo + .filter((modInfo: ModuleInfoWithConstraints) => !modInfo.required) + .map((modInfo: ModuleInfoWithConstraints) => ({ + varname: modInfo.mod.moduleCode, + cost: parseInt(modInfo.mod.moduleCredit, 10), + })); + // Compulsory modules make up the baseline workload + const compulsoryWorkloadSum: number = this.optimizerInput.moduleInfo + .filter((modInfo: ModuleInfoWithConstraints) => modInfo.required) + .map((modInfo: ModuleInfoWithConstraints) => parseInt(modInfo.mod.moduleCredit, 10)) + .reduce((a, n) => a + Number(n), 0); + // console.log(compulsoryWorkloadSum); + // Indicate that each boolean selector from the loop above has a cost if chosen + this.z3tt.setBooleanSelectorCosts( + optionalWorkloads, + compulsoryWorkloadSum, + this.optimizerInput.constraints.minWorkload, + this.optimizerInput.constraints.maxWorkload, + ); + } + + // Add requirements for free day: this ensures that we won't get SAT unless an entire day is free + if ( + this.optimizerInput.constraints.isFreeDayActive || + this.optimizerInput.constraints.isSpecificFreeDaysActive + ) { + const slotConstraints: Array = this.generateFreeDaySlotconstraints(); + if (this.optimizerInput.constraints.isFreeDayActive) { + // We fulfil K out of N possible free days based on user selection + // this.z3tt.add_constraints_fulfil_only_one(slotConstraints); + this.z3tt.addSlotConstraintsFulfilExactlyN( + slotConstraints, + this.optimizerInput.constraints.numRequiredFreeDays, + ); + } + if (this.optimizerInput.constraints.isSpecificFreeDaysActive) { + // We ensure that the days specified are free + // Assume that the free day slot constraints are in order of day-of-week + this.optimizerInput.constraints.specificFreeDays.forEach((freeday: string) => { + const dayIdx = dayStrToIdx(freeday); + const dayFreedayConstraints = slotConstraints[dayIdx]; + this.z3tt.addSlotConstraintsFulfilOnlyOne([dayFreedayConstraints]); + }); + } + } + + // Keep all mods close together + if (this.optimizerInput.constraints.isPreferCompactTimetable) { + this.z3tt.addCompactnessConstraint(); + } + + // Allow lunch hours + if (this.optimizerInput.constraints.isLunchBreakActive) { + const slotConstraints: Array = this.generateLunchBreakSlotconstraints(); + slotConstraints.forEach((sc: SlotConstraint) => { + this.z3tt.addNegativevalueSlotConstraintToNConsecutive( + sc, + this.optimizerInput.constraints.lunchHalfHours, + ); + }); + } + + // Start / end too late in the day constraint + if (this.optimizerInput.constraints.isTimeConstraintActive) { + const slotConstraint: + | SlotConstraint + | undefined = this.generateTimeconstraintSlotconstraint(); + if (slotConstraint !== undefined) { + // MUST fulfil the single slot constraint generated for the start too early / end too late + this.z3tt.addSlotConstraintsFulfilOnlyOne([slotConstraint]); + } + } + const smtlib2Str = this.z3tt.generateSmtlib2String(randomize); + return smtlib2Str; + } + + /** + * STEP 4: + * Convert the string output by the Z3 solver into an output that the caller can understand + * */ + z3OutputToTimetable(z3Output: string): OptimizerOutput { + const parsedExpr: Expr = parse(z3Output); + // console.log(parsed_expr) + + // We know from smt output that the first line is an ExprNode and not a raw string + // parsed_expr[0] === {type: "atom", content: "sat", location: {…}} + const firstLine: ExprNode = parsedExpr[0] as ExprNode; + const isSat: boolean = firstLine.content === 'sat'; + if (!isSat) return { isSat: false, timetable: {} }; // Nothing to do here + + // parsed_expr[1] === {type: "list", content: Array(19), location: {…}} + const variableAssignmentsExprs: ExprNode[] = (parsedExpr[1] as ExprNode).content as ExprNode[]; + variableAssignmentsExprs.shift(); // Removes first "model" expr: {type: "atom", content: "model", location: {…}} + const variableAssignments: Record = {}; + variableAssignmentsExprs.forEach((expr) => { + // Example expr: {type: "list", content: Array(5), location: {…}} + // Inside Array(5): + /* 0: {type: "atom", content: "define-fun", location: {…}} + 1: {type: "atom", content: "h33", location: {…}} + 2: {type: "list", content: Array(0), location: {…}} + 3: {type: "atom", content: "Int", location: {…}} + 4: {type: "atom", content: "1024", location: {…}} + */ + // We assume all model returns values have this structure, and are assigning varnames to ints + const varName: string = (expr.content[1] as ExprNode).content as string; + const varValueExpr = ((expr as ExprNode).content[4] as ExprNode).content; + let varValue = -2; + // Var_value could be an integer or an expression where the second element is the value of a negative number + // console.log(var_value_expr) + if (typeof varValueExpr === 'string') { + varValue = parseInt(varValueExpr, 10); + } else { + varValue = -1 * parseInt((varValueExpr[1] as ExprNode).content as string, 10); + } + + variableAssignments[varName] = varValue; + }); + // console.log(variableAssignments); + + // Lessons chosen in the end + // Raw inputs will be of the form [LSM1301, Lecture, 1] [LSM1301, Tutorial, 03B] + // We want that to be {"LSM1301": {"Lecture": 1, "Tutorial": 03B}} + const lessons: SemTimetableConfig = {}; + + // Create the final output timetable based on hour assignments + Object.keys(variableAssignments).forEach((key: string) => { + // Hour assignment + if (key.startsWith('t')) { + // const keySplit = key.split('_')[0]; + // const halfhouridx = parseInt(keySplit.substr(1), 10); + // const [offset, day, week] = z3TimeToGenericTime(halfhouridx); + const val = variableAssignments[key]; + if (val === UNASSIGNED) return; // Un-assigned slot + const assignment: string = this.ownerIdToStringTable[val]; + if (assignment === undefined) { + return; + // throw new Error(`Undefined assignment for variable_assignments[${key}] = ${variable_assignments[key]}`) + } + // console.log(`For z3 t${halfhouridx}, offset: ${offset}, day: ${day}, week: ${week}`); + const lessonDetails = z3VarnameToLessonInfo(assignment); + nestObject(lessons, lessonDetails); + } + }); + + // console.log(lessons); + const output: OptimizerOutput = { + isSat, + timetable: lessons, + }; + return output; + } + + /** + * Takes all lessons of a particular type from the module and converts it into a set of slot constraints, + * where only one of them need to be fulfilled + * */ + moduleLessonsToSlotconstraints( + moduleCode: string, + lessonsForLessonType: LessonsForLessonType, + ): Array { + const scs: Array = []; + + Object.keys(lessonsForLessonType).forEach((classNo: string) => { + const lessonsForClassNo: readonly RawLesson[] = lessonsForLessonType[classNo]; + // TODO abstract out key generation to function + const key: string = lessonInfoToZ3Varname( + moduleCode, + lessonsForClassNo[0].lessonType, + lessonsForClassNo[0].classNo, + ); + const ownerId: Z3LessonID = this.stringToOwnerIdTable[key]; + const startEndTimes: Array<[number, number]> = []; + // A classNo can have multiple lessons with different startEndTimes (e.g., lecture classNo 01 on Monday and Friday) + lessonsForClassNo.forEach((lesson: RawLesson) => { + // If no week calculation, run everything as every week + // TODO evaluate if this branch is even necessary anymore + if (this.weeksToSimulate.size === 0) { + const startTime = this.hhmmToZ3Time(lesson.startTime, lesson.day); + const endTime = this.hhmmToZ3Time(lesson.endTime, lesson.day); + startEndTimes.push([startTime, endTime]); + } else { + // Only add start-end times for lessons on the weeks that we are actively simulating + const weeksForLesson = lesson.weeks as NumericWeeks; + const weeksToSim = weeksForLesson.filter((week: number) => + this.weeksToSimulate.has(week), + ); + // For each week that we need to simulate, calculate the time constraints + weeksToSim.forEach((week: number) => { + // console.log(`Simulating week ${week}`); + const startTime = this.hhmmToZ3Time(lesson.startTime, lesson.day, week - 1); + const endTime = this.hhmmToZ3Time(lesson.endTime, lesson.day, week - 1); + startEndTimes.push([startTime, endTime]); + }); + } + }); + const sc: SlotConstraint = { + startEndTimes, + ownerId, + ownerString: key, + }; + scs.push(sc); + }); + // console.log(scs); + return scs; + } + + /** + * Generates an entire set of slot constraints where the solver is asked to pick exactly 1 + * This ensures that at least 1 day is free. + * NOTE: this method cares about the start-end of day timeconstraints, and will not generate variables for those slots. + * Otherwise, we will get UNSAT when we assert that those times are both free_day slots and too_early / too_late slots + * */ + generateFreeDaySlotconstraints(): Array { + const scs: Array = []; + // For each day of the week, add a slot constraint blocking out the whole day + // Free Saturday is too easy, remove it (TODO: this creates a subtle bug if DAYS is not exactly 6!) + // We won't count the last day of the week for free day calculations then + for (let day = 0; day < DAYS - 1; day++) { + const name = `FREE_${idxToDayStr(day)}`; // Timeslots for this day will be named FREE_monday for e.g, + const ownerId = FREE - day; // FREE == -2, so we generate a separate ownerid for each day by subtracting + + // To display the results in the table we need to map the owner ID and reverse tables + this.stringToOwnerIdTable[name] = ownerId; + this.ownerIdToStringTable[ownerId] = name; + + let startOffset = 0; + let endOffset = HOURS_PER_DAY * 2; + if (this.optimizerInput.constraints.isTimeConstraintActive) { + startOffset = this.hhmmToZ3Time(this.optimizerInput.constraints.earliestLessonStartTime); + endOffset = this.hhmmToZ3Time(this.optimizerInput.constraints.latestLessonEndTime); + // console.log(`Start offset: ${startOffset}, endOffset: ${endOffset}`); + } + + const startEndIdxs: Array<[number, number]> = []; + Array.from(this.weeksToSimulate).forEach((week: number) => { + // Generate the slot constraints for each day + const startidx = + (week - 1) * (HOURS_PER_WEEK * 2) + day * (HOURS_PER_DAY * 2) + startOffset; + const endidx = startidx + (endOffset - startOffset); + startEndIdxs.push([startidx, endidx]); + }); + + const sc: SlotConstraint = { + startEndTimes: startEndIdxs, + ownerId, + ownerString: name, + }; + scs.push(sc); + } + return scs; + } + + /** + * Generates a single slot constraint representing time blocked off for too-early / too-late in the day for classes. + * This method doesn't care about the times generated by other methods - it just blindly generates the constraints + * */ + generateTimeconstraintSlotconstraint(): SlotConstraint | undefined { + const startEndTimes: Array<[number, number]> = []; + const name = 'TOO_EARLY_OR_LATE'; + const ownerId = TOOEARLY_LATE; + this.stringToOwnerIdTable[name] = ownerId; + this.ownerIdToStringTable[ownerId] = name; + + const startOffset = this.hhmmToZ3Time(this.optimizerInput.constraints.earliestLessonStartTime); + const endOffset = this.hhmmToZ3Time(this.optimizerInput.constraints.latestLessonEndTime); + // Not even constraining any of the day, ignore (this is an additional optimization to avoid extra clauses) + if (startOffset === 0 && endOffset - startOffset === HOURS_PER_DAY * 2) return undefined; + + // Outside bounds + if (startOffset >= endOffset || startOffset < 0) + throw new Error(`Either startOffset ${startOffset} < 0 or >= endOffset ${endOffset}!`); + + // For each day of the week, add a slot constraint blocking out hours before and after our ideal timings + for (let day = 0; day < DAYS; day++) { + // Compute the two time windows necessary to block off start and end of day + // Start-of-day time starts at the initial index of the day, up until the offset + // Do this for every week that we have to simulate + Array.from(this.weeksToSimulate).forEach((week: number) => { + const startidx = (week - 1) * (HOURS_PER_WEEK * 2) + day * (HOURS_PER_DAY * 2); + const startidxEndidx = startidx + startOffset; + if (startidxEndidx - startidx > 0) { + startEndTimes.push([startidx, startidxEndidx]); + } + + const endidx = startidx + HOURS_PER_DAY * 2; + const endidxStartidx = startidx + endOffset; + if (endidxStartidx - endidx > 0) { + startEndTimes.push([startidx, startidxEndidx]); + } + startEndTimes.push([endidxStartidx, endidx]); + }); + } + + const sc: SlotConstraint = { + startEndTimes, + ownerId, + ownerString: name, + }; + // console.log('Slotconstraints for timeconstraint'); + // console.log(sc); + return sc; + } + + /** + * Generates a set of slotconstraints representing the times that could be blocked off for lunch. + * */ + generateLunchBreakSlotconstraints(): Array { + const scs: Array = []; + + // Calculate offsets within the day + const startOffset = this.hhmmToZ3Time(this.optimizerInput.constraints.lunchStart); + const endOffset = this.hhmmToZ3Time(this.optimizerInput.constraints.lunchEnd); + if (startOffset >= endOffset || startOffset < 0) + throw new Error(`Either startOffset ${startOffset} < 0 or >= endOffset ${endOffset}!`); + + // For each day of the week, add a slot constraint blocking out hours before and after our ideal timings + for (let day = 0; day < DAYS; day++) { + // Compute the lunch break window for each day and week + Array.from(this.weeksToSimulate).forEach((week: number) => { + const baseidx = (week - 1) * (HOURS_PER_WEEK * 2) + day * (HOURS_PER_DAY * 2); + const startidx = baseidx + startOffset; + const endidx = baseidx + endOffset; + const sc: SlotConstraint = { + startEndTimes: [[startidx, endidx]], + ownerId: UNASSIGNED, + ownerString: 'UNASSIGNED', + }; + scs.push(sc); + }); + } + + // console.log('Slotconstraints for lunchbreak'); + // console.log(scs); + return scs; + } + + /** + * Convert a time (hhmm format), the day it occurs, and the week it occurs, + * into the integer representation of that timeslot in Z3. + * There are defined start and end times to reduce the number of variables in Z3. + * No point having vars to represent midnight to 8am if no classes are there, same for evening. + * */ + hhmmToZ3Time(time: LessonTime, day: DayText = 'Monday', week = 0): number { + const hour = parseInt(time.substring(0, 2), 10); + const minuteOffset = parseInt(time.substring(2), 10) === 0 ? 0 : 1; + // We assume lessons within start to end hour each day + if ( + hour < this.startHour || + hour > this.endHour || + (hour === this.endHour && minuteOffset === 1) + ) { + throw new Error( + `Lesson either starts before start_hour ${hour} < ${this.startHour} or ends after end_hour ${hour}`, + ); + } else { + const hourIndex = hour - this.startHour; + const dayIndex = dayStrToIdx(day); + if (dayIndex === undefined) throw new Error(`Day ${day} is not a valid day string!`); + // hour_index * 2 (since we count half-hours) + // + half_hour_addon since we offset by 1 unit if it's a half hour + // + number of hours in a day * 2 to get number of half-hours + // + number of weeks offset from the "base week" + const idx = + hourIndex * 2 + minuteOffset + dayIndex * (HOURS_PER_DAY * 2) + week * (HOURS_PER_WEEK * 2); + return idx; + } + } +} + +/* + Conversion from times like 0 --> (1, 0) (1st slot of the day 0-indexed, Monday) + */ +function z3TimeToGenericTime(z3Time: number): [number, number, number] { + // Day is easy: each day has(self.end_hour - self.start_hour) * 2) slots + // If there are 60 slots per week, and we are at slot 70, we're 10 slots into the current week + const week = Math.floor(z3Time / (HOURS_PER_WEEK * 2)); + const z3TimeWeek = z3Time % (HOURS_PER_WEEK * 2); + const day = Math.floor(z3TimeWeek / (HOURS_PER_DAY * 2)); + const offset = z3TimeWeek % (HOURS_PER_DAY * 2); + return [offset, day, week]; +} + +/** + * Simple conversion of string into a monday-index-0 number + * */ +function dayStrToIdx(day: string): number { + return DAY_IDXS[day.toLowerCase()]; +} + +/** + * Simple conversion of string into a monday-index-0 number + * */ +function idxToDayStr(idx: number): string { + return IDX_DAYS[idx]; +} + +/** + * Converts a module, lesson type, and class number for a lesson + * into a variable name that z3 can use. We have to get the abbreviated name + * since + * */ +function lessonInfoToZ3Varname(moduleCode: string, lessonType: string, classNo: string): string { + const lessonTypeAbbrev = LESSON_TYPE_ABBREV[lessonType]; + return [moduleCode, lessonTypeAbbrev, classNo].join('__'); +} + +/** + * Recovers lesson info from z3 variable name + * */ +function z3VarnameToLessonInfo(z3Varname: string): [string, string, string] { + const [moduleCode, lessonTypeAbbrev, classNo] = z3Varname.split('__'); + const lessonType = LESSON_ABBREV_TYPE[lessonTypeAbbrev]; + return [moduleCode, lessonType, classNo]; +} + +// Assign an array of properties to an object - creating nested levels +// E.g., nestObject({}, [a, b, c]) ==> {a: {b: c}} +// TODO extract to file? +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function nestObject(obj: any, keyPath: any) { + let curObj = obj; + const value = keyPath[keyPath.length - 1]; + const lastKeyIndex = Math.max(0, keyPath.length - 2); + for (let i = 0; i < lastKeyIndex; ++i) { + const key = keyPath[i]; + if (!(key in curObj)) { + curObj[key] = {}; + } + curObj = curObj[key]; + } + curObj[keyPath[lastKeyIndex]] = value; +} diff --git a/website/src/utils/optimizer/timetableOptimizer.test.ts b/website/src/utils/optimizer/timetableOptimizer.test.ts new file mode 100644 index 0000000000..d95ff0cb24 --- /dev/null +++ b/website/src/utils/optimizer/timetableOptimizer.test.ts @@ -0,0 +1,225 @@ +import { TimetableOptimizer } from 'utils/optimizer/timetableOptimizer'; +import { + OptimizerInput, + OptimizerCallbacks, + Z3WorkerMessage, + Z3WorkerMessageKind, + defaultConstraints, +} from 'types/optimizer'; +import { OptimizerInputSmtlibConverter } from 'utils/optimizer/converter'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore Jest will fix this for us +import WebpackWorker, { mockOnmessage, mockPostMessage } from './z3WebWorker.worker'; + +// Directly mock the converter functions, allows us to track them +const mockGenerateWeekSolveSmtLib2String = jest.fn(); +const mockUpdateZ3WeeksolveOutput = jest.fn(); +const mockGenerateTimetableSolveSmtLib2String = jest.fn(); +const mockZ3OutputToTimetable = jest.fn(); +jest.mock('utils/optimizer/converter', () => + // Works and lets you check for constructor calls: + ({ + // eslint-disable-next-line @typescript-eslint/no-empty-function + OptimizerInputSmtlibConverter: jest.fn().mockImplementation(() => ({ + generateWeekSolveSmtLib2String: mockGenerateWeekSolveSmtLib2String, + updateZ3WeeksolveOutput: mockUpdateZ3WeeksolveOutput, + generateTimetableSolveSmtLib2String: mockGenerateTimetableSolveSmtLib2String, + z3OutputToTimetable: mockZ3OutputToTimetable, + })), + }), +); + +// Mock webworker from __mocks__ +jest.mock('./z3WebWorker.worker.ts'); + +beforeEach(() => { + WebpackWorker.mockClear(); + mockOnmessage.mockClear(); + mockPostMessage.mockClear(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Jest will fix this for us + OptimizerInputSmtlibConverter.mockClear(); +}); + +const callbacks: OptimizerCallbacks = { + onOptimizerInitialized: jest.fn(), + onSmtLib2ResultOutput: jest.fn(), + onSmtlib2InputCreated: jest.fn(), + onTimetableOutput: jest.fn(), +}; + +describe('initOptimizer', () => { + it('should have a clear buffer, start before the stage 1 solve, and send an init mesage', () => { + expect(mockPostMessage).toHaveBeenCalledTimes(0); + TimetableOptimizer.initOptimizer(callbacks); + expect(TimetableOptimizer.printBuffer).toEqual(''); + expect(TimetableOptimizer.errBuffer).toEqual(''); + expect(TimetableOptimizer.completedStage1Solve).toBe(false); + const message: Z3WorkerMessage = { kind: Z3WorkerMessageKind.INIT, msg: '' }; + expect(mockPostMessage).toHaveBeenCalledWith(message); + expect(mockPostMessage).toHaveBeenCalledTimes(1); + }); +}); + +describe('loadInput', () => { + it('should call the converter', () => { + TimetableOptimizer.initOptimizer(callbacks); + const optInput: OptimizerInput = { + moduleInfo: [], + constraints: defaultConstraints, + }; + TimetableOptimizer.loadInput(optInput); + expect(OptimizerInputSmtlibConverter).toHaveBeenCalled(); + }); +}); + +describe('solve', () => { + it('should clear buffers and call the worker to optimizer', () => { + TimetableOptimizer.initOptimizer(callbacks); + const optInput: OptimizerInput = { + moduleInfo: [], + constraints: defaultConstraints, + }; + TimetableOptimizer.loadInput(optInput); + TimetableOptimizer.solve(); + // First call is from init, second call is from solve() + expect(mockPostMessage.mock.calls[1][0]).toMatchObject({ kind: Z3WorkerMessageKind.OPTIMIZE }); + expect(TimetableOptimizer.printBuffer).toEqual(''); + expect(TimetableOptimizer.errBuffer).toEqual(''); + }); +}); + +describe('receiveWorkerMessage', () => { + it('should call the initialized callback when the worker sends an initialized message', () => { + TimetableOptimizer.initOptimizer(callbacks); + // Send initialized message + const messageData: Z3WorkerMessage = { kind: Z3WorkerMessageKind.INITIALIZED, msg: '' }; + const msg: MessageEvent = new MessageEvent('placeholderType', { + data: messageData, + lastEventId: 'placeholder', + origin: 'placeholder', + ports: [], + source: null, + }); + expect(callbacks.onOptimizerInitialized).toBeCalledTimes(0); + TimetableOptimizer.receiveWorkerMessage(msg); + expect(callbacks.onOptimizerInitialized).toBeCalledTimes(1); + }); + + it('should add to the stdout buffer when it receives a print message', () => { + TimetableOptimizer.initOptimizer(callbacks); + // Send initialized message + const messageData: Z3WorkerMessage = { kind: Z3WorkerMessageKind.PRINT, msg: 'someData' }; + const msg: MessageEvent = new MessageEvent('placeholderType', { + data: messageData, + lastEventId: 'placeholder', + origin: 'placeholder', + ports: [], + source: null, + }); + expect(TimetableOptimizer.printBuffer).toEqual(''); + TimetableOptimizer.receiveWorkerMessage(msg); + expect(TimetableOptimizer.printBuffer).toEqual(`${messageData.msg}\n`); + expect(TimetableOptimizer.errBuffer).toEqual(''); + }); + + it('should add to the stderr buffer when it receives an error message', () => { + TimetableOptimizer.initOptimizer(callbacks); + // Send initialized message + const messageData: Z3WorkerMessage = { kind: Z3WorkerMessageKind.ERR, msg: 'someData' }; + const msg: MessageEvent = new MessageEvent('placeholderType', { + data: messageData, + lastEventId: 'placeholder', + origin: 'placeholder', + ports: [], + source: null, + }); + expect(TimetableOptimizer.errBuffer).toEqual(''); + TimetableOptimizer.receiveWorkerMessage(msg); + expect(TimetableOptimizer.errBuffer).toEqual(`${messageData.msg}\n`); + expect(TimetableOptimizer.printBuffer).toEqual(''); + }); + + it('should not do anything if an unknown message type is passed', () => { + TimetableOptimizer.initOptimizer(callbacks); + // Send initialized message + const messageData: Z3WorkerMessage = { kind: Z3WorkerMessageKind.ABORT, msg: 'someData' }; + const msg: MessageEvent = new MessageEvent('placeholderType', { + data: messageData, + lastEventId: 'placeholder', + origin: 'placeholder', + ports: [], + source: null, + }); + + TimetableOptimizer.receiveWorkerMessage(msg); + expect(TimetableOptimizer.printBuffer).toEqual(''); + expect(TimetableOptimizer.errBuffer).toEqual(''); + expect(callbacks.onSmtlib2InputCreated).not.toBeCalled(); + expect(callbacks.onSmtLib2ResultOutput).not.toBeCalled(); + expect(callbacks.onTimetableOutput).not.toBeCalled(); + }); + + it('should return without calling any callbacks if the buffers are empty', () => { + TimetableOptimizer.initOptimizer(callbacks); + // Send initialized message + const messageData: Z3WorkerMessage = { kind: Z3WorkerMessageKind.EXIT, msg: '' }; + const msg: MessageEvent = new MessageEvent('placeholderType', { + data: messageData, + lastEventId: 'placeholder', + origin: 'placeholder', + ports: [], + source: null, + }); + + expect(TimetableOptimizer.printBuffer).toEqual(''); + expect(TimetableOptimizer.errBuffer).toEqual(''); + TimetableOptimizer.receiveWorkerMessage(msg); + expect(callbacks.onSmtlib2InputCreated).not.toBeCalled(); + expect(callbacks.onSmtLib2ResultOutput).not.toBeCalled(); + expect(callbacks.onTimetableOutput).not.toBeCalled(); + }); + + it('should run the stage 1 solve procedure if it is not already completed', () => { + TimetableOptimizer.initOptimizer(callbacks); + TimetableOptimizer.printBuffer = 'some smt2lib stuff'; + // Send initialized message + const messageData: Z3WorkerMessage = { kind: Z3WorkerMessageKind.EXIT, msg: '' }; + const msg: MessageEvent = new MessageEvent('placeholderType', { + data: messageData, + lastEventId: 'placeholder', + origin: 'placeholder', + ports: [], + source: null, + }); + TimetableOptimizer.receiveWorkerMessage(msg); + expect(TimetableOptimizer.completedStage1Solve).toBe(true); // should get updated to progress to next stage + expect(mockUpdateZ3WeeksolveOutput).toBeCalledWith('some smt2lib stuff'); + expect(mockGenerateTimetableSolveSmtLib2String).toBeCalled(); + expect(callbacks.onSmtLib2ResultOutput).not.toBeCalled(); + expect(callbacks.onSmtlib2InputCreated).toBeCalled(); + expect(mockPostMessage.mock.calls[1][0]).toMatchObject({ kind: Z3WorkerMessageKind.OPTIMIZE }); + }); + + it('should run the timetable solve procedure if the week solve is completed', () => { + TimetableOptimizer.initOptimizer(callbacks); + TimetableOptimizer.completedStage1Solve = true; + TimetableOptimizer.printBuffer = 'some smt2lib stuff'; + // Send initialized message + const messageData: Z3WorkerMessage = { kind: Z3WorkerMessageKind.EXIT, msg: '' }; + const msg: MessageEvent = new MessageEvent('placeholderType', { + data: messageData, + lastEventId: 'placeholder', + origin: 'placeholder', + ports: [], + source: null, + }); + TimetableOptimizer.receiveWorkerMessage(msg); + + expect(TimetableOptimizer.completedStage1Solve).toBe(false); // should get reset + expect(mockUpdateZ3WeeksolveOutput).toBeCalledWith('some smt2lib stuff'); + expect(callbacks.onSmtLib2ResultOutput).toBeCalledWith('some smt2lib stuff\n'); + expect(mockZ3OutputToTimetable).toBeCalledWith('some smt2lib stuff'); + expect(callbacks.onTimetableOutput).toBeCalled(); + }); +}); diff --git a/website/src/utils/optimizer/timetableOptimizer.ts b/website/src/utils/optimizer/timetableOptimizer.ts new file mode 100644 index 0000000000..3e4b77f095 --- /dev/null +++ b/website/src/utils/optimizer/timetableOptimizer.ts @@ -0,0 +1,154 @@ +// import { TimetableOutput, TimetableSmtlib2Converter } from './timetable_to_smtlib2'; +import { OptimizerInputSmtlibConverter } from 'utils/optimizer/converter'; +import { + OptimizerInput, + OptimizerOutput, + OptimizerCallbacks, + Z3WorkerMessage, + Z3WorkerMessageKind, +} from 'types/optimizer'; +import { DAY_START_HOUR, DAY_END_HOUR, NUM_WEEKS, HOURS_PER_WEEK } from 'utils/optimizer/constants'; +// ts extension is necessary from webpack documentation for worker-loader +// eslint-disable-next-line import/extensions +import WebpackWorker from './z3WebWorker.worker.ts'; + +/** + * The TimetableOptimizer takes a generic timetable as input and manages the lifecycle of running the + * Z3 system to find a timetable solution. + * Runs the web worker, receives input/output, communicates through callbacks to the calling class. + * + * Only one TimetableOptimizer per application - allows stateless components to run manager methods + * */ +export class TimetableOptimizer { + static optInput: OptimizerInput; + + static converter: OptimizerInputSmtlibConverter; + + static smtString: string; + + static callbacks: OptimizerCallbacks; + + static printBuffer: string; + + static errBuffer: string; + + static worker: WebpackWorker; + + static completedStage1Solve: boolean; // Need to complete week-solving before timetable-solving + + static initOptimizer(callbacks: OptimizerCallbacks) { + // console.log('Starting to initialize Z3...'); + TimetableOptimizer.callbacks = callbacks; + TimetableOptimizer.resetBuffers(); + TimetableOptimizer.completedStage1Solve = false; + // Set up worker if it's not set up + if (!TimetableOptimizer.worker) { + TimetableOptimizer.worker = new WebpackWorker(); + TimetableOptimizer.worker.onmessage = TimetableOptimizer.receiveWorkerMessage; + } + TimetableOptimizer.managerPostMessage(Z3WorkerMessageKind.INIT, ''); + } + + /** + * Register a generic timetable a set of callbacks to be called for different states in the Z3 solver lifecycle + * */ + static loadInput(optInput: OptimizerInput) { + // console.log('Loaded optimizer input'); + // console.log(optInput); + TimetableOptimizer.optInput = optInput; + TimetableOptimizer.converter = new OptimizerInputSmtlibConverter( + TimetableOptimizer.optInput, + NUM_WEEKS * HOURS_PER_WEEK * 2, // Number of "half-hour" slots + DAY_START_HOUR, // Start at 0800 (8 am) + DAY_END_HOUR, /// End at 2200 (10 pm) + ); + } + + static solve() { + TimetableOptimizer.resetBuffers(); + // TODO handle errors from this generation + const weekSolveStr = TimetableOptimizer.converter.generateWeekSolveSmtLib2String(); + TimetableOptimizer.managerPostMessage(Z3WorkerMessageKind.OPTIMIZE, weekSolveStr); + } + + static receiveWorkerMessage(e: MessageEvent) { + const message: Z3WorkerMessage = e.data; + // console.log("Kind: %s, Message: %s", message.kind, message.msg) + switch (message.kind) { + case Z3WorkerMessageKind.INITIALIZED: + // Call the initialization callback + // console.log('Manager initialized Z3!'); + TimetableOptimizer.callbacks.onOptimizerInitialized(); + break; + case Z3WorkerMessageKind.PRINT: + TimetableOptimizer.printBuffer += `${message.msg}\n`; + break; + case Z3WorkerMessageKind.ERR: + TimetableOptimizer.errBuffer += `${message.msg}\n`; + break; + case Z3WorkerMessageKind.EXIT: + // Z3 Initialization exit + // console.log('Z3 messages on exit: '); + if (TimetableOptimizer.printBuffer === '' && TimetableOptimizer.errBuffer === '') { + // console.log('Premature exit - Z3 was initializing (this is normal)'); + return; // Premature exit (probably initialization) + } + + // Print buffers generically + if (TimetableOptimizer.printBuffer !== '') { + // console.log(TimetableOptimizer.printBuffer); + } + if (TimetableOptimizer.errBuffer !== '') { + // console.error(TimetableOptimizer.errBuffer); + } + + if (!TimetableOptimizer.completedStage1Solve) { + // Indicate that next time we call this callback, we have the timetable result + TimetableOptimizer.completedStage1Solve = true; + // Update the converter with the week-solve result + // TODO: enable + TimetableOptimizer.converter.updateZ3WeeksolveOutput(TimetableOptimizer.printBuffer); + // Generate the SMTLIB2 string based on the week-solve:w + TimetableOptimizer.smtString = TimetableOptimizer.converter.generateTimetableSolveSmtLib2String(); + // Run callback to update the generated smtlib2 string + TimetableOptimizer.callbacks.onSmtlib2InputCreated(TimetableOptimizer.smtString); + // Reset state for our next optimization run + TimetableOptimizer.resetBuffers(); + // Two stage solve: first solve for the week constraints, then solve for the actual timetable + TimetableOptimizer.managerPostMessage( + Z3WorkerMessageKind.OPTIMIZE, + TimetableOptimizer.smtString, + ); + } else { + // Reset solve state + TimetableOptimizer.completedStage1Solve = false; + // Deal with real solve state + // Call the output callback + TimetableOptimizer.callbacks.onSmtLib2ResultOutput( + `${TimetableOptimizer.printBuffer}\n${TimetableOptimizer.errBuffer}`, + ); + // Process the output text we just got from the Z3 solver + const timetable: OptimizerOutput = TimetableOptimizer.converter.z3OutputToTimetable( + TimetableOptimizer.printBuffer, + ); + TimetableOptimizer.callbacks.onTimetableOutput(timetable); + } + break; + default: + break; + } + } + + /** + * Generically post a message to the worker + * */ + static managerPostMessage(kind: Z3WorkerMessageKind, msg: string) { + const message: Z3WorkerMessage = { kind, msg }; + TimetableOptimizer.worker.postMessage(message); + } + + static resetBuffers() { + TimetableOptimizer.printBuffer = ''; + TimetableOptimizer.errBuffer = ''; + } +} diff --git a/website/src/utils/optimizer/z3TimetableSolver.test.ts b/website/src/utils/optimizer/z3TimetableSolver.test.ts new file mode 100644 index 0000000000..cc92258bdf --- /dev/null +++ b/website/src/utils/optimizer/z3TimetableSolver.test.ts @@ -0,0 +1,323 @@ +import { + Z3TimetableSolver, + SELECTOR_PREFIX, + SELECTOR_FULFIL_N_PREFIX, + SELECTOR_OPTIONAL_PREFIX, +} from 'utils/optimizer/z3TimetableSolver'; +import { SlotConstraint, WorkloadCost } from 'types/optimizer'; + +describe('constructor', () => { + it('constructs initial time arrays list as expected', () => { + const z3ts = new Z3TimetableSolver(4); + expect(z3ts.timevars).toEqual(['t0', 't1', 't2', 't3']); + }); + + it('constructs initial time arrays list with string variables', () => { + const z3ts = new Z3TimetableSolver(4, ['h0', 'h1', 'h2', 'h3']); + expect(z3ts.timevars).toEqual(['t0_h0', 't1_h1', 't2_h2', 't3_h3']); + }); + + it('errors when too many variable names are passed', () => { + expect(() => new Z3TimetableSolver(2, ['h0', 'h1', 'h2'])).toThrow(); + }); + + it('errors when too few variable names are passed', () => { + expect(() => new Z3TimetableSolver(2, ['h0'])).toThrow(); + }); +}); + +// Constants for tests +const sc: SlotConstraint = { + startEndTimes: [[0, 1]], + ownerId: 7, + ownerString: '7', +}; // ID 7 assigned to slots 0 and 1 +const sc2: SlotConstraint = { + startEndTimes: [[2, 3]], + ownerId: 8, + ownerString: '8', +}; // ID 8 assigned to slots 2 and 3 +const scErrorSmaller: SlotConstraint = { + startEndTimes: [[-2, -3]], + ownerId: 8, + ownerString: '8', +}; +const scErrorLarger: SlotConstraint = { + startEndTimes: [[16, 17]], + ownerId: 8, + ownerString: '8', +}; + +describe('addSlotConstraintsFulfilOnlyOne', () => { + it('generates expected smtlib2 string when choosing between one of two slots', () => { + const z3ts = new Z3TimetableSolver(4); + z3ts.addSlotConstraintsFulfilOnlyOne([sc, sc2]); + + // Expect + const expected = `(declare-fun ${SELECTOR_PREFIX}7_8 () Int) +(declare-fun t0 () Int) +(declare-fun t2 () Int) +(assert (or (= ${SELECTOR_PREFIX}7_8 7) (= ${SELECTOR_PREFIX}7_8 8))) +(assert (= (= ${SELECTOR_PREFIX}7_8 7) (= t0 7))) +(assert (= (= ${SELECTOR_PREFIX}7_8 8) (= t2 8))) +(assert (or (= t0 7) (= t0 -1))) +(assert (or (= t2 8) (= t2 -1))) +(declare-fun BUGFIX_VAR_DONTASK () Int) +(assert-soft (= BUGFIX_VAR_DONTASK 10)) +(check-sat) +(get-model) +(get-objectives) +(exit)`; + // Turn off randomization so that the generated string is also deterministic + const actual = z3ts.generateSmtlib2String(false); + expect(actual).toEqual(expected); + }); + + it('generates expected smtlib2 string when choosing between one of two slots with an extra boolean variable', () => { + const z3ts = new Z3TimetableSolver(4); + + const boolVar = 'booleanSelector'; + z3ts.addSlotConstraintsFulfilOnlyOne([sc, sc2], boolVar); + + // Expect + const expected = `(declare-fun ${SELECTOR_OPTIONAL_PREFIX}booleanSelector () Bool) +(declare-fun ${SELECTOR_PREFIX}7_8 () Int) +(declare-fun t0 () Int) +(declare-fun t2 () Int) +(assert (= ${SELECTOR_OPTIONAL_PREFIX}booleanSelector (or (= ${SELECTOR_PREFIX}7_8 7) (= ${SELECTOR_PREFIX}7_8 8)))) +(assert (= (= ${SELECTOR_PREFIX}7_8 7) (= t0 7))) +(assert (= (= ${SELECTOR_PREFIX}7_8 8) (= t2 8))) +(assert (or (= t0 7) (= t0 -1))) +(assert (or (= t2 8) (= t2 -1))) +(declare-fun BUGFIX_VAR_DONTASK () Int) +(assert-soft (= BUGFIX_VAR_DONTASK 10)) +(check-sat) +(get-model) +(get-objectives) +(exit)`; + // Turn off randomization so that the generated string is also deterministic + const actual = z3ts.generateSmtlib2String(false); + expect(actual).toEqual(expected); + }); + + it('errors when slotConstraint when too small a time value is passed', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addSlotConstraintsFulfilOnlyOne([sc2, scErrorSmaller])).toThrow(); + }); + + it('errors when slotConstraint when too large a time value is passed', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addSlotConstraintsFulfilOnlyOne([sc2, scErrorLarger])).toThrow(); + }); +}); + +const sc3: SlotConstraint = { + startEndTimes: [[0, 1]], + ownerId: 9, + ownerString: '9', +}; // ID 7 assigned to slots 0 and 1 +describe('addSlotConstraintsFulfilExactlyN', () => { + it('generates expected smtlib2 string when choosing between two of three slots', () => { + const z3ts = new Z3TimetableSolver(4); + z3ts.addSlotConstraintsFulfilExactlyN([sc, sc2, sc3], 2); + // Expect + const expected = `(declare-fun ${SELECTOR_FULFIL_N_PREFIX}7 () Bool) +(declare-fun ${SELECTOR_FULFIL_N_PREFIX}8 () Bool) +(declare-fun ${SELECTOR_FULFIL_N_PREFIX}9 () Bool) +(declare-fun t0 () Int) +(declare-fun t2 () Int) +(assert (= ${SELECTOR_FULFIL_N_PREFIX}7 (= t0 7))) +(assert (= ${SELECTOR_FULFIL_N_PREFIX}8 (= t2 8))) +(assert (= ${SELECTOR_FULFIL_N_PREFIX}9 (= t0 9))) +(assert ((_ pbeq 2 1 1 1) ${SELECTOR_FULFIL_N_PREFIX}7 ${SELECTOR_FULFIL_N_PREFIX}8 ${SELECTOR_FULFIL_N_PREFIX}9)) +(assert (or (= t0 7) (= t0 -1) (= t0 9))) +(assert (or (= t2 8) (= t2 -1))) +(declare-fun BUGFIX_VAR_DONTASK () Int) +(assert-soft (= BUGFIX_VAR_DONTASK 10)) +(check-sat) +(get-model) +(get-objectives) +(exit)`; + // Turn off randomization so that the generated string is also deterministic + const actual = z3ts.generateSmtlib2String(false); + expect(actual).toEqual(expected); + }); + + it('errors when slotConstraint when too small a time value is passed', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addSlotConstraintsFulfilExactlyN([sc2, scErrorSmaller], 1)).toThrow(); + }); + + it('errors when slotConstraint when too large a time value is passed', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addSlotConstraintsFulfilExactlyN([sc2, scErrorLarger], 1)).toThrow(); + }); + + it('errors when asked to choosing less than 0 slots', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addSlotConstraintsFulfilExactlyN([sc, sc2, sc3], -1)).toThrow(); + }); + it('errors when asked to choosing more than the passed slots', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addSlotConstraintsFulfilExactlyN([sc, sc2, sc3], 7)).toThrow(); + }); +}); + +describe('setBooleanSelectorCosts', () => { + it('generates expected smtlib2 string when setting basic workload costs', () => { + const z3ts = new Z3TimetableSolver(4); + const boolSelectorCosts: WorkloadCost[] = [ + { + varname: 'v1', + cost: 10, + }, + { + varname: 'v2', + cost: 1, + }, + ]; + const baseCost = 20; + const minCost = 1; + const maxCost = 45; + z3ts.setBooleanSelectorCosts(boolSelectorCosts, baseCost, minCost, maxCost); + const expected = `(declare-fun ${SELECTOR_OPTIONAL_PREFIX}v1 () Bool) +(declare-fun ${SELECTOR_OPTIONAL_PREFIX}v2 () Bool) +(declare-fun workloadsum () Int) +(assert (= workloadsum (+ ${baseCost} (ite ${SELECTOR_OPTIONAL_PREFIX}v1 10 0) (ite ${SELECTOR_OPTIONAL_PREFIX}v2 1 0)))) +(assert (>= workloadsum ${minCost})) +(assert (<= workloadsum ${maxCost})) +(declare-fun BUGFIX_VAR_DONTASK () Int) +(assert-soft (= BUGFIX_VAR_DONTASK 10)) +(check-sat) +(get-model) +(get-objectives) +(exit)`; + // Turn off randomization so that the generated string is also deterministic + const actual = z3ts.generateSmtlib2String(false); + expect(actual).toEqual(expected); + }); + + // TODO: Test minCost > maxCost, baseCost > maxCost, etc, right now there are no UI preventions for this + // This is OK since it will just return unsat. +}); + +describe('addNegativevalueSlotConstraintToNConsecutive', () => { + it('generates expected smtlib2 string when asking for 2 consecutive slots out of a 3 slot period', () => { + const z3ts = new Z3TimetableSolver(4); + const scConsec: SlotConstraint = { + startEndTimes: [[0, 3]], + ownerId: 7, + ownerString: '7', + }; + z3ts.addNegativevalueSlotConstraintToNConsecutive(scConsec, 2); + // This doesn't include t2 - t3 since a slotConstraint ending with 3 ==> we can't assign to the t3 slot + // Note that the slots are asserted later to -1 because based on the calls here, they can only take the -1 value + // i.e., due to addPossibleValuesToVariable + const expected = `(declare-fun t0 () Int) +(declare-fun t1 () Int) +(declare-fun t2 () Int) +(assert (or (and (<= t0 -1) (<= t1 -1)) (and (<= t1 -1) (<= t2 -1)))) +(assert (= t0 -1)) +(assert (= t1 -1)) +(assert (= t2 -1)) +(declare-fun BUGFIX_VAR_DONTASK () Int) +(assert-soft (= BUGFIX_VAR_DONTASK 10)) +(check-sat) +(get-model) +(get-objectives) +(exit)`; + // Turn off randomization so that the generated string is also deterministic + const actual = z3ts.generateSmtlib2String(false); + expect(actual).toEqual(expected); + }); + + it('errors when slotConstraint when too small a time value is passed', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addNegativevalueSlotConstraintToNConsecutive(scErrorSmaller, 1)).toThrow(); + }); + + it('errors when slotConstraint when too large a time value is passed', () => { + const z3ts = new Z3TimetableSolver(4); + expect(() => z3ts.addNegativevalueSlotConstraintToNConsecutive(scErrorLarger, 1)).toThrow(); + }); +}); + +describe('addCompactnessConstraint', () => { + it('generates expected smtlib2 string when constraining compactness for all adjacent modules', () => { + const z3ts = new Z3TimetableSolver(4); + // sc1: [0, 1], scMiddle: [1, 2], sc2: [2, 3] + // We will say that sc1 and scMiddle are in a "choose only 1" scenario + // Then, assert sc2 unconditionally + // Compactness should assert that t0 and t1 and t2 are all assigned + const scMiddle: SlotConstraint = { + startEndTimes: [[1, 2]], + ownerId: 9, + ownerString: '9', + }; // ID 7 assigned to slots 0 and 1 + z3ts.addSlotConstraintsFulfilOnlyOne([sc, scMiddle]); + z3ts.addSlotConstraintsFulfilOnlyOne([sc2]); + z3ts.addCompactnessConstraint(); + // We expect to see the compactness constraint between t0, t1, and t2 since t1 is a possible choice and lies between them + const expected = `(declare-fun SL_7_9 () Int) +(declare-fun t0 () Int) +(declare-fun t1 () Int) +(declare-fun SL_8 () Int) +(declare-fun t2 () Int) +(assert (or (= SL_7_9 7) (= SL_7_9 9))) +(assert (= (= SL_7_9 7) (= t0 7))) +(assert (= (= SL_7_9 9) (= t1 9))) +(assert (= SL_8 8)) +(assert (= (= SL_8 8) (= t2 8))) +(assert (or (= t0 7) (= t0 -1))) +(assert (or (= t1 9) (= t1 -1))) +(assert (or (= t2 8) (= t2 -1))) +(assert-soft (= (>= t0 0) (or (= t1 t0) (and (not (= t1 t0)) (>= t1 0)))) :weight 1 :id nextvar) +(assert-soft (= (>= t1 0) (or (= t2 t1) (and (not (= t2 t1)) (>= t2 0)))) :weight 1 :id nextvar) +(declare-fun BUGFIX_VAR_DONTASK () Int) +(assert-soft (= BUGFIX_VAR_DONTASK 10)) +(check-sat) +(get-model) +(get-objectives) +(exit)`; + // Turn off randomization so that the generated string is also deterministic + const actual = z3ts.generateSmtlib2String(false); + expect(actual).toEqual(expected); + }); + it('generates expected smtlib2 string without compactness clauses when no modules are adjacent', () => { + const z3ts = new Z3TimetableSolver(4); + z3ts.addSlotConstraintsFulfilOnlyOne([sc]); + z3ts.addSlotConstraintsFulfilOnlyOne([sc2]); + z3ts.addCompactnessConstraint(); + // We expect to see no compactness constraints (no assert-softs) since no lessons could possibly be placed next to each other + const expected = `(declare-fun SL_7 () Int) +(declare-fun t0 () Int) +(declare-fun SL_8 () Int) +(declare-fun t2 () Int) +(assert (= SL_7 7)) +(assert (= (= SL_7 7) (= t0 7))) +(assert (= SL_8 8)) +(assert (= (= SL_8 8) (= t2 8))) +(assert (or (= t0 7) (= t0 -1))) +(assert (or (= t2 8) (= t2 -1))) +(declare-fun BUGFIX_VAR_DONTASK () Int) +(assert-soft (= BUGFIX_VAR_DONTASK 10)) +(check-sat) +(get-model) +(get-objectives) +(exit)`; + // Turn off randomization so that the generated string is also deterministic + const actual = z3ts.generateSmtlib2String(false); + expect(actual).toEqual(expected); + }); +}); + +it('randomness option generates string containing the options that enforce randomness', () => { + const z3ts = new Z3TimetableSolver(4); + const actual = z3ts.generateSmtlib2String(true); + const randomPart1 = '(set-option :auto_config false)'; + const randomPart2 = '(set-option :smt.phase_selection 5)'; + const randomPart3 = '(set-option :smt.random-seed'; + expect(actual.includes(randomPart1)).toBeTruthy(); + expect(actual.includes(randomPart2)).toBeTruthy(); + expect(actual.includes(randomPart3)).toBeTruthy(); +}); diff --git a/website/src/utils/optimizer/z3TimetableSolver.ts b/website/src/utils/optimizer/z3TimetableSolver.ts new file mode 100644 index 0000000000..18b0b12b8e --- /dev/null +++ b/website/src/utils/optimizer/z3TimetableSolver.ts @@ -0,0 +1,402 @@ +// TODO convert to import explicitly +import { SlotConstraint, WorkloadCost } from 'types/optimizer'; + +import * as smt from 'smtlib-ext'; + +// Constants for owner id values that are not lessons +export const UNASSIGNED = -1; +export const FREE = -2; +export const TOOEARLY_LATE = -20; +export const VAR_UNASSIGNED_WEIGHT = 1; +export const BOOLVAR_ASSIGNED_WEIGHT = 100000; + +// Prefixes +export const TIMEVAR_PREFIX = 't'; +export const SELECTOR_PREFIX = 'SL_'; +export const SELECTOR_FULFIL_N_PREFIX = 'SLKB_'; +export const SELECTOR_OPTIONAL_PREFIX = 'OPT_'; + +export class Z3TimetableSolver { + timevars: string[]; // ["t0", "t1", ....] representing all possible half-hour slots + + assignedIntvarsPossiblevalues: Record>; // What allowed values can each time val have + + boolSelectorsSet: Set; // Basically module names e.g, CS3203, to select or de-select a module + + variablesSolver: smt.BaseSolver; // Just for variables assignment - hack to get the variables assignment ABOVE the constraints + + solver: smt.BaseSolver; // For the actual constraints + + constrainCompactness: boolean; // whether we want all classes to be right after each other as much as possible + + /** + * Initializes the solvers and all the time variables representing each half-hour slot. + * These don't have to be half-hours, the solver treats them generically as discrete time units. + * Callers can also specify the name of each time unit in case t0, t1, etc, is too undescriptive. + * Generally, this is now called with the day of the week, actual hour and minute, and the week number (for ease of reading) + * */ + constructor(totalTimeUnits: number, timeUnitNames?: string[]) { + // Create time variable names based on just the raw time unit, or pass in a list of strings to be appended to the raw time hours + if (timeUnitNames !== undefined) { + if (timeUnitNames.length !== totalTimeUnits) { + throw new Error('Size of time_unit_names array must be equal to total_time_units!'); + } else { + this.timevars = Array.from( + new Array(totalTimeUnits), + (_: number, i: number) => `${TIMEVAR_PREFIX}${i}_${timeUnitNames[i]}`, + ); + } + } else { + this.timevars = Array.from( + new Array(totalTimeUnits), + (_: number, i: number) => `${TIMEVAR_PREFIX}${i}`, + ); + } + this.assignedIntvarsPossiblevalues = {}; + this.boolSelectorsSet = new Set(); + this.variablesSolver = new smt.BaseSolver('QF_ALL_SUPPORTED'); + this.solver = new smt.BaseSolver('QF_ALL_SUPPORTED'); + this.constrainCompactness = false; + } + + /** + * Add a list of constraint options, exactly one of which has to be satisfied. + If a single constraint is passed in, it will definitely be satisfied. + + An optional boolean selector variable name can also be passed in. This makes the fulfilment of the slots optional. + i.e., only if the boolean selector is true, then the slots are chosen. This represents for e.g., an optional module. + The solver can decide whether or not the boolean selector is chosen based on the other constraints + + IMPLEMENTATION HISTORY (so that we don't go back to a failed idea if we try to optimize): + To ensure that only one SlotConstraint is selected, we need to create a new variable that represent this selection: + + v1: (SL78 = 7 or SL78 = 8) and (SL78 = 7 => (.. slot constraints for id 7 ..)) ... + + v2: v1 Doesn't work since due to the single-direction implication, solver can assign all the RHS without triggering LHS selector + v2: (SL78 = 7 or SL78 = 8) and (SL78 = 7 <=> (.. slot constraints for id 7 ..)) ... [DOUBLE IMPLICATION] + + v3: v2 Doesn't work since the solver just sets one of the hour values to a random number to avoid triggering the LHS condition + v3: v2 + add a soft constraint to all assigned variables, soft-prefer them to be marked as UNASSIGNED + + v4: Give the constraint selectors user friendly names + + v5: We need three things for us to assign a selector to ONLY one set of time constraints uniquely + 1) Assert for EACH hour-val combo, that (selector == val) == (hour == val) + - Implication 1: If ANY of those values in that hour block are == val, then the selector is == val + - Implication 2: if the selector is == val, then ALL those hours must be == val + 2) Set a constraint on each hour slot such that it can only take a value that could feasibly be there due to a real timeslot + - Otherwise, the solver just puts some random number there, which can be another slot value, or even unassigned + * + * */ + addSlotConstraintsFulfilOnlyOne(slots: SlotConstraint[], booleanSelector?: string) { + // If we are selecting between owner ids 0, 1024, and 2048, the selector variable will be named SL_0_1024_2048 + const selectorVar = `${SELECTOR_PREFIX}${slots.map((slot) => slot.ownerString).join('_')}`; + + // Indicate that we need to declare this later as an unconstrained variable (but we constrain it here instead) + this.addPossibleValuesToVariable(selectorVar); + + // Create a list of constraints for the possible values the selector can take + // With same example, we have SL_0_1024_2048 == 0 OR SL_0_1024_2048 == 1024 OR SL_0_1024_2048 == 2048 + const selectorVarPossibleValues: smt.SNode[] = slots.map((slot) => + smt.Eq(selectorVar, slot.ownerId), + ); + + // We indicate with the boolean selector that options are selected ONLY IF the boolean selector is true + if (booleanSelector !== undefined) { + const selector = `${SELECTOR_OPTIONAL_PREFIX}${booleanSelector}`; // Ensure we have an OPT prefix to indicate an optional mod + this.boolSelectorsSet.add(selector); // Make sure we declare the selector later + // Asserts that IF the boolean selector is true, then all the possible values it can take must have at least 1 true (functionally only 1) + this.solver.assert(smt.Eq(selector, smt.Or(...selectorVarPossibleValues))); + } else { + // Asserts unconditionally that the selector must take one of the possible values + this.solver.assert(smt.Or(...selectorVarPossibleValues)); + } + + // Now, for each slot, create a double implication (equality) between the selector value and each of the constrained hours + const constraints: smt.SNode[] = slots + .map((slot) => { + // Holds all the constraints, assuming this slotconstraint is selected + const slotRequirements: smt.SNode[] = []; + slot.startEndTimes.forEach(([startTime, endTime]) => { + if (this.isSlotConstraintTimeInvalid(startTime, endTime)) + throw new Error(`Slot ${slot} time invalid: ${startTime}, ${endTime}`); + // Create a constraint to be "owner id" for all the start and end times in the slot constraint + // If we said: for this slot, time slots t1 and t2 need to be = ID 1024, then + // t1 == 1024 + // t2 == 1024 + for (let i = startTime; i < endTime; i++) { + const timevar = this.timevars[i]; + // Make sure we declare this timevar since we use it + this.addPossibleValuesToVariable(timevar, [slot.ownerId, UNASSIGNED]); + // For this seletion, constraint the timevar to the owner id requested + // Assert individually that if a selector is selector, that hour must be selected to it, and vice versa + slotRequirements.push( + smt.Eq(smt.Eq(selectorVar, slot.ownerId), smt.Eq(timevar, slot.ownerId)), + ); + } + }); + return slotRequirements; + }) + .flat(); // Flatten in case we return multiple constraints per slot + + // Assert all built-up constraints now + constraints.forEach((constraint: smt.SNode) => this.solver.assert(constraint)); + } + + /** + * Use Z3's PbEq / PbLe / PbGe (pseudo-boolean equal / less-than / greater-than) functional to assert that a certain + * number out of a set of constraints must remain true. + * + * This is used to assert that we have at least N free days, for e.g. + * + * Each slotconstraint has multiple start/end times, so many hours must be asserted to be true for a slot constraint to be true. + * ((_ pbeq 2 1 1 1) a b c) --> pseudo-boolean equals: weight a, b, and c as 1, 1 and 1, and make sure total weight == 2 (choose 2 out of 3) + * + * Technique: we create a selector for each set of slots, e.g., to constraint t0 = 1, t1 = 1, vs t50 = 2, t51 = 2: + * SL_FREEDAY_Monday == (t0 = 1) + * SL_FREEDAY_Monday == (t1 = 1) + * + * SL_FREEDAY_Tuesday == (t50 = 2) + * SL_FREEDAY_Tuesay == (t51 = 2) + * + * Then: + * ((_ pbeq N 1 1) SL_FREEDAY_Monday SL_FREEDAY_Tuesday) + * */ + addSlotConstraintsFulfilExactlyN(slots: SlotConstraint[], n: number) { + const selectorVarList: string[] = []; + if (n < 0 || n > slots.length) + throw new Error( + `Selected either too small or too large n (= ${n}) for ${slots.length} slots`, + ); + + // Now, for each slot, create a double implication (equality) between the selector value and each of the constrained hours + const constraints: smt.SNode[] = slots + .map((slot) => { + // Holds all the constraints, assuming this slotconstraint is selected + const slotRequirements: smt.SNode[] = []; + // Create a selector variable for this (SLKB = Selector for K-out-of-N, boolean) + const selectorVar = `${SELECTOR_FULFIL_N_PREFIX}${slot.ownerString}`; + selectorVarList.push(selectorVar); + slot.startEndTimes.forEach(([startTime, endTime]) => { + if (this.isSlotConstraintTimeInvalid(startTime, endTime)) + throw new Error(`Slot ${slot} time invalid: ${startTime}, ${endTime}`); + // Create a constraint to be owner id for all the start and end times in the slot constraint + // If we said: for this slot, time slots h1 and h2 need to be = ID 1024, then + // h1 == 1024 + // h2 == 1024 + for (let i = startTime; i < endTime; i++) { + const timevar = this.timevars[i]; + // Make sure we declare this timevar since we use it + this.addPossibleValuesToVariable(timevar, [slot.ownerId, UNASSIGNED]); + // For this seletion, constraint the timevar to the owner id requested\ + // Assert individually that if the boolean selector is true (selected), that hour must be selected to it, and vice versa + slotRequirements.push(smt.Eq(selectorVar, smt.Eq(timevar, slot.ownerId))); + } + }); + return slotRequirements; + }) + .flat(); // Flatten in case we return multiple constraints per slot + + // Ensures we declare the selector later + selectorVarList.forEach((selector: string) => this.boolSelectorsSet.add(selector)); + + // Assert all the constraints that relate the selector variable to the selected constrains + constraints.forEach((constraint: smt.SNode) => this.solver.assert(constraint)); + + // Assert a K-out-of-N constraint for the selector variables + const kOfN: smt.SExpr = smt.PbEq(selectorVarList, new Array(selectorVarList.length).fill(1), n); + this.solver.assert(kOfN); + } + + /** + * Sets the "workload" costs of choosing an optional module, modelled as a cost on the boolean selectors. + * TODO: naming-wise, make this sound more generic? This seems leaky through the naming but it's not. + * */ + setBooleanSelectorCosts( + workloads: WorkloadCost[], + baseCost: number, // Base cost so far (modelling the credits from compulsory modules) + minCost: number, // Minimium workload to satisfy constraints (min modular credits) + maxCost: number, // Maximum workload to satisfy constraints (max modular credits) + ) { + // Create a variable for the cost of the boolean selectors, add it to declarations list + const workloadSumName = 'workloadsum'; + this.assignedIntvarsPossiblevalues[workloadSumName] = new Set(); + + const terms: smt.SNode[] = [baseCost]; + workloads.forEach(({ varname, cost }) => { + // Make sure varname is declared + const fullvarname = `${SELECTOR_OPTIONAL_PREFIX}${varname}`; + this.boolSelectorsSet.add(fullvarname); + terms.push(smt.If(fullvarname, cost, 0)); + }); + const sumOfTerms = smt.Sum(...terms); + this.solver.assert(smt.Eq(workloadSumName, sumOfTerms)); + + // Assert that the workload should be >= than the minimum workload and <= the maximum workload + this.solver.assert(smt.GEq(workloadSumName, minCost)); + this.solver.assert(smt.LEq(workloadSumName, maxCost)); + } + + /** + * Bit hacky since it requires knowing our owner id constraint format, but basically: + * If a slot is assigned to a owner id > 0 (meaning not UNASSIGNED / FREE / etc) + * Then: (assert-soft) that the NEXT slot is also assigned > 0 + * Here, we just set a flag, since this must be done at the end after all variables are assigned + * */ + addCompactnessConstraint() { + this.constrainCompactness = true; + } + + /** + * Asserts that N CONSECUTIVE time values in the slot constraint are assigned to a NEGATIVE number. + * This is kind of like a "windowing" function, + * where one of the windows within the slot constraint must be consecutively assigned to a negative value. + * Used to handle free days / starting early/later and lunch hours etc, since those are modelled as negative values. + * */ + addNegativevalueSlotConstraintToNConsecutive(slot: SlotConstraint, n: number) { + // Holds all the constraints, assuming this slotconstraint is selected + const slotRequirements: smt.SNode[] = []; + // We only care about first slotconstraint slot, multiple slots are meaningless here + const [startTime, endTime] = slot.startEndTimes[0]; + if (this.isSlotConstraintTimeInvalid(startTime, endTime)) + throw new Error(`Slot ${slot} time invalid: ${startTime}, ${endTime}`); + // Take the startTime --> endTime range as windows of size n + for (let startT = startTime; startT < endTime - n + 1; startT += 1) { + // For each window, we need to assert that ALL of the hours are unassigned + // Then we OR across all windows + const windowRequirements: smt.SNode[] = []; + for (let i = 0; i < n; i++) { + const timevar = this.timevars[startT + i]; + // Make sure we declare this timevar since we use it, at least allow it to be unassigned (negative) + this.addPossibleValuesToVariable(timevar, [UNASSIGNED]); + // Assert that the slot is < 0 (either UNASSIGNED / FREE / etc) + windowRequirements.push(smt.LEq(timevar, -1)); + } + slotRequirements.push(smt.And(...windowRequirements)); + } + const finalRequirements = smt.Or(...slotRequirements); + this.solver.assert(finalRequirements); + } + + /** + * Creates a list of variables to declare as Ints later. + * They can be constrained to have a certain set of values. + * This prevents the solver from just assigned some random integer to them and calling it a day. + * If not constrained, the set will be empty, and we will just declare the varname later. + * */ + addPossibleValuesToVariable(varname: string, values: number[] = []) { + if (this.assignedIntvarsPossiblevalues[varname] === undefined) { + // Make sure we at least have the UNASSIGNED possible value for the var + this.assignedIntvarsPossiblevalues[varname] = new Set(values); + } else { + // No set union. have to add each val independently + values.forEach((val: number) => this.assignedIntvarsPossiblevalues[varname].add(val)); + } + } + + isSlotConstraintTimeInvalid(startTime: number, endTime: number) { + return ( + startTime < 0 || + startTime > this.timevars.length - 1 || + endTime < 0 || + endTime > this.timevars.length - 1 + ); + } + + /** + * Generates the final SMTLIB2 solve string. + * Declares all variables and if necessary, ensure that they must be assigned to one of a set of values. + * If necessary, constrain the timetable to be as compact as possible (has to be done now after all the declarations are made) + * Randomize the run of the solver by default. Add a bugfix string to force the solver into optimizer mode regardless. + * This is necessary due to some obscure bugs within the Z3 wasm (something to do with pthreads that don't exist in WASM) + * Removes the first line (QF_ALL_SUPPORTED) from the output SMTLIB2 text + * */ + generateSmtlib2String(randomize = true): string { + // Declare all the boolean vars + this.boolSelectorsSet.forEach((boolvar: string) => { + this.variablesSolver.add(smt.DeclareFun(boolvar, [], 'Bool')); + // this.variables_solver.add(smt.AssertSoft(boolvar, BOOLVAR_ASSIGNED_WEIGHT, 'defaultval')); + }); + + // For each variable that we use, we need to generate an indicate that it's an integer + // We also need to assert-soft that each variable should be UNASSIGNED if possible + Object.keys(this.assignedIntvarsPossiblevalues).forEach((varname: string) => { + // Declare variable + this.variablesSolver.add(smt.DeclareFun(varname, [], 'Int')); + + // Constrain the possible values of the var if the set is nonempty + const varValues: Set = this.assignedIntvarsPossiblevalues[varname]; + if (varValues.size > 0) { + // [(= t1 7) (= t1 8) (= t1 9)...] + const possibleValsEq = Array.from(varValues).map((val: number) => smt.Eq(varname, val)); + // Statement that var can take all these values + const allPossibleValsOr = smt.Or(...possibleValsEq); + this.solver.assert(allPossibleValsOr); + } + // this.variables_solver.add.smt.AssertSoft(smt.Eq(timevar, UNASSIGNED), VAR_UNASSIGNED_WEIGHT, 'defaultval')); + }); + + if (this.constrainCompactness) { + // Now that all variables are declared, add the constraint-compactness soft asserts + Object.keys(this.assignedIntvarsPossiblevalues).forEach((varname: string) => { + // For each variable that is assigned to a mod (owner id > 0), find the next variable that could possibly be assigned, + // and assert-soft that it IS assigned + + // We only care about time slot vars + if (!varname.startsWith('t')) return; + + // Get the timeslot ID (e.g., 2044) + let varId = parseInt(varname.split('_')[0].substring(1), 10); + + // Find the next variable after this one. If it doesn't exist, return + if (varId + 1 >= this.timevars.length) return; + varId += 1; + const nextVarName = this.timevars[varId]; + if (!(nextVarName in this.assignedIntvarsPossiblevalues)) return; + if (nextVarName === undefined || nextVarName === '') return; + + // If the current var is assigned to a mod, we assert-soft that + // the next one is either the same mod (continuation of slot), or a different mod immediately + const assertSoftNextvarAssigned = smt.AssertSoft( + smt.Eq( + smt.GEq(varname, 0), + smt.Or( + smt.Eq(nextVarName, varname), + smt.And(smt.NEq(nextVarName, varname), smt.GEq(nextVarName, 0)), + ), + ), + 1, + 'nextvar', + ); + this.solver.add(assertSoftNextvarAssigned); + }); + } + + let variablesStr = ''; + this.variablesSolver.forEachStatement((stmt: smt.SNode) => { + variablesStr += `${stmt}\n`; + }); + variablesStr = variablesStr.substring(variablesStr.indexOf('\n') + 1); + + let constraintStr = ''; + this.solver.forEachStatement((stmt: smt.SNode) => { + constraintStr += `${stmt}\n`; + }); + constraintStr = constraintStr.substring(constraintStr.indexOf('\n') + 1); + + // Makes solver output random + const randomInt = Math.floor(Math.random() * 100000000); + const randomPrefix = randomize + ? `(set-option :auto_config false)\n(set-option :smt.phase_selection 5)\n(set-option :smt.random-seed ${randomInt})\n` + : ''; + // A random string that fixes a latent bug with pthread creation, likely because it causes the optimizer to kick in + const fixBugStr = + '(declare-fun BUGFIX_VAR_DONTASK () Int)\n(assert-soft (= BUGFIX_VAR_DONTASK 10))\n'; + // The string that executes the solver and retrives the final model and objectives + const solveStr = '(check-sat)\n(get-model)\n(get-objectives)\n(exit)'; + // Overall SMTLIB2 string to return + const finalStr = randomPrefix + variablesStr + constraintStr + fixBugStr + solveStr; + // console.log(finalStr); + return finalStr; + } +} diff --git a/website/src/utils/optimizer/z3WebWorker.worker.ts b/website/src/utils/optimizer/z3WebWorker.worker.ts new file mode 100644 index 0000000000..2928e4a390 --- /dev/null +++ b/website/src/utils/optimizer/z3WebWorker.worker.ts @@ -0,0 +1,93 @@ +/// + +// Default type of `self` is `WorkerGlobalScope & typeof globalThis` +// https://github.com/microsoft/TypeScript/issues/14877 +/** + * WebWorker script to run and communicate with the Z3 Solver (z3w.wasm). + * Imports the emscripten wrapper file z3w.js (must be accessible on the server). + * After import, initializes the Z3 solver, which may require downloading z3w.wasm from the server. + * */ +import { Z3WorkerMessage, Z3WorkerMessageKind } from 'types/optimizer'; + +declare let self: DedicatedWorkerGlobalScope; + +// Only one solver instance +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let solver: any = null; + +// Context variable from self, removes self error +// eslint-disable-next-line no-restricted-globals +const ctx = self; + +/** + * Initializes the Z3 system and sends a mesage back when the runtime is initialized + * */ +function startZ3() { + // Imports all names from z3w.js (includes Z3, etc) + ctx.importScripts(`${ctx.location.origin}/z3w.js`); + // TODO give vendor types to Z3? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore No types for Z3 WASM + solver = Z3({ + ENVIRONMENT: 'WORKER', // Setup for a WebWorker environemtn + onRuntimeInitialized, + print(message: string) { + postMessage(Z3WorkerMessageKind.PRINT, message); + }, + printErr(message: string) { + postMessage(Z3WorkerMessageKind.ERR, message); + }, + postRun() { + postMessage(Z3WorkerMessageKind.EXIT, ''); + }, + }); +} + +/** + * Send a message to the worker caller that we have initialized the Z3 system + * */ +function onRuntimeInitialized() { + postMessage(Z3WorkerMessageKind.INITIALIZED, ''); +} + +/** + * Generic function to post a message back to the caller of this worker + * */ +function postMessage(kind: Z3WorkerMessageKind, msg: string) { + const message: Z3WorkerMessage = { kind, msg }; + ctx.postMessage(message); +} + +function runZ3(input: string) { + // Input filename doesn't matter + const INPUT_FNAME = 'input.smt2'; + // Run using smtlib2 mode + const args = ['-smt2', INPUT_FNAME]; + // This writes the required smtlib2 code to the emscripten virtual filesystem + solver.FS.writeFile(INPUT_FNAME, input, { encoding: 'utf8' }); + // Finally, runs the solver. The print / printErr function will be called as required + solver.callMain(args); + // Run when the solver is done + postMessage(Z3WorkerMessageKind.EXIT, ''); +} + +/** + * Main handler for all incoming messages + * */ +ctx.addEventListener( + 'message', + (e) => { + const message: Z3WorkerMessage = e.data; + switch (message.kind) { + case Z3WorkerMessageKind.INIT: + startZ3(); + break; + case Z3WorkerMessageKind.OPTIMIZE: + runZ3(message.msg); + break; + default: + break; + } + }, + false, +); diff --git a/website/src/utils/optimizer/z3WeekSolver.test.ts b/website/src/utils/optimizer/z3WeekSolver.test.ts new file mode 100644 index 0000000000..ab002a53f3 --- /dev/null +++ b/website/src/utils/optimizer/z3WeekSolver.test.ts @@ -0,0 +1,75 @@ +import { Z3WeekSolver } from 'utils/optimizer/z3WeekSolver'; + +it('should generate 1-vector correctly', () => { + const solver = new Z3WeekSolver(3); + expect(solver.generateOne()).toEqual('#b001'); +}); + +it('should generate 0-vector correctly', () => { + const solver = new Z3WeekSolver(3); + expect(solver.generateZero()).toEqual('#b000'); +}); + +it('should generate 3-week popCnt correctly', () => { + const solver = new Z3WeekSolver(3); + expect(solver.generatePopcnt()).toEqual(`(define-fun popCount13 ((x (_ BitVec 3))) (_ BitVec 3) +(bvadd +(ite (= #b1 ((_ extract 0 0) x)) #b001 #b000) +(ite (= #b1 ((_ extract 1 1) x)) #b001 #b000) +(ite (= #b1 ((_ extract 2 2) x)) #b001 #b000) +))`); +}); + +it('should generate 3-week bitvec correctly', () => { + const solver = new Z3WeekSolver(3); + expect(solver.generateBitvec([0, 1, 0])).toEqual('#b010'); +}); + +it('should error when 3-week bitvec is too small', () => { + const solver = new Z3WeekSolver(3); + expect(() => solver.generateBitvec([0, 1])).toThrow(Error); +}); + +it('should error when 3-week bitvec is too large', () => { + const solver = new Z3WeekSolver(3); + expect(() => solver.generateBitvec([0, 1, 0, 1])).toThrow(Error); +}); + +it('should declare bitvec for 3-week case correctly', () => { + const solver = new Z3WeekSolver(3); + expect(solver.generateDecl('testvar').toString()).toEqual( + '(declare-fun testvar () (_ BitVec 3))', + ); +}); + +it('should create 6 week solver string correctly (manually generated smtlib code from external tools)', () => { + // Y M D H M (10:30) + const solver = new Z3WeekSolver(6); + solver.addWeeks([2, 4, 6], 'x'); + solver.addWeeks([4, 6], 'y'); + solver.addWeeks([1], 'z'); + const outStr = solver.generateSmtlib2String(); + expect(outStr).toEqual(`(declare-fun weeks_to_simulate () (_ BitVec 6)) +(define-fun popCount13 ((x (_ BitVec 6))) (_ BitVec 6) +(bvadd +(ite (= #b1 ((_ extract 0 0) x)) #b000001 #b000000) +(ite (= #b1 ((_ extract 1 1) x)) #b000001 #b000000) +(ite (= #b1 ((_ extract 2 2) x)) #b000001 #b000000) +(ite (= #b1 ((_ extract 3 3) x)) #b000001 #b000000) +(ite (= #b1 ((_ extract 4 4) x)) #b000001 #b000000) +(ite (= #b1 ((_ extract 5 5) x)) #b000001 #b000000) +)) +(declare-fun x () (_ BitVec 6)) +(assert (= x #b010101)) +(assert (not (= (bvand x weeks_to_simulate) #b000000))) +(declare-fun y () (_ BitVec 6)) +(assert (= y #b000101)) +(assert (not (= (bvand y weeks_to_simulate) #b000000))) +(declare-fun z () (_ BitVec 6)) +(assert (= z #b100000)) +(assert (not (= (bvand z weeks_to_simulate) #b000000))) +(minimize (popCount13 weeks_to_simulate)) +(check-sat) +(get-value (weeks_to_simulate)) +(exit)`); +}); diff --git a/website/src/utils/optimizer/z3WeekSolver.ts b/website/src/utils/optimizer/z3WeekSolver.ts new file mode 100644 index 0000000000..4b3417d8e2 --- /dev/null +++ b/website/src/utils/optimizer/z3WeekSolver.ts @@ -0,0 +1,152 @@ +import * as smt from 'smtlib-ext'; + +const OUTPUT_WEEKS_TO_SIMULATE_VARNAME = 'weeks_to_simulate'; +/** + * Separate solver to solve the Hitting Set NP-Hard problem: finding the minimum number of weeks to simulate. + * We pass in all the weeks for each lesson as a bitvector, and this generates the smtlib2 string that solves for + * the minimum number of weeks to cover all possible clashes. + * + * Mathematically: + * - Weeks = { 1...13 } + * - Simulated SUBSETOF Weeks + * - S = { S_i | S_i SUBSETOF Weeks is a list of weeks that a lesson must be held } + * - FORALL(S_i) IN S: S_i intersect Simulated != nullset + * - Minimize cardinality of Simulated set (for performance). + * + * Conceptually, imagine if we have four lessons L1 - L4. + * Each lesson is held on some set of weeks, say L1 is held on weeks 2, 4, 6, and so on. + * We can represent them like this: + * L1 = { 2 4 6 } + * L2 = { 4 6 } + * L3 = { 1 } + * L4 = { 1 } + * In this example, L1 and L2 must be simulated on week 4 OR week 6 so that if they clash, we can catch it. + * Similarly, L3 and L4 must be simulated on week 1 to compute any clashes. + * If we were to simulate ALL the weeks here, we would have to sim weeks 1, 2, 4, 6. + * However, it's clear that we can get away with just simulating weeks 1 and 4, half as many. + * + * Therefore, we represent each of the lesson week requirements, and the output list of weeks to simulate, as bitvectors. + * We assert that each (week constraint BINARYAND output) is not 0, so that each lesson is simulated for at least ONE of their weeks. + * Then, we ask the solver to minimize the number of 1s in the output bitvector so that we simulate the min number of weeks. + * */ +export class Z3WeekSolver { + solver: smt.BaseSolver; // For the actual constraints + + numWeeks: number; // Defines bitvec sizes + + zero: string; // Bitvec of numWeeks length representing 0 + + one: string; // Bitvec of numWeeks length representing 1 + + constructor(numWeeks: number) { + this.solver = new smt.BaseSolver('QF_ALL_SUPPORTED'); + this.numWeeks = numWeeks; + this.zero = this.generateZero(); + this.one = this.generateOne(); + this.solver.add(this.generateDecl(OUTPUT_WEEKS_TO_SIMULATE_VARNAME)); // Add in our required "sim" variable + this.solver.add(this.generatePopcnt()); // Add in the variable definition for popcount for this weeksize + } + + /** + * Indicates that at least one of the weeks in the array must be fulfilled for a particular idString + * E.g., addWeeks([1, 2], "a") ==> for "a" to be fulfilled, either week 1 or 2 must be simulated. + * We assume all idStrings need to be fulfilled in the end. + * The format of the idString is not important. + * */ + addWeeks(weeks: number[], idStr: string) { + const values = new Array(this.numWeeks).fill(0); + weeks.forEach((week: number) => { + values[week - 1] = 1; + }); + this.declareConstraint(values, idStr); + } + + /** + * Generates the SMTLIB2 code to pass to the solver. + * Adds all the bitvector constraints for each week, and then asserts that the + * number of 1s in the output variable is minimized. + * Checks sat + returns only the value of the output variable. + * */ + generateSmtlib2String(): string { + let constraintStr = ''; + this.solver.forEachStatement((stmt: smt.SNode) => { + constraintStr += `${stmt}\n`; + }); + constraintStr = constraintStr.substring(constraintStr.indexOf('\n') + 1); + const minimizeStr = `(minimize (popCount13 ${OUTPUT_WEEKS_TO_SIMULATE_VARNAME}))\n`; + // The string that executes the solver and retrives the final model and objectives + const solveStr = `(check-sat)\n(get-value (${OUTPUT_WEEKS_TO_SIMULATE_VARNAME}))\n(exit)`; + // Overall SMTLIB2 string to return + const finalStr = constraintStr + minimizeStr + solveStr; + // console.log(finalStr); + return finalStr; + } + + /// /////////////// + // Internal Utils + /// /////////////// + + /** + * Sets the idStr to the newly declared name of the bitvector, + * then asserts that the (final weeks output (SIM_VARNAME) && new bitvector) is not 0. + * Meaning: at least ONE of the weeks represented by the bitvector MUST be in the output. + * This ensures that each lesson is simulated. + * */ + declareConstraint(values: number[], idStr: string): void { + const bv = this.generateBitvec(values); + this.solver.add(this.generateDecl(idStr)); + this.solver.assert(smt.Eq(idStr, bv)); + this.solver.assert(smt.NEq(smt.BVAnd(idStr, OUTPUT_WEEKS_TO_SIMULATE_VARNAME), this.zero)); + } + + /** + * Declare a variable so that we can use that name in assertions + * */ + generateDecl(varname: string): smt.SExpr { + return smt.DeclareFun(varname, [], `(_ BitVec ${this.numWeeks})`); + } + + /** + * Generically create any bitvec from an array of size numWeeks. + * If numWeeks is 3, values is [0, 1, 0], the output is #b010 + * */ + generateBitvec(values: number[]): string { + if (values.length !== this.numWeeks) { + throw new Error( + 'Programming error: the values array passed to this function must be consistent with the SMT Sort used (BitVec num_weeks)', + ); + } + const str = `#b${values.map((val: number) => (val === 0 ? '0' : '1')).join('')}`; + return str; + } + + /** + * Generates the function representing the popCnt (population count) function. + * popCnt is used during smt solving to count how many 1s there are in a bitvector. + * */ + generatePopcnt(): string { + const line1 = `(define-fun popCount13 ((x (_ BitVec ${this.numWeeks}))) (_ BitVec ${this.numWeeks})\n(bvadd\n`; + let ites = ''; + for (let i = 0; i < this.numWeeks; i++) { + ites += `(ite (= #b1 ((_ extract ${i} ${i}) x)) ${this.one} ${this.zero})\n`; + } + const end = `))`; + return line1 + ites + end; + } + + /** + * Generates a bitvector of all zeroes + * */ + generateZero(): string { + return this.generateBitvec(new Array(this.numWeeks).fill(0)); + } + + /** + * Generates a bitvector representing the number 1 + * */ + generateOne(): string { + const arr = new Array(this.numWeeks).fill(0); + arr[arr.length - 1] = 1; + return this.generateBitvec(arr); + } +} diff --git a/website/src/utils/timetables.ts b/website/src/utils/timetables.ts index 42d69219b5..cd38c95846 100644 --- a/website/src/utils/timetables.ts +++ b/website/src/utils/timetables.ts @@ -54,6 +54,7 @@ import { getModuleSemesterData, getModuleTimetable } from './modules'; import { deltas } from './array'; type lessonTypeAbbrev = { [lessonType: string]: string }; +// Note: timetable optimizer depends on these abbreviations having no spaces export const LESSON_TYPE_ABBREV: lessonTypeAbbrev = { 'Design Lecture': 'DLEC', Laboratory: 'LAB', diff --git a/website/src/views/optimizer/OptimizerConstraints.scss b/website/src/views/optimizer/OptimizerConstraints.scss new file mode 100644 index 0000000000..824b23220c --- /dev/null +++ b/website/src/views/optimizer/OptimizerConstraints.scss @@ -0,0 +1,71 @@ +@import '~styles/utils/modules-entry'; + +:global(.page-container).constraintsArea { + // max-width: 60rem; + // margin-left: 0; + + :global { + animation-name: $page-entering-animation; + } + + hr { + clear: both; + } + + h4 { + margin-top: 2rem; + } + + // Styles nested for specificity + .toggleRow { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + align-items: flex-start; + + .toggleDescription { + flex: 1 1 auto; + max-width: 35em; + } + + .toggle { + display: flex; + flex: 0 0 auto; + flex-direction: column; + align-items: flex-end; + width: auto; + margin-left: 1rem; + } + } +} + +.title { + margin: 1rem 0 0; + font-size: $font-size-xlg; +} + +.preview { + position: relative; + clear: both; + overflow-y: hidden; + height: 13.8rem; + margin: 1rem 0 2rem; +} + +.themeOption { + // See utils/themes.scss - used as a specificity override + composes: theme-option from global; + float: left; + width: 25%; + margin-bottom: 0.4rem; + color: var(--body-color); + background: none; + + @include media-breakpoint-down('sm') { + width: 50%; + } +} + +.betaToggle { + margin-bottom: 1rem; +} diff --git a/website/src/views/optimizer/OptimizerConstraints.tsx b/website/src/views/optimizer/OptimizerConstraints.tsx new file mode 100644 index 0000000000..a8f14e7831 --- /dev/null +++ b/website/src/views/optimizer/OptimizerConstraints.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import classnames from 'classnames'; +import Toggle from 'views/components/Toggle'; +import styles from './OptimizerConstraints.scss'; +// (evt) => props.setModRegScheduleType(evt.target.value as ScheduleType) +// {SCHEDULE_TYPES.map((type) => ( +// +const OptimizerConstraints: React.FC = () => ( + <> +
+

Constraints

+ +
+
+
+
Lesson Start/End Times
+
+ +
+

Earliest Lesson Start Time

+
+
+ 1} /> +
+ +
+

Enable Constraint?

+
+
+ 1} /> +
+
+ +
+
+
Free Days
+
+ +
+

Number of Free Days

+
+
+ 1} /> +
+ +
+

Enable Constraint?

+
+
+ 1} /> +
+
+ +
+
+
+ +); + +export default OptimizerConstraints; diff --git a/website/src/views/optimizer/TimetableOptimizerContainer.tsx b/website/src/views/optimizer/TimetableOptimizerContainer.tsx new file mode 100644 index 0000000000..d9d3c0daff --- /dev/null +++ b/website/src/views/optimizer/TimetableOptimizerContainer.tsx @@ -0,0 +1,110 @@ +import * as React from 'react'; +import { TrendingUp } from 'react-feather'; +import { SemTimetableConfig, ModuleLessonConfig } from 'types/timetables'; +import { Module, Semester, SemesterData, RawLesson } from 'types/modules'; +import { ModulesMap } from 'types/reducers'; +import { TimetableOptimizer } from 'utils/optimizer/timetableOptimizer'; +import { useDispatch } from 'react-redux'; +import { + OptimizerInput, + OptimizerOutput, + OptimizerCallbacks, + ModuleInfoWithConstraints, + lessonByGroupsByClassNo, + GlobalConstraints, + defaultConstraints, +} from 'types/optimizer'; +import { setLessonConfig } from 'actions/timetables'; +import OptimizerConstraints from './OptimizerConstraints'; + +type OwnProps = { + semester: Semester; + timetable: SemTimetableConfig; + modules: ModulesMap; +}; + +const TimetableOptimizerContainer: React.FC = ({ semester, timetable, modules }) => { + const dispatch = useDispatch(); + + const onOptimizerInitialized = () => { + // console.log('Optimizer Initialized!'); + runOptimizer(); + }; + // const onSmtlib2InputCreated = (s: string) => console.log(`OnSmtlib2: ${s}`); + // eslint-disable-next-line @typescript-eslint/no-empty-function + const onSmtlib2InputCreated = () => {}; + const onSmtLib2ResultOutput = () => { + // console.log(`OnOutput:\n${s}`); + }; + const onTimetableOutput = (optimizerOutput: OptimizerOutput) => { + // console.log(`optimizerOutput:`); + // console.log(optimizerOutput); + + if (optimizerOutput.isSat) { + // Have to get the color map here to use this + // dispatch(setTimetable(semester, optimizerOutput.timetable)); + + // Maybe this looks more interesting :) + Object.keys(optimizerOutput.timetable).forEach((moduleCode: string) => { + const modLessonConfig: ModuleLessonConfig = optimizerOutput.timetable[moduleCode]; + dispatch(setLessonConfig(semester, moduleCode, modLessonConfig)); + }); + } else { + // TODO do something proper if it's not sat + // alert('No timetable found!'); + } + }; + const callbacks: OptimizerCallbacks = { + onOptimizerInitialized, + onSmtlib2InputCreated, + onSmtLib2ResultOutput, + onTimetableOutput, + }; + + function initAndRunOptimizer() { + // const moduleCodes = Object.keys(timetable); + // console.log(moduleCodes); + TimetableOptimizer.initOptimizer(callbacks); + } + + function runOptimizer() { + const moduleInfo: ModuleInfoWithConstraints[] = Object.keys(timetable).map( + (moduleCode: string) => { + const mod: Module = modules[moduleCode]; + const required = true; // TODO change this based on UI + // Should be assured that the semester data is inside the module data + const allLessons: readonly RawLesson[] | undefined = mod.semesterData.find( + (v: SemesterData) => v.semester === semester, + )?.timetable; + if (!allLessons) { + throw new Error(`Cannot find semester ${semester} data for mod ${mod.moduleCode}`); + } + const lessonsGrouped = lessonByGroupsByClassNo(allLessons); + return { mod, required, lessonsGrouped }; + }, + ); + const constraints: GlobalConstraints = defaultConstraints; + const optimizerInput: OptimizerInput = { + moduleInfo, + constraints, + }; + TimetableOptimizer.loadInput(optimizerInput); + TimetableOptimizer.solve(); + } + + return ( + <> +
+ + + + ); +}; + +export default TimetableOptimizerContainer; diff --git a/website/src/views/timetable/TimetableActions.tsx b/website/src/views/timetable/TimetableActions.tsx index ee2a010c9a..b4099b38e2 100644 --- a/website/src/views/timetable/TimetableActions.tsx +++ b/website/src/views/timetable/TimetableActions.tsx @@ -3,10 +3,11 @@ import classnames from 'classnames'; import { connect } from 'react-redux'; import { toggleTimetableOrientation, toggleTitleDisplay } from 'actions/theme'; +import { toggleOptimizerDisplay } from 'actions/optimizer'; import { Semester } from 'types/modules'; import { SemTimetableConfig } from 'types/timetables'; -import { Calendar, Grid, Sidebar, Type } from 'react-feather'; +import { Calendar, Cpu, Grid, Sidebar, Type } from 'react-feather'; import elements from 'views/elements'; import config from 'config'; import ShareTimetable from './ShareTimetable'; @@ -26,6 +27,9 @@ type Props = { showExamCalendar: boolean; toggleExamCalendar: () => void; + + isOptimizerEnabled: boolean; + toggleOptimizerDisplay: () => void; }; const TimetableActions: React.FC = (props) => ( @@ -78,6 +82,15 @@ const TimetableActions: React.FC = (props) => ( )} )} +
@@ -91,4 +104,5 @@ const TimetableActions: React.FC = (props) => ( export default connect(null, { toggleTimetableOrientation, toggleTitleDisplay, + toggleOptimizerDisplay, })(TimetableActions); diff --git a/website/src/views/timetable/TimetableContent.tsx b/website/src/views/timetable/TimetableContent.tsx index 9e50356664..8b25b88e2b 100644 --- a/website/src/views/timetable/TimetableContent.tsx +++ b/website/src/views/timetable/TimetableContent.tsx @@ -46,6 +46,7 @@ import ErrorBoundary from 'views/errors/ErrorBoundary'; import ModRegNotification from 'views/components/notfications/ModRegNotification'; import { State as StoreState } from 'types/state'; import { TombstoneModule } from 'types/views'; +import TimetableOptimizerContainer from 'views/optimizer/TimetableOptimizerContainer'; import Timetable from './Timetable'; import TimetableActions from './TimetableActions'; import TimetableModulesTable from './TimetableModulesTable'; @@ -75,6 +76,7 @@ type Props = OwnProps & { timetableOrientation: TimetableOrientation; showTitle: boolean; hiddenInTimetable: ModuleCode[]; + isOptimizerShown: boolean; // Actions addModule: (semester: Semester, moduleCode: ModuleCode) => void; @@ -406,6 +408,7 @@ class TimetableContent extends React.Component { timetable={this.props.timetable} showExamCalendar={showExamCalendar} toggleExamCalendar={() => this.setState({ showExamCalendar: !showExamCalendar })} + isOptimizerEnabled={this.props.isOptimizerShown} />
@@ -423,6 +426,15 @@ class TimetableContent extends React.Component {
{this.renderModuleSections(addedModules, !isVerticalOrientation)}
+
+ {this.props.isOptimizerShown && ( + + )} +
@@ -437,6 +449,7 @@ class TimetableContent extends React.Component { function mapStateToProps(state: StoreState, ownProps: OwnProps) { const { semester, timetable } = ownProps; const { modules } = state.moduleBank; + const { isOptimizerShown } = state.optimizer; const timetableWithLessons = hydrateSemTimetableWithLessons(timetable, modules, semester); const hiddenInTimetable = state.timetables.hidden[semester] || []; @@ -449,6 +462,7 @@ function mapStateToProps(state: StoreState, ownProps: OwnProps) { timetableOrientation: state.theme.timetableOrientation, showTitle: state.theme.showTitle, hiddenInTimetable, + isOptimizerShown, }; } diff --git a/website/static/base/z3w.js b/website/static/base/z3w.js new file mode 100644 index 0000000000..7199472bf9 --- /dev/null +++ b/website/static/base/z3w.js @@ -0,0 +1,5900 @@ +var Z3 = function (Z3) { + Z3 = Z3 || {}; + + var Module = typeof Z3 !== "undefined" ? Z3 : {}; + var moduleOverrides = {}; + var key; + for (key in Module) { + if (Module.hasOwnProperty(key)) { + moduleOverrides[key] = Module[key]; + } + } + Module["arguments"] = []; + Module["thisProgram"] = "./this.program"; + Module["quit"] = function (status, toThrow) { + throw toThrow; + }; + Module["preRun"] = []; + Module["postRun"] = []; + var ENVIRONMENT_IS_WEB = false; + var ENVIRONMENT_IS_WORKER = false; + var ENVIRONMENT_IS_NODE = false; + var ENVIRONMENT_IS_SHELL = false; + if (Module["ENVIRONMENT"]) { + if (Module["ENVIRONMENT"] === "WEB") { + ENVIRONMENT_IS_WEB = true; + } else if (Module["ENVIRONMENT"] === "WORKER") { + ENVIRONMENT_IS_WORKER = true; + } else if (Module["ENVIRONMENT"] === "NODE") { + ENVIRONMENT_IS_NODE = true; + } else if (Module["ENVIRONMENT"] === "SHELL") { + ENVIRONMENT_IS_SHELL = true; + } else { + throw new Error("Module['ENVIRONMENT'] value is not valid. must be one of: WEB|WORKER|NODE|SHELL."); + } + } else { + ENVIRONMENT_IS_WEB = typeof window === "object"; + ENVIRONMENT_IS_WORKER = typeof importScripts === "function"; + ENVIRONMENT_IS_NODE = typeof process === "object" && typeof require === "function" && !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER; + ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + } + if (ENVIRONMENT_IS_NODE) { + var nodeFS; + var nodePath; + Module["read"] = function shell_read(filename, binary) { + var ret; + if (!nodeFS) nodeFS = require("fs"); + if (!nodePath) nodePath = require("path"); + filename = nodePath["normalize"](filename); + ret = nodeFS["readFileSync"](filename); + return binary ? ret : ret.toString(); + }; + Module["readBinary"] = function readBinary(filename) { + var ret = Module["read"](filename, true); + if (!ret.buffer) { + ret = new Uint8Array(ret); + } + assert(ret.buffer); + return ret; + }; + if (process["argv"].length > 1) { + Module["thisProgram"] = process["argv"][1].replace(/\\/g, "/"); + } + Module["arguments"] = process["argv"].slice(2); + process["on"]("uncaughtException", function (ex) { + if (!(ex instanceof ExitStatus)) { + throw ex; + } + }); + process["on"]("unhandledRejection", function (reason, p) { + process["exit"](1); + }); + Module["inspect"] = function () { + return "[Emscripten Module object]"; + }; + } else if (ENVIRONMENT_IS_SHELL) { + if (typeof read != "undefined") { + Module["read"] = function shell_read(f) { + return read(f); + }; + } + Module["readBinary"] = function readBinary(f) { + var data; + if (typeof readbuffer === "function") { + return new Uint8Array(readbuffer(f)); + } + data = read(f, "binary"); + assert(typeof data === "object"); + return data; + }; + if (typeof scriptArgs != "undefined") { + Module["arguments"] = scriptArgs; + } else if (typeof arguments != "undefined") { + Module["arguments"] = arguments; + } + if (typeof quit === "function") { + Module["quit"] = function (status, toThrow) { + quit(status); + }; + } + } else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + Module["read"] = function shell_read(url) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.send(null); + return xhr.responseText; + }; + if (ENVIRONMENT_IS_WORKER) { + Module["readBinary"] = function readBinary(url) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.responseType = "arraybuffer"; + xhr.send(null); + return new Uint8Array(xhr.response); + }; + } + Module["readAsync"] = function readAsync(url, onload, onerror) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function xhr_onload() { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { + onload(xhr.response); + return; + } + onerror(); + }; + xhr.onerror = onerror; + xhr.send(null); + }; + Module["setWindowTitle"] = function (title) { + document.title = title; + }; + } + Module["print"] = typeof console !== "undefined" ? console.log.bind(console) : typeof print !== "undefined" ? print : null; + Module["printErr"] = typeof printErr !== "undefined" ? printErr : (typeof console !== "undefined" && console.warn.bind(console)) || Module["print"]; + Module.print = Module["print"]; + Module.printErr = Module["printErr"]; + for (key in moduleOverrides) { + if (moduleOverrides.hasOwnProperty(key)) { + Module[key] = moduleOverrides[key]; + } + } + moduleOverrides = undefined; + var STACK_ALIGN = 16; + function staticAlloc(size) { + assert(!staticSealed); + var ret = STATICTOP; + STATICTOP = (STATICTOP + size + 15) & -16; + return ret; + } + function dynamicAlloc(size) { + assert(DYNAMICTOP_PTR); + var ret = HEAP32[DYNAMICTOP_PTR >> 2]; + var end = (ret + size + 15) & -16; + HEAP32[DYNAMICTOP_PTR >> 2] = end; + if (end >= TOTAL_MEMORY) { + var success = enlargeMemory(); + if (!success) { + HEAP32[DYNAMICTOP_PTR >> 2] = ret; + return 0; + } + } + return ret; + } + function alignMemory(size, factor) { + if (!factor) factor = STACK_ALIGN; + var ret = (size = Math.ceil(size / factor) * factor); + return ret; + } + function getNativeTypeSize(type) { + switch (type) { + case "i1": + case "i8": + return 1; + case "i16": + return 2; + case "i32": + return 4; + case "i64": + return 8; + case "float": + return 4; + case "double": + return 8; + default: { + if (type[type.length - 1] === "*") { + return 4; + } else if (type[0] === "i") { + var bits = parseInt(type.substr(1)); + assert(bits % 8 === 0); + return bits / 8; + } else { + return 0; + } + } + } + } + var functionPointers = new Array(0); + var GLOBAL_BASE = 1024; + var ABORT = 0; + var EXITSTATUS = 0; + function assert(condition, text) { + if (!condition) { + abort("Assertion failed: " + text); + } + } + function setValue(ptr, value, type, noSafe) { + type = type || "i8"; + if (type.charAt(type.length - 1) === "*") type = "i32"; + switch (type) { + case "i1": + HEAP8[ptr >> 0] = value; + break; + case "i8": + HEAP8[ptr >> 0] = value; + break; + case "i16": + HEAP16[ptr >> 1] = value; + break; + case "i32": + HEAP32[ptr >> 2] = value; + break; + case "i64": + (tempI64 = [ + value >>> 0, + ((tempDouble = value), +Math_abs(tempDouble) >= 1 ? (tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0) : 0), + ]), + (HEAP32[ptr >> 2] = tempI64[0]), + (HEAP32[(ptr + 4) >> 2] = tempI64[1]); + break; + case "float": + HEAPF32[ptr >> 2] = value; + break; + case "double": + HEAPF64[ptr >> 3] = value; + break; + default: + abort("invalid type for setValue: " + type); + } + } + var ALLOC_STATIC = 2; + var ALLOC_NONE = 4; + function allocate(slab, types, allocator, ptr) { + var zeroinit, size; + if (typeof slab === "number") { + zeroinit = true; + size = slab; + } else { + zeroinit = false; + size = slab.length; + } + var singleType = typeof types === "string" ? types : null; + var ret; + if (allocator == ALLOC_NONE) { + ret = ptr; + } else { + ret = [typeof _malloc === "function" ? _malloc : staticAlloc, stackAlloc, staticAlloc, dynamicAlloc][allocator === undefined ? ALLOC_STATIC : allocator](Math.max(size, singleType ? 1 : types.length)); + } + if (zeroinit) { + var stop; + ptr = ret; + assert((ret & 3) == 0); + stop = ret + (size & ~3); + for (; ptr < stop; ptr += 4) { + HEAP32[ptr >> 2] = 0; + } + stop = ret + size; + while (ptr < stop) { + HEAP8[ptr++ >> 0] = 0; + } + return ret; + } + if (singleType === "i8") { + if (slab.subarray || slab.slice) { + HEAPU8.set(slab, ret); + } else { + HEAPU8.set(new Uint8Array(slab), ret); + } + return ret; + } + var i = 0, + type, + typeSize, + previousType; + while (i < size) { + var curr = slab[i]; + type = singleType || types[i]; + if (type === 0) { + i++; + continue; + } + if (type == "i64") type = "i32"; + setValue(ret + i, curr, type); + if (previousType !== type) { + typeSize = getNativeTypeSize(type); + previousType = type; + } + i += typeSize; + } + return ret; + } + function Pointer_stringify(ptr, length) { + if (length === 0 || !ptr) return ""; + var hasUtf = 0; + var t; + var i = 0; + while (1) { + t = HEAPU8[(ptr + i) >> 0]; + hasUtf |= t; + if (t == 0 && !length) break; + i++; + if (length && i == length) break; + } + if (!length) length = i; + var ret = ""; + if (hasUtf < 128) { + var MAX_CHUNK = 1024; + var curr; + while (length > 0) { + curr = String.fromCharCode.apply(String, HEAPU8.subarray(ptr, ptr + Math.min(length, MAX_CHUNK))); + ret = ret ? ret + curr : curr; + ptr += MAX_CHUNK; + length -= MAX_CHUNK; + } + return ret; + } + return UTF8ToString(ptr); + } + var UTF8Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined; + function UTF8ArrayToString(u8Array, idx) { + var endPtr = idx; + while (u8Array[endPtr]) ++endPtr; + if (endPtr - idx > 16 && u8Array.subarray && UTF8Decoder) { + return UTF8Decoder.decode(u8Array.subarray(idx, endPtr)); + } else { + var u0, u1, u2, u3, u4, u5; + var str = ""; + while (1) { + u0 = u8Array[idx++]; + if (!u0) return str; + if (!(u0 & 128)) { + str += String.fromCharCode(u0); + continue; + } + u1 = u8Array[idx++] & 63; + if ((u0 & 224) == 192) { + str += String.fromCharCode(((u0 & 31) << 6) | u1); + continue; + } + u2 = u8Array[idx++] & 63; + if ((u0 & 240) == 224) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + u3 = u8Array[idx++] & 63; + if ((u0 & 248) == 240) { + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | u3; + } else { + u4 = u8Array[idx++] & 63; + if ((u0 & 252) == 248) { + u0 = ((u0 & 3) << 24) | (u1 << 18) | (u2 << 12) | (u3 << 6) | u4; + } else { + u5 = u8Array[idx++] & 63; + u0 = ((u0 & 1) << 30) | (u1 << 24) | (u2 << 18) | (u3 << 12) | (u4 << 6) | u5; + } + } + } + if (u0 < 65536) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 65536; + str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023)); + } + } + } + } + function UTF8ToString(ptr) { + return UTF8ArrayToString(HEAPU8, ptr); + } + function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) { + if (!(maxBytesToWrite > 0)) return 0; + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; + for (var i = 0; i < str.length; ++i) { + var u = str.charCodeAt(i); + if (u >= 55296 && u <= 57343) u = (65536 + ((u & 1023) << 10)) | (str.charCodeAt(++i) & 1023); + if (u <= 127) { + if (outIdx >= endIdx) break; + outU8Array[outIdx++] = u; + } else if (u <= 2047) { + if (outIdx + 1 >= endIdx) break; + outU8Array[outIdx++] = 192 | (u >> 6); + outU8Array[outIdx++] = 128 | (u & 63); + } else if (u <= 65535) { + if (outIdx + 2 >= endIdx) break; + outU8Array[outIdx++] = 224 | (u >> 12); + outU8Array[outIdx++] = 128 | ((u >> 6) & 63); + outU8Array[outIdx++] = 128 | (u & 63); + } else if (u <= 2097151) { + if (outIdx + 3 >= endIdx) break; + outU8Array[outIdx++] = 240 | (u >> 18); + outU8Array[outIdx++] = 128 | ((u >> 12) & 63); + outU8Array[outIdx++] = 128 | ((u >> 6) & 63); + outU8Array[outIdx++] = 128 | (u & 63); + } else if (u <= 67108863) { + if (outIdx + 4 >= endIdx) break; + outU8Array[outIdx++] = 248 | (u >> 24); + outU8Array[outIdx++] = 128 | ((u >> 18) & 63); + outU8Array[outIdx++] = 128 | ((u >> 12) & 63); + outU8Array[outIdx++] = 128 | ((u >> 6) & 63); + outU8Array[outIdx++] = 128 | (u & 63); + } else { + if (outIdx + 5 >= endIdx) break; + outU8Array[outIdx++] = 252 | (u >> 30); + outU8Array[outIdx++] = 128 | ((u >> 24) & 63); + outU8Array[outIdx++] = 128 | ((u >> 18) & 63); + outU8Array[outIdx++] = 128 | ((u >> 12) & 63); + outU8Array[outIdx++] = 128 | ((u >> 6) & 63); + outU8Array[outIdx++] = 128 | (u & 63); + } + } + outU8Array[outIdx] = 0; + return outIdx - startIdx; + } + function stringToUTF8(str, outPtr, maxBytesToWrite) { + return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + } + function lengthBytesUTF8(str) { + var len = 0; + for (var i = 0; i < str.length; ++i) { + var u = str.charCodeAt(i); + if (u >= 55296 && u <= 57343) u = (65536 + ((u & 1023) << 10)) | (str.charCodeAt(++i) & 1023); + if (u <= 127) { + ++len; + } else if (u <= 2047) { + len += 2; + } else if (u <= 65535) { + len += 3; + } else if (u <= 2097151) { + len += 4; + } else if (u <= 67108863) { + len += 5; + } else { + len += 6; + } + } + return len; + } + var UTF16Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-16le") : undefined; + function allocateUTF8(str) { + var size = lengthBytesUTF8(str) + 1; + var ret = _malloc(size); + if (ret) stringToUTF8Array(str, HEAP8, ret, size); + return ret; + } + function allocateUTF8OnStack(str) { + var size = lengthBytesUTF8(str) + 1; + var ret = stackAlloc(size); + stringToUTF8Array(str, HEAP8, ret, size); + return ret; + } + function demangle(func) { + return func; + } + function demangleAll(text) { + var regex = /__Z[\w\d_]+/g; + return text.replace(regex, function (x) { + var y = demangle(x); + return x === y ? x : x + " [" + y + "]"; + }); + } + function jsStackTrace() { + var err = new Error(); + if (!err.stack) { + try { + throw new Error(0); + } catch (e) { + err = e; + } + if (!err.stack) { + return "(no stack trace available)"; + } + } + return err.stack.toString(); + } + function stackTrace() { + var js = jsStackTrace(); + if (Module["extraStackTrace"]) js += "\n" + Module["extraStackTrace"](); + return demangleAll(js); + } + var WASM_PAGE_SIZE = 65536; + var ASMJS_PAGE_SIZE = 16777216; + var MIN_TOTAL_MEMORY = 16777216; + function alignUp(x, multiple) { + if (x % multiple > 0) { + x += multiple - (x % multiple); + } + return x; + } + var buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64; + function updateGlobalBuffer(buf) { + Module["buffer"] = buffer = buf; + } + function updateGlobalBufferViews() { + Module["HEAP8"] = HEAP8 = new Int8Array(buffer); + Module["HEAP16"] = HEAP16 = new Int16Array(buffer); + Module["HEAP32"] = HEAP32 = new Int32Array(buffer); + Module["HEAPU8"] = HEAPU8 = new Uint8Array(buffer); + Module["HEAPU16"] = HEAPU16 = new Uint16Array(buffer); + Module["HEAPU32"] = HEAPU32 = new Uint32Array(buffer); + Module["HEAPF32"] = HEAPF32 = new Float32Array(buffer); + Module["HEAPF64"] = HEAPF64 = new Float64Array(buffer); + } + var STATIC_BASE, STATICTOP, staticSealed; + var STACK_BASE, STACKTOP, STACK_MAX; + var DYNAMIC_BASE, DYNAMICTOP_PTR; + STATIC_BASE = STATICTOP = STACK_BASE = STACKTOP = STACK_MAX = DYNAMIC_BASE = DYNAMICTOP_PTR = 0; + staticSealed = false; + if (!Module["reallocBuffer"]) + Module["reallocBuffer"] = function (size) { + var ret; + try { + if (ArrayBuffer.transfer) { + ret = ArrayBuffer.transfer(buffer, size); + } else { + var oldHEAP8 = HEAP8; + ret = new ArrayBuffer(size); + var temp = new Int8Array(ret); + temp.set(oldHEAP8); + } + } catch (e) { + return false; + } + var success = _emscripten_replace_memory(ret); + if (!success) return false; + return ret; + }; + function enlargeMemory() { + var PAGE_MULTIPLE = Module["usingWasm"] ? WASM_PAGE_SIZE : ASMJS_PAGE_SIZE; + var LIMIT = 2147483648 - PAGE_MULTIPLE; + if (HEAP32[DYNAMICTOP_PTR >> 2] > LIMIT) { + return false; + } + var OLD_TOTAL_MEMORY = TOTAL_MEMORY; + TOTAL_MEMORY = Math.max(TOTAL_MEMORY, MIN_TOTAL_MEMORY); + while (TOTAL_MEMORY < HEAP32[DYNAMICTOP_PTR >> 2]) { + if (TOTAL_MEMORY <= 536870912) { + TOTAL_MEMORY = alignUp(2 * TOTAL_MEMORY, PAGE_MULTIPLE); + } else { + TOTAL_MEMORY = Math.min(alignUp((3 * TOTAL_MEMORY + 2147483648) / 4, PAGE_MULTIPLE), LIMIT); + } + } + var replacement = Module["reallocBuffer"](TOTAL_MEMORY); + if (!replacement || replacement.byteLength != TOTAL_MEMORY) { + TOTAL_MEMORY = OLD_TOTAL_MEMORY; + return false; + } + updateGlobalBuffer(replacement); + updateGlobalBufferViews(); + return true; + } + var byteLength; + try { + byteLength = Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get); + byteLength(new ArrayBuffer(4)); + } catch (e) { + byteLength = function (buffer) { + return buffer.byteLength; + }; + } + var TOTAL_STACK = Module["TOTAL_STACK"] || 5242880; + var TOTAL_MEMORY = Module["TOTAL_MEMORY"] || 16777216; + if (TOTAL_MEMORY < TOTAL_STACK) Module.printErr("TOTAL_MEMORY should be larger than TOTAL_STACK, was " + TOTAL_MEMORY + "! (TOTAL_STACK=" + TOTAL_STACK + ")"); + if (Module["buffer"]) { + buffer = Module["buffer"]; + } else { + if (typeof WebAssembly === "object" && typeof WebAssembly.Memory === "function") { + Module["wasmMemory"] = new WebAssembly.Memory({ initial: TOTAL_MEMORY / WASM_PAGE_SIZE }); + buffer = Module["wasmMemory"].buffer; + } else { + buffer = new ArrayBuffer(TOTAL_MEMORY); + } + Module["buffer"] = buffer; + } + updateGlobalBufferViews(); + function getTotalMemory() { + return TOTAL_MEMORY; + } + HEAP32[0] = 1668509029; + HEAP16[1] = 25459; + if (HEAPU8[2] !== 115 || HEAPU8[3] !== 99) throw "Runtime error: expected the system to be little-endian!"; + function callRuntimeCallbacks(callbacks) { + while (callbacks.length > 0) { + var callback = callbacks.shift(); + if (typeof callback == "function") { + callback(); + continue; + } + var func = callback.func; + if (typeof func === "number") { + if (callback.arg === undefined) { + Module["dynCall_v"](func); + } else { + Module["dynCall_vi"](func, callback.arg); + } + } else { + func(callback.arg === undefined ? null : callback.arg); + } + } + } + var __ATPRERUN__ = []; + var __ATINIT__ = []; + var __ATMAIN__ = []; + var __ATEXIT__ = []; + var __ATPOSTRUN__ = []; + var runtimeInitialized = false; + var runtimeExited = false; + function preRun() { + if (Module["preRun"]) { + if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]]; + while (Module["preRun"].length) { + addOnPreRun(Module["preRun"].shift()); + } + } + callRuntimeCallbacks(__ATPRERUN__); + } + function ensureInitRuntime() { + if (runtimeInitialized) return; + runtimeInitialized = true; + callRuntimeCallbacks(__ATINIT__); + } + function preMain() { + callRuntimeCallbacks(__ATMAIN__); + } + function exitRuntime() { + callRuntimeCallbacks(__ATEXIT__); + runtimeExited = true; + } + function postRun() { + if (Module["postRun"]) { + if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]]; + while (Module["postRun"].length) { + addOnPostRun(Module["postRun"].shift()); + } + } + callRuntimeCallbacks(__ATPOSTRUN__); + } + function addOnPreRun(cb) { + __ATPRERUN__.unshift(cb); + } + function addOnPostRun(cb) { + __ATPOSTRUN__.unshift(cb); + } + function writeArrayToMemory(array, buffer) { + HEAP8.set(array, buffer); + } + function writeAsciiToMemory(str, buffer, dontAddNull) { + for (var i = 0; i < str.length; ++i) { + HEAP8[buffer++ >> 0] = str.charCodeAt(i); + } + if (!dontAddNull) HEAP8[buffer >> 0] = 0; + } + var Math_abs = Math.abs; + var Math_cos = Math.cos; + var Math_sin = Math.sin; + var Math_tan = Math.tan; + var Math_acos = Math.acos; + var Math_asin = Math.asin; + var Math_atan = Math.atan; + var Math_atan2 = Math.atan2; + var Math_exp = Math.exp; + var Math_log = Math.log; + var Math_sqrt = Math.sqrt; + var Math_ceil = Math.ceil; + var Math_floor = Math.floor; + var Math_pow = Math.pow; + var Math_imul = Math.imul; + var Math_fround = Math.fround; + var Math_round = Math.round; + var Math_min = Math.min; + var Math_max = Math.max; + var Math_clz32 = Math.clz32; + var Math_trunc = Math.trunc; + var runDependencies = 0; + var runDependencyWatcher = null; + var dependenciesFulfilled = null; + function getUniqueRunDependency(id) { + return id; + } + function addRunDependency(id) { + runDependencies++; + if (Module["monitorRunDependencies"]) { + Module["monitorRunDependencies"](runDependencies); + } + } + function removeRunDependency(id) { + runDependencies--; + if (Module["monitorRunDependencies"]) { + Module["monitorRunDependencies"](runDependencies); + } + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); + } + } + } + Module["preloadedImages"] = {}; + Module["preloadedAudios"] = {}; + var dataURIPrefix = "data:application/octet-stream;base64,"; + function isDataURI(filename) { + return String.prototype.startsWith ? filename.startsWith(dataURIPrefix) : filename.indexOf(dataURIPrefix) === 0; + } + function integrateWasmJS() { + var wasmTextFile = "z3w.wast"; + var wasmBinaryFile = self.location.origin + "/z3w.wasm"; + var asmjsCodeFile = "z3w.temp.asm.js"; + if (typeof Module["locateFile"] === "function") { + if (!isDataURI(wasmTextFile)) { + wasmTextFile = Module["locateFile"](wasmTextFile); + } + if (!isDataURI(wasmBinaryFile)) { + wasmBinaryFile = Module["locateFile"](wasmBinaryFile); + } + if (!isDataURI(asmjsCodeFile)) { + asmjsCodeFile = Module["locateFile"](asmjsCodeFile); + } + } + var wasmPageSize = 64 * 1024; + var info = { + global: null, + env: null, + asm2wasm: { + "f64-rem": function (x, y) { + return x % y; + }, + debugger: function () { + debugger; + }, + }, + parent: Module, + }; + var exports = null; + function mergeMemory(newBuffer) { + var oldBuffer = Module["buffer"]; + if (newBuffer.byteLength < oldBuffer.byteLength) { + Module["printErr"]("the new buffer in mergeMemory is smaller than the previous one. in native wasm, we should grow memory here"); + } + var oldView = new Int8Array(oldBuffer); + var newView = new Int8Array(newBuffer); + newView.set(oldView); + updateGlobalBuffer(newBuffer); + updateGlobalBufferViews(); + } + function fixImports(imports) { + return imports; + } + function getBinary() { + try { + if (Module["wasmBinary"]) { + return new Uint8Array(Module["wasmBinary"]); + } + if (Module["readBinary"]) { + return Module["readBinary"](wasmBinaryFile); + } else { + throw "on the web, we need the wasm binary to be preloaded and set on Module['wasmBinary']. emcc.py will do that for you when generating HTML (but not JS)"; + } + } catch (err) { + abort(err); + } + } + function doNativeWasm(global, env, providedBuffer) { + if (typeof WebAssembly !== "object") { + Module["printErr"]("no native wasm support detected"); + return false; + } + if (!(Module["wasmMemory"] instanceof WebAssembly.Memory)) { + Module["printErr"]("no native wasm Memory in use"); + return false; + } + env["memory"] = Module["wasmMemory"]; + info["global"] = { NaN: NaN, Infinity: Infinity }; + info["global.Math"] = Math; + info["env"] = env; + function receiveInstance(instance, module) { + exports = instance.exports; + if (exports.memory) mergeMemory(exports.memory); + Module["asm"] = exports; + Module["usingWasm"] = true; + removeRunDependency("wasm-instantiate"); + } + addRunDependency("wasm-instantiate"); + if (Module["instantiateWasm"]) { + try { + return Module["instantiateWasm"](info, receiveInstance); + } catch (e) { + Module["printErr"]("Module.instantiateWasm callback failed with error: " + e); + return false; + } + } + var instance; + try { + instance = new WebAssembly.Instance(new WebAssembly.Module(getBinary()), info); + } catch (e) { + Module["printErr"]("failed to compile wasm module: " + e); + if (e.toString().indexOf("imported Memory with incompatible size") >= 0) { + Module["printErr"]( + "Memory size incompatibility issues may be due to changing TOTAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set TOTAL_MEMORY at runtime to something smaller than it was at compile time)." + ); + } + return false; + } + receiveInstance(instance); + return exports; + } + Module["asmPreload"] = Module["asm"]; + var asmjsReallocBuffer = Module["reallocBuffer"]; + var wasmReallocBuffer = function (size) { + var PAGE_MULTIPLE = Module["usingWasm"] ? WASM_PAGE_SIZE : ASMJS_PAGE_SIZE; + size = alignUp(size, PAGE_MULTIPLE); + var old = Module["buffer"]; + var oldSize = old.byteLength; + if (Module["usingWasm"]) { + try { + var result = Module["wasmMemory"].grow((size - oldSize) / wasmPageSize); + if (result !== (-1 | 0)) { + return (Module["buffer"] = Module["wasmMemory"].buffer); + } else { + return null; + } + } catch (e) { + return null; + } + } + }; + Module["reallocBuffer"] = function (size) { + if (finalMethod === "asmjs") { + return asmjsReallocBuffer(size); + } else { + return wasmReallocBuffer(size); + } + }; + var finalMethod = ""; + Module["asm"] = function (global, env, providedBuffer) { + env = fixImports(env); + if (!env["table"]) { + var TABLE_SIZE = Module["wasmTableSize"]; + if (TABLE_SIZE === undefined) TABLE_SIZE = 1024; + var MAX_TABLE_SIZE = Module["wasmMaxTableSize"]; + if (typeof WebAssembly === "object" && typeof WebAssembly.Table === "function") { + if (MAX_TABLE_SIZE !== undefined) { + env["table"] = new WebAssembly.Table({ initial: TABLE_SIZE, maximum: MAX_TABLE_SIZE, element: "anyfunc" }); + } else { + env["table"] = new WebAssembly.Table({ initial: TABLE_SIZE, element: "anyfunc" }); + } + } else { + env["table"] = new Array(TABLE_SIZE); + } + Module["wasmTable"] = env["table"]; + } + if (!env["memoryBase"]) { + env["memoryBase"] = Module["STATIC_BASE"]; + } + if (!env["tableBase"]) { + env["tableBase"] = 0; + } + var exports; + exports = doNativeWasm(global, env, providedBuffer); + if (!exports) abort("no binaryen method succeeded. consider enabling more options, like interpreting, if you want that: https://github.com/kripken/emscripten/wiki/WebAssembly#binaryen-methods"); + return exports; + }; + } + integrateWasmJS(); + STATIC_BASE = GLOBAL_BASE; + STATICTOP = STATIC_BASE + 329840; + __ATINIT__.push( + { + func: function () { + __GLOBAL__I_000101(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_nlsat_explain_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_iostream_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_rational_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_cooperate_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_params_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_inf_int_rational_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_inf_rational_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_prime_generator_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_nlsat_solver_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_opt_frontend_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_ast_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_iz3translate_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_iz3profiling_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_iz3interp_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_nlqsat_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_duality_profiling_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_api_config_params_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_api_interp_cpp(); + }, + }, + { + func: function () { + __GLOBAL__sub_I_main_cpp(); + }, + } + ); + var STATIC_BUMP = 329840; + Module["STATIC_BASE"] = STATIC_BASE; + Module["STATIC_BUMP"] = STATIC_BUMP; + STATICTOP += 16; + function __exit(status) { + Module["exit"](status); + } + function _exit(status) { + __exit(status); + } + function __Exit(status) { + __exit(status); + } + function __ZSt18uncaught_exceptionv() { + return !!__ZSt18uncaught_exceptionv.uncaught_exception; + } + function ___assert_fail(condition, filename, line, func) { + abort("Assertion failed: " + Pointer_stringify(condition) + ", at: " + [filename ? Pointer_stringify(filename) : "unknown filename", line, func ? Pointer_stringify(func) : "unknown function"]); + } + function ___cxa_allocate_exception(size) { + return _malloc(size); + } + var EXCEPTIONS = { + last: 0, + caught: [], + infos: {}, + deAdjust: function (adjusted) { + if (!adjusted || EXCEPTIONS.infos[adjusted]) return adjusted; + for (var ptr in EXCEPTIONS.infos) { + var info = EXCEPTIONS.infos[ptr]; + if (info.adjusted === adjusted) { + return ptr; + } + } + return adjusted; + }, + addRef: function (ptr) { + if (!ptr) return; + var info = EXCEPTIONS.infos[ptr]; + info.refcount++; + }, + decRef: function (ptr) { + if (!ptr) return; + var info = EXCEPTIONS.infos[ptr]; + assert(info.refcount > 0); + info.refcount--; + if (info.refcount === 0 && !info.rethrown) { + if (info.destructor) { + Module["dynCall_vi"](info.destructor, ptr); + } + delete EXCEPTIONS.infos[ptr]; + ___cxa_free_exception(ptr); + } + }, + clearRef: function (ptr) { + if (!ptr) return; + var info = EXCEPTIONS.infos[ptr]; + info.refcount = 0; + }, + }; + function ___cxa_begin_catch(ptr) { + var info = EXCEPTIONS.infos[ptr]; + if (info && !info.caught) { + info.caught = true; + __ZSt18uncaught_exceptionv.uncaught_exception--; + } + if (info) info.rethrown = false; + EXCEPTIONS.caught.push(ptr); + EXCEPTIONS.addRef(EXCEPTIONS.deAdjust(ptr)); + return ptr; + } + function ___cxa_free_exception(ptr) { + try { + return _free(ptr); + } catch (e) {} + } + function ___cxa_end_catch() { + Module["setThrew"](0); + var ptr = EXCEPTIONS.caught.pop(); + if (ptr) { + EXCEPTIONS.decRef(EXCEPTIONS.deAdjust(ptr)); + EXCEPTIONS.last = 0; + } + } + function ___cxa_find_matching_catch_2() { + return ___cxa_find_matching_catch.apply(null, arguments); + } + function ___cxa_find_matching_catch_3() { + return ___cxa_find_matching_catch.apply(null, arguments); + } + function ___cxa_find_matching_catch_4() { + return ___cxa_find_matching_catch.apply(null, arguments); + } + function ___cxa_find_matching_catch_5() { + return ___cxa_find_matching_catch.apply(null, arguments); + } + function ___cxa_find_matching_catch_6() { + return ___cxa_find_matching_catch.apply(null, arguments); + } + function ___cxa_find_matching_catch_7() { + return ___cxa_find_matching_catch.apply(null, arguments); + } + function ___cxa_get_exception_ptr(ptr) { + return ptr; + } + function ___cxa_pure_virtual() { + ABORT = true; + throw "Pure virtual function called!"; + } + function ___cxa_rethrow() { + var ptr = EXCEPTIONS.caught.pop(); + if (!EXCEPTIONS.infos[ptr].rethrown) { + EXCEPTIONS.caught.push(ptr); + EXCEPTIONS.infos[ptr].rethrown = true; + } + EXCEPTIONS.last = ptr; + throw ptr; + } + function ___resumeException(ptr) { + if (!EXCEPTIONS.last) { + EXCEPTIONS.last = ptr; + } + throw ptr; + } + function ___cxa_find_matching_catch() { + var thrown = EXCEPTIONS.last; + if (!thrown) { + return (setTempRet0(0), 0) | 0; + } + var info = EXCEPTIONS.infos[thrown]; + var throwntype = info.type; + if (!throwntype) { + return (setTempRet0(0), thrown) | 0; + } + var typeArray = Array.prototype.slice.call(arguments); + var pointer = Module["___cxa_is_pointer_type"](throwntype); + if (!___cxa_find_matching_catch.buffer) ___cxa_find_matching_catch.buffer = _malloc(4); + HEAP32[___cxa_find_matching_catch.buffer >> 2] = thrown; + thrown = ___cxa_find_matching_catch.buffer; + for (var i = 0; i < typeArray.length; i++) { + if (typeArray[i] && Module["___cxa_can_catch"](typeArray[i], throwntype, thrown)) { + thrown = HEAP32[thrown >> 2]; + info.adjusted = thrown; + return (setTempRet0(typeArray[i]), thrown) | 0; + } + } + thrown = HEAP32[thrown >> 2]; + return (setTempRet0(throwntype), thrown) | 0; + } + function ___cxa_throw(ptr, type, destructor) { + EXCEPTIONS.infos[ptr] = { ptr: ptr, adjusted: ptr, type: type, destructor: destructor, refcount: 0, caught: false, rethrown: false }; + EXCEPTIONS.last = ptr; + if (!("uncaught_exception" in __ZSt18uncaught_exceptionv)) { + __ZSt18uncaught_exceptionv.uncaught_exception = 1; + } else { + __ZSt18uncaught_exceptionv.uncaught_exception++; + } + throw ptr; + } + function ___lock() {} + var ERRNO_CODES = { + EPERM: 1, + ENOENT: 2, + ESRCH: 3, + EINTR: 4, + EIO: 5, + ENXIO: 6, + E2BIG: 7, + ENOEXEC: 8, + EBADF: 9, + ECHILD: 10, + EAGAIN: 11, + EWOULDBLOCK: 11, + ENOMEM: 12, + EACCES: 13, + EFAULT: 14, + ENOTBLK: 15, + EBUSY: 16, + EEXIST: 17, + EXDEV: 18, + ENODEV: 19, + ENOTDIR: 20, + EISDIR: 21, + EINVAL: 22, + ENFILE: 23, + EMFILE: 24, + ENOTTY: 25, + ETXTBSY: 26, + EFBIG: 27, + ENOSPC: 28, + ESPIPE: 29, + EROFS: 30, + EMLINK: 31, + EPIPE: 32, + EDOM: 33, + ERANGE: 34, + ENOMSG: 42, + EIDRM: 43, + ECHRNG: 44, + EL2NSYNC: 45, + EL3HLT: 46, + EL3RST: 47, + ELNRNG: 48, + EUNATCH: 49, + ENOCSI: 50, + EL2HLT: 51, + EDEADLK: 35, + ENOLCK: 37, + EBADE: 52, + EBADR: 53, + EXFULL: 54, + ENOANO: 55, + EBADRQC: 56, + EBADSLT: 57, + EDEADLOCK: 35, + EBFONT: 59, + ENOSTR: 60, + ENODATA: 61, + ETIME: 62, + ENOSR: 63, + ENONET: 64, + ENOPKG: 65, + EREMOTE: 66, + ENOLINK: 67, + EADV: 68, + ESRMNT: 69, + ECOMM: 70, + EPROTO: 71, + EMULTIHOP: 72, + EDOTDOT: 73, + EBADMSG: 74, + ENOTUNIQ: 76, + EBADFD: 77, + EREMCHG: 78, + ELIBACC: 79, + ELIBBAD: 80, + ELIBSCN: 81, + ELIBMAX: 82, + ELIBEXEC: 83, + ENOSYS: 38, + ENOTEMPTY: 39, + ENAMETOOLONG: 36, + ELOOP: 40, + EOPNOTSUPP: 95, + EPFNOSUPPORT: 96, + ECONNRESET: 104, + ENOBUFS: 105, + EAFNOSUPPORT: 97, + EPROTOTYPE: 91, + ENOTSOCK: 88, + ENOPROTOOPT: 92, + ESHUTDOWN: 108, + ECONNREFUSED: 111, + EADDRINUSE: 98, + ECONNABORTED: 103, + ENETUNREACH: 101, + ENETDOWN: 100, + ETIMEDOUT: 110, + EHOSTDOWN: 112, + EHOSTUNREACH: 113, + EINPROGRESS: 115, + EALREADY: 114, + EDESTADDRREQ: 89, + EMSGSIZE: 90, + EPROTONOSUPPORT: 93, + ESOCKTNOSUPPORT: 94, + EADDRNOTAVAIL: 99, + ENETRESET: 102, + EISCONN: 106, + ENOTCONN: 107, + ETOOMANYREFS: 109, + EUSERS: 87, + EDQUOT: 122, + ESTALE: 116, + ENOTSUP: 95, + ENOMEDIUM: 123, + EILSEQ: 84, + EOVERFLOW: 75, + ECANCELED: 125, + ENOTRECOVERABLE: 131, + EOWNERDEAD: 130, + ESTRPIPE: 86, + }; + function ___setErrNo(value) { + if (Module["___errno_location"]) HEAP32[Module["___errno_location"]() >> 2] = value; + return value; + } + function ___map_file(pathname, size) { + ___setErrNo(ERRNO_CODES.EPERM); + return -1; + } + var ERRNO_MESSAGES = { + 0: "Success", + 1: "Not super-user", + 2: "No such file or directory", + 3: "No such process", + 4: "Interrupted system call", + 5: "I/O error", + 6: "No such device or address", + 7: "Arg list too long", + 8: "Exec format error", + 9: "Bad file number", + 10: "No children", + 11: "No more processes", + 12: "Not enough core", + 13: "Permission denied", + 14: "Bad address", + 15: "Block device required", + 16: "Mount device busy", + 17: "File exists", + 18: "Cross-device link", + 19: "No such device", + 20: "Not a directory", + 21: "Is a directory", + 22: "Invalid argument", + 23: "Too many open files in system", + 24: "Too many open files", + 25: "Not a typewriter", + 26: "Text file busy", + 27: "File too large", + 28: "No space left on device", + 29: "Illegal seek", + 30: "Read only file system", + 31: "Too many links", + 32: "Broken pipe", + 33: "Math arg out of domain of func", + 34: "Math result not representable", + 35: "File locking deadlock error", + 36: "File or path name too long", + 37: "No record locks available", + 38: "Function not implemented", + 39: "Directory not empty", + 40: "Too many symbolic links", + 42: "No message of desired type", + 43: "Identifier removed", + 44: "Channel number out of range", + 45: "Level 2 not synchronized", + 46: "Level 3 halted", + 47: "Level 3 reset", + 48: "Link number out of range", + 49: "Protocol driver not attached", + 50: "No CSI structure available", + 51: "Level 2 halted", + 52: "Invalid exchange", + 53: "Invalid request descriptor", + 54: "Exchange full", + 55: "No anode", + 56: "Invalid request code", + 57: "Invalid slot", + 59: "Bad font file fmt", + 60: "Device not a stream", + 61: "No data (for no delay io)", + 62: "Timer expired", + 63: "Out of streams resources", + 64: "Machine is not on the network", + 65: "Package not installed", + 66: "The object is remote", + 67: "The link has been severed", + 68: "Advertise error", + 69: "Srmount error", + 70: "Communication error on send", + 71: "Protocol error", + 72: "Multihop attempted", + 73: "Cross mount point (not really error)", + 74: "Trying to read unreadable message", + 75: "Value too large for defined data type", + 76: "Given log. name not unique", + 77: "f.d. invalid for this operation", + 78: "Remote address changed", + 79: "Can access a needed shared lib", + 80: "Accessing a corrupted shared lib", + 81: ".lib section in a.out corrupted", + 82: "Attempting to link in too many libs", + 83: "Attempting to exec a shared library", + 84: "Illegal byte sequence", + 86: "Streams pipe error", + 87: "Too many users", + 88: "Socket operation on non-socket", + 89: "Destination address required", + 90: "Message too long", + 91: "Protocol wrong type for socket", + 92: "Protocol not available", + 93: "Unknown protocol", + 94: "Socket type not supported", + 95: "Not supported", + 96: "Protocol family not supported", + 97: "Address family not supported by protocol family", + 98: "Address already in use", + 99: "Address not available", + 100: "Network interface is not configured", + 101: "Network is unreachable", + 102: "Connection reset by network", + 103: "Connection aborted", + 104: "Connection reset by peer", + 105: "No buffer space available", + 106: "Socket is already connected", + 107: "Socket is not connected", + 108: "Can't send after socket shutdown", + 109: "Too many references", + 110: "Connection timed out", + 111: "Connection refused", + 112: "Host is down", + 113: "Host is unreachable", + 114: "Socket already connected", + 115: "Connection already in progress", + 116: "Stale file handle", + 122: "Quota exceeded", + 123: "No medium (in tape drive)", + 125: "Operation canceled", + 130: "Previous owner died", + 131: "State not recoverable", + }; + var PATH = { + splitPath: function (filename) { + var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + return splitPathRe.exec(filename).slice(1); + }, + normalizeArray: function (parts, allowAboveRoot) { + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === ".") { + parts.splice(i, 1); + } else if (last === "..") { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + if (allowAboveRoot) { + for (; up; up--) { + parts.unshift(".."); + } + } + return parts; + }, + normalize: function (path) { + var isAbsolute = path.charAt(0) === "/", + trailingSlash = path.substr(-1) === "/"; + path = PATH.normalizeArray( + path.split("/").filter(function (p) { + return !!p; + }), + !isAbsolute + ).join("/"); + if (!path && !isAbsolute) { + path = "."; + } + if (path && trailingSlash) { + path += "/"; + } + return (isAbsolute ? "/" : "") + path; + }, + dirname: function (path) { + var result = PATH.splitPath(path), + root = result[0], + dir = result[1]; + if (!root && !dir) { + return "."; + } + if (dir) { + dir = dir.substr(0, dir.length - 1); + } + return root + dir; + }, + basename: function (path) { + if (path === "/") return "/"; + var lastSlash = path.lastIndexOf("/"); + if (lastSlash === -1) return path; + return path.substr(lastSlash + 1); + }, + extname: function (path) { + return PATH.splitPath(path)[3]; + }, + join: function () { + var paths = Array.prototype.slice.call(arguments, 0); + return PATH.normalize(paths.join("/")); + }, + join2: function (l, r) { + return PATH.normalize(l + "/" + r); + }, + resolve: function () { + var resolvedPath = "", + resolvedAbsolute = false; + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = i >= 0 ? arguments[i] : FS.cwd(); + if (typeof path !== "string") { + throw new TypeError("Arguments to path.resolve must be strings"); + } else if (!path) { + return ""; + } + resolvedPath = path + "/" + resolvedPath; + resolvedAbsolute = path.charAt(0) === "/"; + } + resolvedPath = PATH.normalizeArray( + resolvedPath.split("/").filter(function (p) { + return !!p; + }), + !resolvedAbsolute + ).join("/"); + return (resolvedAbsolute ? "/" : "") + resolvedPath || "."; + }, + relative: function (from, to) { + from = PATH.resolve(from).substr(1); + to = PATH.resolve(to).substr(1); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== "") break; + } + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== "") break; + } + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + var fromParts = trim(from.split("/")); + var toParts = trim(to.split("/")); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push(".."); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + return outputParts.join("/"); + }, + }; + var TTY = { + ttys: [], + init: function () {}, + shutdown: function () {}, + register: function (dev, ops) { + TTY.ttys[dev] = { input: [], output: [], ops: ops }; + FS.registerDevice(dev, TTY.stream_ops); + }, + stream_ops: { + open: function (stream) { + var tty = TTY.ttys[stream.node.rdev]; + if (!tty) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + stream.tty = tty; + stream.seekable = false; + }, + close: function (stream) { + stream.tty.ops.flush(stream.tty); + }, + flush: function (stream) { + stream.tty.ops.flush(stream.tty); + }, + read: function (stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new FS.ErrnoError(ERRNO_CODES.ENXIO); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function (stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new FS.ErrnoError(ERRNO_CODES.ENXIO); + } + for (var i = 0; i < length; i++) { + try { + stream.tty.ops.put_char(stream.tty, buffer[offset + i]); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + }, + }, + default_tty_ops: { + get_char: function (tty) { + if (!tty.input.length) { + var result = null; + if (ENVIRONMENT_IS_NODE) { + var BUFSIZE = 256; + var buf = new Buffer(BUFSIZE); + var bytesRead = 0; + var isPosixPlatform = process.platform != "win32"; + var fd = process.stdin.fd; + if (isPosixPlatform) { + var usingDevice = false; + try { + fd = fs.openSync("/dev/stdin", "r"); + usingDevice = true; + } catch (e) {} + } + try { + bytesRead = fs.readSync(fd, buf, 0, BUFSIZE, null); + } catch (e) { + if (e.toString().indexOf("EOF") != -1) bytesRead = 0; + else throw e; + } + if (usingDevice) { + fs.closeSync(fd); + } + if (bytesRead > 0) { + result = buf.slice(0, bytesRead).toString("utf-8"); + } else { + result = null; + } + } else if (typeof window != "undefined" && typeof window.prompt == "function") { + result = window.prompt("Input: "); + if (result !== null) { + result += "\n"; + } + } else if (typeof readline == "function") { + result = readline(); + if (result !== null) { + result += "\n"; + } + } + if (!result) { + return null; + } + tty.input = intArrayFromString(result, true); + } + return tty.input.shift(); + }, + put_char: function (tty, val) { + if (val === null || val === 10) { + Module["print"](UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + flush: function (tty) { + if (tty.output && tty.output.length > 0) { + Module["print"](UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } + }, + }, + default_tty1_ops: { + put_char: function (tty, val) { + if (val === null || val === 10) { + Module["printErr"](UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + flush: function (tty) { + if (tty.output && tty.output.length > 0) { + Module["printErr"](UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } + }, + }, + }; + var MEMFS = { + ops_table: null, + mount: function (mount) { + return MEMFS.createNode(null, "/", 16384 | 511, 0); + }, + createNode: function (parent, name, mode, dev) { + if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (!MEMFS.ops_table) { + MEMFS.ops_table = { + dir: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + lookup: MEMFS.node_ops.lookup, + mknod: MEMFS.node_ops.mknod, + rename: MEMFS.node_ops.rename, + unlink: MEMFS.node_ops.unlink, + rmdir: MEMFS.node_ops.rmdir, + readdir: MEMFS.node_ops.readdir, + symlink: MEMFS.node_ops.symlink, + }, + stream: { llseek: MEMFS.stream_ops.llseek }, + }, + file: { + node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr }, + stream: { llseek: MEMFS.stream_ops.llseek, read: MEMFS.stream_ops.read, write: MEMFS.stream_ops.write, allocate: MEMFS.stream_ops.allocate, mmap: MEMFS.stream_ops.mmap, msync: MEMFS.stream_ops.msync }, + }, + link: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr, readlink: MEMFS.node_ops.readlink }, stream: {} }, + chrdev: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr }, stream: FS.chrdev_stream_ops }, + }; + } + var node = FS.createNode(parent, name, mode, dev); + if (FS.isDir(node.mode)) { + node.node_ops = MEMFS.ops_table.dir.node; + node.stream_ops = MEMFS.ops_table.dir.stream; + node.contents = {}; + } else if (FS.isFile(node.mode)) { + node.node_ops = MEMFS.ops_table.file.node; + node.stream_ops = MEMFS.ops_table.file.stream; + node.usedBytes = 0; + node.contents = null; + } else if (FS.isLink(node.mode)) { + node.node_ops = MEMFS.ops_table.link.node; + node.stream_ops = MEMFS.ops_table.link.stream; + } else if (FS.isChrdev(node.mode)) { + node.node_ops = MEMFS.ops_table.chrdev.node; + node.stream_ops = MEMFS.ops_table.chrdev.stream; + } + node.timestamp = Date.now(); + if (parent) { + parent.contents[name] = node; + } + return node; + }, + getFileDataAsRegularArray: function (node) { + if (node.contents && node.contents.subarray) { + var arr = []; + for (var i = 0; i < node.usedBytes; ++i) arr.push(node.contents[i]); + return arr; + } + return node.contents; + }, + getFileDataAsTypedArray: function (node) { + if (!node.contents) return new Uint8Array(); + if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); + return new Uint8Array(node.contents); + }, + expandFileStorage: function (node, newCapacity) { + if (node.contents && node.contents.subarray && newCapacity > node.contents.length) { + node.contents = MEMFS.getFileDataAsRegularArray(node); + node.usedBytes = node.contents.length; + } + if (!node.contents || node.contents.subarray) { + var prevCapacity = node.contents ? node.contents.length : 0; + if (prevCapacity >= newCapacity) return; + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2 : 1.125)) | 0); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); + if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); + return; + } + if (!node.contents && newCapacity > 0) node.contents = []; + while (node.contents.length < newCapacity) node.contents.push(0); + }, + resizeFileStorage: function (node, newSize) { + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; + node.usedBytes = 0; + return; + } + if (!node.contents || node.contents.subarray) { + var oldContents = node.contents; + node.contents = new Uint8Array(new ArrayBuffer(newSize)); + if (oldContents) { + node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); + } + node.usedBytes = newSize; + return; + } + if (!node.contents) node.contents = []; + if (node.contents.length > newSize) node.contents.length = newSize; + else while (node.contents.length < newSize) node.contents.push(0); + node.usedBytes = newSize; + }, + node_ops: { + getattr: function (node) { + var attr = {}; + attr.dev = FS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (FS.isDir(node.mode)) { + attr.size = 4096; + } else if (FS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (FS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.timestamp); + attr.mtime = new Date(node.timestamp); + attr.ctime = new Date(node.timestamp); + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr: function (node, attr) { + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + MEMFS.resizeFileStorage(node, attr.size); + } + }, + lookup: function (parent, name) { + throw FS.genericErrors[ERRNO_CODES.ENOENT]; + }, + mknod: function (parent, name, mode, dev) { + return MEMFS.createNode(parent, name, mode, dev); + }, + rename: function (old_node, new_dir, new_name) { + if (FS.isDir(old_node.mode)) { + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (new_node) { + for (var i in new_node.contents) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); + } + } + } + delete old_node.parent.contents[old_node.name]; + old_node.name = new_name; + new_dir.contents[new_name] = old_node; + old_node.parent = new_dir; + }, + unlink: function (parent, name) { + delete parent.contents[name]; + }, + rmdir: function (parent, name) { + var node = FS.lookupNode(parent, name); + for (var i in node.contents) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); + } + delete parent.contents[name]; + }, + readdir: function (node) { + var entries = [".", ".."]; + for (var key in node.contents) { + if (!node.contents.hasOwnProperty(key)) { + continue; + } + entries.push(key); + } + return entries; + }, + symlink: function (parent, newname, oldpath) { + var node = MEMFS.createNode(parent, newname, 511 | 40960, 0); + node.link = oldpath; + return node; + }, + readlink: function (node) { + if (!FS.isLink(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return node.link; + }, + }, + stream_ops: { + read: function (stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); + assert(size >= 0); + if (size > 8 && contents.subarray) { + buffer.set(contents.subarray(position, position + size), offset); + } else { + for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i]; + } + return size; + }, + write: function (stream, buffer, offset, length, position, canOwn) { + if (!length) return 0; + var node = stream.node; + node.timestamp = Date.now(); + if (buffer.subarray && (!node.contents || node.contents.subarray)) { + if (canOwn) { + node.contents = buffer.subarray(offset, offset + length); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0 && position === 0) { + node.contents = new Uint8Array(buffer.subarray(offset, offset + length)); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { + node.contents.set(buffer.subarray(offset, offset + length), position); + return length; + } + } + MEMFS.expandFileStorage(node, position + length); + if (node.contents.subarray && buffer.subarray) node.contents.set(buffer.subarray(offset, offset + length), position); + else { + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; + } + } + node.usedBytes = Math.max(node.usedBytes, position + length); + return length; + }, + llseek: function (stream, offset, whence) { + var position = offset; + if (whence === 1) { + position += stream.position; + } else if (whence === 2) { + if (FS.isFile(stream.node.mode)) { + position += stream.node.usedBytes; + } + } + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return position; + }, + allocate: function (stream, offset, length) { + MEMFS.expandFileStorage(stream.node, offset + length); + stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); + }, + mmap: function (stream, buffer, offset, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + var ptr; + var allocated; + var contents = stream.node.contents; + if (!(flags & 2) && (contents.buffer === buffer || contents.buffer === buffer.buffer)) { + allocated = false; + ptr = contents.byteOffset; + } else { + if (position > 0 || position + length < stream.node.usedBytes) { + if (contents.subarray) { + contents = contents.subarray(position, position + length); + } else { + contents = Array.prototype.slice.call(contents, position, position + length); + } + } + allocated = true; + ptr = _malloc(length); + if (!ptr) { + throw new FS.ErrnoError(ERRNO_CODES.ENOMEM); + } + buffer.set(contents, ptr); + } + return { ptr: ptr, allocated: allocated }; + }, + msync: function (stream, buffer, offset, length, mmapFlags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + if (mmapFlags & 2) { + return 0; + } + var bytesWritten = MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false); + return 0; + }, + }, + }; + var NODEFS = { + isWindows: false, + staticInit: function () { + NODEFS.isWindows = !!process.platform.match(/^win/); + var flags = process["binding"]("constants"); + if (flags["fs"]) { + flags = flags["fs"]; + } + NODEFS.flagsForNodeMap = { "1024": flags["O_APPEND"], "64": flags["O_CREAT"], "128": flags["O_EXCL"], "0": flags["O_RDONLY"], "2": flags["O_RDWR"], "4096": flags["O_SYNC"], "512": flags["O_TRUNC"], "1": flags["O_WRONLY"] }; + }, + bufferFrom: function (arrayBuffer) { + return Buffer.alloc ? Buffer.from(arrayBuffer) : new Buffer(arrayBuffer); + }, + mount: function (mount) { + assert(ENVIRONMENT_IS_NODE); + return NODEFS.createNode(null, "/", NODEFS.getMode(mount.opts.root), 0); + }, + createNode: function (parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = NODEFS.node_ops; + node.stream_ops = NODEFS.stream_ops; + return node; + }, + getMode: function (path) { + var stat; + try { + stat = fs.lstatSync(path); + if (NODEFS.isWindows) { + stat.mode = stat.mode | ((stat.mode & 292) >> 2); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return stat.mode; + }, + realPath: function (node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join.apply(null, parts); + }, + flagsForNode: function (flags) { + flags &= ~2097152; + flags &= ~2048; + flags &= ~32768; + flags &= ~524288; + var newFlags = 0; + for (var k in NODEFS.flagsForNodeMap) { + if (flags & k) { + newFlags |= NODEFS.flagsForNodeMap[k]; + flags ^= k; + } + } + if (!flags) { + return newFlags; + } else { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + }, + node_ops: { + getattr: function (node) { + var path = NODEFS.realPath(node); + var stat; + try { + stat = fs.lstatSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + if (NODEFS.isWindows && !stat.blksize) { + stat.blksize = 4096; + } + if (NODEFS.isWindows && !stat.blocks) { + stat.blocks = ((stat.size + stat.blksize - 1) / stat.blksize) | 0; + } + return { + dev: stat.dev, + ino: stat.ino, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks, + }; + }, + setattr: function (node, attr) { + var path = NODEFS.realPath(node); + try { + if (attr.mode !== undefined) { + fs.chmodSync(path, attr.mode); + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + var date = new Date(attr.timestamp); + fs.utimesSync(path, date, date); + } + if (attr.size !== undefined) { + fs.truncateSync(path, attr.size); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + lookup: function (parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + var mode = NODEFS.getMode(path); + return NODEFS.createNode(parent, name, mode); + }, + mknod: function (parent, name, mode, dev) { + var node = NODEFS.createNode(parent, name, mode, dev); + var path = NODEFS.realPath(node); + try { + if (FS.isDir(node.mode)) { + fs.mkdirSync(path, node.mode); + } else { + fs.writeFileSync(path, "", { mode: node.mode }); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return node; + }, + rename: function (oldNode, newDir, newName) { + var oldPath = NODEFS.realPath(oldNode); + var newPath = PATH.join2(NODEFS.realPath(newDir), newName); + try { + fs.renameSync(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + unlink: function (parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + try { + fs.unlinkSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + rmdir: function (parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + try { + fs.rmdirSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readdir: function (node) { + var path = NODEFS.realPath(node); + try { + return fs.readdirSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + symlink: function (parent, newName, oldPath) { + var newPath = PATH.join2(NODEFS.realPath(parent), newName); + try { + fs.symlinkSync(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readlink: function (node) { + var path = NODEFS.realPath(node); + try { + path = fs.readlinkSync(path); + path = NODEJS_PATH.relative(NODEJS_PATH.resolve(node.mount.opts.root), path); + return path; + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + }, + stream_ops: { + open: function (stream) { + var path = NODEFS.realPath(stream.node); + try { + if (FS.isFile(stream.node.mode)) { + stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags)); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + close: function (stream) { + try { + if (FS.isFile(stream.node.mode) && stream.nfd) { + fs.closeSync(stream.nfd); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + read: function (stream, buffer, offset, length, position) { + if (length === 0) return 0; + try { + return fs.readSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + write: function (stream, buffer, offset, length, position) { + try { + return fs.writeSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + llseek: function (stream, offset, whence) { + var position = offset; + if (whence === 1) { + position += stream.position; + } else if (whence === 2) { + if (FS.isFile(stream.node.mode)) { + try { + var stat = fs.fstatSync(stream.nfd); + position += stat.size; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + } + } + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return position; + }, + }, + }; + STATICTOP += 16; + STATICTOP += 16; + STATICTOP += 16; + var FS = { + root: null, + mounts: [], + devices: {}, + streams: [], + nextInode: 1, + nameTable: null, + currentPath: "/", + initialized: false, + ignorePermissions: true, + trackingDelegate: {}, + tracking: { openFlags: { READ: 1, WRITE: 2 } }, + ErrnoError: null, + genericErrors: {}, + filesystems: null, + syncFSRequests: 0, + handleFSError: function (e) { + if (!(e instanceof FS.ErrnoError)) throw e + " : " + stackTrace(); + return ___setErrNo(e.errno); + }, + lookupPath: function (path, opts) { + path = PATH.resolve(FS.cwd(), path); + opts = opts || {}; + if (!path) return { path: "", node: null }; + var defaults = { follow_mount: true, recurse_count: 0 }; + for (var key in defaults) { + if (opts[key] === undefined) { + opts[key] = defaults[key]; + } + } + if (opts.recurse_count > 8) { + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); + } + var parts = PATH.normalizeArray( + path.split("/").filter(function (p) { + return !!p; + }), + false + ); + var current = FS.root; + var current_path = "/"; + for (var i = 0; i < parts.length; i++) { + var islast = i === parts.length - 1; + if (islast && opts.parent) { + break; + } + current = FS.lookupNode(current, parts[i]); + current_path = PATH.join2(current_path, parts[i]); + if (FS.isMountpoint(current)) { + if (!islast || (islast && opts.follow_mount)) { + current = current.mounted.root; + } + } + if (!islast || opts.follow) { + var count = 0; + while (FS.isLink(current.mode)) { + var link = FS.readlink(current_path); + current_path = PATH.resolve(PATH.dirname(current_path), link); + var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count }); + current = lookup.node; + if (count++ > 40) { + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); + } + } + } + } + return { path: current_path, node: current }; + }, + getPath: function (node) { + var path; + while (true) { + if (FS.isRoot(node)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length - 1] !== "/" ? mount + "/" + path : mount + path; + } + path = path ? node.name + "/" + path : node.name; + node = node.parent; + } + }, + hashName: function (parentid, name) { + var hash = 0; + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % FS.nameTable.length; + }, + hashAddNode: function (node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.nameTable[hash]; + FS.nameTable[hash] = node; + }, + hashRemoveNode: function (node) { + var hash = FS.hashName(node.parent.id, node.name); + if (FS.nameTable[hash] === node) { + FS.nameTable[hash] = node.name_next; + } else { + var current = FS.nameTable[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } + } + }, + lookupNode: function (parent, name) { + var err = FS.mayLookup(parent); + if (err) { + throw new FS.ErrnoError(err, parent); + } + var hash = FS.hashName(parent.id, name); + for (var node = FS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + return FS.lookup(parent, name); + }, + createNode: function (parent, name, mode, rdev) { + if (!FS.FSNode) { + FS.FSNode = function (parent, name, mode, rdev) { + if (!parent) { + parent = this; + } + this.parent = parent; + this.mount = parent.mount; + this.mounted = null; + this.id = FS.nextInode++; + this.name = name; + this.mode = mode; + this.node_ops = {}; + this.stream_ops = {}; + this.rdev = rdev; + }; + FS.FSNode.prototype = {}; + var readMode = 292 | 73; + var writeMode = 146; + Object.defineProperties(FS.FSNode.prototype, { + read: { + get: function () { + return (this.mode & readMode) === readMode; + }, + set: function (val) { + val ? (this.mode |= readMode) : (this.mode &= ~readMode); + }, + }, + write: { + get: function () { + return (this.mode & writeMode) === writeMode; + }, + set: function (val) { + val ? (this.mode |= writeMode) : (this.mode &= ~writeMode); + }, + }, + isFolder: { + get: function () { + return FS.isDir(this.mode); + }, + }, + isDevice: { + get: function () { + return FS.isChrdev(this.mode); + }, + }, + }); + } + var node = new FS.FSNode(parent, name, mode, rdev); + FS.hashAddNode(node); + return node; + }, + destroyNode: function (node) { + FS.hashRemoveNode(node); + }, + isRoot: function (node) { + return node === node.parent; + }, + isMountpoint: function (node) { + return !!node.mounted; + }, + isFile: function (mode) { + return (mode & 61440) === 32768; + }, + isDir: function (mode) { + return (mode & 61440) === 16384; + }, + isLink: function (mode) { + return (mode & 61440) === 40960; + }, + isChrdev: function (mode) { + return (mode & 61440) === 8192; + }, + isBlkdev: function (mode) { + return (mode & 61440) === 24576; + }, + isFIFO: function (mode) { + return (mode & 61440) === 4096; + }, + isSocket: function (mode) { + return (mode & 49152) === 49152; + }, + flagModes: { r: 0, rs: 1052672, "r+": 2, w: 577, wx: 705, xw: 705, "w+": 578, "wx+": 706, "xw+": 706, a: 1089, ax: 1217, xa: 1217, "a+": 1090, "ax+": 1218, "xa+": 1218 }, + modeStringToFlags: function (str) { + var flags = FS.flagModes[str]; + if (typeof flags === "undefined") { + throw new Error("Unknown file open mode: " + str); + } + return flags; + }, + flagsToPermissionString: function (flag) { + var perms = ["r", "w", "rw"][flag & 3]; + if (flag & 512) { + perms += "w"; + } + return perms; + }, + nodePermissions: function (node, perms) { + if (FS.ignorePermissions) { + return 0; + } + if (perms.indexOf("r") !== -1 && !(node.mode & 292)) { + return ERRNO_CODES.EACCES; + } else if (perms.indexOf("w") !== -1 && !(node.mode & 146)) { + return ERRNO_CODES.EACCES; + } else if (perms.indexOf("x") !== -1 && !(node.mode & 73)) { + return ERRNO_CODES.EACCES; + } + return 0; + }, + mayLookup: function (dir) { + var err = FS.nodePermissions(dir, "x"); + if (err) return err; + if (!dir.node_ops.lookup) return ERRNO_CODES.EACCES; + return 0; + }, + mayCreate: function (dir, name) { + try { + var node = FS.lookupNode(dir, name); + return ERRNO_CODES.EEXIST; + } catch (e) {} + return FS.nodePermissions(dir, "wx"); + }, + mayDelete: function (dir, name, isdir) { + var node; + try { + node = FS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var err = FS.nodePermissions(dir, "wx"); + if (err) { + return err; + } + if (isdir) { + if (!FS.isDir(node.mode)) { + return ERRNO_CODES.ENOTDIR; + } + if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { + return ERRNO_CODES.EBUSY; + } + } else { + if (FS.isDir(node.mode)) { + return ERRNO_CODES.EISDIR; + } + } + return 0; + }, + mayOpen: function (node, flags) { + if (!node) { + return ERRNO_CODES.ENOENT; + } + if (FS.isLink(node.mode)) { + return ERRNO_CODES.ELOOP; + } else if (FS.isDir(node.mode)) { + if (FS.flagsToPermissionString(flags) !== "r" || flags & 512) { + return ERRNO_CODES.EISDIR; + } + } + return FS.nodePermissions(node, FS.flagsToPermissionString(flags)); + }, + MAX_OPEN_FDS: 4096, + nextfd: function (fd_start, fd_end) { + fd_start = fd_start || 0; + fd_end = fd_end || FS.MAX_OPEN_FDS; + for (var fd = fd_start; fd <= fd_end; fd++) { + if (!FS.streams[fd]) { + return fd; + } + } + throw new FS.ErrnoError(ERRNO_CODES.EMFILE); + }, + getStream: function (fd) { + return FS.streams[fd]; + }, + createStream: function (stream, fd_start, fd_end) { + if (!FS.FSStream) { + FS.FSStream = function () {}; + FS.FSStream.prototype = {}; + Object.defineProperties(FS.FSStream.prototype, { + object: { + get: function () { + return this.node; + }, + set: function (val) { + this.node = val; + }, + }, + isRead: { + get: function () { + return (this.flags & 2097155) !== 1; + }, + }, + isWrite: { + get: function () { + return (this.flags & 2097155) !== 0; + }, + }, + isAppend: { + get: function () { + return this.flags & 1024; + }, + }, + }); + } + var newStream = new FS.FSStream(); + for (var p in stream) { + newStream[p] = stream[p]; + } + stream = newStream; + var fd = FS.nextfd(fd_start, fd_end); + stream.fd = fd; + FS.streams[fd] = stream; + return stream; + }, + closeStream: function (fd) { + FS.streams[fd] = null; + }, + chrdev_stream_ops: { + open: function (stream) { + var device = FS.getDevice(stream.node.rdev); + stream.stream_ops = device.stream_ops; + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + }, + llseek: function () { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + }, + }, + major: function (dev) { + return dev >> 8; + }, + minor: function (dev) { + return dev & 255; + }, + makedev: function (ma, mi) { + return (ma << 8) | mi; + }, + registerDevice: function (dev, ops) { + FS.devices[dev] = { stream_ops: ops }; + }, + getDevice: function (dev) { + return FS.devices[dev]; + }, + getMounts: function (mount) { + var mounts = []; + var check = [mount]; + while (check.length) { + var m = check.pop(); + mounts.push(m); + check.push.apply(check, m.mounts); + } + return mounts; + }, + syncfs: function (populate, callback) { + if (typeof populate === "function") { + callback = populate; + populate = false; + } + FS.syncFSRequests++; + if (FS.syncFSRequests > 1) { + console.log("warning: " + FS.syncFSRequests + " FS.syncfs operations in flight at once, probably just doing extra work"); + } + var mounts = FS.getMounts(FS.root.mount); + var completed = 0; + function doCallback(err) { + assert(FS.syncFSRequests > 0); + FS.syncFSRequests--; + return callback(err); + } + function done(err) { + if (err) { + if (!done.errored) { + done.errored = true; + return doCallback(err); + } + return; + } + if (++completed >= mounts.length) { + doCallback(null); + } + } + mounts.forEach(function (mount) { + if (!mount.type.syncfs) { + return done(null); + } + mount.type.syncfs(mount, populate, done); + }); + }, + mount: function (type, opts, mountpoint) { + var root = mountpoint === "/"; + var pseudo = !mountpoint; + var node; + if (root && FS.root) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } else if (!root && !pseudo) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + mountpoint = lookup.path; + node = lookup.node; + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + if (!FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + } + var mount = { type: type, opts: opts, mountpoint: mountpoint, mounts: [] }; + var mountRoot = type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + if (root) { + FS.root = mountRoot; + } else if (node) { + node.mounted = mount; + if (node.mount) { + node.mount.mounts.push(mount); + } + } + return mountRoot; + }, + unmount: function (mountpoint) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + if (!FS.isMountpoint(lookup.node)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node = lookup.node; + var mount = node.mounted; + var mounts = FS.getMounts(mount); + Object.keys(FS.nameTable).forEach(function (hash) { + var current = FS.nameTable[hash]; + while (current) { + var next = current.name_next; + if (mounts.indexOf(current.mount) !== -1) { + FS.destroyNode(current); + } + current = next; + } + }); + node.mounted = null; + var idx = node.mount.mounts.indexOf(mount); + assert(idx !== -1); + node.mount.mounts.splice(idx, 1); + }, + lookup: function (parent, name) { + return parent.node_ops.lookup(parent, name); + }, + mknod: function (path, mode, dev) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + if (!name || name === "." || name === "..") { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var err = FS.mayCreate(parent, name); + if (err) { + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.mknod) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + return parent.node_ops.mknod(parent, name, mode, dev); + }, + create: function (path, mode) { + mode = mode !== undefined ? mode : 438; + mode &= 4095; + mode |= 32768; + return FS.mknod(path, mode, 0); + }, + mkdir: function (path, mode) { + mode = mode !== undefined ? mode : 511; + mode &= 511 | 512; + mode |= 16384; + return FS.mknod(path, mode, 0); + }, + mkdirTree: function (path, mode) { + var dirs = path.split("/"); + var d = ""; + for (var i = 0; i < dirs.length; ++i) { + if (!dirs[i]) continue; + d += "/" + dirs[i]; + try { + FS.mkdir(d, mode); + } catch (e) { + if (e.errno != ERRNO_CODES.EEXIST) throw e; + } + } + }, + mkdev: function (path, mode, dev) { + if (typeof dev === "undefined") { + dev = mode; + mode = 438; + } + mode |= 8192; + return FS.mknod(path, mode, dev); + }, + symlink: function (oldpath, newpath) { + if (!PATH.resolve(oldpath)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + var lookup = FS.lookupPath(newpath, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + var newname = PATH.basename(newpath); + var err = FS.mayCreate(parent, newname); + if (err) { + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.symlink) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + return parent.node_ops.symlink(parent, newname, oldpath); + }, + rename: function (old_path, new_path) { + var old_dirname = PATH.dirname(old_path); + var new_dirname = PATH.dirname(new_path); + var old_name = PATH.basename(old_path); + var new_name = PATH.basename(new_path); + var lookup, old_dir, new_dir; + try { + lookup = FS.lookupPath(old_path, { parent: true }); + old_dir = lookup.node; + lookup = FS.lookupPath(new_path, { parent: true }); + new_dir = lookup.node; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + if (!old_dir || !new_dir) throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + if (old_dir.mount !== new_dir.mount) { + throw new FS.ErrnoError(ERRNO_CODES.EXDEV); + } + var old_node = FS.lookupNode(old_dir, old_name); + var relative = PATH.relative(old_path, new_dirname); + if (relative.charAt(0) !== ".") { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + relative = PATH.relative(new_path, old_dirname); + if (relative.charAt(0) !== ".") { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); + } + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (old_node === new_node) { + return; + } + var isdir = FS.isDir(old_node.mode); + var err = FS.mayDelete(old_dir, old_name, isdir); + if (err) { + throw new FS.ErrnoError(err); + } + err = new_node ? FS.mayDelete(new_dir, new_name, isdir) : FS.mayCreate(new_dir, new_name); + if (err) { + throw new FS.ErrnoError(err); + } + if (!old_dir.node_ops.rename) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + if (new_dir !== old_dir) { + err = FS.nodePermissions(old_dir, "w"); + if (err) { + throw new FS.ErrnoError(err); + } + } + try { + if (FS.trackingDelegate["willMovePath"]) { + FS.trackingDelegate["willMovePath"](old_path, new_path); + } + } catch (e) { + console.log("FS.trackingDelegate['willMovePath']('" + old_path + "', '" + new_path + "') threw an exception: " + e.message); + } + FS.hashRemoveNode(old_node); + try { + old_dir.node_ops.rename(old_node, new_dir, new_name); + } catch (e) { + throw e; + } finally { + FS.hashAddNode(old_node); + } + try { + if (FS.trackingDelegate["onMovePath"]) FS.trackingDelegate["onMovePath"](old_path, new_path); + } catch (e) { + console.log("FS.trackingDelegate['onMovePath']('" + old_path + "', '" + new_path + "') threw an exception: " + e.message); + } + }, + rmdir: function (path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var err = FS.mayDelete(parent, name, true); + if (err) { + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.rmdir) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + try { + if (FS.trackingDelegate["willDeletePath"]) { + FS.trackingDelegate["willDeletePath"](path); + } + } catch (e) { + console.log("FS.trackingDelegate['willDeletePath']('" + path + "') threw an exception: " + e.message); + } + parent.node_ops.rmdir(parent, name); + FS.destroyNode(node); + try { + if (FS.trackingDelegate["onDeletePath"]) FS.trackingDelegate["onDeletePath"](path); + } catch (e) { + console.log("FS.trackingDelegate['onDeletePath']('" + path + "') threw an exception: " + e.message); + } + }, + readdir: function (path) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + if (!node.node_ops.readdir) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + return node.node_ops.readdir(node); + }, + unlink: function (path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var err = FS.mayDelete(parent, name, false); + if (err) { + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.unlink) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + try { + if (FS.trackingDelegate["willDeletePath"]) { + FS.trackingDelegate["willDeletePath"](path); + } + } catch (e) { + console.log("FS.trackingDelegate['willDeletePath']('" + path + "') threw an exception: " + e.message); + } + parent.node_ops.unlink(parent, name); + FS.destroyNode(node); + try { + if (FS.trackingDelegate["onDeletePath"]) FS.trackingDelegate["onDeletePath"](path); + } catch (e) { + console.log("FS.trackingDelegate['onDeletePath']('" + path + "') threw an exception: " + e.message); + } + }, + readlink: function (path) { + var lookup = FS.lookupPath(path); + var link = lookup.node; + if (!link) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + if (!link.node_ops.readlink) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return PATH.resolve(FS.getPath(link.parent), link.node_ops.readlink(link)); + }, + stat: function (path, dontFollow) { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + var node = lookup.node; + if (!node) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + if (!node.node_ops.getattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + return node.node_ops.getattr(node); + }, + lstat: function (path) { + return FS.stat(path, true); + }, + chmod: function (path, mode, dontFollow) { + var node; + if (typeof path === "string") { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + node.node_ops.setattr(node, { mode: (mode & 4095) | (node.mode & ~4095), timestamp: Date.now() }); + }, + lchmod: function (path, mode) { + FS.chmod(path, mode, true); + }, + fchmod: function (fd, mode) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + FS.chmod(stream.node, mode); + }, + chown: function (path, uid, gid, dontFollow) { + var node; + if (typeof path === "string") { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + node.node_ops.setattr(node, { timestamp: Date.now() }); + }, + lchown: function (path, uid, gid) { + FS.chown(path, uid, gid, true); + }, + fchown: function (fd, uid, gid) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + FS.chown(stream.node, uid, gid); + }, + truncate: function (path, len) { + if (len < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node; + if (typeof path === "string") { + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!FS.isFile(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var err = FS.nodePermissions(node, "w"); + if (err) { + throw new FS.ErrnoError(err); + } + node.node_ops.setattr(node, { size: len, timestamp: Date.now() }); + }, + ftruncate: function (fd, len) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + FS.truncate(stream.node, len); + }, + utime: function (path, atime, mtime) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + node.node_ops.setattr(node, { timestamp: Math.max(atime, mtime) }); + }, + open: function (path, flags, mode, fd_start, fd_end) { + if (path === "") { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + flags = typeof flags === "string" ? FS.modeStringToFlags(flags) : flags; + mode = typeof mode === "undefined" ? 438 : mode; + if (flags & 64) { + mode = (mode & 4095) | 32768; + } else { + mode = 0; + } + var node; + if (typeof path === "object") { + node = path; + } else { + path = PATH.normalize(path); + try { + var lookup = FS.lookupPath(path, { follow: !(flags & 131072) }); + node = lookup.node; + } catch (e) {} + } + var created = false; + if (flags & 64) { + if (node) { + if (flags & 128) { + throw new FS.ErrnoError(ERRNO_CODES.EEXIST); + } + } else { + node = FS.mknod(path, mode, 0); + created = true; + } + } + if (!node) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + if (FS.isChrdev(node.mode)) { + flags &= ~512; + } + if (flags & 65536 && !FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + if (!created) { + var err = FS.mayOpen(node, flags); + if (err) { + throw new FS.ErrnoError(err); + } + } + if (flags & 512) { + FS.truncate(node, 0); + } + flags &= ~(128 | 512); + var stream = FS.createStream({ node: node, path: FS.getPath(node), flags: flags, seekable: true, position: 0, stream_ops: node.stream_ops, ungotten: [], error: false }, fd_start, fd_end); + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + if (Module["logReadFiles"] && !(flags & 1)) { + if (!FS.readFiles) FS.readFiles = {}; + if (!(path in FS.readFiles)) { + FS.readFiles[path] = 1; + Module["printErr"]("read file: " + path); + } + } + try { + if (FS.trackingDelegate["onOpenFile"]) { + var trackingFlags = 0; + if ((flags & 2097155) !== 1) { + trackingFlags |= FS.tracking.openFlags.READ; + } + if ((flags & 2097155) !== 0) { + trackingFlags |= FS.tracking.openFlags.WRITE; + } + FS.trackingDelegate["onOpenFile"](path, trackingFlags); + } + } catch (e) { + console.log("FS.trackingDelegate['onOpenFile']('" + path + "', flags) threw an exception: " + e.message); + } + return stream; + }, + close: function (stream) { + if (stream.getdents) stream.getdents = null; + try { + if (stream.stream_ops.close) { + stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + FS.closeStream(stream.fd); + } + }, + llseek: function (stream, offset, whence) { + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + stream.position = stream.stream_ops.llseek(stream, offset, whence); + stream.ungotten = []; + return stream.position; + }, + read: function (stream, buffer, offset, length, position) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!stream.stream_ops.read) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var seeking = typeof position !== "undefined"; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write: function (stream, buffer, offset, length, position, canOwn) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!stream.stream_ops.write) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if (stream.flags & 1024) { + FS.llseek(stream, 0, 2); + } + var seeking = typeof position !== "undefined"; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); + if (!seeking) stream.position += bytesWritten; + try { + if (stream.path && FS.trackingDelegate["onWriteToFile"]) FS.trackingDelegate["onWriteToFile"](stream.path); + } catch (e) { + console.log("FS.trackingDelegate['onWriteToFile']('" + path + "') threw an exception: " + e.message); + } + return bytesWritten; + }, + allocate: function (stream, offset, length) { + if (offset < 0 || length <= 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (!FS.isFile(stream.node.mode) && !FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + if (!stream.stream_ops.allocate) { + throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP); + } + stream.stream_ops.allocate(stream, offset, length); + }, + mmap: function (stream, buffer, offset, length, position, prot, flags) { + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(ERRNO_CODES.EACCES); + } + if (!stream.stream_ops.mmap) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags); + }, + msync: function (stream, buffer, offset, length, mmapFlags) { + if (!stream || !stream.stream_ops.msync) { + return 0; + } + return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags); + }, + munmap: function (stream) { + return 0; + }, + ioctl: function (stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTTY); + } + return stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile: function (path, opts) { + opts = opts || {}; + opts.flags = opts.flags || "r"; + opts.encoding = opts.encoding || "binary"; + if (opts.encoding !== "utf8" && opts.encoding !== "binary") { + throw new Error('Invalid encoding type "' + opts.encoding + '"'); + } + var ret; + var stream = FS.open(path, opts.flags); + var stat = FS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + FS.read(stream, buf, 0, length, 0); + if (opts.encoding === "utf8") { + ret = UTF8ArrayToString(buf, 0); + } else if (opts.encoding === "binary") { + ret = buf; + } + FS.close(stream); + return ret; + }, + writeFile: function (path, data, opts) { + opts = opts || {}; + opts.flags = opts.flags || "w"; + var stream = FS.open(path, opts.flags, opts.mode); + if (typeof data === "string") { + var buf = new Uint8Array(lengthBytesUTF8(data) + 1); + var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length); + FS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn); + } else if (ArrayBuffer.isView(data)) { + FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn); + } else { + throw new Error("Unsupported data type"); + } + FS.close(stream); + }, + cwd: function () { + return FS.currentPath; + }, + chdir: function (path) { + var lookup = FS.lookupPath(path, { follow: true }); + if (lookup.node === null) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + if (!FS.isDir(lookup.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + var err = FS.nodePermissions(lookup.node, "x"); + if (err) { + throw new FS.ErrnoError(err); + } + FS.currentPath = lookup.path; + }, + createDefaultDirectories: function () { + FS.mkdir("/tmp"); + FS.mkdir("/home"); + FS.mkdir("/home/web_user"); + }, + createDefaultDevices: function () { + FS.mkdir("/dev"); + FS.registerDevice(FS.makedev(1, 3), { + read: function () { + return 0; + }, + write: function (stream, buffer, offset, length, pos) { + return length; + }, + }); + FS.mkdev("/dev/null", FS.makedev(1, 3)); + TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); + TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); + FS.mkdev("/dev/tty", FS.makedev(5, 0)); + FS.mkdev("/dev/tty1", FS.makedev(6, 0)); + var random_device; + if (typeof crypto !== "undefined") { + var randomBuffer = new Uint8Array(1); + random_device = function () { + crypto.getRandomValues(randomBuffer); + return randomBuffer[0]; + }; + } else if (ENVIRONMENT_IS_NODE) { + random_device = function () { + return require("crypto")["randomBytes"](1)[0]; + }; + } else { + random_device = function () { + return (Math.random() * 256) | 0; + }; + } + FS.createDevice("/dev", "random", random_device); + FS.createDevice("/dev", "urandom", random_device); + FS.mkdir("/dev/shm"); + FS.mkdir("/dev/shm/tmp"); + }, + createSpecialDirectories: function () { + FS.mkdir("/proc"); + FS.mkdir("/proc/self"); + FS.mkdir("/proc/self/fd"); + FS.mount( + { + mount: function () { + var node = FS.createNode("/proc/self", "fd", 16384 | 511, 73); + node.node_ops = { + lookup: function (parent, name) { + var fd = +name; + var stream = FS.getStream(fd); + if (!stream) throw new FS.ErrnoError(ERRNO_CODES.EBADF); + var ret = { + parent: null, + mount: { mountpoint: "fake" }, + node_ops: { + readlink: function () { + return stream.path; + }, + }, + }; + ret.parent = ret; + return ret; + }, + }; + return node; + }, + }, + {}, + "/proc/self/fd" + ); + }, + createStandardStreams: function () { + if (Module["stdin"]) { + FS.createDevice("/dev", "stdin", Module["stdin"]); + } else { + FS.symlink("/dev/tty", "/dev/stdin"); + } + if (Module["stdout"]) { + FS.createDevice("/dev", "stdout", null, Module["stdout"]); + } else { + FS.symlink("/dev/tty", "/dev/stdout"); + } + if (Module["stderr"]) { + FS.createDevice("/dev", "stderr", null, Module["stderr"]); + } else { + FS.symlink("/dev/tty1", "/dev/stderr"); + } + var stdin = FS.open("/dev/stdin", "r"); + assert(stdin.fd === 0, "invalid handle for stdin (" + stdin.fd + ")"); + var stdout = FS.open("/dev/stdout", "w"); + assert(stdout.fd === 1, "invalid handle for stdout (" + stdout.fd + ")"); + var stderr = FS.open("/dev/stderr", "w"); + assert(stderr.fd === 2, "invalid handle for stderr (" + stderr.fd + ")"); + }, + ensureErrnoError: function () { + if (FS.ErrnoError) return; + FS.ErrnoError = function ErrnoError(errno, node) { + this.node = node; + this.setErrno = function (errno) { + this.errno = errno; + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } + }; + this.setErrno(errno); + this.message = ERRNO_MESSAGES[errno]; + if (this.stack) Object.defineProperty(this, "stack", { value: new Error().stack, writable: true }); + }; + FS.ErrnoError.prototype = new Error(); + FS.ErrnoError.prototype.constructor = FS.ErrnoError; + [ERRNO_CODES.ENOENT].forEach(function (code) { + FS.genericErrors[code] = new FS.ErrnoError(code); + FS.genericErrors[code].stack = ""; + }); + }, + staticInit: function () { + FS.ensureErrnoError(); + FS.nameTable = new Array(4096); + FS.mount(MEMFS, {}, "/"); + FS.createDefaultDirectories(); + FS.createDefaultDevices(); + FS.createSpecialDirectories(); + FS.filesystems = { MEMFS: MEMFS, NODEFS: NODEFS }; + }, + init: function (input, output, error) { + assert(!FS.init.initialized, "FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)"); + FS.init.initialized = true; + FS.ensureErrnoError(); + Module["stdin"] = input || Module["stdin"]; + Module["stdout"] = output || Module["stdout"]; + Module["stderr"] = error || Module["stderr"]; + FS.createStandardStreams(); + }, + quit: function () { + FS.init.initialized = false; + var fflush = Module["_fflush"]; + if (fflush) fflush(0); + for (var i = 0; i < FS.streams.length; i++) { + var stream = FS.streams[i]; + if (!stream) { + continue; + } + FS.close(stream); + } + }, + getMode: function (canRead, canWrite) { + var mode = 0; + if (canRead) mode |= 292 | 73; + if (canWrite) mode |= 146; + return mode; + }, + joinPath: function (parts, forceRelative) { + var path = PATH.join.apply(null, parts); + if (forceRelative && path[0] == "/") path = path.substr(1); + return path; + }, + absolutePath: function (relative, base) { + return PATH.resolve(base, relative); + }, + standardizePath: function (path) { + return PATH.normalize(path); + }, + findObject: function (path, dontResolveLastLink) { + var ret = FS.analyzePath(path, dontResolveLastLink); + if (ret.exists) { + return ret.object; + } else { + ___setErrNo(ret.error); + return null; + } + }, + analyzePath: function (path, dontResolveLastLink) { + try { + var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + path = lookup.path; + } catch (e) {} + var ret = { isRoot: false, exists: false, error: 0, name: null, path: null, object: null, parentExists: false, parentPath: null, parentObject: null }; + try { + var lookup = FS.lookupPath(path, { parent: true }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === "/"; + } catch (e) { + ret.error = e.errno; + } + return ret; + }, + createFolder: function (parent, name, canRead, canWrite) { + var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); + var mode = FS.getMode(canRead, canWrite); + return FS.mkdir(path, mode); + }, + createPath: function (parent, path, canRead, canWrite) { + parent = typeof parent === "string" ? parent : FS.getPath(parent); + var parts = path.split("/").reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join2(parent, part); + try { + FS.mkdir(current); + } catch (e) {} + parent = current; + } + return current; + }, + createFile: function (parent, name, properties, canRead, canWrite) { + var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); + var mode = FS.getMode(canRead, canWrite); + return FS.create(path, mode); + }, + createDataFile: function (parent, name, data, canRead, canWrite, canOwn) { + var path = name ? PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name) : parent; + var mode = FS.getMode(canRead, canWrite); + var node = FS.create(path, mode); + if (data) { + if (typeof data === "string") { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); + data = arr; + } + FS.chmod(node, mode | 146); + var stream = FS.open(node, "w"); + FS.write(stream, data, 0, data.length, 0, canOwn); + FS.close(stream); + FS.chmod(node, mode); + } + return node; + }, + createDevice: function (parent, name, input, output) { + var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); + var mode = FS.getMode(!!input, !!output); + if (!FS.createDevice.major) FS.createDevice.major = 64; + var dev = FS.makedev(FS.createDevice.major++, 0); + FS.registerDevice(dev, { + open: function (stream) { + stream.seekable = false; + }, + close: function (stream) { + if (output && output.buffer && output.buffer.length) { + output(10); + } + }, + read: function (stream, buffer, offset, length, pos) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function (stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset + i]); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + }, + }); + return FS.mkdev(path, mode, dev); + }, + createLink: function (parent, name, target, canRead, canWrite) { + var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); + return FS.symlink(target, path); + }, + forceLoadFile: function (obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; + var success = true; + if (typeof XMLHttpRequest !== "undefined") { + throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); + } else if (Module["read"]) { + try { + obj.contents = intArrayFromString(Module["read"](obj.url), true); + obj.usedBytes = obj.contents.length; + } catch (e) { + success = false; + } + } else { + throw new Error("Cannot load without read() or XMLHttpRequest."); + } + if (!success) ___setErrNo(ERRNO_CODES.EIO); + return success; + }, + createLazyFile: function (parent, name, url, canRead, canWrite) { + function LazyUint8Array() { + this.lengthKnown = false; + this.chunks = []; + } + LazyUint8Array.prototype.get = function LazyUint8Array_get(idx) { + if (idx > this.length - 1 || idx < 0) { + return undefined; + } + var chunkOffset = idx % this.chunkSize; + var chunkNum = (idx / this.chunkSize) | 0; + return this.getter(chunkNum)[chunkOffset]; + }; + LazyUint8Array.prototype.setDataGetter = function LazyUint8Array_setDataGetter(getter) { + this.getter = getter; + }; + LazyUint8Array.prototype.cacheLength = function LazyUint8Array_cacheLength() { + var xhr = new XMLHttpRequest(); + xhr.open("HEAD", url, false); + xhr.send(null); + if (!((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); + var datalength = Number(xhr.getResponseHeader("Content-length")); + var header; + var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; + var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; + var chunkSize = 1024 * 1024; + if (!hasByteServing) chunkSize = datalength; + var doXHR = function (from, to) { + if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); + if (to > datalength - 1) throw new Error("only " + datalength + " bytes available! programmer error!"); + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); + if (typeof Uint8Array != "undefined") xhr.responseType = "arraybuffer"; + if (xhr.overrideMimeType) { + xhr.overrideMimeType("text/plain; charset=x-user-defined"); + } + xhr.send(null); + if (!((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); + if (xhr.response !== undefined) { + return new Uint8Array(xhr.response || []); + } else { + return intArrayFromString(xhr.responseText || "", true); + } + }; + var lazyArray = this; + lazyArray.setDataGetter(function (chunkNum) { + var start = chunkNum * chunkSize; + var end = (chunkNum + 1) * chunkSize - 1; + end = Math.min(end, datalength - 1); + if (typeof lazyArray.chunks[chunkNum] === "undefined") { + lazyArray.chunks[chunkNum] = doXHR(start, end); + } + if (typeof lazyArray.chunks[chunkNum] === "undefined") throw new Error("doXHR failed!"); + return lazyArray.chunks[chunkNum]; + }); + if (usesGzip || !datalength) { + chunkSize = datalength = 1; + datalength = this.getter(0).length; + chunkSize = datalength; + console.log("LazyFiles on gzip forces download of the whole file when length is accessed"); + } + this._length = datalength; + this._chunkSize = chunkSize; + this.lengthKnown = true; + }; + if (typeof XMLHttpRequest !== "undefined") { + if (!ENVIRONMENT_IS_WORKER) throw "Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc"; + var lazyArray = new LazyUint8Array(); + Object.defineProperties(lazyArray, { + length: { + get: function () { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._length; + }, + }, + chunkSize: { + get: function () { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._chunkSize; + }, + }, + }); + var properties = { isDevice: false, contents: lazyArray }; + } else { + var properties = { isDevice: false, url: url }; + } + var node = FS.createFile(parent, name, properties, canRead, canWrite); + if (properties.contents) { + node.contents = properties.contents; + } else if (properties.url) { + node.contents = null; + node.url = properties.url; + } + Object.defineProperties(node, { + usedBytes: { + get: function () { + return this.contents.length; + }, + }, + }); + var stream_ops = {}; + var keys = Object.keys(node.stream_ops); + keys.forEach(function (key) { + var fn = node.stream_ops[key]; + stream_ops[key] = function forceLoadLazyFile() { + if (!FS.forceLoadFile(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + return fn.apply(null, arguments); + }; + }); + stream_ops.read = function stream_ops_read(stream, buffer, offset, length, position) { + if (!FS.forceLoadFile(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + var contents = stream.node.contents; + if (position >= contents.length) return 0; + var size = Math.min(contents.length - position, length); + assert(size >= 0); + if (contents.slice) { + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents[position + i]; + } + } else { + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents.get(position + i); + } + } + return size; + }; + node.stream_ops = stream_ops; + return node; + }, + createPreloadedFile: function (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) { + Browser.init(); + var fullname = name ? PATH.resolve(PATH.join2(parent, name)) : parent; + var dep = getUniqueRunDependency("cp " + fullname); + function processData(byteArray) { + function finish(byteArray) { + if (preFinish) preFinish(); + if (!dontCreateFile) { + FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn); + } + if (onload) onload(); + removeRunDependency(dep); + } + var handled = false; + Module["preloadPlugins"].forEach(function (plugin) { + if (handled) return; + if (plugin["canHandle"](fullname)) { + plugin["handle"](byteArray, fullname, finish, function () { + if (onerror) onerror(); + removeRunDependency(dep); + }); + handled = true; + } + }); + if (!handled) finish(byteArray); + } + addRunDependency(dep); + if (typeof url == "string") { + Browser.asyncLoad( + url, + function (byteArray) { + processData(byteArray); + }, + onerror + ); + } else { + processData(url); + } + }, + indexedDB: function () { + return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + }, + DB_NAME: function () { + return "EM_FS_" + window.location.pathname; + }, + DB_VERSION: 20, + DB_STORE_NAME: "FILE_DATA", + saveFilesToDB: function (paths, onload, onerror) { + onload = onload || function () {}; + onerror = onerror || function () {}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = function openRequest_onupgradeneeded() { + console.log("creating db"); + var db = openRequest.result; + db.createObjectStore(FS.DB_STORE_NAME); + }; + openRequest.onsuccess = function openRequest_onsuccess() { + var db = openRequest.result; + var transaction = db.transaction([FS.DB_STORE_NAME], "readwrite"); + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, + fail = 0, + total = paths.length; + function finish() { + if (fail == 0) onload(); + else onerror(); + } + paths.forEach(function (path) { + var putRequest = files.put(FS.analyzePath(path).object.contents, path); + putRequest.onsuccess = function putRequest_onsuccess() { + ok++; + if (ok + fail == total) finish(); + }; + putRequest.onerror = function putRequest_onerror() { + fail++; + if (ok + fail == total) finish(); + }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; + }, + loadFilesFromDB: function (paths, onload, onerror) { + onload = onload || function () {}; + onerror = onerror || function () {}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = onerror; + openRequest.onsuccess = function openRequest_onsuccess() { + var db = openRequest.result; + try { + var transaction = db.transaction([FS.DB_STORE_NAME], "readonly"); + } catch (e) { + onerror(e); + return; + } + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, + fail = 0, + total = paths.length; + function finish() { + if (fail == 0) onload(); + else onerror(); + } + paths.forEach(function (path) { + var getRequest = files.get(path); + getRequest.onsuccess = function getRequest_onsuccess() { + if (FS.analyzePath(path).exists) { + FS.unlink(path); + } + FS.createDataFile(PATH.dirname(path), PATH.basename(path), getRequest.result, true, true, true); + ok++; + if (ok + fail == total) finish(); + }; + getRequest.onerror = function getRequest_onerror() { + fail++; + if (ok + fail == total) finish(); + }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; + }, + }; + var SYSCALLS = { + DEFAULT_POLLMASK: 5, + mappings: {}, + umask: 511, + calculateAt: function (dirfd, path) { + if (path[0] !== "/") { + var dir; + if (dirfd === -100) { + dir = FS.cwd(); + } else { + var dirstream = FS.getStream(dirfd); + if (!dirstream) throw new FS.ErrnoError(ERRNO_CODES.EBADF); + dir = dirstream.path; + } + path = PATH.join2(dir, path); + } + return path; + }, + doStat: function (func, path, buf) { + try { + var stat = func(path); + } catch (e) { + if (e && e.node && PATH.normalize(path) !== PATH.normalize(FS.getPath(e.node))) { + return -ERRNO_CODES.ENOTDIR; + } + throw e; + } + HEAP32[buf >> 2] = stat.dev; + HEAP32[(buf + 4) >> 2] = 0; + HEAP32[(buf + 8) >> 2] = stat.ino; + HEAP32[(buf + 12) >> 2] = stat.mode; + HEAP32[(buf + 16) >> 2] = stat.nlink; + HEAP32[(buf + 20) >> 2] = stat.uid; + HEAP32[(buf + 24) >> 2] = stat.gid; + HEAP32[(buf + 28) >> 2] = stat.rdev; + HEAP32[(buf + 32) >> 2] = 0; + HEAP32[(buf + 36) >> 2] = stat.size; + HEAP32[(buf + 40) >> 2] = 4096; + HEAP32[(buf + 44) >> 2] = stat.blocks; + HEAP32[(buf + 48) >> 2] = (stat.atime.getTime() / 1e3) | 0; + HEAP32[(buf + 52) >> 2] = 0; + HEAP32[(buf + 56) >> 2] = (stat.mtime.getTime() / 1e3) | 0; + HEAP32[(buf + 60) >> 2] = 0; + HEAP32[(buf + 64) >> 2] = (stat.ctime.getTime() / 1e3) | 0; + HEAP32[(buf + 68) >> 2] = 0; + HEAP32[(buf + 72) >> 2] = stat.ino; + return 0; + }, + doMsync: function (addr, stream, len, flags) { + var buffer = new Uint8Array(HEAPU8.subarray(addr, addr + len)); + FS.msync(stream, buffer, 0, len, flags); + }, + doMkdir: function (path, mode) { + path = PATH.normalize(path); + if (path[path.length - 1] === "/") path = path.substr(0, path.length - 1); + FS.mkdir(path, mode, 0); + return 0; + }, + doMknod: function (path, mode, dev) { + switch (mode & 61440) { + case 32768: + case 8192: + case 24576: + case 4096: + case 49152: + break; + default: + return -ERRNO_CODES.EINVAL; + } + FS.mknod(path, mode, dev); + return 0; + }, + doReadlink: function (path, buf, bufsize) { + if (bufsize <= 0) return -ERRNO_CODES.EINVAL; + var ret = FS.readlink(path); + var len = Math.min(bufsize, lengthBytesUTF8(ret)); + var endChar = HEAP8[buf + len]; + stringToUTF8(ret, buf, bufsize + 1); + HEAP8[buf + len] = endChar; + return len; + }, + doAccess: function (path, amode) { + if (amode & ~7) { + return -ERRNO_CODES.EINVAL; + } + var node; + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + var perms = ""; + if (amode & 4) perms += "r"; + if (amode & 2) perms += "w"; + if (amode & 1) perms += "x"; + if (perms && FS.nodePermissions(node, perms)) { + return -ERRNO_CODES.EACCES; + } + return 0; + }, + doDup: function (path, flags, suggestFD) { + var suggest = FS.getStream(suggestFD); + if (suggest) FS.close(suggest); + return FS.open(path, flags, 0, suggestFD, suggestFD).fd; + }, + doReadv: function (stream, iov, iovcnt, offset) { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAP32[(iov + i * 8) >> 2]; + var len = HEAP32[(iov + (i * 8 + 4)) >> 2]; + var curr = FS.read(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) break; + } + return ret; + }, + doWritev: function (stream, iov, iovcnt, offset) { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAP32[(iov + i * 8) >> 2]; + var len = HEAP32[(iov + (i * 8 + 4)) >> 2]; + var curr = FS.write(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + } + return ret; + }, + varargs: 0, + get: function (varargs) { + SYSCALLS.varargs += 4; + var ret = HEAP32[(SYSCALLS.varargs - 4) >> 2]; + return ret; + }, + getStr: function () { + var ret = Pointer_stringify(SYSCALLS.get()); + return ret; + }, + getStreamFromFD: function () { + var stream = FS.getStream(SYSCALLS.get()); + if (!stream) throw new FS.ErrnoError(ERRNO_CODES.EBADF); + return stream; + }, + getSocketFromFD: function () { + var socket = SOCKFS.getSocket(SYSCALLS.get()); + if (!socket) throw new FS.ErrnoError(ERRNO_CODES.EBADF); + return socket; + }, + getSocketAddress: function (allowNull) { + var addrp = SYSCALLS.get(), + addrlen = SYSCALLS.get(); + if (allowNull && addrp === 0) return null; + var info = __read_sockaddr(addrp, addrlen); + if (info.errno) throw new FS.ErrnoError(info.errno); + info.addr = DNS.lookup_addr(info.addr) || info.addr; + return info; + }, + get64: function () { + var low = SYSCALLS.get(), + high = SYSCALLS.get(); + if (low >= 0) assert(high === 0); + else assert(high === -1); + return low; + }, + getZero: function () { + assert(SYSCALLS.get() === 0); + }, + }; + function ___syscall140(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(), + offset_high = SYSCALLS.get(), + offset_low = SYSCALLS.get(), + result = SYSCALLS.get(), + whence = SYSCALLS.get(); + var offset = offset_low; + FS.llseek(stream, offset, whence); + HEAP32[result >> 2] = stream.position; + if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; + return 0; + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall145(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(), + iov = SYSCALLS.get(), + iovcnt = SYSCALLS.get(); + return SYSCALLS.doReadv(stream, iov, iovcnt); + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall146(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(), + iov = SYSCALLS.get(), + iovcnt = SYSCALLS.get(); + return SYSCALLS.doWritev(stream, iov, iovcnt); + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall195(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var path = SYSCALLS.getStr(), + buf = SYSCALLS.get(); + return SYSCALLS.doStat(FS.stat, path, buf); + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall221(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(), + cmd = SYSCALLS.get(); + switch (cmd) { + case 0: { + var arg = SYSCALLS.get(); + if (arg < 0) { + return -ERRNO_CODES.EINVAL; + } + var newStream; + newStream = FS.open(stream.path, stream.flags, 0, arg); + return newStream.fd; + } + case 1: + case 2: + return 0; + case 3: + return stream.flags; + case 4: { + var arg = SYSCALLS.get(); + stream.flags |= arg; + return 0; + } + case 12: + case 12: { + var arg = SYSCALLS.get(); + var offset = 0; + HEAP16[(arg + offset) >> 1] = 2; + return 0; + } + case 13: + case 14: + case 13: + case 14: + return 0; + case 16: + case 8: + return -ERRNO_CODES.EINVAL; + case 9: + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + default: { + return -ERRNO_CODES.EINVAL; + } + } + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall340(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var pid = SYSCALLS.get(), + resource = SYSCALLS.get(), + new_limit = SYSCALLS.get(), + old_limit = SYSCALLS.get(); + if (old_limit) { + HEAP32[old_limit >> 2] = -1; + HEAP32[(old_limit + 4) >> 2] = -1; + HEAP32[(old_limit + 8) >> 2] = -1; + HEAP32[(old_limit + 12) >> 2] = -1; + } + return 0; + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall5(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var pathname = SYSCALLS.getStr(), + flags = SYSCALLS.get(), + mode = SYSCALLS.get(); + var stream = FS.open(pathname, flags, mode); + return stream.fd; + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall54(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(), + op = SYSCALLS.get(); + switch (op) { + case 21509: + case 21505: { + if (!stream.tty) return -ERRNO_CODES.ENOTTY; + return 0; + } + case 21510: + case 21511: + case 21512: + case 21506: + case 21507: + case 21508: { + if (!stream.tty) return -ERRNO_CODES.ENOTTY; + return 0; + } + case 21519: { + if (!stream.tty) return -ERRNO_CODES.ENOTTY; + var argp = SYSCALLS.get(); + HEAP32[argp >> 2] = 0; + return 0; + } + case 21520: { + if (!stream.tty) return -ERRNO_CODES.ENOTTY; + return -ERRNO_CODES.EINVAL; + } + case 21531: { + var argp = SYSCALLS.get(); + return FS.ioctl(stream, op, argp); + } + case 21523: { + if (!stream.tty) return -ERRNO_CODES.ENOTTY; + return 0; + } + default: + abort("bad ioctl syscall " + op); + } + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall6(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(); + FS.close(stream); + return 0; + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall75(which, varargs) { + SYSCALLS.varargs = varargs; + try { + return 0; + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___syscall91(which, varargs) { + SYSCALLS.varargs = varargs; + try { + var addr = SYSCALLS.get(), + len = SYSCALLS.get(); + var info = SYSCALLS.mappings[addr]; + if (!info) return 0; + if (len === info.len) { + var stream = FS.getStream(info.fd); + SYSCALLS.doMsync(addr, stream, len, info.flags); + FS.munmap(stream); + SYSCALLS.mappings[addr] = null; + if (info.allocated) { + _free(info.malloc); + } + } + return 0; + } catch (e) { + if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + } + function ___unlock() {} + function _abort() { + Module["abort"](); + } + function _clock() { + if (_clock.start === undefined) _clock.start = Date.now(); + return ((Date.now() - _clock.start) * (1e6 / 1e3)) | 0; + } + function _emscripten_get_now() { + abort(); + } + function _emscripten_get_now_is_monotonic() { + return ENVIRONMENT_IS_NODE || typeof dateNow !== "undefined" || ((ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) && self["performance"] && self["performance"]["now"]); + } + function _clock_gettime(clk_id, tp) { + var now; + if (clk_id === 0) { + now = Date.now(); + } else if (clk_id === 1 && _emscripten_get_now_is_monotonic()) { + now = _emscripten_get_now(); + } else { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + HEAP32[tp >> 2] = (now / 1e3) | 0; + HEAP32[(tp + 4) >> 2] = ((now % 1e3) * 1e3 * 1e3) | 0; + return 0; + } + var _environ = STATICTOP; + STATICTOP += 16; + function ___buildEnvironment(env) { + var MAX_ENV_VALUES = 64; + var TOTAL_ENV_SIZE = 1024; + var poolPtr; + var envPtr; + if (!___buildEnvironment.called) { + ___buildEnvironment.called = true; + ENV["USER"] = ENV["LOGNAME"] = "web_user"; + ENV["PATH"] = "/"; + ENV["PWD"] = "/"; + ENV["HOME"] = "/home/web_user"; + ENV["LANG"] = "C.UTF-8"; + ENV["_"] = Module["thisProgram"]; + poolPtr = staticAlloc(TOTAL_ENV_SIZE); + envPtr = staticAlloc(MAX_ENV_VALUES * 4); + HEAP32[envPtr >> 2] = poolPtr; + HEAP32[_environ >> 2] = envPtr; + } else { + envPtr = HEAP32[_environ >> 2]; + poolPtr = HEAP32[envPtr >> 2]; + } + var strings = []; + var totalSize = 0; + for (var key in env) { + if (typeof env[key] === "string") { + var line = key + "=" + env[key]; + strings.push(line); + totalSize += line.length; + } + } + if (totalSize > TOTAL_ENV_SIZE) { + throw new Error("Environment size exceeded TOTAL_ENV_SIZE!"); + } + var ptrSize = 4; + for (var i = 0; i < strings.length; i++) { + var line = strings[i]; + writeAsciiToMemory(line, poolPtr); + HEAP32[(envPtr + i * ptrSize) >> 2] = poolPtr; + poolPtr += line.length + 1; + } + HEAP32[(envPtr + strings.length * ptrSize) >> 2] = 0; + } + var ENV = {}; + function _getenv(name) { + if (name === 0) return 0; + name = Pointer_stringify(name); + if (!ENV.hasOwnProperty(name)) return 0; + if (_getenv.ret) _free(_getenv.ret); + _getenv.ret = allocateUTF8(ENV[name]); + return _getenv.ret; + } + var cttz_i8 = allocate( + [ + 8, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 6, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 7, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 6, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + ], + "i8", + ALLOC_STATIC + ); + function _llvm_eh_typeid_for(type) { + return type; + } + function _llvm_exp2_f32(x) { + return Math.pow(2, x); + } + function _llvm_exp2_f64() { + return _llvm_exp2_f32.apply(null, arguments); + } + function _llvm_trap() { + abort("trap!"); + } + function _emscripten_memcpy_big(dest, src, num) { + HEAPU8.set(HEAPU8.subarray(src, src + num), dest); + return dest; + } + function _pthread_cond_destroy() { + return 0; + } + function _pthread_cond_init() { + return 0; + } + function _pthread_cond_signal() { + return 0; + } + function _pthread_cond_timedwait() { + return 0; + } + function _pthread_cond_wait() { + return 0; + } + function _pthread_create() { + return 11; + } + var PTHREAD_SPECIFIC = {}; + function _pthread_getspecific(key) { + return PTHREAD_SPECIFIC[key] || 0; + } + function _pthread_join() {} + var PTHREAD_SPECIFIC_NEXT_KEY = 1; + function _pthread_key_create(key, destructor) { + if (key == 0) { + return ERRNO_CODES.EINVAL; + } + HEAP32[key >> 2] = PTHREAD_SPECIFIC_NEXT_KEY; + PTHREAD_SPECIFIC[PTHREAD_SPECIFIC_NEXT_KEY] = 0; + PTHREAD_SPECIFIC_NEXT_KEY++; + return 0; + } + function _pthread_mutex_destroy() {} + function _pthread_mutex_init() {} + function _pthread_once(ptr, func) { + if (!_pthread_once.seen) _pthread_once.seen = {}; + if (ptr in _pthread_once.seen) return; + Module["dynCall_v"](func); + _pthread_once.seen[ptr] = 1; + } + function _pthread_setspecific(key, value) { + if (!(key in PTHREAD_SPECIFIC)) { + return ERRNO_CODES.EINVAL; + } + PTHREAD_SPECIFIC[key] = value; + return 0; + } + function _raise(sig) { + ___setErrNo(ERRNO_CODES.ENOSYS); + return -1; + } + function _sched_yield() { + return 0; + } + var __sigalrm_handler = 0; + function _signal(sig, func) { + if (sig == 14) { + __sigalrm_handler = func; + } else { + } + return 0; + } + function __isLeapYear(year) { + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); + } + function __arraySum(array, index) { + var sum = 0; + for (var i = 0; i <= index; sum += array[i++]); + return sum; + } + var __MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var __MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function __addDays(date, days) { + var newDate = new Date(date.getTime()); + while (days > 0) { + var leap = __isLeapYear(newDate.getFullYear()); + var currentMonth = newDate.getMonth(); + var daysInCurrentMonth = (leap ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR)[currentMonth]; + if (days > daysInCurrentMonth - newDate.getDate()) { + days -= daysInCurrentMonth - newDate.getDate() + 1; + newDate.setDate(1); + if (currentMonth < 11) { + newDate.setMonth(currentMonth + 1); + } else { + newDate.setMonth(0); + newDate.setFullYear(newDate.getFullYear() + 1); + } + } else { + newDate.setDate(newDate.getDate() + days); + return newDate; + } + } + return newDate; + } + function _strftime(s, maxsize, format, tm) { + var tm_zone = HEAP32[(tm + 40) >> 2]; + var date = { + tm_sec: HEAP32[tm >> 2], + tm_min: HEAP32[(tm + 4) >> 2], + tm_hour: HEAP32[(tm + 8) >> 2], + tm_mday: HEAP32[(tm + 12) >> 2], + tm_mon: HEAP32[(tm + 16) >> 2], + tm_year: HEAP32[(tm + 20) >> 2], + tm_wday: HEAP32[(tm + 24) >> 2], + tm_yday: HEAP32[(tm + 28) >> 2], + tm_isdst: HEAP32[(tm + 32) >> 2], + tm_gmtoff: HEAP32[(tm + 36) >> 2], + tm_zone: tm_zone ? Pointer_stringify(tm_zone) : "", + }; + var pattern = Pointer_stringify(format); + var EXPANSION_RULES_1 = { "%c": "%a %b %d %H:%M:%S %Y", "%D": "%m/%d/%y", "%F": "%Y-%m-%d", "%h": "%b", "%r": "%I:%M:%S %p", "%R": "%H:%M", "%T": "%H:%M:%S", "%x": "%m/%d/%y", "%X": "%H:%M:%S" }; + for (var rule in EXPANSION_RULES_1) { + pattern = pattern.replace(new RegExp(rule, "g"), EXPANSION_RULES_1[rule]); + } + var WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + function leadingSomething(value, digits, character) { + var str = typeof value === "number" ? value.toString() : value || ""; + while (str.length < digits) { + str = character[0] + str; + } + return str; + } + function leadingNulls(value, digits) { + return leadingSomething(value, digits, "0"); + } + function compareByDay(date1, date2) { + function sgn(value) { + return value < 0 ? -1 : value > 0 ? 1 : 0; + } + var compare; + if ((compare = sgn(date1.getFullYear() - date2.getFullYear())) === 0) { + if ((compare = sgn(date1.getMonth() - date2.getMonth())) === 0) { + compare = sgn(date1.getDate() - date2.getDate()); + } + } + return compare; + } + function getFirstWeekStartDate(janFourth) { + switch (janFourth.getDay()) { + case 0: + return new Date(janFourth.getFullYear() - 1, 11, 29); + case 1: + return janFourth; + case 2: + return new Date(janFourth.getFullYear(), 0, 3); + case 3: + return new Date(janFourth.getFullYear(), 0, 2); + case 4: + return new Date(janFourth.getFullYear(), 0, 1); + case 5: + return new Date(janFourth.getFullYear() - 1, 11, 31); + case 6: + return new Date(janFourth.getFullYear() - 1, 11, 30); + } + } + function getWeekBasedYear(date) { + var thisDate = __addDays(new Date(date.tm_year + 1900, 0, 1), date.tm_yday); + var janFourthThisYear = new Date(thisDate.getFullYear(), 0, 4); + var janFourthNextYear = new Date(thisDate.getFullYear() + 1, 0, 4); + var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear); + var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear); + if (compareByDay(firstWeekStartThisYear, thisDate) <= 0) { + if (compareByDay(firstWeekStartNextYear, thisDate) <= 0) { + return thisDate.getFullYear() + 1; + } else { + return thisDate.getFullYear(); + } + } else { + return thisDate.getFullYear() - 1; + } + } + var EXPANSION_RULES_2 = { + "%a": function (date) { + return WEEKDAYS[date.tm_wday].substring(0, 3); + }, + "%A": function (date) { + return WEEKDAYS[date.tm_wday]; + }, + "%b": function (date) { + return MONTHS[date.tm_mon].substring(0, 3); + }, + "%B": function (date) { + return MONTHS[date.tm_mon]; + }, + "%C": function (date) { + var year = date.tm_year + 1900; + return leadingNulls((year / 100) | 0, 2); + }, + "%d": function (date) { + return leadingNulls(date.tm_mday, 2); + }, + "%e": function (date) { + return leadingSomething(date.tm_mday, 2, " "); + }, + "%g": function (date) { + return getWeekBasedYear(date).toString().substring(2); + }, + "%G": function (date) { + return getWeekBasedYear(date); + }, + "%H": function (date) { + return leadingNulls(date.tm_hour, 2); + }, + "%I": function (date) { + var twelveHour = date.tm_hour; + if (twelveHour == 0) twelveHour = 12; + else if (twelveHour > 12) twelveHour -= 12; + return leadingNulls(twelveHour, 2); + }, + "%j": function (date) { + return leadingNulls(date.tm_mday + __arraySum(__isLeapYear(date.tm_year + 1900) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, date.tm_mon - 1), 3); + }, + "%m": function (date) { + return leadingNulls(date.tm_mon + 1, 2); + }, + "%M": function (date) { + return leadingNulls(date.tm_min, 2); + }, + "%n": function () { + return "\n"; + }, + "%p": function (date) { + if (date.tm_hour >= 0 && date.tm_hour < 12) { + return "AM"; + } else { + return "PM"; + } + }, + "%S": function (date) { + return leadingNulls(date.tm_sec, 2); + }, + "%t": function () { + return "\t"; + }, + "%u": function (date) { + var day = new Date(date.tm_year + 1900, date.tm_mon + 1, date.tm_mday, 0, 0, 0, 0); + return day.getDay() || 7; + }, + "%U": function (date) { + var janFirst = new Date(date.tm_year + 1900, 0, 1); + var firstSunday = janFirst.getDay() === 0 ? janFirst : __addDays(janFirst, 7 - janFirst.getDay()); + var endDate = new Date(date.tm_year + 1900, date.tm_mon, date.tm_mday); + if (compareByDay(firstSunday, endDate) < 0) { + var februaryFirstUntilEndMonth = __arraySum(__isLeapYear(endDate.getFullYear()) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, endDate.getMonth() - 1) - 31; + var firstSundayUntilEndJanuary = 31 - firstSunday.getDate(); + var days = firstSundayUntilEndJanuary + februaryFirstUntilEndMonth + endDate.getDate(); + return leadingNulls(Math.ceil(days / 7), 2); + } + return compareByDay(firstSunday, janFirst) === 0 ? "01" : "00"; + }, + "%V": function (date) { + var janFourthThisYear = new Date(date.tm_year + 1900, 0, 4); + var janFourthNextYear = new Date(date.tm_year + 1901, 0, 4); + var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear); + var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear); + var endDate = __addDays(new Date(date.tm_year + 1900, 0, 1), date.tm_yday); + if (compareByDay(endDate, firstWeekStartThisYear) < 0) { + return "53"; + } + if (compareByDay(firstWeekStartNextYear, endDate) <= 0) { + return "01"; + } + var daysDifference; + if (firstWeekStartThisYear.getFullYear() < date.tm_year + 1900) { + daysDifference = date.tm_yday + 32 - firstWeekStartThisYear.getDate(); + } else { + daysDifference = date.tm_yday + 1 - firstWeekStartThisYear.getDate(); + } + return leadingNulls(Math.ceil(daysDifference / 7), 2); + }, + "%w": function (date) { + var day = new Date(date.tm_year + 1900, date.tm_mon + 1, date.tm_mday, 0, 0, 0, 0); + return day.getDay(); + }, + "%W": function (date) { + var janFirst = new Date(date.tm_year, 0, 1); + var firstMonday = janFirst.getDay() === 1 ? janFirst : __addDays(janFirst, janFirst.getDay() === 0 ? 1 : 7 - janFirst.getDay() + 1); + var endDate = new Date(date.tm_year + 1900, date.tm_mon, date.tm_mday); + if (compareByDay(firstMonday, endDate) < 0) { + var februaryFirstUntilEndMonth = __arraySum(__isLeapYear(endDate.getFullYear()) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, endDate.getMonth() - 1) - 31; + var firstMondayUntilEndJanuary = 31 - firstMonday.getDate(); + var days = firstMondayUntilEndJanuary + februaryFirstUntilEndMonth + endDate.getDate(); + return leadingNulls(Math.ceil(days / 7), 2); + } + return compareByDay(firstMonday, janFirst) === 0 ? "01" : "00"; + }, + "%y": function (date) { + return (date.tm_year + 1900).toString().substring(2); + }, + "%Y": function (date) { + return date.tm_year + 1900; + }, + "%z": function (date) { + var off = date.tm_gmtoff; + var ahead = off >= 0; + off = Math.abs(off) / 60; + off = (off / 60) * 100 + (off % 60); + return (ahead ? "+" : "-") + String("0000" + off).slice(-4); + }, + "%Z": function (date) { + return date.tm_zone; + }, + "%%": function () { + return "%"; + }, + }; + for (var rule in EXPANSION_RULES_2) { + if (pattern.indexOf(rule) >= 0) { + pattern = pattern.replace(new RegExp(rule, "g"), EXPANSION_RULES_2[rule](date)); + } + } + var bytes = intArrayFromString(pattern, false); + if (bytes.length > maxsize) { + return 0; + } + writeArrayToMemory(bytes, s); + return bytes.length - 1; + } + function _strftime_l(s, maxsize, format, tm) { + return _strftime(s, maxsize, format, tm); + } + FS.staticInit(); + __ATINIT__.unshift(function () { + if (!Module["noFSInit"] && !FS.init.initialized) FS.init(); + }); + __ATMAIN__.push(function () { + FS.ignorePermissions = false; + }); + __ATEXIT__.push(function () { + FS.quit(); + }); + __ATINIT__.unshift(function () { + TTY.init(); + }); + __ATEXIT__.push(function () { + TTY.shutdown(); + }); + if (ENVIRONMENT_IS_NODE) { + var fs = require("fs"); + var NODEJS_PATH = require("path"); + NODEFS.staticInit(); + } + if (ENVIRONMENT_IS_NODE) { + _emscripten_get_now = function _emscripten_get_now_actual() { + var t = process["hrtime"](); + return t[0] * 1e3 + t[1] / 1e6; + }; + } else if (typeof dateNow !== "undefined") { + _emscripten_get_now = dateNow; + } else if (typeof self === "object" && self["performance"] && typeof self["performance"]["now"] === "function") { + _emscripten_get_now = function () { + return self["performance"]["now"](); + }; + } else if (typeof performance === "object" && typeof performance["now"] === "function") { + _emscripten_get_now = function () { + return performance["now"](); + }; + } else { + _emscripten_get_now = Date.now; + } + ___buildEnvironment(ENV); + DYNAMICTOP_PTR = staticAlloc(4); + STACK_BASE = STACKTOP = alignMemory(STATICTOP); + STACK_MAX = STACK_BASE + TOTAL_STACK; + DYNAMIC_BASE = alignMemory(STACK_MAX); + HEAP32[DYNAMICTOP_PTR >> 2] = DYNAMIC_BASE; + staticSealed = true; + function intArrayFromString(stringy, dontAddNull, length) { + var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; + var u8array = new Array(len); + var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); + if (dontAddNull) u8array.length = numBytesWritten; + return u8array; + } + Module["wasmTableSize"] = 23600; + Module["wasmMaxTableSize"] = 23600; + function invoke_di(index, a1) { + try { + return Module["dynCall_di"](index, a1); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_dii(index, a1, a2) { + try { + return Module["dynCall_dii"](index, a1, a2); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_diid(index, a1, a2, a3) { + try { + return Module["dynCall_diid"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_diii(index, a1, a2, a3) { + try { + return Module["dynCall_diii"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_diiid(index, a1, a2, a3, a4) { + try { + return Module["dynCall_diiid"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_fii(index, a1, a2) { + try { + return Module["dynCall_fii"](index, a1, a2); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_fiii(index, a1, a2, a3) { + try { + return Module["dynCall_fiii"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_i(index) { + try { + return Module["dynCall_i"](index); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_id(index, a1) { + try { + return Module["dynCall_id"](index, a1); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_ii(index, a1) { + try { + return Module["dynCall_ii"](index, a1); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iid(index, a1, a2) { + try { + return Module["dynCall_iid"](index, a1, a2); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iii(index, a1, a2) { + try { + return Module["dynCall_iii"](index, a1, a2); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiid(index, a1, a2, a3) { + try { + return Module["dynCall_iiid"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiii(index, a1, a2, a3) { + try { + return Module["dynCall_iiii"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiif(index, a1, a2, a3, a4) { + try { + return Module["dynCall_iiiif"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiii(index, a1, a2, a3, a4) { + try { + return Module["dynCall_iiiii"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiid(index, a1, a2, a3, a4, a5) { + try { + return Module["dynCall_iiiiid"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiii(index, a1, a2, a3, a4, a5) { + try { + return Module["dynCall_iiiiii"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiii(index, a1, a2, a3, a4, a5, a6) { + try { + return Module["dynCall_iiiiiii"](index, a1, a2, a3, a4, a5, a6); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiiii(index, a1, a2, a3, a4, a5, a6, a7) { + try { + return Module["dynCall_iiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8) { + try { + return Module["dynCall_iiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) { + try { + return Module["dynCall_iiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + try { + return Module["dynCall_iiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) { + try { + return Module["dynCall_iiiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) { + try { + return Module["dynCall_iiiiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) { + try { + return Module["dynCall_iiiiiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiiij(index, a1, a2, a3, a4, a5, a6) { + try { + return Module["dynCall_iiiiij"](index, a1, a2, a3, a4, a5, a6); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiij(index, a1, a2, a3, a4) { + try { + return Module["dynCall_iiij"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiiji(index, a1, a2, a3, a4, a5) { + try { + return Module["dynCall_iiiji"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iij(index, a1, a2, a3) { + try { + return Module["dynCall_iij"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iiji(index, a1, a2, a3, a4) { + try { + return Module["dynCall_iiji"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_iijii(index, a1, a2, a3, a4, a5) { + try { + return Module["dynCall_iijii"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_j(index) { + try { + return Module["dynCall_j"](index); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_ji(index, a1) { + try { + return Module["dynCall_ji"](index, a1); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_jii(index, a1, a2) { + try { + return Module["dynCall_jii"](index, a1, a2); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_jiiii(index, a1, a2, a3, a4) { + try { + return Module["dynCall_jiiii"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_jiij(index, a1, a2, a3, a4) { + try { + return Module["dynCall_jiij"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_v(index) { + try { + Module["dynCall_v"](index); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_vi(index, a1) { + try { + Module["dynCall_vi"](index, a1); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_vid(index, a1, a2) { + try { + Module["dynCall_vid"](index, a1, a2); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_vidi(index, a1, a2, a3) { + try { + Module["dynCall_vidi"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_vidii(index, a1, a2, a3, a4) { + try { + Module["dynCall_vidii"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_vifi(index, a1, a2, a3) { + try { + Module["dynCall_vifi"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_vii(index, a1, a2) { + try { + Module["dynCall_vii"](index, a1, a2); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viid(index, a1, a2, a3) { + try { + Module["dynCall_viid"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viidi(index, a1, a2, a3, a4) { + try { + Module["dynCall_viidi"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viifi(index, a1, a2, a3, a4) { + try { + Module["dynCall_viifi"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viii(index, a1, a2, a3) { + try { + Module["dynCall_viii"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiid(index, a1, a2, a3, a4) { + try { + Module["dynCall_viiid"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiii(index, a1, a2, a3, a4) { + try { + Module["dynCall_viiii"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiid(index, a1, a2, a3, a4, a5) { + try { + Module["dynCall_viiiid"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiif(index, a1, a2, a3, a4, a5) { + try { + Module["dynCall_viiiif"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiii(index, a1, a2, a3, a4, a5) { + try { + Module["dynCall_viiiii"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiii(index, a1, a2, a3, a4, a5, a6) { + try { + Module["dynCall_viiiiii"](index, a1, a2, a3, a4, a5, a6); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiii(index, a1, a2, a3, a4, a5, a6, a7) { + try { + Module["dynCall_viiiiiii"](index, a1, a2, a3, a4, a5, a6, a7); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8) { + try { + Module["dynCall_viiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) { + try { + Module["dynCall_viiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + try { + Module["dynCall_viiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) { + try { + Module["dynCall_viiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) { + try { + Module["dynCall_viiiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) { + try { + Module["dynCall_viiiiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) { + try { + Module["dynCall_viiiiiiiiiiiiiii"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiiji(index, a1, a2, a3, a4, a5, a6, a7, a8) { + try { + Module["dynCall_viiiiiji"](index, a1, a2, a3, a4, a5, a6, a7, a8); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiiiijj(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) { + try { + Module["dynCall_viiiiijj"](index, a1, a2, a3, a4, a5, a6, a7, a8, a9); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viij(index, a1, a2, a3, a4) { + try { + Module["dynCall_viij"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viiji(index, a1, a2, a3, a4, a5) { + try { + Module["dynCall_viiji"](index, a1, a2, a3, a4, a5); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viijji(index, a1, a2, a3, a4, a5, a6, a7) { + try { + Module["dynCall_viijji"](index, a1, a2, a3, a4, a5, a6, a7); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_vij(index, a1, a2, a3) { + try { + Module["dynCall_vij"](index, a1, a2, a3); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + function invoke_viji(index, a1, a2, a3, a4) { + try { + Module["dynCall_viji"](index, a1, a2, a3, a4); + } catch (e) { + if (typeof e !== "number" && e !== "longjmp") throw e; + Module["setThrew"](1, 0); + } + } + Module.asmGlobalArg = {}; + Module.asmLibraryArg = { + abort: abort, + enlargeMemory: enlargeMemory, + getTotalMemory: getTotalMemory, + invoke_di: invoke_di, + invoke_dii: invoke_dii, + invoke_diid: invoke_diid, + invoke_diii: invoke_diii, + invoke_diiid: invoke_diiid, + invoke_fii: invoke_fii, + invoke_fiii: invoke_fiii, + invoke_i: invoke_i, + invoke_id: invoke_id, + invoke_ii: invoke_ii, + invoke_iid: invoke_iid, + invoke_iii: invoke_iii, + invoke_iiid: invoke_iiid, + invoke_iiii: invoke_iiii, + invoke_iiiif: invoke_iiiif, + invoke_iiiii: invoke_iiiii, + invoke_iiiiid: invoke_iiiiid, + invoke_iiiiii: invoke_iiiiii, + invoke_iiiiiii: invoke_iiiiiii, + invoke_iiiiiiii: invoke_iiiiiiii, + invoke_iiiiiiiii: invoke_iiiiiiiii, + invoke_iiiiiiiiii: invoke_iiiiiiiiii, + invoke_iiiiiiiiiii: invoke_iiiiiiiiiii, + invoke_iiiiiiiiiiii: invoke_iiiiiiiiiiii, + invoke_iiiiiiiiiiiii: invoke_iiiiiiiiiiiii, + invoke_iiiiiiiiiiiiii: invoke_iiiiiiiiiiiiii, + invoke_iiiiij: invoke_iiiiij, + invoke_iiij: invoke_iiij, + invoke_iiiji: invoke_iiiji, + invoke_iij: invoke_iij, + invoke_iiji: invoke_iiji, + invoke_iijii: invoke_iijii, + invoke_j: invoke_j, + invoke_ji: invoke_ji, + invoke_jii: invoke_jii, + invoke_jiiii: invoke_jiiii, + invoke_jiij: invoke_jiij, + invoke_v: invoke_v, + invoke_vi: invoke_vi, + invoke_vid: invoke_vid, + invoke_vidi: invoke_vidi, + invoke_vidii: invoke_vidii, + invoke_vifi: invoke_vifi, + invoke_vii: invoke_vii, + invoke_viid: invoke_viid, + invoke_viidi: invoke_viidi, + invoke_viifi: invoke_viifi, + invoke_viii: invoke_viii, + invoke_viiid: invoke_viiid, + invoke_viiii: invoke_viiii, + invoke_viiiid: invoke_viiiid, + invoke_viiiif: invoke_viiiif, + invoke_viiiii: invoke_viiiii, + invoke_viiiiii: invoke_viiiiii, + invoke_viiiiiii: invoke_viiiiiii, + invoke_viiiiiiii: invoke_viiiiiiii, + invoke_viiiiiiiii: invoke_viiiiiiiii, + invoke_viiiiiiiiii: invoke_viiiiiiiiii, + invoke_viiiiiiiiiii: invoke_viiiiiiiiiii, + invoke_viiiiiiiiiiii: invoke_viiiiiiiiiiii, + invoke_viiiiiiiiiiiii: invoke_viiiiiiiiiiiii, + invoke_viiiiiiiiiiiiiii: invoke_viiiiiiiiiiiiiii, + invoke_viiiiiji: invoke_viiiiiji, + invoke_viiiiijj: invoke_viiiiijj, + invoke_viij: invoke_viij, + invoke_viiji: invoke_viiji, + invoke_viijji: invoke_viijji, + invoke_vij: invoke_vij, + invoke_viji: invoke_viji, + __Exit: __Exit, + __ZSt18uncaught_exceptionv: __ZSt18uncaught_exceptionv, + ___assert_fail: ___assert_fail, + ___cxa_allocate_exception: ___cxa_allocate_exception, + ___cxa_begin_catch: ___cxa_begin_catch, + ___cxa_end_catch: ___cxa_end_catch, + ___cxa_find_matching_catch_2: ___cxa_find_matching_catch_2, + ___cxa_find_matching_catch_3: ___cxa_find_matching_catch_3, + ___cxa_find_matching_catch_4: ___cxa_find_matching_catch_4, + ___cxa_find_matching_catch_5: ___cxa_find_matching_catch_5, + ___cxa_find_matching_catch_6: ___cxa_find_matching_catch_6, + ___cxa_find_matching_catch_7: ___cxa_find_matching_catch_7, + ___cxa_free_exception: ___cxa_free_exception, + ___cxa_get_exception_ptr: ___cxa_get_exception_ptr, + ___cxa_pure_virtual: ___cxa_pure_virtual, + ___cxa_rethrow: ___cxa_rethrow, + ___cxa_throw: ___cxa_throw, + ___lock: ___lock, + ___map_file: ___map_file, + ___resumeException: ___resumeException, + ___setErrNo: ___setErrNo, + ___syscall140: ___syscall140, + ___syscall145: ___syscall145, + ___syscall146: ___syscall146, + ___syscall195: ___syscall195, + ___syscall221: ___syscall221, + ___syscall340: ___syscall340, + ___syscall5: ___syscall5, + ___syscall54: ___syscall54, + ___syscall6: ___syscall6, + ___syscall75: ___syscall75, + ___syscall91: ___syscall91, + ___unlock: ___unlock, + _abort: _abort, + _clock: _clock, + _clock_gettime: _clock_gettime, + _emscripten_memcpy_big: _emscripten_memcpy_big, + _exit: _exit, + _getenv: _getenv, + _llvm_eh_typeid_for: _llvm_eh_typeid_for, + _llvm_exp2_f64: _llvm_exp2_f64, + _llvm_trap: _llvm_trap, + _pthread_cond_destroy: _pthread_cond_destroy, + _pthread_cond_init: _pthread_cond_init, + _pthread_cond_signal: _pthread_cond_signal, + _pthread_cond_timedwait: _pthread_cond_timedwait, + _pthread_cond_wait: _pthread_cond_wait, + _pthread_create: _pthread_create, + _pthread_getspecific: _pthread_getspecific, + _pthread_join: _pthread_join, + _pthread_key_create: _pthread_key_create, + _pthread_mutex_destroy: _pthread_mutex_destroy, + _pthread_mutex_init: _pthread_mutex_init, + _pthread_once: _pthread_once, + _pthread_setspecific: _pthread_setspecific, + _raise: _raise, + _sched_yield: _sched_yield, + _signal: _signal, + _strftime_l: _strftime_l, + DYNAMICTOP_PTR: DYNAMICTOP_PTR, + STACKTOP: STACKTOP, + }; + var asm = Module["asm"](Module.asmGlobalArg, Module.asmLibraryArg, buffer); + var __GLOBAL__I_000101 = (Module["__GLOBAL__I_000101"] = asm["__GLOBAL__I_000101"]); + var __GLOBAL__sub_I_api_config_params_cpp = (Module["__GLOBAL__sub_I_api_config_params_cpp"] = asm["__GLOBAL__sub_I_api_config_params_cpp"]); + var __GLOBAL__sub_I_api_interp_cpp = (Module["__GLOBAL__sub_I_api_interp_cpp"] = asm["__GLOBAL__sub_I_api_interp_cpp"]); + var __GLOBAL__sub_I_ast_cpp = (Module["__GLOBAL__sub_I_ast_cpp"] = asm["__GLOBAL__sub_I_ast_cpp"]); + var __GLOBAL__sub_I_cooperate_cpp = (Module["__GLOBAL__sub_I_cooperate_cpp"] = asm["__GLOBAL__sub_I_cooperate_cpp"]); + var __GLOBAL__sub_I_duality_profiling_cpp = (Module["__GLOBAL__sub_I_duality_profiling_cpp"] = asm["__GLOBAL__sub_I_duality_profiling_cpp"]); + var __GLOBAL__sub_I_inf_int_rational_cpp = (Module["__GLOBAL__sub_I_inf_int_rational_cpp"] = asm["__GLOBAL__sub_I_inf_int_rational_cpp"]); + var __GLOBAL__sub_I_inf_rational_cpp = (Module["__GLOBAL__sub_I_inf_rational_cpp"] = asm["__GLOBAL__sub_I_inf_rational_cpp"]); + var __GLOBAL__sub_I_iostream_cpp = (Module["__GLOBAL__sub_I_iostream_cpp"] = asm["__GLOBAL__sub_I_iostream_cpp"]); + var __GLOBAL__sub_I_iz3interp_cpp = (Module["__GLOBAL__sub_I_iz3interp_cpp"] = asm["__GLOBAL__sub_I_iz3interp_cpp"]); + var __GLOBAL__sub_I_iz3profiling_cpp = (Module["__GLOBAL__sub_I_iz3profiling_cpp"] = asm["__GLOBAL__sub_I_iz3profiling_cpp"]); + var __GLOBAL__sub_I_iz3translate_cpp = (Module["__GLOBAL__sub_I_iz3translate_cpp"] = asm["__GLOBAL__sub_I_iz3translate_cpp"]); + var __GLOBAL__sub_I_main_cpp = (Module["__GLOBAL__sub_I_main_cpp"] = asm["__GLOBAL__sub_I_main_cpp"]); + var __GLOBAL__sub_I_nlqsat_cpp = (Module["__GLOBAL__sub_I_nlqsat_cpp"] = asm["__GLOBAL__sub_I_nlqsat_cpp"]); + var __GLOBAL__sub_I_nlsat_explain_cpp = (Module["__GLOBAL__sub_I_nlsat_explain_cpp"] = asm["__GLOBAL__sub_I_nlsat_explain_cpp"]); + var __GLOBAL__sub_I_nlsat_solver_cpp = (Module["__GLOBAL__sub_I_nlsat_solver_cpp"] = asm["__GLOBAL__sub_I_nlsat_solver_cpp"]); + var __GLOBAL__sub_I_opt_frontend_cpp = (Module["__GLOBAL__sub_I_opt_frontend_cpp"] = asm["__GLOBAL__sub_I_opt_frontend_cpp"]); + var __GLOBAL__sub_I_params_cpp = (Module["__GLOBAL__sub_I_params_cpp"] = asm["__GLOBAL__sub_I_params_cpp"]); + var __GLOBAL__sub_I_prime_generator_cpp = (Module["__GLOBAL__sub_I_prime_generator_cpp"] = asm["__GLOBAL__sub_I_prime_generator_cpp"]); + var __GLOBAL__sub_I_rational_cpp = (Module["__GLOBAL__sub_I_rational_cpp"] = asm["__GLOBAL__sub_I_rational_cpp"]); + var ___cxa_can_catch = (Module["___cxa_can_catch"] = asm["___cxa_can_catch"]); + var ___cxa_is_pointer_type = (Module["___cxa_is_pointer_type"] = asm["___cxa_is_pointer_type"]); + var ___errno_location = (Module["___errno_location"] = asm["___errno_location"]); + var _emscripten_replace_memory = (Module["_emscripten_replace_memory"] = asm["_emscripten_replace_memory"]); + var _free = (Module["_free"] = asm["_free"]); + var _main = (Module["_main"] = asm["_main"]); + var _malloc = (Module["_malloc"] = asm["_malloc"]); + var setTempRet0 = (Module["setTempRet0"] = asm["setTempRet0"]); + var setThrew = (Module["setThrew"] = asm["setThrew"]); + var stackAlloc = (Module["stackAlloc"] = asm["stackAlloc"]); + var dynCall_di = (Module["dynCall_di"] = asm["dynCall_di"]); + var dynCall_dii = (Module["dynCall_dii"] = asm["dynCall_dii"]); + var dynCall_diid = (Module["dynCall_diid"] = asm["dynCall_diid"]); + var dynCall_diii = (Module["dynCall_diii"] = asm["dynCall_diii"]); + var dynCall_diiid = (Module["dynCall_diiid"] = asm["dynCall_diiid"]); + var dynCall_fii = (Module["dynCall_fii"] = asm["dynCall_fii"]); + var dynCall_fiii = (Module["dynCall_fiii"] = asm["dynCall_fiii"]); + var dynCall_i = (Module["dynCall_i"] = asm["dynCall_i"]); + var dynCall_id = (Module["dynCall_id"] = asm["dynCall_id"]); + var dynCall_ii = (Module["dynCall_ii"] = asm["dynCall_ii"]); + var dynCall_iid = (Module["dynCall_iid"] = asm["dynCall_iid"]); + var dynCall_iii = (Module["dynCall_iii"] = asm["dynCall_iii"]); + var dynCall_iiid = (Module["dynCall_iiid"] = asm["dynCall_iiid"]); + var dynCall_iiii = (Module["dynCall_iiii"] = asm["dynCall_iiii"]); + var dynCall_iiiif = (Module["dynCall_iiiif"] = asm["dynCall_iiiif"]); + var dynCall_iiiii = (Module["dynCall_iiiii"] = asm["dynCall_iiiii"]); + var dynCall_iiiiid = (Module["dynCall_iiiiid"] = asm["dynCall_iiiiid"]); + var dynCall_iiiiii = (Module["dynCall_iiiiii"] = asm["dynCall_iiiiii"]); + var dynCall_iiiiiii = (Module["dynCall_iiiiiii"] = asm["dynCall_iiiiiii"]); + var dynCall_iiiiiiii = (Module["dynCall_iiiiiiii"] = asm["dynCall_iiiiiiii"]); + var dynCall_iiiiiiiii = (Module["dynCall_iiiiiiiii"] = asm["dynCall_iiiiiiiii"]); + var dynCall_iiiiiiiiii = (Module["dynCall_iiiiiiiiii"] = asm["dynCall_iiiiiiiiii"]); + var dynCall_iiiiiiiiiii = (Module["dynCall_iiiiiiiiiii"] = asm["dynCall_iiiiiiiiiii"]); + var dynCall_iiiiiiiiiiii = (Module["dynCall_iiiiiiiiiiii"] = asm["dynCall_iiiiiiiiiiii"]); + var dynCall_iiiiiiiiiiiii = (Module["dynCall_iiiiiiiiiiiii"] = asm["dynCall_iiiiiiiiiiiii"]); + var dynCall_iiiiiiiiiiiiii = (Module["dynCall_iiiiiiiiiiiiii"] = asm["dynCall_iiiiiiiiiiiiii"]); + var dynCall_iiiiij = (Module["dynCall_iiiiij"] = asm["dynCall_iiiiij"]); + var dynCall_iiij = (Module["dynCall_iiij"] = asm["dynCall_iiij"]); + var dynCall_iiiji = (Module["dynCall_iiiji"] = asm["dynCall_iiiji"]); + var dynCall_iij = (Module["dynCall_iij"] = asm["dynCall_iij"]); + var dynCall_iiji = (Module["dynCall_iiji"] = asm["dynCall_iiji"]); + var dynCall_iijii = (Module["dynCall_iijii"] = asm["dynCall_iijii"]); + var dynCall_j = (Module["dynCall_j"] = asm["dynCall_j"]); + var dynCall_ji = (Module["dynCall_ji"] = asm["dynCall_ji"]); + var dynCall_jii = (Module["dynCall_jii"] = asm["dynCall_jii"]); + var dynCall_jiiii = (Module["dynCall_jiiii"] = asm["dynCall_jiiii"]); + var dynCall_jiij = (Module["dynCall_jiij"] = asm["dynCall_jiij"]); + var dynCall_v = (Module["dynCall_v"] = asm["dynCall_v"]); + var dynCall_vi = (Module["dynCall_vi"] = asm["dynCall_vi"]); + var dynCall_vid = (Module["dynCall_vid"] = asm["dynCall_vid"]); + var dynCall_vidi = (Module["dynCall_vidi"] = asm["dynCall_vidi"]); + var dynCall_vidii = (Module["dynCall_vidii"] = asm["dynCall_vidii"]); + var dynCall_vifi = (Module["dynCall_vifi"] = asm["dynCall_vifi"]); + var dynCall_vii = (Module["dynCall_vii"] = asm["dynCall_vii"]); + var dynCall_viid = (Module["dynCall_viid"] = asm["dynCall_viid"]); + var dynCall_viidi = (Module["dynCall_viidi"] = asm["dynCall_viidi"]); + var dynCall_viifi = (Module["dynCall_viifi"] = asm["dynCall_viifi"]); + var dynCall_viii = (Module["dynCall_viii"] = asm["dynCall_viii"]); + var dynCall_viiid = (Module["dynCall_viiid"] = asm["dynCall_viiid"]); + var dynCall_viiii = (Module["dynCall_viiii"] = asm["dynCall_viiii"]); + var dynCall_viiiid = (Module["dynCall_viiiid"] = asm["dynCall_viiiid"]); + var dynCall_viiiif = (Module["dynCall_viiiif"] = asm["dynCall_viiiif"]); + var dynCall_viiiii = (Module["dynCall_viiiii"] = asm["dynCall_viiiii"]); + var dynCall_viiiiii = (Module["dynCall_viiiiii"] = asm["dynCall_viiiiii"]); + var dynCall_viiiiiii = (Module["dynCall_viiiiiii"] = asm["dynCall_viiiiiii"]); + var dynCall_viiiiiiii = (Module["dynCall_viiiiiiii"] = asm["dynCall_viiiiiiii"]); + var dynCall_viiiiiiiii = (Module["dynCall_viiiiiiiii"] = asm["dynCall_viiiiiiiii"]); + var dynCall_viiiiiiiiii = (Module["dynCall_viiiiiiiiii"] = asm["dynCall_viiiiiiiiii"]); + var dynCall_viiiiiiiiiii = (Module["dynCall_viiiiiiiiiii"] = asm["dynCall_viiiiiiiiiii"]); + var dynCall_viiiiiiiiiiii = (Module["dynCall_viiiiiiiiiiii"] = asm["dynCall_viiiiiiiiiiii"]); + var dynCall_viiiiiiiiiiiii = (Module["dynCall_viiiiiiiiiiiii"] = asm["dynCall_viiiiiiiiiiiii"]); + var dynCall_viiiiiiiiiiiiiii = (Module["dynCall_viiiiiiiiiiiiiii"] = asm["dynCall_viiiiiiiiiiiiiii"]); + var dynCall_viiiiiji = (Module["dynCall_viiiiiji"] = asm["dynCall_viiiiiji"]); + var dynCall_viiiiijj = (Module["dynCall_viiiiijj"] = asm["dynCall_viiiiijj"]); + var dynCall_viij = (Module["dynCall_viij"] = asm["dynCall_viij"]); + var dynCall_viiji = (Module["dynCall_viiji"] = asm["dynCall_viiji"]); + var dynCall_viijji = (Module["dynCall_viijji"] = asm["dynCall_viijji"]); + var dynCall_vij = (Module["dynCall_vij"] = asm["dynCall_vij"]); + var dynCall_viji = (Module["dynCall_viji"] = asm["dynCall_viji"]); + Module["asm"] = asm; + Module["FS"] = FS; + Module["then"] = function (func) { + if (Module["calledRun"]) { + func(Module); + } else { + var old = Module["onRuntimeInitialized"]; + Module["onRuntimeInitialized"] = function () { + if (old) old(); + func(Module); + }; + } + return Module; + }; + function ExitStatus(status) { + this.name = "ExitStatus"; + this.message = "Program terminated with exit(" + status + ")"; + this.status = status; + } + ExitStatus.prototype = new Error(); + ExitStatus.prototype.constructor = ExitStatus; + var initialStackTop; + var calledMain = false; + dependenciesFulfilled = function runCaller() { + if (!Module["calledRun"]) run(); + if (!Module["calledRun"]) dependenciesFulfilled = runCaller; + }; + Module["callMain"] = function callMain(args) { + args = args || []; + ensureInitRuntime(); + var argc = args.length + 1; + var argv = stackAlloc((argc + 1) * 4); + HEAP32[argv >> 2] = allocateUTF8OnStack(Module["thisProgram"]); + for (var i = 1; i < argc; i++) { + HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i - 1]); + } + HEAP32[(argv >> 2) + argc] = 0; + try { + var ret = Module["_main"](argc, argv, 0); + exit(ret, true); + } catch (e) { + if (e instanceof ExitStatus) { + return; + } else if (e == "SimulateInfiniteLoop") { + Module["noExitRuntime"] = true; + return; + } else { + var toLog = e; + if (e && typeof e === "object" && e.stack) { + toLog = [e, e.stack]; + } + Module.printErr("exception thrown: " + toLog); + Module["quit"](1, e); + } + } finally { + calledMain = true; + } + }; + function run(args) { + args = args || Module["arguments"]; + if (runDependencies > 0) { + return; + } + preRun(); + if (runDependencies > 0) return; + if (Module["calledRun"]) return; + function doRun() { + if (Module["calledRun"]) return; + Module["calledRun"] = true; + if (ABORT) return; + ensureInitRuntime(); + preMain(); + if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"](); + if (Module["_main"] && shouldRunNow) Module["callMain"](args); + postRun(); + } + if (Module["setStatus"]) { + Module["setStatus"]("Running..."); + setTimeout(function () { + setTimeout(function () { + Module["setStatus"](""); + }, 1); + doRun(); + }, 1); + } else { + doRun(); + } + } + Module["run"] = run; + function exit(status, implicit) { + if (implicit && Module["noExitRuntime"] && status === 0) { + return; + } + if (Module["noExitRuntime"]) { + } else { + ABORT = true; + EXITSTATUS = status; + STACKTOP = initialStackTop; + exitRuntime(); + if (Module["onExit"]) Module["onExit"](status); + } + if (ENVIRONMENT_IS_NODE) { + process["exit"](status); + } + Module["quit"](status, new ExitStatus(status)); + } + Module["exit"] = exit; + function abort(what) { + if (Module["onAbort"]) { + Module["onAbort"](what); + } + if (what !== undefined) { + Module.print(what); + Module.printErr(what); + what = JSON.stringify(what); + } else { + what = ""; + } + ABORT = true; + EXITSTATUS = 1; + throw "abort(" + what + "). Build with -s ASSERTIONS=1 for more info."; + } + Module["abort"] = abort; + if (Module["preInit"]) { + if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]]; + while (Module["preInit"].length > 0) { + Module["preInit"].pop()(); + } + } + var shouldRunNow = false; + if (Module["noInitialRun"]) { + shouldRunNow = false; + } + Module["noExitRuntime"] = true; + run(); + + return Z3; +}; +if (typeof exports === "object" && typeof module === "object") module.exports = Z3; +else if (typeof define === "function" && define["amd"]) + define([], function () { + return Z3; + }); +else if (typeof exports === "object") exports["Z3"] = Z3; diff --git a/website/static/base/z3w.wasm b/website/static/base/z3w.wasm new file mode 100644 index 0000000000..4821d322c6 Binary files /dev/null and b/website/static/base/z3w.wasm differ diff --git a/website/webpack/webpack.config.common.js b/website/webpack/webpack.config.common.js index e5dccafba5..65ba5ec496 100644 --- a/website/webpack/webpack.config.common.js +++ b/website/webpack/webpack.config.common.js @@ -54,6 +54,10 @@ const commonConfig = { module: { rules: [ + { + test: /\.worker\.ts$/, + loader: 'worker-loader', + }, { test: /\.[j|t]sx?$/, include: [ diff --git a/website/yarn.lock b/website/yarn.lock index ea9514a65f..4337fe68f8 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -11599,6 +11599,11 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +sexpr-plus@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/sexpr-plus/-/sexpr-plus-7.0.0.tgz#3ec5830ee5ae85f2d3a625856968f7641c9c8c9f" + integrity sha1-PsWDDuWuhfLTpiWFaWj3ZBycjJ8= + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" @@ -11702,6 +11707,11 @@ smart-buffer@^1.0.13: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" integrity sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY= +smtlib-ext@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/smtlib-ext/-/smtlib-ext-1.0.0.tgz#396aae4d88521721761a3bc10a09cd8c1a87a42b" + integrity sha512-juyvJizsDBbUUlhrOLqYZTE78pXpxPruqapfxMdrtvV5Fm7pnzdW6HQg4fRfjbUy/6xI8oIDqCthUri+MiyMyA== + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -13275,6 +13285,14 @@ wordwrap@~0.0.2: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= +worker-loader@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37" + integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + worker-rpc@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"