The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

CodeGen::Cpppp - The C Perl-Powered Pre-Processor

RATIONALE

It's very special, because, if you can see, the preprocessor, goes up, to perl. Look, right across the directory, perl, perl, perl.

    And most distributions go up to m4

Exactly

    Does that mean it's more powerful? ...Is it more powerful?

Well, it's one layer of abstraction higher, isn't it? It's not m4. You see, most blokes gonna be templating with cpp or m4, you're on m4 here all the way up, all the way up, aaaall the way up, you're at m4 for your pre-processing, Where can you go from there? Where? Nowhere! Exactly.

What we do is if we need that extra, push over the cliff, you know what we do?

    put it up to perl

perl, exactly. One higher.

    Why don't you just download the cpp source, and enhance it with the abstractions you need? Make cpp more powerful, and make cpp be the preprocessor?

...

... These go to perl.

SYNOPSIS

  # Cpppp object is a template factory
  my $cpppp= CodeGen::Cpppp->new(%options);
  
  # Simple templates immediately generate their output during
  # construction, which goes to the output accumulator of $cpppp
  # by default.
  $cpppp->new_template($filename, %params);
  
  # Complex templates can define custom methods
  my $tpl= $cpppp->new_template($otherfile, %params);
  $tpl->generate_more_stuff(...);
  
  # Inspect or print the accumulated output
  say $cpppp->output;
  $cpppp->write_sections_to_file(public  => 'project.h');
  $cpppp->write_sections_to_file(private => 'project.c');

Input:

  #! /usr/bin/env cpppp
  ## param $min_bits = 8;
  ## param $max_bits = 16;
  ## param $feature_parent = 0;
  ## param $feature_count = 0;
  ## param @extra_node_fields;
  ##
  ## for (my $bits= $min_bits; $bits <= $max_bits; $bits <<= 1) {
  struct tree_node_$bits {
    uint${bits}_t  left :  ${{$bits-1}},
                   color:  1,
                   right:  ${{$bits-1}},
                   parent,   ## if $feature_parent;
                   count,    ## if $feature_count;
                   $trim_comma $trim_ws;
    @extra_node_fields;
  };
  ## }

Output:

  struct tree_node_8 {
    uint8_t  left :  7,
             color:  1,
             right:  7;
  };
  struct tree_node_16 {
    uint16_t left : 15,
             color:  1,
             right: 15;
  };

SECURITY

Templates are equivalent to perl scripts. Use the same caution when using cpppp templates that you would use when running perl scripts. Do not load, compile, or render templates from un-trusted authors.

DESCRIPTION

This module is a preprocessor for C, or maybe more like a perl template engine that specializes in generating C code. Each input file gets translated to Perl in a way that declares a new OO class, and then you can create instances of that class with various parameters to generate your C output, or call methods on it like automatically generating headers or function prototypes.

For the end-user, there is a 'cpppp' command line tool that behaves much like the 'cpp' tool.

WARNING: this API is not stable. It would be unwise to use cpppp as part of a distribution's build scripts yet, but it is perfectly safe to use it to generate sources and then add those generated files to a project.

If you have an interest in this topic, contact me, because I could use help brainstorming ideas about how to accommodate the most possibilities, here.

Possible Future Features:

  • Scan existing headers to discover available macros, structs, and functions on the host.

  • Pass a list of headers through the real cpp and analyze the macro output.

  • Shell out to a compiler to find 'sizeof' information for structs.

  • Directly perform the work of inlining one function into another.

ATTRIBUTES

autoindent

Default value for new templates; determines whether embedded newlines inside variables that expand in the source code will automatically have indent applied.

autocolumn

Default value for new templates; enables the feature that detects column layout in the source template, and attempts to line up those same elements in the output after variables have been expanded.

convert_linecomment_to_c89

If true, rewrite the output to convert newer '//' comments into traditional '/*' comments.

include_path

An arrayref of directories to search for template files during require_template. Make sure no un-trusted users have control over any directory in this path, the same as you would do for Perl's @INC paths.

output

An instance of CodeGen::Cpppp::Output that is used as the default output parameter for all automatically-created templates, thus collecting all their output.

CONSTRUCTOR

new

Bare-bones for now, it accepts whatever hash values you hand to it.

METHODS

require_template

  $tpl_class= $cpppp->require_template($filename);

Load a template from a file, and die if not found or if it fails to compile. Subsequent loads of the same file return the same class.

find_template

  $abs_path= $cpppp->find_template($filename);

Check the filename itself, and relative to all paths in "include_path", and return the absolute path to the first match.

new_template

  $tpl_instance= $cpppp->new_template($class_or_filename, %params);

Load a template by filename (or use an already-loaded class) and construct a new instance using %params but also with the context and output defaulting to this $cpppp instance, and return the template object.

compile_cpppp

  $cpppp->compile_cpppp($filename);
  $cpppp->compile_cpppp($input_fh, $filename);
  $cpppp->compile_cpppp(\$scalar_tpl, $filename, $line_offset);

This reads the input file handle (or scalar-ref) and builds a new perl template class out of it (and dies if there are syntax errors in the template).

Yes, this 'eval's the input, and no, there are not any guards against malicious templates. But you run the same risk any time you run someone's './configure' script.

patch_file

  $cpppp->patch_file($filename, $marker, $new_content);

Reads $filename, looking for lines containing "BEGIN $marker" and "END $marker". If not found, it dies. It then replaces all the lines between those two lines with $new_content, and writes it back to the same file handle.

Example:

  my $tpl= $cpppp->require_template("example.cp");
  my $out= $tpl->new->output;
  $cpppp->patch_file("project.h", "example.cp", $out->get('public'));
  $cpppp->patch_file("internal.h", "example.cp", $out->get('protected'));

backup_and_overwrite_file

  $cpppp->backup_and_overwrite_file($filename, $new_content);

Create a backup of $filename if it already exists, and then write a new file containing $new_content. The backup is created by appending a ".N" to the filename, choosing the first available "N" counting upward from 0.

get_filtered_output

  my $text= $cpppp->get_filtered_output($sections);

Like $cpppp->output->get, but also apply filters to the output, like "convert_linecomment_to_c89".

write_sections_to_file

  $cpppp->write_sections_to_file($section_spec, $filename);
  $cpppp->write_sections_to_file($section_spec, $filename, $patch_markers);

This is a simple wrapper around "get" in CodeGen::Cpppp::Output and either "backup_and_overwrite_file" or "patch_file", depending on whether you supply $patch_markers.

AUTHOR

Michael Conrad <mike@nrdvana.net>

VERSION

version 0.003

COPYRIGHT AND LICENSE

This software is copyright (c) 2023 by Michael Conrad.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.