/**
 * Copyright 2018 Twitter, Inc.
 * Licensed under the Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 */ import invariant from "../jslib/invariant" ; import { SqrlSlot } from "../slot/SqrlSlot" ; import { Ast , CallAst , ConstantAst , FeatureAst , ListAst , AstLocation , LetAst , mapAst , SlotAst , jsonAst , NotAst , BinaryExprAst , IncludeAst , } from "./Ast" ; import { SqrlObject } from "../object/SqrlObject" ; import { AstBuilder } from "../helpers/AstBuilder" ; import { murmurhashHexSync } from "../jslib/murmurhashJson" ; function invariantAst ( ast ) { invariant ( typeof ast === "object" && typeof ast . type === "string" , "Expected an AST node" ) ; } const SqrlAst = { SqrlSlot , featureEquals ( feature , val ) { return { type : "binary_expr" , left : SqrlAst . feature ( feature ) , operator : "=" , right : SqrlAst . constant ( val ) , } ; } , letStatement ( feature , expr , options = { } ) { const rv = { feature , expr , type : "let" , where : SqrlAst . constant ( true ) , final : false , description : null , isDefaultCase : false , } ; Object . assign ( rv , options ) ; return rv ; } , removeLocation ( input ) { return mapAst ( input , ( ast ) => { if ( ast . hasOwnProperty ( "location" ) ) { const rv = Object . assign ( { } , ast ) ; delete rv . location ; return rv ; } return ast ; } ) ; } , hash ( ast ) { return murmurhashHexSync ( jsonAst ( ast ) ) ; } , areEqual ( left , right ) { const lBuffer = jsonAst ( left ) ; const rBuffer = jsonAst ( right ) ; return ( lBuffer . length === rBuffer . length && Buffer . compare ( lBuffer , rBuffer ) === 0 ) ; } , call ( func , args ) { invariant ( args . every ( ( arg ) => typeof arg === "object" && typeof arg . type === "string" ) , "Expected every argument to a call to be an ast" ) ; return { type : "call" , func , args } ; } , bool ( ast ) { return SqrlAst . call ( "bool" , [ ast ] ) ; } , and ( ... asts ) { asts . forEach ( ( ast ) => invariantAst ( ast ) ) ; return AstBuilder . and ( asts ) ; } , or ( ... asts ) { asts . forEach ( ( ast ) => invariantAst ( ast ) ) ; return AstBuilder . or ( asts ) ; } , not ( ast ) { return { type : "not" , expr : ast , } ; } , areAllConstant ( asts ) { return asts . every ( ( ast ) => ast . type === "constant" ) ; } , isExactConstant ( ast , value ) { while ( ast . type === "switch" && ast . cases . length === 1 && SqrlAst . isConstantTrue ( ast . cases [ 0 ] . where ) ) { ast = ast . cases [ 0 ] . expr ; } return ast . type === "constant" && ast . value === value ; } , isConstantNull ( ast ) { return SqrlAst . isExactConstant ( ast , null ) ; } , isConstantTrue ( ast ) { return SqrlAst . isExactConstant ( ast , true ) ; } , isConstantFalse ( ast ) { return SqrlAst . isExactConstant ( ast , false ) ; } , isSimpleDataObject ( ast ) { invariant ( ast , "expected simple data object but got null" ) ; return ( ( ast . type === "constant" && typeof ast . value === "object" ) || ( ast . type === "call" && ast . func === "createMap" && ast . args . length % 2 === 0 ) || ( ast . type === "call" && ast . func === "_createMap" && ast . args . length % 2 === 0 ) ) ; } , constant ( value ) { return { type : "constant" , value } ; } , constants ( ... values ) { return values . map ( SqrlAst . constant ) ; } , list ( ... exprs ) { return AstBuilder . list ( exprs ) ; } , registerCall ( ast ) { invariant ( ast . type === "call" , "must be call" ) ; return Object . assign ( { } , ast , { type : "registeredCall" , } ) ; } , slotName ( slotName ) { return { type : "slot" , slotName , } ; } , include ( filename ) { return { type : "include" , filename , where : SqrlAst . constant ( true ) , } ; } , slot ( slot ) { invariant ( slot instanceof SqrlSlot , "Expected SqrlSlot object" ) ; return { type : "slot" , slotName : slot . name , } ; } , feature ( name ) { return { type : "feature" , value : name } ; } , features ( ... features ) { return features . map ( SqrlAst . feature ) ; } , props ( obj ) { invariant ( typeof obj === "object" , "Expected object for SqrlAst.props" ) ; return AstBuilder . props ( obj ) ; } , branch ( condition , trueBranch , falseBranch = null ) { falseBranch = falseBranch || SqrlAst . constant ( null ) ; if ( condition . type === "constant" ) { return SqrlObject . isTruthy ( condition . value ) ? trueBranch : falseBranch ; } return AstBuilder . branch ( condition , trueBranch , falseBranch ) ; } , branchOrNull ( condition , trueBranch ) { return SqrlAst . branch ( condition , trueBranch , SqrlAst . constant ( null ) ) ; } , srcFileLineNumber ( location ) { return `${ location . filename || "" }:${ location . start . line }` ; } , srcLine ( location ) { return location . source . split ( "\n" ) [ location . start . line - 1 ] ; } , srcLines ( locations ) { const filenames = locations . map ( SqrlAst . srcFileLineNumber ) ; const lines = locations . map ( SqrlAst . srcLine ) ; let table = "" ; for ( let i = 0 ; i < filenames . length ; i ++ ) { table += filenames [ i ] + "  " + lines [ i ] . trim ( ) ; } return table ; } , } ; export default SqrlAst ;