@sap/cds-compiler
Advanced tools
Comparing version 1.1.1 to 1.1.2
# ChangeLog for cdx compiler and backends | ||
## Version 1.1.2 | ||
Features: | ||
* Allow reserved names for annotations/properties in assignments. | ||
* Allow final `,` for much more "lists" (e.g. arguments). | ||
* It is now possible to omit the select list in a view definition, | ||
which is the same as writing `select from <name> {*}`. | ||
* Allow `array of` as type spec for a parameter definition. | ||
* SQL generation for sqlite now supports a mode where associations are resolved | ||
to joins. | ||
Changes: | ||
* Improved messages for syntax errors. | ||
* `WHERE` now is a reserved keyword and so cannot be used anymore as name at many places. | ||
Fixes: | ||
* In `toOdata()` with the `hdbcds` naming convention, the value of the `@cds.persistence.name` | ||
annotation now uses `.` rather than `_` as separator for the names of flattened structured | ||
entity elements. | ||
* Numeric values in OData annotations are now correctly mapped to edmx. | ||
## Version 1.1.1 | ||
@@ -4,0 +25,0 @@ |
@@ -5,2 +5,16 @@ // Functions and classes for syntax messages | ||
// For messageIds, where no severity has been provided via code (central def) | ||
const standardSeverities = { | ||
'syntax-anno-after-struct': 'Warning', | ||
'syntax-anno-after-enum': 'Warning', | ||
'syntax-anno-after-params': 'Warning', // very ugly! | ||
} | ||
// For messageIds, where no text has been provided via code (central def) | ||
const standardTexts = { | ||
'syntax-anno-after-struct': 'Avoid annotation assignments after structure definitions', | ||
'syntax-anno-after-enum': 'Avoid annotation assignments after enum definitions', | ||
'syntax-anno-after-params': 'Avoid annotation assignments after parameters', | ||
} | ||
function hasErrors( messages ) { | ||
@@ -83,6 +97,10 @@ return messages && messages.some( m => m.severity === 'Error' ); | ||
return function message( id, location, params = {}, severity = undefined, texts = undefined ) { | ||
let s = normalizedSeverity( severity ); // as specified | ||
if (!severity) // TODO: check that they are always eq per messageId | ||
severity = standardSeverities[id]; | ||
let s = normalizedSeverity( severity ); | ||
if ((s !== 'Error' || severity instanceof Array) && id && id in config ) | ||
s = normalizedSeverity( config[id] ); | ||
let text = (typeof params === 'string') ? params : messageText( texts, params ); | ||
let text = (typeof params === 'string') | ||
? params | ||
: messageText( texts || standardTexts[id], params ); | ||
let msg = new CompileMessage( location, text, s, id ); | ||
@@ -98,2 +116,4 @@ model.messages.push( msg ); | ||
art: transformArg, | ||
code: n => '`' + n + '`', | ||
name: n => msgName(n), | ||
id: n => msgName(n) | ||
@@ -100,0 +120,0 @@ }; |
@@ -30,2 +30,3 @@ // Consistency checker on model (XSN = augmented CSN) | ||
'version', // TODO: do not set in parser | ||
'@sql_mapping', // TODO: it is time that a 'header' attribute replaces 'version' | ||
], | ||
@@ -147,3 +148,2 @@ }, | ||
dbType: { kind: true, test: () => true }, | ||
temporary: { kind: true, test: () => true }, // TODO: HANA-CDS - keep? | ||
source: { kind: true, test: () => true }, // TODO: remove in JSON/CDL parser | ||
@@ -366,9 +366,2 @@ projection: { kind: true, test: () => true }, // TODO: remove in JSON/CDL parser | ||
function assertConsistency2(model, stage) { | ||
let sql_mapping = model["@sql_mapping"]; | ||
delete model["@sql_mapping"] | ||
assertConsistency(model, stage) | ||
model["@sql_mapping"]=sql_mapping | ||
} | ||
module.exports = assertConsistency2; | ||
module.exports = assertConsistency; |
@@ -195,2 +195,4 @@ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN | ||
let absolute; | ||
if (path.broken) | ||
return parent; | ||
for (let item of path) { | ||
@@ -415,2 +417,4 @@ let id = item.id; | ||
addToDefinitions( art, undefined, prefix, parent ); | ||
if (art.dbType && !options.hanaFlavor) | ||
message( `TABLE TYPE is not supported yet`, art.dbType.location ); | ||
defineAnnotations( art, art, block ); | ||
@@ -417,0 +421,0 @@ initMembers( art, art, block ); |
@@ -190,2 +190,6 @@ // Compiler phase "resolve": resolve all references | ||
return; | ||
if (!options.hanaFlavor && !options.betaMode && tc.location) { | ||
message( null, tc.location, {}, 'Error', | ||
'TECHNICAL CONFIGURATION is not supported yet' ); | ||
} | ||
@@ -1003,4 +1007,8 @@ // secondary and fulltext indexes | ||
break; | ||
if (step.namedArgs) | ||
if (step.namedArgs) { | ||
if (!options.betaMode && !options.hanaFlavor) | ||
message( null, dictLocation( step.namedArgs ), {}, | ||
'Error', 'View arguments are not supported yet' ); | ||
resolveParams( step.namedArgs, step._artifact, query._main._block ); // block for future :const | ||
} | ||
if (step.where) // TODO: support for $projection, $parameters, ...? | ||
@@ -1007,0 +1015,0 @@ resolveExpr( step.where, 'element', null, {}, |
@@ -434,3 +434,3 @@ // Compiler functions and utilities shared across all phases | ||
function pathName (path) { | ||
return path.map( id => id.id ).join('.'); | ||
return (path.broken) ? '' : path.map( id => id.id ).join('.'); | ||
} | ||
@@ -437,0 +437,0 @@ |
@@ -255,4 +255,6 @@ 'use strict'; | ||
Bool : val => val == "true", | ||
Byte : val => parseInt(val), | ||
Int : val => parseInt(val), | ||
Decimal : val => parseFloat(val), | ||
Float : val => parseFloat(val), | ||
EnumMember : val => handleEnumValue(val), | ||
@@ -259,0 +261,0 @@ Path : val => ({ "=": val.replace(/\//g, '.') }), |
@@ -145,2 +145,8 @@ const parseXml = require('./xmlParserWithLocations'); | ||
}, | ||
Float: val => { | ||
return { | ||
literal: 'decimal', | ||
val: Number(val) | ||
} | ||
}, | ||
Int: val => { | ||
@@ -147,0 +153,0 @@ return { |
@@ -37,2 +37,4 @@ const edmxDict = require('./vocabularies/Dictionary.json'); | ||
case 'Edm.Double': | ||
checkIfProp('Float'); | ||
break; | ||
case 'Edm.Decimal': | ||
@@ -39,0 +41,0 @@ checkIfProp('Decimal'); |
@@ -43,8 +43,13 @@ 'use strict'; | ||
// handle the annotations directly tied to the object | ||
handleAnnotations(objName, object); | ||
// handle the annotations of the object's elements | ||
handleElements(objName, object); | ||
// handle the annotations of the object's actions | ||
handleActions(objName, object); | ||
if (object.kind == "action" || object.kind == "function") { | ||
handleAction(objName, object); | ||
} | ||
else { // service, entity, anything else? | ||
// handle the annotations directly tied to the object | ||
handleAnnotations(objName, object); | ||
// handle the annotations of the object's elements | ||
handleElements(objName, object); | ||
// handle the annotations of the object's actions | ||
handleBoundActions(objName, object); | ||
} | ||
} | ||
@@ -128,9 +133,6 @@ | ||
// handle the annotations of actions and action parameters | ||
// handle the annotations of cObject's (an entity) bound actions and action parameters | ||
// in: cObjectname : name of the object that holds the actions | ||
// cObject : the object itself | ||
function handleActions(cObjectname, cObject) { | ||
// edm target name is "<serviceName>.EntityContainer/<actionName>" or | ||
// "<serviceName>.EntityContainer/<actionName>/<parameterName>" | ||
function handleBoundActions(cObjectname, cObject) { | ||
// TODO the respective entity names do not appear in the target name | ||
@@ -148,13 +150,19 @@ // => in edm all actions are in the same namespace | ||
let action = cObject.actions[actionName]; | ||
let edmTargetName = serviceName + ".EntityContainer/" + actionName; | ||
handleAnnotations(edmTargetName, action); | ||
for (let paramName in cObject.actions[actionName].params) { | ||
let param = cObject.actions[actionName].params[paramName]; | ||
edmTargetName = serviceName + ".EntityContainer/" + actionName + "/" + paramName; | ||
handleAnnotations(edmTargetName, param); | ||
} | ||
handleAction(serviceName + "." + actionName, action); | ||
} | ||
} | ||
// handle the annotations of an action and its parameters | ||
// in: cActionName : name of the action, full name | ||
// cAction : the action object | ||
function handleAction(cActionName, cAction) { | ||
handleAnnotations(cActionName, cAction); | ||
for (let paramName in cAction.params) { | ||
let param = cAction.params[paramName]; | ||
let edmTargetName = cActionName + "/" + paramName; | ||
handleAnnotations(edmTargetName, param); | ||
} | ||
} | ||
// note: in csn, all annotations are flattened out | ||
@@ -213,2 +221,7 @@ // => values can be | ||
} | ||
else if (carrier.kind === 'action' || carrier.kind === 'function' || carrier.kind === 'param') { | ||
// annotated object is an action/function: | ||
// find last . in name and insert "EntityContainer/" | ||
stdName = edmCarrierName.replace(/\.(?=[^.]*$)/, '.EntityContainer/'); | ||
} | ||
@@ -387,2 +400,7 @@ // result objects that holds all the annotation objects to be created | ||
// expected type is dTypeName | ||
// mappping rule for values: | ||
// if expected type is ... the expression to be generated is ... | ||
// floating point type except Edm.Decimal -> Float | ||
// Edm.Decimal -> Decimal | ||
// integer tpye -> Int | ||
function handleSimpleValue(val, dTypeName, context) { | ||
@@ -412,3 +430,3 @@ // caller already made sure that val is neither object nor array | ||
} | ||
else if (dTypeName == "Edm.Double") { | ||
else if (dTypeName == "Edm.Decimal") { | ||
if (isNaN(val) || isNaN(parseFloat(val))) { | ||
@@ -421,2 +439,10 @@ errorMessage(context, "found non-numeric string, but expected type " + dTypeName); | ||
} | ||
else if (dTypeName == "Edm.Double") { | ||
if (isNaN(val) || isNaN(parseFloat(val))) { | ||
errorMessage(context, "found non-numeric string, but expected type " + dTypeName); | ||
} | ||
else { | ||
typeName = "Float"; | ||
} | ||
} | ||
else if (isComplexType(dTypeName)) { | ||
@@ -455,10 +481,13 @@ errorMessage(context, "found String, but expected complex type " + dTypeName); | ||
} | ||
else if (dTypeName == "Edm.Double") { | ||
typeName = "Decimal"; | ||
} | ||
else if (dTypeName == "Edm.Boolean") { | ||
errorMessage(context, "found number, but expected type " + dTypeName); | ||
} | ||
else if (dTypeName == "Edm.Decimal") { | ||
typeName = "Decimal"; | ||
} | ||
else if (dTypeName == "Edm.Double") { | ||
typeName = "Float"; | ||
} | ||
else { | ||
typeName = Number.isInteger(val) ? 'Int' : 'Decimal'; | ||
typeName = Number.isInteger(val) ? 'Int' : 'Float'; | ||
} | ||
@@ -465,0 +494,0 @@ } |
@@ -31,3 +31,8 @@ 'use strict'; | ||
// helper function | ||
// helper functions | ||
function getTargetOfAssoc(assoc) { | ||
// assoc.target can be the name of the target or the object itself | ||
return (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target]; | ||
} | ||
function getKeyOfTargetOfManagedAssoc(assoc) { | ||
@@ -160,8 +165,16 @@ // assoc.target can be the name of the target or the object itself | ||
let path = ae["="]; | ||
if (carrier.kind == 'entity') { | ||
let element = carrier.elements[path]; | ||
// FIXME: Deal with multi-step paths properly | ||
if (element && glue.isAssociation(element) && !element.onCond) { | ||
ae["="] = path + fkSeparator + getKeyOfTargetOfManagedAssoc(element); | ||
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) { // only for managed | ||
ae["="] += fkSeparator + getKeyOfTargetOfManagedAssoc(el); | ||
} | ||
} | ||
} | ||
@@ -326,9 +339,8 @@ } | ||
if (options && options.tntFlavor && !options.tntFlavor.skipAnnosSubstitutingFKeysForAssocs) { | ||
// replace association by fk field | ||
if (aNameWithoutQualifier == "@UI.LineItem" || | ||
aNameWithoutQualifier == "@UI.Identification" || | ||
aNameWithoutQualifier == "@UI.FieldGroup") { | ||
for (let i in a) { | ||
let ae = a[i]; | ||
if (ae["Value"] != undefined && ae["Value"]["="] != undefined) { | ||
// replace association by fk field, mind nested annotatinos | ||
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) { | ||
if (ae["Value"] && ae["Value"]["="]) { | ||
replaceManagedAssocByFK(ae["Value"]); | ||
@@ -341,4 +353,3 @@ } | ||
if (aNameWithoutQualifier == "@UI.SelectionFields") { | ||
for (let i in a) { | ||
let ae = a[i]; | ||
for (let ae of a) { | ||
if ("=" in ae) { | ||
@@ -345,0 +356,0 @@ replaceManagedAssocByFK(ae); |
@@ -302,7 +302,9 @@ // Transform augmented CSN into compact "official" CSN | ||
let model = compact( ...args ); | ||
let result = Object.create(null); | ||
let definitions = model.definitions; | ||
for (let k of Object.keys( definitions ).sort()) | ||
result[k] = normalizeNode( definitions[k] ); | ||
model.definitions = result; | ||
if (model.definitions) { | ||
let result = Object.create(null); | ||
let definitions = model.definitions; | ||
for (let k of Object.keys( definitions ).sort()) | ||
result[k] = normalizeNode( definitions[k] ); | ||
model.definitions = result; | ||
} | ||
return model; | ||
@@ -309,0 +311,0 @@ } |
@@ -9,3 +9,3 @@ // Wrapper around generated ANTLR parser | ||
var { CompileMessage } = require('../base/messages'); | ||
var { getMessageFunction, CompileMessage } = require('../base/messages'); | ||
var errorStrategy = require('./errorStrategy'); | ||
@@ -20,17 +20,8 @@ | ||
super(...args); | ||
this.messages = []; | ||
} | ||
// Push message `msg` with location `loc` to array of errors: | ||
message( msg, loc, severity ) { | ||
this.messages.push( new CompileMessage( loc, msg, severity ) ); | ||
} | ||
// method which is called by generated parser: | ||
// method which is called by generated parser with --trace-parser[-amg]: | ||
syntaxError( recognizer, offendingSymbol, line, column, msg, e ) { | ||
var loc = recognizer.tokenLocation( offendingSymbol ); | ||
//console.log(e); throw new CompileMessage( loc, msg ); | ||
var err = new CompileMessage( loc, msg[0].toUpperCase() + msg.slice(1) ); | ||
if (e && e.expectedTokens) | ||
err.expectedTokens = e.expectedTokens; | ||
this.messages.push( err ); | ||
if (!(e instanceof CompileMessage)) // not already reported | ||
recognizer.message( null, offendingSymbol, msg ); | ||
} | ||
@@ -103,5 +94,7 @@ } | ||
parser.filename = filename; | ||
parser.options = options; | ||
parser.$message = getMessageFunction( parser ); // sets parser.messages | ||
initTokenRewrite( parser, tokenStream ); | ||
parser.options = options; | ||
parser.filename = filename; | ||
// comment the following 2 lines if you want to output the parser errors directly: | ||
@@ -133,4 +126,5 @@ parser.messageErrorListener = errorListener; | ||
parser.removeErrorListeners(); | ||
parser.addErrorListener( errorListener ); | ||
parser.avoidErrorListeners = true; | ||
} | ||
parser.addErrorListener( errorListener ); | ||
@@ -147,3 +141,3 @@ if (options.parseListener) { | ||
ast.messages = errorListener.messages; | ||
ast.messages = parser.messages; | ||
if (options.attachTokens === true || options.attachTokens === filename) | ||
@@ -150,0 +144,0 @@ ast.tokenStream = tokenStream; |
@@ -39,2 +39,4 @@ // Error strategy with special handling for (non-reserved) keywords | ||
var SEMI = null; | ||
// Remember context for potential error message | ||
@@ -97,2 +99,3 @@ function epsilon() { | ||
sync, | ||
singleTokenInsertion, | ||
reportNoViableAlternative, | ||
@@ -107,2 +110,3 @@ reportInputMismatch, | ||
getExpectedTokensForMessage, | ||
getTokenDisplay, | ||
constructor: KeywordErrorStrategy | ||
@@ -134,2 +138,4 @@ }); | ||
} | ||
// TODO: expected token is identifier, current is KEYWORD | ||
if (nextTokens.contains(antlr4.Token.EPSILON)) { | ||
@@ -154,3 +160,4 @@ if (recognizer.$nextTokensToken !== token) { | ||
// report error and recover if possible | ||
if( this.singleTokenDeletion(recognizer) !== null) { | ||
if( token.text !== '}' && | ||
this.singleTokenDeletion(recognizer) !== null) { // also calls reportUnwantedToken | ||
return; | ||
@@ -172,2 +179,26 @@ } else { | ||
} | ||
function singleTokenInsertion( recognizer ) { | ||
var currentSymbol = recognizer.getTokenStream().LT(1); | ||
// if current token is consistent with what could come after current | ||
// ATN state, then we know we're missing a token; error recovery | ||
// is free to conjure up and insert the missing token | ||
var atn = recognizer._interp.atn; | ||
var currentState = atn.states[recognizer.state]; | ||
var next = currentState.transitions[0].target; | ||
var expectingAtLL2 = atn.nextTokens(next, recognizer._ctx); | ||
if (expectingAtLL2.contains(currentSymbol.type) ) { | ||
if (currentSymbol.text === '}') { | ||
if (SEMI == null) | ||
SEMI = recognizer.literalNames.indexOf( "';'" ); | ||
if (atn.nextTokens( currentState ).contains(SEMI)) | ||
return true; | ||
} | ||
this.reportMissingToken(recognizer); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
// Report `NoViableAltException e` signalled by parser `recognizer` | ||
@@ -177,2 +208,14 @@ function reportNoViableAlternative( recognizer, e ) { | ||
if (e.startToken === e.offendingToken) { // mismatch at LA(1) | ||
if (e.offendingToken.text === '}') { | ||
// Do it generally, or even in reportError()? - the catch clause does not work in Antlr4.Js | ||
if (SEMI == null) | ||
SEMI = recognizer.literalNames.indexOf( "';'" ); | ||
let s = recognizer._interp.atn.states[recognizer.state]; | ||
let nextTokens = recognizer.atn.nextTokens(s); | ||
if (nextTokens.contains(SEMI)) | ||
return; | ||
// Remarks: this does not work if it fails in a subrule, i.e. if | ||
// nextTokens.contains(EPSILON) - more serious ANTLR hacking would be | ||
// required to get the nextTokens of the calling rule... | ||
} | ||
this.reportInputMismatch( recognizer, e ); | ||
@@ -194,8 +237,16 @@ } | ||
this.getExpectedTokensForMessage( recognizer, e.offendingToken, deadEnds ); | ||
var msg = "Mismatched input " + this.getTokenErrorDisplay(e.offendingToken); | ||
if (expecting) { | ||
msg += " expecting " + expecting.toString(recognizer.literalNames, recognizer.symbolicNames); | ||
e.expectedTokens = intervalSetToArray( recognizer, expecting ); | ||
let offending = this.getTokenDisplay( e.offendingToken, recognizer ); | ||
let err; | ||
if (expecting && expecting.length) { | ||
err = recognizer.message( 'syntax-mismatched-token', e.offendingToken, | ||
{ offending, expecting: expecting.join(', ') }, | ||
'Error', 'Mismatched $(OFFENDING), expecting $(EXPECTING)' ); | ||
err.expectedTokens = expecting; | ||
} | ||
recognizer.notifyErrorListeners(msg, e.offendingToken, e); | ||
else { // should not really happen anymore... -> no messageId ! | ||
err = recognizer.message( null, e.offendingToken, { offending }, | ||
'Error', 'Mismatched $(OFFENDING)' ); | ||
} | ||
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig | ||
recognizer.notifyErrorListeners( err.message, e.offendingToken, err ); | ||
} | ||
@@ -205,14 +256,15 @@ | ||
function reportUnwantedToken( recognizer ) { | ||
if (this.inErrorRecoveryMode(recognizer)) { | ||
if (this.inErrorRecoveryMode(recognizer)) | ||
return; | ||
} | ||
this.beginErrorCondition(recognizer); | ||
var t = recognizer.getCurrentToken(); | ||
var tokenName = this.getTokenErrorDisplay(t); | ||
var expecting = this.getExpectedTokensForMessage( recognizer, t ); | ||
var e = new antlr4.error.InputMismatchException( recognizer ); | ||
e.expectedTokens = intervalSetToArray( recognizer, expecting ); | ||
var msg = "Extraneous input " + tokenName + " expecting " + | ||
expecting.toString(recognizer.literalNames, recognizer.symbolicNames); | ||
recognizer.notifyErrorListeners(msg, t, e); | ||
var token = recognizer.getCurrentToken(); | ||
var expecting = this.getExpectedTokensForMessage( recognizer, token ); | ||
var offending = this.getTokenDisplay( token, recognizer ); | ||
let err = recognizer.message( 'syntax-extraneous-token', token, | ||
{ offending, expecting: expecting.join(', ') }, | ||
'Error', 'Extraneous $(OFFENDING), expecting $(EXPECTING)' ); | ||
err.expectedTokens = expecting; | ||
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig | ||
recognizer.notifyErrorListeners( err.message, token, err ); | ||
} | ||
@@ -222,17 +274,18 @@ | ||
function reportMissingToken( recognizer ) { | ||
if ( this.inErrorRecoveryMode(recognizer)) { | ||
if ( this.inErrorRecoveryMode(recognizer)) | ||
return; | ||
} | ||
this.beginErrorCondition(recognizer); | ||
var t = recognizer.getCurrentToken(); | ||
var expecting = this.getExpectedTokensForMessage( recognizer, t ); | ||
var e = new antlr4.error.InputMismatchException( recognizer ); | ||
e.expectedTokens = intervalSetToArray( recognizer, expecting ); | ||
var msg = "Missing " + expecting.toString(recognizer.literalNames, recognizer.symbolicNames) + | ||
" at " + this.getTokenErrorDisplay(t); | ||
recognizer.notifyErrorListeners(msg, t, e); | ||
var token = recognizer.getCurrentToken(); | ||
var expecting = this.getExpectedTokensForMessage( recognizer, token ); | ||
var offending = this.getTokenDisplay( token, recognizer ); | ||
// TODO: if non-reserved keyword will not been parsed as keyword, use Identifier for offending | ||
let err = recognizer.message( 'syntax-missing-token', token, | ||
{ offending, expecting: expecting.join(', ') }, | ||
'Error', 'Missing $(EXPECTING) before $(OFFENDING)' ); | ||
err.expectedTokens = expecting; | ||
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig | ||
recognizer.notifyErrorListeners( err.message, token, err ); | ||
} | ||
var SEMI = null; | ||
function consumeUntil( recognizer, set ) { | ||
@@ -260,2 +313,4 @@ if (SEMI == null) | ||
// We now also allow keywords if the Identifier is expected. | ||
// Called by match() and in generated parser in "else part" before consume() | ||
// for ( TOKEN1 | TOKEN2 ) | ||
function recoverInline( recognizer ) { | ||
@@ -300,6 +355,42 @@ var identType = recognizer.constructor.Identifier; | ||
} | ||
names.sort( (a, b) => tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1 ); | ||
return names; | ||
} | ||
const token1sort = { | ||
// 0: Identifier, Number, ... | ||
// 1: separators: | ||
',': 1, '.': 1, ':': 1, ';': 1, | ||
// 2: parentheses: | ||
'(': 2, ')': 2, '[': 2, ']':2, '{': 2, '}': 2, | ||
// 3: special: | ||
'!': 3, '#': 3, '$': 3, '?': 3, '@': 3, | ||
// 4: operators: | ||
'*': 4, '+': 4, '-': 4, '/': 4, '<': 4, '=': 4, '>': 4, '|': 4, | ||
// 8: KEYWORD | ||
// 9: <EOF> | ||
} | ||
function tokenPrecedence( name ) { | ||
if (name.length < 2 || name === '<EOF>') | ||
return '9' + name; | ||
let prec = token1sort[ name.charAt(1) ]; | ||
if (prec) | ||
return '' + prec + name; | ||
else | ||
return (name.charAt(1) < 'a' ? '8' : '0') + name; | ||
} | ||
function getTokenDisplay( token, recognizer ) { | ||
if (!token) | ||
return '<EOF>'; | ||
let t = token.type; | ||
if (t === antlr4.Token.EOF || t === antlr4.Token.EPSILON ) | ||
return '<EOF>'; | ||
else if (token.text === '.') // also for DOTbeforeBRACE | ||
return '\'.\''; | ||
else | ||
return recognizer.literalNames[t] || recognizer.symbolicNames[t]; | ||
} | ||
// Return an IntervalSet of token types which the parser had expected. Do not | ||
@@ -316,3 +407,3 @@ // include non-reserved keywords if not mentioned explicitly (i.e. other than | ||
if (recognizer.state < 0) | ||
return null; | ||
return []; | ||
if (recognizer.state >= atn.states.length) | ||
@@ -325,3 +416,3 @@ throw( 'Invalid state number ' + recognizer.state + ' for ' + | ||
if (!identType || !beforeUnreserved || beforeUnreserved + 2 > identType) | ||
return super1.getExpectedTokens.call( this, recognizer ); | ||
return intervalSetToArray( recognizer, super1.getExpectedTokens.call( this, recognizer ) ); | ||
@@ -355,3 +446,3 @@ var ll1 = new antlr4_LL1Analyzer(atn); | ||
// console.log(state, recognizer.$nextTokensState, expected.toString(recognizer.literalNames, recognizer.symbolicNames)); | ||
return expected; | ||
return intervalSetToArray( recognizer, expected ); | ||
@@ -384,4 +475,2 @@ // Add an interval `v` to the IntervalSet `this`. If `v` contains the token | ||
// probably overwrite getTokenErrorDisplay() - use token text | ||
module.exports = { | ||
@@ -388,0 +477,0 @@ epsilon, |
@@ -46,4 +46,6 @@ // Generic ANTLR parser class with AST-building functions | ||
setOnce, | ||
notYet, | ||
hanaFlavorOnly, | ||
noAssignmentInSameLine, | ||
noSemicolonHere, | ||
isStraightBefore, | ||
constructor: GenericAntlrParser // keep this last | ||
@@ -88,23 +90,22 @@ }); | ||
// Push message `msg` with location `loc` to array of errors: | ||
function message( msg, loc, severity ) { | ||
if (!this.options.parseOnly) // TODO: remove this test | ||
this.messageErrorListener.message( msg, loc, severity ); | ||
function message( id, loc, ...args ) { | ||
return this.$message( id, // function $message is set in antlrParser.js | ||
(loc instanceof antlr4.CommonToken) ? this.tokenLocation(loc) : loc, | ||
...args ); | ||
} | ||
// Push a message to the array of errors complaining that language construct | ||
// 'feature' is not yet supported (using the location `loc`, which can also be a token for its locatio'), unless | ||
// option 'parseOnly' or any of the options from 'optionsArray' are set. | ||
function notYet( feature, loc, optionsArray=[] ) { | ||
if (this.options.parseOnly) { | ||
// Generally ignore if only parsing | ||
// Use the following functions for language constructs which we (currently) | ||
// just being able to parse, in able to run tests from HANA CDS. As soon as we | ||
// create ASTs for the language construct and put it into a CSN, a | ||
// corresponding check should actually be inside the compiler, because the same | ||
// language construct can come from a CSN as source. | ||
// TODO: this is not completely done this way | ||
function hanaFlavorOnly( text, ...tokens ) { | ||
if (!text || this.options.hanaFlavor) | ||
return; | ||
if (typeof text !== 'string') { | ||
tokens = [ text, ...tokens ]; | ||
text = tokens.map( t => t.text.toUpperCase() ).join(' ') + ' is not supported'; | ||
} | ||
for (let option of optionsArray) { | ||
// Grammar says to ignore for this option | ||
if (this.options[option]) { | ||
return; | ||
} | ||
} | ||
this.messageErrorListener.message( `${feature} not supported yet`, | ||
(loc instanceof antlr4.CommonToken) ? this.tokenLocation(loc) : loc ); | ||
this.message( null, this.tokenLocation( tokens[0], tokens[ tokens.length-1 ] ), text ); | ||
} | ||
@@ -115,7 +116,24 @@ | ||
if (t.text === ';') | ||
this.messageErrorListener.message( `Unexpected ';' - previous keyword 'with' is ignored`, | ||
this.tokenLocation(t), 'Warning' ); | ||
this.message( 'syntax-ignored-with', t, {}, | ||
'Warning', `Unexpected ';' after WITH - ignored WITH` ); | ||
// TODO remove ';' from set of expected tokens | ||
} | ||
function noAssignmentInSameLine() { | ||
var t = this.getCurrentToken(); | ||
if (t.text === '@' && t.line <= this._input.LT(-1).line) | ||
this.message( 'syntax-anno-same-line', t, {}, | ||
'Warning', `Annotation assignment belongs to next statement` ); | ||
} | ||
// Use after matching ',' to allow ',' in front of the closing paren. Be sure | ||
// that you know what to do if successful - break/return/... = check the | ||
// generated grammar; inside loops, you can use `break`. This function is | ||
// still the preferred way to express an optional ',' at the end, because it | ||
// does not influence the error reporting. It might also allow to match | ||
// reserved keywords, because there is no ANTLR generated decision in front of it. | ||
function isStraightBefore( closing ) { | ||
return this.getCurrentToken().text === closing; | ||
} | ||
// Attach location matched by current rule to node `art`. If a location is | ||
@@ -186,4 +204,4 @@ // already provided, only set the end location. Use this function only | ||
if (!id) { | ||
this.message( "Quoted identifier must contain at least one character", | ||
this.tokenLocation( token ) ); | ||
this.message( 'syntax-empty-ident', token, {}, | ||
'Error', 'Quoted identifier must contain at least one character' ); | ||
} | ||
@@ -222,4 +240,6 @@ return { id, quoted: true, location: this.tokenLocation( token ) }; | ||
if (p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) | ||
this.message( p.test_msg, location ); | ||
// TODO: make tests available for CSN parser | ||
if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) && | ||
!this.options.parseOnly) | ||
this.message( null, location, p.test_msg ); // TODO: message id | ||
@@ -230,7 +250,7 @@ if (p.unexpected_char) | ||
if (~idx) { | ||
this.message( p.unexpected_msg, { | ||
this.message( null, { // TODO: message id | ||
filename: location.filename, | ||
start: atChar( idx ), | ||
end: atChar( idx + (val[idx] == '\'' ? 2 : 1) ) | ||
} ); | ||
}, p.unexpected_msg ); | ||
} | ||
@@ -291,3 +311,3 @@ } | ||
} | ||
else if (kind) { | ||
else if (kind || this.options.parseOnly) { | ||
addToDictWithIndexNo( parent, env, art.name.id, art ); | ||
@@ -299,7 +319,10 @@ } | ||
if (kind === 0) | ||
this.message( `Duplicate value for view parameter "${name}"`, loc ); | ||
this.message( 'duplicate-argument', loc, { name }, | ||
'Error', 'Duplicate value for parameter $(NAME)' ); | ||
else if (kind === '') | ||
this.message( `Duplicate EXCLUDING for source element "${name}"`, loc ); | ||
this.message( 'duplicate-excluding', loc, { name }, | ||
'Error', 'Duplicate EXCLUDING for source element $(NAME)' ); | ||
else | ||
this.message( `Duplicate assignment for structure property "${name}"`, loc ); | ||
this.message( 'duplicate-prop', loc, { name }, | ||
'Error', 'Duplicate value for structure property $(NAME)' ); | ||
} ); | ||
@@ -372,3 +395,4 @@ } | ||
if (prev) { | ||
this.message( `Option ${prev.option} has already been specified`, loc ); | ||
this.message( 'syntax-repeated-option', loc, { option: prev.option }, | ||
'Error', 'Option $(OPTION) has already been specified' ); | ||
} | ||
@@ -375,0 +399,0 @@ if (typeof value === 'boolean') { |
@@ -84,4 +84,5 @@ // Main entry point for the Research Vanilla CDS Compiler | ||
model.$frontend = 'json'; | ||
if(model.version && model.version.csn === "0.1.99") | ||
options.newCsn=true; // force new version TODO optimize? | ||
// TODO: I (CW) do not think that the following is a good idea... | ||
if (model.version && model.version.csn === "0.1.99") | ||
options.newCsn = true; // force new version TODO optimize? | ||
return model; | ||
@@ -88,0 +89,0 @@ } else if (options.fallbackParser || ['.cds', '.hdbcds', '.hdbdd'].includes(ext)) |
@@ -781,3 +781,5 @@ | ||
// date'2017-11-02' | ||
return result + v.literal + "'" + v.val + "'"; | ||
// date('2017-11-02') if sqlite | ||
return result + v.literal + `${options.toSql.dialect === 'sqlite' ? | ||
"('" : "'"}` + v.val + `${options.toSql.dialect === 'sqlite' ? "')" : "'"}`; | ||
} else if (v.literal == 'struct') { | ||
@@ -784,0 +786,0 @@ // { foo: 1 } |
@@ -134,3 +134,7 @@ "use strict"; | ||
// Perform implicit redirection of non-exposed association targets | ||
addImplicitRedirections(model); | ||
// Don't do it when producing SQL for sqlite, as there are no assocs to redirect any more | ||
// (FIXME : ... and implicit redirection fails when translation assoc-to-join has already run) | ||
if (!(options.toSql && options.toSql.dialect === 'sqlite')) { | ||
addImplicitRedirections(model); | ||
} | ||
@@ -137,0 +141,0 @@ // Process all artifacts (pass 1) |
@@ -277,2 +277,6 @@ 'use strict'; | ||
if (['element', 'key', 'param'].includes(member.kind)) { | ||
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation | ||
if (member._flatElementNameWithDots) { | ||
memberName = member._flatElementNameWithDots; | ||
} | ||
addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.toOdata.names), member); | ||
@@ -279,0 +283,0 @@ } |
@@ -21,3 +21,3 @@ 'use strict'; | ||
skipPropagatingFromInclude: false, // if true: Do not propagate properties from (first) included artifact | ||
skipPropagatingActions: false, // if true: Do not propagate bound actions (and functions) | ||
skipPropagatingActions: true, // if true: Do not propagate bound actions (and functions) | ||
skipPropagatingIncludes: false, // if true: Do not propagate the list of included artifacts | ||
@@ -24,0 +24,0 @@ skipNotPropagatingIndexableAnno: false, // if true: Do not make an exception for the propagation of the '@Indexable' annotation |
@@ -188,4 +188,14 @@ 'use strict'; | ||
let valueForeignKeyElementName = assoc.value.element.replace(/\./g, pathDelimiter) + fkSeparator + foreignKey.name.id; | ||
// For the foreign key element, take the same path as for the assoc, just without the last step | ||
let valueForeignKeyElementPath = []; | ||
if (assoc.value.path) { | ||
valueForeignKeyElementPath = cloneWithTransformations(assoc.value.path, {}).slice(0, -1); | ||
// Take over _artifact links from original path, because flattenStructStepsInPath requires them | ||
for (let i = 0; i < valueForeignKeyElementPath.length; i++) { | ||
valueForeignKeyElementPath[i]._artifact = assoc.value.path[i]._artifact; | ||
} | ||
} | ||
valueForeignKeyElementPath.push({ id: valueForeignKeyElementName }); | ||
foreignKeyElement.value = { | ||
path: [{ id: valueForeignKeyElementName }], | ||
path: valueForeignKeyElementPath, | ||
absolute: assoc.value.absolute, | ||
@@ -283,2 +293,4 @@ element: valueForeignKeyElementName, | ||
} | ||
// Preserve the generated element name as it would have been with 'hdbcds' names | ||
setProp(flatElem, '_flatElementNameWithDots', elem.name.id + '.' + grandChildName); | ||
result[flatElemName] = flatElem; | ||
@@ -316,2 +328,4 @@ } | ||
} | ||
// Preserve the generated element name as it would have been with 'hdbcds' names | ||
setProp(flatElem, '_flatElementNameWithDots', elem.name.id + '.' + childName); | ||
result[flatElemName] = flatElem; | ||
@@ -318,0 +332,0 @@ } |
@@ -18,5 +18,2 @@ 'use strict' | ||
if(!options.betaMode) | ||
signal(error`Conversion of associations into JOINs is not supported yet`); | ||
// Note: This is called from the 'forHana' transformations, so it is controlled by its options) | ||
@@ -590,9 +587,16 @@ const pathDelimiter = (options.forHana && options.forHana.names == 'hdbcds') ? '.' : '_'; | ||
replace the content of the old node with the new one | ||
If newNode is a join tree (rewritten from path), | ||
oldPath must be cleared first. | ||
If newNode is a path => oldNode._artifact === newNode._artifact, | ||
no need to exchange _artifact (as non-iterable property it is | ||
not assigned). | ||
*/ | ||
function replaceNodeContent(oldNode, newNode) | ||
{ | ||
Object.keys(oldNode).forEach(k => { | ||
delete oldNode[k] }); | ||
delete oldNode._artifact; | ||
delete oldNode._status; | ||
// if the newNode is a join expression, throw away old path content | ||
if(newNode.op) { | ||
Object.keys(oldNode).forEach(k => { | ||
delete oldNode[k] }); | ||
delete oldNode._artifact; | ||
} | ||
Object.assign(oldNode, newNode); | ||
@@ -599,0 +603,0 @@ } |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "1.1.1", | ||
"version": "1.1.2", | ||
"dependencies": { | ||
@@ -5,0 +5,0 @@ "ajv": { |
@@ -1,1 +0,1 @@ | ||
{"bin":{"cdsc":"bin/cdsc.js"},"bundleDependencies":false,"dependencies":{"ajv":"6.1.1","antlr4":"4.7.1","commander":"2.14.0","fs-extra":"5.0.0","resolve":"1.5.0","sax":"1.2.4"},"deprecated":false,"description":"Standard-Feature-Set Vanilla-CDS in Product Quality","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","repository":{"type":"git","url":"git@github.wdf.sap.corp/CDS/cds-compiler.git"},"version":"1.1.1","license":"SEE LICENSE IN developer-license-3.1.txt"} | ||
{"bin":{"cdsc":"bin/cdsc.js"},"bundleDependencies":false,"dependencies":{"ajv":"6.1.1","antlr4":"4.7.1","commander":"2.14.0","fs-extra":"5.0.0","resolve":"1.5.0","sax":"1.2.4"},"deprecated":false,"description":"Standard-Feature-Set Vanilla-CDS in Product Quality","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","repository":{"type":"git","url":"git@github.wdf.sap.corp/CDS/cds-compiler.git"},"version":"1.1.2","license":"SEE LICENSE IN developer-license-3.1.txt"} |
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
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
54111
2908874