When I created the Ter/Terlang programming language one of the things I wanted it to have was precisely: the ease of being able to hack the language and incorporate functions built in by the programmer.
In other words, you can create your own native functions. This will be interesting when I start incorporating libraries, mainly from GameDev, such as: SFML, SDL, Raylib and others to be used by Ter/Terlang.
In this example, we will create the native function: helloworld()
, that is, when printing this function, it should display the message: Hello, World!.
Right now if you create a helloworld.ter
file and try to do this:
output(helloworld())
After running: ter helloworld.ter
, the output will be an error:
[line 1] Error: Undefined variable: 'helloworld'.
So, let’s modify the source code to make this work!
We just need to follow 3 basic steps to do this.
And enter the project:
git clone https://github.com/terroo/terlang
cd terlang
./src/Builtin.hpp
And add the code below to the end of the file:
class HelloWorld : public Callable {
public:
int arity() override;
std::any call(Interpreter &interpreter, std::vector<std::any> arguments) override;
std::string toString() override;
};
All functions need to inherit Callable
in a public form. The member functions: arity()
, call()
and toString()
are the template for all functions that will be built in and need to be public.
HelloWorld
class that we added.Edit the file ./src/Builtin.cpp
and insert the following content at the end of the file:
// ------ HelloWorld -----------
int HelloWorld::arity(){
return 0;
}
std::any HelloWorld::call(Interpreter &interpreter, std::vector<std::any> arguments){
if(arguments.size() > (size_t)arity() && interpreter.global != nullptr){
builtinError("helloworld");
}
std::string hello = "Hello, World!";
return std::any_cast<std::string>(hello);
}
std::string HelloWorld::toString(){
return "<function builtin>";
}
The member function arity()
needs to return the number of arguments it receives. Since the function helloworld()
does not receive any arguments, we return zero. If it were, for example, a function named add(x, y)
that receives 2 arguments, we would need to return return 2;
The member function call()
always needs to have this initial if
to check the number of arguments. All returns need to be transformed into std::any with std::any_cast
. Since we want it to return a string
, we convert it to std::string
, which will be the sentence that will be displayed.
And finally toString()
must always have this content so that we can map the error type and know that the failure is actually in this type of function.
helloworld
to the Terlang mapNow let’s edit the file: ./src/BuiltinFactory.cpp
and add the context AT THE END OF THE MAPS builtinFactory
and builtinNames
. With the syntax below, inform the name of the class of your built-in function, in this case: HelloWorld
:
Remember that in the line above it, you need to ADD A COMMA:
,
at the end of the line to show that we have a new element.
{typeid(std::shared_ptr<HelloWorld>), [](){ return std::make_shared<HelloWorld>(); }}
And do the same in builtinNames
, the first element of this map is the name you want to call in your .ter
file, in this case we call it "helloworld"
:
{"helloworld", typeid(std::shared_ptr<HelloWorld>)}
All right now just compile and test:
# rm -rf build/ if you have already built it once, because CMake can use the cache
cmake -B build . cmake --build build
Create the test file: helloworld.ter
:
auto hello = helloworld()
output(hello)
In this case, we did it differently from the file above, we stored the return of
helloworld()
in the variablehello
, but you can also print it directly if you want:output(helloworld())
And run:
./build/ter helloworld.ter
The output will be: Hello, World!
If you want it to be available for your system, just install it: sudo cmake --install build/
.
Pretty simple, right?! This procedure is available on the Wiki.
For more information, access the repository: https://github.com/terroo/terlang/.
Learn how to create your own programming language with our Course: