Portál AbcLinuxu, 1. května 2025 07:59

Quick-and-dirty parsování

28.7.2007 07:39 | Přečteno: 1377× | Lisp | Výběrový blog

Podobně jako TeX, i Common Lisp má mechanismus pro modifikování parseru (ve smyslu lexikální analýzy) svých programů. Doteď jsem to k ničemu nepotřeboval, ale dnes se to hodilo - překvapilo mne, jak jednoduché bylo pomocí modifikace parseru vytvořit parser pro konfigurační soubor v na první pohled odlišném formátu

Soubor, který jsem chtěl parsovat, měl podobný formát jako třeba /etc/lvm/lvm.conf nebo /etc/logrotate.conf: komentářové řádky uvozené #, informace seskupené pomocí { }, řetězce v uvozovkách.

Abych byl schopen soubor načíst pomocí funkce read, potřeboval jsem tedy zejména nadefinovat chování { a #:

;;; Nemodifikujeme přímo to, co používá lisp, ale separátní kopii
(defparameter *conf-readtable* (copy-readtable)
  "Definice parsování pro konfigurační soubory")

; # se bude chovat stejně jako ;
(set-macro-character #\# (get-macro-character #\;)
   nil *conf-readtable*) 

; { } bude načítat list
(set-macro-character #\{
  (lambda (stream char)
     (read-delimited-list #\} stream t))
  t *conf-readtable*)
Toť vše. Nyní už lze použít běžné prostředky pro načtení souboru - pro jednoduchost třeba s makry balíku series:
(let ((*readtable* *conf-readtable*)) ; bude se používat nová tabulka
  (collect (scan-file #P "/etc/lvm/lvm.conf")))
=>
(DEVICES
 (DIR = "/dev" SCAN = [ "/dev" ] FILTER = [ "r|/dev/cdrom|" ] CACHE =
  "/etc/lvm/.cache" WRITE_CACHE_STATE = 1 SYSFS_SCAN = 1 MD_COMPONENT_DETECTION
  = 1)
 LOG
 (VERBOSE = 0 SYSLOG = 1 OVERWRITE = 0 LEVEL = 0 INDENT = 1 COMMAND_NAMES = 0
  PREFIX = "  ")
 BACKUP
 (BACKUP = 1 BACKUP_DIR = "/etc/lvm/backup" ARCHIVE = 1 ARCHIVE_DIR =
  "/etc/lvm/archive" RETAIN_MIN = 10 RETAIN_DAYS = 30)
 SHELL (HISTORY_SIZE = 100) GLOBAL
 (UMASK = 77 TEST = 0 ACTIVATION = 1 PROC = "/proc" LOCKING_TYPE = 1
  LOCKING_DIR = "/var/lock/lvm")
 ACTIVATION
 (MISSING_STRIPE_FILLER = "/dev/ioerror" RESERVED_STACK = 256 RESERVED_MEMORY =
  8192 PROCESS_PRIORITY = -18 MIRROR_REGION_SIZE = 512 MIRROR_LOG_FAULT_POLICY
  = "allocate" MIRROR_DEVICE_FAULT_POLICY = "remove"))
Pro hledání konkrétní informace lze pak použít běžné nástroje pro práci se seznamy, což zrovna v Lispu jde docela dobře:
(third ; tag = value ...
  (member 'proc (getf * 'global)))
=>
"/proc"
Samozřejmě, není to dokonalé - pro to co jsem potřeboval jsem ještě musel předefinovat 0, aby byla rozpoznána konvence 0777 (viz umask nakoře) resp 0xffff, a u toho lvm by mohlo být vhodné předefinovat i [, ale je to vcelku přímočaré a zřejmě občas užitečné. A o řád přesnější než grep.        

Hodnocení: 91 %

        špatnédobré        

Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

Komentáře

Nástroje: Začni sledovat (2) ?Zašle upozornění na váš email při vložení nového komentáře. , Tisk

Vložit další komentář

Josef Kufner avatar 29.7.2007 20:24 Josef Kufner | skóre: 70
Rozbalit Rozbalit vše Re: Quick-and-dirty parsování
Odpovědět | Sbalit | Link | Blokovat | Admin
Tohle by mohlo být zajímavé udělat třeba v céčku, ale nevím, jestli načítat konfiguráky pomocí gcc a dlopen by bylo dostatečně zvrhlé.
Hello world ! Segmentation fault (core dumped)
30.7.2007 03:04 Kyosuke | skóre: 28 | blog: nalady_v_modre
Rozbalit Rozbalit vše Re: Quick-and-dirty parsování
Nebylo, o dost lepší je použít libtcc. ;-) (Asi jsem snob, ale i na ten lvm.conf bych si radši napsal parser, už kvůli detekci chyb. A pro vlastní aplikace jedině S-expy a READ, proč to komplikovat. :-))
30.7.2007 08:08 Tom.š Ze.le.in | skóre: 21 | blog: tz
Rozbalit Rozbalit vše Re: Quick-and-dirty parsování
Jestli psát parser záleží na účelu. Já potřeboval jednorázově a rychle dostat nějakou informaci z několika podobných souborů stejného typu. Kdyby to měla být knihovna kterou bych chtěl reusovat/zveřejnit, měl na to čas - a zejména znal přesně syntaxi souborů :), tak bych o parseru možná začal uvažovat. I když - tohle je taky parser, ne? :)

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.