import { getType } from './get_type' ; import { parse } from '../../../grammar' ; export type ExpressionArgAST = string | boolean | number | Ast ; export interface ExpressionFunctionAST { type : 'function' ; function : string ; arguments : { [ key ] : ExpressionArgAST [ ] ; } ; } export interface Ast { internal function : any ; internal arguments : any ; type : 'expression' ; chain : ExpressionFunctionAST [ ] ; internal replace ( regExp , s ) : string ; } function getArgumentString ( arg , argKey , level = 0 ) { const type = getType ( arg ) ; function maybeArgKey ( argKey , argString ) { return argKey == null || argKey === '_' ? argString : `${ argKey }=${ argString }` ; } if ( type === 'string' ) { const escapedArg = arg . replace ( /[\\"]/g , '\\$&' ) ; return maybeArgKey ( argKey , `"${ escapedArg }"` ) ; } if ( type === 'boolean' || type === 'null' || type === 'number' ) { return maybeArgKey ( argKey , `${ arg }` ) ; } if ( type === 'expression' ) { return maybeArgKey ( argKey , `{${ getExpression ( arg . chain , level + 1 ) }}` ) ; } throw new Error ( `Invalid argument type in AST: ${ type }` ) ; } function getExpressionArgs ( block , level = 0 ) { const args = block . arguments ; const hasValidArgs = typeof args === 'object' && args != null && ! Array . isArray ( args ) ; if ( ! hasValidArgs ) throw new Error ( 'Arguments can only be an object' ) ; const argKeys = Object . keys ( args ) ; const MAX_LINE_LENGTH = 80 ; return argKeys . map ( ( argKey ) => args [ argKey ] . reduce ( ( acc , arg ) => { const argString = getArgumentString ( arg , argKey , level ) ; const lineLength = acc . split ( '\n' ) . pop ( ) . length ; if ( level === 0 && lineLength + argString . length > MAX_LINE_LENGTH ) { return `${ acc }\n  ${ argString }` ; } if ( lineLength > 0 ) return `${ acc } ${ argString }` ; return argString ; } , '' ) ) ; } function fnWithArgs ( fnName , args ) { if ( ! args || args . length === 0 ) return fnName ; return `${ fnName } ${ args . join ( ' ' ) }` ; } function getExpression ( chain , level = 0 ) { if ( ! chain ) throw new Error ( 'Expressions must contain a chain' ) ; const separator = level > 0 ? ' | ' : '\n| ' ; return chain . map ( ( chainObj ) => { const type = getType ( chainObj ) ; if ( type === 'function' ) { const fn = chainObj . function ; if ( ! fn || fn . length === 0 ) throw new Error ( 'Functions must have a function name' ) ; const expArgs = getExpressionArgs ( chainObj , level ) ; return fnWithArgs ( fn , expArgs ) ; } } , [ ] ) . join ( separator ) ; } export function fromExpression ( expression , type = 'expression' ) { try { return parse ( String ( expression ) , { startRule : type } ) ; } catch ( e ) { throw new Error ( `Unable to parse expression: ${ e . message }` ) ; } } export function safeElementFromExpression ( expression ) { try { return fromExpression ( expression ) ; } catch ( e ) { return fromExpression ( `markdown
"## Crud.
Canvas could not parse this element's expression. I am so sorry this error isn't more useful. I promise it will be soon.

Thanks for understanding,
#### Management
"` ) ; } } export function toExpression ( astObj , type = 'expression' ) { if ( type === 'argument' ) { return getArgumentString ( astObj ) ; } const validType = [ 'expression' , 'function' ] . includes ( getType ( astObj ) ) ; if ( ! validType ) throw new Error ( 'Expression must be an expression or argument function' ) ; if ( getType ( astObj ) === 'expression' ) { if ( ! Array . isArray ( astObj . chain ) ) throw new Error ( 'Expressions must contain a chain' ) ; return getExpression ( astObj . chain ) ; } const expArgs = getExpressionArgs ( astObj ) ; return fnWithArgs ( astObj . function , expArgs ) ; }