async parser
guest226-Oct-2007/15:02:10+2:00
Comme j'aime bien les compliments (merci les gars) et que sur Altme tout le monde s'en fout, je reposte ici le dernier de mes petits essais.

Alors il s'agit d'un parser asynchrone qui parse (ben oui) le contenu d'un fichier en ne chargeant les données qu'au fur et à mesure du parsing.
Donc en gros on évite de charger tout le fichier en mémoire.
Dans l'exemple (incomplet) ci-joint, je récupère les propriétés de dimension de l'image d'un fichier jpeg.

Un truc à finir, c'est le retour en arrière dans le parser qui n'est pas bien synchronisé avec le seek (repositionnement) sur le fichier mais bon, pour un premier exemple, ça le fait.

Je m'étonne encore chaque jour de ce qu'on peut faire avec si peu de lignes de code.

Les amateurs auront sûrement reconnu que le dialecte utilisé par le parser est un peu différent du standard mais ça c'est ma grande manie du moment.

REBOL []

parse-async: func [
        file rules
        /local port buffer offset getf seek meta & && result
][
        port: open/seek/binary  file
        buffer: clear #{}
        offset: 1
        getf: func [len][
                offset: offset - length? buffer
                clear buffer 
                append buffer copy/part at port offset len 
                offset: offset + len 
        ]
        seek: [(offset: offset + 1)]
        ..: func [blk] [change/part & compose/deep blk && ]
        parse rules meta: [
                some [
                        &: binary! &&: (.. [buffer: (to-paren reduce ['getf length? &/1]) (&/1)]) :& 3 skip 
                        | &: 'skip &&: (.. [seek]) :& skip
                        | &: 'get word! integer! &&:
                                (.. [buffer: (to-paren compose/deep [getf (&/3) set [(&/2)] to integer! as-binary cp buffer]) to end]) :& 4 skip
                        | &: string! &&: (.. [(as-binary &/1)]) :& 
                        | 'end 'skip 
                        | into meta
                        | skip
                ]
        ]
        result: parse/all buffer rules
        close port
        result
 ]
 
 
;**** LE TEST SUR UN JPEG **** 
 
if parse-async %15.jpg [
        #{FFD8}   ; jpeg Header
        [
                 #{FFE0}                        ;JFIF header
                        get len 2               ;get data length  for the current header (2 bytes)
                         "JFIF"                 ;yeah it's a JFIF (confirmation)
                        (len: len - 6) len skip ;skip data (len) times
                        some [
                                 #{FFC0}        ;good ! i found the length properties
                                        2 skip  ; skip length of this header
                                        skip    ; filler ??? always = #{08}
                                        get height 2
                                        get width 2
                                        break   ; finished

                                | #{FF} skip    ;skip this header
                                        get len 2 
                                        (len: len - 2) len skip
                                | 
                                        [end skip]     ;error format
                        ]

                | #{FFE1}                       ;EXIF header
                        get len 2               ;get length of a header
                                                ;... to do
                        [end skip]
        ]
        to end
][
   ?? height
   ?? width
]        
halt
Didec29-Oct-2007/11:32+1:00
Je sais ce que c'est. On a parfois l'impression de faire un truc génial et tout le monde s'en fou.
C'est un processus de projection classique (en psycho).

J'ai appris depuis à proposer ce qui ME plait avec humilité et sans espérer trop de retour. L'important c'est que toi tu trouve ton "bonheur" dans ce que tu fais.

Donc, non, je n'ai pas testé ton code, car JE n'ai pas le temps et que pour le moment, JE n'ai pas le besoin d'un parseur async. Mais j'ai lu vite fait le code et ça me parais vachement bien quand même.

alors lâche pas l'affaire
GreG29-Oct-2007/15:03:24+1:00
Je me reveille un peu tardivement mais moi, ca va peut-etre m'interresse, en particulier l'application aux jpeg.
J'ai deja ecrit une fonction, pas tres proprement d'ailleurs, et je testerai ta solution pour voir laquelle est la plus performante.
Comme le dit Didec, il n'y a pas toujours d'echo dans les annonces. Faut pas se frustrer pour autant, faut bien comprendre que le public en mesure d'apprecier ce travail est assez restreint!
guest229-Oct-2007/15:12:26+1:00
Ah Ah
T'inquiète Didec, ma sortie était plus une boutade qu'autre chose. Je fais mes ptits trucs dans mon coin, j'ai l'habitude que ça ne passionne pas les foules (surtout les histoires de parseurs un peu retords).

En général, les reboleurs préfèrent une approche directe (procédurale) d'un problème pour que ce soit plus performant et évitent l'emploi d'un dialecte (et donc d'un parseur) même si in fine le code est plus facilement maintenable et évolutif avec un parseur.

Le cas du streaming sur un fichier jpeg est révélateur. On peut facilement étendre ma solution pour traiter n'importe quel type de fichier pour en extraire des informations utiles. Facile pourquoi ? parce que grace à l'utilisation de ce dialecte il suffit de décrire la structure de données du fichier (de façon très intuitive) pour récupérer l'information intéressante.

Mais comme je l'ai dit, les Reboleurs prefereront une approche directe (oldschool) du problème comme le montre la solution de Oldes sur Altme (concernant le jpeg) qui a l'avantage d'être un peu plus rapide que la mienne mais qui n'est absolument pas faite pour être étendue à d'autres formats et qui ne rend pas apparente la structure de données du fichier qui est traitée.

D'une façon générale, je pense que le parsing (et donc l'utilisation de dialectes) est sous employée pour notemment générer automatiquement du code rebol.
C'est une des raisons (à mon avis) de l'échec relatif de la communauté à réaliser des gros grosses applications en Rebol.
Les Reboleurs ne pensent pas assez à réaliser des frameworks (via l'utilisation de dialectes) en amont de leur projet. Ce qui fait qu'ils se retrouvent très vite avec des tonnes de code difficilement maintenable à plus ou moins breve échéance parce qu'il est difficle de distinguer ce qui est fonctionnel (métier) de ce qui est purement technique. De ce point de vue Rebol n'est pas plus pratique qu'un autre langage.

L'autre jour je me baladais sur cette page http://fr.wikipedia.org/wiki/Compilateur_de_compilateur et je me disais à quel point il était facile de faire la même chose avec Rebol. J'irais même plus loin en disant que REBOL 'EST' le language pour faire ça.
En regardant quelques uns de ces programmes je me suis également dit qu'on pouvait faire bien plus puissant à peu de cout, c'est d'ailleurs assez bizarre que REBOL ne soit pas dans cette page.
J'ai regardé en particulier CodeWorker http://codeworker.free.fr/ développé par un français. Et franchement ça m'a tout à fait l'air d'un ersatz du parseur de Rebol en plus lourdingue.
Avec Rebol on fait ce genre de chose sans y penser.

C'est tout pour le moment...
guest229-Oct-2007/15:30:36+1:00
Greg si l'approche par dialecte t'intéresse fait le moi savoir car il reste 2, 3 choses à régler et je suis passé à autre chose entre temps.
Si c'est pour récupérer les dimensions des jpeg uniquement alors utilise la solution de Oldes (sur Altme #core) qui a l'air d'être au point (j'ai pas testé).
GreG29-Oct-2007/15:33:14+1:00
La solution de Oldes... qui est une iteration de ma solution que je lui ai donne en prive!
Je tenais a le dire quelque-part puisqu'il n'en parle pas lui meme!
Non mais!
guest229-Oct-2007/15:47:57+1:00
hin hin !!!
ZeBrain31-Oct-2007/18:40:02+1:00
L'intérêt de wikipedia est qu'on peut modifier les pages ... ce que je viens de faire
guest28-Nov-2007/19:57:26+1:00
Oldes a posté la version actualisée de son parser gif sur Altme:
GIF parser. At this moment it just prints info and or inserts comment.

do h__p://box.lebeda.ws/~hmm/rebol/gif_latest.r
        gif/parse h__p://box.lebeda.ws/~hmm/rswf/bitmaps/chinese.gif 
        write/binary %test.gif gif/add-comment h__p://box.lebeda.ws/~hmm/rswf/bitmaps/chinese.gif "test" 
        gif/parse %test.gif

The script is using %stream-io.r script which I use more and more for binary manipulations.

To test it with animatedGif you may try:  gif/parse h__p://upload.wikimedia.org/wikipedia/en/9/95/Headloss.gif


Mes impressions ?
Ce n'est pas un parseur, je veux dire qu'il n'en a que le nom, car il n'a formalisé aucun dialecte.
Il me semble que c'est devenu effroyablement compliqué pour pas grand chose.

La partie déclarative des données contenues dans le gif ressemble à ça:
GIF: make stream-io [
    verbal?: true 
    file: none 
    BlockSize: 
    Image: 
    GifHeader: 
    Application: 
    CommentData: 
    GraphicControl: 
    PlainText: none 
    parseGIFHeader: func [
        "Reads and parses GIF Header from a stream buffer"
    ] [
        either "GIF" = as-string readBytes 3 [
            GifHeader: context [
                Version: as-string readBytes 3 
                Width: readUI16 
                Height: readUI16 
                GlobalColorTable?: readBitLogic 
                ColorResolution: 1 + readUB 3 
                Sort?: readBitLogic 
                GlobalColorTableSize: to-integer (2 ** (1 + readUB 3)) 
                BackgrounColorIndex: readUI8 
                PixelAspectRatio: readUI8 
                GlobalColorTable: either GlobalColorTable? [
                    readBytes 3 * GlobalColorTableSize
                ] [none]
            ] 
            if verbal? [? GifHeader] 
            GifHeader
        ] [
            print ["!!! Not a GIF (" mold GIF-file ")"] 
            none
        ]
    ] 
    onGIFExtension: func [label ExtensionData] [
        prin [
            "-= Extension: " 
            select [
                #{FF} "Application" 
                #{FE} "Comment" 
                #{F9} "GraphicControl" 
                #{01} "PlainText"
            ] label 
            mold label ">> "
        ] 
        ? ExtensionData
    ] 
    onGIFImage: func [ImageData] [
        prin ["-= Image: "] 
        ? ImageData
    ] 
    onGIFend: does [print "-= END =-"] 
    parse: func [
        "Reads GIF file into buffer and parses it" 
        gif-file [file! url! binary!] "GIF file or binary data to parse" 
        /local tag Label
    ] [
        open gif-file 
        if parseGIFHeader [
            forever [
                switch/default tag: readByte [
                    #{21} [
                        if verbal? [] 
                        Label: readByte 
                        switch/default Label [
                            #{FF} [
                                skipByte 
                                Application: context [
                                    Identifier: as-string readBytes 8 
                                    AuthenticationCode: as-string readBytes 3 
                                    Data: make binary! 100
                                ] 
                                while [0 < BlockSize: readUI8] [
                                    append Application/Data readBytes BlockSize
                                ] 
                                onGIFExtension #{FF} Application
                            ] 
                            #{FE} [
                                CommentData: copy "" 
                                while [0 < blockSize: readUI8] [
                                    append CommentData as-string readBytes blockSize
                                ] 
                                onGIFExtension #{FE} CommentData
                            ] 
                            #{F9} [
                                GraphicControl: context [
                                    BlockSize: readUI8 
                                    DisposalMethod: (skipBits 3 readUB 3) 
                                    UserInput?: readbitLogic 
                                    TransparentColor?: readbitLogic 
                                    DelayTime: (byteAlign readUI16) 
                                    TransparentColor: readUI8
                                ] 
                                skipByte 
                                onGIFExtension #{F9} GraphicControl
                            ] 
                            #{01} [
                                PlainText: context [
                                    BlockSize: readUI8 
                                    TextGridLeftPosition: readUI16 
                                    TextGridTopPosition: readUI16 
                                    TextGridWidth: readUI16 
                                    TextGridHeight: readUI16 
                                    CharacterCellWidth: readUI8 
                                    CharacterCellHeight: readUI8 
                                    TextForegroundColor: readUI8 
                                    TextBackgroundColor: readUI8 
                                    TextData: copy ""
                                ] 
                                while [0 < BlockSize: readUI8] [
                                    append PlainTex/TextData as-string readBytes BlockSize
                                ] 
                                onGIFExtension #{F9} PlainText
                            ]
                        ] [
                            print ["!!! Unknown label" mold Label] 
                            break
                        ]
                    ] 
                    #{2C} [
                        Image: context [
                            LeftPosition: readUI16 
                            TopPosition: readUI16 
                            Width: readUI16 
                            Height: readUI16 
                            LocalColorTable?: readBitLogic 
                            Interlace?: readBitLogic 
                            Sort?: readBitLogic 
                            LocalColorTableSize: (skipBits 2 either LocalColorTable? [to-integer (2 ** (1 + readUB 3))] [none]) 
                            LocalColorTable: either LocalColorTable? [
                                readBytes 3 * LocalColorTableSize
                            ] [none] 
                            LZWMinimumCodeSize: readUI8 
                            LZWData: make binary! 10000
                        ] 
                        while [0 < BlockSize: readUI8] [
                            append Image/LZWData readBytes BlockSize
                        ] 
                        onGIFImage Image
                    ] 
                    #{3B} [
                        onGIFend 
                        break
                    ]
                ] [
                    print ["!!! Unknown tag:" mold tag] 
                    break
                ]
            ]
        ]
    ] 
    open: func ["Reads GIF file into buffer" gif-file] [
        setStreamBuffer either binary? gif-file [file: none gif-file] [read/binary file: gif-file]
    ] 
    add-comment: func [gif-file comment] [
        open gif-file 
        if parseGIFHeader [
            writeBytes #{21FE} 
            while [not tail? comment] [
                writeUI8 length? block: copy/part comment 255 
                writeBytes block 
                comment: skip comment 255
            ] 
            writeBytes #{00}
        ] 
        head inBuffer
    ]
]


A comparer avec le dialecte que j'avais commencé à formaliser:
[
        #{FFD8}   ; jpeg Header
        [
                 #{FFE0}                        ;JFIF header
                        get len 2               ;get data length  for the current header (2 bytes)
                         "JFIF"                 ;yeah it's a JFIF (confirmation)
                        (len: len - 6) len skip ;skip data (len) times
                        some [
                                 #{FFC0}        ;good ! i found the length properties
                                        2 skip  ; skip length of this header
                                        skip    ; filler ??? always = #{08}
                                        get height 2
                                        get width 2
                                        break   ; finished

                                | #{FF} skip    ;skip this header
                                        get len 2 
                                        (len: len - 2) len skip
                                | 
                                        [end skip]     ;error format
                        ]

                | #{FFE1}                       ;EXIF header
                        get len 2               ;get length of a header
                                                ;... to do
                        [end skip]
        ]
        to end
]


Bien sûr, mon dialecte extrait moins d'infos que ce que Oldes propose, mais si je l'avais complété, je peux garantir que ce serait resté 10 fois plus lisible et compact. Je ne dis pas ça pour faire le malin. Je dis ça parce que parse est un outil très puissant et que ça évite de faire des usines à gaz.

Enfin bref...
GreG3-Dec-2007/20:30:30+1:00
Ca y est, j'ai mis ton parser-async et le decodeur jpeg dans mon soft, ca marche tres bien, merci
Vu que c'est tres rapide, je prefere ta solution qui comme tu le dis, est plus evolutive.
guest24-Dec-2007/15:04:06+1:00
euh Greg ! t'as bien testé ?
Il me semble que je gère mal le seek en arrière sur le fichier (il devrait y avoir un seek en arrière quand une sous règle du parseur est invalidée).

Et puis si on parle d'optimisation tu aurais tout intérêt à sortir la construction du parseur en dehors de la fonction de lecture.

Cette partie là n'est à effectuer qu'une fois par type de parseur.
parse rules meta: [
                some [
                        &: binary! &&: (.. [buffer: (to-paren reduce ['getf length? &/1]) (&/1)]) :& 3 skip 
                        | &: 'skip &&: (.. [seek]) :& skip
                        | &: 'get word! integer! &&:
                                (.. [buffer: (to-paren compose/deep [getf (&/3) set [(&/2)] to integer! as-binary cp buffer]) to end]) :& 4 skip
                        | &: string! &&: (.. [(as-binary &/1)]) :& 
                        | 'end 'skip 
                        | into meta
                        | skip
                ]
        ]
guest24-Dec-2007/15:29:07+1:00
Hum pour le problème du seek en arrière, je crois avoir la soluce.
il suffit d'ajouter des données bidons dans le buffer quand tu fais un seek, comme ça quand le parseur revient en arrière dans le buffer, il est correctement positionné et sait à quelle position dans le fichier il doit reprendre sa lecture.

ligne à modifier:
seek: [(offset: offset + 1 insert tail buffer #{00})]


Et oui c'était pas plus compliqué que ça !!!
guest24-Dec-2007/15:31:27+1:00
il fallait lire:
il suffit d'ajouter des données bidons dans le buffer quand tu fais un SKIP
guest24-Dec-2007/15:45:53+1:00
hum, ça vaudrait presque le coup de le toiletter un peu et de le poster sur rebol.org
guest24-Dec-2007/16:16:41+1:00
Ah là là mais je devrais être en train de bosser moi au lieu de faire ça.

Je poste une nouvelle version (pas testée désolé).
- La fonction détecte si les règles (rules) ont déjà été construites ou pas, ça devrait donc être sensiblement plus rapide, si on enchaine les parsings sur une série de fichiers utilisant la même règle.
- La fonction ferme le fichier (close) si une erreur lors du parsing s'est produite (bien !)
- ajout d'un mot clé [failed] équivalant à [end skip] (ça produit l'arrêt du parseur (qui renvoie false)
- le constructeur ne risque plus de modifier le contenu des parenthèses éventuellement présentes dans la règle.

parse-async: func [
        file [file! url!] rules [block!]
        /local port buffer offset getf seek meta & && result new
][
        buffer: clear #{}
        offset: 1
        getf: func [len][
                offset: offset - length? buffer
                clear buffer 
                append buffer copy/part at port offset len 
                offset: offset + len 
        ]
        seek: [(offset: offset + 1) insert tail buffer #{00}]
        ..: func [blk] [change/part & compose/deep blk && ]
        if not rules/1 = 'constructed [
		parse rules meta: [
			some [
				&: binary! &&: (.. [buffer: (to-paren reduce ['getf length? &/1]) (&/1)]) :& 3 skip 
				| &: 'skip &&: (.. [seek]) :& skip
				| &: 'get word! integer! &&:
					(.. [buffer: (to-paren compose/deep [getf (&/3) set [(&/2)] to integer! as-binary cp buffer]) to end]) :& 4 skip
				| &: string! &&: (.. [(as-binary &/1)]) :& 
				| &: 'failed &&: [.. [end skip]] :& 2 skip 
				| 'end 'skip 
				| paren! | path! | into meta
				| skip
			]
		]
		new: reduce ['constructed cp/deep rules]
		clear change rules new
        ]
        port: open/seek/binary file
        error? set/any 'result try [parse/all buffer rules/constructed]
        close port
        :result
 ]


bons tests...
GreG4-Dec-2007/16:37:15+1:00
Oui j'ai teste, et effectivement ca marchait pas pour tous les jpg. Du coup, j'ai teste la version de Oldes mais qui ne marchait pas bien non plus... Au final, j'ai repris ma version que j'ai ameliore un peu!
Je referais des tests!
guest24-Dec-2007/19:27:14+1:00
A noter que dans mon exemple je ne traite pas les headers EXIF, ça c'est à toi de le rajouter...

Login required to Post.


Powered by RebelBB and REBOL 2.7.8.4.2