/**
 * Copyright 2018 Twitter, Inc.
 * Licensed under the Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 */ import { Ast , SlotAst , jsonAst , CallAst , ListAst , walkAst , astSlotNames , RuleAst , StatementAst , } from "../ast/Ast" ; import SqrlAst from "../ast/SqrlAst" ; import SqrlAstTransformer from "../ast/SqrlAstTransformer" ; import { SqrlSlot , SqrlConstantSlot , SqrlStatementSlot , SqrlFixedSlot , SqrlIteratorSlot , SqrlEmptySlot , SqrlInputSlot , } from "../slot/SqrlSlot" ; import { SqrlInstance } from "../function/Instance" ; import invariant from "../jslib/invariant" ; import mapObject from "../jslib/mapObject" ; import { SerializedSlot , deserializeSlot } from "../slot/SerializedSlot" ; import { isValidFeatureName } from "../feature/FeatureName" ; import SqrlImporter from "./SqrlImporter" ; import SqrlFeatureSlot from "../slot/SqrlFeatureSlot" ; import { reduceTruthTable } from "./SqrlTruthTable" ; import { murmurhashJsonHexSync } from "../jslib/murmurhashJson" ; import { EntityId } from "../platform/EntityId" ; import { Filesystem , EmptyFilesystem } from "../api/filesystem" ; import SqrlRuleSlot from "../slot/SqrlRuleSlot" ; import { RuleSpec , RuleSpecMap } from "../api/spec" ; import { AbstractLogger } from "../util/Logger" ; import { LogProperties , getGlobalLogger } from "../api/log" ; import { buildSqrlError } from "./buildSqrlError" ; import { sqrlInvariant } from "../api/parse" ; import { FeatureMap } from "../api/execute" ; import { INPUT_FUNCTION } from "../function/ControlFunctions" ; export interface SqrlParserSourceOptions { statements : StatementAst [ ] ; source : string ; filesystem : Filesystem ; } export interface SqrlParserOptions { statements : StatementAst [ ] ; filesystem ? : Filesystem ; instance : SqrlInstance ; allowAssertions ? : boolean ; allowPrivate ? : boolean ; baseLibrary ? : string ; source ? : string ; mainFile ? : string ; setInputs ? : FeatureMap ; allowReplaceInput ? : boolean ; usedFiles ? : string [ ] ; } export interface SqrlSerialized { slots : { [ name ] : SerializedSlot ; } ; } export type SlotFilter = ( slotName : string ) => boolean ; function labelOperationWaitNames ( props ? ) { const { operation , label , feature } = props ; return [ `wait-label:op=${ operation }:label=${ label }:feature=${ feature }` ] ; } export abstract class SqrlParseInfo extends AbstractLogger { baseLibrary : string ; allowAssertions : boolean ; allowPrivate : boolean ; instance : SqrlInstance ; importer : SqrlImporter ; filesystem : Filesystem ; remainingInputs : FeatureMap ; allowReplaceInput : boolean ; statements : StatementAst [ ] ; constructor ( public slots , public options ) { super ( ) ; this . statements = options . statements ; this . allowAssertions = options . allowAssertions || false ; this . allowPrivate = options . allowPrivate || false ; this . baseLibrary = options . baseLibrary || "common.sqrl" ; this . instance = options . instance ; this . allowReplaceInput = options . allowReplaceInput || false ; this . filesystem = options . filesystem || new EmptyFilesystem ( ) ; this . importer = new SqrlImporter ( this . filesystem , this . instance . customFunctions ) ; this . remainingInputs = Object . assign ( { } , options . setInputs || { } ) ; } log ( level , props , format , ... params ) { getGlobalLogger ( ) . log ( level , props , format , params ) ; } mapToSlots ( names ) { return names . map ( ( name ) => { invariant ( this . slots . hasOwnProperty ( name ) , "Could not find slot:: " + name ) ; return this . slots [ name ] ; } ) ; } foreachRuleSlot ( callback ) { Object . values ( this . slots ) . forEach ( ( slot ) => { if ( slot instanceof SqrlRuleSlot ) { callback ( slot ) ; } } ) ; } foreachFeatureSlot ( callback ) { Object . values ( this . slots ) . forEach ( ( slot ) => { if ( slot instanceof SqrlFeatureSlot ) { callback ( slot ) ; } } ) ; } resetReplaceableFeatures ( ) { this . foreachFeatureSlot ( ( slot ) => slot . allowReplace ( ) ) ; } getRuleSlots ( ) { const rv = { } ; this . foreachRuleSlot ( ( slot ) => { rv [ slot . name ] = slot ; } ) ; return rv ; } getFeatureSlotNames ( ) { return Object . keys ( this . slots ) . filter ( isValidFeatureName ) ; } getRuleSpecs ( ) { return mapObject ( this . getRuleSlots ( ) , ( value ) => { return value . ruleSpec ; } ) ; } getRuleSpec ( sourceAst , ruleName ) { const slot = this . slots [ ruleName ] ; if ( slot instanceof SqrlRuleSlot ) { return slot . ruleSpec ; } else { throw buildSqrlError ( sourceAst , `Unknown rule: ${ ruleName }` ) ; } } } export class SqrlParserState extends SqrlParseInfo { _astTransformer : SqrlAstTransformer | null ; _pushStatement : ( ast : Ast ) => void = null ; globalWhere : Ast = SqrlAst . constant ( true ) ; currentIterator : string | null = null ; instance : SqrlInstance ; usedFiles : Set < string > ; constructor ( options , serialized = null ) { super ( serialized ? mapObject ( serialized . slots , ( data ) => deserializeSlot ( data ) ) : { } , options ) ; this . instance = this . instance ; this . usedFiles = new Set ( options . usedFiles || [ ] ) ; this . setDefaultValue ( "SqrlMutate" , SqrlAst . constant ( true ) ) ; this . setDefaultValue ( "SqrlIsClassify" , SqrlAst . constant ( true ) ) ; this . setDefaultValue ( "SqrlClock" , SqrlAst . call ( "now" , [ ] ) ) ; const executionComplete = this . ensureStatementFeature ( null , "SqrlExecutionComplete" ) ; [ ... this . instance . statementFeatures ] . forEach ( ( name ) => { this . ensureStatementFeature ( null , name ) ; if ( name !== "SqrlAssertionStatements" ) { executionComplete . addWait ( name ) ; } } ) ; } ensureIterator ( ast , name ) { if ( this . slots . hasOwnProperty ( name ) ) { sqrlInvariant ( ast , this . slots [ name ] instanceof SqrlIteratorSlot , "Iterators cannot have the same name as a feature or rule" ) ; } else { this . slots [ name ] = new SqrlIteratorSlot ( name ) ; } } setDefaultValue ( name , valueAst ) { invariant ( this . slotIsEmpty ( name ) , "Slot was already defined: %s" , name ) ; this . slots [ name ] = new SqrlFixedSlot ( name , valueAst , true ) ; } pushStatement ( ast ) { invariant ( this . _pushStatement !== null , "ParserState push statement not set" ) ; if ( ast . type === "let" ) { if ( ast . expr . type === "call" && ast . expr . func === INPUT_FUNCTION ) { if ( ! this . slotIsEmpty ( ast . feature ) ) { throw buildSqrlError ( ast , "Multiple definitions of feature" ) ; } if ( this . remainingInputs . hasOwnProperty ( ast . feature ) ) { this . slots [ ast . feature ] = new SqrlConstantSlot ( ast . feature , this . remainingInputs [ ast . feature ] ) ; delete this . remainingInputs [ ast . feature ] ; } else { this . slots [ ast . feature ] = new SqrlInputSlot ( ast . feature ) ; } return ; } } else if ( ast . type === "call" && ast . func === "_resetReplaceableFeatures" ) { sqrlInvariant ( ast , ast . args . length === 0 , "Expected no arguments to _resetReplaceableFeatures" ) ; this . resetReplaceableFeatures ( ) ; return ; } ast = this . transform ( ast ) ; if ( ast . type === "let" ) { const oldSlot = this . slots [ ast . feature ] ; if ( this . slotIsEmpty ( ast . feature ) || ( oldSlot instanceof SqrlInputSlot && this . allowReplaceInput ) || ( oldSlot instanceof SqrlFixedSlot && oldSlot . replaceable ) ) { this . slots [ ast . feature ] = new SqrlFeatureSlot ( ast . feature ) ; } const slot = this . slots [ ast . feature ] ; if ( ! ( slot instanceof SqrlFeatureSlot ) ) { throw buildSqrlError ( ast , "Feature was previously defined" ) ; } slot . mergeGlobal ( ast , this . globalWhere , ( ast , globalWhere ) => this . combineWithProvidedGlobalWhere ( ast , globalWhere ) ) ; } else if ( ast . type === "rule" ) { if ( ! this . slotIsEmpty ( ast . name ) ) { throw buildSqrlError ( ast , "Feature was previously defined" ) ; } const slot = new SqrlRuleSlot ( ast . name ) ; this . slots [ ast . name ] = slot ; slot . setRule ( ast as RuleAst , this . globalWhere ) ; } else { this . _pushStatement ( ast ) ; } } setPushStatement ( pushStatement ) { this . _pushStatement = pushStatement ; } slotIsEmpty ( name ) { if ( ! this . slots . hasOwnProperty ( name ) ) { return true ; } else if ( this . slots [ name ] instanceof SqrlEmptySlot ) { return true ; } else { return false ; } } hasSlot ( name ) { return this . slots . hasOwnProperty ( name ) ; } getSlot ( name ) { invariant ( this . hasSlot ( name ) , "Could not find named slot:: %s" , name ) ; return this . slots [ name ] ; } getFeatureSlot ( sourceAst , name ) { if ( ! this . hasSlot ( name ) ) { throw buildSqrlError ( sourceAst , "Could not find feature: %s" , name ) ; } const slot = this . slots [ name ] ; if ( ! ( slot instanceof SqrlFeatureSlot ) ) { throw new Error ( "Expected feature slot: " + name ) ; } return slot ; } ensureAllSlots ( slotNames ) { slotNames . forEach ( ( name ) => { if ( ! this . slots . hasOwnProperty ( name ) ) { this . slots [ name ] = new SqrlEmptySlot ( name ) ; } } ) ; } astContainsCurrentIterator ( ast ) { let seenIterator = false ; walkAst ( ast , ( node ) => { if ( node . type === "iterator" ) { seenIterator = true ; } } ) ; return seenIterator ; } ensureStatementFeature ( sourceAst , featureName ) { if ( this . slots . hasOwnProperty ( featureName ) ) { const slot = this . slots [ featureName ] ; if ( ! ( slot instanceof SqrlStatementSlot ) ) { throw new Error ( "Expected statement slot: " + featureName ) ; } return slot ; } else { return this . addStatementFeature ( sourceAst , featureName ) ; } } addWhenStatement ( sourceAst , basicLabelOperations , ast , name = null ) { const registeredCall = SqrlAst . registerCall ( ast ) ; this . addStatement ( sourceAst , "SqrlWhenStatements" , registeredCall , name ) ; basicLabelOperations . forEach ( ( labelOperation ) => { const waitNames = labelOperationWaitNames ( labelOperation ) ; waitNames . forEach ( ( waitName ) => { this . ensureStatementFeature ( sourceAst , waitName ) ; this . addStatement ( sourceAst , waitName , registeredCall ) ; } ) ; } ) ; } addCallStatement ( sourceAst , ast ) { this . instance . assertStatementAst ( ast ) ; const statementFeature = this . instance . statementFeature ( ast . func ) ; return this . addStatement ( sourceAst , statementFeature , SqrlAst . registerCall ( ast ) ) ; } addStatement ( sourceAst , globalName , ast , name = null ) { const slotAst = this . newGlobal ( sourceAst , ast , name ) ; invariant ( this . slots . hasOwnProperty ( slotAst . slotName ) , "Could not find given slot" ) ; invariant ( this . slots . hasOwnProperty ( globalName ) , "Could not find global for statement:: %s" , globalName ) ; this . addStatementSlot ( globalName , slotAst ) ; return slotAst ; } addStatementSlot ( globalName , slotAst ) { const slot = this . slots [ globalName ] ; if ( ! ( slot instanceof SqrlStatementSlot ) ) { throw new Error ( "Expected statement slot: " + globalName ) ; } slot . addWait ( slotAst . slotName ) ; } newGlobal ( sourceAst , ast , name = null ) { invariant ( typeof sourceAst === "object" , "Expected object sourceAst" ) ; invariant ( typeof ast === "object" , "Expected object ast" ) ; sqrlInvariant ( sourceAst , this . currentIterator === null || ! this . astContainsCurrentIterator ( ast ) , "Expression is not valid during a list comprehension" ) ; ast = SqrlAst . branch ( this . globalWhere , this . transform ( ast ) , null ) ; if ( ! name ) { name = "ast:" + SqrlAst . hash ( ast ) ; } if ( ! this . slotIsEmpty ( name ) ) { const slot = this . slots [ name ] ; if ( ! ( slot instanceof SqrlFixedSlot ) ) { throw new Error ( "expected fixed slot for: " + name ) ; } if ( ! SqrlAst . areEqual ( ast , slot . finalizedAst ( ) ) ) { throw new Error ( "Slots saved with the same name must be identical:: " + `${ name } [${ jsonAst ( slot . finalizedAst ( ) ) } != ${ jsonAst ( ast ) }]` ) ; } } else { this . slots [ name ] = new SqrlFixedSlot ( name , ast ) ; } return SqrlAst . slot ( this . getSlot ( name ) ) ; } addStatementFeature ( ast , featureName ) { if ( this . slots . hasOwnProperty ( featureName ) ) { throw buildSqrlError ( ast , "Feature already exists: " + featureName ) ; } const slot = new SqrlStatementSlot ( featureName ) ; this . slots [ featureName ] = slot ; return slot ; } ensureConstantSlot ( sourceAst , name , value ) { if ( this . slots . hasOwnProperty ( name ) ) { const slot = this . slots [ name ] ; if ( ! ( slot instanceof SqrlConstantSlot ) ) { throw new Error ( "Expected SqrlConstantSlot for " + name ) ; } return slot ; } else { const slot = new SqrlConstantSlot ( name , value ) ; this . slots [ name ] = slot ; return slot ; } } ensureGlobalWithoutWhere ( sourceAst , name , callback ) { if ( ! this . slots . hasOwnProperty ( name ) ) { const rv = callback ( ) ; this . newGlobalWithoutWhere ( sourceAst , rv as Ast , name ) ; return this . slots [ name ] ; } return this . slots [ name ] ; } newGlobalWithoutWhere ( sourceAst , ast , name = null ) { return this . withoutGlobalWhere ( ( ) => this . newGlobal ( sourceAst , ast , name ) ) ; } printFeatureLoop ( slot ) { const current = new Set ( ) ; const results = [ ] ; const printLevel = ( slot , prefix = "" ) => { if ( current . has ( slot . name ) ) { results . push ( prefix + slot . name ) ; return ; } const add = ! current . has ( slot . name ) ; if ( add ) { current . add ( slot . name ) ; } astSlotNames ( slot . finalizedAst ( ) ) . forEach ( ( slotName ) => { printLevel ( this . slots [ slotName ] , prefix + slot . name + " > " ) ; } ) ; if ( add ) { current . delete ( slot . name ) ; } } ; printLevel ( slot ) ; return results ; } combineWithProvidedGlobalWhere ( whereAst , globalWhere ) { const combinedAst = SqrlAst . and ( globalWhere , whereAst ) ; combinedAst . location = whereAst . location ; const { features : whereFeatures , truthTable : whereTruth , } = reduceTruthTable ( combinedAst ) ; whereAst = SqrlAst . bool ( combinedAst ) ; if ( whereAst . type !== "constant" ) { const whereBoolName = `bool(${ whereFeatures . join ( "," ) }:${ whereTruth })` ; if ( this . slots . hasOwnProperty ( whereBoolName ) ) { whereAst = SqrlAst . slot ( this . getSlot ( whereBoolName ) ) ; } else { whereAst = this . newGlobalWithoutWhere ( whereAst , SqrlAst . bool ( combinedAst ) , whereBoolName ) ; } } return { combinedAst , whereAst , whereFeatures , whereTruth } ; } combineGlobalWhere ( whereAst ) { return this . combineWithProvidedGlobalWhere ( whereAst , this . globalWhere ) ; } transform ( ast ) { if ( ! this . _astTransformer ) { this . _astTransformer = new SqrlAstTransformer ( this ) ; } return this . _astTransformer . transform ( ast ) ; } withoutGlobalWhere < T > ( callback ) { const prevWhere = this . globalWhere ; this . globalWhere = SqrlAst . constant ( true ) ; const result = callback ( ) ; this . globalWhere = prevWhere ; return result ; } wrapWhere < T > ( whereAst , callback ) { reduceTruthTable ( whereAst ) ; const prevWhere = this . globalWhere ; this . globalWhere = SqrlAst . and ( this . globalWhere , whereAst ) ; const result = callback ( ) ; this . globalWhere = prevWhere ; return result ; } wrapIterator < T > ( iterator , callback ) { invariant ( this . currentIterator === null , "Multiple levels of iterators are not supported." ) ; this . currentIterator = iterator ; const result = callback ( ) ; this . currentIterator = null ; return result ; } serialize ( ) { return { slots : mapObject ( this . slots , ( slot ) => slot . serialize ( ) ) , } ; } extractDataObjectConstantKeys ( ast ) { sqrlInvariant ( ast , SqrlAst . isSimpleDataObject ( ast ) , "Expected argument to be object with constant keys" ) ; if ( ast . type === "constant" && typeof ast . value === "object" ) { return Object . keys ( ast . value ) ; } const keys = [ ] ; for ( let idx = 0 ; idx < ast . args . length ; idx += 2 ) { const arg = ast . args [ idx ] ; sqrlInvariant ( arg , arg . type === "constant" && typeof arg . value === "string" , "Expected constant string key for object here" ) ; keys . push ( arg . value ) ; } return keys ; } extractListConstantStrings ( ast ) { sqrlInvariant ( ast , ast . type === "list" , "Expected list" ) ; return ast . exprs . map ( ( e ) => { if ( e . type !== "constant" || typeof e . value !== "string" ) { throw buildSqrlError ( ast , "Expected constant string here" ) ; } return e . value ; } ) ; } interpretCounterCallAst ( ast ) { if ( ast . type !== "call" ) { throw new Error ( "Cannot interpret non-function call:: " + ast . type ) ; } sqrlInvariant ( ast , ast . args . length === 2 , `${ ast . func }() function requires keyword arguments` ) ; const args = ast . args [ 0 ] ; const { whereAst , whereFeatures , whereTruth } = this . combineGlobalWhere ( ast . args [ 1 ] ) ; const rv = { args , whereAst } ; if ( whereTruth !== "" ) { Object . assign ( rv , { whereFeatures , whereTruth } ) ; } return rv ; } constantEntityAst ( sourceAst , type , key ) { const entityAst = SqrlAst . call ( "entity" , SqrlAst . constants ( type , key ) ) ; return this . withoutGlobalWhere ( ( ) => this . newGlobal ( sourceAst , entityAst , `entity(${ type }/${ key })` ) ) ; } counterEntity ( sourceAst , entityType , props ) { const entityKey = murmurhashJsonHexSync ( props ) ; const entityId = new EntityId ( entityType , entityKey ) ; const entityAst = this . constantEntityAst ( sourceAst , entityId . type , entityId . key ) ; return { entityId , entityAst } ; } }