A YAML parser for Fortran: fortran-yaml-cpp

I put together a YAML parser for Fortran: GitHub - Nicholaswogan/fortran-yaml-cpp: Fortran wrapper to the yaml-cpp parser . Currently at version 0.0.1

It is an interface to the C++ package yaml-cpp. I use yaml-cpp to parse a yaml document, then I load the data into Fortran derived types created by fortran-yaml. So it’s just knitting two packages together. The API is nearly identical to fortran-yaml.

The reason I did this was because fortran-yaml isn’t a full yaml parser. It can’t deal with flow style, or multiline strings, and a bunch of other stuff. Initially, I tried improving the parser in fortran-yaml so that it could parse more yaml syntax, but this turned out to be really hard. Knitting together yaml-cpp and fortran-yaml was much easier.

I would appreciate any feedback/issues/suggestions! This is the most involved merger of Fortran and C++ I’ve tried, so I probably made mistakes that need catching.

10 Likes

Many thanks for doing this @nicholaswogan. I am really excited by this news and I can’t wait to test it (although I might not have time for this in the near future :frowning_face: ).

Although it is probably not the sort of feedback that you are expecting, I’d ask if you can package the library with fpm. For my use case, it would be a big improvement.

Also, have you compared the features of your parser with the young but excellent yaFyaml?

3 Likes

@nicholaswogan thanks for doing that. As @epagone asked, if you wouldn’t mind adding fpm build system that would make it much easier for others to try.

2 Likes

yaFyaml does looks excellent!! I think I like the API more than fortran-yaml. But I can’t figure out how to build yaFyaml. It has some dependencies which are not included in the github repo (GFTL).

@certik @epagone I will package fortran-yaml-cpp with fpm, but I have to work out how to do this when CMake is involved. CMake has to be involved because it needs to build the C++ package yaml-cpp.

1 Like

I think there are three ways:

  • You can just call cmake to build the C++ package in a build script that fpm just calls
  • You can assume it is already installed, so you just tell fpm the correct compiler flags to link with it
  • You provide an fpm build system for the C++ package and just depend on it

@awvwgk, what is the best way forward in this case?

Linking against a system library seems like the most feasible approach IMO.

1 Like

@awvwgk I just tried this approach, and its not working out. fpm doesn’t seem to be executing my build-script.

Is this implemented in the latest release? I am using Version: 0.4.0, alpha

Can you provide steps for us to reproduce the error you are getting?

Here is my directory structure:

.
├── CMakeLists.txt
├── LICENSE.txt
├── README.md
├── fpm.toml
├── my_build_script.sh
├── src
│   ├── CMakeLists.txt
│   ├── yaml-cpp
│   ├── yaml.cpp
│   ├── yaml.f90
│   └── yaml_types.f90
├── test.yaml
└── tests
    ├── example.f90
    └── test.f90

My fpm.toml:

name = "fortran-yaml-cpp"
version = "0.0.1"
license = "MIT"
author = "Nick Wogan"
maintainer = "nicholaswogan@gmail.com"
copyright = "2021 Nick Wogan"

[library]
source-dir="src"
build-script="my_build_script.sh"

[[ executable ]]
name="runTests"
source-dir="tests"
main="test.f90"

and my_build_script.sh:

mkdir cmake_build_dir
cmake -B cmake_build_dir -S . -DFPM_BUILD=ON
cmake --build cmake_build_dir -j

The idea is to do all the right things in CMakeLists.txt for fpm work when FPM_BUILD=ON.

When I run fpm build, I get the following

 + mkdir -p build/dependencies
 + mkdir -p build/gfortran_2A42023B310FA28D/fortran-yaml-cpp
 + gfortran -c ././src/yaml_types.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build/gfortran_2A42023B310FA28D/fortran-yaml-cpp -I build/gfortran_2A42023B310FA28D/fortran-yaml-cpp  -o build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/src_yaml_types.f90.o
././src/yaml_types.f90:192:40:

  192 |    subroutine null_dump(self,unit,indent)
      |                                        1
Warning: Unused dummy argument 'indent' at (1) [-Wunused-dummy-argument]
././src/yaml_types.f90:192:28:

  192 |    subroutine null_dump(self,unit,indent)
      |                            1
Warning: Unused dummy argument 'self' at (1) [-Wunused-dummy-argument]
././src/yaml_types.f90:186:41:

  186 |    subroutine value_dump(self,unit,indent)
      |                                         1
Warning: Unused dummy argument 'indent' at (1) [-Wunused-dummy-argument]
././src/yaml_types.f90:107:42:

  107 |    recursive subroutine node_finalize(self)
      |                                          1
Warning: Unused dummy argument 'self' at (1) [-Wunused-dummy-argument]
 + gfortran -c ././src/yaml.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build/gfortran_2A42023B310FA28D/fortran-yaml-cpp -I build/gfortran_2A42023B310FA28D/fortran-yaml-cpp  -o build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/src_yaml.f90.o
././src/yaml.f90:55:37:

   55 |     filename_copy = filename//char(0)
      |                                     ^
Warning: '.filename_copy' may be used uninitialized [-Wmaybe-uninitialized]
 + ar -rs build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/libfortran-yaml-cpp.a build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/src_yaml_types.f90.o build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/src_yaml.f90.o
ar: creating archive build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/libfortran-yaml-cpp.a
 + gfortran -c tests/test.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build/gfortran_2A42023B310FA28D/fortran-yaml-cpp -I build/gfortran_2A42023B310FA28D/fortran-yaml-cpp  -o build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/tests_test.f90.o
 + gfortran -c tests/example.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build/gfortran_2A42023B310FA28D/fortran-yaml-cpp -I build/gfortran_2A42023B310FA28D/fortran-yaml-cpp  -o build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/tests_example.f90.o
tests/example.f90:41:61:

   41 |     string = trim(dict%get_string("equation",error = io_err))
      |                                                             ^
Warning: '.string' may be used uninitialized [-Wmaybe-uninitialized]
 + mkdir -p build/gfortran_2A42023B310FA28D/app/
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build/gfortran_2A42023B310FA28D/fortran-yaml-cpp -I build/gfortran_2A42023B310FA28D/fortran-yaml-cpp  build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/tests_test.f90.o build/gfortran_2A42023B310FA28D/fortran-yaml-cpp/libfortran-yaml-cpp.a -o build/gfortran_2A42023B310FA28D/app/runTests
Undefined symbols for architecture x86_64:
  "_DestroyNode", referenced from:
      ___yaml_MOD_parse in libfortran-yaml-cpp.a(src_yaml.f90.o)
  "_LoadFile_c", referenced from:
      ___yaml_MOD_loadfile in libfortran-yaml-cpp.a(src_yaml.f90.o)
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status
<ERROR> Compilation failed for object " runTests "
<ERROR>stopping due to failed compilation
STOP 1

my_build_script.sh is never executed. Instead, fpm compiles src/yaml.f90 and src/yaml_types.f90 and does not compile src/yaml.cpp, and makes libfortran-yaml-cpp.a. Then fpm tries to build and link the executable runTests, but fails because symbols from src/yaml.cpp are not contained in libfortran-yaml-cpp.a.

1 Like

Thank you for this. @awvwgk do you know where the problem could be?

We currently don’t support build scripts in Fortran fpm. The key in library is still leftover from the Haskell implementation, but we didn’t get around to actually implement a build script hook yet in the Fortran version. So this is an open issue at the moment.

1 Like

I don’t think fpm’s support for C++ files is there yet either. It might work in some cases just because the gcc or icc compiler commands by default enable both C and C++. I think there can be some issues with choosing the right linker.

The related issue for fpm is here:

To work with fpm currently, you’ll have to install your C++ dependency first as a system library, and then add something like:

link = ["yaml-cpp", "stdc++"]

into your TOML file.

Improved C++ support in fpm is something that should definitely be pursued.

1 Like

The way I’ve laid out the package currently, I think this method won’t quite work because fortran-yaml-cpp has a little bit of C++ code in it, which needs compiling.

So I think I would need to fork yaml-cpp, add my C++ code to libyaml-cpp, and make this modified version of yaml-cpp a dependency.

But there would still be the issue of the linker. I think fpm would need to use a C++ linker to make an executable.

2 Likes

If you have time to help us improve fpm to work well with C++, please do. It’s a must have feature as far as I am concerned.

2 Likes