async parser | |
guest2 | 26-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 | |
Didec | 29-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 | |
GreG | 29-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! | |
guest2 | 29-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... | |
guest2 | 29-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é). | |
GreG | 29-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! | |
guest2 | 29-Oct-2007/15:47:57+1:00 |
hin hin !!! | |
ZeBrain | 31-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 | |
guest2 | 8-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... | |
GreG | 3-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. | |
guest2 | 4-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 ] ] | |
guest2 | 4-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 !!! | |
guest2 | 4-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 | |
guest2 | 4-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 | |
guest2 | 4-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... | |
GreG | 4-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! | |
guest2 | 4-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. |