Minimal OpenGL example in C using GLEW and GLFW

OpenGL is a reasonably abstract API for doing 3D graphics. In the past I did an example of OpenGL using GLUT. However GLUT is a bit outdated now and a more modern alternative is GLFW. The example still uses GLEW to setup the OpenGL extensions.

This example is minimal and only uses a vertex shader and a fragment shader to get started with OpenGL. For an example using tesselation and geometry shaders as well, see my short introduction to OpenGL.

Note that it is important to add code for retrieving error messages (as I have done below) in order to be able to do development of the shaders.

As in my old example, the code draws a coloured triangle on the screen.

// Minimal OpenGL example using GLFW and GLEW
#include <math.h>
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>

// Vertex shader source code:
// This shader takes in vertex positions and texture coordinates,
// passing them to the fragment shader.
const char *vertexSource = "#version 130\n\
in mediump vec3 point;\n\
in mediump vec2 texcoord;\n\
out mediump vec2 UV;\n\
void main()\n\
{\n\
  gl_Position = vec4(point, 1);\n\
  UV = texcoord;\n\
}";

// Fragment shader source code:
// This shader samples the color from a texture based on UV coordinates.
const char *fragmentSource = "#version 130\n\
in mediump vec2 UV;\n\
out mediump vec3 fragColor;\n\
uniform sampler2D tex;\n\
void main()\n\
{\n\
  fragColor = texture(tex, UV).rgb;\n\
}";

GLuint vao; // Vertex Array Object
GLuint vbo; // Vertex Buffer Object
GLuint idx; // Index Buffer Object
GLuint tex; // Texture
GLuint program; // Shader program
int width = 320; // Width of window in pixels
int height = 240; // Height of window in pixels

// Function to handle shader compile errors
void handleCompileError(const char *step, GLuint shader)
{
  GLint result = GL_FALSE;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
  if (result == GL_FALSE) {
    char buffer[1024];
    glGetShaderInfoLog(shader, 1024, NULL, buffer);
    if (buffer[0])
      fprintf(stderr, "%s: %s\n", step, buffer);
  };
}

// Function to handle shader program link errors
void handleLinkError(const char *step, GLuint program)
{
  GLint result = GL_FALSE;
  glGetProgramiv(program, GL_LINK_STATUS, &result);
  if (result == GL_FALSE) {
    char buffer[1024];
    glGetProgramInfoLog(program, 1024, NULL, buffer);
    if (buffer[0])
      fprintf(stderr, "%s: %s\n", step, buffer);
  };
}

// Vertex data:
// Each vertex has a position (x, y, z) and a texture coordinate (u, v)
GLfloat vertices[] = {
   0.5f,  0.5f,  0.0f, 1.0f, 1.0f,  // Top right
  -0.5f,  0.5f,  0.0f, 0.0f, 1.0f,  // Top left
  -0.5f, -0.5f,  0.0f, 0.0f, 0.0f   // Bottom left
};

// Indices for drawing the triangle
unsigned int indices[] = { 0, 1, 2 };

// Texture BGR data for a 2x2 texture
float pixels[] = {
  0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
  1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
};

int main(int argc, char** argv)
{
  // Initialize GLFW library.
  glfwInit();
  // Create a window.
  GLFWwindow *window = glfwCreateWindow(width, height, "minimal OpenGL example", NULL, NULL);
  // Set current OpenGL context to window.
  glfwMakeContextCurrent(window);
  // Initialize GLEW library.
  glewInit();

  glViewport(0, 0, width, height);

  // Compile and check vertex shader.
  GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertexShader, 1, &vertexSource, NULL);
  glCompileShader(vertexShader);
  handleCompileError("Vertex shader", vertexShader);

  // Compile and check fragment shader.
  GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
  glCompileShader(fragmentShader);
  handleCompileError("Fragment shader", fragmentShader);

  // Link and check shader program.
  program = glCreateProgram();
  glAttachShader(program, vertexShader);
  glAttachShader(program, fragmentShader);
  glLinkProgram(program);
  handleLinkError("Shader program", program);

  // Create a vertex array object which serves as context for the
  // vertex buffer object and the index buffer object.
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  // Initialize vertex buffer object with the vertex data.
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  // Initialize the index buffer object with the index data.
  glGenBuffers(1, &idx);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

  // Set up layout of vertex buffer object.
  glVertexAttribPointer(glGetAttribLocation(program, "point"), 3, GL_FLOAT,
                        GL_FALSE, 5 * sizeof(float), (void *)0);
  glVertexAttribPointer(glGetAttribLocation(program, "texcoord"), 2, GL_FLOAT,
                        GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float)));

  // Enable depth testing using depth buffer.
  glEnable(GL_DEPTH_TEST);

  // Switch to the shader program.
  glUseProgram(program);

  // Enable the two variables of the vertex buffer layout.
  glEnableVertexAttribArray(glGetAttribLocation(program, "point"));
  glEnableVertexAttribArray(glGetAttribLocation(program, "texcoord"));

  // Initialize texture.
  glGenTextures(1, &tex);
  // Bind texture to first slot.
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, tex);
  // Set uniform texture in shader object to first texture.
  glUniform1i(glGetUniformLocation(program, "tex"), 0);
  // Load pixel data into texture.
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_BGR, GL_FLOAT, pixels);
  // Set texture wrapping mode and interpolation modes.
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  // Initialize multiresolution layers.
  glGenerateMipmap(GL_TEXTURE_2D);

  // Loop until the user closes the window.
  while (!glfwWindowShouldClose(window)) {
    // Clear color buffer and depth buffer.
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    // Switch to the shader program.
    glUseProgram(program);
    // Draw triangle(s).
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, (void *)0);
    // Swap front and back buffers.
    glfwSwapBuffers(window);
    // Poll for and process events.
    glfwPollEvents();
  };

  // Disable the two shader variables.
  glDisableVertexAttribArray(glGetAttribLocation(program, "point"));
  glDisableVertexAttribArray(glGetAttribLocation(program, "texcoord"));

  // Unbind and delete the texture.
  glBindTexture(GL_TEXTURE_2D, 0);
  glDeleteTextures(1, &tex);

  // Unbind and delete the index buffer object.
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  glDeleteBuffers(1, &idx);

  // Unbind and delete the vertex buffer object.
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glDeleteBuffers(1, &vbo);

  // Unbind and delete the vertex array object.
  glBindVertexArray(0);
  glDeleteVertexArrays(1, &vao);

  // Unlink and delete the shader program.
  glDetachShader(program, vertexShader);
  glDetachShader(program, fragmentShader);
  glDeleteProgram(program);
  glDeleteShader(vertexShader);
  glDeleteShader(fragmentShader);

  // Set OpenGL context to NULL.
  glfwMakeContextCurrent(NULL);
  // Destroy window.
  glfwDestroyWindow(window);
  // Terminate GLFW.
  glfwTerminate();
  return 0;
}

The example uses the widely supported OpenGL version 3.1 (which has the version tag 130). You can download, compile, and run the example as follows:

wget https://www.wedesoft.de/downloads/raw-opengl-glfw.c
gcc -o raw-opengl-glfw raw-opengl-glfw.c $(pkg-config --libs glfw3 glew)
./raw-opengl-glfw

image

Any feedback, comments, and suggestions are welcome.

Enjoy!

Getting started with the Jolt Physics Engine

Motivation

In the past I have experimented with sequential impulses to implement constraints (see part 1, part 2, part 3, part 4, part 5, part 6 of my rigid body physics series). I tried to integrate Runge-Kutta integration with sequential impulses. However it was difficult to prevent interpenetration of objects. Also implementing a vehicle with wheels and suspension, where the weight ratio between the vehicle and the wheels was high, required a high number of iterations to stabilise. Finally stacking of boxes turned out to be unstable.

In a GDC 2014 talk, Erin Catto showed sequential impulses and stable box stacking in the Box2D engine. Stacking of 2D boxes was made stable by solving for multiple impulses at the same time.

In 2022 Jorrit Rouwé released JoltPhysics which is a physics engine for 3D rigid objects also using sequential impulses. His GDC 2022 talk Architecting Jolt Physics for Horizon Forbidden West refers to Erin Catto’s talk and discusses various performance optimisations developed in Jolt Physics.

In the following I have provided a few Jolt physics example programs to demonstrate some capabilities of the physics engine.

Installing Jolt

Jolt Physics is a C++ library built using CMake. To compile with double precision, I invoked JoltPhysics/Build/cmake_linux_clang_gcc.sh as follows:

cd Build
./cmake_linux_clang_gcc.sh Release g++ -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DDOUBLE_PRECISION=ON \
    -DDEBUG_RENDERER_IN_DEBUG_AND_RELEASE=OFF -DPROFILER_IN_DEBUG_AND_RELEASE=OFF

A release build with g++ and installation is done as follows:

cd Linux_Release
make -j `nproc`
sudo make install
cd ../..

Next you can have a look at JoltPhysics/HelloWorld/HelloWorld.cpp which is a simple example of a sphere bouncing on a floor. The example shows how to implement the required layers and collision filters (e.g. stationary objects cannot collide with each other). Make sure to define the Trace variable so you get useful warnings if something goes wrong.

Tumbling object in space

In this section we test the tumbling motion of a cuboid in space.

To compile a C++ program using Jolt, you need to use the same preprocessor definitions which were used to compile Jolt. If you have set up the Trace function, you will get a warning if the preprocessor definitions do not match.

Here is an example Makefile to compile and link a program with the release build of the Jolt library, GLFW, and GLEW.

CCFLAGS = -g -O3 -fPIC -Wall -Werror -DNDEBUG -DJPH_OBJECT_STREAM -DJPH_DOUBLE_PRECISION $(shell pkg-config --cflags glfw3 glew)
LDFLAGS = -flto=auto $(shell pkg-config --libs glfw3 glew) -lJolt

all: tumble

tumble: tumble.o
	g++ -o $@ $^ $(LDFLAGS)

clean:
	rm -f tumble *.o

.cc.o:
	g++ -c $(CCFLAGS) -o $@ $<

See Makefile for complete build code.

The core of the example creates a shape of dimension a×b×c and sets the density to 1000.0. Furthermore the convex radius used for approximating collision shapes needs to be much smaller than the object dimensions. The limit for the linear velocity is lifted and most importantly the solution for gyroscopic forces is enabled. Furthermore linear and angular damping are set to zero. Finally the body is created, added to the physics system, and the angular velocity is set to an interesting value. The code snippet is shown below:

float a = 1.0;
float b = 0.1;
float c = 0.5;
// ...
BoxShapeSettings body_shape_settings(Vec3(a, b, c));
body_shape_settings.mConvexRadius = 0.01;
body_shape_settings.SetDensity(1000.0);
body_shape_settings.SetEmbedded();
ShapeSettings::ShapeResult body_shape_result = body_shape_settings.Create();
ShapeRefC body_shape = body_shape_result.Get();
BodyCreationSettings body_settings(body_shape, RVec3(0.0, 0.0, 0.0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
body_settings.mMaxLinearVelocity = 10000.0;
body_settings.mApplyGyroscopicForce = true;
body_settings.mLinearDamping = 0.0;
body_settings.mAngularDamping = 0.0;
Body *body = body_interface.CreateBody(body_settings);
body_interface.AddBody(body->GetID(), EActivation::Activate);
body_interface.SetLinearVelocity(body->GetID(), Vec3(0.0, 0.0, 0.0));
body_interface.SetAngularVelocity(body->GetID(), Vec3(0.3, 0.0, 5.0));

Here is a video showing the result of the simulation. As one can see, Jolt is able to simulate a tumbling motion without deterioation.

See tumble.cc for full source code.

Stack of cuboids

In this section we test the falling motion of a stack of cuboids. Three cuboids are created and the initial positions are staggered in the x direction to get a more interesting result. Using i = 0, 1, 2 the cuboids are created in the following way:

BoxShapeSettings body_shape_settings(Vec3(0.5 * a, 0.5 * b, 0.5 * c));
body_shape_settings.mConvexRadius = 0.01;
body_shape_settings.SetDensity(1000.0);
body_shape_settings.SetEmbedded();
ShapeSettings::ShapeResult body_shape_result = body_shape_settings.Create();
ShapeRefC body_shape = body_shape_result.Get();
BodyCreationSettings body_settings(body_shape, RVec3(i * 0.4, 0.2 + i * 0.2, -i * 0.3), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
body_settings.mMaxLinearVelocity = 10000.0;
body_settings.mApplyGyroscopicForce = true;
body_settings.mLinearDamping = 0.0;
body_settings.mAngularDamping = 0.0;
Body *body = body_interface.CreateBody(body_settings);
body->SetFriction(0.5);
body->SetRestitution(0.3f);
body_interface.AddBody(body->GetID(), EActivation::Activate);

Furthermore a ground shape is created. Note that for simplicity I only created one layer. If the ground was composed of multiple convex objects, a static layer should be created and used.

BoxShapeSettings ground_shape_settings(Vec3(3.0, 0.1, 3.0));
ground_shape_settings.mConvexRadius = 0.01;
ground_shape_settings.SetEmbedded();
ShapeSettings::ShapeResult ground_shape_result = ground_shape_settings.Create();
ShapeRefC ground_shape = ground_shape_result.Get();
BodyCreationSettings ground_settings(ground_shape, RVec3(0.0, -0.5, 0.0), Quat::sIdentity(), EMotionType::Static, Layers::MOVING);
Body *ground = body_interface.CreateBody(ground_settings);
ground->SetFriction(0.5);
body_interface.AddBody(ground->GetID(), EActivation::DontActivate);

Note that the bodies need to be activated for the simulation to take place.

body_interface.ActivateBody(body->GetID());

The simulation is run by repeatedly calling the Update method on the physics system.

const int cCollisionSteps = 1;
physics_system.Update(dt, cCollisionSteps, &temp_allocator, &job_system);

The following video shows the result of the simulation.

See stack.cc for full source code.

For a more challenging demo of this type, see the Stable Box Stacking demo video by Jorrit Rouwé.

Double pendulum

The double pendulum is created using the HingeConstraintSettings class. There are two hinges. One between the base and the upper arm of the pendulum and one between the upper arm and the lower arm. The physics library also requires initialisation of a vector normal to the hinge axis.

HingeConstraintSettings hinge1;
hinge1.mPoint1 = hinge1.mPoint2 = RVec3(0.0, 0.5, 0);
hinge1.mHingeAxis1 = hinge1.mHingeAxis2 = Vec3::sAxisZ();
hinge1.mNormalAxis1 = hinge1.mNormalAxis2 = Vec3::sAxisY();
physics_system.AddConstraint(hinge1.Create(*base, *upper));

HingeConstraintSettings hinge2;
hinge2.mPoint1 = hinge2.mPoint2 = RVec3(a, 0.5, 0);
hinge2.mHingeAxis1 = hinge2.mHingeAxis2 = Vec3::sAxisZ();
hinge2.mNormalAxis1 = hinge2.mNormalAxis2 = Vec3::sAxisY();
physics_system.AddConstraint(hinge2.Create(*upper, *lower));

The following video shows the result.

See pendulum.cc for full source code.

Suspension

Another test case is a prismatic joint with a suspension constraint. The prismatic joint is created using the SliderConstraintSettings class. The suspension is created using a soft distance constraint. The code snippet is shown below:

SliderConstraintSettings slider_settings;
slider_settings.mAutoDetectPoint = true;
slider_settings.SetSliderAxis(Vec3::sAxisY());
physics_system.AddConstraint(slider_settings.Create(*boxes[0], *boxes[1]));

DistanceConstraintSettings distance_settings;
distance_settings.mPoint1 = RVec3(0.0, 0.0, 0.0);
distance_settings.mPoint2 = RVec3(0.0, 0.4, 0.0);
distance_settings.mLimitsSpringSettings.mDamping = 0.1f;
distance_settings.mLimitsSpringSettings.mStiffness = 1.0f;
physics_system.AddConstraint(distance_settings.Create(*boxes[0], *boxes[1]));

The video shows the result of running this sumulation.

See suspension.cc for full source code.

Wheeled vehicle

Jolt comes with a specialised implementation for simulating wheeled vehicles (there is also even one for tracked vehicles). The vehicle API allows placing the wheels and adjusting the suspension minimum and maximum length. One can set the angular damping of the wheels to zero. Furthermore there are longitudinal and lateral friction curves of the wheels which I haven’t modified. Finally there is a vehicle controller object for setting motor, steering angle, brakes, and hand brake.

RefConst<Shape> car_shape = new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length));
BodyCreationSettings car_body_settings(car_shape, RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
car_body_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
car_body_settings.mMassPropertiesOverride.mMass = 1500.0f;
car_body_settings.mLinearDamping = 0.0;
car_body_settings.mAngularDamping = 0.0;

VehicleConstraintSettings vehicle;

WheelSettingsWV *w1 = new WheelSettingsWV;
w1->mPosition = Vec3(0.0f, -0.9f * half_vehicle_height, half_vehicle_length - 1.0f * wheel_radius);
w1->mSuspensionMinLength = wheel_radius;
w1->mSuspensionMaxLength = 2 * wheel_radius;
w1->mAngularDamping = 0.0f;
w1->mMaxSteerAngle = 0.0f; // max_steering_angle;
w1->mMaxHandBrakeTorque = 0.0f;
w1->mRadius = wheel_radius;
w1->mWidth = wheel_width;

WheelSettingsWV *w2 = new WheelSettingsWV;
w2->mPosition = Vec3(half_vehicle_width, -0.9f * half_vehicle_height, -half_vehicle_length + 1.0f * wheel_radius);
// ...

WheelSettingsWV *w3 = new WheelSettingsWV;
w3->mPosition = Vec3(-half_vehicle_width, -0.9f * half_vehicle_height, -half_vehicle_length + 1.0f * wheel_radius);
// ...

vehicle.mWheels = {w1, w2, w3};

WheeledVehicleControllerSettings *controller = new WheeledVehicleControllerSettings;
vehicle.mController = controller;

Body *car_body = body_interface.CreateBody(car_body_settings);
body_interface.AddBody(car_body->GetID(), EActivation::Activate);
VehicleConstraint *constraint = new VehicleConstraint(*car_body, vehicle);
VehicleCollisionTester *tester = new VehicleCollisionTesterRay(Layers::MOVING);
constraint->SetVehicleCollisionTester(tester);
physics_system.AddConstraint(constraint);
physics_system.AddStepListener(constraint);

vehicle_controller->SetDriverInput(0.0f, 0.0f, 0.0f, 0.0f);

A vehicle dropping on the ground with horizontal speed is shown in the following video.

Note that the inertia of the wheels was high in this video. One can correct this by reducing the inertia of the wheels as follows.

w1->mInertia = 0.01;
w2->mInertia = 0.01;
w3->mInertia = 0.01;

See vehicle.cc for full source code.

Enjoy!

Update:

To prevent tunnelling when fast objects are colliding, you can switch the motion quality to linear cast instead of discrete:

body_settings.mMotionQuality = EMotionQuality::LinearCast;

Update:

Note that there is a mMinVelocityForRestitution setting. I.e. if two bodies collide at a velocity below that (default is 1.0 m/s), an inelastic collision will occur.

Run Europa Universalis IV with Epic Games on GNU/Linux

This is how I managed to run Europa Universalis IV via Epic Games on GNU/Linux (Debian 12).

First I installed Wine 9.0 from the Wine package repositories. Next I used winetricks to add dependencies for the Epic Launcher.

winetricks -q d3dcompiler_43 d3dcompiler_47 d3dx9 corefonts cjkfonts faudio

I then was able to install a working version of the Epic Games Launcher.

wine EpicInstaller-15.17.1.msi

Finally I installed Europa Universalis IV using Epic Launcher and run it.

Europa Universalis IV on GNU/Linux (Debian 12)

Enjoy!

Update: Installing from the Wine repository breaks many Steam games, because Steam relies on some libraries which get changed by the new Wine version.

Update: It is better to use Lutris if you want Steam to work on the same machine.

Implement an Interpreter using Clojure Instaparse

This is a small example on how to implement an interpreter using Clojure and the Instaparse library.

Dependencies

First we create a deps.edn file to get Rich Hickey’s Clojure, Mark Engelberg’s Instaparse, the Midje test suite by Brian Marick, and my modified version of Max Miorim’s midje-runner:

{:deps {org.clojure/clojure {:mvn/version "1.11.3"}
        instaparse/instaparse {:mvn/version "1.5.0"}}
 :paths ["src"]
 :aliases
 {:test
  {:extra-deps
   {midje/midje {:mvn/version "1.10.9"}
    midje-runner/midje-runner
    {:git/url "https://github.com/wedesoft/midje-runner.git"
     :git/sha "8ceb29d5781b9fc43ad4116fc645ade797342fad"}}
   :extra-paths ["test"]
   :main-opts ["-m" "midje-runner.runner"]}}}

Initial setup

Next we create a test suite with an initial test in test/clj_calculator/t_core.clj:

(ns clj-calculator.t-core
  (:require [midje.sweet :refer :all]
            [instaparse.core :as insta]
            [clj-calculator.core :refer :all]))

(facts "Test parser"
       (calc-parser "-42") => [:START [:NUMBER "-42"]])

You can run the test suite as follows which should give an error.

clj -M:test

Next we create a module with the parser in src/clj_calculator/core.clj:

(ns clj-calculator.core
    (:require [instaparse.core :as insta]))

(def calc-parser
  (insta/parser
    (slurp "resources/clj_calculator/calculator.bnf")))

We also need to create an initial grammar in resources/clj_calculator/calculator.bnf defining a minimal grammar and a regular expression for parsing an integer:

START  = NUMBER
NUMBER = #'[-+]?[0-9]+'

At this point the first test should pass.

Ignoring whitespace

Next we add a test to ignore whitespace.

(facts "Test parser"
       ; ...
       (calc-parser " -42 ") => [:START [:NUMBER "-42"]])

The test should fail with unexpected input. The grammar needs to be modified to pass this test:

START      = <WHITESPACE?> NUMBER <WHITESPACE?>
NUMBER     = #'[-+]?[0-9]+'
WHITESPACE = #'[,\ \t]+'

Note the use of ‘<’ and ‘>’ to omit the parsed whitespace from the parse tree.

Parsing expressions

Next we can add tests for sum, difference, or product of two numbers:

(facts "Test parser"
       ; ...
       (calc-parser "1 + 2")
       => [:START [:SUM [:NUMBER "1"] [:NUMBER "2"]]]
       (calc-parser "5 - 4")
       => [:START [:DIFF [:NUMBER "5"] [:NUMBER "4"]]]
       (calc-parser "2 * 3")
       => [:START [:PROD [:NUMBER "2"] [:NUMBER "3"]]])

The grammar now becomes:

START      = <WHITESPACE?> (NUMBER | SUM | DIFF | PROD) <WHITESPACE?>
SUM        = NUMBER <WHITESPACE?> <'+'> <WHITESPACE?> NUMBER
DIFF       = NUMBER <WHITESPACE?> <'-'> <WHITESPACE?> NUMBER
PROD       = NUMBER <WHITESPACE?> <'*'> <WHITESPACE?> NUMBER
NUMBER     = #'[-+]?[0-9]+'
WHITESPACE = #'[,\ \t]+'

Transforming syntax trees

Instaparse comes with a useful transformation function for recursively transforming the abstract syntax tree we obtained from parsing. First we write and run a failing test for transforming a string to an integer:

(facts "Test calculator"
       (calculate "-42") => -42)

To pass the test we implement a calculator function which transforms the syntax tree. Initially it only needs to deal with the nonterminal symbols START and NUMBER:

(defn calculate
  [string]
  (instaparse.transform/transform
    {:START  identity
     :NUMBER #(Integer/parseInt %)}
    (calc-parser string)))

Performing calculations

Obviously we can use the transformation function to also perform the calculations. Here are the tests for the three possible operations of the parse tree.

(facts "Test calculator"
       (calculate "-42") => -42
       (calculate "1 + 2") => 3
       (calculate "5 - 4") => 1
       (calculate "2 * 3") => 6)

The implementation using the Instaparse transformation function is quite elegant:

(defn calculate
  [string]
  (instaparse.transform/transform
    {:START  identity
     :NUMBER #(Integer/parseInt %)
     :SUM    +
     :DIFF   -
     :PROD   *}
    (calc-parser string)))

Recursive Grammar

The next test is about implementing an expression with two operations.

(facts "Recursive grammar"
       (calculate "2 - 1 + 3") => 4)

A naive implementation using a blind EXPR nonterminal symbol passes the test:

START      = <WHITESPACE?> (EXPR | SUM | DIFF | PROD) <WHITESPACE?>
<EXPR>     = SUM | DIFF | PROD | NUMBER
SUM        = EXPR <WHITESPACE?> <'+'> <WHITESPACE?> EXPR
DIFF       = EXPR <WHITESPACE?> <'-'> <WHITESPACE?> EXPR
PROD       = EXPR <WHITESPACE?> <'*'> <WHITESPACE?> EXPR
NUMBER     = #'[-+]?[0-9]+'
WHITESPACE = #'[,\ \t]+'

However there is a problem with this grammar: It is ambiguous. The following failing test shows that the parser could generate two different parse trees:

(facts "Recursive grammar"
       ; ...
       (count (insta/parses calc-parser "2 - 1 + 3")) => 1)

When parsing small strings, this might not be a problem. However if you use an ambiguous grammar to parse a large file with a syntax error near the end, the resulting combinatorial explosion leads to a long processing time before the parser can return the syntax error. The good thing is, that Instaparse uses the GLL parsing algorithm, i.e. it can handle a left-recursive grammar to resolve the ambiguity:

START      = <WHITESPACE?> (EXPR | SUM | DIFF | PROD) <WHITESPACE?>
<EXPR>     = SUM | DIFF | PROD | NUMBER
SUM        = EXPR <WHITESPACE?> <'+'> <WHITESPACE?> NUMBER
DIFF       = EXPR <WHITESPACE?> <'-'> <WHITESPACE?> NUMBER
PROD       = EXPR <WHITESPACE?> <'*'> <WHITESPACE?> NUMBER
NUMBER     = #'[-+]?[0-9]+'
WHITESPACE = #'[,\ \t]+'

This grammar is not ambiguous any more and will pass above test.

Grouping using brackets

We might want to use brackets to group expressions and influence the order expressions are applied:

(facts "Recursive grammar"
       ; ...
       (calculate "2 - ( 1 + 3 )") => -2
       (calculate "( 2 - 1 ) + 3") => 4)

The following grammar implements this:

START      = <WHITESPACE?> (EXPR | SUM | DIFF | PROD) <WHITESPACE?>
<EXPR>     = SUM | DIFF | PROD | GROUP
<GROUP>    = NUMBER | <'('> <WHITESPACE?> EXPR <WHITESPACE?>  <')'>
SUM        = EXPR <WHITESPACE?> <'+'> <WHITESPACE?> GROUP
DIFF       = EXPR <WHITESPACE?> <'-'> <WHITESPACE?> GROUP
PROD       = EXPR <WHITESPACE?> <'*'> <WHITESPACE?> GROUP
NUMBER     = #'[-+]?[0-9]+'
WHITESPACE = #'[,\ \t]+'

A final consideration is operator precedence of multiplication over addition and subtraction. I leave this as an exercise for the interested reader ;)

Main function

Now we only need a main function to be able to use the calculator program.

(ns clj-calculator.core
    (:require [instaparse.core :as insta])
    (:gen-class))

; ...

(defn -main
  [& _args]
  (loop [line (read-line)]
        (when line
          (println (calculate line))
          (recur (read-line))))
  (System/exit 0))

Now one can run the program as follows:

clj -M -m clj-calculator.core

To exit the calculator, simply press CTRL+D.

See github.com/wedesoft/clj-calculator for source code.

Enjoy!

A Short Introduction to OpenGL

Here is a short introduction to OpenGL using GLFW and GLEW.

Click above image to watch the 30 minutes presentation.

You can get the slides here: opengl.pdf

See github.com/wedesoft/opengl for source code of slides and opengl-example.c.

Here are instructions on how to compile and run the program with GCC under GNU/Linux:

sudo apt install build-essential libglew-dev libglfw3-dev
wget https://www.wedesoft.de/downloads/opengl-example.c
gcc -o opengl-example opengl-example.c -lglfw -lGLEW -lGL -lm
./opengl-example