📜 ⬆️ ⬇️

Philosophy or when the letters were green

Previously, everything was better: the computers were large, and the letters on the screen were green. Then they were called computers, and engineers went around the engine room in white coats. In those blessed times, no one bothered about user-friendly interfaces, but simply required the user to prepare a deck of punch cards in accordance with a specific format. Prepared wrong - himself to blame. This does not seem very convenient and is not at all “intuitively understandable,” but this Spartan approach made it possible to calculate some very serious tasks, such as modeling nuclear reactions or calculating the flight task for space rockets. And all this with resources, two or three orders of magnitude smaller than almost any modern smartphone.

Time passed, and punch cards with magnetic drums sank into oblivion, in favor of the user, programs with a developed GUI began to dominate. It began to be presented as technical progress and concern for user convenience. Is it always good? Is it always more convenient than a regular text configuration file? Is it convenient to perceive something?

image

image

Not really ... The nesting of tuning windows is more than 2 levels, in each of which there are a dozen of two or three “puffs” - is this convenient? And if you are an administrator, and you need to replicate this engineering marvel for two hundred jobs with all the settings? Oh ... how good it was before, when you just could put a configuration file next.
')
Indeed, the test configuration file has a lot of advantages, and it is not at all obsolete:

• no need to write a very time-consuming GUI to configure the software
• easily replicated with software;
• easy to create programmatically, if it is great;
• if the configuration file uses the principle of optional parameters, it can be made more readable than many forms with dozens of controls.

There is only one serious problem with the configuration file: if the syntax is clear and readable, then the parser is a rather laborious product. Separate individuals may argue that there is XML in the world for which everything is ready for a long time, but I would not call XML a well-read and convenient format. To the advocates of this approach, I would like to work with large XML tables in the field, having at hand only the editor vi. I am sure that it will sober up the supporters of XML.

It is very convenient to work with the configuration file, the syntax of which is as close to the application as possible. In this case, the best option is to create a parser using a parser generator, for example, one of those listed in this list .

Of all the variety, the COCO / R generator, created at the University of Linz in Upper Austria, is distinguished by its simplicity and affordability. I selected it based on the following considerations:

• it can create parsers for C, C ++, C #, F #, Java, Ada, all types of Pascal, Modula, Ruby and several other languages;
• tokens and actions with them are described in one file, which is easy to read;
• it can be built and used on any platform;
• it can be integrated into Visual Studio (a trifle, but nice).

I will not go into the specifics of using COCO / R, since this is beautifully written in its documentation, but I will immediately turn to a practical example.

We create a parser to build an abstract network routing table consisting of several routers and several end devices. It is convenient to present the general view of the configuration file as follows (herewith, we are in no way limited by the number of routers, the number of ports on them and the number of terminals):

//     1102 DEVICE ID=(18,0) DI=0x03740038 NAME="sw1102" UMASK=0666 CAPS={MN} { PORT = 7 PW=X1 LS=G25 ID={ 0 }, //  PORT = 9 PW=X4 LS=G3125 ID={ 8 }, //  PORT = 10 PW=X4 LS=G25 ID={ 16 } //   }; DEVICE ID=(0,1) DI=0x04000003 NAME="host" UMASK=0444 CAPS={ MN,MB0, DB,DIO }; DEVICE ID=(8,1) NAME="meter" UMASK=0444 CAPS={ MN,MB0,MB1,MB2,MB3,DB,DIO }; DEVICE ID=(16,1) DI=0x03780038 NAME="sw1101" UMASK=0444 CAPS={ MN } { PORT = 12 PW=X1 LS=G25 ID={ 0, 8, 18 } // , ,   }; 

Now we build the syntax descriptor for the parser (we build the parser for C ++):

 // #include <_> COMPILER SRIO_CONFIG //    CHARACTERS letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz". digit = "0123456789". cr = '\r'. lf = '\n'. tab = '\t'. stringch = ANY - '"' - '\\' - cr - lf - tab. hexDigit = "0123456789abcdefABCDEF". printable = '\u0020' .. '\u007e'. TOKENS ident = letter {"_"} { letter | "_" | digit }. number = digit { digit } | "0x" hexDigit {hexDigit}. string = '"' { stringch | "\"\"" } '"' . COMMENTS FROM "/*" TO "*/" NESTED COMMENTS FROM "//" TO lf IGNORE cr + lf + tab PRODUCTIONS //      DEVICE<int &iStepDefNo> (. ConfDevice dev; dev.iStepNo=iStepDefNo; .) = "DEVICE" (. dev.iId = 0xFF; dev.iHopCnt=0xFF; .) [ { DEVNAME<dev> | "ID" "=" "(" NUMBER<dev.iId> "," NUMBER<dev.iHopCnt> ")" | "DI" "=" NUMBER<(int &)dev.uDidCar> | DEVCAPS<dev> | "UMASK" "=" NUMBER<(int &)dev.uMask> } ] [ "{" { (. ConfPort cport; .) ASSIGPort<cport> (. dev.lstPorts.Add(cport); .) [","] } "}" ] [ ";" ] (. m_pDevices->Add(dev); .) . //     ASSIGPort<ConfPort &pPort> (. int num=-1; .) = "PORT" "=" NUMBER<pPort.nPort> { "PW" "=" PW<pPort.Pw> | "LS" "=" LS<pPort.Ls> | "ID" "=" "{" NUMBER<num> (. pPort.lstIds.Push(num); .) { ',' NUMBER<num> (. pPort.lstIds.Push(num); .) } "}" } . //      PW<ePW &pw> (. int iTmp=0; .) = ( "X1" (. pw = PWX1; .) | "X2" (. pw = PWX2; .) | "X4" (. pw = PWX4; .) | "X1L0" (. pw = PWX1L0; .) | "X1L1" (. pw = PWX1L1; .) | "X1L2" (. pw = PWX1L2; .) |NUMBER<iTmp> (. pw=(ePW)iTmp; .) ) . //     LS<eLS &ls> (. int iTmp=0; .) = ( "G125" (. ls = LSG125; .) | "G25" (. ls = LSG25; .) | "G3125" (. ls = LSG3125; .) | "G5" (. ls = LSG5; .) | "G625" (. ls = LSG625; .) |NUMBER<iTmp> (. ls=(eLS)iTmp; .) ) . //  ,    STRING<CStr &str> = string (. str=CStr::FromPosLen(t->val, 1, strlen(t->val)-2); .) . //  10-  16-  NUMBER<int &value> = number (. value=Utils::ToInt32(t->val); .) . //   (,   devfs  ) DEVNAME<ConfDevice &dev> (. CStr str; .) = "NAME" "=" STRING<str> (. strcpy(dev.szName, (LPCSTR)str); .) . //  API  DEVCAPS<ConfDevice &dev> (. dev.uCaps = 0; .) = "CAPS" "=" "{" { [','] ( "MN" (. dev.uCaps |= MN; .) |"MB0" (. dev.uCaps |= MB0; .) |"MB1" (. dev.uCaps |= MB1; .) |"MB2" (. dev.uCaps |= MB2; .) |"MB3" (. dev.uCaps |= MB3; .) |"DB" (. dev.uCaps |= DB; .) |"DIO" (. dev.uCaps |= DIO; .) |"ST" (. dev.uCaps |= ST; .) ) } "}" . ////////////////////////////////////////////////////////////// //   SRIO_CONFIG = DEVICE<iStepDefNo> { DEVICE<iStepDefNo> } . END SRIO_CONFIG 

It turns out very briefly and elegantly. Now it is enough to pass this descriptor through the parser generator. In this case, a generator modified by me many years ago was used:

 D:\WRL>cocor cocor –namespace cfg sdrv_conf.atg Coco/R (Nov 17, 2010), changed by APRCPV checking sdrv_conf.atg(1): warning LL1: in DEVICE: contents of [...] or {...} must not be deletable sdrv_conf.atg(1): warning LL1: in BLK_STEPDEF: "DEVICE" is start & successor of deletable structure parser + scanner generated 0 errors detected D:\WRL > 

The result of the generator is the Parser.cpp files of Scanner.cpp Parser.h Scanner.h, which we will immediately include in the project and can immediately use:

 bool ReadConfigFile() { bool bSuc=false; //     if (access(m_szConfFileName, 0)!=0) { TRACE(eLL_CRIT, "Error: Configuration file '%s' not found or unaccessable!\n", m_szConfFileName); abort(); } cfg::Scanner &s = *new cfg::Scanner(m_szConfFileName); //   cfg::Parser &p = *new cfg::Parser(&s); //   p.m_pConfigData = this; //      p.Parse(); //     //        //  ,      : error(row:col) bSuc=(p.errors->count < 1); delete &p; delete &s; //       return bSuc; } 

It's done, now my program is able to perceive the configuration file in the format in which it is convenient to write. Moreover, you can always change the syntax and add new features to it.

Using this technology, you can not only parse the configuration files, but generally disassemble any formal syntax, which is very useful. For example, you can create a specialized editor with syntax highlighting no worse than in Eclipse or Visual Studio, but only the syntax we can define ourselves.

And now let's fantasize a bit ... Surely, many were not bothered by the laurels of Nicholas Wirth or Kernighan and Ricci: why did they manage to develop a new programming language, but I can't ?! And indeed, why? After all, from building a configuration file parser to a programming language is just one step. You can write an interpreter, but you can also have a fully-fledged compiler if you add code generation, which we have already discussed in one of the articles .

Arkady Pchelintsev, project architect

Source: https://habr.com/ru/post/328296/


All Articles