Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
3
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 1.13.4 to 1.15.0

lib/model/csnUtils.js

38

bin/cdsc.js

@@ -16,2 +16,4 @@ #!/usr/bin/env node

const compiler = require('../lib/main');
const { compactModel } = require('../lib/json/to-csn');
const { toOdataWithCsn, toHanaWithCsn } = require('../lib/backends');
var util = require('util');

@@ -130,3 +132,21 @@ var fs = require('fs');

}
compiler.compile( args, undefined, options )
// special case when we want to call a transformer directly
// without compiling before that (for now only available for toOdata, toHana, toSql)
// works only with combination of --beta-mode in the transformers
if (options.skipCompile && ['toOdata', 'toHana', 'toSql'].includes(command)) {
if (args.length !== 1)
throw new Error(`--skip-compile option works with only one file`);
let csn = '';
try {
csn = JSON.parse(fs.readFileSync(args[0]));
} catch (e) {
throw new Error(`${args[0]} must be a valid json file`);
}
options.betaMode = true;
csn.options = options;
commands[command](csn);
} else {
compiler.compile( args, undefined, options )
.then( commands[command] )

@@ -136,2 +156,4 @@ .then( displayMessages, displayErrors )

}
if (unknownOptions.length) {

@@ -163,3 +185,3 @@ let out = process.stdout;

function toHana( model ) {
let hanaResult = compiler.toHana(model);
let hanaResult = options.newTransformers ? toHanaWithCsn(compactModel(model, options), options) : compiler.toHana(model);
for (let name in hanaResult.hdbcds) {

@@ -177,3 +199,3 @@ writeToFileOrDisplay(options.out, name + '.hdbcds', hanaResult.hdbcds[name]);

function toOdata( model ) {
let odataResult = compiler.toOdata(model);
let odataResult = options.newTransformers ? toOdataWithCsn(compactModel(model, options), options) : compiler.toOdata(model);
for (let serviceName in odataResult.services) {

@@ -277,4 +299,2 @@ // <service>_metadata.xml (metadata)

console.error( util.inspect( reveal( err.model ), false, null ));
else if (options.internalMsg)
console.error( util.inspect( reveal( err.model ).messages, { depth: null, maxArrayLength: null} ));
else

@@ -302,3 +322,5 @@ displayMessages( err.model, err.errors );

for (let msg of messages) {
if (messageLevels[ msg.severity ] <= options.warning)
if (options.internalMsg)
console.error( util.inspect( msg, { depth: null, maxArrayLength: null} ) );
else if (messageLevels[ msg.severity ] <= options.warning)
console.error( compiler.messageString( msg, normalizeFilename, !options.showMessageId, !options.testMode ) );

@@ -321,3 +343,3 @@ }

}
else {
else if (!options.lintMode && !options.internalMsg) {
writeToFileOrDisplay(options.out, name + '.json', compiler.toCsn(model), true);

@@ -333,2 +355,4 @@ }

function writeToFileOrDisplay(dir, filename, content, omitHeadline = false) {
if (options.lintMode || options.internalMsg)
return;
filename = filename.replace(/[:/\\]/g, '_');

@@ -335,0 +359,0 @@ if (!(content instanceof String || typeof content == 'string')) {

@@ -16,3 +16,3 @@ #!/usr/bin/env node

const commands = {
complete
complete, lint
}

@@ -65,3 +65,3 @@

let fname = path.resolve( '', file );
compiler.compile( [file], '', { attachValidNames: true, betaMode: true, newRedirectImpl: true } , { [fname]: src } )
compiler.compile( [file], '', { attachValidNames: true, lintMode: true, betaMode: true } , { [fname]: src } )
.then( ident, ident );

@@ -84,2 +84,20 @@ }

function lint( err, buf ) {
if (err)
return usage( err );
let fname = path.resolve( '', file );
compiler.compile( [file], '', { lintMode: true, betaMode: true } , { [fname]: buf } )
.then( display, display );
return true;
function display( xsnOrErr ) {
let messages = xsnOrErr.messages || xsnOrErr.errors;
if (!messages)
return usage( xsnOrErr );
for (let msg of messages)
console.log( compiler.messageString( msg ) );
return true;
}
}
function tokensAt( buf, offset, symbol ) {

@@ -86,0 +104,0 @@ let src = buf.substring( 0, offset ) + '≠' + buf.substring( offset );

@@ -8,2 +8,3 @@ 'use strict';

const { transformForHana } = require('./transform/forHana');
const { transformForHanaWithCsn } = require('./transform/forHanaNew');
const { compact, compactSorted } = require('./json/compactor')

@@ -15,4 +16,5 @@ const { compactModel } = require('./json/to-csn')

const { transform4odata, getServiceNames } = require('./transform/forOdata');
const { transform4odataWithCsn } = require('./transform/forOdataNew');
const csn2edm = require('./edm/csn2edm');
const { mergeOptions, verifyModelSignature } = require('./model/modelUtils');
const { mergeOptions } = require('./model/modelUtils');
const { transformTntExtensions } = require('./transform/tntSpecific');

@@ -86,5 +88,2 @@ const alerts = require('./base/alerts');

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toHana');
// Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that)

@@ -110,2 +109,62 @@ let forHanaAugmented = transformForHana(model, mergeOptions(options, { forHana: { dialect: 'hana' } }, { forHana : options.toHana } ));

// The twin of the toHana function but using CSN as a model
function toHanaWithCsn(model, options) {
// In case of API usage the options are in the 'options' argument
// put the OData specific options under the 'options.toHana' wrapper
// and leave the rest under 'options'
if (options && !options.toHana) {
_wrapRelevantOptionsForCmd(options, 'toHana');
}
// Provide defaults and merge options with those from model
options = mergeOptions({ toHana : getDefaultBackendOptions().toHana }, model.options, options);
// Provide something to generate if nothing else was given (conditional default)
if (!options.toHana.src && !options.toHana.csn) {
options.toHana.src = true;
}
// Backward compatibility for old naming modes
// FIXME: Remove after a few releases
const { warning, signal } = alerts(model);
if (options.toHana.names == 'flat') {
signal(warning`Option "{ toHana.names: 'flat' }" is deprecated, use "{ toHana.names: 'plain' }" instead`);
options.toHana.names = 'plain';
}
else if (options.toHana.names == 'deep') {
signal(warning`Option "{ toHana.names: 'deep' }" is deprecated, use "{ toHana.names: 'quoted' }" instead`);
options.toHana.names = 'quoted';
}
// Verify options
optionProcessor.verifyOptions(options, 'toHana').map(complaint => signal(warning`${complaint}`));
// Special case: For naming variant 'hdbcds' in combination with 'toHana' (and only there!), 'forHana'
// must leave namespaces, structs and associations alone.
if (options.toHana.names == 'hdbcds') {
options = mergeOptions(options, { forHana : { keepNamespaces: true, keepStructsAssocs: true } });
}
// Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that)
let forHanaAugmented = transformForHanaWithCsn(model, mergeOptions(options, { forHana: { dialect: 'hana' } }, { forHana : options.toHana } ));
// Assemble result
let result = {};
if (options.toHana.src) {
/* eslint no-console:off */
console.log('### toCdsSource() not yet working on compact CSN');
//result.hdbcds = toCdsSource(forHanaAugmented);
}
if (options.toHana.csn) {
result._augmentedCsn = forHanaAugmented;
result.csn = options.newCsn === false ? compactSorted(forHanaAugmented) : compactModel(forHanaAugmented);
}
// Transfer warnings (errors would have resulted in an exception before we come here)
if (forHanaAugmented.messages && forHanaAugmented.messages.length > 0) {
result.messages = forHanaAugmented.messages;
}
return result;
}
// ----------- toOdata -----------

@@ -190,3 +249,3 @@

signal(warning`Option "{ toOdata.names: 'deep' }" is deprecated, use "{ toOdata.names: 'quoted' }" instead`);
options.toOdata.names = 'deep';
options.toOdata.names = 'quoted';
}

@@ -197,5 +256,2 @@

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toOdata');
// Perform extra-magic for TNT if requested

@@ -259,2 +315,91 @@ if ((model.options && model.options.tntFlavor) || options.tntFlavor) {

// Generate ODATA for CSN `model` using `options`.
// The twin of the toOdata function but using CSN
function toOdataWithCsn(model, options) {
const { error, warning, signal } = alerts(model);
// In case of API usage the options are in the 'options' argument
// put the OData specific options under the 'options.toOdata' wrapper
// and leave the rest under 'options'
if (options && !options.toOdata) {
_wrapRelevantOptionsForCmd(options, 'toOdata');
}
// Provide defaults and merge options with those from model
options = mergeOptions({ toOdata : getDefaultBackendOptions().toOdata }, model.options, options);
// Provide something to generate if nothing else was given (conditional default)
if (!options.toOdata.xml && !options.toOdata.json && !options.toOdata.csn) {
options.toOdata.xml = true;
}
if (!options.toOdata.separate && !options.toOdata.combined) {
options.toOdata.combined = true;
}
// Backward compatibility for old naming modes
// FIXME: Remove after a few releases
if (options.toOdata.names == 'flat') {
signal(warning`Option "{ toOdata.names: 'flat' }" is deprecated, use "{ toOdata.names: 'plain' }" instead`);
options.toOdata.names = 'plain';
}
else if (options.toOdata.names == 'deep') {
signal(warning`Option "{ toOdata.names: 'deep' }" is deprecated, use "{ toOdata.names: 'quoted' }" instead`);
options.toOdata.names = 'deep';
}
// Verify options
optionProcessor.verifyOptions(options, 'toOdata').map(complaint => signal(warning`${complaint}`));
// Perform extra-magic for TNT if requested
if ((model.options && model.options.tntFlavor) || options.tntFlavor) {
model = transformTntExtensions(model, options);
}
// Prepare model for ODATA processing
let forOdataCSN = transform4odataWithCsn(model, options);
// Assemble result object
let result = {
services: Object.create(null),
messages: model.messages,
}
if (options.toOdata.csn) {
result.csn = forOdataCSN;
}
// Create annotations and metadata once per service
if (options.toOdata.xml || options.toOdata.json) {
// Compact the model
setProp(forOdataCSN, 'messages', forOdataCSN.messages);
for (let serviceName of getServiceNames(model)) {
// FIXME: Unify handling of version and tntFlavor (use original options)
let l_edm = csn2edm(forOdataCSN, serviceName, options);
result.services[serviceName] = {};
if (options.toOdata.xml) {
if (options.toOdata.separate) {
result.services[serviceName].annotations = l_edm.toXML('annotations');
result.services[serviceName].metadata = l_edm.toXML('metadata');
}
if (options.toOdata.combined) {
result.services[serviceName].combined = l_edm.toXML('all');
}
}
if (options.toOdata.json) {
// JSON output is not available for ODATA V2
if (options.toOdata.version == 'v2') {
signal(error`ODATA JSON output is not available for ODATA V2`);
}
// FIXME: Why only metadata_json - isn't this rather a 'combined_json' ? If so, rename it!
result.services[serviceName].metadata_json = l_edm.toJSON();
}
}
}
// Transfer warnings (errors would have resulted in an exception before we come here)
if (forOdataCSN.messages && forOdataCSN.messages.length > 0) {
result.messages = forOdataCSN.messages;
}
return result;
}
// Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)

@@ -309,4 +454,2 @@ // using 'options'

optionProcessor.verifyOptions(options, 'toCdl').map(complaint => signal(warning`${complaint}`));
// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toCdl');
return toCdsSource(model, options);

@@ -363,4 +506,2 @@ }

optionProcessor.verifyOptions(options, 'toSwagger').map(complaint => signal(warning`${complaint}`));
// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toSwagger');
// Actual implementation

@@ -442,5 +583,2 @@ return csnToSwagger(model, options);

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toSql');
// FIXME: Currently, '--to-sql' implies transformation for HANA (transferring the options to forHana)

@@ -571,5 +709,2 @@ let forHanaOptions = options.toSql;

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toRename');
// Requires beta mode

@@ -633,8 +768,4 @@ if (!options.betaMode) {

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toCsn');
if (options.toCsn.flavor && options.newCsn === false) {
signal(error`CSN in "gensrc" flavor can only be generated as new-style CSN`);
// FIXME: CompilationError? Why at all?
if (options.toCsn.gensrc && !options.newCsn === false) {
signal(error`CSN in "gensrc" flavor can only be generated as new-style CSN (option "newCsn" required)`);
throw new CompilationError(sortMessages(model.messages), model);

@@ -689,3 +820,5 @@ }

toHana,
toHanaWithCsn,
toOdata,
toOdataWithCsn,
preparedCsnToEdmx,

@@ -692,0 +825,0 @@ preparedCsnToEdm,

8

lib/base/alerts.js

@@ -39,6 +39,8 @@ // Implementation of alerts

//
function alerts(model) {
function alerts(model, options = model.options || {}) {
// If 'model' does not have a 'messages' array yet (may happen for plain CSNs), create one
model.messages = model.messages || [];
let collected = model.messages;
let collected = options.messages || model.messages ||
Object.defineProperty( model, 'messages',
{ value: [], configurable: true, writable: true } )
.messages;

@@ -45,0 +47,0 @@ return {

@@ -14,5 +14,23 @@ // Deep copy an object structure

let map = new WeakMap();
return clone(obj);
let copyStack = [];
let result = copy(obj);
function clone(obj) {
while (copyStack.length) {
let entry = copyStack.pop();
let {src, target, prop} = entry;
// copy element
let newObj = copy(src[prop]);
// store reference to copy in target property
let desc = (src && typeof src === 'object') ? Object.getOwnPropertyDescriptor(src, prop) : null;
if (desc && desc.enumerable === false && desc.get === undefined && desc.set === undefined) {
desc.value = newObj;
Object.defineProperty(target, prop, desc);
} else {
target[prop] = newObj;
}
}
return result;
function copy(obj) {
let newObj;

@@ -32,12 +50,14 @@ if (typeof obj !== 'object' || obj === null) // return primitive type, note that typeof null === 'object'

map.set(obj, newObj);
let props = Object.getOwnPropertyNames(obj); // we clone only own properties, not inherited one's
for (let p of props) {
let pd = Object.getOwnPropertyDescriptor(obj, p);
if (pd && pd.enumerable === false && pd.get === undefined && pd.set === undefined ) {
pd.value = clone(obj[p]);
Object.defineProperty(newObj, p, pd);
// loop over all properties and add them to copyStack
// in reverse order to keep same order in copied object
let propertiesToPush = [];
for (let propName of Object.getOwnPropertyNames(obj)) { // we clone only own properties, not inherited one's
if (propName === 'location' && Object.getPrototypeOf(obj)) {
// performance optimization: only link location instead of copying the object
newObj.location = obj.location;
} else {
propertiesToPush.push({ src: obj, prop: propName, target: newObj });
}
else
newObj[p] = clone(obj[p]);
}
copyStack.push(...propertiesToPush.reverse());
return newObj;

@@ -47,2 +67,2 @@ }

module.exports = deepCopy;
module.exports = deepCopy;

@@ -12,3 +12,4 @@ // Functions for dictionaries (Objects without prototype)

var found = dict[name];
if (!found) {
if (!found || found.builtin) { // do not replace a builtin definition
// XSN TODO: store duplicate definitions in prop $duplicates, do not use array (except $combined?)
dict[name] = entry; // also ok if array (redefined)

@@ -15,0 +16,0 @@ return entry;

@@ -109,3 +109,3 @@ // Functions and classes for syntax messages

* @param {any} msg The message text
* @param {string} [severity='Error'] Severity: Info, Warning, Error
* @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {any} id The ID of the message - visible as property messageId

@@ -134,8 +134,10 @@ * @param {any} home

function handleMessages( model ) {
if (model.messages && model.messages.length) {
model.messages.sort( compareMessage );
if (hasErrors( model.messages ))
throw new CompilationError( model.messages, model );
function handleMessages( model, options = model.options || {} ) {
let messages = options.messages || model.messages;
if (messages && messages.length) {
messages.sort( compareMessage );
if (hasErrors( messages ))
throw new CompilationError( messages, model );
}
return model;
}

@@ -157,6 +159,11 @@

function getMessageFunction( model ) {
if (!model.messages)
model.messages = [];
let config = model.options && model.options.severities || {};
// Return message function to issue errors, warnings, info and debug messages.
// Messages are put into the `messages` property of argument `options` or `model`.
// If those do not exist, define a non-enumerable property `messages` in `model`.
function getMessageFunction( model, options = model.options || {} ) {
let messages = options.messages || model.messages ||
Object.defineProperty( model, 'messages',
{ value: [], configurable: true, writable: true } )
.messages;
let config = options.severities || {};

@@ -174,3 +181,3 @@ return function message( id, location, home, params = {}, severity = undefined, texts = undefined ) {

(typeof home === 'string' ? home : homeName(home)) );
model.messages.push( msg );
messages.push( msg );
return msg;

@@ -177,0 +184,0 @@ }

@@ -38,4 +38,4 @@ //

function forEachMemberRecursively( construct, callback ) {
forEachMember( construct, ( member, memberName ) => {
callback( member, memberName );
forEachMember( construct, ( member, memberName, prop ) => {
callback( member, memberName, prop );
// Descend into nested members, too

@@ -42,0 +42,0 @@ forEachMemberRecursively( member, callback );

@@ -107,3 +107,7 @@ 'use strict';

if(artifactName.absolute && artifactName.element) {
let argTarget = model.definitions[artifactName.absolute].elements[artifactName.element];
let targetArtifact = model.definitions[artifactName.absolute];
if (targetArtifact instanceof Array) {
return;
}
let argTarget = targetArtifact.elements[artifactName.element];
//the check is valid for unmanaged associations

@@ -110,0 +114,0 @@ //TODO clarify if the full resolved path to the target field should consist of managed associations or just the first

@@ -111,7 +111,2 @@ 'use strict';

// exception: they can live in cds.foundation
if (art.kind != 'namespace' &&
(art.name.absolute == 'cds' ||
(art.name.absolute.match(/^cds\./) && !art.name.absolute.match(/^cds\.foundation\./)))) {
signal(error`The namespace "cds" is reserved for CDS builtins`, art.name.location);
}
if (options.toHana) {

@@ -118,0 +113,0 @@ checkNotEmptyOrOnlyVirtualElems(art, model);

@@ -69,3 +69,3 @@ // Consistency checker on model (XSN = augmented CSN)

const { locationString } = require('../base/messages');
const { locationString, hasErrors } = require('../base/messages');
const { queryOps } = require('../base/model');

@@ -85,4 +85,4 @@

':model': { // top-level from compiler
requires: ['messages','options','definitions','sources'],
optional: ['extensions','version','$version','meta','$magicVariables','$builtins','$internal','$compositionTargets','_entities','$entity'] // version without --test-mode
requires: ['options','definitions','sources'],
optional: ['messages','extensions','version','$version','meta','$magicVariables','$builtins','$internal','$compositionTargets','_entities','$entity'] // version without --test-mode
},

@@ -126,3 +126,6 @@ ':parser': { // top-level from parser

$frontend: { parser: true, test: isString, enum: ['cdl','json','xml'] },
messages: { test: isArray( TODO ) }, // TODO: check message object
messages: {
enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser
test: isArray( TODO )
}, // TODO: check message object
options: { test: TODO }, // TODO: check option object

@@ -200,3 +203,4 @@ definitions: {

]
}
},
none: { optional: () => true } // parse error
},

@@ -264,3 +268,3 @@ from: {

kind: {
isRequired: () => true, // required even with parse errors
isRequired: !stageParser && (() => true), // required to be set by Core Compiler even with parse errors
test: isString,

@@ -441,17 +445,4 @@ enum: [

function noSyntaxErrors() {
if (_noSyntaxErrors != null)
return _noSyntaxErrors;
// TODO: currently, we only have errors - use hasErrors() later
if (stageParser || !model.sources) {
_noSyntaxErrors = !(model.messages && model.messages.length);
return _noSyntaxErrors;
}
for (let file in model.sources) {
let s = model.sources[file];
if (s.messages && model.messages.length) {
_noSyntaxErrors = false;
return _noSyntaxErrors;
}
}
_noSyntaxErrors = true;
if (_noSyntaxErrors == null)
_noSyntaxErrors = !hasErrors( options.messages || model.messages ); // TODO: check messageId?
return _noSyntaxErrors;

@@ -472,3 +463,5 @@ }

let enumerable = ('enumerable' in spec) ? spec.enumerable : char !== '_';
if (parent.propertyIsEnumerable(prop) !== enumerable)
if (enumerable instanceof Function
? !enumerable( parent, prop )
: parent.propertyIsEnumerable(prop) !== enumerable)
throw new Error( `Unexpected enumerability ${!enumerable}${at( [node, parent], prop )}` );

@@ -475,0 +468,0 @@ }

@@ -82,3 +82,3 @@ // The builtin artifacts of CDS

setMagicVariables( magicVariables );
let cds = createNamespace('cds');
let cds = createNamespace( 'cds', 'reserved' );
// setProp( model.definitions, 'cds', cds );

@@ -89,3 +89,3 @@ model.definitions.cds = cds; // not setProp - oData

// namespace to store localized table views
let localized = createNamespace('localized');
let localized = createNamespace( 'localized', true );
model.definitions.localized = localized;

@@ -102,3 +102,3 @@ }

function createNamespace( name ) {
function createNamespace( name, builtin ) {
return {

@@ -109,2 +109,3 @@ kind: 'namespace',

artifacts: Object.create(null),
builtin,
location: createDummyLocation()

@@ -111,0 +112,0 @@ };

@@ -90,2 +90,3 @@ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN

var initBuiltins = require('./builtins');
const { addBoolAnnotationTo } = require('../model/modelUtils');

@@ -160,4 +161,4 @@ // Export function of this file. Transform argument `sources` = dictionary of

forEachGeneric( src, 'definitions', function( art, name ) {
if (!art.kind) // TODO: should be done by augmentor
art.kind = 'type';
if (!art.kind) // Wrong CSN input:
art.kind = 'type'; // ...use default as defined for CSN (TODO: or extra to avoid further errors?)
let dot = name.lastIndexOf('.');

@@ -187,5 +188,6 @@ if (dot > name.indexOf('::')) // there is a dot (after '::')

let builtin = model.$builtins[ last.id ];
if (builtin && !builtin.internal) {
message( 'ref-shadowed-builtin', src.namespace.location, null, // no home artifact
{ id: last.id, art: src.namespace, code: `using ${builtin.name.absolute};` },
let absolute = namespace.name.absolute;
if (builtin && !builtin.internal && absolute !== 'cds') {
message( 'ref-shadowed-builtin', namespace.location, null, // no home artifact
{ id: last.id, art: namespace, code: `using ${builtin.name.absolute};` },
'Warning', '$(ID) now refers to $(ART) - consider $(CODE)' );

@@ -198,3 +200,2 @@ }

};
let absolute = namespace.name.absolute;
block.artifacts[ last.id ] = { // assert( last.id === namespace.name.id )

@@ -213,5 +214,7 @@ kind: 'using', // TODO: or store namespace directly?

function namespaceContext( path, prefix = '', parent, declLocation ) {
// declLocation also means: check reserved cds on last item
let absolute;
if (path.broken)
return parent;
let check = declLocation && path[ path.length-1 ];
for (let item of path) {

@@ -236,3 +239,3 @@ let id = item.id;

}
addToDefinitions( context, absolute );
addToDefinitions( context, absolute, undefined, undefined, item !== check );
setProp( context, '_parent', parent || null );

@@ -249,4 +252,3 @@ }

// another artifact has already been defined with the same absolute name.
// TODO: do our own check for reserved names in compiler !!!
function addToDefinitions( art, absolute = art.name.absolute, prefix, parent ) {
function addToDefinitions( art, absolute = art.name.absolute, prefix, parent, noReservedCheck ) {
let context = reuse();

@@ -271,9 +273,13 @@ //console.log(context?'Reuse':'Def',art.name,prefix ,parent&&parent.name)

}
if (absolute === 'cds') {
// TODO: move all 'cds' prefix checks into compiler
message( null, art.name.location, parent,
if (!noReservedCheck &&
(absolute === 'cds' ||
absolute.match(/^cds\./) && !absolute.match(/^cds\.foundation(\.|$)/))) {
message( null, art.name.location, null,
`The namespace "cds" is reserved for CDS builtins` );
}
else
addToDict( model.definitions, absolute, art );
else if (!noReservedCheck && absolute === 'localized') {
message( null, art.name.location, null,
`The namespace "localized" is reserved for localized convenience views` );
}
addToDict( model.definitions, absolute, art );
return art;

@@ -305,3 +311,3 @@

let path = contextName.substring(colons).split('.').map( id => ({ id, location }) );
context = namespaceContext( path, contextName.substring(0,colons), undefined, location );
context = namespaceContext( path, contextName.substring(0,colons) );
}

@@ -441,5 +447,6 @@ if (context instanceof Array || !context.kind) // no proper construct

art.$queries = [];
art.queries = [];
art.queries = []; // TODO: remove
setProp( art, '_leadingQuery', initQueryExpression( art, art.query ) );
setProp( art._leadingQuery, '_$next', art );
if (art._leadingQuery) // null in case of parser error
setProp( art._leadingQuery, '_$next', art );
// resolve parameters and actions:

@@ -555,6 +562,3 @@ initMembers( art, art, block ); // before setting art.elements!

if (query.on) {
addQuery();
// TODO: use first tabalias name on the right side of the join (if much
// easier: last of left side) as 'param' in name of this "query" (do
// not use that for user msg, only for --raw-output)
addQuery( parents[0].name.query );
initSubQuery( query );

@@ -565,6 +569,14 @@ parents = [...parents, query];

initQueryExpression( art, tab, parents );
if (query.name) { // with ON (after processing args to get the $tableAliases
let aliases = Object.keys( query.$tableAliases || {} );
// Use first tabalias name on the right side of the join to name the
// (internal) query, should only be relevant for --raw-output, not for
// user messages or references
query.name.param = aliases[1] || aliases[0] || '<unknown>';
}
}
else if (query.from) { // select
addQuery();
query._main.$queries.push( query ); // TODO: set number with it
addQuery( (art._main||art).$queries.length );
// TODO: 1-based if view elems are proxies to leading queries elements
query._main.$queries.push( query );
if (parents.length)

@@ -604,3 +616,3 @@ addAlias( {}, query );

setProp( query, '_leadingQuery', leading );
if (query.orderBy) {
if (leading && query.orderBy) {
if (leading.$orderBy)

@@ -668,3 +680,3 @@ leading.$orderBy.push( ...query.orderBy );

function addQuery() {
function addQuery( name ) {
setProp( query, '_$next', art );

@@ -675,3 +687,3 @@ setProp( query, '_block', art._block );

query.name = { location: query.location };
setMemberParent( query, art.queries.length, art );
setMemberParent( query, name, art );
art.queries.push( query );

@@ -736,2 +748,4 @@ query.$tableAliases = Object.create( null ); // table aliases and mixin definitions

function init ( elem, name, prop ) {
if (!elem.kind) // wrong CSN input
elem.kind = dictKinds[ prop ];
if (!elem.name) {

@@ -771,2 +785,5 @@ let ref = elem.targetElement || elem.kind === 'element' && elem.value;

return false;
const names = Object.getOwnPropertyNames( dict );
if (!names.length)
return false;
let feature = kindProperties[ parent.kind ][prop];

@@ -776,3 +793,3 @@ if (feature &&

return true;
let location = dictLocation( dict );
let location = dictLocation( names.map( name => dict[name] ) );
if (prop === 'actions') {

@@ -946,3 +963,3 @@ message( 'unexpected-actions', location, {}, construct, 'Error',

}
else if (!options.lintMode) {
else {
// If not lint-mode, complain about unused extensions, i.e. those

@@ -1126,22 +1143,4 @@ // which do not point to a valid artifact

if(ext.columns) { // extend projection - copy columns
let loc = ext.location;
let query = art.query;
let name = ext.name.path.map(X => X.id).join('/');
if(query.from == undefined) {
message( 'ext-proj-from-one', loc, null, { name, source:query.op.val }, 'Error',
'Can not extend projection $(NAME) on $(SOURCE) as source' );
} else if(query.from.length!==1) {
message( 'ext-proj-from-one', loc, null, { name }, 'Error',
'Can not extend projection $(NAME) having more than one sources' );
} else {
if(query.columns==undefined)
query.columns=[];
for (let columnName in ext.columns) {
let column = ext.columns[ columnName ];
setProp(column,'_block', ext._block);
query.columns.push(column)
}
}
} // extend projection
if (ext.columns) // extend projection
extendColumns( ext, art );
}

@@ -1163,2 +1162,28 @@ if (elemExtensions.length > 1)

// Copy columns for EXTEND PROJECTION
function extendColumns( ext, art ) {
// TODO: consider reportUnstableExtensions
let location = ext.location;
let query = art.query;
if (!query) {
if (art.kind !== 'annotate')
message( 'extend-columns', location, ext, { art }, 'Error',
'Artifact $(ART) cannot be extended with columns, only projections can' );
return;
}
if (!query.from || query.from.length !==1 || !query.from[0].path) {
message( 'extend-columns', location, ext, { art }, 'Error',
'Artifact $(ART) cannot be extended with columns, only projections can' );
}
else {
if (!query.columns)
query.columns = [ { location, val: '*' } ];
for (let column of ext.columns) {
setProp( column,'_block', ext._block );
query.columns.push(column)
}
}
}
function reportUnstableExtensions( extensions ) {

@@ -1349,2 +1374,3 @@ // Report 'Warning: Unstable element order due to repeated extensions'.

}
addBoolAnnotationTo('@odata.draft.enabled', false, art);
let locale = {

@@ -1351,0 +1377,0 @@ name: { location, id: 'locale' },

@@ -193,3 +193,3 @@ //

function returns( prop, target, source, ok ) {
function returns( prop, target, source, ok, deleteProp ) {
// TODO: remove returns in XSN

@@ -201,2 +201,5 @@ if (ok || target.$inferred === 'proxy' || target.$inferred === 'include' ) {

setProp( target[prop], '_outer', target._outer || target ); // for setMemberParent
if (deleteProp) {
delete target[deleteProp];
}
}

@@ -212,3 +215,3 @@ }

!line.elements && !line.enum && !line.items )
returns( prop, target, source, !options.hanaFlavor );
returns( prop, target, source, !options.hanaFlavor, 'type');
}

@@ -215,0 +218,0 @@ }

@@ -623,7 +623,6 @@ // Compiler functions and utilities shared across all phases

elem.name.absolute = elem._main.name.absolute;
let normalized = kindProperties[ elem.kind ].normalized || elem.kind;
[ 'element', 'alias', 'query', 'param', 'action' ].forEach( function( kind ) {
// console.log(elem.kind || elem)
let normalized = kindProperties[ elem.kind ].normalized || elem.kind;
if (normalized === kind) {
elem.name[kind] = (parent.name[kind] != null) ? parent.name[kind] + '.' + name : name;
elem.name[kind] = (parent.name[kind] != null && kind !== 'query') ? parent.name[kind] + '.' + name : name;
}

@@ -630,0 +629,0 @@ else if (parent.name[kind] != null) {

@@ -44,7 +44,7 @@ {

"Authorization.SecuritySchemes": {
"Type": "Collection(Auth.SecurityScheme)",
"Type": "Collection(Authorization.SecurityScheme)",
"AppliesTo": "EntityContainer"
},
"Authorization.Authorizations": {
"Type": "Collection(Auth.Authorization)",
"Type": "Collection(Authorization.Authorization)",
"AppliesTo": "EntityContainer"

@@ -631,2 +631,6 @@ },

},
"UI.ConnectedFields": {
"Type": "UI.ConnectedFieldsType",
"AppliesTo": "EntityType"
},
"UI.GeoLocations": {

@@ -740,2 +744,6 @@ "Type": "Collection(UI.GeoLocationType)",

},
"UI.DataFieldDefault": {
"Type": "UI.DataFieldAbstract",
"AppliesTo": "Property"
},
"UI.Criticality": {

@@ -877,3 +885,3 @@ "Type": "UI.CriticalityType",

"Properties": {
"Scopes": "Collection(Auth.AuthorizationScope)",
"Scopes": "Collection(Authorization.AuthorizationScope)",
"RefreshUrl": "Edm.String",

@@ -889,3 +897,3 @@ "Name": "Edm.String",

"TokenUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"Scopes": "Collection(Authorization.AuthorizationScope)",
"RefreshUrl": "Edm.String",

@@ -901,3 +909,3 @@ "Name": "Edm.String",

"AuthorizationUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"Scopes": "Collection(Authorization.AuthorizationScope)",
"RefreshUrl": "Edm.String",

@@ -913,3 +921,3 @@ "Name": "Edm.String",

"TokenUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"Scopes": "Collection(Authorization.AuthorizationScope)",
"RefreshUrl": "Edm.String",

@@ -926,3 +934,3 @@ "Name": "Edm.String",

"TokenUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"Scopes": "Collection(Authorization.AuthorizationScope)",
"RefreshUrl": "Edm.String",

@@ -945,3 +953,3 @@ "Name": "Edm.String",

"KeyName": "Edm.String",
"Location": "Auth.KeyLocation",
"Location": "Authorization.KeyLocation",
"Name": "Edm.String",

@@ -1619,4 +1627,4 @@ "Description": "Edm.String"

"TypeNamePlural": "Edm.String",
"Title": "UI.DataField",
"Description": "UI.DataField",
"Title": "UI.DataFieldAbstract",
"Description": "UI.DataFieldAbstract",
"ImageUrl": "Edm.String",

@@ -1689,2 +1697,3 @@ "TypeImageUrl": "Edm.String"

"Criticality": "UI.CriticalityType",
"CriticalityLabels": "Edm.AnnotationPath",
"CriticalityCalculation": "UI.CriticalityCalculationType",

@@ -1752,2 +1761,3 @@ "Trend": "UI.TrendType",

"DataPoint": "UI.DataPointType",
"AdditionalDataPoints": "Collection(UI.DataPointType)",
"Detail": "UI.KPIDetailType"

@@ -1754,0 +1764,0 @@ }

@@ -31,3 +31,3 @@ const parseXml = require('./xmlParserWithLocations');

*/
function edmx2csn(src, filename) {
function edmx2csn(src, filename, options) {
let source = parseXml(src, filename);

@@ -41,2 +41,5 @@

forEachAnnotationsTag(source);
// The EDMX parser do not use the normal message function:
if (options.messages && csn.messages)
options.messages.push( ...csn.messages );
return csn;

@@ -43,0 +46,0 @@

@@ -140,3 +140,2 @@ 'use strict';

}
if (object.kind == "action" || object.kind == "function") {

@@ -251,3 +250,2 @@ handleAction(objName, object, null);

let element = object.elements[elemName];
// determine the name of the target in the resulting edm

@@ -331,8 +329,11 @@ // for non-assoc element, this simply is "<objectName>/<elementName>"

let params = [];
if (bindingParam) params.push(bindingParam);
if (bindingParam) {
params.push(action['@cds.odata.bindingparameter.collection'] ? 'Collection(' + bindingParam + ')' : bindingParam);
}
if (action.kind === 'function') {
for (let n in action.params) {
let p = action.params[n];
let otype = p.type.startsWith('cds.') ? glue.mapCdsToEdmType(p.type, false /*is only called for v4*/) : p.type;
params.push(otype);
let mapType = (type) => type.startsWith('cds.') ? glue.mapCdsToEdmType(type, false /*is only called for v4*/) : type;
let isArrayType = !p.type && p.items && p.items.type;
params.push(isArrayType ? 'Collection(' + mapType(p.items.type) + ')' : mapType(p.type));
}

@@ -354,2 +355,6 @@ }

// - have a value other than null
if(carrier['@cds.api.ignore']) {
return;
}
let annoNames = Object.keys(carrier).filter( x => x.substr(0,1) == "@" );

@@ -356,0 +361,0 @@ let nullWhitelist = [ '@Core.OperationAvailable' ];

@@ -226,3 +226,2 @@ 'use strict';

let aNameWithoutQualifier = aName.split("#")[0];
let a = carrier[aName];

@@ -232,11 +231,29 @@ //for warning messages

// Always - draft annotations, value is action name
// - v2: prefix with entity name
// - prefix with service name
draftAnnotations(carrier, aName, aNameWithoutQualifier);
// Always - FixedValueListShortcut
// expand shortcut form of ValueList annotation
fixedValueListShortCut(carrier, aNameWithoutQualifier, ctx);
// Always - TextArrangementReordering
// convert @Common.TextArrangement annotation that is on same level as Text annotation into a nested annotation
// TnT only: also accept @UI.TextArrangement
textArrangementReordering(carrier, aName, aNameWithoutQualifier, ctx);
// TNT - SubstitutingFKeysForAssocs
substituteFKeysForAssocsTNT(carrier, aName, aNameWithoutQualifier);
}
// inner functions
function draftAnnotations(carrier, aName, aNameWithoutQualifier) {
if ((carrier.kind === 'entity' || carrier.kind === 'view') &&
(aNameWithoutQualifier == "@Common.DraftRoot.PreparationAction" ||
aNameWithoutQualifier == "@Common.DraftRoot.ActivationAction" ||
aNameWithoutQualifier == "@Common.DraftRoot.EditAction" ||
aNameWithoutQualifier == "@Common.DraftNode.PreparationAction")
) {
aNameWithoutQualifier == "@Common.DraftRoot.ActivationAction" ||
aNameWithoutQualifier == "@Common.DraftRoot.EditAction" ||
aNameWithoutQualifier == "@Common.DraftNode.PreparationAction")
) {
let value = carrier[aName];

@@ -255,5 +272,5 @@ // prefix with service name, if not already done

}
}
// Always - FixedValueListShortcut
// expand shortcut form of ValueList annotation
function fixedValueListShortCut(carrier, aNameWithoutQualifier, ctx) {
if (aNameWithoutQualifier == "@Common.ValueList.entity" ||

@@ -336,2 +353,11 @@ aNameWithoutQualifier == "@Common.ValueList.viaAssociation") {

// if this carrier is a generated foreign key field and the association is marked @cds.api.ignore
// rename the localDataProp to be 'assocName/key'
if(carrier['@cds.api.ignore']) {
let assocName = carrier['@odata.foreignKey4'];
if(assocName && options.isV4()) {
localDataProp = localDataProp.replace(assocName+fkSeparator, assocName+'/');
}
}
// valueListProp: the (single) key field of the value list entity

@@ -417,6 +443,5 @@ // if no key or multiple keys -> warning

}
}
// Always - TextArrangementReordering
// convert @Common.TextArrangement annotation that is on same level as Text annotation into a nested annotation
// TnT only: also accept @UI.TextArrangement
function textArrangementReordering(carrier, aName, aNameWithoutQualifier, ctx) {
let tntTextArrangement = options && options.tntFlavor && !options.tntFlavor.skipAnnosTextArrangementReordering;

@@ -445,10 +470,12 @@ if (aNameWithoutQualifier == "@Common.TextArrangement" ||

}
}
// TNT - SubstitutingFKeysForAssocs
function substituteFKeysForAssocsTNT(carrier, aName, aNameWithoutQualifier) {
if (options && options.tntFlavor && !options.tntFlavor.skipAnnosSubstitutingFKeysForAssocs) {
// replace association by fk field, mind nested annotatinos
let annotationValue = carrier[aName];
if (aNameWithoutQualifier == "@UI.LineItem" || aNameWithoutQualifier == "@UI.LineItem.$value" ||
aNameWithoutQualifier == "@UI.Identification" || aNameWithoutQualifier == "@UI.Identification.$value" ||
aNameWithoutQualifier == "@UI.FieldGroup" || aNameWithoutQualifier == "@UI.FieldGroup.$value") {
for (let ae of a) {
for (let ae of annotationValue) {
if (ae["Value"] && ae["Value"]["="]) {

@@ -462,3 +489,3 @@ replaceManagedAssocByFK(ae["Value"]);

if (aNameWithoutQualifier == "@UI.SelectionFields") {
for (let ae of a) {
for (let ae of annotationValue) {
if ("=" in ae) {

@@ -470,19 +497,19 @@ replaceManagedAssocByFK(ae);

}
}
function replaceManagedAssocByFK(ae) {
let path = ae["="];
let steps = path.split('.');
let ent = carrier;
for (let i in steps) {
if (!ent || ent.kind != 'entity') return;
let el = ent.elements[steps[i]];
if (el && glue.isAssociation(el)) {
if (i < steps.length-1) {
ent = getTargetOfAssoc(el);
}
else { //last step
if (!el.onCond && !el.on) { // only for managed
ae["="] += fkSeparator + getKeyOfTargetOfManagedAssoc(el);
function replaceManagedAssocByFK(ae) {
let path = ae["="];
let steps = path.split('.');
let ent = carrier;
for (let i in steps) {
if (!ent || ent.kind != 'entity') return;
let el = ent.elements[steps[i]];
if (el && glue.isAssociation(el)) {
if (i < steps.length-1) {
ent = getTargetOfAssoc(el);
}
else { //last step
if (!el.onCond && !el.on) { // only for managed
ae["="] += fkSeparator + getKeyOfTargetOfManagedAssoc(el);
}
}
}

@@ -489,0 +516,0 @@ }

@@ -13,3 +13,3 @@ 'use strict';

const alerts = require('../base/alerts');
const { CompilationError, hasErrors, sortMessages } = require('../base/messages');
const { handleMessages } = require('../base/messages');
const { setProp } = require('../base/model');

@@ -49,4 +49,4 @@

let serviceCsn = glue.initializeModel(model, options);
if(serviceCsn == undefined)
let serviceCsn = glue.initializeModel(model, options, signal, warning);
if(serviceCsn === undefined)
signal(error`No Service found in model`);

@@ -99,14 +99,21 @@

// fetch all exising children names in a map
let NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
if(acc[cur.Name] === undefined) {
acc[cur.Name] = [ cur ];
} else {
acc[cur.Name].push(cur);
}
return acc;
}, {} );
navigationProperties.forEach(np => {
/*
if(np._csn._partnerCsn.length > 1) {
if(options.tntFlavor) {
signal(warning`NavigationProperty "${np._csn._parent.name}/${np.Name}" has more then one partner: ${np._csn._partnerCsn.map(csn=>'"'+csn._parent.name+'/'+csn.Name+'"').join(', ')}`);
}
else {
signal(error`NavigationProperty "${np._csn._parent.name}/${np.Name}" has more then one partner: ${np._csn._partnerCsn.map(csn=>'"'+csn._parent.name+'/'+csn.Name+'"').join(', ')}`);
}
signal(warning`NavigationProperty "${np._csn._parent.name}/${np.Name}" has more then one partner: ${np._csn._partnerCsn.map(csn=>'"'+csn._parent.name+'/'+csn.Name+'"').join(', ')}`);
}
*/
if(options.isV4()) {
// V4: No referential constraints for Containment Relationships
if(!np.isContainment())
if(!np.isContainment() && !np.isToMany())
np.addReferentialConstraintNodes();

@@ -120,2 +127,7 @@ }

for(let name in NamesInSchemaXRef) {
if(NamesInSchemaXRef[name].length > 1) {
signal(error`Duplicate name "${name}" in Namespace "${Schema.Namespace}"`);
}
}
if(Schema._ec._children.length == 0)

@@ -125,6 +137,3 @@ signal(error`EntityContainer must contain at least one EntitySet`);

// Throw up if we have errors
if (hasErrors(model.messages)) {
throw new CompilationError(sortMessages(model.messages), model);
}
handleMessages(model, options);
return edm

@@ -146,2 +155,5 @@

if(properties.length === 0) {
signal(error`EntityType "${serviceName}/${EntityTypeName}" has no properties`);
}
// construct EntityType attributes

@@ -198,3 +210,3 @@ let attributes = { Name : EntityTypeName };

let bpType = actionCsn['@cds.odata.bindingparameter.collection'] ?
'$Collection('+fullQualified(entityCsn.name)+')' : fullQualified(entityCsn.name);
'Collection('+fullQualified(entityCsn.name)+')' : fullQualified(entityCsn.name);
// Binding Parameter: 'in' at first position in sequence, this is decisive!

@@ -209,3 +221,3 @@ actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType }, {} ));

let rt = actionCsn.returns && (actionCsn.returns.type || actionCsn.returns.items.type);
let rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
if(rt) // add EntitySet attribute only if return type is a non abstract entity

@@ -254,3 +266,3 @@ {

let rt = actionCsn.returns && (actionCsn.returns.type || actionCsn.returns.items.type);
let rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
if(rt) // add EntitySet attribute only if return type is an entity

@@ -310,3 +322,3 @@ {

// it is safe to assume that either type or items.type are set
let type = action.returns && (action.returns.type || action.returns.items.type);
let type = action.returns && ((action.returns.items && action.returns.items.type) || action.returns.type);
if(type && type.startsWith('cds.'))

@@ -422,5 +434,10 @@ type = glue.mapCdsToEdmType(type, options.isV2());

{
let constraints = navigationProperty._referentialConstraints;
let constraints = navigationProperty._csn._constraints;
let parentName = navigationProperty._csn._parent.name.replace(namespace, '');
let assocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
let assocName = plainAssocName;
let i = 1;
while(NamesInSchemaXRef[assocName] !== undefined) {
assocName = plainAssocName + '_' + i++;
}

@@ -482,7 +499,13 @@ let fromRole = parentName;

parentName = forwardAssocCsn._parent.name.replace(namespace, '');
assocName = parentName + NAVPROP_TRENNER + forwardAssocCsn.name.replace(VALUELIST_NAVPROP_PREFIX, '');
assocName = plainAssocName = parentName + NAVPROP_TRENNER + forwardAssocCsn.name.replace(VALUELIST_NAVPROP_PREFIX, '');
i = 1;
while(NamesInSchemaXRef[assocName] !== undefined && !(NamesInSchemaXRef[assocName][0] instanceof Edm.Association)) {
assocName = plainAssocName + '_' + i++;
}
navigationProperty.Relationship = fullQualified(assocName)
reuseAssoc = !!forwardAssocCsn._NavigationProperty;
constraints = navigationProperty.getReferentialConstraints(forwardAssocCsn);
constraints = glue.getReferentialConstraints(forwardAssocCsn, signal, warning);
constraints._multiplicity = glue.determineMultiplicity(forwardAssocCsn);
}

@@ -518,6 +541,11 @@

[ toRole, fullQualified(toEntityType) ],
constraints.multiplicity );
constraints._multiplicity );
if(NamesInSchemaXRef[assocName] === undefined) {
NamesInSchemaXRef[assocName] = [ navigationProperty._edmAssociation ];
}
else {
navigationProperty._edmAssociation.push(navigationProperty._edmAssociation);
}
// Add ReferentialConstraints if any
if(Object.keys(constraints.constraints).length > 0) {
if(!navigationProperty._isCollection && Object.keys(constraints.constraints).length > 0) {
// A managed composition is treated as association

@@ -524,0 +552,0 @@ if(navigationProperty._csn.type == 'cds.Composition' && (navigationProperty._csn.on || navigationProperty._csn.onCond)) {

@@ -533,3 +533,3 @@ 'use strict'

{
let type = csn.type || csn.items.type;
let type = (csn.items && csn.items.type) || csn.type;
if(type && type.startsWith('cds.'))

@@ -847,14 +847,17 @@ type = glue.mapCdsToEdmType(type, false, false);

{
NavigationProperty.DOLLAR_SELF = '$self'
super(v, attributes, csn);
super(v, attributes, csn);
let [src, tgt] = glue.determineMultiplicity(csn._constraints._originAssocCsn || csn);
csn._constraints._multiplicity = csn._constraints._originAssocCsn ? [tgt, src] : [src, tgt];
this.set( {
_type: attributes.Type,
_isCollection: glue.isToMany(csn),
_referentialConstraints: this.getReferentialConstraints(),
_targetCsn: csn.target } );
_isCollection: this.isToMany(),
_targetCsn: csn.target
} );
if (this.v4)
{
// either csn has multiplicity or we have to use the multiplicity of the backlink
if(this._isCollection || this._referentialConstraints.multiplicity[1] == '*') {
if(this._isCollection) {
this.Type = `Collection(${attributes.Type})`

@@ -872,2 +875,7 @@ // attribute Nullable is not allowed in combination with Collection (see Spec)

let partner = (csn._partnerCsn.length > 0) ? csn._partnerCsn[0] : csn._constraints._originAssocCsn;
if(partner && partner['@odata.navigable'] !== false) {
this.Partner = partner.name;
}
/*

@@ -884,6 +892,2 @@ 1) If this navigation property belongs to an EntityType for a parameterized entity

this.ContainsTarget = true;
// if backlink has established partner before this underlying NavProp is created, use it
if(csn._partnerCsn.length > 0)
this.Partner = csn._partnerCsn[0].name;
}

@@ -916,2 +920,5 @@

}
isToMany() {
return (this._isCollection || this._csn._constraints._multiplicity[1] == '*');
}

@@ -963,266 +970,5 @@ toJSONattributes(json)

{
glue.forAll(this._referentialConstraints.constraints,
glue.forAll(this._csn._constraints.constraints,
c => this.append(new ReferentialConstraint(this._v, { Property: c[0], ReferencedProperty: c[1] } ) ) );
}
getReferentialConstraints(assocCsn = undefined)
{
let result = { multiplicity: [ ], constraints: Object.create(null), selfs: [], termCount: 0 };
assocCsn = assocCsn || this._csn;
if(assocCsn.on)
{
// fill constraint array with [prop, depProp]
getExpressionArguments(assocCsn.on);
// for all $self conditions, fill constraints of partner (if any)
let isBacklink = result.selfs.length == 1 && result.termCount == 1;
/* example for originalTarget:
entity E (with parameters) {
... keys and all the stuff ...
toE: association to E;
back: association to E on back.toE = $self
}
toE target 'E' is redirected to 'EParameters' (must be as the new parameter list is required)
back target 'E' is also redirected to 'EParameters' (otherwise backlink would fail)
ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is
required for that
*/
result.selfs.forEach(partner => {
let originAssocCsn = assocCsn.target.elements[partner];
if(originAssocCsn == undefined && assocCsn.originalTarget)
originAssocCsn = assocCsn.originalTarget.elements[partner];
if(originAssocCsn == undefined)
throw Error('Could not find forward association "' + assocCsn.target.name + '.' + partner +
' for backlink association "' + assocCsn._parent.name + '.' + assocCsn.name + '", maybe forward is not published in service?');
// let originAssocCsn = assocCsn.target.elements[partner] || assocCsn.originalTarget.elements[partner];
if(glue.isAssociationOrComposition(originAssocCsn)) {
// if the assoc is marked as primary key, add all its foreign keys as constraint
// as they are primary keys of the other entity as well
if(!assocCsn.target.isParamEntity && originAssocCsn.key) {
if(originAssocCsn.keys) {
for(let fk of originAssocCsn.keys) {
let c = [ fk.ref[0], fk.$generatedFieldName ];
result.constraints[c] = c;
}
}
}
// Mark this association as backlink if $self appears exactly once
// to surpress edm:Association generation in V2 mode
if(isBacklink) {
result._originAssocCsn = originAssocCsn;
// store this backlink at forward association
originAssocCsn._partnerCsn.push(assocCsn);
// V4 Set the Partner attribute with the name of the opposite
// association to the two corresponding csn's and to this NavProp
// (but only if originAssoc is navigable (undefined !== false) still evaluates to true)
if(this.v4 && originAssocCsn['@odata.navigable'] !== false)
{
// set the Partner attribute to this (backlink) NavProp
this.Partner = originAssocCsn.name;
// if the other NavProp has been created already, set NavProp.Partner
// if not, Partner will be set during creation of other NavProp
if(originAssocCsn._NavigationProperty)
originAssocCsn._NavigationProperty.Partner = assocCsn.name;
}
}
}
else {
/*
entity E {
key id : Integer;
toMe: associaton to E on toMe.id = $self; };
*/
throw Error('Backlink association element is not an association or composition: "' + originAssocCsn.name);
}
});
if(!assocCsn.target.isParamEntity) {
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
let dependentEntity = assocCsn._parent;
let principalEntity = assocCsn.target;
if(assocCsn.type == 'cds.Composition') {
principalEntity = assocCsn._parent;
dependentEntity = assocCsn.target;
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
Object.keys(result.constraints).forEach(cn => {
result.constraints[cn] = [ result.constraints[cn][1], result.constraints[cn][0] ] } );
}
// Remove all arget elements that are not key in the principal entity
// and all elements that annotated with '@cds.api.ignore'
glue.foreach(result.constraints,
c => {
let fk = dependentEntity.elements[c[0]];
let pk = principalEntity.key[c[1]];
return !(pk && fk && !(pk['@cds.api.ignore'] || fk['@cds.api.ignore']));
},
(c, cn) => { delete result.constraints[cn]; } );
}
}
// Handle managed association, a managed composition is treated as association
else
{
// If FK is key in target => constraint
// Don't consider primary key associations (fks become keys on the source entity) as
// this would impose a constraint against the target.
// Filter out all elements that annotated with '@cds.api.ignore'
// FIXME: If path is something structured, perform a path resolution (or use augmented CSN)
if(!assocCsn.target.isParamEntity && assocCsn.keys) {
for(let fk of assocCsn.keys) {
let realFk = assocCsn._parent.elements[fk.$generatedFieldName];
let pk = assocCsn.target.elements[fk.ref[0]].key;
if(pk && !(pk['@cds.api.ignore'] || realFk['@cds.api.ignore']))
{
let c = [ fk.$generatedFieldName, fk.ref[0] ];
result.constraints[c] = c;
}
}
}
}
// If this association points to a redirected Parameter EntityType, do not calculate any constraints,
// continue with multiplicity
if(assocCsn.target.isParamEntity)
{
result.constraints = Object.create(null);
}
determineMultiplicity(result._originAssocCsn);
return result;
// nested functions
function getExpressionArguments(expr)
{
let allowedTokens = [ '=', 'and', '(', ')' ];
if(expr && Array.isArray(expr))
// if some returns true, this term is not usable as a constraint term
if(!expr.some(isNotAConstraintTerm))
expr.forEach(fillConstraints)
// return true if token is not one of '=', 'and', '(', ')' or object
function isNotAConstraintTerm(tok)
{
if(tok.xpr)
return tok.xpr.some(isNotAConstraintTerm);
if(Array.isArray(tok))
return tok.some(isNotAConstraintTerm);
return !(typeof tok === 'object' && tok != null || allowedTokens.includes(tok));
}
// fill constraints object with [dependent, principal] pairs and collect all forward assocs for $self terms
function fillConstraints(arg, pos)
{
if(arg.xpr)
arg.xpr.map(fillConstraints);
else if(pos > 0 && pos < expr.length)
{
let lhs = expr[pos-1];
let rhs = expr[pos+1];
if(['='].includes(arg))
{
result.termCount++;
if(lhs.ref && rhs.ref) // ref is a path
{
lhs = lhs.ref;
rhs = rhs.ref;
// if exactly one operand starts with the prefix then this is potentially a constraint
if((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) ||
(lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name))
{
// order is always [ property, referencedProperty ]
//backlink [ self, assocName ]
let c;
if(lhs[0] === assocCsn.name)
c = [rhs[0], lhs[1]];
else
c = [lhs[0], rhs[1]];
// do we have a $self id?
// if so, store partner in selfs array
if(c[0] === NavigationProperty.DOLLAR_SELF)
result.selfs.push(c[1]);
else
result.constraints[c] = c;
}
}
}
}
}
}
function determineMultiplicity(originCsn)
{
/*
=> SRC Cardinality
CDS => EDM
------------
undef => '*' // CDS default mapping for associations
1 => 0..1 // CDS default mapping for compositions
n => '*'
n/a => 1 // not expressable
* => *
=> TGT Cardinality
CDS => EDM
------------
undef => 0..1 // CDS default mapping for associations
0..1 => 0..1
1 => 0..1
1 not null => 1 (targetMin=1 is set by transform/toOdata.js)
1..1 => 1
0..m => '*' // CDS default mapping for compositions
m => '*'
1..n => '*'
n..m => '*'
* => '*'
*/
let csn = originCsn || assocCsn;
/* new csn:
src, min, max
*/
if(!csn.cardinality)
csn.cardinality = Object.create(null);
// set missing defaults
// A managed composition is treated as an ordinary association
/*
if(csn.type == 'cds.Composition' && csn.on || csn.onCond) {
if(!csn.cardinality.src)
csn.cardinality.src = '1';
if(!csn.cardinality.min)
csn.cardinality.min = 0;
if(!csn.cardinality.max)
csn.cardinality.max = '*';
}
else */
{
if(!csn.cardinality.src)
csn.cardinality.src = '*';
if(!csn.cardinality.min)
csn.cardinality.min = 0;
if(!csn.cardinality.max)
csn.cardinality.max = 1;
}
let srcCardinality = (csn.cardinality.src == 1) ? '0..1' : '*';
let tgtCardinality = (csn.cardinality.max > 1 || csn.cardinality.max == '*') ? '*' :
(csn.cardinality.min == 1) ? '1' : '0..1';
// if originCsn was provided, reverse multiplicity for backlink
result.multiplicity = originCsn ? [tgtCardinality, srcCardinality] : [srcCardinality, tgtCardinality];
}
}
}

@@ -1229,0 +975,0 @@

@@ -127,3 +127,3 @@ 'use strict';

// FIXME: Should move to some more ODATA-specific location
function initializeModel(model, options)
function initializeModel(model, options, signal, warning)
{

@@ -158,2 +158,4 @@ if(options == undefined)

foreach(model.definitions, isStructuredArtifact, initializeAssociation);
foreach(model.definitions, isStructuredArtifact, initializeConstraints);
return service;

@@ -251,12 +253,8 @@

// Only collect @cds.valid.xy in beta-mode -> Only enable temporal in beta-mode
if(options.betaMode){
if(element['@cds.valid.key']) {
validKey.push(element);
}
if(element['@cds.valid.from']) {
validFrom.push(element);
}
if(element['@cds.valid.key']) {
validKey.push(element);
}
if(element['@cds.valid.from']) {
validFrom.push(element);
}
// Collect keys

@@ -286,4 +284,5 @@ if (element.key) {

});
struct['@Core.AlternateKeys'] = altKeys;
keys = Object.create(null);
if(struct['@Core.AlternateKeys'] === undefined) {
struct['@Core.AlternateKeys'] = altKeys;
} keys = Object.create(null);
validKey.forEach(e => {

@@ -296,2 +295,3 @@ e.key = true;

validFrom.forEach(e => {
e.key = true;
keys[e.name] = e;

@@ -340,3 +340,3 @@ });

// target to <Type>Parameters entity (see initializeParameterizedEntityOrView() above)
// Preserve the original target for backlinks (see edm.js NavigationProperty.getReferentialConstraints()
// Preserve the original target for backlinks (getReferentialConstraints()
// for details

@@ -361,2 +361,30 @@ if(element.target._containerEntity && element.target.params) {

function initializeConstraints(struct) {
foreach(struct.elements, isAssociationOrComposition, element => {
if (element._ignore) return;
element._constraints = getReferentialConstraints(element, signal, warning);
// only in V2 we must set the target cardinality of the backlink to the forward:
if(element._constraints._originAssocCsn && element.cardinality && element.cardinality.max) {
if(element._constraints._originAssocCsn.cardinality) {
if(element._constraints._originAssocCsn.cardinality.src) {
let srcMult = (element._constraints._originAssocCsn.cardinality.src == 1) ? '0..1' : '*';
let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
if(options.isV2() && srcMult != newMult) {
// Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
signal(warning`Source cardinality "${element._constraints._originAssocCsn.cardinality.src}" of "${element._constraints._originAssocCsn._parent.name}/${element._constraints._originAssocCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
}
}
else {
element._constraints._originAssocCsn.cardinality.src = element.cardinality.max;
}
}
else {
element._constraints._originAssocCsn.cardinality = { src: element.cardinality.max };
}
}
});
}
/*

@@ -403,2 +431,252 @@ If an element is annotated with @Common.ValueList.entity, an additional

function getReferentialConstraints(assocCsn, signal, warning)
{
let result = { constraints: Object.create(null), selfs: [], termCount: 0 };
if(assocCsn.on)
{
// fill constraint array with [prop, depProp]
getExpressionArguments(assocCsn.on);
// for all $self conditions, fill constraints of partner (if any)
let isBacklink = result.selfs.length == 1 && result.termCount == 1;
/* example for originalTarget:
entity E (with parameters) {
... keys and all the stuff ...
toE: association to E;
back: association to E on back.toE = $self
}
toE target 'E' is redirected to 'EParameters' (must be as the new parameter list is required)
back target 'E' is also redirected to 'EParameters' (otherwise backlink would fail)
ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is
required for that
*/
result.selfs.forEach(partner => {
let originAssocCsn = assocCsn.target.elements[partner];
if(originAssocCsn == undefined && assocCsn.originalTarget)
originAssocCsn = assocCsn.originalTarget.elements[partner];
if(originAssocCsn === undefined) {
signal(warning`Cannot resolve backlink to ${assocCsn.target.name}/${partner}" from "${assocCsn._parent.name}/${assocCsn.name}"`);
}
else
{
if(originAssocCsn.target != assocCsn._parent) {
signal(warning`"${assocCsn._parent.name}/${assocCsn.name}" references "${originAssocCsn._parent.name}/${partner}" in $self ON condition with target "${originAssocCsn.target.name}"`);
}
// let originAssocCsn = assocCsn.target.elements[partner] || assocCsn.originalTarget.elements[partner];
if(isAssociationOrComposition(originAssocCsn)) {
// if the assoc is marked as primary key, add all its foreign keys as constraint
// as they are primary keys of the other entity as well
if(!assocCsn.target.isParamEntity && originAssocCsn.key) {
if(originAssocCsn.keys) {
for(let fk of originAssocCsn.keys) {
let c = [ fk.ref[0], fk.$generatedFieldName ];
result.constraints[c] = c;
}
}
}
// Mark this association as backlink if $self appears exactly once
// to surpress edm:Association generation in V2 mode
if(isBacklink) {
// use first backlink as partner
if(originAssocCsn._partnerCsn.length == 0) {
result._originAssocCsn = originAssocCsn;
}
else {
isBacklink = false;
}
// collect all backlinks at forward association
originAssocCsn._partnerCsn.push(assocCsn);
}
}
else {
/*
entity E {
key id : Integer;
toMe: associaton to E on toMe.id = $self; };
*/
throw Error('Backlink association element is not an association or composition: "' + originAssocCsn.name);
}
}
});
if(!assocCsn.target.isParamEntity) {
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
let dependentEntity = assocCsn._parent;
let principalEntity = assocCsn.target;
if(assocCsn.type == 'cds.Composition') {
principalEntity = assocCsn._parent;
dependentEntity = assocCsn.target;
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
Object.keys(result.constraints).forEach(cn => {
result.constraints[cn] = [ result.constraints[cn][1], result.constraints[cn][0] ] } );
}
// Remove all arget elements that are not key in the principal entity
// and all elements that annotated with '@cds.api.ignore'
foreach(result.constraints,
c => {
let fk = dependentEntity.elements[c[0]];
let pk = principalEntity.key[c[1]];
return !(pk && fk && !(pk['@cds.api.ignore'] || fk['@cds.api.ignore']));
},
(c, cn) => { delete result.constraints[cn]; } );
}
}
// Handle managed association, a managed composition is treated as association
else
{
// If FK is key in target => constraint
// Don't consider primary key associations (fks become keys on the source entity) as
// this would impose a constraint against the target.
// Filter out all elements that annotated with '@cds.api.ignore'
// FIXME: If path is something structured, perform a path resolution (or use augmented CSN)
if(!assocCsn.target.isParamEntity && assocCsn.keys) {
for(let fk of assocCsn.keys) {
let realFk = assocCsn._parent.elements[fk.$generatedFieldName];
let pk = assocCsn.target.elements[fk.ref[0]].key;
if(pk && !(pk['@cds.api.ignore'] || realFk['@cds.api.ignore']))
{
let c = [ fk.$generatedFieldName, fk.ref[0] ];
result.constraints[c] = c;
}
}
}
}
// If this association points to a redirected Parameter EntityType, do not calculate any constraints,
// continue with multiplicity
if(assocCsn.target.isParamEntity)
{
result.constraints = Object.create(null);
}
return result;
// nested functions
function getExpressionArguments(expr)
{
let allowedTokens = [ '=', 'and', '(', ')' ];
if(expr && Array.isArray(expr))
// if some returns true, this term is not usable as a constraint term
if(!expr.some(isNotAConstraintTerm))
expr.forEach(fillConstraints)
// return true if token is not one of '=', 'and', '(', ')' or object
function isNotAConstraintTerm(tok)
{
if(tok.xpr)
return tok.xpr.some(isNotAConstraintTerm);
if(Array.isArray(tok))
return tok.some(isNotAConstraintTerm);
return !(typeof tok === 'object' && tok != null || allowedTokens.includes(tok));
}
// fill constraints object with [dependent, principal] pairs and collect all forward assocs for $self terms
function fillConstraints(arg, pos)
{
if(arg.xpr)
arg.xpr.map(fillConstraints);
else if(pos > 0 && pos < expr.length)
{
let lhs = expr[pos-1];
let rhs = expr[pos+1];
if(['='].includes(arg))
{
result.termCount++;
if(lhs.ref && rhs.ref) // ref is a path
{
lhs = lhs.ref;
rhs = rhs.ref;
// if exactly one operand starts with the prefix then this is potentially a constraint
if((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) ||
(lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name))
{
// order is always [ property, referencedProperty ]
//backlink [ self, assocName ]
let c;
if(lhs[0] === assocCsn.name)
c = [rhs[0], lhs[1]];
else
c = [lhs[0], rhs[1]];
// do we have a $self id?
// if so, store partner in selfs array
if(c[0] === '$self')
result.selfs.push(c[1]);
else
result.constraints[c] = c;
}
}
}
}
}
}
}
function determineMultiplicity(csn)
{
/*
=> SRC Cardinality
CDS => EDM
------------
undef => '*' // CDS default mapping for associations
1 => 0..1 // CDS default mapping for compositions
n => '*'
n/a => 1 // not expressable
* => *
=> TGT Cardinality
CDS => EDM
------------
undef => 0..1 // CDS default mapping for associations
0..1 => 0..1
1 => 0..1
1 not null => 1 (targetMin=1 is set by transform/toOdata.js)
1..1 => 1
0..m => '*' // CDS default mapping for compositions
m => '*'
1..n => '*'
n..m => '*'
* => '*'
*/
/* new csn:
src, min, max
*/
if(!csn.cardinality)
csn.cardinality = Object.create(null);
// set missing defaults
// A managed composition is treated as an ordinary association
/*
if(csn.type == 'cds.Composition' && csn.on || csn.onCond) {
if(!csn.cardinality.src)
csn.cardinality.src = '1';
if(!csn.cardinality.min)
csn.cardinality.min = 0;
if(!csn.cardinality.max)
csn.cardinality.max = '*';
}
else */
{
if(!csn.cardinality.src)
csn.cardinality.src = '*';
if(!csn.cardinality.min)
csn.cardinality.min = 0;
if(!csn.cardinality.max)
csn.cardinality.max = 1;
}
let srcCardinality = (csn.cardinality.src == 1) ? '0..1' : '*';
let tgtCardinality = (csn.cardinality.max > 1 || csn.cardinality.max == '*') ? '*' :
(csn.cardinality.min == 1) ? '1' : '0..1';
return [srcCardinality, tgtCardinality];
}
function mapCdsToEdmType(cdsType, isV2=false, isMediaType=false)

@@ -495,4 +773,6 @@ {

initializeModel,
getReferentialConstraints,
determineMultiplicity,
mapCdsToEdmType,
addTypeFacets
}

@@ -28,10 +28,27 @@ // Generated from JSON.g4 by ANTLR 4.7.1

function unquote(s) {
return JSON.parse(s); // unquote for free
function unquoteNumber(s) {
return JSON.parse(s); // unquote
}
function unquoteString(s) {
if(s.startsWith("'") && s.endsWith("'")) {
let ss = s.substring(1,s.length-1);
ss = ss.replace(/\\/g, '\\\\')
ss = ss.replace(/\"/g, '\\"')
s = '"' + ss + '"';
}
if(!s.startsWith('"') && !s.endsWith('"') && !isCharNumber(s[0])) { // plain string
s = '"' + s + '"';
}
return JSON.parse(s); // unquote
function isCharNumber(c) {
return c >= '0' && c <= '9';
}
}
var serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964",
"\u0002\u0010\u00a5\b\u0001\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004",
"\u0002\u0010\u00ed\b\u0001\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004",
"\u0004\t\u0004\u0004\u0005\t\u0005\u0004\u0006\t\u0006\u0004\u0007\t",

@@ -41,100 +58,151 @@ "\u0007\u0004\b\t\b\u0004\t\t\t\u0004\n\t\n\u0004\u000b\t\u000b\u0004",

"\t\u0010\u0004\u0011\t\u0011\u0004\u0012\t\u0012\u0004\u0013\t\u0013",
"\u0004\u0014\t\u0014\u0003\u0002\u0003\u0002\u0003\u0003\u0003\u0003",
"\u0003\u0004\u0003\u0004\u0003\u0005\u0003\u0005\u0003\u0006\u0003\u0006",
"\u0003\u0007\u0003\u0007\u0003\b\u0003\b\u0003\b\u0003\b\u0003\b\u0003",
"\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\n\u0003\n\u0003\n\u0003",
"\n\u0003\n\u0003\u000b\u0003\u000b\u0003\u000b\u0007\u000bI\n\u000b",
"\f\u000b\u000e\u000bL\u000b\u000b\u0003\u000b\u0003\u000b\u0003\f\u0003",
"\f\u0003\f\u0005\fS\n\f\u0003\r\u0003\r\u0003\r\u0003\r\u0003\r\u0003",
"\r\u0003\u000e\u0003\u000e\u0003\u000f\u0005\u000f^\n\u000f\u0003\u000f",
"\u0003\u000f\u0003\u000f\u0006\u000fc\n\u000f\r\u000f\u000e\u000fd\u0003",
"\u000f\u0005\u000fh\n\u000f\u0003\u000f\u0005\u000fk\n\u000f\u0003\u000f",
"\u0003\u000f\u0003\u000f\u0003\u000f\u0005\u000fq\n\u000f\u0003\u000f",
"\u0005\u000ft\n\u000f\u0003\u0010\u0003\u0010\u0003\u0010\u0007\u0010",
"y\n\u0010\f\u0010\u000e\u0010|\u000b\u0010\u0005\u0010~\n\u0010\u0003",
"\u0011\u0003\u0011\u0005\u0011\u0082\n\u0011\u0003\u0011\u0003\u0011",
"\u0003\u0012\u0006\u0012\u0087\n\u0012\r\u0012\u000e\u0012\u0088\u0003",
"\u0012\u0003\u0012\u0003\u0013\u0003\u0013\u0003\u0013\u0003\u0013\u0007",
"\u0013\u0091\n\u0013\f\u0013\u000e\u0013\u0094\u000b\u0013\u0003\u0013",
"\u0003\u0013\u0003\u0013\u0003\u0013\u0003\u0013\u0003\u0014\u0003\u0014",
"\u0003\u0014\u0003\u0014\u0007\u0014\u009f\n\u0014\f\u0014\u000e\u0014",
"\u00a2\u000b\u0014\u0003\u0014\u0003\u0014\u0003\u0092\u0002\u0015\u0003",
"\u0003\u0005\u0004\u0007\u0005\t\u0006\u000b\u0007\r\b\u000f\t\u0011",
"\n\u0013\u000b\u0015\f\u0017\u0002\u0019\u0002\u001b\u0002\u001d\r\u001f",
"\u0002!\u0002#\u000e%\u000f\'\u0010\u0003\u0002\u000b\u0004\u0002$$",
"^^\n\u0002$$11^^ddhhppttvv\u0005\u00022;CHch\u0003\u00022;\u0003\u0002",
"3;\u0004\u0002GGgg\u0004\u0002--//\u0005\u0002\u000b\f\u000f\u000f\"",
"\"\u0004\u0002\f\f\u000f\u000f\u0002\u00af\u0002\u0003\u0003\u0002\u0002",
"\u0002\u0002\u0005\u0003\u0002\u0002\u0002\u0002\u0007\u0003\u0002\u0002",
"\u0002\u0002\t\u0003\u0002\u0002\u0002\u0002\u000b\u0003\u0002\u0002",
"\u0002\u0002\r\u0003\u0002\u0002\u0002\u0002\u000f\u0003\u0002\u0002",
"\u0002\u0002\u0011\u0003\u0002\u0002\u0002\u0002\u0013\u0003\u0002\u0002",
"\u0002\u0002\u0015\u0003\u0002\u0002\u0002\u0002\u001d\u0003\u0002\u0002",
"\u0002\u0002#\u0003\u0002\u0002\u0002\u0002%\u0003\u0002\u0002\u0002",
"\u0002\'\u0003\u0002\u0002\u0002\u0003)\u0003\u0002\u0002\u0002\u0005",
"+\u0003\u0002\u0002\u0002\u0007-\u0003\u0002\u0002\u0002\t/\u0003\u0002",
"\u0002\u0002\u000b1\u0003\u0002\u0002\u0002\r3\u0003\u0002\u0002\u0002",
"\u000f5\u0003\u0002\u0002\u0002\u0011:\u0003\u0002\u0002\u0002\u0013",
"@\u0003\u0002\u0002\u0002\u0015E\u0003\u0002\u0002\u0002\u0017O\u0003",
"\u0002\u0002\u0002\u0019T\u0003\u0002\u0002\u0002\u001bZ\u0003\u0002",
"\u0002\u0002\u001ds\u0003\u0002\u0002\u0002\u001f}\u0003\u0002\u0002",
"\u0002!\u007f\u0003\u0002\u0002\u0002#\u0086\u0003\u0002\u0002\u0002",
"%\u008c\u0003\u0002\u0002\u0002\'\u009a\u0003\u0002\u0002\u0002)*\u0007",
"}\u0002\u0002*\u0004\u0003\u0002\u0002\u0002+,\u0007.\u0002\u0002,\u0006",
"\u0003\u0002\u0002\u0002-.\u0007\u007f\u0002\u0002.\b\u0003\u0002\u0002",
"\u0002/0\u0007<\u0002\u00020\n\u0003\u0002\u0002\u000212\u0007]\u0002",
"\u00022\f\u0003\u0002\u0002\u000234\u0007_\u0002\u00024\u000e\u0003",
"\u0002\u0002\u000256\u0007v\u0002\u000267\u0007t\u0002\u000278\u0007",
"w\u0002\u000289\u0007g\u0002\u00029\u0010\u0003\u0002\u0002\u0002:;",
"\u0007h\u0002\u0002;<\u0007c\u0002\u0002<=\u0007n\u0002\u0002=>\u0007",
"u\u0002\u0002>?\u0007g\u0002\u0002?\u0012\u0003\u0002\u0002\u0002@A",
"\u0007p\u0002\u0002AB\u0007w\u0002\u0002BC\u0007n\u0002\u0002CD\u0007",
"n\u0002\u0002D\u0014\u0003\u0002\u0002\u0002EJ\u0007$\u0002\u0002FI",
"\u0005\u0017\f\u0002GI\n\u0002\u0002\u0002HF\u0003\u0002\u0002\u0002",
"HG\u0003\u0002\u0002\u0002IL\u0003\u0002\u0002\u0002JH\u0003\u0002\u0002",
"\u0002JK\u0003\u0002\u0002\u0002KM\u0003\u0002\u0002\u0002LJ\u0003\u0002",
"\u0002\u0002MN\u0007$\u0002\u0002N\u0016\u0003\u0002\u0002\u0002OR\u0007",
"^\u0002\u0002PS\t\u0003\u0002\u0002QS\u0005\u0019\r\u0002RP\u0003\u0002",
"\u0002\u0002RQ\u0003\u0002\u0002\u0002S\u0018\u0003\u0002\u0002\u0002",
"TU\u0007w\u0002\u0002UV\u0005\u001b\u000e\u0002VW\u0005\u001b\u000e",
"\u0002WX\u0005\u001b\u000e\u0002XY\u0005\u001b\u000e\u0002Y\u001a\u0003",
"\u0002\u0002\u0002Z[\t\u0004\u0002\u0002[\u001c\u0003\u0002\u0002\u0002",
"\\^\u0007/\u0002\u0002]\\\u0003\u0002\u0002\u0002]^\u0003\u0002\u0002",
"\u0002^_\u0003\u0002\u0002\u0002_`\u0005\u001f\u0010\u0002`b\u00070",
"\u0002\u0002ac\t\u0005\u0002\u0002ba\u0003\u0002\u0002\u0002cd\u0003",
"\u0002\u0002\u0002db\u0003\u0002\u0002\u0002de\u0003\u0002\u0002\u0002",
"eg\u0003\u0002\u0002\u0002fh\u0005!\u0011\u0002gf\u0003\u0002\u0002",
"\u0002gh\u0003\u0002\u0002\u0002ht\u0003\u0002\u0002\u0002ik\u0007/",
"\u0002\u0002ji\u0003\u0002\u0002\u0002jk\u0003\u0002\u0002\u0002kl\u0003",
"\u0002\u0002\u0002lm\u0005\u001f\u0010\u0002mn\u0005!\u0011\u0002nt",
"\u0003\u0002\u0002\u0002oq\u0007/\u0002\u0002po\u0003\u0002\u0002\u0002",
"pq\u0003\u0002\u0002\u0002qr\u0003\u0002\u0002\u0002rt\u0005\u001f\u0010",
"\u0002s]\u0003\u0002\u0002\u0002sj\u0003\u0002\u0002\u0002sp\u0003\u0002",
"\u0002\u0002t\u001e\u0003\u0002\u0002\u0002u~\u00072\u0002\u0002vz\t",
"\u0006\u0002\u0002wy\t\u0005\u0002\u0002xw\u0003\u0002\u0002\u0002y",
"|\u0003\u0002\u0002\u0002zx\u0003\u0002\u0002\u0002z{\u0003\u0002\u0002",
"\u0002{~\u0003\u0002\u0002\u0002|z\u0003\u0002\u0002\u0002}u\u0003\u0002",
"\u0002\u0002}v\u0003\u0002\u0002\u0002~ \u0003\u0002\u0002\u0002\u007f",
"\u0081\t\u0007\u0002\u0002\u0080\u0082\t\b\u0002\u0002\u0081\u0080\u0003",
"\u0002\u0002\u0002\u0081\u0082\u0003\u0002\u0002\u0002\u0082\u0083\u0003",
"\u0002\u0002\u0002\u0083\u0084\u0005\u001f\u0010\u0002\u0084\"\u0003",
"\u0002\u0002\u0002\u0085\u0087\t\t\u0002\u0002\u0086\u0085\u0003\u0002",
"\u0002\u0002\u0087\u0088\u0003\u0002\u0002\u0002\u0088\u0086\u0003\u0002",
"\u0002\u0002\u0088\u0089\u0003\u0002\u0002\u0002\u0089\u008a\u0003\u0002",
"\u0002\u0002\u008a\u008b\b\u0012\u0002\u0002\u008b$\u0003\u0002\u0002",
"\u0002\u008c\u008d\u00071\u0002\u0002\u008d\u008e\u0007,\u0002\u0002",
"\u008e\u0092\u0003\u0002\u0002\u0002\u008f\u0091\u000b\u0002\u0002\u0002",
"\u0090\u008f\u0003\u0002\u0002\u0002\u0091\u0094\u0003\u0002\u0002\u0002",
"\u0092\u0093\u0003\u0002\u0002\u0002\u0092\u0090\u0003\u0002\u0002\u0002",
"\u0093\u0095\u0003\u0002\u0002\u0002\u0094\u0092\u0003\u0002\u0002\u0002",
"\u0095\u0096\u0007,\u0002\u0002\u0096\u0097\u00071\u0002\u0002\u0097",
"\u0098\u0003\u0002\u0002\u0002\u0098\u0099\b\u0013\u0002\u0002\u0099",
"&\u0003\u0002\u0002\u0002\u009a\u009b\u00071\u0002\u0002\u009b\u009c",
"\u00071\u0002\u0002\u009c\u00a0\u0003\u0002\u0002\u0002\u009d\u009f",
"\n\n\u0002\u0002\u009e\u009d\u0003\u0002\u0002\u0002\u009f\u00a2\u0003",
"\u0002\u0002\u0002\u00a0\u009e\u0003\u0002\u0002\u0002\u00a0\u00a1\u0003",
"\u0002\u0002\u0002\u00a1\u00a3\u0003\u0002\u0002\u0002\u00a2\u00a0\u0003",
"\u0002\u0002\u0002\u00a3\u00a4\b\u0014\u0002\u0002\u00a4(\u0003\u0002",
"\u0002\u0002\u0012\u0002HJR]dgjpsz}\u0081\u0088\u0092\u00a0\u0003\b",
"\u0002\u0002"].join("");
"\u0004\u0014\t\u0014\u0004\u0015\t\u0015\u0004\u0016\t\u0016\u0004\u0017",
"\t\u0017\u0004\u0018\t\u0018\u0004\u0019\t\u0019\u0004\u001a\t\u001a",
"\u0004\u001b\t\u001b\u0004\u001c\t\u001c\u0004\u001d\t\u001d\u0004\u001e",
"\t\u001e\u0004\u001f\t\u001f\u0004 \t \u0003\u0002\u0003\u0002\u0003",
"\u0003\u0003\u0003\u0003\u0004\u0003\u0004\u0003\u0005\u0003\u0005\u0003",
"\u0006\u0003\u0006\u0003\u0007\u0003\u0007\u0003\b\u0003\b\u0003\b\u0003",
"\b\u0003\b\u0003\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\n\u0003",
"\n\u0003\n\u0003\n\u0003\n\u0003\u000b\u0003\u000b\u0003\u000b\u0005",
"\u000ba\n\u000b\u0003\f\u0003\f\u0003\f\u0003\f\u0007\fg\n\f\f\f\u000e",
"\fj\u000b\f\u0003\r\u0003\r\u0003\r\u0003\r\u0003\r\u0003\r\u0005\r",
"r\n\r\u0003\u000e\u0003\u000e\u0003\u000f\u0003\u000f\u0003\u0010\u0003",
"\u0010\u0003\u0011\u0003\u0011\u0003\u0012\u0003\u0012\u0003\u0013\u0003",
"\u0013\u0003\u0014\u0003\u0014\u0003\u0015\u0003\u0015\u0003\u0016\u0003",
"\u0016\u0003\u0016\u0007\u0016\u0087\n\u0016\f\u0016\u000e\u0016\u008a",
"\u000b\u0016\u0003\u0016\u0003\u0016\u0003\u0017\u0003\u0017\u0003\u0017",
"\u0007\u0017\u0091\n\u0017\f\u0017\u000e\u0017\u0094\u000b\u0017\u0003",
"\u0017\u0003\u0017\u0003\u0018\u0003\u0018\u0003\u0018\u0005\u0018\u009b",
"\n\u0018\u0003\u0019\u0003\u0019\u0003\u0019\u0003\u0019\u0003\u0019",
"\u0003\u0019\u0003\u001a\u0003\u001a\u0003\u001b\u0005\u001b\u00a6\n",
"\u001b\u0003\u001b\u0003\u001b\u0003\u001b\u0006\u001b\u00ab\n\u001b",
"\r\u001b\u000e\u001b\u00ac\u0003\u001b\u0005\u001b\u00b0\n\u001b\u0003",
"\u001b\u0005\u001b\u00b3\n\u001b\u0003\u001b\u0003\u001b\u0003\u001b",
"\u0003\u001b\u0005\u001b\u00b9\n\u001b\u0003\u001b\u0005\u001b\u00bc",
"\n\u001b\u0003\u001c\u0003\u001c\u0003\u001c\u0007\u001c\u00c1\n\u001c",
"\f\u001c\u000e\u001c\u00c4\u000b\u001c\u0005\u001c\u00c6\n\u001c\u0003",
"\u001d\u0003\u001d\u0005\u001d\u00ca\n\u001d\u0003\u001d\u0003\u001d",
"\u0003\u001e\u0006\u001e\u00cf\n\u001e\r\u001e\u000e\u001e\u00d0\u0003",
"\u001e\u0003\u001e\u0003\u001f\u0003\u001f\u0003\u001f\u0003\u001f\u0007",
"\u001f\u00d9\n\u001f\f\u001f\u000e\u001f\u00dc\u000b\u001f\u0003\u001f",
"\u0003\u001f\u0003\u001f\u0003\u001f\u0003\u001f\u0003 \u0003 \u0003",
" \u0003 \u0007 \u00e7\n \f \u000e \u00ea\u000b \u0003 \u0003 \u0003",
"\u00da\u0002!\u0003\u0003\u0005\u0004\u0007\u0005\t\u0006\u000b\u0007",
"\r\b\u000f\t\u0011\n\u0013\u000b\u0015\f\u0017\u0002\u0019\u0002\u001b",
"\u0002\u001d\u0002\u001f\u0002!\u0002#\u0002%\u0002\'\u0002)\u0002+",
"\u0002-\u0002/\u00021\u00023\u00025\r7\u00029\u0002;\u000e=\u000f?\u0010",
"\u0003\u0002\u000e\u0007\u0002##%(--`a\u0080\u0080\u0004\u0002C\\c|",
"\u0003\u00022;\u0004\u0002))^^\u0004\u0002$$^^\n\u0002$$11^^ddhhppt",
"tvv\u0005\u00022;CHch\u0003\u00023;\u0004\u0002GGgg\u0004\u0002--//",
"\u0005\u0002\u000b\f\u000f\u000f\"\"\u0004\u0002\f\f\u000f\u000f\u0002",
"\u00f7\u0002\u0003\u0003\u0002\u0002\u0002\u0002\u0005\u0003\u0002\u0002",
"\u0002\u0002\u0007\u0003\u0002\u0002\u0002\u0002\t\u0003\u0002\u0002",
"\u0002\u0002\u000b\u0003\u0002\u0002\u0002\u0002\r\u0003\u0002\u0002",
"\u0002\u0002\u000f\u0003\u0002\u0002\u0002\u0002\u0011\u0003\u0002\u0002",
"\u0002\u0002\u0013\u0003\u0002\u0002\u0002\u0002\u0015\u0003\u0002\u0002",
"\u0002\u00025\u0003\u0002\u0002\u0002\u0002;\u0003\u0002\u0002\u0002",
"\u0002=\u0003\u0002\u0002\u0002\u0002?\u0003\u0002\u0002\u0002\u0003",
"A\u0003\u0002\u0002\u0002\u0005C\u0003\u0002\u0002\u0002\u0007E\u0003",
"\u0002\u0002\u0002\tG\u0003\u0002\u0002\u0002\u000bI\u0003\u0002\u0002",
"\u0002\rK\u0003\u0002\u0002\u0002\u000fM\u0003\u0002\u0002\u0002\u0011",
"R\u0003\u0002\u0002\u0002\u0013X\u0003\u0002\u0002\u0002\u0015`\u0003",
"\u0002\u0002\u0002\u0017b\u0003\u0002\u0002\u0002\u0019q\u0003\u0002",
"\u0002\u0002\u001bs\u0003\u0002\u0002\u0002\u001du\u0003\u0002\u0002",
"\u0002\u001fw\u0003\u0002\u0002\u0002!y\u0003\u0002\u0002\u0002#{\u0003",
"\u0002\u0002\u0002%}\u0003\u0002\u0002\u0002\'\u007f\u0003\u0002\u0002",
"\u0002)\u0081\u0003\u0002\u0002\u0002+\u0083\u0003\u0002\u0002\u0002",
"-\u008d\u0003\u0002\u0002\u0002/\u0097\u0003\u0002\u0002\u00021\u009c",
"\u0003\u0002\u0002\u00023\u00a2\u0003\u0002\u0002\u00025\u00bb\u0003",
"\u0002\u0002\u00027\u00c5\u0003\u0002\u0002\u00029\u00c7\u0003\u0002",
"\u0002\u0002;\u00ce\u0003\u0002\u0002\u0002=\u00d4\u0003\u0002\u0002",
"\u0002?\u00e2\u0003\u0002\u0002\u0002AB\u0007}\u0002\u0002B\u0004\u0003",
"\u0002\u0002\u0002CD\u0007.\u0002\u0002D\u0006\u0003\u0002\u0002\u0002",
"EF\u0007\u007f\u0002\u0002F\b\u0003\u0002\u0002\u0002GH\u0007<\u0002",
"\u0002H\n\u0003\u0002\u0002\u0002IJ\u0007]\u0002\u0002J\f\u0003\u0002",
"\u0002\u0002KL\u0007_\u0002\u0002L\u000e\u0003\u0002\u0002\u0002MN\u0007",
"v\u0002\u0002NO\u0007t\u0002\u0002OP\u0007w\u0002\u0002PQ\u0007g\u0002",
"\u0002Q\u0010\u0003\u0002\u0002\u0002RS\u0007h\u0002\u0002ST\u0007c",
"\u0002\u0002TU\u0007n\u0002\u0002UV\u0007u\u0002\u0002VW\u0007g\u0002",
"\u0002W\u0012\u0003\u0002\u0002\u0002XY\u0007p\u0002\u0002YZ\u0007w",
"\u0002\u0002Z[\u0007n\u0002\u0002[\\\u0007n\u0002\u0002\\\u0014\u0003",
"\u0002\u0002\u0002]a\u0005+\u0016\u0002^a\u0005-\u0017\u0002_a\u0005",
"\u0017\f\u0002`]\u0003\u0002\u0002\u0002`^\u0003\u0002\u0002\u0002`",
"_\u0003\u0002\u0002\u0002a\u0016\u0003\u0002\u0002\u0002bh\u0005\u0019",
"\r\u0002cg\u0005\u0019\r\u0002dg\u0005)\u0015\u0002eg\u0005%\u0013\u0002",
"fc\u0003\u0002\u0002\u0002fd\u0003\u0002\u0002\u0002fe\u0003\u0002\u0002",
"\u0002gj\u0003\u0002\u0002\u0002hf\u0003\u0002\u0002\u0002hi\u0003\u0002",
"\u0002\u0002i\u0018\u0003\u0002\u0002\u0002jh\u0003\u0002\u0002\u0002",
"kr\u0005\u001d\u000f\u0002lr\u0005\u001b\u000e\u0002mr\u0005\u001f\u0010",
"\u0002nr\u0005!\u0011\u0002or\u0005#\u0012\u0002pr\u0005\'\u0014\u0002",
"qk\u0003\u0002\u0002\u0002ql\u0003\u0002\u0002\u0002qm\u0003\u0002\u0002",
"\u0002qn\u0003\u0002\u0002\u0002qo\u0003\u0002\u0002\u0002qp\u0003\u0002",
"\u0002\u0002r\u001a\u0003\u0002\u0002\u0002st\t\u0002\u0002\u0002t\u001c",
"\u0003\u0002\u0002\u0002uv\t\u0003\u0002\u0002v\u001e\u0003\u0002\u0002",
"\u0002wx\u0007,\u0002\u0002x \u0003\u0002\u0002\u0002yz\u0007A\u0002",
"\u0002z\"\u0003\u0002\u0002\u0002{|\u00070\u0002\u0002|$\u0003\u0002",
"\u0002\u0002}~\u0007/\u0002\u0002~&\u0003\u0002\u0002\u0002\u007f\u0080",
"\u0007B\u0002\u0002\u0080(\u0003\u0002\u0002\u0002\u0081\u0082\t\u0004",
"\u0002\u0002\u0082*\u0003\u0002\u0002\u0002\u0083\u0088\u0007)\u0002",
"\u0002\u0084\u0087\u0005/\u0018\u0002\u0085\u0087\n\u0005\u0002\u0002",
"\u0086\u0084\u0003\u0002\u0002\u0002\u0086\u0085\u0003\u0002\u0002\u0002",
"\u0087\u008a\u0003\u0002\u0002\u0002\u0088\u0086\u0003\u0002\u0002\u0002",
"\u0088\u0089\u0003\u0002\u0002\u0002\u0089\u008b\u0003\u0002\u0002\u0002",
"\u008a\u0088\u0003\u0002\u0002\u0002\u008b\u008c\u0007)\u0002\u0002",
"\u008c,\u0003\u0002\u0002\u0002\u008d\u0092\u0007$\u0002\u0002\u008e",
"\u0091\u0005/\u0018\u0002\u008f\u0091\n\u0006\u0002\u0002\u0090\u008e",
"\u0003\u0002\u0002\u0002\u0090\u008f\u0003\u0002\u0002\u0002\u0091\u0094",
"\u0003\u0002\u0002\u0002\u0092\u0090\u0003\u0002\u0002\u0002\u0092\u0093",
"\u0003\u0002\u0002\u0002\u0093\u0095\u0003\u0002\u0002\u0002\u0094\u0092",
"\u0003\u0002\u0002\u0002\u0095\u0096\u0007$\u0002\u0002\u0096.\u0003",
"\u0002\u0002\u0002\u0097\u009a\u0007^\u0002\u0002\u0098\u009b\t\u0007",
"\u0002\u0002\u0099\u009b\u00051\u0019\u0002\u009a\u0098\u0003\u0002",
"\u0002\u0002\u009a\u0099\u0003\u0002\u0002\u0002\u009b0\u0003\u0002",
"\u0002\u0002\u009c\u009d\u0007w\u0002\u0002\u009d\u009e\u00053\u001a",
"\u0002\u009e\u009f\u00053\u001a\u0002\u009f\u00a0\u00053\u001a\u0002",
"\u00a0\u00a1\u00053\u001a\u0002\u00a12\u0003\u0002\u0002\u0002\u00a2",
"\u00a3\t\b\u0002\u0002\u00a34\u0003\u0002\u0002\u0002\u00a4\u00a6\u0007",
"/\u0002\u0002\u00a5\u00a4\u0003\u0002\u0002\u0002\u00a5\u00a6\u0003",
"\u0002\u0002\u0002\u00a6\u00a7\u0003\u0002\u0002\u0002\u00a7\u00a8\u0005",
"7\u001c\u0002\u00a8\u00aa\u00070\u0002\u0002\u00a9\u00ab\t\u0004\u0002",
"\u0002\u00aa\u00a9\u0003\u0002\u0002\u0002\u00ab\u00ac\u0003\u0002\u0002",
"\u0002\u00ac\u00aa\u0003\u0002\u0002\u0002\u00ac\u00ad\u0003\u0002\u0002",
"\u0002\u00ad\u00af\u0003\u0002\u0002\u0002\u00ae\u00b0\u00059\u001d",
"\u0002\u00af\u00ae\u0003\u0002\u0002\u0002\u00af\u00b0\u0003\u0002\u0002",
"\u0002\u00b0\u00bc\u0003\u0002\u0002\u0002\u00b1\u00b3\u0007/\u0002",
"\u0002\u00b2\u00b1\u0003\u0002\u0002\u0002\u00b2\u00b3\u0003\u0002\u0002",
"\u0002\u00b3\u00b4\u0003\u0002\u0002\u0002\u00b4\u00b5\u00057\u001c",
"\u0002\u00b5\u00b6\u00059\u001d\u0002\u00b6\u00bc\u0003\u0002\u0002",
"\u0002\u00b7\u00b9\u0007/\u0002\u0002\u00b8\u00b7\u0003\u0002\u0002",
"\u0002\u00b8\u00b9\u0003\u0002\u0002\u0002\u00b9\u00ba\u0003\u0002\u0002",
"\u0002\u00ba\u00bc\u00057\u001c\u0002\u00bb\u00a5\u0003\u0002\u0002",
"\u0002\u00bb\u00b2\u0003\u0002\u0002\u0002\u00bb\u00b8\u0003\u0002\u0002",
"\u0002\u00bc6\u0003\u0002\u0002\u0002\u00bd\u00c6\u00072\u0002\u0002",
"\u00be\u00c2\t\t\u0002\u0002\u00bf\u00c1\t\u0004\u0002\u0002\u00c0\u00bf",
"\u0003\u0002\u0002\u0002\u00c1\u00c4\u0003\u0002\u0002\u0002\u00c2\u00c0",
"\u0003\u0002\u0002\u0002\u00c2\u00c3\u0003\u0002\u0002\u0002\u00c3\u00c6",
"\u0003\u0002\u0002\u0002\u00c4\u00c2\u0003\u0002\u0002\u0002\u00c5\u00bd",
"\u0003\u0002\u0002\u0002\u00c5\u00be\u0003\u0002\u0002\u0002\u00c68",
"\u0003\u0002\u0002\u0002\u00c7\u00c9\t\n\u0002\u0002\u00c8\u00ca\t\u000b",
"\u0002\u0002\u00c9\u00c8\u0003\u0002\u0002\u0002\u00c9\u00ca\u0003\u0002",
"\u0002\u0002\u00ca\u00cb\u0003\u0002\u0002\u0002\u00cb\u00cc\u00057",
"\u001c\u0002\u00cc:\u0003\u0002\u0002\u0002\u00cd\u00cf\t\f\u0002\u0002",
"\u00ce\u00cd\u0003\u0002\u0002\u0002\u00cf\u00d0\u0003\u0002\u0002\u0002",
"\u00d0\u00ce\u0003\u0002\u0002\u0002\u00d0\u00d1\u0003\u0002\u0002\u0002",
"\u00d1\u00d2\u0003\u0002\u0002\u0002\u00d2\u00d3\b\u001e\u0002\u0002",
"\u00d3<\u0003\u0002\u0002\u0002\u00d4\u00d5\u00071\u0002\u0002\u00d5",
"\u00d6\u0007,\u0002\u0002\u00d6\u00da\u0003\u0002\u0002\u0002\u00d7",
"\u00d9\u000b\u0002\u0002\u0002\u00d8\u00d7\u0003\u0002\u0002\u0002\u00d9",
"\u00dc\u0003\u0002\u0002\u0002\u00da\u00db\u0003\u0002\u0002\u0002\u00da",
"\u00d8\u0003\u0002\u0002\u0002\u00db\u00dd\u0003\u0002\u0002\u0002\u00dc",
"\u00da\u0003\u0002\u0002\u0002\u00dd\u00de\u0007,\u0002\u0002\u00de",
"\u00df\u00071\u0002\u0002\u00df\u00e0\u0003\u0002\u0002\u0002\u00e0",
"\u00e1\b\u001f\u0002\u0002\u00e1>\u0003\u0002\u0002\u0002\u00e2\u00e3",
"\u00071\u0002\u0002\u00e3\u00e4\u00071\u0002\u0002\u00e4\u00e8\u0003",
"\u0002\u0002\u0002\u00e5\u00e7\n\r\u0002\u0002\u00e6\u00e5\u0003\u0002",
"\u0002\u0002\u00e7\u00ea\u0003\u0002\u0002\u0002\u00e8\u00e6\u0003\u0002",
"\u0002\u0002\u00e8\u00e9\u0003\u0002\u0002\u0002\u00e9\u00eb\u0003\u0002",
"\u0002\u0002\u00ea\u00e8\u0003\u0002\u0002\u0002\u00eb\u00ec\b \u0002",
"\u0002\u00ec@\u0003\u0002\u0002\u0002\u0018\u0002`fhq\u0086\u0088\u0090",
"\u0092\u009a\u00a5\u00ac\u00af\u00b2\u00b8\u00bb\u00c2\u00c5\u00c9\u00d0",
"\u00da\u00e8\u0003\b\u0002\u0002"].join("");

@@ -191,5 +259,7 @@

"T__5", "T__6", "T__7", "T__8", "STRING",
"ESC", "UNICODE", "HEX", "NUMBER", "INT",
"EXP", "WHITESPACE", "MULTI_LINE_COMMENT",
"SINGLE_LINE_COMMENT" ];
"ASTRING", "STRSYM", "SPECHAR", "LETTER",
"STAR", "QUESTION", "DOT", "MINUS", "AtSIGN",
"DIGIT", "Q1STRING", "Q2STRING", "ESC",
"UNICODE", "HEX", "NUMBER", "INT", "EXP",
"WHITESPACE", "MULTI_LINE_COMMENT", "SINGLE_LINE_COMMENT" ];

@@ -196,0 +266,0 @@ JSONLexer.prototype.grammarFileName = "JSON.g4";

@@ -27,54 +27,75 @@ // Generated from JSON.g4 by ANTLR 4.7.1

function unquote(s) {
return JSON.parse(s); // unquote for free
function unquoteNumber(s) {
return JSON.parse(s); // unquote
}
function unquoteString(s) {
if(s.startsWith("'") && s.endsWith("'")) {
let ss = s.substring(1,s.length-1);
ss = ss.replace(/\\/g, '\\\\')
ss = ss.replace(/\"/g, '\\"')
s = '"' + ss + '"';
}
if(!s.startsWith('"') && !s.endsWith('"') && !isCharNumber(s[0])) { // plain string
s = '"' + s + '"';
}
return JSON.parse(s); // unquote
function isCharNumber(c) {
return c >= '0' && c <= '9';
}
}
var grammarFileName = "JSON.g4";
var serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964",
"\u0003\u0010H\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004\u0004\t",
"\u0003\u0010N\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004\u0004\t",
"\u0004\u0004\u0005\t\u0005\u0004\u0006\t\u0006\u0003\u0002\u0003\u0002",
"\u0005\u0002\u000f\n\u0002\u0003\u0003\u0003\u0003\u0003\u0003\u0003",
"\u0003\u0007\u0003\u0015\n\u0003\f\u0003\u000e\u0003\u0018\u000b\u0003",
"\u0003\u0003\u0003\u0003\u0003\u0003\u0003\u0003\u0005\u0003\u001e\n",
"\u0003\u0003\u0004\u0003\u0004\u0003\u0004\u0003\u0004\u0003\u0004\u0003",
"\u0004\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003",
"\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0007\u0005/\n\u0005\f\u0005",
"\u000e\u00052\u000b\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003",
"\u0005\u0005\u00058\n\u0005\u0003\u0006\u0003\u0006\u0003\u0006\u0003",
"\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003",
"\u0006\u0003\u0006\u0003\u0006\u0005\u0006F\n\u0006\u0003\u0006\u0002",
"\u0002\u0007\u0002\u0004\u0006\b\n\u0002\u0002\u0002M\u0002\u000e\u0003",
"\u0002\u0002\u0002\u0004\u001d\u0003\u0002\u0002\u0002\u0006\u001f\u0003",
"\u0002\u0002\u0002\b7\u0003\u0002\u0002\u0002\nE\u0003\u0002\u0002\u0002",
"\f\u000f\u0005\u0004\u0003\u0002\r\u000f\u0005\b\u0005\u0002\u000e\f",
"\u0003\u0002\u0002\u0002\u000e\r\u0003\u0002\u0002\u0002\u000f\u0003",
"\u0003\u0002\u0002\u0002\u0010\u0011\u0007\u0003\u0002\u0002\u0011\u0016",
"\u0005\u0006\u0004\u0002\u0012\u0013\u0007\u0004\u0002\u0002\u0013\u0015",
"\u0005\u0006\u0004\u0002\u0014\u0012\u0003\u0002\u0002\u0002\u0015\u0018",
"\u0003\u0002\u0002\u0002\u0016\u0014\u0003\u0002\u0002\u0002\u0016\u0017",
"\u0003\u0002\u0002\u0002\u0017\u0019\u0003\u0002\u0002\u0002\u0018\u0016",
"\u0003\u0002\u0002\u0002\u0019\u001a\u0007\u0005\u0002\u0002\u001a\u001e",
"\u0003\u0002\u0002\u0002\u001b\u001c\u0007\u0003\u0002\u0002\u001c\u001e",
"\u0007\u0005\u0002\u0002\u001d\u0010\u0003\u0002\u0002\u0002\u001d\u001b",
"\u0003\u0002\u0002\u0002\u001e\u0005\u0003\u0002\u0002\u0002\u001f ",
"\u0007\f\u0002\u0002 !\b\u0004\u0001\u0002!\"\u0007\u0006\u0002\u0002",
"\"#\u0005\n\u0006\u0002#$\b\u0004\u0001\u0002$\u0007\u0003\u0002\u0002",
"\u0002%&\u0007\u0007\u0002\u0002&\'\b\u0005\u0001\u0002\'(\u0005\n\u0006",
"\u0002(0\b\u0005\u0001\u0002)*\u0007\u0004\u0002\u0002*+\b\u0005\u0001",
"\u0002+,\u0005\n\u0006\u0002,-\b\u0005\u0001\u0002-/\u0003\u0002\u0002",
"\u0002.)\u0003\u0002\u0002\u0002/2\u0003\u0002\u0002\u00020.\u0003\u0002",
"\u0002\u000201\u0003\u0002\u0002\u000213\u0003\u0002\u0002\u000220\u0003",
"\u0002\u0002\u000234\u0007\b\u0002\u000248\u0003\u0002\u0002\u00025",
"6\u0007\u0007\u0002\u000268\u0007\b\u0002\u00027%\u0003\u0002\u0002",
"\u000275\u0003\u0002\u0002\u00028\t\u0003\u0002\u0002\u00029:\u0007",
"\f\u0002\u0002:F\b\u0006\u0001\u0002;<\u0007\r\u0002\u0002<F\b\u0006",
"\u0001\u0002=F\u0005\u0004\u0003\u0002>F\u0005\b\u0005\u0002?@\u0007",
"\t\u0002\u0002@F\b\u0006\u0001\u0002AB\u0007\n\u0002\u0002BF\b\u0006",
"\u0001\u0002CD\u0007\u000b\u0002\u0002DF\b\u0006\u0001\u0002E9\u0003",
"\u0002\u0002\u0002E;\u0003\u0002\u0002\u0002E=\u0003\u0002\u0002\u0002",
"E>\u0003\u0002\u0002\u0002E?\u0003\u0002\u0002\u0002EA\u0003\u0002\u0002",
"\u0002EC\u0003\u0002\u0002\u0002F\u000b\u0003\u0002\u0002\u0002\b\u000e",
"\u0016\u001d07E"].join("");
"\u0003\u0003\u0005\u0003\u001b\n\u0003\u0003\u0003\u0003\u0003\u0003",
"\u0003\u0003\u0003\u0005\u0003!\n\u0003\u0003\u0004\u0003\u0004\u0003",
"\u0004\u0003\u0004\u0003\u0004\u0003\u0004\u0003\u0005\u0003\u0005\u0003",
"\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003",
"\u0005\u0007\u00052\n\u0005\f\u0005\u000e\u00055\u000b\u0005\u0003\u0005",
"\u0005\u00058\n\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005",
"\u0005\u0005>\n\u0005\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006",
"\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006",
"\u0003\u0006\u0003\u0006\u0005\u0006L\n\u0006\u0003\u0006\u0002\u0002",
"\u0007\u0002\u0004\u0006\b\n\u0002\u0002\u0002U\u0002\u000e\u0003\u0002",
"\u0002\u0002\u0004 \u0003\u0002\u0002\u0002\u0006\"\u0003\u0002\u0002",
"\u0002\b=\u0003\u0002\u0002\u0002\nK\u0003\u0002\u0002\u0002\f\u000f",
"\u0005\u0004\u0003\u0002\r\u000f\u0005\b\u0005\u0002\u000e\f\u0003\u0002",
"\u0002\u0002\u000e\r\u0003\u0002\u0002\u0002\u000f\u0003\u0003\u0002",
"\u0002\u0002\u0010\u0011\u0007\u0003\u0002\u0002\u0011\u0016\u0005\u0006",
"\u0004\u0002\u0012\u0013\u0007\u0004\u0002\u0002\u0013\u0015\u0005\u0006",
"\u0004\u0002\u0014\u0012\u0003\u0002\u0002\u0002\u0015\u0018\u0003\u0002",
"\u0002\u0002\u0016\u0014\u0003\u0002\u0002\u0002\u0016\u0017\u0003\u0002",
"\u0002\u0002\u0017\u001a\u0003\u0002\u0002\u0002\u0018\u0016\u0003\u0002",
"\u0002\u0002\u0019\u001b\u0007\u0004\u0002\u0002\u001a\u0019\u0003\u0002",
"\u0002\u0002\u001a\u001b\u0003\u0002\u0002\u0002\u001b\u001c\u0003\u0002",
"\u0002\u0002\u001c\u001d\u0007\u0005\u0002\u0002\u001d!\u0003\u0002",
"\u0002\u0002\u001e\u001f\u0007\u0003\u0002\u0002\u001f!\u0007\u0005",
"\u0002\u0002 \u0010\u0003\u0002\u0002\u0002 \u001e\u0003\u0002\u0002",
"\u0002!\u0005\u0003\u0002\u0002\u0002\"#\u0007\f\u0002\u0002#$\b\u0004",
"\u0001\u0002$%\u0007\u0006\u0002\u0002%&\u0005\n\u0006\u0002&\'\b\u0004",
"\u0001\u0002\'\u0007\u0003\u0002\u0002\u0002()\u0007\u0007\u0002\u0002",
")*\b\u0005\u0001\u0002*+\u0005\n\u0006\u0002+3\b\u0005\u0001\u0002,",
"-\u0007\u0004\u0002\u0002-.\b\u0005\u0001\u0002./\u0005\n\u0006\u0002",
"/0\b\u0005\u0001\u000202\u0003\u0002\u0002\u00021,\u0003\u0002\u0002",
"\u000225\u0003\u0002\u0002\u000231\u0003\u0002\u0002\u000234\u0003\u0002",
"\u0002\u000247\u0003\u0002\u0002\u000253\u0003\u0002\u0002\u000268\u0007",
"\u0004\u0002\u000276\u0003\u0002\u0002\u000278\u0003\u0002\u0002\u0002",
"89\u0003\u0002\u0002\u00029:\u0007\b\u0002\u0002:>\u0003\u0002\u0002",
"\u0002;<\u0007\u0007\u0002\u0002<>\u0007\b\u0002\u0002=(\u0003\u0002",
"\u0002\u0002=;\u0003\u0002\u0002\u0002>\t\u0003\u0002\u0002\u0002?@",
"\u0007\r\u0002\u0002@L\b\u0006\u0001\u0002AB\u0007\f\u0002\u0002BL\b",
"\u0006\u0001\u0002CL\u0005\u0004\u0003\u0002DL\u0005\b\u0005\u0002E",
"F\u0007\t\u0002\u0002FL\b\u0006\u0001\u0002GH\u0007\n\u0002\u0002HL",
"\b\u0006\u0001\u0002IJ\u0007\u000b\u0002\u0002JL\b\u0006\u0001\u0002",
"K?\u0003\u0002\u0002\u0002KA\u0003\u0002\u0002\u0002KC\u0003\u0002\u0002",
"\u0002KD\u0003\u0002\u0002\u0002KE\u0003\u0002\u0002\u0002KG\u0003\u0002",
"\u0002\u0002KI\u0003\u0002\u0002\u0002L\u000b\u0003\u0002\u0002\u0002",
"\n\u000e\u0016\u001a 37=K"].join("");

@@ -243,5 +264,5 @@

try {
this.state = 27;
this.state = 30;
this._errHandler.sync(this);
var la_ = this._interp.adaptivePredict(this._input,2,this._ctx);
var la_ = this._interp.adaptivePredict(this._input,3,this._ctx);
switch(la_) {

@@ -256,13 +277,24 @@ case 1:

this._errHandler.sync(this);
_la = this._input.LA(1);
while(_la===JSONParser.T__1) {
this.state = 16;
this.match(JSONParser.T__1);
this.state = 17;
this.pair(localctx.ret.value);
var _alt = this._interp.adaptivePredict(this._input,1,this._ctx)
while(_alt!=2 && _alt!=antlr4.atn.ATN.INVALID_ALT_NUMBER) {
if(_alt===1) {
this.state = 16;
this.match(JSONParser.T__1);
this.state = 17;
this.pair(localctx.ret.value);
}
this.state = 22;
this._errHandler.sync(this);
_la = this._input.LA(1);
_alt = this._interp.adaptivePredict(this._input,1,this._ctx);
}
this.state = 23;
this.state = 24;
this._errHandler.sync(this);
_la = this._input.LA(1);
if(_la===JSONParser.T__1) {
this.state = 23;
this.match(JSONParser.T__1);
}
this.state = 26;
this.match(JSONParser.T__2);

@@ -273,5 +305,5 @@ break;

this.enterOuterAlt(localctx, 2);
this.state = 25;
this.state = 28;
this.match(JSONParser.T__0);
this.state = 26;
this.state = 29;
this.match(JSONParser.T__2);

@@ -337,10 +369,10 @@ break;

this.enterOuterAlt(localctx, 1);
this.state = 29;
this.state = 32;
localctx._STRING = this.match(JSONParser.STRING);
this.state = 31;
this.state = 34;
this.match(JSONParser.T__3);
this.state = 32;
this.state = 35;
this.value(localctx.val);
localctx.ret[unquote((localctx._STRING===null ? null : localctx._STRING.text))]=localctx.val
localctx.ret[unquoteString((localctx._STRING===null ? null : localctx._STRING.text))]=localctx.val
this._ctx.stop = this._input.LT(-1);

@@ -404,29 +436,40 @@ getLoc(localctx,localctx.val)

try {
this.state = 53;
this.state = 59;
this._errHandler.sync(this);
var la_ = this._interp.adaptivePredict(this._input,4,this._ctx);
var la_ = this._interp.adaptivePredict(this._input,6,this._ctx);
switch(la_) {
case 1:
this.enterOuterAlt(localctx, 1);
this.state = 35;
this.state = 38;
this.match(JSONParser.T__4);
localctx.val={}
this.state = 37;
this.state = 40;
this.value(localctx.val);
localctx.ret.value[ID++]=localctx.val
this.state = 46;
this.state = 49;
this._errHandler.sync(this);
var _alt = this._interp.adaptivePredict(this._input,4,this._ctx)
while(_alt!=2 && _alt!=antlr4.atn.ATN.INVALID_ALT_NUMBER) {
if(_alt===1) {
this.state = 42;
this.match(JSONParser.T__1);
localctx.val={}
this.state = 44;
this.value(localctx.val);
localctx.ret.value[ID++]=localctx.val
}
this.state = 51;
this._errHandler.sync(this);
_alt = this._interp.adaptivePredict(this._input,4,this._ctx);
}
this.state = 53;
this._errHandler.sync(this);
_la = this._input.LA(1);
while(_la===JSONParser.T__1) {
this.state = 39;
if(_la===JSONParser.T__1) {
this.state = 52;
this.match(JSONParser.T__1);
localctx.val={}
this.state = 41;
this.value(localctx.val);
localctx.ret.value[ID++]=localctx.val
this.state = 48;
this._errHandler.sync(this);
_la = this._input.LA(1);
}
this.state = 49;
this.state = 55;
this.match(JSONParser.T__5);

@@ -437,5 +480,5 @@ break;

this.enterOuterAlt(localctx, 2);
this.state = 51;
this.state = 57;
this.match(JSONParser.T__4);
this.state = 52;
this.state = 58;
this.match(JSONParser.T__5);

@@ -472,4 +515,4 @@ break;

this.ret = null
this._NUMBER = null; // Token
this._STRING = null; // Token
this._NUMBER = null; // Token
this.ret = ret || null;

@@ -482,2 +525,6 @@ return this;

ValueContext.prototype.NUMBER = function() {
return this.getToken(JSONParser.NUMBER, 0);
};
ValueContext.prototype.STRING = function() {

@@ -487,6 +534,2 @@ return this.getToken(JSONParser.STRING, 0);

ValueContext.prototype.NUMBER = function() {
return this.getToken(JSONParser.NUMBER, 0);
};
ValueContext.prototype.obj = function() {

@@ -511,20 +554,20 @@ return this.getTypedRuleContext(ObjContext,0);

try {
this.state = 67;
this.state = 73;
this._errHandler.sync(this);
switch(this._input.LA(1)) {
case JSONParser.STRING:
case JSONParser.NUMBER:
this.enterOuterAlt(localctx, 1);
this.state = 55;
localctx._STRING = this.match(JSONParser.STRING);
localctx.ret.type="string"; localctx.ret.value=unquote((localctx._STRING===null ? null : localctx._STRING.text));
this.state = 61;
localctx._NUMBER = this.match(JSONParser.NUMBER);
localctx.ret.type="number"; localctx.ret.value=unquoteNumber((localctx._NUMBER===null ? null : localctx._NUMBER.text));
break;
case JSONParser.NUMBER:
case JSONParser.STRING:
this.enterOuterAlt(localctx, 2);
this.state = 57;
localctx._NUMBER = this.match(JSONParser.NUMBER);
localctx.ret.type="number"; localctx.ret.value=unquote((localctx._NUMBER===null ? null : localctx._NUMBER.text));
this.state = 63;
localctx._STRING = this.match(JSONParser.STRING);
localctx.ret.type="string"; localctx.ret.value=unquoteString((localctx._STRING===null ? null : localctx._STRING.text));
break;
case JSONParser.T__0:
this.enterOuterAlt(localctx, 3);
this.state = 59;
this.state = 65;
this.obj(localctx.ret);

@@ -534,3 +577,3 @@ break;

this.enterOuterAlt(localctx, 4);
this.state = 60;
this.state = 66;
this.array(localctx.ret);

@@ -540,3 +583,3 @@ break;

this.enterOuterAlt(localctx, 5);
this.state = 61;
this.state = 67;
this.match(JSONParser.T__6);

@@ -547,3 +590,3 @@ localctx.ret.type="boolean"; localctx.ret.value=true;

this.enterOuterAlt(localctx, 6);
this.state = 63;
this.state = 69;
this.match(JSONParser.T__7);

@@ -554,3 +597,3 @@ localctx.ret.type="boolean"; localctx.ret.value=false;

this.enterOuterAlt(localctx, 7);
this.state = 65;
this.state = 71;
this.match(JSONParser.T__8);

@@ -557,0 +600,0 @@ localctx.ret.type="null"; localctx.ret.value=null;

// contains query relevant augmentor functions
let W = require("./walker");
let udict = require("../transform/udict");

@@ -239,3 +240,3 @@ // creates a new instance which export all functions

function augmentViewArgs(args,path) {
return W.dmap(args, (arg,obj) => {
return udict.dmap(args, (obj,arg) => {
if(obj.param === true)

@@ -394,3 +395,3 @@ return {

if(typeof val.args == "object" && Array.isArray(val.args)==false) {
r.namedArgs=W.dmap(val.args, (N,O) => {
r.namedArgs=udict.dmap(val.args, (O,N) => {
return newValue(O.val,aPath.concat(N), N, U.WILO_FULL, undefined, U.WILO_FIRST);

@@ -397,0 +398,0 @@ })

@@ -290,3 +290,6 @@ // Transform augmented CSN into compact "official" CSN

function compactSorted( ...args ) {
let model = compact( ...args );
return sortCsn(compact( ...args ));
}
function sortCsn(model) {
if (model.definitions) {

@@ -357,2 +360,3 @@ let result = Object.create(null);

getCompactors,
sortCsn,
};

@@ -32,2 +32,5 @@ /**

delete model["locations"];
// The CSN parser does not use the normal message function:
if (options.messages && model.messages)
options.messages.push( ...model.messages );
return model;

@@ -34,0 +37,0 @@ }

@@ -78,2 +78,3 @@ // Transform augmented CSN into compact "official" CSN

messages: ignore, // consider compactQuery / compactExpr
options: ignore,
sourceMax: renameTo( 'src', value ), // TODO XSN: rename?

@@ -88,4 +89,4 @@ targetMin: renameTo( 'min', value ),

_ignore: a => a, // not yet obsolete - still required by toHana (FIXME: maybe rename to $ignore, or use an annotation instead?)
_ignoreMasked: standard, // FIXME: prop starting with _ is link and non-enumerable
_isToContainer: standard, // FIXME: prop starting with _ is link and non-enumerable
_ignoreMasked: b => b, // FIXME: prop starting with _ is link and non-enumerable
_isToContainer: b => b, // FIXME: prop starting with _ is link and non-enumerable
location, // -> $location

@@ -97,2 +98,3 @@ $extra: (e, csn) => { Object.assign( csn, e ); },

blocks: ignore, // FIXME: make it $blocks
builtin: ignore, // XSN: $builtin, "cds" namespace probably exposed by current transformers
queries: ignore, // FIXME: make it $queries (flat)

@@ -99,0 +101,0 @@ typeArguments: ignore, // FIXME: make it $typeArgs

function Context(options) {
return {
options,
messages:[]
messages:[],
toRemove:[]
}

@@ -6,0 +7,0 @@ }

@@ -85,2 +85,14 @@ let W = require("../walker")

// remove marked for deletion
ctx.toRemove.forEach(P => {
let node = csn;
P.forEach((N,I) => {
if(node===undefined) return;
if(I===P.length-1) { // last node in path?
delete node[N]; // delete the property
} else {
node = node[N];
}
})
})
}

@@ -130,2 +142,7 @@

function rmv(ctx,p) {
if(ctx && ctx.toRemove)
ctx.toRemove.push(p.filter(X => X[0]!=='{'))
}
module.exports = {

@@ -138,2 +155,3 @@ checkTypeOf,

fuzzy,
rmv
}

@@ -34,2 +34,20 @@ /**

/**
* Callback of the forEach function called for each node it walks
* @callback forEachCallback
* @param {string} name of the node
* @param {object} node
*/
/**
* Loops over all elements in an object and calls the specified callback(key,obj)
* @param {object} obj
* @param {forEachCallback} callback
*/
function forEach(obj, callback) {
for(var key in obj) {
callback(key, obj[key]);
}
}
/**
* Callback of the walk function

@@ -310,92 +328,4 @@ * @callback walkCallback

/**
* Callback of the forEach function called for each node it walks
* @callback forEachCallback
* @param {string} name of the node
* @param {object} node
*/
/**
* Loops over all elements in an object and calls the specified callback(key,obj)
* @param {object} obj
* @param {forEachCallback} callback
*/
function forEach(obj, callback) {
for(var key in obj) {
callback(key, obj[key]);
}
}
/**
* Callback of the forEachObject function called for each node it walks
* @callback forEachObjectCallback
* @param {string} name of the node
* @param {object} node
* @return false stops looping
*/
/**
* Loops over all object-elements in an object and calls the specified callback
* @param {object} obj
* @param {forEachObjectCallback} callback
*/
function forEachObject(obj, callback) {
for(var key in obj) {
let iobj = obj[key];
if(isObject(iobj)) {
if(callback(key, iobj)===false)
break; //early exit
}
}
}
/**
* Callback of the forEachProp function called for each leaf it walks
* @callback forEachPropCallback
* @param {string} name of the node
* @param {object} leaf
* @return false stops looping
*/
/**
* Loops over all leafs in an object and calls the specified callback
* @param {object} obj
* @param {forEachPropCallback} callback
*/
function forEachProp(obj, callback) {
for(var key in obj) {
let iobj = obj[key];
if(!isObject(iobj)) {
if(callback(key, iobj)===false)
break; //early exit
}
}
}
/**
* Callback of the dmap function called for each leaf it walks
* @callback dmapCallback
* @param {string} name of the node
* @param {object} leaf
* @return resulting object
*/
/**
* Transforms all elements of a dictionary into another.
* Loops over all elements in an object and calls the specified callback.
* The callback function returns the new representation of the passed element.
* @param {object} obj
* @param {dmapCallback} callback
* @return resulting dictionary
*/
function dmap(obj, callback) {
let R = Object.create(null);
for(var key in obj) {
let iobj = obj[key];
R[key] = callback(key,iobj);
}
return R;
}
/**
* Callback of the walkNodesEx function to obtain the next node to walk

@@ -481,6 +411,3 @@ * @callback getNextElements

module.exports = {
dmap,
forEach,
forEachObject,
forEachProp,
walk,

@@ -487,0 +414,0 @@ walkNodesEx,

@@ -94,3 +94,3 @@ // Wrapper around generated ANTLR parser

parser.options = options;
parser.$message = getMessageFunction( parser ); // sets parser.messages
parser.$message = getMessageFunction( parser, options ); // sets parser.messages

@@ -132,6 +132,10 @@ initTokenRewrite( parser, tokenStream );

var ast = tree && tree[ rulespec.returns ] || {};
ast.options = options;
if (rulespec.frontend)
ast.$frontend = rulespec.frontend;
ast.messages = parser.messages;
if (parser.messages) {
Object.defineProperty( ast, 'messages',
{ value: parser.messages, configurable: true, writable: true } );
}
if (options.attachTokens === true || options.attachTokens === filename)

@@ -138,0 +142,0 @@ ast.tokenStream = tokenStream;

@@ -17,3 +17,2 @@ // Main entry point for the Research Vanilla CDS Compiler

const backends = require('./backends');
const { signModel } = require('./model/modelUtils');

@@ -78,4 +77,5 @@ // The compiler version (taken from package.json)

let ext = path.extname( filename ).toLowerCase();
if (ext === '.xml')
if (ext === '.xml') {
return emdx2csn( source, filename, options );
}
else if (['.json', '.csn'].includes(ext)) {

@@ -90,3 +90,3 @@ let parseCSNfromJson = require("./json/from-json");

let model = {};
const message = getMessageFunction( model );
const message = getMessageFunction( model, options );
message( 'file-unknown-ext',

@@ -113,4 +113,3 @@ { filename, start: { offset: 0, line: 1, column: 1 } }, null,

// - Truthy `parseOnly`: stop compilation after parsing.
// - Truthy `lintMode`: do not report errors for using directives pointing to
// non-existing artifacts.
// - Truthy `lintMode`: do not do checks and propagation
// - many others - TODO

@@ -146,5 +145,5 @@

var messagesArray = [[]];
const message = getMessageFunction({ messages: messagesArray[0] });
let model = { sources: a.sources, options };
const message = getMessageFunction( model );
const parseOptions = optionsWithMessages( options, model );
var all = promiseAllDoNotRejectImmediately( a.files.map(readAndParse) );

@@ -160,15 +159,12 @@

});
if (!options.lintMode && !options.parseOnly)
if (!options.parseOnly)
all = all.then( readDependencies );
return all.then( function() {
moduleLayers.setLayers( a.sources );
for (let name in a.sources)
messagesArray.push( a.sources[name].messages || [] );
if (options.collectSources)
return collect();
return compileDo( a.sources, messagesArray, options, a.fileContentDict );
return compileDo( model, a.fileContentDict );
});
function collect() {
let model = { messages: [].concat( ...messagesArray ) };
handleMessages( model );

@@ -204,7 +200,7 @@ let dependencies = Object.create(null);

a.fileContentDict[filename] = source;
let ast = parse( source, rel, options );
let ast = parse( source, rel, parseOptions );
a.sources[filename] = ast;
ast.filename = rel;
ast.dirname = path.dirname( filename );
assertConsistency( ast, options );
assertConsistency( ast, parseOptions );
fulfill( ast );

@@ -438,3 +434,5 @@ }

let sources = Object.create(null);
var messagesArray = [];
let model = { sources, options };
getMessageFunction( model ); // make sure that we have a "central" messages array
const parseOptions = optionsWithMessages( options, model );

@@ -444,8 +442,6 @@ for (let filename in content) {

if (typeof source === 'string') {
let ast = parse( source, filename, options );
let ast = parse( source, filename, parseOptions );
sources[filename] = ast;
ast.filename = filename;
assertConsistency( ast, options );
if (ast && ast.messages)
messagesArray.push( ast.messages );
assertConsistency( ast, parseOptions );
}

@@ -470,7 +466,15 @@ // else

return compileDo( sources, messagesArray, options, sourcesDict );
return compileDo( model );
}
function compileDo( sources, messagesArray, options = {}, fileContentDict ) {
var model = { sources, options, messages: [].concat( ...messagesArray ) }; // flatten
// Make sure to use a "central" messages array during parsing:
function optionsWithMessages( options, model ) {
return (options.messages)
? options
// : node 8.3 / elint-5+: { messages: model.messages, ...options };
: Object.assign( { messages: model.messages }, options );
}
function compileDo( model ) {
let options = model.options;
if (!options.testMode) {

@@ -480,8 +484,7 @@ model.version = versionObject( options );//TODO remove

}
// if (!options.parseOnly) return define(model);
if (!options.parseOnly) {
define(model);
resolve(model);
}
if (options.parseOnly)
return handleMessages( model );
define( model );
resolve( model );
assertConsistency( model );

@@ -491,17 +494,6 @@ handleMessages( model ); // stop compilation with errors

return model;
semanticChecks(model);
handleMessages( model );
model = propagator.propagate( model );
if (!options.modelExtender || '$draft.cds' in sources)
return signModel(model);
let draft = options.modelExtender( model );
if (!draft)
return signModel(model);
if (typeof draft === 'string')
return signModel(compileSources( Object.assign( { '$draft.cds': draft }, fileContentDict ),
options ));
handleMessages( draft );
throw new Error( 'Option `modelExtender` returns illegal value' );
return propagator.propagate( model );
}

@@ -661,2 +653,4 @@

toOdata : backends.toOdata,
// TODO: Expose when transformers are switched to CSN
// toOdataWithCsn: backends.toOdataWithCsn,
preparedCsnToEdmx : backends.preparedCsnToEdmx,

@@ -663,0 +657,0 @@ preparedCsnToEdm : backends.preparedCsnToEdm,

'use strict'
const revealInternalProperties = require('./revealInternalProperties');
const util = require('util');
const crypto = require('crypto');
// Low-level utility functions to work with augmented CSN.

@@ -166,5 +162,7 @@

absolute: absoluteName.substring(1),
location: node.location, // inherit location from main element
},
val: theValue,
literal: 'boolean',
location: node.location, // inherit location from main element
};

@@ -442,35 +440,2 @@ }

// If model verification is enabled, add a SHA-256 hash digest of the raw augmented CSN of 'model.definitions'
// to 'model' and return 'model'.
// Otherwise, just return 'model' unchanged
function signModel(model) {
if (!model.options || !model.options.verifyModel) {
return model;
}
model.hash = hashModel(model);
return model;
}
// If model verification is enabled, verify the SHA-256 hash digest of the raw augmented CSN of
// 'model.definitions' against 'model.hash', throwing an error mentioning 'procesor' if it does
// not match
// Return 'model' unchanged.
function verifyModelSignature(model, processor) {
if (!model.options || !model.options.verifyModel) {
return model;
}
let hash = hashModel(model);
if (hash != model.hash) {
throw new Error(`This augmented CSN was not produced by the CDS compiler and cannot be processed by ${processor || 'the backend'}`);
}
return model;
}
// Return a SHA-256 hash digest of the raw augmented CSN of 'model.definitions'
function hashModel(model) {
let hash = crypto.createHash('sha256');
hash.update(util.inspect(revealInternalProperties({ definitions: model.definitions }), false, null));
return hash.digest('hex');
}
module.exports = {

@@ -504,4 +469,2 @@ isManagedAssociationElement,

getElementDatabaseNameOf,
signModel,
verifyModelSignature,
};

@@ -25,2 +25,4 @@ // Make internal properties of the XSN / augmented CSN visible

location: locationString,
options: revealOptions,
messages: reveal,
artifacts: artifactDictionary,

@@ -187,2 +189,8 @@ definitions: artifactDictionary,

function revealOptions( node ) {
return reveal( node.messages
? Object.assign( {}, node, { messages: node.messages.length } )
: node );
}
function reveal( node, protoProp, _array, identifierKind = 'query' ) {

@@ -189,0 +197,0 @@ // warning: 'protoProp' bound via map()

@@ -20,6 +20,6 @@ const { createOptionProcessor } = require('./base/optionProcessorHelper');

.option(' --trace-fs')
.option(' --verify-model')
.option('-R, --raw-output')
.option(' --internal-msg')
.option(' --beta-mode')
.option(' --new-transformers')
.option(' --new-csn')

@@ -36,2 +36,3 @@ .option(' --old-csn')

.option(' --old-localized-conv')
.option(' --skip-compile')
.help(`

@@ -56,4 +57,3 @@ Usage: cdsc <command> [options] <file...>

-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
-l, --lint-mode Generate nothing, just produce single-file error messages if any (for
use by editors)
-l, --lint-mode Generate nothing, just produce messages if any (for use by editors)
--fuzzy-csn-error Report free-style CSN properties as errors

@@ -66,3 +66,2 @@ -- Indicate the end of options (helpful if source names start with "-")

--trace-fs Trace file system access caused by "using from"
--verify-model Verify in backend that augmented CSN was created by compiler

@@ -78,2 +77,3 @@ Internal options (for testing only, may be changed/removed at any time)

requires --new-redirect-impl
--new-transformers Use the new transformers that work on CSN instead of XSN
--new-csn Produce CSN version 1.0 format (default)

@@ -86,2 +86,4 @@ --old-csn Produce CSN version 0.1 format (deprecated, overrides --new-csn)

--old-localized-conv Create localized convenience views on top of the original view
--skip-compile Call directly the specified tranformer without compiling the source.
Apply only with one CSN file and to commands toOdata, toHana and toSql.

@@ -152,4 +154,4 @@ Backward compatibility options (deprecated, do not use)

-v, --version <version> ODATA version
v2: (default) ODATA V2
v4: ODATA V4
v2: ODATA V2
v4: (default) ODATA V4
-x, --xml (default) Generate XML output (separate or combined)

@@ -156,0 +158,0 @@ -j, --json Generate JSON output as "<svc>.json" (not available for v2)

@@ -21,3 +21,3 @@ "use strict";

function toCdsSource(model, options) {
const { signal, warning } = alerts(model);
const { signal, warning, error } = alerts(model);

@@ -31,3 +31,4 @@ // Merge options (arguments first, then model options)

// FIXME: This should happen in the caller
let csn = compactModel(model);
// with newTransformers model is already compacted
let csn = options.newTransformers ? model : compactModel(model);

@@ -574,3 +575,13 @@ // Create artificial namespace objects, so that each artifact has parents up to top-level.

} else {
const key = (col.key || (options.forHana && leaf && env._artifact.elements[leaf] && env._artifact.elements[leaf].key) ? 'key ' : '');
// If key is explicitly set in a not-first query of a UNION, issue an error.
if(col.key && env.skipKeys){
// Make the $location look like a normal location property
const location = {
start: { column: col.$location.col, line: col.$location.line, offset: 0 }, // TODO: maybe col-4, to account for "key "?
end: { column: col.$location.col, line: col.$location.line, offset: 0 },
filename: col.$location.file
}
signal(error`KEY must only be added in the first query of a UNION`, location);
}
const key = (!env.skipKeys && (col.key || (options.forHana && leaf && env._artifact.elements[leaf] && env._artifact.elements[leaf].key)) ? 'key ' : '');
result += env.indent + key + renderExpr(col, env, true);

@@ -644,3 +655,12 @@ let alias = col.as;

if (query.SET.op) {
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[1], false, 'view', env)}`;
if(query.SET.op === 'union'){
// For UNION, don't render "key X as X" in the second SELECT
env.skipKeys = true;
}
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
for(let i = 1; i < query.SET.args.length; i++){
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, 'view', env)}`;
}
// Reset afterwards
env.skipKeys = false;
}

@@ -647,0 +667,0 @@ result += ')';

@@ -58,27 +58,25 @@ 'use strict';

// Only run the temporal-validation checks in beta-mode
if(options.betaMode){
// semantic checks before flattening
forEachDefinition(model, artifact => {
// semantic checks before flattening
forEachDefinition(model, artifact => {
// Gather all elements with @cds.valid.from/to/key
let validFrom = [], validTo = [], validKey = [];
forEachMemberRecursively(artifact, member => {
let [f, t, k] = extractValidFromToKeyElement(member);
validFrom.push(...f);
validTo.push(...t);
validKey.push(...k);
});
let validFrom = [], validTo = [], validKey = [];
forEachMemberRecursively(artifact, member => {
let [f, t, k] = extractValidFromToKeyElement(member);
validFrom.push(...f);
validTo.push(...t);
validKey.push(...k);
});
// Check that @cds.valid.from/to/key is only in valid places
validFrom.forEach(e => checkAssignment('@cds.valid.from', e, artifact));
validTo.forEach(e => checkAssignment('@cds.valid.to', e, artifact));
validKey.forEach(e => checkAssignment('@cds.valid.key', e, artifact));
checkMultipleAssignments(validFrom, '@cds.valid.from', artifact);
checkMultipleAssignments(validTo, '@cds.valid.to', artifact);
checkMultipleAssignments(validKey, '@cds.valid.key', artifact);
if(validKey.length && !(validFrom.length && validTo.length)){
signal(error`@cds.valid.key was used but @cds.valid.from and @cds.valid.to are missing`, artifact.location);
}
});
}
validFrom.forEach(e => checkAssignment('@cds.valid.from', e, artifact));
validTo.forEach(e => checkAssignment('@cds.valid.to', e, artifact));
validKey.forEach(e => checkAssignment('@cds.valid.key', e, artifact));
checkMultipleAssignments(validFrom, '@cds.valid.from', artifact);
checkMultipleAssignments(validTo, '@cds.valid.to', artifact);
checkMultipleAssignments(validKey, '@cds.valid.key', artifact);
if(validKey.length && !(validFrom.length && validTo.length)){
signal(error`@cds.valid.key was used but @cds.valid.from and @cds.valid.to are missing`, artifact.location);
}
});
// Second walk: Flatten structs, unravel derived types, deal with annotations

@@ -121,2 +119,3 @@ forEachDefinition(model, (artifact) => {

toFinalBaseType(member.returns);
toFinalBaseType(member.returns && member.returns.items);
// Mark fields with @odata.on.insert/update as @Core.Computed

@@ -141,2 +140,3 @@ annotateCoreComputed(member);

toFinalBaseType(artifact.returns);
toFinalBaseType(artifact.returns && artifact.returns.items);
}

@@ -290,3 +290,3 @@ // If the artifact is a derived structured type, unravel that as well

if (!containedElem.notNull) {
signal(error`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity must have "NOT NULL"`, containedElem.location);
signal(warning`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity must have "NOT NULL"`, containedElem.location);
}

@@ -297,3 +297,3 @@ }

if (containedElem.notNull) {
signal(error`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity in a recursive hierarchy must not have "NOT NULL`, containedElem.location);
signal(warning`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity in a recursive hierarchy must not have "NOT NULL`, containedElem.location);
}

@@ -342,6 +342,6 @@ }

// https://github.wdf.sap.corp/cdx/cds-compiler/issues/837
// add check here for @Analytics.Measure and @Aggregation.Default
// add check here for @Analytics.Measure and @Aggregation.default
// @Analytics has scope element
if (member.kind && member.kind === 'element'
&& member['@Analytics.Measure'] && !member['@Aggregation.Default']) {
&& member['@Analytics.Measure'] && !member['@Aggregation.default']) {
signal(

@@ -384,2 +384,3 @@ warning`'@Analytics.Measure' expects '@Aggregation.default' to be assigned as well in element '${member.name.absolute}.${member.name.id}'`,

// Walk the elements
let eltCount = 0;
for (let elemName in artifact.elements) {

@@ -397,5 +398,7 @@ let elem = artifact.elements[elemName];

let ignore = hasBoolAnnotation(elem, '@cds.api.ignore', true);
if (elem.key && elem.key.val && !ignore) {
keyCount++;
if(!elem._ignore) {
eltCount++;
if (elem.key && elem.key.val && !ignore) {
keyCount++;
}
}

@@ -407,2 +410,5 @@ if (elem['@Core.MediaType']) {

if(eltCount == 0) {
signal(error`Entity "${artifact.name.absolute}" must have at least one element`, artifact.location);
}
// Exposed non-abstract entities in non-contained artifacts must have a key

@@ -590,2 +596,6 @@ if (keyCount == 0 && !artifact._containerEntity) {

}
// if odata.draft.enabled is explicitly set to false, we ignore it when linked via composition
else if (hasBoolAnnotation(draftNode, '@odata.draft.enabled', false)) {
continue;
}
else {

@@ -592,0 +602,0 @@ // Generate draft stuff into the target

@@ -11,2 +11,4 @@ 'use strict';

const { copyAnnotations } = require('../model/modelUtils');
const { dfilter } = require('./udict');
const { getUtils } = require('../model/csnUtils');

@@ -16,3 +18,2 @@ // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).

// 'model' is compacted new style CSN
// TODO: Check if all the functions related to redirections and auto-exposures are needed at all?
// TODO: Error and warnings handling with compacted CSN? - currently just throw new Error for everything

@@ -23,2 +24,6 @@ // TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true

let options = model.options || {};
const {
getCsnDef,
isStructured
} = getUtils(model);

@@ -30,10 +35,12 @@ return {

flattenStructuredElement,
flattenOnCond,
// TODO: is it needed in compacted CSN? flattenStructStepsInPath,
// TODO: is it needed? checkExposedAssoc,
checkExposedAssoc,
toFinalBaseType,
getServiceName,
// TODO: is it needed? addImplicitRedirections,
// TODO: is it needed in compacted CSN? isAssociationOperand,
// TODO: is it needed in compacted CSN? isDollarSelfOperand,
// TODO: is it needed? createExposingProjection,
// TODO: draft: createAndAddDraftAdminDataProjection,
createExposingProjection,
createAndAddDraftAdminDataProjection,
createScalarElement,

@@ -68,9 +75,13 @@ createAssociationElement,

// get all the elements from the target that have 'key' identifier
let targetKeys =
Object.keys(targetArt.elements)
.filter(elem => targetArt.elements[elem].key)
.reduce(
(obj, keyName) => Object.assign(obj, { [keyName]: targetArt.elements[keyName] }),
Object.create(null)
);
let targetKeys = dfilter(targetArt.elements, elem => elem.key === true);
// in case we have explicitly defined FKs
Object.assign(targetKeys, dfilter(targetArt.elements, (elem, elemName) => {
if (elem._flatElementNameWithDots) {
// this is flattened elem -> keys still not flattened, have to check if starts with key ref
return keys.map(key => key.ref.join('.')).some(keyDotName => elemName.startsWith(keyDotName));
} else {
// exact match of the name
return keys.map(key => key.ref.join('.')).some(keyDotName => keyDotName === elemName);
}
}))

@@ -123,7 +134,7 @@ let result = [];

function createForeignKeyElement(assoc, assocName, foreignKey, artifact) {
// (TNT only): Use <assoc><key> instead of <assoc>_key>
let fkSeparator = (options.tntFlavor && !options.tntFlavor.skipGeneratedFKsWithout_) ? '' : pathDelimiter;
let fkSeparator = pathDelimiter;
// Assemble foreign key element name from assoc name, '_' and foreign key name/alias
let foreignKeyElementName = assocName.replace(/\./g, pathDelimiter) + fkSeparator + foreignKey.ref.join(pathDelimiter);
// let foreignKeyElementName = assocName.replace(/\./g, pathDelimiter) + fkSeparator + foreignKey.ref.join(pathDelimiter);
let foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${fkSeparator}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;

@@ -133,3 +144,3 @@ // Assemble artificial foreign key element

let fkArtifact = assocTargetDef.elements[foreignKey.ref.join(pathDelimiter)];
let fkArtifact = assocTargetDef.elements[foreignKey.ref.join(pathDelimiter)]; // foreignKey.as ???

@@ -146,2 +157,4 @@ let foreignKeyElement = Object.create(null);

copyAnnotations(assoc, foreignKeyElement, true);
// If the association is non-fkArtifact resp. key, so should be the foreign key field

@@ -243,3 +256,3 @@ for (let prop of ['notNull', 'key']) {

let childElem = struct[childName];
if (childElem.elements) {
if (isStructured(childElem)) {
// Descend recursively into structured children

@@ -311,2 +324,111 @@ let grandChildElems = flattenStructuredElement(childElem, childName);

// After flattening of elements we need to flatten the on-conditions of
// unmanaged associations using those newly created elements
//
// Examples:
// Input as: assoc.on: [{ ref: ['a1', 'x'] }, '=', { ref: ['$self', 'x'] }]
// ^^^ ^^^ ^^^ ^^^
// (1) (2) (3) (4)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a1: association to ... }})
// (2) is non-structured element in the target
// (3) is '$self' ... clearly
// (4) is non-structured element in the current scope
// The flatten on-condition is: [{ ref: ['s_a1', 'x'] }, '=', { ref: ['x'] }]
//
// Input as: assoc.on: [{ ref: ['a2', 'x'] }, '=', { ref: ['$self', 's', 'y'] }]
// ^^^ ^^^ ^^^ ^^^^^^^
// (1) (2) (3) (4)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a2: association to ... }})
// (2) is non-structured element in the target
// (3) is '$self' ... clearly
// (4) is structured element in the current scope
// The flatten on-condition is: [{ ref: ['s_a2', 'x'] }, '=', { ref: ['s_y'] }]
//
// Input as: assoc.on: [{ ref: [{ ref: ['a3', 'x'] }, '=', { ref: ['y'] }]
// ^^^ ^^^ ^^^
// (1) (2) (3)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a3: association to ... }})
// (2) is non-structured element in the target
// (3) element from the current nameresolution scope ( .. elements: { s : { y: ... ; a3: association to ... })
// The flatten on-condition is: [{ ref: ['s_a3', 'x'] }, '=', { ref: ['s_y'] }]
//
// Input as: assoc.on: [{ ref: ['a4', 'struct', 'k1'] }, '=', { ref: ['$self', 's', 'struct', 'k2'] }]
// ^^^ ^^^^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^
// (1) (2) (3) (4)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a4: association to ... }})
// (2) is structured element in the target
// (3) is '$self' ... clearly
// (4) is structured element in the current scope
// The flatten on-condition is: [{ ref: ['s_a4', 'struct_k1'] }, '=', { ref: ['s_struct_k2'] }]
function flattenOnCond(assoc, assocName, defElements) {
if (!assoc.on) return; // nothing to do
let allRefs = assoc.on.filter(elem => typeof elem === 'object' && elem.ref);
let targetDef = getCsnDef(assoc.target);
for (let refObj of allRefs) {
// if reference is just '$self'
if (refObj.ref.length === 1 && refObj.ref[0] === '$self')
continue;
// remove '$self' when first elem in ref and flatten the rest if needed
if (refObj.ref[0] === '$self') {
refObj.ref.shift();
// the rest of the reference should point to an element from the current definition
let potentialFlattenElem = refObj.ref.join(pathDelimiter);
if (defElements && defElements[potentialFlattenElem] && defElements[potentialFlattenElem].viaTransform) {
refObj.ref = [potentialFlattenElem];
}
continue;
}
// assoc element itself was flatten
// -> every first step where current assoc is specified needs to be replaced
if (assoc.viaTransform && assocName.endsWith(`${pathDelimiter}${refObj.ref[0]}`)) {
refObj.ref.splice(0, 1, assocName);
// the rest of the reference should point to an element from the target
let potentialFlattenElem = refObj.ref.slice(1).join(pathDelimiter);
if (targetDef.elements && targetDef.elements[potentialFlattenElem] && targetDef.elements[potentialFlattenElem].viaTransform) {
refObj.ref.splice(1, refObj.ref.length - 1, potentialFlattenElem);
}
}
// reference to flattened element in the target
let flattenRefName = refObj.ref.join(pathDelimiter);
if (targetDef.elements) {
// exact match of flatten element name
if (targetDef.elements[flattenRefName] && targetDef.elements[flattenRefName].viaTransform) {
refObj.ref = [flattenRefName];
continue;
}
// when element is defined in the current name resolution scope, like
// entity E {
// key x: Integer;
// s : {
// y : Integer;
// a3 : association to E on a3.x = y;
// }
// }
let potentialFlattenElem = Object.keys(targetDef.elements)
.find(elemName => elemName.endsWith(`${pathDelimiter}${flattenRefName}`));
if (potentialFlattenElem) {
refObj.ref = [potentialFlattenElem];
continue;
}
}
}
}
// Check that exposed associations do not point to non-exposed targets
function checkExposedAssoc(artName, assocDef, assocName, service) {
let assocTargetDef = getCsnDef(assocDef.target);
if (!assocDef._ignore && assocDef.target && assocTargetDef && !assocDef.target.startsWith(service)) {
// If we have a 'preserved dotted name' -> a result of flattening -> This scenario is not supported yet
if (assocDef._flatElementNameWithDots)
throw new Error(`Redirection for sub elements not supported yet - association "${artName}.${assocName}"`);
else
throw new Error(
`Association "${artName}.${assocName}" must be redirected: Target "${assocDef.target}" is not exposed by service "${service}"`
);
}
}
// Replace the type of 'node' with its final base type (in contrast to the compiler,

@@ -324,3 +446,3 @@ // also unravel derived enum types, i.e. take the final base type of the enum's base type.

// Nothing to do if type is an array or a struct type
if (!typeDef.type) return;
if (typeDef.items || typeDef.elements) return;
// if the declared element is an enum, these values are with priority

@@ -335,2 +457,130 @@ if (!node.enum && typeDef.enum)

function getServiceName(artifactName) {
for(;;) {
let idx = artifactName.lastIndexOf('.');
if (idx == -1) return null;
artifactName = artifactName.substring(0, idx);
let artifact = model.definitions[artifactName];
if (artifact && artifact.kind === 'service') {
return artifactName;
}
}
}
// Return a full projection 'projectionId' of artifact 'art' for exposure in 'service'.
// Add the created projection to the model and complain if artifact already exists.
// Used by Draft generation
function createExposingProjection(art, artName, projectionId, service) {
let projectionAbsoluteName = `${service}.${projectionId}`;
// If there already is an artifact with this name, this is either the second attempt or a conflict
let existingProjection = model.definitions[projectionAbsoluteName];
if (existingProjection) {
throw new Error(`Cannot generate projection "${projectionAbsoluteName}" because of name conflict with existing artifact "${service.name.absolute}.$projectionId}"`);
}
// Create elements matching the artifact's elements
let elements = Object.create(null);
for (let elemName in art.elements) {
let artElem = art.elements[elemName];
let elem = Object.assign({}, artElem);
// Transfer xrefs, that are redirected to the projection
// TODO: shall we remove the transfered elements from the original?
// if (artElem._xref) {
// setProp(elem, '_xref', artElem._xref.filter(xref => xref.user && xref.user._main && xref.user._main._service == service));
// }
// FIXME: Remove once the compactor no longer renders 'origin'
elements[elemName] = elem;
}
let query = {
'SELECT': {
'from': {
'ref': [
artName
]
}
}
};
// Assemble the projection itself and add it into the model
let projection = {
'kind': 'entity',
query,
elements,
//'$syntax': 'projection'
};
// copy annotations from art to projection
for (let a of Object.keys(art).filter(x => x.startsWith('@'))) {
projection[a] = art[a];
}
// Sanity check: Can't already be there (checked above)
if (model.definitions[projectionAbsoluteName]) {
throw new Error('Duplicate projection: ' + projectionAbsoluteName);
}
model.definitions[projectionAbsoluteName] = projection;
return projection;
}
// Create a 'DraftAdministrativeData' projection on entity 'DRAFT.DraftAdministrativeData'
// in service 'service' and add it to the model.
function createAndAddDraftAdminDataProjection(service) {
// Make sure we have a DRAFT.DraftAdministrativeData entity
let draftAdminDataEntity = model.definitions['DRAFT.DraftAdministrativeData'];
if (!draftAdminDataEntity) {
draftAdminDataEntity = createAndAddDraftAdminDataEntity();
model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
}
// Barf if it is not an entity or not what we expect
if (draftAdminDataEntity.kind != 'entity' || !draftAdminDataEntity.elements['DraftUUID']) {
throw new Error(`Generated entity "DRAFT.DraftAdministrativeData" conflicts with existing artifact`);
}
// Create a projection within this service
return createExposingProjection(draftAdminDataEntity, 'DRAFT.DraftAdministrativeData', 'DraftAdministrativeData', service);
// Create the 'DRAFT.DraftAdministrativeData' entity (unless it already exist)
// Return the 'DRAFT.DraftAdministrativeData' entity.
function createAndAddDraftAdminDataEntity() {
// Create the 'DRAFT.DraftAdministrativeData' entity
let artifact = {
kind: 'entity',
elements: Object.create(null),
}
// key DraftUUID : UUID
let draftUuid = createScalarElement('DraftUUID', 'cds.UUID', true);
addElement(draftUuid, artifact);
// CreationDateTime : Timestamp;
let creationDateTime = createScalarElement('CreationDateTime', 'cds.Timestamp');
addElement(creationDateTime, artifact);
// CreatedByUser : String(256);
let createdByUser = createScalarElement('CreatedByUser', 'cds.String');
createdByUser['CreatedByUser'].length = 256;
addElement(createdByUser, artifact);
// DraftIsCreatedByMe : Boolean;
let draftIsCreatedByMe = createScalarElement('DraftIsCreatedByMe', 'cds.Boolean');
addElement(draftIsCreatedByMe, artifact);
// LastChangeDateTime : Timestamp;
let lastChangeDateTime = createScalarElement('LastChangeDateTime', 'cds.Timestamp');
addElement(lastChangeDateTime, artifact);
// LastChangedByUser : String(256);
let lastChangedByUser = createScalarElement('LastChangedByUser', 'cds.String');
lastChangedByUser['LastChangedByUser'].length = 256;
addElement(lastChangedByUser, artifact);
// InProcessByUser : String(256);
let inProcessByUser = createScalarElement('InProcessByUser', 'cds.String');
inProcessByUser['InProcessByUser'].length = 256;
addElement(inProcessByUser, artifact);
// DraftIsProcessedByMe : Boolean;
let draftIsProcessedByMe = createScalarElement('DraftIsProcessedByMe', 'cds.Boolean');
addElement(draftIsProcessedByMe, artifact);
return artifact;
}
}
// Create an artificial scalar element 'elemName' with final type 'typeName'.

@@ -368,3 +618,3 @@ // Make the element a key element if 'isKey' is true.

// type: 'cds.Association', target: 'Foo',
// keys: [{ ref: ['id'] }], implicitForeignKeys: true
// keys: [{ ref: ['id'] }]
// } }

@@ -386,3 +636,2 @@ function createAssociationElement(elemName, target, isManaged = false) {

addForeignKey(foreignKey, assoc);
assoc.implicitForeignKeys = true;
}

@@ -473,5 +722,4 @@ }

let result = { [elemName]: {} };
let elemProps = elem[Object.keys(elem)[0]];
for (let prop in elemProps)
result[elemName][prop] = elemProps[prop];
for (let prop in elem)
result[elemName][prop] = elem[prop];
Object.assign(artifact.elements, result);

@@ -537,11 +785,2 @@ return result;

}
// some model utilities => TODO: move them to separate file
function getCsnDef(defName) {
if (model.definitions[defName])
return model.definitions[defName]
else
throw new Error(`Nonexistent definition in the model: '${defName}'`);
}
}

@@ -548,0 +787,0 @@

@@ -10,8 +10,8 @@ 'use strict'

// Paths that start with an artifact of protected kind are special
// either ignore them in QAT building or in path rewriting
const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
function translateAssocsToJoins(model)
{
// Paths that start with an artifact of protected kind are special
// either ignore them in QAT building or in path rewriting
const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
const { error, signal } = alerts(model);

@@ -31,8 +31,2 @@

// Throw up if we have errors
/*
if (hasErrors(model.messages)) {
throw new CompilationError( sortMessages(model.messages), model)
}
*/
return model;

@@ -1145,145 +1139,2 @@

// Crawl all relevant sections of the query AST for paths
function walkQuery(query, env)
{
if(!query)
return;
if(!env.walkover)
env.walkover = {};
env.location = query.op;
if(query.op.val === 'query')
{
env.lead = query;
env.location = 'from';
walkFrom(query.from);
env.location = 'select';
if(env.walkover[env.location])
{
for(let alias in query.elements)
walk(query.elements[alias].value, env);
env.location = 'Where';
walk(query.where, env);
env.location = 'GroupBy';
walk(query.groupBy, env);
env.location = 'Having';
walk(query.having, env);
env.location = 'OrderBy';
walk(query.orderBy, env);
// outer orderBy's of anonymous union
walk(query.$orderBy, env);
env.location = 'Limit';
walk(query.limit, env);
env.location = 'Offset';
walk(query.offset, env);
}
}
function walkFrom(query)
{
let aliases = [];
if(query)
{
if(Array.isArray(query))
{
for(let q of query)
aliases = aliases.concat(walkFrom(q));
}
else if(env.walkover[env.location] && walkPath(query, env))
{
if(query.name)
aliases.push(query.name.id);
}
else
{
aliases = walkFrom(query.args);
env.location = 'onCondFrom';
if(env.walkover[env.location])
{
env.tableAliases = aliases;
walk(query.onCond || query.on, env)
delete env.tableAliases;
}
env.location = 'from';
}
}
return aliases;
}
}
/* node: any
env: { callback: (array of) callback methods with signature(thing, env)
...: any additional payload for the callback
}
*/
function walk(node, env)
{
// In some expressions queries can occur, do not follow them as they
// are walked as member of the queries array
if(!env || !node || (node && node.op && node.op.val == 'query'))
return;
if(typeof node === 'object' && !Array.isArray(node))
if(walkPath(node, env))
return;
// Ask for Array before typeof object (which would also be true for Array)
if(Array.isArray(node))
node.map(n => walk(n, env));
// instanceof Object doesn't respect dictionaries...
else if(typeof node === 'object')
for(let n in node)
walk(node[n], env);
}
function walkPath(node, env)
{
let path = node['path'];
// Ignore paths that that have no artifact (function calls etc) or that are builtins ($now, $user)
// or that are parameters ($parameters or escaped paths (':')
//path.length && path[ path.length-1 ]._artifact
let art = path && path.length && path[path.length-1]._artifact;
if(art && !internalArtifactKinds.includes(art.kind))
{
if(env.callback)
{
// an array of callbacks applied to the node
if(Array.isArray(env.callback))
env.callback.forEach(cb => cb(node, env));
else
env.callback(node, env);
}
/*
NOTE: As long as association path steps are not allowed in filters,
it is not required to walk over filter expressions.
Simple filter paths are rewritten inin createJoinTree (first filter)
and createJoinQA (subsequent one that belong to the ON condition).
If the filter becomes JOIN relevant, default FILTERS (part of the
association definition) MUST be CLONED to each assoc path step
BEFORE resolution.
let filterEnv = Object.assign({walkover: {} }, env);
filterEnv.location = 'filter';
if(filterEnv.walkover[filterEnv.location])
{
// Walk over all filter expressions (not JOIN relevant,
// cannot be detected in generic walk. Store path step
// to which this filter was attached to in filterEnt.pathStep
path.filter(pathStep=>pathStep.where).forEach(pathStep => {
filterEnv.pathStep = pathStep;
walk(pathStep.where, filterEnv) });
}
*/
// TODO: Parameter expressions!
}
return path;
}
function pathAsStr(p, delim='')

@@ -1395,4 +1246,147 @@ {

// Crawl all relevant sections of the query AST for paths
function walkQuery(query, env)
{
if(!query)
return;
if(!env.walkover)
env.walkover = {};
env.location = query.op;
module.exports = { translateAssocsToJoins, constructPathNode };
if(query.op.val === 'query')
{
env.lead = query;
env.location = 'from';
walkFrom(query.from);
env.location = 'select';
if(env.walkover[env.location])
{
for(let alias in query.elements)
walk(query.elements[alias].value, env);
env.location = 'Where';
walk(query.where, env);
env.location = 'GroupBy';
walk(query.groupBy, env);
env.location = 'Having';
walk(query.having, env);
env.location = 'OrderBy';
walk(query.orderBy, env);
// outer orderBy's of anonymous union
walk(query.$orderBy, env);
env.location = 'Limit';
walk(query.limit, env);
env.location = 'Offset';
walk(query.offset, env);
}
}
function walkFrom(query)
{
let aliases = [];
if(query)
{
if(Array.isArray(query))
{
for(let q of query)
aliases = aliases.concat(walkFrom(q));
}
else if(env.walkover[env.location] && walkPath(query, env))
{
if(query.name)
aliases.push(query.name.id);
}
else
{
aliases = walkFrom(query.args);
env.location = 'onCondFrom';
if(env.walkover[env.location])
{
env.tableAliases = aliases;
walk(query.onCond || query.on, env)
delete env.tableAliases;
}
env.location = 'from';
}
}
return aliases;
}
}
/* node: any
env: { callback: (array of) callback methods with signature(thing, env)
...: any additional payload for the callback
}
*/
function walk(node, env)
{
// In some expressions queries can occur, do not follow them as they
// are walked as member of the queries array
if(!env || !node || (node && node.op && node.op.val == 'query'))
return;
if(typeof node === 'object' && !Array.isArray(node))
if(walkPath(node, env))
return;
// Ask for Array before typeof object (which would also be true for Array)
if(Array.isArray(node))
node.map(n => walk(n, env));
// instanceof Object doesn't respect dictionaries...
else if(typeof node === 'object')
for(let n in node)
walk(node[n], env);
}
function walkPath(node, env)
{
let path = node['path'];
// Ignore paths that that have no artifact (function calls etc) or that are builtins ($now, $user)
// or that are parameters ($parameters or escaped paths (':')
//path.length && path[ path.length-1 ]._artifact
let art = path && path.length && path[path.length-1]._artifact;
if(art && !internalArtifactKinds.includes(art.kind))
{
if(env.callback)
{
// an array of callbacks applied to the node
if(Array.isArray(env.callback))
env.callback.forEach(cb => cb(node, env));
else
env.callback(node, env);
}
/*
NOTE: As long as association path steps are not allowed in filters,
it is not required to walk over filter expressions.
Simple filter paths are rewritten inin createJoinTree (first filter)
and createJoinQA (subsequent one that belong to the ON condition).
If the filter becomes JOIN relevant, default FILTERS (part of the
association definition) MUST be CLONED to each assoc path step
BEFORE resolution.
let filterEnv = Object.assign({walkover: {} }, env);
filterEnv.location = 'filter';
if(filterEnv.walkover[filterEnv.location])
{
// Walk over all filter expressions (not JOIN relevant,
// cannot be detected in generic walk. Store path step
// to which this filter was attached to in filterEnt.pathStep
path.filter(pathStep=>pathStep.where).forEach(pathStep => {
filterEnv.pathStep = pathStep;
walk(pathStep.where, filterEnv) });
}
*/
// TODO: Parameter expressions!
}
return path;
}
module.exports = { translateAssocsToJoins, constructPathNode, walkQuery };
{
"name": "@sap/cds-compiler",
"version": "1.13.4",
"version": "1.15.0",
"dependencies": {

@@ -5,0 +5,0 @@ "antlr4": {

@@ -1,1 +0,1 @@

{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.13.4","license":"SEE LICENSE IN developer-license-3.1.txt"}
{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.15.0","license":"SEE LICENSE IN developer-license-3.1.txt"}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc