MarTeX-Cpp Development

Getting Started

Prerequisites

MarTeX-Cpp can be built as a module for several other languages. Currently supported are PHP and WebAssembly and an option to build as a simple utility using standard i/o.

A common dependencies for all module options is a C++ compiler with full C++11 support (gcc 4.9 or higher, clang 3.3). The makefile is set up to work with gcc but this can be easily changed in the config files.

MarTeX-Cpp's PHP module was tested with PHP versions 7 and higher. To build it you first need your relevant php development package and then you need to install the PHP-CPP library.

MarTeX-Cpp's WebAssembly module uses the emscripten compiler. Make sure the em++ command is on your PATH by sourcing emsdk_env.sh.

The simple MarTeX-Cpp utility program has no further dependencies.

Installing

If you have obtained a clone of the repository you can build the project:

As PHP module

make target=php

this will result in a php extension called martex.so in the bin folder. You can find out where to put it by typing:

php-config --extension-dir

Now you need to add "extension=martex.so" to your php.ini file or add it as separate .ini (example in res/martex.ini) in the php loading process. Your milage may vary but the location of these .ini's is /etc/php/7.1/mods-available on my system. You should then be able to use:

phpenmod martex

to activate the module.

As WebAssembly module

make target=wasm

This will yield a martex.wasm and a martex.js file in the bin folder. Host these with a webserver to run MarTeX-Cpp directly in a browser. The file res/wasm.html demonstrates the usage.

As simple utility

make target=cpp

This will yield a file martex in the bin folder. It takes TeX over standard input, will output the result over standard output and report errors over standard error.

Tests

You might complain that my test file format is weird and is annoying to parse. I like it however, because it allows you to describe tests in however much detail you want and is also language independant, which is important because I would like to be able to run the tests in whatever module form I desire.

Project structure

Making a module

There is more than one way to make a module. The one I will describe here is for making modules for C++. This is the way to produce the fastest modules, but the disadvantage is that you need them available at compile time. For PHP there are tools available to make modules pluggable at runtime, these could be made available for other languages too.

In C++ you inherit from the virtual class CppModule. It allows you to register your MarTeX commands quickly. You also have to implement methods to tell MarTeX which environments your module provides. We'll implement a basic module here that can print "hello world" with the command \hello. The header looks as follows:

#pragma once
#include "core/util/cppmodule.hpp"

class TestModule : public util::CppModule<TestModule>
{
  public:
    /// constructor
    TestModule();

    /// methods used to register Environments
    /// We won't use them here
    std::vector<std::string> GetEnvs();
    std::shared_ptr<Environment> MakeEnv(std::string, 
        std::shared_ptr<Environment>);

    /// command to implement
    Value hello(std::shared_ptr<Environment>, Token,
        std::vector<Value>);
};

Now in the matching .cpp file we register and implement the command. Registering happens in the constructor:

#include "testmodule.hpp"
#include "core/language/runtime_error.hpp"

TestModule::TestModule()
{
    AddMethod("hello", &TestModule::hello);
}

This maps \hello in TeX input to the TestModule::hello method. It will get called with a reference to the environment it gets called it, the token so you can throw errors with a line number for the users convenience and any arguments to the command. Our \hello command will not take any arguments.

We also need to quickly implement that we don't have any environments:

std::vector<std::string> TestModule::GetEnvs()
{
    return {};
}

std::shared_ptr<Environment> TestModule::MakeEnv(
    std::string name, std::shared_ptr<Environment> parent)
{
    return nullptr;
}

Now we can finally implement our actual command. It constructs a Value object with the "hello, world!" text in it. The Value objects are pretty flexible and you can create any html construct you like. However, for performance reasons Value objects cannot be copied, only moved. You'll need to rely on std::move to get any of your input arguments into the output. Several examples of this exist throughout the codebase, take a peek!

Value TestModule::hello(std::shared_ptr<Environment>, Token cmd,
        std::vector<Value> args)
{
   if (args.size() > 0)
       throw RuntimeError(cmd, "takes no arguments");
   
   return Value(t_string, "Hello, world!");
}