Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next cpp setup #4

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft

Next cpp setup #4

wants to merge 25 commits into from

Conversation

hobovsky
Copy link

@hobovsky hobovsky commented Jan 22, 2024

Very rudimentary prototype of a setup with snippets preprocessor.

Directory snippets contains snippets of a kata as received from user or read from database: solution.snippet, test_solution.snippet, and preloaded.snippet.

Directory preprocessor contains source code of a homegrown preprocessor cwpp.

The snippet are preprocessed with cwpp in a following way:

  • solution.snippet is searched for a header marker //! CW_SOLUTION_H and implementation marker //! CW_SOLUTION_CPP.
    • if no marker is present, then solution.snippet gets copied to both include/solution.h and src/solution.cpp,
    • if only header marker is present, then solution.snippet gets copied to include/solution.h and an empty src/solution.cpp is created,
    • if only cpp marker is present, then solution.snippet gets copied to src/solution.cpp and an empty include/solution.h is created,
    • if both markers are present, then solution.snippet gets split into both include/solution.h and src/solution.cpp in a way described with the markers.
  • The same procedure is repeated for preloaded.snippet.
  • After snippets are converted into source files and distributed into expected locations, configure and run-tests are called.

see distribute-snippets.sh for invocation of the CW preprocessor. Use run-all.sh to run all the steps: preprocessing, build, and tests.

@kazk
Copy link
Member

kazk commented Jan 23, 2024

The runner works like the following for the Codewars style challenge with max 3 files:

  1. Receives a payload with the snippets
  2. Convert the snippets into files array (type File = {path: string; contents: string};)
  3. Create a container
  4. Copy the files into the container under the project directory
  5. Start the container

So in the actual implementation, the preprocessor to split the solution/preloaded will be in step 2, outside the container.


For the marker, can't we use #ifndef NAME_INCLUDED/#endif // NAME_INCLUDED?

#ifndef SOLUTION_INCLUDED
#define SOLUTION_INCLUDED
// solution.h
#endif // SOLUTION_INCLUDED
// anything after is `solution.cpp`
  • If a line with #endif // SOLUTION_INCLUDED was found:
    • Anything up to that line is include/solution.h.
    • Anything after that line is src/solution.cpp.
  • Otherwise, everything is src/solution.cpp (same as if it were found at line 0). Is there a reason to also copy it to include/solution.h?

@hobovsky
Copy link
Author

hobovsky commented Jan 23, 2024

For the marker, can't we use #ifndef NAME_INCLUDED/#endif // NAME_INCLUDED?

We can, and it would be even better, with a small caveat that if we wanted to use a language-neutral #endif (without a trailing comment), then logic of the parser would get a little bit more complex because #if/#ifdef directives can nest. It's not a big problem because preprocessor grammar is extremely simple and a simple nesting counter would suffice. If we decide that closing comment is literally #endif // NAME_INCLUDED, or #endif NAME_INCLUDED, then it's absolutely no problem.

  • Is there a reason to also copy it to include/solution.h?

Not saving a header when is not there in the snippet would be indeed clearer. I saved it because I was trying to build on top of Unnamed's idea to let author choose if the sippet should be used as a header or as an implementation if they choose to not use markers. The idea works (see snippets-Cafeteria), but feels a bit hacky to me, yes. I would support the idea of not saving a header file if there is no header markup. If we agree that we do preprocessing, then saving a snippet as both header and cpp is not necessary. It would be helpful if we decided to go without preprocessing.

@kazk
Copy link
Member

kazk commented Jan 23, 2024

I think the following works:

const splitSnippet = (code: string, name: string): [header: string, impl: string] => {
  const lines = code.split(/(?<=\n)/); // Keep LF in each line
  const marker = new RegExp(
    `^\\s*#\\s*ifndef\\s+${name.toUpperCase()}_INCLUDED`
  );
  const start = lines.findIndex((line) => marker.test(line));
  // No header file unless there's a start marker.
  if (start === -1) return ["", code];

  let counter = 1;
  const end = lines.findIndex((line, i) => {
    if (i > start) {
      if (/^\s*#\s*endif\s*/.test(line)) return --counter === 0;
      if (/^\s*#\s*if(?:n?def)?\s+/.test(line)) ++counter;
    }
    return false;
  });
  // Put everything in header unless there's a matching `#endif`.
  // The error should make more sense that way.
  return end === -1
    ? [code, ""]
    : [lines.slice(0, end + 1).join(""), lines.slice(end + 1).join("")];
};

It's only #if, #ifdef and #ifndef, right? # can have whitespace before and after.

Used like:

const [solutionH, solutionCpp] = splitSnippet(solution, "solution");
const files = [
  {path: "include/solution.h", contents: solutionH},
  {path: "src/solution.cpp", contents: solutionCpp},
  // ...
];

With this convention, if users want to solve locally, they can just use separate files and concatenate on Codewars without adding anything proprietary.

@hobovsky
Copy link
Author

It's only #if, #ifdef and #ifndef, right?

Looking at the reference, there's a few more, but yes, these three begin a new nesting level.

# can have whitespace before and after.

I think the latter is not necessary. IIRC a preprocessor directive must follow its starting #, but I think it does not help much in our case? Maybe simplifies one or two regex a bit.

Important part is to make sure that both files, h and cpp, exist after preprocessing, even if they end up empty.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants