import { OP_CODE , runtime } from "./runtime" ; export const OR = OP_CODE . OR ; export const AND = OP_CODE . AND ; export const EQ = OP_CODE . EQUAL ; const NOT_OP = 4 ; const OP_STR = { [ OR ] : "OR" , [ AND ] : "AND" , [ EQ ] : "EQ" , [ NOT_OP ] : "NOT" , [ OP_CODE . SEP ] : "SEP" , } ; /**
 * The index of an input value to be resolved at runtime.
 **/ export type Value = number ; type UID = number ; /**
 * Represents the negation of a resolved or computed value.
 **/ export interface NotValue { val : Value | Expression ; op : typeof NOT_OP ; } /**
 * Constructs an expression that negates a value or expression.
 */ export function NOT ( val ) { return { val , op : NOT_OP } ; } param v function isNotValue ( v ) { return typeof v === "object" && v . op === NOT_OP ; } /**
 * Represents a single node in a boolean expression binary tree.
 */ export interface Expression { left : Expression | Value ; notLeft : boolean ; op : OP_CODE ; right : Expression | Value ; notRight : boolean ; } /**
 * Valid operand values for our expr() function.
 **/ export type Operand = Value | Expression | NotValue ; /**
 * SimpleExpression objects are a special representation of Expressions
 * that will only reference resolved values at runtime. They are completely flat.
 */ export interface SimpleExpression extends Expression { left : Value ; right : Value ; } /**
 * The flattened representation of an Expression binary tree.
 * This is converted basically 1:1 to the binary runtime shapes.
 */ export interface Flattened { arguments : Set < number > ; expressions : SimpleExpression [ ] ; indices : Map < UID , number > ; } param left param op param right export function expr ( left , op , right ) { let notLeft = false ; let notRight = false ; if ( isNotValue ( left ) ) { notLeft = true ; left = left . val ; } if ( isNotValue ( right ) ) { notRight = true ; right = right . val ; } return { left , op , right , notLeft , notRight } ; } param expr export function debugExpression ( expr ) { return `VAR ${ expr . notLeft ? "!" : "" }${ expr . left } ${ OP_STR [ expr . op ] } VAR ${ expr . notRight ? "!" : "" }${ expr . right }` ; } /**
 * Generates a unique identifier for an expression.
 * This only works for values referencing an index of 16383 or less.
 */ function genUID ( expr ) { if ( expr . left > 16383 || expr . right > 16383 ) { throw new Error ( "index out of bounds." ) ; } let uid = ( expr . notLeft ? 1 : 0 ) << 31 ; uid |= expr . left << 17 ; uid |= ( expr . notRight ? 1 : 0 ) << 16 ; uid |= ( expr . right ) << 2 ; uid |= expr . op ; return uid ; } param expr param out function getArgs ( expr , out ) { if ( typeof expr . left === "number" ) { out . arguments . add ( expr . left ) ; } else { getArgs ( expr . left , out ) ; } if ( typeof expr . right === "number" ) { out . arguments . add ( expr . right ) ; } else { getArgs ( expr . right , out ) ; } } param expr param out function visit ( expr , out ) { let simpleExpr = { left : ( typeof expr . left !== "number" ) ? visit ( expr . left , out ) : expr . left , right : ( typeof expr . right !== "number" ) ? visit ( expr . right , out ) : expr . right , op : expr . op , notLeft : expr . notLeft , notRight : expr . notRight , } ; let uid = genUID ( simpleExpr ) ; if ( ! out . indices . has ( uid ) ) { out . expressions . push ( simpleExpr ) ; out . indices . set ( uid , out . expressions . length - 1 ) ; } return out . indices . get ( uid ) ! + out . arguments . size ; } param el function flattenExpressions ( el ) { const out = { arguments : new Set ( ) , expressions : [ ] , indices : new Map ( ) , } ; el . forEachClass ( ( _c , e ) => getArgs ( e , out ) ) ; const classExprs = el . getExprs ( ) . map ( ( expr ) : SimpleExpression => ( { left : ( typeof expr . left !== "number" ) ? visit ( expr . left , out ) : expr . left , right : ( typeof expr . right !== "number" ) ? visit ( expr . right , out ) : expr . right , op : expr . op , notLeft : expr . notLeft , notRight : expr . notRight , } ) ) ; out . expressions = [ ... out . expressions , ... classExprs ] ; return out ; } export class ExpressionContainer { private classes : { [ key ] : Expression ; } = { } ; param name param expr class ( name , expr ) { this . classes [ name ] = expr ; } param cb forEachClass ( cb ) { for ( let key of Object . keys ( this . classes ) ) { cb ( key , this . classes [ key ] ) ; } return this ; } getExprs ( ) { return Object . values ( this . classes ) ; } getClasses ( ) { return Object . keys ( this . classes ) ; } /**
     * Get the binary string (ex: "1001001001010") that represents all the registered classes'
     * application logic in this ExpressionContainer.
     */ getBinaryString ( ) { const flattened = flattenExpressions ( this ) ; const argCount = flattened . arguments . size ; const classesCount = Object . keys ( this . classes ) . length ; let exprCount = 0 ; let out = "" ; for ( let idx = 0 ; idx < flattened . expressions . length ; idx ++ ) { let expr = flattened . expressions [ idx ] ; let isSep = ( flattened . expressions . length - classesCount ) === idx ; let size = ~ ~ Math . log2 ( exprCount + argCount - 1 ) + 1 ; let left = expr . left . toString ( 2 ) . padStart ( size , "0" ) + ( expr . notLeft ? "1" : "0" ) ; let op = expr . op . toString ( 2 ) . padStart ( 2 , "0" ) ; let right = expr . right . toString ( 2 ) . padStart ( size , "0" ) + ( expr . notRight ? "1" : "0" ) ; out += ( isSep ? OP_CODE . SEP . toString ( 2 ) : "" ) + op + left + right ; exprCount ++ ; } return out ; } /**
     * Calculate the base36 binary string encoding of this expression's logic shape.
     */ getBinaryEncoding ( ) { return this . getBinaryString ( ) . match ( /.{1,32}/g ) ! . map ( ( s ) => parseInt ( s . split ( "" ) . reverse ( ) . join ( "" ) , 2 ) . toString ( 36 ) ) ; } param args exec ( ... args ) { return runtime ( this . getBinaryEncoding ( ) , this . getClasses ( ) , args ) ; } }