Examples for conditional configuration data

I’m currently writing tutorials on creating parsers in Fortran. The latest is about creating a tokenizer for JSON in a somewhat creative way: Writing a custom lexer — TOML Fortran

The next tutorial after tokenization would obviously be creating a parser.

For configuration files like TOML or JSON, the data is usually static, however it would be possible to use an expression interpreter to conditionally include subtables as done for example in Cargo: Specifying Dependencies - The Cargo Book.

I’m working now on a tutorial to create such an expression parser for use in TOML Fortran (with potential use for fpm in the future). The example provided in the Cargo docs shows how this can be used for dependencies:

[target.'cfg(windows)'.dependencies]
winhttp = "0.4.0"

[target.'cfg(unix)'.dependencies]
openssl = "1.0.1"

[target.'cfg(target_arch = "x86")'.dependencies]
native-i686 = { path = "native/i686" }

[target.'cfg(target_arch = "x86_64")'.dependencies]
native-x86_64 = { path = "native/x86_64" }

Which however makes me wonder why the full expression parsing machinery is needed if the above could be expressed more conveniently as

[target.windows.dependencies]
winhttp = "0.4.0"

[target.unix.dependencies]
openssl = "1.0.1"

[target.x86.dependencies]
native-i686 = { path = "native/i686" }

[target.x86_64.dependencies]
native-x86_64 = { path = "native/x86_64" }

To me it seems that careful design of the configuration input can effectively circumvent the need for conditional configuration. Which raises the question whether the conditional configuration data is needed at all, making the cfg(…) feature in Cargo look like a bad design choice.

What do you think, is there an actual application for such dynamic configuration files (in fpm or elsewhere)?

I think the answer might be hidden the grammar for conditional compilation. If nothing else the keyword arguments provide some documentation value. Also predicates including all and any are supported.

I do think there is a need, however I don’t have any examples at hand right now.

Thanks for the reference. I started using the following grammar for my expression parser for now, looks like I can throw out the relational rule and maybe also the logic_or and logic_and. Written down in ABNF instead of MKF:

;; Custom expression grammar
expression = *WSP logic_or *WSP

;; Allow logical operators
logic_or = logic_and *("|" logic_and)
logic_and = equality *("&" equality)

;; Binary operations only include equality and relational comparisons
equality = relational *( ( "=" / "~=" ) relational)
relational = unary *( ( "<" / ">" / "<=" / ">=" ) unary)

;; For negation we will use the tilde operator
unary = ( "~" unary / func_call )

;; Function calls are allowed
func_call = primary *( "(" [ arg_list ] ")" )
arg_list = expression *( "," expression )

;; The acutual terminals are only identifiers, no literal values
primary = *WSP ( "(" expression ")" / IDENTIFIER / STRING ) *WSP
IDENTIFIER = 1*( ALPHA / DIGIT / "_" / "-" )
STRING = %x22 *( WSP / %x21 / %x23-5B / %x5D-7E ) %x22

I’m surprised that the conditional compilation uses keywords for all, any and not, this just makes the grammar more complicated for no reason. I’ll go with making them func_call’s and have the respective built-in function handle this.