Chomik For Programmers — Part 0: Dissecting the Main

Pawel BiernackiPawel Biernacki
3 min read

If you want to extend or embed Chomik, the best place to start is not with advanced features, but with the small main function inside chomik.cc. That function demonstrates the three core components and how they cooperate:

  • Program — holds parsed Chomik code and metadata.

  • Parser — reads source from a file or string and fills the program.

  • Machine — contains runtime memory, variables, streams, types, and execution state.

The parser is constructed with the program so it can populate it.


Register the Parser

Before using the parser, you must register it:

chomik::parser::register_parser(&the_parser);


Parsing Without Execution

If you want to only parse without executing:

if (the_parser.parse(filename.c_str()) == 0)
{
// parsing succeeded!
}


Parsing from a File

The normal case is parsing from a file:

the_parser.parse(filename.c_str());


Parsing from a String

If you want to embed Chomik code directly into your C++ code at runtime, use parse_string:

the_parser.parse_string(code_string, std::cerr);

When using parse_string, scanner-level file operations such as include directives are disabled.
This means the parser will not open other files during parsing.

⚠️ Important: This does not disable runtime file operations in Chomik code. The code can still perform file I/O unless you explicitly forbid it by creating your own machine subclass and overriding:

bool get_can_create_files() override;
void create_predefined_streams() override;


Creating and Initializing the Machine

For execution, create a machine and initialize it with predefined types, variables, and streams:

chomik::machine m;
m.create_predefined_types();
m.create_predefined_variables();
m.create_predefined_streams();


Executing the Program

Once the machine is ready, execute the program:

the_program.execute(m);

If you want to preserve state across multiple program executions, reuse the same machine instance — this way, variables and other runtime data are retained.


Accessing the Program Return Value

To access special built-in variables like "the program return", you must first construct a chomik::generic_name, then wrap it in a chomik::signature, because the machine understands signatures rather than raw names.

chomik::generic_name gn; gn.add_generic_name_item(std::make_shared<chomik::identifier_name_item>("the")); gn.add_generic_name_item(std::make_shared<chomik::identifier_name_item>("program")); gn.add_generic_name_item(std::make_shared<chomik::identifier_name_item>("return"));

chomik::signature s0{gn};
int return_value = m.get_variable_value_integer(s0);


About Parsers in Dialects

In theory, you could write a new parser for a Chomik dialect.
In practice, it’s rare. Most dialects — for example, SDL_Chomik — reuse the regular chomik::parser.

Chomik does not introduce native opaque types like C’s FILE structure.
Instead, resources such as streams, images, or fonts are represented as integers, which keeps the parser unchanged and simplifies integration.


Practical Tips

  • Register your parser early.

  • parse_string disables scanner-level includes, but not runtime I/O.

  • To forbid runtime file creation or access, override get_can_create_files and create_predefined_streams in your machine subclass.

  • Always call create_predefined_types, create_predefined_variables, and create_predefined_streams. This must be done once per machine, before any executing.

  • Reuse the same machine when you want persistent state.


Understanding this basic parse → machine → execute flow makes extending or embedding Chomik much easier.
Whether you’re adding new built-in variables, integrating with a host application, or creating a specialized dialect, the same small pattern applies.


0
Subscribe to my newsletter

Read articles from Pawel Biernacki directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pawel Biernacki
Pawel Biernacki

I was born in 1973, in Cracow, Poland. I am a software engineer.