import type { Nullable } from "@thi.ng/api" ; import { timedResult } from "@thi.ng/bench/timed" ; import { downloadWithMime } from "@thi.ng/dl-asset/raw" ; import { DOWNLOAD } from "@thi.ng/hiccup-carbon-icons/download" ; import { withSize } from "@thi.ng/hiccup-carbon-icons/with-size" ; import { div } from "@thi.ng/hiccup-html/blocks" ; import { textArea } from "@thi.ng/hiccup-html/forms" ; import { anchor } from "@thi.ng/hiccup-html/inline" ; import { h1 , main } from "@thi.ng/hiccup-html/sections" ; import type { Language } from "@thi.ng/parse" ; import { defContext } from "@thi.ng/parse/context" ; import { defGrammar } from "@thi.ng/parse/grammar" ; import { print } from "@thi.ng/parse/xform/print" ; import { dynamicDropdown , staticDropdown , } from "@thi.ng/rdom-components/dropdown" ; import { editor } from "@thi.ng/rdom-components/editor" ; import { iconButton } from "@thi.ng/rdom-components/icon-button" ; import { tabs } from "@thi.ng/rdom-components/tabs" ; import { $compile } from "@thi.ng/rdom/compile" ; import { CloseMode } from "@thi.ng/rstream/api" ; import { metaStream } from "@thi.ng/rstream/metastream" ; import { reactive } from "@thi.ng/rstream/stream" ; import { sync } from "@thi.ng/rstream/sync" ; import { interpolate } from "@thi.ng/strings/interpolate" ; import { base64Decode , base64Encode } from "@thi.ng/transducers-binary/base64" ; import { filter } from "@thi.ng/transducers/filter" ; import { map } from "@thi.ng/transducers/map" ; import { pluck } from "@thi.ng/transducers/pluck" ; import { range } from "@thi.ng/transducers/range" ; import { deserialize , serialize } from "@ygoe/msgpack" ; import type { ParseResult , Status } from "./api" ; import { BG_COLS , BUTTON_CLASSES , CODE_TEMPLATES , DEFAULT_GRAMMAR , DEFAULT_INPUTS , DEFAULT_RULE , DOC_URL , DROPDOWN_ATTRIBS , EDITOR_OPTS , LINK_CLASSES , PANEL_CLASSES , SRC_URL , TAB_CLASSES , } from "./config" ; const parseState = ( ( ) : Nullable < string [ ] > => { try { return deserialize ( base64Decode ( location . hash . substr ( 1 ) ) ) ; } catch ( e ) { } } ) ( ) || [ DEFAULT_GRAMMAR , DEFAULT_RULE , ... DEFAULT_INPUTS ] ; const [ srcGrammar , activeRule , ... srcInputs ] = parseState . map ( ( src ) => reactive ( src , { closeOut : CloseMode . NEVER } ) ) ; console . log ( srcGrammar . deref ( ) ) ; console . log ( srcInputs [ 0 ] . deref ( ) ) ; const activeTpl = reactive ( "ts" ) ; const inputID = reactive ( 0 ) ; const activeInput = inputID . subscribe ( metaStream ( ( id ) => srcInputs [ id ] ) ) ; const lang = srcGrammar . map ( ( src ) : Partial < { lang : Language ; error : Error ; } > => { try { return { lang : defGrammar ( src ) } ; } catch ( e ) { return { error : < Error > e } ; } } ) ; const ruleIDs = lang . transform ( filter ( ( l ) => ! ! l . lang ) , map ( ( l ) => [ "Choose parser..." , ... Object . keys ( l . lang . rules ) . filter ( ( x ) => /^[a-z0-9._$-]+$/ . test ( x ) ) , ] ) ) ; const $result = ( status , body , time ? ) : ParseResult => ( { status , body , time , } ) ; const result = sync ( { src : { lang , src : activeInput , rule : activeRule , } , } ) . map ( ( { lang , src , rule } ) : ParseResult => { if ( ! lang . lang ) return $result ( "err" , lang . error ! . message ) ; const parser = lang . lang . rules [ rule ] ; if ( ! parser ) return $result ( "err" , `invalid or missing parser: ${ rule }` ) ; try { const ast = [ ] ; const ctx = defContext ( src , { retain : true } ) ; const [ res , time ] = timedResult ( ( ) => print ( parser , ( x ) => ast . push ( x ) ) ( ctx ) ) ; const body = ast . join ( "\n" ) ; return res ? ctx . done ? $result ( "ok" , body , time ) : $result ( "partial" , `partial match only (stopped @ ${ ctx . state . l }:${ ctx . state . c })...\n\n${ body }` , time ) : $result ( "fail" , `input parse failure (no match)...\n\n${ body }` , time ) ; } catch ( e ) { return $result ( "err" , `Parse error: ${ e }` ) ; } } ) ; sync ( { src : { grammar : srcGrammar , rule : activeRule , _ : activeInput } , } ) . subscribe ( { next ( { grammar , rule } ) { const hash = base64Encode ( { safe : true } , serialize ( [ grammar , rule , ... srcInputs . map ( ( i ) => i . deref ( ) ) ] ) ) . replace ( /=/g , "" ) ; location . hash = hash . length < 0x10000 ? hash : "content-too-large-for-uri-hash" ; } , } ) ; const formatStatus = ( res ) => PANEL_CLASSES + " " + BG_COLS [ res . status ] ; const formatTime = ( res ) => `parsed in: ${ res . time != null ? ~ ~ res . time + "ms" : "n/a" }` ; const downloadParser = ( tplID , src ) => { const { ext , code } = CODE_TEMPLATES [ tplID ] ; downloadWithMime ( `parser.${ ext }` , interpolate ( code , location . href , new Date ( ) . toISOString ( ) , src . trim ( ) . replace ( /\\/g , "\\\\" ) , activeRule . deref ( ) ! ) , { mime : "text/plain" , utf8 : true } ) ; } ; const link = ( href , label ) => anchor ( { class : LINK_CLASSES , target : "_blank" , href } , label ) ; $compile ( div ( { } , h1 ( ".ma0.fw2" , { } , "Let's make a parser... " ) , div ( ".mb2" , { } , link ( DOC_URL , "Documentation" ) , " / " , link ( SRC_URL , "Source code" ) ) , main ( { } , div ( { } , editor ( srcGrammar , { ... EDITOR_OPTS , wrapper : { class : "relative mt4-l" } , } ) ) , div ( { } , tabs ( inputID , { head : ( _ , title , id , selected ) => div ( TAB_CLASSES , { class : { "bg-white black" : selected , "bg-moon-gray gray" : ! selected , } , onclick : ( ) => inputID . next ( id ) , } , title ) , sections : [ ... map ( ( i ) => ( { title : `#${ i + 1 }` , content : async ( ) => editor ( srcInputs [ i ] , EDITOR_OPTS ) , } ) , range ( DEFAULT_INPUTS . length ) ) , ] , } ) ) , div ( EDITOR_OPTS . wrapper , textArea ( { class : result . map ( formatStatus ) , value : result . transform ( pluck ( "body" ) ) , disabled : true , rows : 16 , } ) , div ( EDITOR_OPTS . cursor ! . attribs , result . map ( formatTime ) ) ) , div ( ".w-100" , { } , dynamicDropdown ( ruleIDs , activeRule , { attribs : DROPDOWN_ATTRIBS , } ) , staticDropdown ( Object . keys ( CODE_TEMPLATES ) , activeTpl , { attribs : DROPDOWN_ATTRIBS , label : ( id ) => CODE_TEMPLATES [ id ] . name , } ) , iconButton ( { attribs : { class : BUTTON_CLASSES , onclick : ( ) => downloadParser ( activeTpl . deref ( ) ! , srcGrammar . deref ( ) ! ) , } , icon : withSize ( DOWNLOAD , "12px" ) , label : "Download parser" , } ) ) ) ) ) . mount ( document . body ) ;