You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

925 lines
31 KiB

/*
* Load, merge, and normalize strings and builtins metadata.
*
* Final metadata object contains merged and normalized objects and strings.
* These can then be used to generate RAM/ROM built-in initialization data.
*
* Various useful indices and helper maps are also added to the metadata
* object.
*/
'use strict';
const { readFileUtf8 } = require('../../extbindings/fileio');
const { parse: parseYaml } = require('../../util/yaml');
const { assert } = require('../../util/assert');
const { createBareObject } = require('../../util/bare');
const { resolveMagic } = require('../magic');
const {
propDefault,
walkObjects,
walkObjectsAndProperties,
walkObjectProperties,
walkStrings,
findPropertyByKey,
findPropertyIndexByKey,
findPropertyAndIndexByKey,
findObjectById,
findObjectAndIndexById,
} = require('./util');
const { normalizeMetadata } = require('./normalize');
const { validateFinalMetadata } = require('./validate');
const { markStridxStringsReachable, markBidxObjectsReachable, removeUnreachableObjectsAndStrings } = require('./gc');
const { jsonDeepClone } = require('../../util/clone');
const { validateStringsAreBstrRecursive } = require('../../util/bstr');
const { isWholeFinite } = require('../../util/double');
// Delete dangling references to removed/missing objects.
function deleteDanglingReferencesToObject(meta, objId) {
walkObjects(meta, (o) => {
let newProps = [];
o.properties.forEach((p) => {
let v = p.value;
let ptype = void 0;
let delProp = false;
if (typeof v === 'object' && v !== null) {
ptype = p.value.type;
}
if (ptype === 'object' && v.id === objId) {
delProp = true;
}
if (ptype === 'accessor' && v.getter_id === objId) {
console.log('delete getter, points to deleted object ' + objId);
delete v.getter_id;
}
if (ptype === 'accessor' && v.setter_id === objId) {
console.log('delete setter, points to deleted object ' + objId);
delete v.setter_id;
}
if (delProp) {
console.log('deleted property ' + p.key + ' of object ' + o.id +
', points to deleted object ' + objId);
} else {
newProps.push(p);
}
});
o.properties = newProps;
});
}
// Merge a user YAML file into current metadata.
function mergeMetadata(meta, newMeta) {
// Merge objects and their properties.
propDefault(newMeta, 'objects', []).forEach((o) => {
assert(o.disable !== true);
var [ targ, targIdx ] = findObjectAndIndexById(meta, o.id);
var action;
// Action to be taken for object. Default is 'add', which is applied
// to automatically created shorthand objects. If the shorthand
// objects are not actually needed, they are removed as orphans.
if (typeof o.action === 'string') {
action = o.action;
} else if (propDefault(o, 'delete', false)) {
action = 'delete';
} else if (propDefault(o, 'replace', false)) {
action = 'replace';
} else if (propDefault(o, 'add', false)) {
action = 'add';
} else if (propDefault(o, 'modify', false)) {
action = 'modify';
} else {
action = 'add';
}
if (action === 'delete') {
console.debug('delete object ' + o.id);
if (!targ) {
throw new TypeError('cannot delete non-existent object ' + o.id);
}
void meta.objects.splice(targIdx, 1);
deleteDanglingReferencesToObject(meta, targ.id);
return;
}
if (action === 'replace') {
console.debug('replace object ' + o.id);
if (!targ) {
console.log('object to be replaced does not exist, append new object');
meta.objects.push(o);
} else {
meta.objects[targIdx] = o;
}
return;
}
if (action === 'add') {
console.debug('add object ' + o.id);
if (targ) {
throw new TypeError('cannot add object ' + o.id + ' which already exists');
}
meta.objects.push(o);
return;
}
assert(action === 'modify');
if (!targ) {
throw new TypeError('cannot modify non-existent object ' + o.id);
}
// Merge top level keys by copying over, except for 'properties'.
Object.keys(o).sort().forEach((k) => {
if (k === 'properties') {
return;
} else {
targ[k] = o[k];
}
});
// Handle properties.
propDefault(o, 'properties', []).forEach((p) => {
assert(p.disable !== true);
var [ prop, propIdx ] = findPropertyAndIndexByKey(targ, p.key);
if (prop) {
if (propDefault(p, 'delete', false)) {
console.debug('delete property ' + p.key + ' of ' + o.id);
void targ.properties.splice(propIdx, 1);
} else {
console.debug('replace property ' + p.key + ' of ' + o.id);
targ.properties[propIdx] = p;
}
} else {
if (propDefault(p, 'delete', false)) {
console.debug('deleting property ' + p.key + ' of ' + o.id +
': does not exist, nop');
} else {
console.debug('add property ' + p.key + ' of ' + o.id);
targ.properties.push(p);
}
}
});
});
// Merge strings.
var strsHave = createBareObject({});
walkStrings(meta, (s) => {
assert(!strsHave[s.str]);
strsHave[s.str] = true;
});
(newMeta.strings || []).forEach((s) => {
if (strsHave[s.str]) {
/* nop */
} else {
strsHave[s.str] = true;
meta.strings.push(s);
}
});
(newMeta.add_forced_strings || []).forEach((s) => {
if (strsHave[s.str]) {
s._force_reachable = 'add_forced_strings';
} else {
strsHave[s.str] = true;
s._force_reachable = 'add_forced_strings';
s._auto_add_user = true;
meta.strings.push(s);
}
});
}
// Remove objects and properties disabled in active configuration.
function removeInactive(meta, activeOpts) {
var objList = [];
var countUnneededObject = 0;
var countDroppedProperty = 0;
var countUnneededProperty = 0;
function presentIfCheck(v) {
let pi = v.present_if;
if (typeof pi === 'undefined') {
return true;
} else if (typeof pi === 'string') {
pi = [ pi ];
}
if (!Array.isArray(pi)) {
throw new TypeError('invalid present_if syntax');
}
// Present if all listed options are true or unknown.
// Absent if any option is known to be false.
for (let opt of pi) {
if (activeOpts[opt] === false) {
return false;
}
}
return true;
}
for (let o of meta.objects) {
assert(o.disable !== true);
let objDropped = false;
if (!presentIfCheck(o)) {
console.debug('removed object not needed in active configuration: ' + o.id);
countUnneededObject++;
objDropped = true;
} else {
objList.push(o);
}
let propList = [];
for (let p of o.properties) {
assert(p.disable !== true);
if (objDropped) {
console.debug('removed dropped property (owning object dropped): ' + p.key + ', object: ' + o.id);
countDroppedProperty++;
} else if (!presentIfCheck(p)) {
console.debug('removed property not needed in active configuration: ' + p.key + ', object: ' + o.id);
countUnneededProperty++;
} else {
propList.push(p);
}
}
o.properties = propList;
}
meta.objects = objList;
let totalCount = countUnneededObject + countDroppedProperty + countUnneededProperty;
if (totalCount > 0) {
console.debug('removed ' + countUnneededObject + ' unneeded objects, ' +
countDroppedProperty + ' dropped properties (owning object dropped), ' +
countUnneededProperty + ' unneeded properties');
}
}
// Handle property value .type: 'share_property_value', useful for at least
// Date.prototype.toGMTString.
function handleSharedPropertyValues(meta) {
walkObjects(meta, (obj) => {
obj.properties.forEach((prop, idx) => {
if (typeof prop.value === 'object' && prop.value !== null && prop.value.type === 'share_property_value') {
console.debug('share property value:', obj.id, prop.key, '->', prop.value.key);
assert(typeof prop.value.key === 'string');
let otherProp = findPropertyByKey(obj, prop.value.key);
assert(otherProp);
var newProp = jsonDeepClone(otherProp);
newProp.key = prop.key;
obj.properties[idx] = newProp;
}
});
});
}
// Date.prototype.toGMTString must point to the same Function object
// as Date.prototype.toUTCString, so special case hack it. (Replaced
// by 'share_property_value' syntax.)
function dateToGMTStringReplacement(meta) {
var obj = findObjectById(meta, 'bi_date_prototype');
if (!obj) {
return;
}
var gmtIndex = findPropertyIndexByKey(obj, 'toGMTString');
var utcIndex = findPropertyIndexByKey(obj, 'toUTCString');
//console.debug(gmtIndex, utcIndex);
if (!(typeof gmtIndex === 'number' && typeof utcIndex === 'number')) {
return;
}
console.log('clone Date.prototype.toUTCString to Date.prototype.toGMTString');
var newProp = jsonDeepClone(obj.properties[utcIndex]);
newProp.key = 'toGMTString';
obj.properties[gmtIndex] = newProp;
}
// Add .name properties to functions, expected by current RAM format.
function addRamFunctionNames(meta) {
let numAdded = 0;
walkObjects(meta, (o) => {
if (!o.callable) {
return;
}
let nameProp = findPropertyByKey(o, 'name');
if (typeof nameProp === 'undefined') {
console.debug('add missing "name" property for object ' + o.id);
numAdded++;
o.properties.push({ key: 'name', value: '', attributes: 'c' });
}
});
if (numAdded > 0) {
console.debug('added missing "name" property for ' + numAdded + ' functions');
}
}
// ROM properties must not be configurable (runtime code depends on this).
// Writability is kept so that instance objects can override parent properties.
function handleRomPropertyAttributes(meta) {
walkObjectsAndProperties(meta, null, (p, o) => {
void o;
p.attributes = p.attributes.replace(/c/g, '');
});
}
// Convert eligible functions to lightfuncs.
function convertLightfuncs(meta) {
var numConverted = 0;
var numSkipped = 0;
walkObjectsAndProperties(meta, null, (p, o) => {
let v = p.value;
if (!(typeof v === 'object' && v !== null && v.type === 'object')) {
return;
}
let targ = findObjectById(meta, v.id);
if (!targ) {
console.log('target object ' + v.id + ' not found in lightfunc conversion check, ignoring');
return;
}
let reasons = [];
if (!targ.callable) {
reasons.push('not-callable');
}
//if (targ.constructable) {
// reasons.push('constructable');
//}
if (typeof targ.native !== 'string') {
reasons.push('no-native');
}
// Don't convert if function has properties that we're not willing
// to sacrifice.
let lfLen = 0;
let allowedKeys = createBareObject({ length: true, name: true });
walkObjectProperties(targ, (p2) => {
if (p2.key === 'length' && typeof p2.value === 'number') {
// Requires length to use number shorthand at present.
if (isWholeFinite(p2.value)) {
lfLen = p2.value;
} else {
reasons.push('unexpected-length');
}
}
if (!allowedKeys[p2.key]) {
reasons.push('nonallowed-property');
}
});
if (!propDefault(p, 'auto_lightfunc', true)) {
reasons.push('no-auto-lightfunc');
}
let lfMagic = 0;
if (typeof targ.magic !== 'undefined') {
try {
// Magic values which resolve to 'bidx' indices cannot
// be resolved here yet, because the bidx map is not
// yet ready. If so, reject the lightfunc conversion
// for now. In practice this doesn't matter.
lfMagic = resolveMagic(targ.magic, {}); // {} = fake bidx map
} catch (e) {
console.debug('failed to resolve magic for ' + p.key + ', skipping lightfunc conversion: ' + e);
reasons.push('magic-resolve-failed');
lfMagic = 0xffffffff; // dummy out of bounds value
}
}
let lfNargs, lfVarargs;
if (propDefault(targ, 'varargs', true)) {
lfNargs = null;
lfVarargs = true;
} else {
lfNargs = propDefault(targ, 'nargs', 0);
lfVarargs = false;
}
if (lfLen < 0 || lfLen > 15) {
reasons.push('len-bounds');
}
if (lfMagic < -0x80 || lfMagic > 0x7f) {
reasons.push('magic-bounds');
}
if (!lfVarargs && (lfNargs < 0 || lfNargs > 14)) {
reasons.push('nargs-bounds');
}
if (reasons.length > 0) {
console.debug('do not convert lightfunc ' + o.id + ' ' + p.key + ' ' + p.value.id + ': ' + reasons.join(','));
numSkipped++;
} else {
// Replace value in place. This typically leaves orphan objects
// which are collected later.
let oldId = p.value.id;
p.value = {
type: 'lightfunc',
native: targ.native,
length: lfLen,
magic: lfMagic,
nargs: lfNargs,
varargs: lfVarargs
}
console.debug('convert to lightfunc ' + o.id + ' ' + p.key + ' ' + oldId);
numConverted++;
}
});
console.log('converted ' + numConverted + ' built-in function properties to lightfuncs, ' + numSkipped + ' skipped as non-eligible');
}
// Prepare a list of built-in objects which need a runtime 'bidx'.
function prepareObjectsBidx(meta) {
var objList = meta.objects;
meta.objects = [];
meta.objects_bidx = [];
// Objects have a 'bidx: true' if they need a DUK_BIDX_xxx constant
// and need to be present in thr->builtins[]. The list is already
// stripped of built-in objects which are not needed based on config.
// Ideally we'd scan the actually needed indices from the source
// but since some usage is inside #if defined()s that's not trivial.
// We could, however, warn about bidx objects which are not referenced
// anywhere in the source.
for (let o of objList) {
if (propDefault(o, 'bidx', false)) {
o.bidx_used = true;
meta.objects.push(o);
meta.objects_bidx.push(o);
}
}
// Append remaining objects.
for (let o of objList) {
if (!o.bidx_used) {
meta.objects.push(o);
}
}
}
// Add C define names for builtin strings. These defines are added to all
// strings, even when they won't get a stridx because the define names are
// used to autodetect referenced strings.
function addStringDefineNames(meta) {
var specialDefs = meta.special_define_names;
walkStrings(meta, (s) => {
let v = s.str;
if (Object.prototype.hasOwnProperty.call(specialDefs, v) && typeof specialDefs[v] !== 'undefined') {
s.define = 'DUK_STRIDX_' + specialDefs[v];
return;
}
let pfx = 'DUK_STRIDX_';
if (v.length >= 1 && v[0] === '\x82') {
pfx = 'DUK_STRIDX_INT_';
v = v.substring(1);
} else if (v.length >= 1 && v[0] === '\x81' && v[v.length - 1] === '\xff') {
pfx = 'DUK_STRIDX_WELLKNOWN_';
v = v.substring(1, v.length - 1);
}
// No support for other forms of Symbols above.
let t = v.replace(/([a-z0-9])([A-Z])/g, (match, cap1, cap2) => cap1 + '_' + cap2); // add underscores: aB -> a_B
t = t.replace('.', '_'); // replace . with _, e.g. Symbol.iterator
s.define = pfx + t.toUpperCase();
});
}
// Order builtin strings (strings with a stridx) into an order satisfying
// multiple constraints.
function orderBuiltinStrings(meta) {
var inputStrlist = meta.strings;
var keywordList = meta.reserved_word_token_order;
// Strings are ordered in the result as follows:
// 1. Non-reserved words requiring 8-bit indices
// 2. Non-reserved words not requiring 8-bit indices
// 3. Reserved words in non-strict mode only
// 4. Reserved words in strict mode
//
// Reserved words must follow an exact order because they are
// translated to/from token numbers by addition/subtraction.
// Some strings require an 8-bit index and must be in the
// beginning.
function deepCopy(v) {
return JSON.parse(JSON.stringify(v));
}
// Strings needing stridx.
var tmpStrs = deepCopy(inputStrlist).filter((s) => s.stridx_used);
// The reserved word list must match token order in duk_lexer.h
// exactly, so pluck them out first. We'll then have two lists:
// keywords[], strs[].
var strIndex = createBareObject({});
var kwIndex = createBareObject({});
var keywords = [];
var strs = [];
for (let s of tmpStrs) {
strIndex[s.str] = s;
}
for (let s of keywordList) {
let v = strIndex[s];
assert(v, 'keyword in strIndex');
keywords.push(v);
kwIndex[s] = true;
}
for (let s of tmpStrs) {
if (!kwIndex[s.str]) {
strs.push(s);
}
}
// Sort the strings by category number; within category keep
// previous order.
strs.forEach((s, idx) => { s._idx = idx; }); // for ensuring stable sort
function req8Bit(s) {
return propDefault(s, 'class_name', false); // currently just class names
}
function getCat(s) {
var req8 = req8Bit(s);
if (propDefault(s, 'reserved_word', false)) {
assert(!req8);
if (propDefault(s, 'future_reserved_word_strict', false)) {
return 4;
} else {
return 3;
}
} else if (req8) {
return 1;
} else {
return 2;
}
}
function sortCmp(a, b) {
var aCat = getCat(a);
var bCat = getCat(b);
if (aCat > bCat) { return 1; }
if (aCat < bCat) { return -1; }
if (a._idx > b._idx) { return 1; } // These guarantee stable sort.
if (a._idx < b._idx) { return -1; }
return 0;
}
strs.sort(sortCmp);
for (let s of strs) {
// Remove temporary _idx properties.
delete s._idx;
}
strs.forEach((s, idx) => {
if (req8Bit(s) && idx >= 256) {
throw new TypeError('8-bit string index not satisfied: ' + s.str);
}
});
var res = strs.concat(keywords);
meta.strings_stridx = res;
}
// Add .stridx_used for strings which need a STRIDX define and must
// be present in a runtime strings[] array.
function addStringUsedStridx(meta, usedStridxEtcMeta) {
var defsNeeded = createBareObject({});
var defsFound = createBareObject({});
usedStridxEtcMeta.usedStridxDefines.forEach((s) => {
defsNeeded[s] = true;
});
// strings whose define is referenced
meta.strings.forEach((s) => {
if (s.define !== 'undefined' && defsNeeded[s.define]) {
s.stridx_used = true;
defsFound[s.define] = true;
//console.log(s);
}
});
// duk_lexer.h needs all reserved words
meta.strings.forEach((s) => {
if (propDefault(s, 'reserved_word', false)) {
assert(typeof s.define === 'string');
s.stridx_used = true;
}
});
// ensure all needed defines are provided
defsFound.DUK_STRIDX_START_RESERVED = true; // special defines provided automatically
defsFound.DUK_STRIDX_START_STRICT_RESERVED = true;
defsFound.DUK_STRIDX_END_RESERVED = true;
defsFound.DUK_STRIDX_TO_TOK = true;
for (let k of Object.keys(defsNeeded).sort()) {
if (!defsFound[k]) {
throw new TypeError('source code needs define ' + k + ' not provided by strings');
}
}
}
// Add final bidx and stridx indices.
function addFinalBidxStridxIndices(meta) {
let bidx = 0;
walkObjects(meta, (o) => {
if (o.bidx_used) {
o.bidx = bidx++;
}
});
let stridx = 0;
walkStrings(meta, (s) => {
if (s.stridx_used) {
s.stridx = stridx++;
}
});
}
// Add a built-in objects list for RAM initialization.
function addRamFilteredObjectList(meta) {
// For RAM init data to support user objects, we need to prepare a
// filtered top level object list, containing only those objects which
// need a value stack index during duk_hthread_builtins.c init process.
//
// Objects in meta['objects'] which are covered by inline property
// notation in the init data (this includes e.g. member functions like
// Math.cos) must not be present.
var objList = [];
walkObjects(meta, (o) => {
let keep = propDefault(o, 'bidx_used', false);
if (o.native && typeof o.bidx === 'undefined') {
// Handled inline by runtime init code.
} else {
// Top level object.
keep = true;
}
if (keep) {
objList.push(o);
}
});
console.debug('filtered RAM object list: ' + meta.objects_bidx.length + ' objects with bidx, ' +
objList.length + ' total top level objects');
meta.objects_ram_toplevel = objList;
}
// Sanity check: object index must match 'bidx' for all objects
// which have a runtime 'bidx'. This is assumed by e.g. RAM
// thread init.
function bidxSanityCheck(meta) {
meta.objects.forEach((o, idx) => {
if (idx < meta.objects_bidx.length) {
assert(meta.objects_bidx[idx] === meta.objects[idx]);
}
if (propDefault(o, 'bidx', false)) {
assert(o.bidx === idx);
}
});
}
// Create some helper index properties for ROM/RAM initdata generation.
// (These should be removed and handled inline in the ROM/RAM code.)
function createHelperProperties(meta) {
Object.assign(meta, {
_strings_plain: [], // ROM
_plain_to_stridx: {}, // RAM
_is_plain_reserved_word: {}, // ROM
_is_plain_strict_reserved_word: {}, // ROM
_objid_to_bidx: {}, // ROM
_objid_to_ramidx: {} // RAM
});
meta.strings.forEach((s) => {
assert(meta._strings_plain.indexOf(s.str) < 0);
meta._strings_plain.push(s.str);
if (propDefault(s, 'reserved_word', false)) {
meta._is_plain_reserved_word[s['str']] = true; // includes also strict reserved words
}
if (propDefault(s, 'future_reserved_word_strict', false)) {
meta._is_plain_strict_reserved_word[s['str']] = true;
}
});
meta.strings_stridx.forEach((s, idx) => {
assert(propDefault(s, 'stridx_used', false) === true);
meta._plain_to_stridx[s.str] = idx;
});
meta.objects_bidx.forEach((o, idx) => {
assert(propDefault(o, 'bidx_used', false) === true);
meta._objid_to_bidx[o.id] = idx;
});
(meta.objects_ram_toplevel || []).forEach((o, idx) => {
meta._objid_to_ramidx[o.id] = idx;
});
}
// Add missing strings into strings metadata. For example, if an object
// property key is not part of the strings list, append it there. This
// is critical for ROM builtins because all strings referenced by a ROM
// object must also be in ROM.
function addMissingRomStrings(meta) {
// We just need plain strings here.
var strsHave = createBareObject({});
walkStrings(meta, (s) => {
strsHave[s.str] = true;
});
// For ROM builtins all the strings must be in the strings list,
// so scan objects for any strings not explicitly listed in metadata.
walkObjectsAndProperties(meta, null, (p, o) => {
void o;
var key = p.key;
if (!strsHave[key]) {
console.debug('add missing key string: ' + key);
meta.strings.push({ str: key, _auto_add_ref: true, _force_reachable: 'rom_missing' });
strsHave[key] = true;
}
if (typeof p.value === 'string') {
if (!strsHave[p.value]) {
console.debug('add missing value string: ' + p.value);
meta.strings.push({ str: p.value, _auto_add_ref: true, _force_reachable: 'rom_missing' });
strsHave[p.value] = true;
}
}
});
}
// Dump some useful stats.
function dumpStats(meta, romBuild) {
var stats = {
countAdd: 0,
countAddRef: 0,
countAddUser: 0
};
meta.strings.forEach((s) => {
if (propDefault(s, '_auto_add_ref', false)) {
stats.countAddRef++;
}
if (propDefault(s, '_auto_add_user', false)) {
stats.countAddUser++;
}
});
stats.countAdd = stats.countAddRef + stats.countAddUser;
console.log('prepared ' + (romBuild ? 'ROM' : 'RAM') + ' metadata: ' +
meta.objects.length + ' objects, ' +
meta.objects_bidx.length + ' objects with bidx, ' +
meta.strings.length + ' strings, ' +
meta.strings_stridx.length + ' strings with stridx, ' +
stats.countAdd + ' strings added (' +
stats.countAddRef + ' property key references, ' +
stats.countAddUser + ' user strings)');
}
// Resolve magic values into final integer values.
function resolveMagicValues(meta) {
// Build temporary objIdToBidx index.
var objIdToBidx = createBareObject({});
walkObjects(meta, (o) => {
if (typeof o.bidx === 'number') {
objIdToBidx[o.id] = o.bidx;
}
});
walkObjects(meta, (o) => {
if (o.magic !== void 0) {
o.magic = resolveMagic(o.magic, objIdToBidx);
assert(typeof o.magic === 'number');
}
});
}
// Load built-in object and string metadata, merge in user metadata files,
// and prepare metadata for either RAM or ROM build.
function loadMetadata(args) {
var romAutoLightfunc = args.romAutoLightfunc;
var usedStridxEtcMeta = args.usedStridxEtcMeta;
var romBuild = args.romBuild;
var dukVersion = args.dukVersion;
var activeOpts = args.activeOpts;
var stringsMetadataFilename = args.stringsMetadataFilename;
var objectsMetadataFilename = args.objectsMetadataFilename;
var userBuiltinFiles = args.userBuiltinFiles;
// Load built-in strings and objects. Merge strings and objects
// metadata as simple top level key merge.
var meta = createBareObject({});
var objectsMetadata = parseYaml(readFileUtf8(objectsMetadataFilename));
var stringsMetadata = parseYaml(readFileUtf8(stringsMetadataFilename));
Object.assign(meta, objectsMetadata);
Object.assign(meta, stringsMetadata);
normalizeMetadata(meta);
// Add user objects.
(userBuiltinFiles || []).forEach((fn) => {
console.log('merging user built-in metadata file ' + fn);
let userMeta = parseYaml(readFileUtf8(fn));
normalizeMetadata(userMeta, activeOpts);
mergeMetadata(meta, userMeta);
});
// Remove objects and properties which are disabled in (known) active
// duk_config.h.
removeInactive(meta, activeOpts);
// Handle shared property values like Date.prototype.toGMTString
// once properties have been normalized.
handleSharedPropertyValues(meta);
// Date.prototype .toGMTString() and .toUTCString() hack. No longer
// needed with property value .type: 'share_property_value'.
//dateToGMTStringReplacement(meta);
void dateToGMTStringReplacement;
// RAM top-level functions must have a 'name'.
if (!romBuild) {
addRamFunctionNames(meta);
}
// Add Duktape.version and Duktape.env for ROM case.
let dukObj = findObjectById(meta, 'bi_duktape');
if (dukObj) {
dukObj.properties.unshift({ key: 'version', value: dukVersion, attributes: '' });
if (romBuild) {
// Use a fixed (quite dummy for now) Duktape.env when ROM
// builtins are in use. In the RAM case this is added
// during global object initialization based on config options
// in use.
dukObj.properties.unshift({ key: 'env', value: 'ROM', attributes: '' });
}
}
// For ROM objects, mark all properties non-configurable.
if (romBuild) {
handleRomPropertyAttributes(meta);
}
// Convert built-in function properties automatically into
// lightfuncs if requested and function is eligible.
if (romBuild && romAutoLightfunc) {
convertLightfuncs(meta)
}
// Now we're ready to start pruning the built-ins. First, mark all
// built-ins with 'bidx' and actually referenced strings with 'stridx'
// as forcibly reachable. These are needed in all cases.
prepareObjectsBidx(meta);
addStringDefineNames(meta);
addStringUsedStridx(meta, usedStridxEtcMeta);
markStridxStringsReachable(meta);
markBidxObjectsReachable(meta);
// For the ROM build: add any strings referenced by built-in objects
// into the string list (not the 'stridx' list though): all strings
// referenced by ROM objects must also be in ROM.
if (romBuild) {
addMissingRomStrings(meta);
}
// Check for unreachable objects and strings and remove them.
removeUnreachableObjectsAndStrings(meta);
// Reorder built-in strings to match multiple constraints,
// e.g. 8-bit stridx indices for some strings.
orderBuiltinStrings(meta);
// Add final stridx and bidx indices to metadata objects and strings.
addFinalBidxStridxIndices(meta);
// Resolve remaining magic values: can only be done once bidx values are
// known because some magic values are bidx dependent.
resolveMagicValues(meta);
// Prepare a filtered RAM top level object list, needed for RAM init.
if (!romBuild) {
addRamFilteredObjectList(meta);
}
// Sanity check for bidx.
bidxSanityCheck(meta);
// Dump stats.
dumpStats(meta, romBuild);
// Final validation steps.
validateStringsAreBstrRecursive(meta);
validateFinalMetadata(meta);
return meta;
}
exports.loadMetadata = loadMetadata;
function augmentMetadata(meta) {
// Create a set of helper lists and maps now that the metadata is
// in its final form.
createHelperProperties(meta);
}
exports.augmentMetadata = augmentMetadata;