[gtk+/wip/otte/shader: 87/203] gsk: Add a skeleton for a GLSL parser



commit be3edfccaf801ff72c1cbf8114407ffdbf0c0ec2
Author: Benjamin Otte <otte redhat com>
Date:   Thu Sep 14 04:16:40 2017 +0200

    gsk: Add a skeleton for a GLSL parser

 docs/reference/gsk/meson.build |    1 +
 gsk/gsk.h                      |    1 +
 gsk/gskpixelshader.c           |  184 ++++
 gsk/gskpixelshaderprivate.h    |   29 +
 gsk/gskslnode.c                |  285 +++++++
 gsk/gskslnodeprivate.h         |   52 ++
 gsk/gskslpreprocessor.c        |  162 ++++
 gsk/gskslpreprocessorprivate.h |   47 +
 gsk/gsksltokenizer.c           | 1838 ++++++++++++++++++++++++++++++++++++++++
 gsk/gsksltokenizerprivate.h    |  293 +++++++
 gsk/gsksltype.c                |  161 ++++
 gsk/gsksltypeprivate.h         |   55 ++
 gsk/gsktypes.h                 |   11 +
 gsk/meson.build                |    6 +
 gtk/inspector/init.c           |    2 +
 testsuite/gsksl/meson.build    |    3 +
 testsuite/gsksl/test-parser.c  |  360 ++++++++
 testsuite/meson.build          |    1 +
 18 files changed, 3491 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gsk/meson.build b/docs/reference/gsk/meson.build
index 725a754..668d13e 100644
--- a/docs/reference/gsk/meson.build
+++ b/docs/reference/gsk/meson.build
@@ -5,6 +5,7 @@ private_headers = [
   'gskgldriverprivate.h',
   'gskglprofilerprivate.h',
   'gskglrendererprivate.h',
+  'gskpixelshaderprivate.h',
   'gskprivate.h',
   'gskprofilerprivate.h',
   'gskrendererprivate.h',
diff --git a/gsk/gsk.h b/gsk/gsk.h
index e330600..7b2743c 100644
--- a/gsk/gsk.h
+++ b/gsk/gsk.h
@@ -21,6 +21,7 @@
 #define __GSK_H_INSIDE__
 
 #include <gsk/gskenums.h>
+#include <gsk/gskpixelshader.h>
 #include <gsk/gskrenderer.h>
 #include <gsk/gskrendernode.h>
 #include <gsk/gskroundedrect.h>
diff --git a/gsk/gskpixelshader.c b/gsk/gskpixelshader.c
new file mode 100644
index 0000000..cec22c0
--- /dev/null
+++ b/gsk/gskpixelshader.c
@@ -0,0 +1,184 @@
+/* GSK - The GTK Scene Kit
+ *
+ * Copyright 2017 © Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:GskPixelShader
+ * @Title: GskPixelShader
+ * @Short_description: A pixel shader
+ *
+ * #GskPixelShader is the object used to create pixel shaders. The language
+ * used is GLSL with a few extensions.
+ *
+ * #GskPixelShader is an immutable object: That means you cannot change
+ * anything about it other than increasing the reference count via
+ * g_object_ref().
+ */
+
+#include "config.h"
+
+#include "gskpixelshaderprivate.h"
+
+#include "gskdebugprivate.h"
+
+#include "gdk/gdkinternals.h"
+
+/**
+ * GskPixelShader:
+ *
+ * The `GskPixelShader` structure contains only private data.
+ *
+ * Since: 3.90
+ */
+
+enum {
+  PROP_0,
+  PROP_N_TEXTURES,
+
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+G_DEFINE_TYPE (GskPixelShader, gsk_pixel_shader, G_TYPE_OBJECT)
+
+static void
+gsk_pixel_shader_set_property (GObject      *gobject,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+ {
+  /* GskPixelShader *self = GSK_PIXEL_SHADER (gobject); */
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gsk_pixel_shader_get_property (GObject    *gobject,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  GskPixelShader *self = GSK_PIXEL_SHADER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_N_TEXTURES:
+      g_value_set_uint (value, self->n_textures);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gsk_pixel_shader_dispose (GObject *object)
+{    
+  GskPixelShader *self = GSK_PIXEL_SHADER (object);
+
+  gsk_sl_node_unref (self->program);
+
+  G_OBJECT_CLASS (gsk_pixel_shader_parent_class)->dispose (object);
+}
+
+static void
+gsk_pixel_shader_class_init (GskPixelShaderClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property = gsk_pixel_shader_set_property;
+  gobject_class->get_property = gsk_pixel_shader_get_property;
+  gobject_class->dispose = gsk_pixel_shader_dispose;
+
+  /**
+   * GskPixelShader:n-textures:
+   *
+   * The number of input textures to the shader.
+   *
+   * Since: 3.92
+   */
+  properties[PROP_N_TEXTURES] =
+    g_param_spec_uint ("n-textures",
+                       "n textures",
+                       "The number of input textures",
+                       0,
+                       G_MAXUINT,
+                       0,
+                       G_PARAM_READABLE |
+                       G_PARAM_STATIC_STRINGS |
+                       G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, properties);
+}
+
+static void
+gsk_pixel_shader_init (GskPixelShader *self)
+{
+}
+
+GskPixelShader *
+gsk_pixel_shader_new_for_data (GBytes             *source,
+                               GskShaderErrorFunc  error_func,
+                               gpointer            error_data)
+{
+  GskPixelShader *shader;
+  GskSlNode *program;
+
+  g_return_val_if_fail (source != NULL, NULL);
+
+  program = gsk_sl_node_new_program (source, NULL);
+  if (program == NULL)
+    return NULL;
+
+  shader = g_object_new (GSK_TYPE_PIXEL_SHADER, NULL);
+
+  shader->program = program;
+
+  return shader;
+}
+
+void
+gsk_pixel_shader_print (GskPixelShader *shader,
+                        GString        *string)
+{
+  g_return_if_fail (GSK_IS_PIXEL_SHADER (shader));
+  g_return_if_fail (string != NULL);
+
+  gsk_sl_node_print (shader->program, string);
+}
+
+char *
+gsk_pixel_shader_to_string (GskPixelShader *shader)
+{
+  GString *string;
+
+  g_return_val_if_fail (GSK_IS_PIXEL_SHADER (shader), NULL);
+
+  string = g_string_new (NULL);
+
+  gsk_pixel_shader_print (shader, string);
+
+  return g_string_free (string, FALSE);
+}
+
diff --git a/gsk/gskpixelshaderprivate.h b/gsk/gskpixelshaderprivate.h
new file mode 100644
index 0000000..a10ba06
--- /dev/null
+++ b/gsk/gskpixelshaderprivate.h
@@ -0,0 +1,29 @@
+#ifndef __GSK_PIXEL_SHADER_PRIVATE_H__
+#define __GSK_PIXEL_SHADER_PRIVATE_H__
+
+#include "gskpixelshader.h"
+
+#include "gskslnodeprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_PIXEL_SHADER_CLASS(klass)            (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_TEXTURE, 
GskPixelShaderClass))
+#define GSK_IS_TEXTURE_CLASS(klass)         (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_TEXTURE))
+#define GSK_PIXEL_SHADER_GET_CLASS(obj)          (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_TEXTURE, 
GskPixelShaderClass))
+
+struct _GskPixelShader
+{
+  GObject parent_instance;
+
+  GskSlNode *program;
+
+  guint n_textures;
+};
+
+struct _GskPixelShaderClass {
+  GObjectClass parent_class;
+};
+
+G_END_DECLS
+
+#endif /* __GSK_PIXEL_SHADER_PRIVATE_H__ */
diff --git a/gsk/gskslnode.c b/gsk/gskslnode.c
new file mode 100644
index 0000000..2ecc6c4
--- /dev/null
+++ b/gsk/gskslnode.c
@@ -0,0 +1,285 @@
+/* GTK - The GIMP Toolkit
+ *   
+ * Copyright © 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gskslnodeprivate.h"
+
+#include "gskslpreprocessorprivate.h"
+#include "gsksltypeprivate.h"
+
+#include <string.h>
+
+static GskSlNode *
+gsk_sl_node_alloc (const GskSlNodeClass *klass,
+                   gsize                 size)
+{
+  GskSlNode *node;
+
+  node = g_slice_alloc0 (size);
+
+  node->class = klass;
+  node->ref_count = 1;
+
+  return node;
+}
+#define gsk_sl_node_new(_name, _klass) ((_name *) gsk_sl_node_alloc ((_klass), sizeof (_name)))
+
+/* PROGRAM */
+
+typedef struct _GskSlNodeProgram GskSlNodeProgram;
+
+struct _GskSlNodeProgram {
+  GskSlNode parent;
+
+  GSList *declarations;
+  GSList *functions;
+};
+
+static void
+gsk_sl_node_program_free (GskSlNode *node)
+{
+  GskSlNodeProgram *program = (GskSlNodeProgram *) node;
+
+  g_slist_free (program->declarations);
+  g_slist_free (program->functions);
+
+  g_slice_free (GskSlNodeProgram, program);
+}
+
+static void
+gsk_sl_node_program_print (GskSlNode *node,
+                           GString   *string)
+{
+  GskSlNodeProgram *program = (GskSlNodeProgram *) node;
+  GSList *l;
+
+  for (l = program->declarations; l; l = l->next)
+    gsk_sl_node_print (l->data, string);
+
+  for (l = program->functions; l; l = l->next)
+    gsk_sl_node_print (l->data, string);
+}
+
+static const GskSlNodeClass GSK_SL_NODE_PROGRAM = {
+  gsk_sl_node_program_free,
+  gsk_sl_node_program_print
+};
+
+/* FUNCTION */
+
+typedef struct _GskSlNodeFunction GskSlNodeFunction;
+
+struct _GskSlNodeFunction {
+  GskSlNode parent;
+
+  GskSlType *return_type;
+  char *name;
+};
+
+static void
+gsk_sl_node_function_free (GskSlNode *node)
+{
+  GskSlNodeFunction *function = (GskSlNodeFunction *) node;
+
+  if (function->return_type)
+    gsk_sl_type_unref (function->return_type);
+  g_free (function->name);
+
+  g_slice_free (GskSlNodeFunction, function);
+}
+
+static void
+gsk_sl_node_function_print (GskSlNode *node,
+                            GString   *string)
+{
+  GskSlNodeFunction *function = (GskSlNodeFunction *) node;
+
+  gsk_sl_type_print (function->return_type, string);
+  g_string_append (string, "\n");
+
+  g_string_append (string, function->name);
+  g_string_append (string, " (");
+  g_string_append (string, ")\n");
+
+  g_string_append (string, "{\n");
+  g_string_append (string, "}\n");
+}
+
+static const GskSlNodeClass GSK_SL_NODE_FUNCTION = {
+  gsk_sl_node_function_free,
+  gsk_sl_node_function_print
+};
+
+/* API */
+
+static GskSlNodeFunction *
+gsk_sl_node_parse_function_prototype (GskSlNodeProgram  *program,
+                                      GskSlPreprocessor *stream)
+{
+  GskSlType *type;
+  GskSlNodeFunction *function;
+  const GskSlToken *token;
+
+  type = gsk_sl_type_new_parse (stream);
+  if (type == NULL)
+    return NULL;
+
+  token = gsk_sl_preprocessor_get (stream);
+  if (!gsk_sl_token_is (token, GSK_SL_TOKEN_IDENTIFIER))
+    {
+      gsk_sl_preprocessor_error (stream, "Expected a function name");
+      gsk_sl_type_unref (type);
+      return NULL;
+    }
+
+  function = gsk_sl_node_new (GskSlNodeFunction, &GSK_SL_NODE_FUNCTION);
+  function->return_type = type;
+  function->name = g_strdup (token->str);
+  gsk_sl_preprocessor_consume (stream, (GskSlNode *) function);
+
+  token = gsk_sl_preprocessor_get (stream);
+  if (!gsk_sl_token_is (token, GSK_SL_TOKEN_LEFT_PAREN))
+    {
+      gsk_sl_preprocessor_error (stream, "Expected an openening \"(\"");
+      gsk_sl_node_unref ((GskSlNode *) function);
+      return NULL;
+    }
+  gsk_sl_preprocessor_consume (stream, (GskSlNode *) function);
+
+  token = gsk_sl_preprocessor_get (stream);
+  if (!gsk_sl_token_is (token, GSK_SL_TOKEN_RIGHT_PAREN))
+    {
+      gsk_sl_preprocessor_error (stream, "Expected a closing \")\"");
+      gsk_sl_node_unref ((GskSlNode *) function);
+      return NULL;
+    }
+  gsk_sl_preprocessor_consume (stream, (GskSlNode *) function);
+
+  return function;
+}
+
+static gboolean
+gsk_sl_node_parse_function_definition (GskSlNodeProgram  *program,
+                                       GskSlPreprocessor *stream)
+{
+  GskSlNodeFunction *function;
+  const GskSlToken *token;
+
+  function = gsk_sl_node_parse_function_prototype (program, stream);
+  if (function == NULL)
+    return FALSE;
+
+  token = gsk_sl_preprocessor_get (stream);
+  if (gsk_sl_token_is (token, GSK_SL_TOKEN_SEMICOLON))
+    {
+      gsk_sl_preprocessor_consume (stream, (GskSlNode *) function);
+      program->functions = g_slist_prepend (program->functions, function);
+      return TRUE;
+    }
+
+  if (!gsk_sl_token_is (token, GSK_SL_TOKEN_LEFT_BRACE))
+    {
+      gsk_sl_preprocessor_error (stream, "Expected an opening \"{\"");
+      gsk_sl_node_unref ((GskSlNode *) function);
+      return FALSE;
+    }
+  gsk_sl_preprocessor_consume (stream, (GskSlNode *) function);
+
+  token = gsk_sl_preprocessor_get (stream);
+  if (!gsk_sl_token_is (token, GSK_SL_TOKEN_RIGHT_BRACE))
+    {
+      gsk_sl_preprocessor_error (stream, "Expected a closing \"}\"");
+      gsk_sl_node_unref ((GskSlNode *) function);
+      return FALSE;
+    }
+  gsk_sl_preprocessor_consume (stream, (GskSlNode *) function);
+
+  program->functions = g_slist_prepend (program->functions, function);
+
+  return TRUE;
+}
+
+static gboolean
+gsk_sl_node_parse_program (GskSlNodeProgram  *program,
+                           GskSlPreprocessor *stream)
+{
+  const GskSlToken *token;
+  gboolean result = TRUE;
+
+  for (token = gsk_sl_preprocessor_get (stream);
+       !gsk_sl_token_is (token, GSK_SL_TOKEN_EOF);
+       token = gsk_sl_preprocessor_get (stream))
+    {
+      if (!gsk_sl_node_parse_function_definition (program, stream))
+        {
+          gsk_sl_preprocessor_consume (stream, (GskSlNode *) program);
+          result = FALSE;
+        }
+    }
+
+  return result;
+}
+
+
+GskSlNode *
+gsk_sl_node_new_program (GBytes  *source,
+                         GError **error)
+{
+  GskSlPreprocessor *stream;
+  GskSlNodeProgram *program;
+
+  program = gsk_sl_node_new (GskSlNodeProgram, &GSK_SL_NODE_PROGRAM);
+  stream = gsk_sl_preprocessor_new (source);
+
+  gsk_sl_node_parse_program (program, stream);
+
+  gsk_sl_preprocessor_unref (stream);
+
+  return (GskSlNode *) program;
+}
+
+GskSlNode *
+gsk_sl_node_ref (GskSlNode *node)
+{
+  g_return_val_if_fail (node != NULL, NULL);
+
+  node->ref_count += 1;
+
+  return node;
+}
+
+void
+gsk_sl_node_unref (GskSlNode *node)
+{
+  if (node == NULL)
+    return;
+
+  node->ref_count -= 1;
+  if (node->ref_count > 0)
+    return;
+
+  node->class->free (node);
+}
+
+void
+gsk_sl_node_print (GskSlNode *node,
+                   GString   *string)
+{
+  node->class->print (node, string);
+}
diff --git a/gsk/gskslnodeprivate.h b/gsk/gskslnodeprivate.h
new file mode 100644
index 0000000..776f59c
--- /dev/null
+++ b/gsk/gskslnodeprivate.h
@@ -0,0 +1,52 @@
+/* GTK - The GIMP Toolkit
+ *
+ * Copyright © 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_SL_NODE_PRIVATE_H__
+#define __GSK_SL_NODE_PRIVATE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GskSlNode GskSlNode;
+typedef struct _GskSlNodeClass GskSlNodeClass;
+
+struct _GskSlNode {
+  const GskSlNodeClass *class;
+  guint ref_count;
+};
+
+struct _GskSlNodeClass {
+  void                  (* free)                                (GskSlNode           *node);
+
+  void                  (* print)                               (GskSlNode           *node,
+                                                                 GString             *string);
+};
+
+GskSlNode *             gsk_sl_node_new_program                 (GBytes              *source,
+                                                                 GError             **error);
+
+GskSlNode *             gsk_sl_node_ref                         (GskSlNode           *node);
+void                    gsk_sl_node_unref                       (GskSlNode           *node);
+
+void                    gsk_sl_node_print                       (GskSlNode           *node,
+                                                                 GString             *string);
+
+G_END_DECLS
+
+#endif /* __GSK_SL_NODE_PRIVATE_H__ */
diff --git a/gsk/gskslpreprocessor.c b/gsk/gskslpreprocessor.c
new file mode 100644
index 0000000..abd07a3
--- /dev/null
+++ b/gsk/gskslpreprocessor.c
@@ -0,0 +1,162 @@
+/* GTK - The GIMP Toolkit
+ *   
+ * Copyright © 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gskslpreprocessorprivate.h"
+
+struct _GskSlPreprocessor
+{
+  int ref_count;
+
+  GskSlTokenizer *tokenizer;
+  GskCodeLocation location;
+  GskSlToken token;
+};
+
+/* API */
+
+static void
+gsk_sl_preprocessor_error_func (GskSlTokenizer        *parser,
+                                gboolean               fatal,
+                                const GskCodeLocation *location,
+                                const GskSlToken      *token,
+                                const GError          *error,
+                                gpointer               user_data)
+{
+  g_printerr ("%3zu:%2zu: error: %3u: %s: %s\n",
+              location->lines + 1, location->line_bytes,
+              token->type, gsk_sl_token_to_string (token),
+              error->message);
+}
+
+GskSlPreprocessor *
+gsk_sl_preprocessor_new (GBytes *source)
+{
+  GskSlPreprocessor *preproc;
+
+  preproc = g_slice_new0 (GskSlPreprocessor);
+
+  preproc->ref_count = 1;
+  preproc->tokenizer = gsk_sl_tokenizer_new (source, 
+                                            gsk_sl_preprocessor_error_func,
+                                            preproc,
+                                            NULL);
+
+  return preproc;
+}
+
+GskSlPreprocessor *
+gsk_sl_preprocessor_ref (GskSlPreprocessor *preproc)
+{
+  g_return_val_if_fail (preproc != NULL, NULL);
+
+  preproc->ref_count += 1;
+
+  return preproc;
+}
+
+void
+gsk_sl_preprocessor_unref (GskSlPreprocessor *preproc)
+{
+  if (preproc == NULL)
+    return;
+
+  preproc->ref_count -= 1;
+  if (preproc->ref_count > 0)
+    return;
+
+  gsk_sl_tokenizer_unref (preproc->tokenizer);
+  gsk_sl_token_clear (&preproc->token);
+
+  g_slice_free (GskSlPreprocessor, preproc);
+}
+
+static gboolean
+gsk_sl_token_is_skipped (const GskSlToken *token)
+{
+  return gsk_sl_token_is (token, GSK_SL_TOKEN_ERROR)
+      || gsk_sl_token_is (token, GSK_SL_TOKEN_WHITESPACE)
+      || gsk_sl_token_is (token, GSK_SL_TOKEN_COMMENT)
+      || gsk_sl_token_is (token, GSK_SL_TOKEN_SINGLE_LINE_COMMENT);
+}
+
+static void
+gsk_sl_token_ensure (GskSlPreprocessor *preproc)
+{
+  if (!gsk_sl_token_is (&preproc->token, GSK_SL_TOKEN_EOF))
+    return;
+
+  do 
+    {
+      preproc->location = *gsk_sl_tokenizer_get_location (preproc->tokenizer);
+      gsk_sl_tokenizer_read_token (preproc->tokenizer, &preproc->token);
+    }
+  while (gsk_sl_token_is_skipped (&preproc->token));
+}
+
+const GskSlToken *
+gsk_sl_preprocessor_get (GskSlPreprocessor *preproc)
+{
+  gsk_sl_token_ensure (preproc);
+
+  return &preproc->token;
+}
+
+const GskCodeLocation *
+gsk_sl_preprocessor_get_location (GskSlPreprocessor *preproc)
+{
+  gsk_sl_token_ensure (preproc);
+
+  return &preproc->location;
+}
+
+void
+gsk_sl_preprocessor_consume (GskSlPreprocessor *preproc,
+                             GskSlNode         *consumer)
+{
+  gsk_sl_token_ensure (preproc);
+
+  gsk_sl_token_clear (&preproc->token);
+}
+
+void
+gsk_sl_preprocessor_error (GskSlPreprocessor *preproc,
+                           const char        *format,
+                           ...)
+{
+  GError *error;
+  va_list args;
+
+  va_start (args, format);
+  error = g_error_new_valist (G_FILE_ERROR,
+                              G_FILE_ERROR_FAILED,
+                              format,
+                              args);
+  va_end (args);
+
+  gsk_sl_token_ensure (preproc);
+  gsk_sl_preprocessor_error_func (preproc->tokenizer,
+                                  TRUE,
+                                  &preproc->location,
+                                  &preproc->token,
+                                  error,
+                                  NULL);
+
+  g_error_free (error);
+}
diff --git a/gsk/gskslpreprocessorprivate.h b/gsk/gskslpreprocessorprivate.h
new file mode 100644
index 0000000..58e5888
--- /dev/null
+++ b/gsk/gskslpreprocessorprivate.h
@@ -0,0 +1,47 @@
+/* GTK - The GIMP Toolkit
+ *
+ * Copyright © 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_SL_PREPROCESSOR_PRIVATE_H__
+#define __GSK_SL_PREPROCESSOR_PRIVATE_H__
+
+#include <glib.h>
+
+#include "gskslnodeprivate.h"
+#include "gsksltokenizerprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskSlPreprocessor GskSlPreprocessor;
+
+GskSlPreprocessor *     gsk_sl_preprocessor_new                 (GBytes              *source);
+
+GskSlPreprocessor *     gsk_sl_preprocessor_ref                 (GskSlPreprocessor   *preproc);
+void                    gsk_sl_preprocessor_unref               (GskSlPreprocessor   *preproc);
+
+const GskSlToken *      gsk_sl_preprocessor_get                 (GskSlPreprocessor   *preproc);
+const GskCodeLocation * gsk_sl_preprocessor_get_location        (GskSlPreprocessor   *preproc);
+void                    gsk_sl_preprocessor_consume             (GskSlPreprocessor   *preproc,
+                                                                 GskSlNode           *consumer);
+
+void                    gsk_sl_preprocessor_error               (GskSlPreprocessor   *preproc,
+                                                                 const char          *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+
+G_END_DECLS
+
+#endif /* __GSK_SL_PREPROCESSOR_PRIVATE_H__ */
diff --git a/gsk/gsksltokenizer.c b/gsk/gsksltokenizer.c
new file mode 100644
index 0000000..fa9a071
--- /dev/null
+++ b/gsk/gsksltokenizer.c
@@ -0,0 +1,1838 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gsksltokenizerprivate.h"
+
+#include "gskpixelshader.h"
+
+#include <math.h>
+#include <string.h>
+
+typedef struct _GskSlTokenReader GskSlTokenReader;
+
+struct _GskSlTokenReader {
+  const char *           data;
+  const char *           end;
+
+  GskCodeLocation        position;
+};
+
+struct _GskSlTokenizer
+{
+  gint                   ref_count;
+  GBytes                *bytes;
+  GskSlTokenizerErrorFunc error_func;
+  gpointer               user_data;
+  GDestroyNotify         user_destroy;
+
+  GskSlTokenReader       reader;
+};
+
+static void
+gsk_code_location_init (GskCodeLocation *location)
+{
+  memset (location, 0, sizeof (GskCodeLocation));
+}
+
+static void
+gsk_code_location_init_copy (GskCodeLocation       *location,
+                             const GskCodeLocation *source)
+{
+  *location = *source;
+}
+
+static void
+gsk_code_location_advance (GskCodeLocation *location,
+                           gsize            bytes,
+                           gsize            chars)
+{
+  location->bytes += bytes;
+  location->chars += chars;
+  location->line_bytes += bytes;
+  location->line_chars += chars;
+}
+
+static void
+gsk_code_location_advance_newline (GskCodeLocation *location,
+                                   gsize            n_chars)
+{
+  gsk_code_location_advance (location, n_chars, n_chars);
+
+  location->lines++;
+  location->line_bytes = 0;
+  location->line_chars = 0;
+}
+
+void
+gsk_sl_token_clear (GskSlToken *token)
+{
+  switch (token->type)
+    {
+    case GSK_SL_TOKEN_IDENTIFIER:
+      g_free (token->str);
+      break;
+
+    case GSK_SL_TOKEN_EOF:
+    case GSK_SL_TOKEN_ERROR:
+    case GSK_SL_TOKEN_WHITESPACE:
+    case GSK_SL_TOKEN_COMMENT:
+    case GSK_SL_TOKEN_SINGLE_LINE_COMMENT:
+  /* real tokens */
+    case GSK_SL_TOKEN_CONST:
+    case GSK_SL_TOKEN_BOOL:
+    case GSK_SL_TOKEN_FLOAT:
+    case GSK_SL_TOKEN_DOUBLE:
+    case GSK_SL_TOKEN_INT:
+    case GSK_SL_TOKEN_UINT:
+    case GSK_SL_TOKEN_BREAK:
+    case GSK_SL_TOKEN_CONTINUE:
+    case GSK_SL_TOKEN_DO:
+    case GSK_SL_TOKEN_ELSE:
+    case GSK_SL_TOKEN_FOR:
+    case GSK_SL_TOKEN_IF:
+    case GSK_SL_TOKEN_DISCARD:
+    case GSK_SL_TOKEN_RETURN:
+    case GSK_SL_TOKEN_SWITCH:
+    case GSK_SL_TOKEN_CASE:
+    case GSK_SL_TOKEN_DEFAULT:
+    case GSK_SL_TOKEN_SUBROUTINE:
+    case GSK_SL_TOKEN_BVEC2:
+    case GSK_SL_TOKEN_BVEC3:
+    case GSK_SL_TOKEN_BVEC4:
+    case GSK_SL_TOKEN_IVEC2:
+    case GSK_SL_TOKEN_IVEC3:
+    case GSK_SL_TOKEN_IVEC4:
+    case GSK_SL_TOKEN_UVEC2:
+    case GSK_SL_TOKEN_UVEC3:
+    case GSK_SL_TOKEN_UVEC4:
+    case GSK_SL_TOKEN_VEC2:
+    case GSK_SL_TOKEN_VEC3:
+    case GSK_SL_TOKEN_VEC4:
+    case GSK_SL_TOKEN_MAT2:
+    case GSK_SL_TOKEN_MAT3:
+    case GSK_SL_TOKEN_MAT4:
+    case GSK_SL_TOKEN_CENTROID:
+    case GSK_SL_TOKEN_IN:
+    case GSK_SL_TOKEN_OUT:
+    case GSK_SL_TOKEN_INOUT:
+    case GSK_SL_TOKEN_UNIFORM:
+    case GSK_SL_TOKEN_PATCH:
+    case GSK_SL_TOKEN_SAMPLE:
+    case GSK_SL_TOKEN_BUFFER:
+    case GSK_SL_TOKEN_SHARED:
+    case GSK_SL_TOKEN_COHERENT:
+    case GSK_SL_TOKEN_VOLATILE:
+    case GSK_SL_TOKEN_RESTRICT:
+    case GSK_SL_TOKEN_READONLY:
+    case GSK_SL_TOKEN_WRITEONLY:
+    case GSK_SL_TOKEN_DVEC2:
+    case GSK_SL_TOKEN_DVEC3:
+    case GSK_SL_TOKEN_DVEC4:
+    case GSK_SL_TOKEN_DMAT2:
+    case GSK_SL_TOKEN_DMAT3:
+    case GSK_SL_TOKEN_DMAT4:
+    case GSK_SL_TOKEN_NOPERSPECTIVE:
+    case GSK_SL_TOKEN_FLAT:
+    case GSK_SL_TOKEN_SMOOTH:
+    case GSK_SL_TOKEN_LAYOUT:
+    case GSK_SL_TOKEN_MAT2X2:
+    case GSK_SL_TOKEN_MAT2X3:
+    case GSK_SL_TOKEN_MAT2X4:
+    case GSK_SL_TOKEN_MAT3X2:
+    case GSK_SL_TOKEN_MAT3X3:
+    case GSK_SL_TOKEN_MAT3X4:
+    case GSK_SL_TOKEN_MAT4X2:
+    case GSK_SL_TOKEN_MAT4X3:
+    case GSK_SL_TOKEN_MAT4X4:
+    case GSK_SL_TOKEN_DMAT2X2:
+    case GSK_SL_TOKEN_DMAT2X3:
+    case GSK_SL_TOKEN_DMAT2X4:
+    case GSK_SL_TOKEN_DMAT3X2:
+    case GSK_SL_TOKEN_DMAT3X3:
+    case GSK_SL_TOKEN_DMAT3X4:
+    case GSK_SL_TOKEN_DMAT4X2:
+    case GSK_SL_TOKEN_DMAT4X3:
+    case GSK_SL_TOKEN_DMAT4X4:
+    case GSK_SL_TOKEN_ATOMIC_UINT:
+    case GSK_SL_TOKEN_SAMPLER1D:
+    case GSK_SL_TOKEN_SAMPLER2D:
+    case GSK_SL_TOKEN_SAMPLER3D:
+    case GSK_SL_TOKEN_SAMPLERCUBE:
+    case GSK_SL_TOKEN_SAMPLER1DSHADOW:
+    case GSK_SL_TOKEN_SAMPLER2DSHADOW:
+    case GSK_SL_TOKEN_SAMPLERCUBESHADOW:
+    case GSK_SL_TOKEN_SAMPLER1DARRAY:
+    case GSK_SL_TOKEN_SAMPLER2DARRAY:
+    case GSK_SL_TOKEN_SAMPLER1DARRAYSHADOW:
+    case GSK_SL_TOKEN_SAMPLER2DARRAYSHADOW:
+    case GSK_SL_TOKEN_ISAMPLER1D:
+    case GSK_SL_TOKEN_ISAMPLER2D:
+    case GSK_SL_TOKEN_ISAMPLER3D:
+    case GSK_SL_TOKEN_ISAMPLERCUBE:
+    case GSK_SL_TOKEN_ISAMPLER1DARRAY:
+    case GSK_SL_TOKEN_ISAMPLER2DARRAY:
+    case GSK_SL_TOKEN_USAMPLER1D:
+    case GSK_SL_TOKEN_USAMPLER2D:
+    case GSK_SL_TOKEN_USAMPLER3D:
+    case GSK_SL_TOKEN_USAMPLERCUBE:
+    case GSK_SL_TOKEN_USAMPLER1DARRAY:
+    case GSK_SL_TOKEN_USAMPLER2DARRAY:
+    case GSK_SL_TOKEN_SAMPLER2DRECT:
+    case GSK_SL_TOKEN_SAMPLER2DRECTSHADOW:
+    case GSK_SL_TOKEN_ISAMPLER2DRECT:
+    case GSK_SL_TOKEN_USAMPLER2DRECT:
+    case GSK_SL_TOKEN_SAMPLERBUFFER:
+    case GSK_SL_TOKEN_ISAMPLERBUFFER:
+    case GSK_SL_TOKEN_USAMPLERBUFFER:
+    case GSK_SL_TOKEN_SAMPLERCUBEARRAY:
+    case GSK_SL_TOKEN_SAMPLERCUBEARRAYSHADOW:
+    case GSK_SL_TOKEN_ISAMPLERCUBEARRAY:
+    case GSK_SL_TOKEN_USAMPLERCUBEARRAY:
+    case GSK_SL_TOKEN_SAMPLER2DMS:
+    case GSK_SL_TOKEN_ISAMPLER2DMS:
+    case GSK_SL_TOKEN_USAMPLER2DMS:
+    case GSK_SL_TOKEN_SAMPLER2DMSARRAY:
+    case GSK_SL_TOKEN_ISAMPLER2DMSARRAY:
+    case GSK_SL_TOKEN_USAMPLER2DMSARRAY:
+    case GSK_SL_TOKEN_IMAGE1D:
+    case GSK_SL_TOKEN_IIMAGE1D:
+    case GSK_SL_TOKEN_UIMAGE1D:
+    case GSK_SL_TOKEN_IMAGE2D:
+    case GSK_SL_TOKEN_IIMAGE2D:
+    case GSK_SL_TOKEN_UIMAGE2D:
+    case GSK_SL_TOKEN_IMAGE3D:
+    case GSK_SL_TOKEN_IIMAGE3D:
+    case GSK_SL_TOKEN_UIMAGE3D:
+    case GSK_SL_TOKEN_IMAGE2DRECT:
+    case GSK_SL_TOKEN_IIMAGE2DRECT:
+    case GSK_SL_TOKEN_UIMAGE2DRECT:
+    case GSK_SL_TOKEN_IMAGECUBE:
+    case GSK_SL_TOKEN_IIMAGECUBE:
+    case GSK_SL_TOKEN_UIMAGECUBE:
+    case GSK_SL_TOKEN_IMAGEBUFFER:
+    case GSK_SL_TOKEN_IIMAGEBUFFER:
+    case GSK_SL_TOKEN_UIMAGEBUFFER:
+    case GSK_SL_TOKEN_IMAGE1DARRAY:
+    case GSK_SL_TOKEN_IIMAGE1DARRAY:
+    case GSK_SL_TOKEN_UIMAGE1DARRAY:
+    case GSK_SL_TOKEN_IMAGE2DARRAY:
+    case GSK_SL_TOKEN_IIMAGE2DARRAY:
+    case GSK_SL_TOKEN_UIMAGE2DARRAY:
+    case GSK_SL_TOKEN_IMAGECUBEARRAY:
+    case GSK_SL_TOKEN_IIMAGECUBEARRAY:
+    case GSK_SL_TOKEN_UIMAGECUBEARRAY:
+    case GSK_SL_TOKEN_IMAGE2DMS:
+    case GSK_SL_TOKEN_IIMAGE2DMS:
+    case GSK_SL_TOKEN_UIMAGE2DMS:
+    case GSK_SL_TOKEN_IMAGE2DMSARRAY:
+    case GSK_SL_TOKEN_IIMAGE2DMSARRAY:
+    case GSK_SL_TOKEN_UIMAGE2DMSARRAY:
+    case GSK_SL_TOKEN_STRUCT:
+    case GSK_SL_TOKEN_VOID:
+    case GSK_SL_TOKEN_WHILE:
+    case GSK_SL_TOKEN_FLOATCONSTANT:
+    case GSK_SL_TOKEN_DOUBLECONSTANT:
+    case GSK_SL_TOKEN_INTCONSTANT:
+    case GSK_SL_TOKEN_UINTCONSTANT:
+    case GSK_SL_TOKEN_BOOLCONSTANT:
+    case GSK_SL_TOKEN_LEFT_OP:
+    case GSK_SL_TOKEN_RIGHT_OP:
+    case GSK_SL_TOKEN_INC_OP:
+    case GSK_SL_TOKEN_DEC_OP:
+    case GSK_SL_TOKEN_LE_OP:
+    case GSK_SL_TOKEN_GE_OP:
+    case GSK_SL_TOKEN_EQ_OP:
+    case GSK_SL_TOKEN_NE_OP:
+    case GSK_SL_TOKEN_AND_OP:
+    case GSK_SL_TOKEN_OR_OP:
+    case GSK_SL_TOKEN_XOR_OP:
+    case GSK_SL_TOKEN_MUL_ASSIGN:
+    case GSK_SL_TOKEN_DIV_ASSIGN:
+    case GSK_SL_TOKEN_ADD_ASSIGN:
+    case GSK_SL_TOKEN_MOD_ASSIGN:
+    case GSK_SL_TOKEN_LEFT_ASSIGN:
+    case GSK_SL_TOKEN_RIGHT_ASSIGN:
+    case GSK_SL_TOKEN_AND_ASSIGN:
+    case GSK_SL_TOKEN_XOR_ASSIGN:
+    case GSK_SL_TOKEN_OR_ASSIGN:
+    case GSK_SL_TOKEN_SUB_ASSIGN:
+    case GSK_SL_TOKEN_LEFT_PAREN:
+    case GSK_SL_TOKEN_RIGHT_PAREN:
+    case GSK_SL_TOKEN_LEFT_BRACKET:
+    case GSK_SL_TOKEN_RIGHT_BRACKET:
+    case GSK_SL_TOKEN_LEFT_BRACE:
+    case GSK_SL_TOKEN_RIGHT_BRACE:
+    case GSK_SL_TOKEN_DOT:
+    case GSK_SL_TOKEN_COMMA:
+    case GSK_SL_TOKEN_COLON:
+    case GSK_SL_TOKEN_EQUAL:
+    case GSK_SL_TOKEN_SEMICOLON:
+    case GSK_SL_TOKEN_BANG:
+    case GSK_SL_TOKEN_DASH:
+    case GSK_SL_TOKEN_TILDE:
+    case GSK_SL_TOKEN_PLUS:
+    case GSK_SL_TOKEN_STAR:
+    case GSK_SL_TOKEN_SLASH:
+    case GSK_SL_TOKEN_PERCENT:
+    case GSK_SL_TOKEN_LEFT_ANGLE:
+    case GSK_SL_TOKEN_RIGHT_ANGLE:
+    case GSK_SL_TOKEN_VERTICAL_BAR:
+    case GSK_SL_TOKEN_CARET:
+    case GSK_SL_TOKEN_AMPERSAND:
+    case GSK_SL_TOKEN_QUESTION:
+    case GSK_SL_TOKEN_INVARIANT:
+    case GSK_SL_TOKEN_PRECISE:
+    case GSK_SL_TOKEN_HIGH_PRECISION:
+    case GSK_SL_TOKEN_MEDIUM_PRECISION:
+    case GSK_SL_TOKEN_LOW_PRECISION:
+    case GSK_SL_TOKEN_PRECISION:
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  token->type = GSK_SL_TOKEN_EOF;
+}
+
+static const char *keywords[] = {
+  [GSK_SL_TOKEN_CONST] = "const",
+  [GSK_SL_TOKEN_BOOL] = "bool",
+  [GSK_SL_TOKEN_FLOAT] = "float",
+  [GSK_SL_TOKEN_DOUBLE] = "double",
+  [GSK_SL_TOKEN_INT] = "int",
+  [GSK_SL_TOKEN_UINT] = "uint",
+  [GSK_SL_TOKEN_BREAK] = "break",
+  [GSK_SL_TOKEN_CONTINUE] = "continue",
+  [GSK_SL_TOKEN_DO] = "do",
+  [GSK_SL_TOKEN_ELSE] = "else",
+  [GSK_SL_TOKEN_FOR] = "for",
+  [GSK_SL_TOKEN_IF] = "if",
+  [GSK_SL_TOKEN_DISCARD] = "discard",
+  [GSK_SL_TOKEN_RETURN] = "return",
+  [GSK_SL_TOKEN_SWITCH] = "switch",
+  [GSK_SL_TOKEN_CASE] = "case",
+  [GSK_SL_TOKEN_DEFAULT] = "default",
+  [GSK_SL_TOKEN_SUBROUTINE] = "subroutine",
+  [GSK_SL_TOKEN_BVEC2] = "bvec2",
+  [GSK_SL_TOKEN_BVEC3] = "bvec3",
+  [GSK_SL_TOKEN_BVEC4] = "bvec4",
+  [GSK_SL_TOKEN_IVEC2] = "ivec2",
+  [GSK_SL_TOKEN_IVEC3] = "ivec3",
+  [GSK_SL_TOKEN_IVEC4] = "ivec4",
+  [GSK_SL_TOKEN_UVEC2] = "uvec2",
+  [GSK_SL_TOKEN_UVEC3] = "uvec3",
+  [GSK_SL_TOKEN_UVEC4] = "uvec4",
+  [GSK_SL_TOKEN_VEC2] = "vec2",
+  [GSK_SL_TOKEN_VEC3] = "vec3",
+  [GSK_SL_TOKEN_VEC4] = "vec4",
+  [GSK_SL_TOKEN_MAT2] = "mat2",
+  [GSK_SL_TOKEN_MAT3] = "mat3",
+  [GSK_SL_TOKEN_MAT4] = "mat4",
+  [GSK_SL_TOKEN_CENTROID] = "centroid",
+  [GSK_SL_TOKEN_IN] = "in",
+  [GSK_SL_TOKEN_OUT] = "out",
+  [GSK_SL_TOKEN_INOUT] = "inout",
+  [GSK_SL_TOKEN_UNIFORM] = "uniform",
+  [GSK_SL_TOKEN_PATCH] = "patch",
+  [GSK_SL_TOKEN_SAMPLE] = "sample",
+  [GSK_SL_TOKEN_BUFFER] = "buffer",
+  [GSK_SL_TOKEN_SHARED] = "shared",
+  [GSK_SL_TOKEN_COHERENT] = "coherent",
+  [GSK_SL_TOKEN_VOLATILE] = "volatile",
+  [GSK_SL_TOKEN_RESTRICT] = "restrict",
+  [GSK_SL_TOKEN_READONLY] = "readonly",
+  [GSK_SL_TOKEN_WRITEONLY] = "writeonly",
+  [GSK_SL_TOKEN_DVEC2] = "dvec2",
+  [GSK_SL_TOKEN_DVEC3] = "dvec3",
+  [GSK_SL_TOKEN_DVEC4] = "dvec4",
+  [GSK_SL_TOKEN_DMAT2] = "dmat2",
+  [GSK_SL_TOKEN_DMAT3] = "dmat3",
+  [GSK_SL_TOKEN_DMAT4] = "dmat4",
+  [GSK_SL_TOKEN_NOPERSPECTIVE] = "noperspective",
+  [GSK_SL_TOKEN_FLAT] = "flat",
+  [GSK_SL_TOKEN_SMOOTH] = "smooth",
+  [GSK_SL_TOKEN_LAYOUT] = "layout",
+  [GSK_SL_TOKEN_MAT2X2] = "mat2x2",
+  [GSK_SL_TOKEN_MAT2X3] = "mat2x3",
+  [GSK_SL_TOKEN_MAT2X4] = "mat2x4",
+  [GSK_SL_TOKEN_MAT3X2] = "mat3x2",
+  [GSK_SL_TOKEN_MAT3X3] = "mat3x3",
+  [GSK_SL_TOKEN_MAT3X4] = "mat3x4",
+  [GSK_SL_TOKEN_MAT4X2] = "mat4x2",
+  [GSK_SL_TOKEN_MAT4X3] = "mat4x3",
+  [GSK_SL_TOKEN_MAT4X4] = "mat4x4",
+  [GSK_SL_TOKEN_DMAT2X2] = "dmat2x2",
+  [GSK_SL_TOKEN_DMAT2X3] = "dmat2x3",
+  [GSK_SL_TOKEN_DMAT2X4] = "dmat2x4",
+  [GSK_SL_TOKEN_DMAT3X2] = "dmat3x2",
+  [GSK_SL_TOKEN_DMAT3X3] = "dmat3x3",
+  [GSK_SL_TOKEN_DMAT3X4] = "dmat3x4",
+  [GSK_SL_TOKEN_DMAT4X2] = "dmat4x2",
+  [GSK_SL_TOKEN_DMAT4X3] = "dmat4x3",
+  [GSK_SL_TOKEN_DMAT4X4] = "dmat4x4",
+  [GSK_SL_TOKEN_ATOMIC_UINT] = "atomic_uint",
+  [GSK_SL_TOKEN_SAMPLER1D] = "sampler1d",
+  [GSK_SL_TOKEN_SAMPLER2D] = "sampler2d",
+  [GSK_SL_TOKEN_SAMPLER3D] = "sampler3d",
+  [GSK_SL_TOKEN_SAMPLERCUBE] = "samplercube",
+  [GSK_SL_TOKEN_SAMPLER1DSHADOW] = "sampler1dshadow",
+  [GSK_SL_TOKEN_SAMPLER2DSHADOW] = "sampler2dshadow",
+  [GSK_SL_TOKEN_SAMPLERCUBESHADOW] = "samplercubeshadow",
+  [GSK_SL_TOKEN_SAMPLER1DARRAY] = "sampler1darray",
+  [GSK_SL_TOKEN_SAMPLER2DARRAY] = "sampler2darray",
+  [GSK_SL_TOKEN_SAMPLER1DARRAYSHADOW] = "sampler1darrayshadow",
+  [GSK_SL_TOKEN_SAMPLER2DARRAYSHADOW] = "sampler2darrayshadow",
+  [GSK_SL_TOKEN_ISAMPLER1D] = "isampler1d",
+  [GSK_SL_TOKEN_ISAMPLER2D] = "isampler2d",
+  [GSK_SL_TOKEN_ISAMPLER3D] = "isampler3d",
+  [GSK_SL_TOKEN_ISAMPLERCUBE] = "isamplercube",
+  [GSK_SL_TOKEN_ISAMPLER1DARRAY] = "isampler1darray",
+  [GSK_SL_TOKEN_ISAMPLER2DARRAY] = "isampler2darray",
+  [GSK_SL_TOKEN_USAMPLER1D] = "usampler1d",
+  [GSK_SL_TOKEN_USAMPLER2D] = "usampler2d",
+  [GSK_SL_TOKEN_USAMPLER3D] = "usampler3d",
+  [GSK_SL_TOKEN_USAMPLERCUBE] = "usamplercube",
+  [GSK_SL_TOKEN_USAMPLER1DARRAY] = "usampler1darray",
+  [GSK_SL_TOKEN_USAMPLER2DARRAY] = "usampler2darray",
+  [GSK_SL_TOKEN_SAMPLER2DRECT] = "sampler2drect",
+  [GSK_SL_TOKEN_SAMPLER2DRECTSHADOW] = "sampler2drectshadow",
+  [GSK_SL_TOKEN_ISAMPLER2DRECT] = "isampler2drect",
+  [GSK_SL_TOKEN_USAMPLER2DRECT] = "usampler2drect",
+  [GSK_SL_TOKEN_SAMPLERBUFFER] = "samplerbuffer",
+  [GSK_SL_TOKEN_ISAMPLERBUFFER] = "isamplerbuffer",
+  [GSK_SL_TOKEN_USAMPLERBUFFER] = "usamplerbuffer",
+  [GSK_SL_TOKEN_SAMPLERCUBEARRAY] = "samplercubearray",
+  [GSK_SL_TOKEN_SAMPLERCUBEARRAYSHADOW] = "samplercubearrayshadow",
+  [GSK_SL_TOKEN_ISAMPLERCUBEARRAY] = "isamplercubearray",
+  [GSK_SL_TOKEN_USAMPLERCUBEARRAY] = "usamplercubearray",
+  [GSK_SL_TOKEN_SAMPLER2DMS] = "sampler2dms",
+  [GSK_SL_TOKEN_ISAMPLER2DMS] = "isampler2dms",
+  [GSK_SL_TOKEN_USAMPLER2DMS] = "usampler2dms",
+  [GSK_SL_TOKEN_SAMPLER2DMSARRAY] = "sampler2dmsarray",
+  [GSK_SL_TOKEN_ISAMPLER2DMSARRAY] = "isampler2dmsarray",
+  [GSK_SL_TOKEN_USAMPLER2DMSARRAY] = "usampler2dmsarray",
+  [GSK_SL_TOKEN_IMAGE1D] = "image1d",
+  [GSK_SL_TOKEN_IIMAGE1D] = "iimage1d",
+  [GSK_SL_TOKEN_UIMAGE1D] = "uimage1d",
+  [GSK_SL_TOKEN_IMAGE2D] = "image2d",
+  [GSK_SL_TOKEN_IIMAGE2D] = "iimage2d",
+  [GSK_SL_TOKEN_UIMAGE2D] = "uimage2d",
+  [GSK_SL_TOKEN_IMAGE3D] = "image3d",
+  [GSK_SL_TOKEN_IIMAGE3D] = "iimage3d",
+  [GSK_SL_TOKEN_UIMAGE3D] = "uimage3d",
+  [GSK_SL_TOKEN_IMAGE2DRECT] = "image2drect",
+  [GSK_SL_TOKEN_IIMAGE2DRECT] = "iimage2drect",
+  [GSK_SL_TOKEN_UIMAGE2DRECT] = "uimage2drect",
+  [GSK_SL_TOKEN_IMAGECUBE] = "imagecube",
+  [GSK_SL_TOKEN_IIMAGECUBE] = "iimagecube",
+  [GSK_SL_TOKEN_UIMAGECUBE] = "uimagecube",
+  [GSK_SL_TOKEN_IMAGEBUFFER] = "imagebuffer",
+  [GSK_SL_TOKEN_IIMAGEBUFFER] = "iimagebuffer",
+  [GSK_SL_TOKEN_UIMAGEBUFFER] = "uimagebuffer",
+  [GSK_SL_TOKEN_IMAGE1DARRAY] = "image1darray",
+  [GSK_SL_TOKEN_IIMAGE1DARRAY] = "iimage1darray",
+  [GSK_SL_TOKEN_UIMAGE1DARRAY] = "uimage1darray",
+  [GSK_SL_TOKEN_IMAGE2DARRAY] = "image2darray",
+  [GSK_SL_TOKEN_IIMAGE2DARRAY] = "iimage2darray",
+  [GSK_SL_TOKEN_UIMAGE2DARRAY] = "uimage2darray",
+  [GSK_SL_TOKEN_IMAGECUBEARRAY] = "imagecubearray",
+  [GSK_SL_TOKEN_IIMAGECUBEARRAY] = "iimagecubearray",
+  [GSK_SL_TOKEN_UIMAGECUBEARRAY] = "uimagecubearray",
+  [GSK_SL_TOKEN_IMAGE2DMS] = "image2dms",
+  [GSK_SL_TOKEN_IIMAGE2DMS] = "iimage2dms",
+  [GSK_SL_TOKEN_UIMAGE2DMS] = "uimage2dms",
+  [GSK_SL_TOKEN_IMAGE2DMSARRAY] = "image2dmsarray",
+  [GSK_SL_TOKEN_IIMAGE2DMSARRAY] = "iimage2dmsarray",
+  [GSK_SL_TOKEN_UIMAGE2DMSARRAY] = "uimage2dmsarray",
+  [GSK_SL_TOKEN_STRUCT] = "struct",
+  [GSK_SL_TOKEN_VOID] = "void",
+  [GSK_SL_TOKEN_WHILE] = "while",
+  [GSK_SL_TOKEN_INVARIANT] = "invariant",
+  [GSK_SL_TOKEN_PRECISE] = "precise",
+  [GSK_SL_TOKEN_HIGH_PRECISION] = "highp",
+  [GSK_SL_TOKEN_MEDIUM_PRECISION] = "mediump",
+  [GSK_SL_TOKEN_LOW_PRECISION] = "lowp",
+  [GSK_SL_TOKEN_PRECISION] = "precision"
+};
+
+void
+gsk_sl_token_print (const GskSlToken *token,
+                    GString          *string)
+{
+  char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+  switch (token->type)
+    {
+    case GSK_SL_TOKEN_EOF:
+    case GSK_SL_TOKEN_ERROR:
+    case GSK_SL_TOKEN_COMMENT:
+    case GSK_SL_TOKEN_SINGLE_LINE_COMMENT:
+      break;
+
+    case GSK_SL_TOKEN_WHITESPACE:
+      g_string_append (string, " ");
+      break;
+
+    case GSK_SL_TOKEN_FLOAT:
+    case GSK_SL_TOKEN_DOUBLE:
+    case GSK_SL_TOKEN_CONST:
+    case GSK_SL_TOKEN_BOOL:
+    case GSK_SL_TOKEN_INT:
+    case GSK_SL_TOKEN_UINT:
+    case GSK_SL_TOKEN_BREAK:
+    case GSK_SL_TOKEN_CONTINUE:
+    case GSK_SL_TOKEN_DO:
+    case GSK_SL_TOKEN_ELSE:
+    case GSK_SL_TOKEN_FOR:
+    case GSK_SL_TOKEN_IF:
+    case GSK_SL_TOKEN_DISCARD:
+    case GSK_SL_TOKEN_RETURN:
+    case GSK_SL_TOKEN_SWITCH:
+    case GSK_SL_TOKEN_CASE:
+    case GSK_SL_TOKEN_DEFAULT:
+    case GSK_SL_TOKEN_SUBROUTINE:
+    case GSK_SL_TOKEN_BVEC2:
+    case GSK_SL_TOKEN_BVEC3:
+    case GSK_SL_TOKEN_BVEC4:
+    case GSK_SL_TOKEN_IVEC2:
+    case GSK_SL_TOKEN_IVEC3:
+    case GSK_SL_TOKEN_IVEC4:
+    case GSK_SL_TOKEN_UVEC2:
+    case GSK_SL_TOKEN_UVEC3:
+    case GSK_SL_TOKEN_UVEC4:
+    case GSK_SL_TOKEN_VEC2:
+    case GSK_SL_TOKEN_VEC3:
+    case GSK_SL_TOKEN_VEC4:
+    case GSK_SL_TOKEN_MAT2:
+    case GSK_SL_TOKEN_MAT3:
+    case GSK_SL_TOKEN_MAT4:
+    case GSK_SL_TOKEN_CENTROID:
+    case GSK_SL_TOKEN_IN:
+    case GSK_SL_TOKEN_OUT:
+    case GSK_SL_TOKEN_INOUT:
+    case GSK_SL_TOKEN_UNIFORM:
+    case GSK_SL_TOKEN_PATCH:
+    case GSK_SL_TOKEN_SAMPLE:
+    case GSK_SL_TOKEN_BUFFER:
+    case GSK_SL_TOKEN_SHARED:
+    case GSK_SL_TOKEN_COHERENT:
+    case GSK_SL_TOKEN_VOLATILE:
+    case GSK_SL_TOKEN_RESTRICT:
+    case GSK_SL_TOKEN_READONLY:
+    case GSK_SL_TOKEN_WRITEONLY:
+    case GSK_SL_TOKEN_DVEC2:
+    case GSK_SL_TOKEN_DVEC3:
+    case GSK_SL_TOKEN_DVEC4:
+    case GSK_SL_TOKEN_DMAT2:
+    case GSK_SL_TOKEN_DMAT3:
+    case GSK_SL_TOKEN_DMAT4:
+    case GSK_SL_TOKEN_NOPERSPECTIVE:
+    case GSK_SL_TOKEN_FLAT:
+    case GSK_SL_TOKEN_SMOOTH:
+    case GSK_SL_TOKEN_LAYOUT:
+    case GSK_SL_TOKEN_MAT2X2:
+    case GSK_SL_TOKEN_MAT2X3:
+    case GSK_SL_TOKEN_MAT2X4:
+    case GSK_SL_TOKEN_MAT3X2:
+    case GSK_SL_TOKEN_MAT3X3:
+    case GSK_SL_TOKEN_MAT3X4:
+    case GSK_SL_TOKEN_MAT4X2:
+    case GSK_SL_TOKEN_MAT4X3:
+    case GSK_SL_TOKEN_MAT4X4:
+    case GSK_SL_TOKEN_DMAT2X2:
+    case GSK_SL_TOKEN_DMAT2X3:
+    case GSK_SL_TOKEN_DMAT2X4:
+    case GSK_SL_TOKEN_DMAT3X2:
+    case GSK_SL_TOKEN_DMAT3X3:
+    case GSK_SL_TOKEN_DMAT3X4:
+    case GSK_SL_TOKEN_DMAT4X2:
+    case GSK_SL_TOKEN_DMAT4X3:
+    case GSK_SL_TOKEN_DMAT4X4:
+    case GSK_SL_TOKEN_ATOMIC_UINT:
+    case GSK_SL_TOKEN_SAMPLER1D:
+    case GSK_SL_TOKEN_SAMPLER2D:
+    case GSK_SL_TOKEN_SAMPLER3D:
+    case GSK_SL_TOKEN_SAMPLERCUBE:
+    case GSK_SL_TOKEN_SAMPLER1DSHADOW:
+    case GSK_SL_TOKEN_SAMPLER2DSHADOW:
+    case GSK_SL_TOKEN_SAMPLERCUBESHADOW:
+    case GSK_SL_TOKEN_SAMPLER1DARRAY:
+    case GSK_SL_TOKEN_SAMPLER2DARRAY:
+    case GSK_SL_TOKEN_SAMPLER1DARRAYSHADOW:
+    case GSK_SL_TOKEN_SAMPLER2DARRAYSHADOW:
+    case GSK_SL_TOKEN_ISAMPLER1D:
+    case GSK_SL_TOKEN_ISAMPLER2D:
+    case GSK_SL_TOKEN_ISAMPLER3D:
+    case GSK_SL_TOKEN_ISAMPLERCUBE:
+    case GSK_SL_TOKEN_ISAMPLER1DARRAY:
+    case GSK_SL_TOKEN_ISAMPLER2DARRAY:
+    case GSK_SL_TOKEN_USAMPLER1D:
+    case GSK_SL_TOKEN_USAMPLER2D:
+    case GSK_SL_TOKEN_USAMPLER3D:
+    case GSK_SL_TOKEN_USAMPLERCUBE:
+    case GSK_SL_TOKEN_USAMPLER1DARRAY:
+    case GSK_SL_TOKEN_USAMPLER2DARRAY:
+    case GSK_SL_TOKEN_SAMPLER2DRECT:
+    case GSK_SL_TOKEN_SAMPLER2DRECTSHADOW:
+    case GSK_SL_TOKEN_ISAMPLER2DRECT:
+    case GSK_SL_TOKEN_USAMPLER2DRECT:
+    case GSK_SL_TOKEN_SAMPLERBUFFER:
+    case GSK_SL_TOKEN_ISAMPLERBUFFER:
+    case GSK_SL_TOKEN_USAMPLERBUFFER:
+    case GSK_SL_TOKEN_SAMPLERCUBEARRAY:
+    case GSK_SL_TOKEN_SAMPLERCUBEARRAYSHADOW:
+    case GSK_SL_TOKEN_ISAMPLERCUBEARRAY:
+    case GSK_SL_TOKEN_USAMPLERCUBEARRAY:
+    case GSK_SL_TOKEN_SAMPLER2DMS:
+    case GSK_SL_TOKEN_ISAMPLER2DMS:
+    case GSK_SL_TOKEN_USAMPLER2DMS:
+    case GSK_SL_TOKEN_SAMPLER2DMSARRAY:
+    case GSK_SL_TOKEN_ISAMPLER2DMSARRAY:
+    case GSK_SL_TOKEN_USAMPLER2DMSARRAY:
+    case GSK_SL_TOKEN_IMAGE1D:
+    case GSK_SL_TOKEN_IIMAGE1D:
+    case GSK_SL_TOKEN_UIMAGE1D:
+    case GSK_SL_TOKEN_IMAGE2D:
+    case GSK_SL_TOKEN_IIMAGE2D:
+    case GSK_SL_TOKEN_UIMAGE2D:
+    case GSK_SL_TOKEN_IMAGE3D:
+    case GSK_SL_TOKEN_IIMAGE3D:
+    case GSK_SL_TOKEN_UIMAGE3D:
+    case GSK_SL_TOKEN_IMAGE2DRECT:
+    case GSK_SL_TOKEN_IIMAGE2DRECT:
+    case GSK_SL_TOKEN_UIMAGE2DRECT:
+    case GSK_SL_TOKEN_IMAGECUBE:
+    case GSK_SL_TOKEN_IIMAGECUBE:
+    case GSK_SL_TOKEN_UIMAGECUBE:
+    case GSK_SL_TOKEN_IMAGEBUFFER:
+    case GSK_SL_TOKEN_IIMAGEBUFFER:
+    case GSK_SL_TOKEN_UIMAGEBUFFER:
+    case GSK_SL_TOKEN_IMAGE1DARRAY:
+    case GSK_SL_TOKEN_IIMAGE1DARRAY:
+    case GSK_SL_TOKEN_UIMAGE1DARRAY:
+    case GSK_SL_TOKEN_IMAGE2DARRAY:
+    case GSK_SL_TOKEN_IIMAGE2DARRAY:
+    case GSK_SL_TOKEN_UIMAGE2DARRAY:
+    case GSK_SL_TOKEN_IMAGECUBEARRAY:
+    case GSK_SL_TOKEN_IIMAGECUBEARRAY:
+    case GSK_SL_TOKEN_UIMAGECUBEARRAY:
+    case GSK_SL_TOKEN_IMAGE2DMS:
+    case GSK_SL_TOKEN_IIMAGE2DMS:
+    case GSK_SL_TOKEN_UIMAGE2DMS:
+    case GSK_SL_TOKEN_IMAGE2DMSARRAY:
+    case GSK_SL_TOKEN_IIMAGE2DMSARRAY:
+    case GSK_SL_TOKEN_UIMAGE2DMSARRAY:
+    case GSK_SL_TOKEN_STRUCT:
+    case GSK_SL_TOKEN_VOID:
+    case GSK_SL_TOKEN_WHILE:
+    case GSK_SL_TOKEN_INVARIANT:
+    case GSK_SL_TOKEN_PRECISE:
+    case GSK_SL_TOKEN_HIGH_PRECISION:
+    case GSK_SL_TOKEN_MEDIUM_PRECISION:
+    case GSK_SL_TOKEN_LOW_PRECISION:
+    case GSK_SL_TOKEN_PRECISION:
+      g_assert (keywords[token->type] != NULL);
+      g_string_append (string, keywords[token->type]);
+      break;
+
+    case GSK_SL_TOKEN_IDENTIFIER:
+      g_string_append (string, token->str);
+      break;
+
+    case GSK_SL_TOKEN_FLOATCONSTANT:
+      g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->f);
+      g_string_append (string, buf);
+      if (strchr (buf, '.') == NULL)
+        g_string_append (string, ".0");
+      g_string_append (string, "f");
+      break;
+
+    case GSK_SL_TOKEN_DOUBLECONSTANT:
+      g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->d);
+      g_string_append (string, buf);
+      if (strchr (buf, '.') == NULL)
+        g_string_append (string, ".0");
+      break;
+
+    case GSK_SL_TOKEN_INTCONSTANT:
+      g_string_append_printf (string, "%" G_GINT32_FORMAT, token->i32);
+      break;
+
+    case GSK_SL_TOKEN_UINTCONSTANT:
+      g_string_append_printf (string, "%" G_GUINT32_FORMAT"u", token->u32);
+      break;
+
+    case GSK_SL_TOKEN_BOOLCONSTANT:
+      g_string_append (string, token->b ? "true" : "false");
+      break;
+
+    case GSK_SL_TOKEN_LEFT_OP:
+      g_string_append (string, "<<");
+      break;
+
+    case GSK_SL_TOKEN_RIGHT_OP:
+      g_string_append (string, ">>");
+      break;
+
+    case GSK_SL_TOKEN_INC_OP:
+      g_string_append (string, "++");
+      break;
+
+    case GSK_SL_TOKEN_DEC_OP:
+      g_string_append (string, "--");
+      break;
+
+    case GSK_SL_TOKEN_LE_OP:
+      g_string_append (string, "<=");
+      break;
+
+    case GSK_SL_TOKEN_GE_OP:
+      g_string_append (string, ">=");
+      break;
+
+    case GSK_SL_TOKEN_EQ_OP:
+      g_string_append (string, "==");
+      break;
+
+    case GSK_SL_TOKEN_NE_OP:
+      g_string_append (string, "!=");
+      break;
+
+    case GSK_SL_TOKEN_AND_OP:
+      g_string_append (string, "&&");
+      break;
+
+    case GSK_SL_TOKEN_OR_OP:
+      g_string_append (string, "||");
+      break;
+
+    case GSK_SL_TOKEN_XOR_OP:
+      g_string_append (string, "^^");
+      break;
+
+    case GSK_SL_TOKEN_MUL_ASSIGN:
+      g_string_append (string, "*=");
+      break;
+
+    case GSK_SL_TOKEN_DIV_ASSIGN:
+      g_string_append (string, "/=");
+      break;
+
+    case GSK_SL_TOKEN_ADD_ASSIGN:
+      g_string_append (string, "+=");
+      break;
+
+    case GSK_SL_TOKEN_MOD_ASSIGN:
+      g_string_append (string, "%=");
+      break;
+
+    case GSK_SL_TOKEN_LEFT_ASSIGN:
+      g_string_append (string, "<<=");
+      break;
+
+    case GSK_SL_TOKEN_RIGHT_ASSIGN:
+      g_string_append (string, ">>=");
+      break;
+
+    case GSK_SL_TOKEN_AND_ASSIGN:
+      g_string_append (string, "&=");
+      break;
+
+    case GSK_SL_TOKEN_XOR_ASSIGN:
+      g_string_append (string, "^=");
+      break;
+
+    case GSK_SL_TOKEN_OR_ASSIGN:
+      g_string_append (string, "|=");
+      break;
+
+    case GSK_SL_TOKEN_SUB_ASSIGN:
+      g_string_append (string, "-=");
+      break;
+
+    case GSK_SL_TOKEN_LEFT_PAREN:
+      g_string_append_c (string, '(');
+      break;
+
+    case GSK_SL_TOKEN_RIGHT_PAREN:
+      g_string_append_c (string, ')');
+      break;
+
+    case GSK_SL_TOKEN_LEFT_BRACKET:
+      g_string_append_c (string, '[');
+      break;
+
+    case GSK_SL_TOKEN_RIGHT_BRACKET:
+      g_string_append_c (string, ']');
+      break;
+
+    case GSK_SL_TOKEN_LEFT_BRACE:
+      g_string_append_c (string, '{');
+      break;
+
+    case GSK_SL_TOKEN_RIGHT_BRACE:
+      g_string_append_c (string, '}');
+      break;
+
+    case GSK_SL_TOKEN_DOT:
+      g_string_append_c (string, '.');
+      break;
+
+    case GSK_SL_TOKEN_COMMA:
+      g_string_append_c (string, ',');
+      break;
+
+    case GSK_SL_TOKEN_COLON:
+      g_string_append_c (string, ':');
+      break;
+
+    case GSK_SL_TOKEN_EQUAL:
+      g_string_append_c (string, '=');
+      break;
+
+    case GSK_SL_TOKEN_SEMICOLON:
+      g_string_append_c (string, ';');
+      break;
+
+    case GSK_SL_TOKEN_BANG:
+      g_string_append_c (string, '!');
+      break;
+
+    case GSK_SL_TOKEN_DASH:
+      g_string_append_c (string, '-');
+      break;
+
+    case GSK_SL_TOKEN_TILDE:
+      g_string_append_c (string, '~');
+      break;
+
+    case GSK_SL_TOKEN_PLUS:
+      g_string_append_c (string, '+');
+      break;
+
+    case GSK_SL_TOKEN_STAR:
+      g_string_append_c (string, '*');
+      break;
+
+    case GSK_SL_TOKEN_SLASH:
+      g_string_append_c (string, '/');
+      break;
+
+    case GSK_SL_TOKEN_PERCENT:
+      g_string_append_c (string, '%');
+      break;
+
+    case GSK_SL_TOKEN_LEFT_ANGLE:
+      g_string_append_c (string, '<');
+      break;
+
+    case GSK_SL_TOKEN_RIGHT_ANGLE:
+      g_string_append_c (string, '>');
+      break;
+
+    case GSK_SL_TOKEN_VERTICAL_BAR:
+      g_string_append_c (string, '|');
+      break;
+
+    case GSK_SL_TOKEN_CARET:
+      g_string_append_c (string, '^');
+      break;
+
+    case GSK_SL_TOKEN_AMPERSAND:
+      g_string_append_c (string, '&');
+      break;
+
+    case GSK_SL_TOKEN_QUESTION:
+      g_string_append_c (string, '?');
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+char *
+gsk_sl_token_to_string (const GskSlToken *token)
+{
+  GString *string;
+
+  string = g_string_new (NULL);
+  gsk_sl_token_print (token, string);
+  return g_string_free (string, FALSE);
+}
+
+static void
+gsk_sl_token_init (GskSlToken     *token,
+                   GskSlTokenType  type)
+{
+  token->type = type;
+}
+
+static void
+gsk_sl_token_init_float (GskSlToken     *token,
+                         GskSlTokenType  type,
+                         double          d)
+{
+  gsk_sl_token_init (token, type);
+
+  if (type == GSK_SL_TOKEN_FLOATCONSTANT)
+    {
+      token->f = d;
+    }
+  else if (type == GSK_SL_TOKEN_DOUBLECONSTANT)
+    {
+      token->d = d;
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gsk_sl_token_init_number (GskSlToken     *token,
+                          GskSlTokenType  type,
+                          guint64         number)
+{
+  gsk_sl_token_init (token, type);
+
+  if (type == GSK_SL_TOKEN_INTCONSTANT)
+    {
+      token->i32 = number;
+    }
+  else if (type == GSK_SL_TOKEN_UINTCONSTANT)
+    {
+      token->u32 = number;
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gsk_sl_token_reader_init (GskSlTokenReader *reader,
+                          GBytes           *bytes)
+{
+  reader->data = g_bytes_get_data (bytes, NULL);
+  reader->end = reader->data + g_bytes_get_size (bytes);
+
+  gsk_code_location_init (&reader->position);
+}
+
+static void
+gsk_sl_token_reader_init_copy (GskSlTokenReader       *reader,
+                               const GskSlTokenReader *source)
+{
+  *reader = *source;
+}
+
+static inline gsize
+gsk_sl_token_reader_remaining (const GskSlTokenReader *reader)
+{
+  return reader->end - reader->data;
+}
+
+static inline gboolean
+is_newline (char c)
+{
+  return c == '\n'
+      || c == '\r';
+}
+
+static gboolean
+is_identifier_start (char c)
+{
+   return g_ascii_isalpha (c)
+       || c == '_';
+}
+
+static gboolean
+is_identifier (char c)
+{
+  return is_identifier_start (c)
+      || g_ascii_isdigit (c);
+}
+
+static inline gboolean
+is_whitespace (char c)
+{
+  return c == ' '
+      || c == '\t'
+      || c == '\f'
+      || c == '\n'
+      || c == '\r';
+}
+
+static inline gsize
+gsk_sl_token_reader_forward (GskSlTokenReader *reader,
+                             gsize             n)
+{
+  gsize i, len;
+
+  i = 0;
+  len = reader->end - reader->data;
+
+  for (; n; n--)
+    {
+      /* Skip '\' + newline */
+      if (len - i > 1 &&
+          reader->data[i] == '\\' && 
+          is_newline (reader->data[i + 1]))
+      {
+        i += 2;
+        if (len - i > 0 &&
+            is_newline (reader->data[i]) &&
+            reader->data[i] != reader->data[i - 1])
+          i++;
+      }
+
+      if (i >= len)
+        return i;
+
+      i++;
+    }
+
+  return i;
+}
+
+static inline char
+gsk_sl_token_reader_get (GskSlTokenReader *reader,
+                         gsize             n)
+{
+  gsize offset;
+  
+  offset = gsk_sl_token_reader_forward (reader, n);
+  
+  if (offset >= reader->end - reader->data)
+    return 0;
+
+  return reader->data[offset];
+}
+
+static inline void
+gsk_sl_token_reader_consume (GskSlTokenReader *reader,
+                             gsize             n)
+{
+  gsize i, offset;
+  
+  offset = gsk_sl_token_reader_forward (reader, n);
+
+  for (i = 0; i < offset; i++)
+    {
+      if (!is_newline (reader->data[i]))
+        {
+          gsk_code_location_advance (&reader->position, 1, 1);
+        }
+      else
+        {
+          if (reader->end - reader->data > i + 2 &&
+              is_newline (reader->data[i + 1]) && 
+              reader->data[i] != reader->data[i + 1])
+            gsk_code_location_advance_newline (&reader->position, 2);
+          else
+            gsk_code_location_advance_newline (&reader->position, 1);
+        }
+    }
+  
+  reader->data += offset;
+}
+
+GskSlTokenizer *
+gsk_sl_tokenizer_new (GBytes                  *bytes,
+                      GskSlTokenizerErrorFunc  func,
+                      gpointer                 user_data,
+                      GDestroyNotify           user_destroy)
+{
+  GskSlTokenizer *tokenizer;
+
+  tokenizer = g_slice_new0 (GskSlTokenizer);
+  tokenizer->ref_count = 1;
+  tokenizer->bytes = g_bytes_ref (bytes);
+  tokenizer->error_func = func;
+  tokenizer->user_data = user_data;
+  tokenizer->user_destroy = user_destroy;
+
+  gsk_sl_token_reader_init (&tokenizer->reader, bytes);
+
+  return tokenizer;
+}
+
+GskSlTokenizer *
+gsk_sl_tokenizer_ref (GskSlTokenizer *tokenizer)
+{
+  tokenizer->ref_count++;
+  
+  return tokenizer;
+}
+
+void
+gsk_sl_tokenizer_unref (GskSlTokenizer *tokenizer)
+{
+  tokenizer->ref_count--;
+  if (tokenizer->ref_count > 0)
+    return;
+
+  if (tokenizer->user_destroy)
+    tokenizer->user_destroy (tokenizer->user_data);
+
+  g_bytes_unref (tokenizer->bytes);
+  g_slice_free (GskSlTokenizer, tokenizer);
+}
+
+const GskCodeLocation *
+gsk_sl_tokenizer_get_location (GskSlTokenizer *tokenizer)
+{
+  return &tokenizer->reader.position;
+}
+
+static void
+set_parse_error (GError     **error,
+                 const char  *format,
+                 ...) G_GNUC_PRINTF(2, 3);
+static void
+set_parse_error (GError     **error,
+                 const char  *format,
+                 ...)
+{
+  va_list args;
+
+  if (error == NULL)
+    return;
+      
+  g_assert (*error == NULL);
+
+  va_start (args, format); 
+  *error = g_error_new_valist (G_FILE_ERROR,
+                               G_FILE_ERROR_FAILED,
+                               format,
+                               args);
+  va_end (args);
+}
+
+static void
+gsk_sl_tokenizer_emit_error (GskSlTokenizer        *tokenizer,
+                             const GskCodeLocation *location,
+                             const GskSlToken      *token,
+                             const GError          *error)
+{
+  if (tokenizer->error_func)
+    tokenizer->error_func (tokenizer, TRUE, location, token, error, tokenizer->user_data);
+  else
+    g_warning ("Unhandled GLSL error: %zu:%zu: %s", location->lines + 1, location->line_chars + 1, 
error->message);
+}
+
+static void
+gsk_sl_token_reader_read_multi_line_comment (GskSlTokenReader  *reader,
+                                             GskSlToken        *token,
+                                             GError           **error)
+{
+  gsk_sl_token_reader_consume (reader, 2);
+
+  while (reader->data < reader->end)
+    {
+      if (gsk_sl_token_reader_get (reader, 0) == '*' &&
+          gsk_sl_token_reader_get (reader, 1) == '/')
+        {
+          gsk_sl_token_reader_consume (reader, 2);
+          gsk_sl_token_init (token, GSK_SL_TOKEN_COMMENT);
+          return;
+        }
+      gsk_sl_token_reader_consume (reader, 1);
+    }
+
+  gsk_sl_token_init (token, GSK_SL_TOKEN_COMMENT);
+  set_parse_error (error, "Unterminated comment at end of document.");
+}
+
+static void
+gsk_sl_token_reader_read_single_line_comment (GskSlTokenReader  *reader,
+                                              GskSlToken        *token,
+                                              GError           **error)
+{
+  char c;
+
+  gsk_sl_token_reader_consume (reader, 2);
+
+  for (c = gsk_sl_token_reader_get (reader, 0);
+       c != 0 && !is_newline (c);
+       c = gsk_sl_token_reader_get (reader, 0))
+    {
+      gsk_sl_token_reader_consume (reader, 1);
+    }
+
+  gsk_sl_token_init (token, GSK_SL_TOKEN_SINGLE_LINE_COMMENT);
+}
+
+static void
+gsk_sl_token_reader_read_whitespace (GskSlTokenReader  *reader,
+                                     GskSlToken        *token)
+{
+  do {
+    gsk_sl_token_reader_consume (reader, 1);
+  } while (is_whitespace (gsk_sl_token_reader_get (reader, 0)));
+
+  gsk_sl_token_init (token, GSK_SL_TOKEN_WHITESPACE);
+}
+
+static gboolean
+gsk_sl_token_reader_read_float_number (GskSlTokenReader  *reader,
+                                       GskSlToken        *token,
+                                       GError           **error)
+{
+  int exponent_sign = 0;
+  guint64 integer = 0;
+  gint64 fractional = 0, fractional_length = 1, exponent = 0;
+  gboolean is_int = TRUE, overflow = FALSE;
+  char c;
+  guint i;
+
+  for (i = 0; ; i++)
+    {
+      c = gsk_sl_token_reader_get (reader, i);
+
+      if (!g_ascii_isdigit (c))
+        break;
+      if (integer > G_MAXUINT64 / 10)
+        overflow = TRUE;
+
+      integer = 10 * integer + g_ascii_digit_value (c);
+    }
+
+  if (c == '.')
+    {
+      is_int = FALSE;
+
+      for (i = i + 1; ; i++)
+        {
+          c = gsk_sl_token_reader_get (reader, i);
+
+          if (!g_ascii_isdigit (c))
+            break;
+
+          if (fractional_length < G_MAXINT64 / 10)
+            {
+              fractional = 10 * fractional + g_ascii_digit_value (c);
+              fractional_length *= 10;
+            }
+        }
+    }
+
+  if (c == 'e' || c == 'E')
+    {
+      is_int = FALSE;
+
+      c = gsk_sl_token_reader_get (reader, i + 1);
+
+      if (c == '-')
+        {
+          exponent_sign = -1;
+          c = gsk_sl_token_reader_get (reader, i + 2);
+        }
+      else if (c == '+')
+        {
+          exponent_sign = 1;
+          c = gsk_sl_token_reader_get (reader, i + 2);
+        }
+
+      if (g_ascii_isdigit (c))
+        {
+          if (exponent_sign == 0)
+            {
+              i++;
+              exponent_sign = 1;
+            }
+          else
+            {
+              i += 2;
+            }
+
+          for (i = 0; ; i++)
+            {
+              c = gsk_sl_token_reader_get (reader, i);
+
+              if (!g_ascii_isdigit (c))
+                break;
+
+              exponent = 10 * exponent + g_ascii_digit_value (c);
+            }
+        }
+      else
+        {
+          c = gsk_sl_token_reader_get (reader, i);
+        }
+    }
+
+  gsk_sl_token_reader_consume (reader, i);
+
+  if (is_int)
+    {
+      if (integer > G_MAXUINT32)
+        overflow = TRUE;
+
+      if (c == 'U' || c == 'u')
+        {
+          gsk_sl_token_reader_consume (reader, 1);
+          gsk_sl_token_init_number (token, GSK_SL_TOKEN_UINTCONSTANT, integer);
+        }
+      else
+        {
+          gsk_sl_token_init_number (token, GSK_SL_TOKEN_INTCONSTANT, integer);
+        }
+
+      if (overflow)
+        {
+          set_parse_error (error, "Overflow in integer constant");
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+  else
+    {
+      double d = (integer + ((double) fractional / fractional_length)) * pow (10, exponent_sign * exponent);
+
+      if (c == 'f' || c == 'F')
+        {
+          gsk_sl_token_reader_consume (reader, 1);
+          gsk_sl_token_init_float (token, GSK_SL_TOKEN_FLOATCONSTANT, d);
+        }
+      else if ((c == 'l' && gsk_sl_token_reader_get (reader, 1) == 'f') ||
+               (c == 'L' && gsk_sl_token_reader_get (reader, 1) == 'F'))
+        {
+          gsk_sl_token_reader_consume (reader, 2);
+          gsk_sl_token_init_float (token, GSK_SL_TOKEN_DOUBLECONSTANT, d);
+        }
+      else
+        {
+          gsk_sl_token_init_float (token, GSK_SL_TOKEN_FLOATCONSTANT, d);
+        }
+
+      if (overflow)
+        {
+          set_parse_error (error, "Overflow in floating point constant");
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+}
+
+static gboolean
+gsk_sl_token_reader_read_hex_number (GskSlTokenReader  *reader,
+                                     GskSlToken        *token,
+                                     GError           **error)
+{
+  char c;
+  guint64 result = 0;
+  gboolean overflow = FALSE;
+
+  for (c = gsk_sl_token_reader_get (reader, 0);
+       g_ascii_isxdigit (c);
+       c = gsk_sl_token_reader_get (reader, 0))
+    {
+      if (result > G_MAXUINT32 / 16)
+        overflow = TRUE;
+
+      result = result * 16 + g_ascii_xdigit_value (c);
+      gsk_sl_token_reader_consume (reader, 1);
+    }
+
+  if (c == 'U' || c == 'u')
+    {
+      gsk_sl_token_reader_consume (reader, 1);
+      gsk_sl_token_init_number (token, GSK_SL_TOKEN_UINTCONSTANT, result);
+    }
+  else
+    {
+      gsk_sl_token_init_number (token, GSK_SL_TOKEN_INTCONSTANT, result);
+    }
+
+  if (overflow)
+    {
+      set_parse_error (error, "Overflow in integer constant");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gsk_sl_token_reader_read_octal_number (GskSlTokenReader  *reader,
+                                       GskSlToken        *token,
+                                       GError           **error)
+{
+  char c;
+  guint i;
+  guint64 result = 0;
+  gboolean overflow = FALSE;
+
+  for (i = 0; ; i++)
+    {
+      c = gsk_sl_token_reader_get (reader, i);
+      if (c < '0' || c > '7')
+        break;
+
+      if (result > G_MAXUINT32 / 8)
+        overflow = TRUE;
+
+      result = result * 8 + g_ascii_digit_value (c);
+    }
+
+  if (c == 'U' || c == 'u')
+    {
+      gsk_sl_token_reader_consume (reader, i + 1);
+      gsk_sl_token_init_number (token, GSK_SL_TOKEN_UINTCONSTANT, result);
+    }
+  else if (c == '.' || c == 'e' || c == 'f' || c == 'E' || c == 'F')
+    {
+      return gsk_sl_token_reader_read_float_number (reader, token, error);
+    }
+  else
+    {
+      gsk_sl_token_reader_consume (reader, i);
+      gsk_sl_token_init_number (token, GSK_SL_TOKEN_INTCONSTANT, result);
+    }
+
+  if (overflow)
+    {
+      set_parse_error (error, "Overflow in octal constant");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gsk_sl_token_reader_read_number (GskSlTokenReader  *reader,
+                                 GskSlToken        *token,
+                                 GError           **error)
+{
+  if (gsk_sl_token_reader_get (reader, 0) == '0')
+    {
+      char c = gsk_sl_token_reader_get (reader, 1);
+      if (c == 'x' || c == 'X')
+        {
+          if (!g_ascii_isxdigit (gsk_sl_token_reader_get (reader, 3)))
+            {
+              gsk_sl_token_init_number (token, GSK_SL_TOKEN_INTCONSTANT, 0);
+              gsk_sl_token_reader_consume (reader, 1);
+              return TRUE;
+            }
+          gsk_sl_token_reader_consume (reader, 2);
+          return gsk_sl_token_reader_read_hex_number (reader, token, error);
+        }
+      else
+        {
+          return gsk_sl_token_reader_read_octal_number (reader, token, error);
+        }
+    }
+
+  return gsk_sl_token_reader_read_float_number (reader, token, error);
+}
+
+static gboolean
+gsk_sl_token_reader_read_identifier (GskSlTokenReader  *reader,
+                                     GskSlToken        *token,
+                                     GError           **error)
+{
+  GString *string;
+  guint i;
+  char *ident;
+  char c;
+
+  string = g_string_new ("");
+
+  for (c = gsk_sl_token_reader_get (reader, 0);
+       is_identifier (c);
+       c = gsk_sl_token_reader_get (reader, 0))
+    {
+      g_string_append_c (string, c);
+      gsk_sl_token_reader_consume (reader, 1);
+    }
+
+  ident = g_string_free (string, FALSE);
+
+  if (g_str_equal (ident, "true"))
+    {
+      gsk_sl_token_init (token, GSK_SL_TOKEN_BOOLCONSTANT);
+      token->b = TRUE;
+      return TRUE;
+    }
+  else if (g_str_equal (ident, "false"))
+    {
+      gsk_sl_token_init (token, GSK_SL_TOKEN_BOOLCONSTANT);
+      token->b = FALSE;
+      return TRUE;
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (keywords); i++)
+    {
+      if (keywords[i] == NULL)
+        continue;
+
+      if (g_str_equal (ident, keywords[i]))
+        {
+          gsk_sl_token_init (token, i);
+          return TRUE;
+        }
+    }
+
+  gsk_sl_token_init (token, GSK_SL_TOKEN_IDENTIFIER);
+  token->str = ident;
+  
+  return TRUE;
+}
+
+void
+gsk_sl_tokenizer_read_token (GskSlTokenizer *tokenizer,
+                             GskSlToken     *token)
+{
+  GskSlTokenReader reader;
+  GError *error = NULL;
+  char c;
+
+  gsk_sl_token_reader_init_copy (&reader, &tokenizer->reader);
+  c = gsk_sl_token_reader_get (&reader, 0);
+
+  switch (c)
+    {
+    case 0:
+      gsk_sl_token_init (token, GSK_SL_TOKEN_EOF);
+      break;
+
+    case '\n':
+    case '\r':
+    case '\t':
+    case '\f':
+    case ' ':
+      gsk_sl_token_reader_read_whitespace (&reader, token);
+      break;
+
+    case '/':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '/':
+          gsk_sl_token_reader_read_single_line_comment (&reader, token, &error);
+          break;
+        case '*':
+          gsk_sl_token_reader_read_multi_line_comment (&reader, token, &error);
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_DIV_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_SLASH);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+    case '<':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '<':
+          if (gsk_sl_token_reader_get (&reader, 2) == '=')
+            {
+              gsk_sl_token_init (token, GSK_SL_TOKEN_LEFT_ASSIGN);
+              gsk_sl_token_reader_consume (&reader, 3);
+            }
+          else
+            {
+              gsk_sl_token_init (token, GSK_SL_TOKEN_LEFT_OP);
+              gsk_sl_token_reader_consume (&reader, 2);
+            }
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_LE_OP);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_LEFT_ANGLE);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+
+    case '>':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '>':
+          if (gsk_sl_token_reader_get (&reader, 2) == '=')
+            {
+              gsk_sl_token_init (token, GSK_SL_TOKEN_RIGHT_ASSIGN);
+              gsk_sl_token_reader_consume (&reader, 3);
+            }
+          else
+            {
+              gsk_sl_token_init (token, GSK_SL_TOKEN_RIGHT_OP);
+              gsk_sl_token_reader_consume (&reader, 2);
+            }
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_GE_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_RIGHT_ANGLE);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+    case '+':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '+':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_INC_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_ADD_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_PLUS);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+    case '-':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '-':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_DEC_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_SUB_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_DASH);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+    case '=':
+      if (gsk_sl_token_reader_get (&reader, 1) == '=')
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_EQ_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+        }
+      else
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_EQUAL);
+          gsk_sl_token_reader_consume (&reader, 1);
+        }
+      break;
+
+    case '!':
+      if (gsk_sl_token_reader_get (&reader, 1) == '=')
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_NE_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+        }
+      else
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_BANG);
+          gsk_sl_token_reader_consume (&reader, 1);
+        }
+      break;
+
+    case '&':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '&':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_AND_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_AND_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_AMPERSAND);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+    case '|':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '|':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_OR_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_OR_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_VERTICAL_BAR);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+    case '^':
+      switch (gsk_sl_token_reader_get (&reader, 1))
+        {
+        case '^':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_XOR_OP);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        case '=':
+          gsk_sl_token_init (token, GSK_SL_TOKEN_XOR_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 2);
+          break;
+        default:
+          gsk_sl_token_init (token, GSK_SL_TOKEN_CARET);
+          gsk_sl_token_reader_consume (&reader, 1);
+          break;
+        }
+      break;
+
+    case '*':
+      if (gsk_sl_token_reader_get (&reader, 1) == '=')
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_MUL_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 2);
+        }
+      else
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_STAR);
+          gsk_sl_token_reader_consume (&reader, 1);
+        }
+      break;
+
+    case '%':
+      if (gsk_sl_token_reader_get (&reader, 1) == '=')
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_MOD_ASSIGN);
+          gsk_sl_token_reader_consume (&reader, 2);
+        }
+      else
+        {
+          gsk_sl_token_init (token, GSK_SL_TOKEN_PERCENT);
+          gsk_sl_token_reader_consume (&reader, 1);
+        }
+      break;
+
+    case '(':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_LEFT_PAREN);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case ')':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_RIGHT_PAREN);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case '[':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_LEFT_BRACKET);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case ']':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_RIGHT_BRACKET);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case '{':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_LEFT_BRACE);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case '}':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_RIGHT_BRACE);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case '.':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_DOT);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case ':':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_COLON);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case ';':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_SEMICOLON);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case '~':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_TILDE);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case '?':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_QUESTION);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    case ',':
+      gsk_sl_token_init (token, GSK_SL_TOKEN_COMMA);
+      gsk_sl_token_reader_consume (&reader, 1);
+      break;
+
+    default:
+      if (g_ascii_isdigit (c))
+        {
+          gsk_sl_token_reader_read_number (&reader, token, &error);
+        }
+      else if (is_identifier_start (c))
+        {
+          gsk_sl_token_reader_read_identifier (&reader, token, &error);
+        }
+      else
+        {
+          set_parse_error (&error, "Unknown character 0x%X", gsk_sl_token_reader_get (&reader, 0));
+          gsk_sl_token_init (token, GSK_SL_TOKEN_ERROR);
+          gsk_sl_token_reader_consume (&reader, 1);
+        }
+      break;
+    }
+
+  if (error != NULL)
+    {
+      GskCodeLocation error_location;
+
+      gsk_code_location_init_copy (&error_location, &reader.position);
+      gsk_sl_token_reader_init_copy (&tokenizer->reader, &reader);
+      gsk_sl_tokenizer_emit_error (tokenizer, &error_location, token, error);
+      g_error_free (error);
+    }
+  else
+    {
+      gsk_sl_token_reader_init_copy (&tokenizer->reader, &reader);
+    }
+}
+
diff --git a/gsk/gsksltokenizerprivate.h b/gsk/gsksltokenizerprivate.h
new file mode 100644
index 0000000..ea769fc
--- /dev/null
+++ b/gsk/gsksltokenizerprivate.h
@@ -0,0 +1,293 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_SL_TOKENIZER_PRIVATE_H__
+#define __GSK_SL_TOKENIZER_PRIVATE_H__
+
+#include <gsktypes.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+  GSK_SL_TOKEN_EOF = 0,
+  GSK_SL_TOKEN_ERROR,
+  GSK_SL_TOKEN_WHITESPACE,
+  GSK_SL_TOKEN_COMMENT,
+  GSK_SL_TOKEN_SINGLE_LINE_COMMENT,
+  /* real tokens */
+  GSK_SL_TOKEN_CONST,
+  GSK_SL_TOKEN_BOOL,
+  GSK_SL_TOKEN_FLOAT,
+  GSK_SL_TOKEN_DOUBLE,
+  GSK_SL_TOKEN_INT,
+  GSK_SL_TOKEN_UINT,
+  GSK_SL_TOKEN_BREAK,
+  GSK_SL_TOKEN_CONTINUE,
+  GSK_SL_TOKEN_DO,
+  GSK_SL_TOKEN_ELSE,
+  GSK_SL_TOKEN_FOR,
+  GSK_SL_TOKEN_IF,
+  GSK_SL_TOKEN_DISCARD,
+  GSK_SL_TOKEN_RETURN,
+  GSK_SL_TOKEN_SWITCH,
+  GSK_SL_TOKEN_CASE,
+  GSK_SL_TOKEN_DEFAULT,
+  GSK_SL_TOKEN_SUBROUTINE,
+  GSK_SL_TOKEN_BVEC2,
+  GSK_SL_TOKEN_BVEC3,
+  GSK_SL_TOKEN_BVEC4,
+  GSK_SL_TOKEN_IVEC2,
+  GSK_SL_TOKEN_IVEC3,
+  GSK_SL_TOKEN_IVEC4,
+  GSK_SL_TOKEN_UVEC2,
+  GSK_SL_TOKEN_UVEC3,
+  GSK_SL_TOKEN_UVEC4,
+  GSK_SL_TOKEN_VEC2,
+  GSK_SL_TOKEN_VEC3,
+  GSK_SL_TOKEN_VEC4,
+  GSK_SL_TOKEN_MAT2,
+  GSK_SL_TOKEN_MAT3,
+  GSK_SL_TOKEN_MAT4,
+  GSK_SL_TOKEN_CENTROID,
+  GSK_SL_TOKEN_IN,
+  GSK_SL_TOKEN_OUT,
+  GSK_SL_TOKEN_INOUT,
+  GSK_SL_TOKEN_UNIFORM,
+  GSK_SL_TOKEN_PATCH,
+  GSK_SL_TOKEN_SAMPLE,
+  GSK_SL_TOKEN_BUFFER,
+  GSK_SL_TOKEN_SHARED,
+  GSK_SL_TOKEN_COHERENT,
+  GSK_SL_TOKEN_VOLATILE,
+  GSK_SL_TOKEN_RESTRICT,
+  GSK_SL_TOKEN_READONLY,
+  GSK_SL_TOKEN_WRITEONLY,
+  GSK_SL_TOKEN_DVEC2,
+  GSK_SL_TOKEN_DVEC3,
+  GSK_SL_TOKEN_DVEC4,
+  GSK_SL_TOKEN_DMAT2,
+  GSK_SL_TOKEN_DMAT3,
+  GSK_SL_TOKEN_DMAT4,
+  GSK_SL_TOKEN_NOPERSPECTIVE,
+  GSK_SL_TOKEN_FLAT,
+  GSK_SL_TOKEN_SMOOTH,
+  GSK_SL_TOKEN_LAYOUT,
+  GSK_SL_TOKEN_MAT2X2,
+  GSK_SL_TOKEN_MAT2X3,
+  GSK_SL_TOKEN_MAT2X4,
+  GSK_SL_TOKEN_MAT3X2,
+  GSK_SL_TOKEN_MAT3X3,
+  GSK_SL_TOKEN_MAT3X4,
+  GSK_SL_TOKEN_MAT4X2,
+  GSK_SL_TOKEN_MAT4X3,
+  GSK_SL_TOKEN_MAT4X4,
+  GSK_SL_TOKEN_DMAT2X2,
+  GSK_SL_TOKEN_DMAT2X3,
+  GSK_SL_TOKEN_DMAT2X4,
+  GSK_SL_TOKEN_DMAT3X2,
+  GSK_SL_TOKEN_DMAT3X3,
+  GSK_SL_TOKEN_DMAT3X4,
+  GSK_SL_TOKEN_DMAT4X2,
+  GSK_SL_TOKEN_DMAT4X3,
+  GSK_SL_TOKEN_DMAT4X4,
+  GSK_SL_TOKEN_ATOMIC_UINT,
+  GSK_SL_TOKEN_SAMPLER1D,
+  GSK_SL_TOKEN_SAMPLER2D,
+  GSK_SL_TOKEN_SAMPLER3D,
+  GSK_SL_TOKEN_SAMPLERCUBE,
+  GSK_SL_TOKEN_SAMPLER1DSHADOW,
+  GSK_SL_TOKEN_SAMPLER2DSHADOW,
+  GSK_SL_TOKEN_SAMPLERCUBESHADOW,
+  GSK_SL_TOKEN_SAMPLER1DARRAY,
+  GSK_SL_TOKEN_SAMPLER2DARRAY,
+  GSK_SL_TOKEN_SAMPLER1DARRAYSHADOW,
+  GSK_SL_TOKEN_SAMPLER2DARRAYSHADOW,
+  GSK_SL_TOKEN_ISAMPLER1D,
+  GSK_SL_TOKEN_ISAMPLER2D,
+  GSK_SL_TOKEN_ISAMPLER3D,
+  GSK_SL_TOKEN_ISAMPLERCUBE,
+  GSK_SL_TOKEN_ISAMPLER1DARRAY,
+  GSK_SL_TOKEN_ISAMPLER2DARRAY,
+  GSK_SL_TOKEN_USAMPLER1D,
+  GSK_SL_TOKEN_USAMPLER2D,
+  GSK_SL_TOKEN_USAMPLER3D,
+  GSK_SL_TOKEN_USAMPLERCUBE,
+  GSK_SL_TOKEN_USAMPLER1DARRAY,
+  GSK_SL_TOKEN_USAMPLER2DARRAY,
+  GSK_SL_TOKEN_SAMPLER2DRECT,
+  GSK_SL_TOKEN_SAMPLER2DRECTSHADOW,
+  GSK_SL_TOKEN_ISAMPLER2DRECT,
+  GSK_SL_TOKEN_USAMPLER2DRECT,
+  GSK_SL_TOKEN_SAMPLERBUFFER,
+  GSK_SL_TOKEN_ISAMPLERBUFFER,
+  GSK_SL_TOKEN_USAMPLERBUFFER,
+  GSK_SL_TOKEN_SAMPLERCUBEARRAY,
+  GSK_SL_TOKEN_SAMPLERCUBEARRAYSHADOW,
+  GSK_SL_TOKEN_ISAMPLERCUBEARRAY,
+  GSK_SL_TOKEN_USAMPLERCUBEARRAY,
+  GSK_SL_TOKEN_SAMPLER2DMS,
+  GSK_SL_TOKEN_ISAMPLER2DMS,
+  GSK_SL_TOKEN_USAMPLER2DMS,
+  GSK_SL_TOKEN_SAMPLER2DMSARRAY,
+  GSK_SL_TOKEN_ISAMPLER2DMSARRAY,
+  GSK_SL_TOKEN_USAMPLER2DMSARRAY,
+  GSK_SL_TOKEN_IMAGE1D,
+  GSK_SL_TOKEN_IIMAGE1D,
+  GSK_SL_TOKEN_UIMAGE1D,
+  GSK_SL_TOKEN_IMAGE2D,
+  GSK_SL_TOKEN_IIMAGE2D,
+  GSK_SL_TOKEN_UIMAGE2D,
+  GSK_SL_TOKEN_IMAGE3D,
+  GSK_SL_TOKEN_IIMAGE3D,
+  GSK_SL_TOKEN_UIMAGE3D,
+  GSK_SL_TOKEN_IMAGE2DRECT,
+  GSK_SL_TOKEN_IIMAGE2DRECT,
+  GSK_SL_TOKEN_UIMAGE2DRECT,
+  GSK_SL_TOKEN_IMAGECUBE,
+  GSK_SL_TOKEN_IIMAGECUBE,
+  GSK_SL_TOKEN_UIMAGECUBE,
+  GSK_SL_TOKEN_IMAGEBUFFER,
+  GSK_SL_TOKEN_IIMAGEBUFFER,
+  GSK_SL_TOKEN_UIMAGEBUFFER,
+  GSK_SL_TOKEN_IMAGE1DARRAY,
+  GSK_SL_TOKEN_IIMAGE1DARRAY,
+  GSK_SL_TOKEN_UIMAGE1DARRAY,
+  GSK_SL_TOKEN_IMAGE2DARRAY,
+  GSK_SL_TOKEN_IIMAGE2DARRAY,
+  GSK_SL_TOKEN_UIMAGE2DARRAY,
+  GSK_SL_TOKEN_IMAGECUBEARRAY,
+  GSK_SL_TOKEN_IIMAGECUBEARRAY,
+  GSK_SL_TOKEN_UIMAGECUBEARRAY,
+  GSK_SL_TOKEN_IMAGE2DMS,
+  GSK_SL_TOKEN_IIMAGE2DMS,
+  GSK_SL_TOKEN_UIMAGE2DMS,
+  GSK_SL_TOKEN_IMAGE2DMSARRAY,
+  GSK_SL_TOKEN_IIMAGE2DMSARRAY,
+  GSK_SL_TOKEN_UIMAGE2DMSARRAY,
+  GSK_SL_TOKEN_STRUCT,
+  GSK_SL_TOKEN_VOID,
+  GSK_SL_TOKEN_WHILE,
+  GSK_SL_TOKEN_IDENTIFIER,
+  GSK_SL_TOKEN_FLOATCONSTANT,
+  GSK_SL_TOKEN_DOUBLECONSTANT,
+  GSK_SL_TOKEN_INTCONSTANT,
+  GSK_SL_TOKEN_UINTCONSTANT,
+  GSK_SL_TOKEN_BOOLCONSTANT,
+  GSK_SL_TOKEN_LEFT_OP,
+  GSK_SL_TOKEN_RIGHT_OP,
+  GSK_SL_TOKEN_INC_OP,
+  GSK_SL_TOKEN_DEC_OP,
+  GSK_SL_TOKEN_LE_OP,
+  GSK_SL_TOKEN_GE_OP,
+  GSK_SL_TOKEN_EQ_OP,
+  GSK_SL_TOKEN_NE_OP,
+  GSK_SL_TOKEN_AND_OP,
+  GSK_SL_TOKEN_OR_OP,
+  GSK_SL_TOKEN_XOR_OP,
+  GSK_SL_TOKEN_MUL_ASSIGN,
+  GSK_SL_TOKEN_DIV_ASSIGN,
+  GSK_SL_TOKEN_ADD_ASSIGN,
+  GSK_SL_TOKEN_MOD_ASSIGN,
+  GSK_SL_TOKEN_LEFT_ASSIGN,
+  GSK_SL_TOKEN_RIGHT_ASSIGN,
+  GSK_SL_TOKEN_AND_ASSIGN,
+  GSK_SL_TOKEN_XOR_ASSIGN,
+  GSK_SL_TOKEN_OR_ASSIGN,
+  GSK_SL_TOKEN_SUB_ASSIGN,
+  GSK_SL_TOKEN_LEFT_PAREN,
+  GSK_SL_TOKEN_RIGHT_PAREN,
+  GSK_SL_TOKEN_LEFT_BRACKET,
+  GSK_SL_TOKEN_RIGHT_BRACKET,
+  GSK_SL_TOKEN_LEFT_BRACE,
+  GSK_SL_TOKEN_RIGHT_BRACE,
+  GSK_SL_TOKEN_DOT,
+  GSK_SL_TOKEN_COMMA,
+  GSK_SL_TOKEN_COLON,
+  GSK_SL_TOKEN_EQUAL,
+  GSK_SL_TOKEN_SEMICOLON,
+  GSK_SL_TOKEN_BANG,
+  GSK_SL_TOKEN_DASH,
+  GSK_SL_TOKEN_TILDE,
+  GSK_SL_TOKEN_PLUS,
+  GSK_SL_TOKEN_STAR,
+  GSK_SL_TOKEN_SLASH,
+  GSK_SL_TOKEN_PERCENT,
+  GSK_SL_TOKEN_LEFT_ANGLE,
+  GSK_SL_TOKEN_RIGHT_ANGLE,
+  GSK_SL_TOKEN_VERTICAL_BAR,
+  GSK_SL_TOKEN_CARET,
+  GSK_SL_TOKEN_AMPERSAND,
+  GSK_SL_TOKEN_QUESTION,
+  GSK_SL_TOKEN_INVARIANT,
+  GSK_SL_TOKEN_PRECISE,
+  GSK_SL_TOKEN_HIGH_PRECISION,
+  GSK_SL_TOKEN_MEDIUM_PRECISION,
+  GSK_SL_TOKEN_LOW_PRECISION,
+  GSK_SL_TOKEN_PRECISION
+} GskSlTokenType;
+
+typedef struct _GskSlToken GskSlToken;
+typedef struct _GskSlTokenizer GskSlTokenizer;
+
+typedef void (* GskSlTokenizerErrorFunc) (GskSlTokenizer        *parser,
+                                          gboolean               fatal,
+                                          const GskCodeLocation *location,
+                                          const GskSlToken      *token,
+                                          const GError          *error,
+                                          gpointer               user_data);
+
+struct _GskSlToken {
+  GskSlTokenType type;
+  union {
+    gint32       i32;
+    guint32      u32;
+    float        f;
+    double       d;
+    gboolean     b;
+    char        *str;
+  };
+};
+
+void                    gsk_sl_token_clear                      (GskSlToken            *token);
+
+gboolean                gsk_sl_token_is_finite                  (const GskSlToken      *token);
+#define gsk_sl_token_is(token, _type) ((token)->type == (_type))
+gboolean                gsk_sl_token_is_ident                   (const GskSlToken      *token,
+                                                                 const char            *ident);
+gboolean                gsk_sl_token_is_function                (const GskSlToken      *token,
+                                                                 const char            *ident);
+
+void                    gsk_sl_token_print                      (const GskSlToken      *token,
+                                                                 GString               *string);
+char *                  gsk_sl_token_to_string                  (const GskSlToken      *token);
+
+GskSlTokenizer *        gsk_sl_tokenizer_new                    (GBytes                 *bytes,
+                                                                 GskSlTokenizerErrorFunc func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+
+GskSlTokenizer *        gsk_sl_tokenizer_ref                    (GskSlTokenizer        *tokenizer);
+void                    gsk_sl_tokenizer_unref                  (GskSlTokenizer        *tokenizer);
+
+const GskCodeLocation * gsk_sl_tokenizer_get_location           (GskSlTokenizer        *tokenizer);
+
+void                    gsk_sl_tokenizer_read_token             (GskSlTokenizer        *tokenizer,
+                                                                 GskSlToken            *token);
+
+G_END_DECLS
+
+#endif /* __GSK_SL_TOKENIZER_PRIVATE_H__ */
diff --git a/gsk/gsksltype.c b/gsk/gsksltype.c
new file mode 100644
index 0000000..8958a41
--- /dev/null
+++ b/gsk/gsksltype.c
@@ -0,0 +1,161 @@
+/* GTK - The GIMP Toolkit
+ *   
+ * Copyright © 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gsksltypeprivate.h"
+
+#include <string.h>
+
+struct _GskSlType
+{
+  int ref_count;
+
+  GskSlBuiltinType builtin;
+};
+
+static GskSlType
+builtin_types[GSK_SL_N_BUILTIN_TYPES] = {
+  [GSK_SL_VOID] = { 1, GSK_SL_VOID },
+  [GSK_SL_FLOAT] = { 1, GSK_SL_FLOAT },
+  [GSK_SL_DOUBLE] = { 1, GSK_SL_DOUBLE },
+  [GSK_SL_INT] = { 1, GSK_SL_INT },
+  [GSK_SL_UINT] = { 1, GSK_SL_UINT },
+  [GSK_SL_BOOL] = { 1, GSK_SL_BOOL },
+  [GSK_SL_VEC2] = { 1, GSK_SL_VEC2 },
+  [GSK_SL_VEC3] = { 1, GSK_SL_VEC3 },
+  [GSK_SL_VEC4] = { 1, GSK_SL_VEC4 }
+};
+
+GskSlType *
+gsk_sl_type_new_parse (GskSlPreprocessor *stream)
+{
+  GskSlBuiltinType builtin;
+  const GskSlToken *token;
+
+  token = gsk_sl_preprocessor_get (stream);
+
+  switch ((guint) token->type)
+  {
+    case GSK_SL_TOKEN_VOID:
+      builtin = GSK_SL_VOID;
+      break;
+    case GSK_SL_TOKEN_FLOAT:
+      builtin = GSK_SL_FLOAT;
+      break;
+    case GSK_SL_TOKEN_DOUBLE:
+      builtin = GSK_SL_DOUBLE;
+      break;
+    case GSK_SL_TOKEN_INT:
+      builtin = GSK_SL_INT;
+      break;
+    case GSK_SL_TOKEN_UINT:
+      builtin = GSK_SL_UINT;
+      break;
+    case GSK_SL_TOKEN_BOOL:
+      builtin = GSK_SL_BOOL;
+      break;
+    case GSK_SL_TOKEN_VEC2:
+      builtin = GSK_SL_VEC2;
+      break;
+    case GSK_SL_TOKEN_VEC3:
+      builtin = GSK_SL_VEC3;
+      break;
+    case GSK_SL_TOKEN_VEC4:
+      builtin = GSK_SL_VEC4;
+      break;
+    default:
+      gsk_sl_preprocessor_error (stream, "Expected type specifier");
+      return NULL;
+  }
+
+  gsk_sl_preprocessor_consume (stream, NULL);
+  return gsk_sl_type_ref (gsk_sl_type_get_builtin (builtin));
+}
+
+GskSlType *
+gsk_sl_type_get_builtin (GskSlBuiltinType builtin)
+{
+  g_assert (builtin < GSK_SL_N_BUILTIN_TYPES);
+
+  return &builtin_types[builtin];
+}
+
+GskSlType *
+gsk_sl_type_ref (GskSlType *type)
+{
+  g_return_val_if_fail (type != NULL, NULL);
+
+  type->ref_count += 1;
+
+  return type;
+}
+
+void
+gsk_sl_type_unref (GskSlType *type)
+{
+  if (type == NULL)
+    return;
+
+  type->ref_count -= 1;
+  if (type->ref_count > 0)
+    return;
+
+  g_assert_not_reached ();
+}
+
+void
+gsk_sl_type_print (const GskSlType *type,
+                   GString         *string)
+{
+  switch (type->builtin)
+  {
+    case GSK_SL_VOID:
+      g_string_append (string, "void");
+      break;
+    case GSK_SL_FLOAT:
+      g_string_append (string, "float");
+      break;
+    case GSK_SL_DOUBLE:
+      g_string_append (string, "double");
+      break;
+    case GSK_SL_INT:
+      g_string_append (string, "int");
+      break;
+    case GSK_SL_UINT:
+      g_string_append (string, "uint");
+      break;
+    case GSK_SL_BOOL:
+      g_string_append (string, "bool");
+      break;
+    case GSK_SL_VEC2:
+      g_string_append (string, "vec2");
+      break;
+    case GSK_SL_VEC3:
+      g_string_append (string, "vec3");
+      break;
+    case GSK_SL_VEC4:
+      g_string_append (string, "vec4");
+      break;
+    /* add more above */
+    case GSK_SL_N_BUILTIN_TYPES:
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+}
diff --git a/gsk/gsksltypeprivate.h b/gsk/gsksltypeprivate.h
new file mode 100644
index 0000000..25f10c5
--- /dev/null
+++ b/gsk/gsksltypeprivate.h
@@ -0,0 +1,55 @@
+/* GTK - The GIMP Toolkit
+ *
+ * Copyright © 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_SL_TYPE_PRIVATE_H__
+#define __GSK_SL_TYPE_PRIVATE_H__
+
+#include <glib.h>
+
+#include "gskslpreprocessorprivate.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+  GSK_SL_VOID,
+  GSK_SL_FLOAT,
+  GSK_SL_DOUBLE,
+  GSK_SL_INT,
+  GSK_SL_UINT,
+  GSK_SL_BOOL,
+  GSK_SL_VEC2,
+  GSK_SL_VEC3,
+  GSK_SL_VEC4,
+  /* add more above */
+  GSK_SL_N_BUILTIN_TYPES
+} GskSlBuiltinType;
+
+typedef struct _GskSlType GskSlType;
+
+GskSlType *             gsk_sl_type_new_parse                   (GskSlPreprocessor   *stream);
+GskSlType *             gsk_sl_type_get_builtin                 (GskSlBuiltinType     builtin);
+
+GskSlType *             gsk_sl_type_ref                         (GskSlType           *type);
+void                    gsk_sl_type_unref                       (GskSlType           *type);
+
+void                    gsk_sl_type_print                       (const GskSlType     *type,
+                                                                 GString             *string);
+
+G_END_DECLS
+
+#endif /* __GSK_SL_TYPE_PRIVATE_H__ */
diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h
index 32fab4c..ff958ab 100644
--- a/gsk/gsktypes.h
+++ b/gsk/gsktypes.h
@@ -26,7 +26,18 @@
 #include <gdk/gdk.h>
 #include <gsk/gskenums.h>
 
+typedef struct _GskCodeLocation         GskCodeLocation;
+typedef struct _GskPixelShader          GskPixelShader;
 typedef struct _GskRenderer             GskRenderer;
 typedef struct _GskTexture              GskTexture;
 
+struct _GskCodeLocation
+{
+  gsize                  bytes;
+  gsize                  chars;
+  gsize                  lines;
+  gsize                  line_bytes;
+  gsize                  line_chars;
+};
+
 #endif /* __GSK_TYPES_H__ */
diff --git a/gsk/meson.build b/gsk/meson.build
index a86e156..d9ecbef 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -14,6 +14,7 @@ gsk_private_source_shaders = [
 ]
 
 gsk_public_sources = files([
+  'gskpixelshader.c',
   'gskrenderer.c',
   'gskrendernode.c',
   'gskrendernodeimpl.c',
@@ -31,10 +32,15 @@ gsk_private_sources = files([
   'gskprivate.c',
   'gskprofiler.c',
   'gskshaderbuilder.c',
+  'gskslnode.c',
+  'gskslpreprocessor.c',
+  'gsksltokenizer.c',
+  'gsksltype.c'
 ])
 
 gsk_public_headers = files([
   'gskenums.h',
+  'gskpixelshader.h',
   'gskrenderer.h',
   'gskrendernode.h',
   'gskroundedrect.h',
diff --git a/gtk/inspector/init.c b/gtk/inspector/init.c
index f3d5738..b509906 100644
--- a/gtk/inspector/init.c
+++ b/gtk/inspector/init.c
@@ -60,6 +60,8 @@ gtk_inspector_init (void)
 
   g_type_ensure (G_TYPE_LIST_STORE);
 
+  g_type_ensure (GSK_TYPE_PIXEL_SHADER);
+
   g_type_ensure (GTK_TYPE_CELL_RENDERER_GRAPH);
   g_type_ensure (GTK_TYPE_GRAPH_DATA);
   g_type_ensure (GTK_TYPE_INSPECTOR_ACTIONS);
diff --git a/testsuite/gsksl/meson.build b/testsuite/gsksl/meson.build
new file mode 100644
index 0000000..6c206c4
--- /dev/null
+++ b/testsuite/gsksl/meson.build
@@ -0,0 +1,3 @@
+
+test_parser = executable('test-parser', 'test-parser.c', dependencies: libgtk_dep)
+test('gsksl/parser', test_parser)
diff --git a/testsuite/gsksl/test-parser.c b/testsuite/gsksl/test-parser.c
new file mode 100644
index 0000000..d35c063
--- /dev/null
+++ b/testsuite/gsksl/test-parser.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2017 Red Hat Inc.
+ *
+ * Author:
+ *      Benjamin Otte <otte redhat com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gstdio.h>
+#include <gsk/gsk.h>
+#include <gtk/gtk.h>
+
+#ifdef G_OS_WIN32
+# include <io.h>
+#endif
+
+static char *
+test_get_reference_file (const char *glsl_file)
+{
+  GString *file = g_string_new (NULL);
+
+  if (g_str_has_suffix (glsl_file, ".glsl"))
+    g_string_append_len (file, glsl_file, strlen (glsl_file) - 4);
+  else
+    g_string_append (file, glsl_file);
+  
+  g_string_append (file, ".ref.glsl");
+
+  if (!g_file_test (file->str, G_FILE_TEST_EXISTS))
+    {
+      g_string_free (file, TRUE);
+      return g_strdup (glsl_file);
+    }
+
+  return g_string_free (file, FALSE);
+}
+
+static char *
+test_get_errors_file (const char *glsl_file)
+{
+  GString *file = g_string_new (NULL);
+
+  if (g_str_has_suffix (glsl_file, ".glsl"))
+    g_string_append_len (file, glsl_file, strlen (glsl_file) - 4);
+  else
+    g_string_append (file, glsl_file);
+  
+  g_string_append (file, ".errors");
+
+  if (!g_file_test (file->str, G_FILE_TEST_EXISTS))
+    {
+      g_string_free (file, TRUE);
+      return NULL;
+    }
+
+  return g_string_free (file, FALSE);
+}
+
+static char *
+diff_with_file (const char  *file1,
+                char        *text,
+                gssize       len,
+                GError     **error)
+{
+  const char *command[] = { "diff", "-u", file1, NULL, NULL };
+  char *diff, *tmpfile;
+  int fd;
+
+  diff = NULL;
+
+  if (len < 0)
+    len = strlen (text);
+  
+  /* write the text buffer to a temporary file */
+  fd = g_file_open_tmp (NULL, &tmpfile, error);
+  if (fd < 0)
+    return NULL;
+
+  if (write (fd, text, len) != (int) len)
+    {
+      close (fd);
+      g_set_error (error,
+                   G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   "Could not write data to temporary file '%s'", tmpfile);
+      goto done;
+    }
+  close (fd);
+  command[3] = tmpfile;
+
+  /* run diff command */
+  g_spawn_sync (NULL, 
+                (char **) command,
+                NULL,
+                G_SPAWN_SEARCH_PATH,
+                NULL, NULL,
+               &diff,
+                NULL, NULL,
+                error);
+
+done:
+  g_unlink (tmpfile);
+  g_free (tmpfile);
+
+  return diff;
+}
+
+#if 0
+static void
+append_error_value (GString *string,
+                    GType    enum_type,
+                    guint    value)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+
+  enum_class = g_type_class_ref (enum_type);
+  enum_value = g_enum_get_value (enum_class, value);
+
+  g_string_append (string, enum_value->value_name);
+
+  g_type_class_unref (enum_class);
+}
+#endif
+
+static void
+parsing_error_cb (GskPixelShader        *shader,
+                  gboolean               fatal,
+                  const GskCodeLocation *location,
+                  const GError          *error,
+                  gpointer               user_data)
+{
+  GString *errors = user_data;
+
+  if (fatal)
+    g_test_fail ();
+
+  g_string_append_printf (errors,
+                          "%zu:%zu: %s: ",
+                          location->lines, location->line_chars,
+                          fatal ? "ERROR" : "warning");
+
+  g_string_append_printf (errors, 
+                          "%s\n",
+                          error->message);
+}
+
+static void
+parse_glsl_file (GFile *file, gboolean generate)
+{
+  GskPixelShader *shader;
+  GBytes *bytes;
+  char *glsl, *diff;
+  char *glsl_file, *reference_file, *errors_file;
+  gsize length;
+  GString *errors;
+  GError *error = NULL;
+
+  glsl_file = g_file_get_path (file);
+  errors = g_string_new ("");
+
+  g_file_load_contents (file, NULL,
+                        &glsl, &length,
+                        NULL, &error);
+  g_assert_no_error (error);
+  bytes = g_bytes_new_take (glsl, length);
+  
+  shader = gsk_pixel_shader_new_for_data (bytes,
+                                          parsing_error_cb,
+                                          errors);
+
+  glsl = gsk_pixel_shader_to_string (shader);
+
+  if (generate)
+    {
+      g_print ("%s", glsl);
+      goto out;
+    }
+
+  reference_file = test_get_reference_file (glsl_file);
+
+  diff = diff_with_file (reference_file, glsl, -1, &error);
+  g_assert_no_error (error);
+
+  if (diff && diff[0])
+    {
+      g_test_message ("Resulting CSS doesn't match reference:\n%s", diff);
+      g_test_fail ();
+    }
+  g_free (reference_file);
+
+  errors_file = test_get_errors_file (glsl_file);
+
+  if (errors_file)
+    {
+      diff = diff_with_file (errors_file, errors->str, errors->len, &error);
+      g_assert_no_error (error);
+
+      if (diff && diff[0])
+        {
+          g_test_message ("Errors don't match expected errors:\n%s", diff);
+          g_test_fail ();
+        }
+    }
+  else if (errors->str[0])
+    {
+      g_test_message ("Unexpected errors:\n%s", errors->str);
+      g_test_fail ();
+    }
+
+  g_object_unref (shader);
+  g_free (errors_file);
+  g_string_free (errors, TRUE);
+
+  g_free (diff);
+
+out:
+  g_free (glsl_file);
+  g_free (glsl);
+}
+
+static void
+test_glsl_file (GFile *file)
+{
+  parse_glsl_file (file, FALSE);
+}
+
+static void
+add_test_for_file (GFile *file)
+{
+  char *path;
+
+  path = g_file_get_path (file);
+
+  g_test_add_vtable (path,
+                     0,
+                     g_object_ref (file),
+                     NULL,
+                     (GTestFixtureFunc) test_glsl_file,
+                     (GTestFixtureFunc) g_object_unref);
+
+  g_free (path);
+}
+
+static int
+compare_files (gconstpointer a, gconstpointer b)
+{
+  GFile *file1 = G_FILE (a);
+  GFile *file2 = G_FILE (b);
+  char *path1, *path2;
+  int result;
+
+  path1 = g_file_get_path (file1);
+  path2 = g_file_get_path (file2);
+
+  result = strcmp (path1, path2);
+
+  g_free (path1);
+  g_free (path2);
+
+  return result;
+}
+
+static void
+add_tests_for_files_in_directory (GFile *dir)
+{
+  GFileEnumerator *enumerator;
+  GFileInfo *info;
+  GList *files;
+  GError *error = NULL;
+
+  enumerator = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error);
+  g_assert_no_error (error);
+  files = NULL;
+
+  while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)))
+    {
+      const char *filename;
+
+      filename = g_file_info_get_name (info);
+
+      if (!g_str_has_suffix (filename, ".glsl") ||
+          g_str_has_suffix (filename, ".out.glsl") ||
+          g_str_has_suffix (filename, ".ref.glsl"))
+        {
+          g_object_unref (info);
+          continue;
+        }
+
+      files = g_list_prepend (files, g_file_get_child (dir, filename));
+
+      g_object_unref (info);
+    }
+  
+  g_assert_no_error (error);
+  g_object_unref (enumerator);
+
+  files = g_list_sort (files, compare_files);
+  g_list_foreach (files, (GFunc) add_test_for_file, NULL);
+  g_list_free_full (files, g_object_unref);
+}
+
+int
+main (int argc, char **argv)
+{
+  gtk_test_init (&argc, &argv);
+
+  if (argc < 2)
+    {
+      const char *basedir;
+      GFile *dir;
+
+      basedir = g_test_get_dir (G_TEST_DIST);
+      dir = g_file_new_for_path (basedir);
+      add_tests_for_files_in_directory (dir);
+
+      g_object_unref (dir);
+    }
+  else if (strcmp (argv[1], "--generate") == 0)
+    {
+      if (argc >= 3)
+        {
+          GFile *file = g_file_new_for_commandline_arg (argv[2]);
+
+          parse_glsl_file (file, TRUE);
+
+          g_object_unref (file);
+        }
+    }
+  else
+    {
+      guint i;
+
+      for (i = 1; i < argc; i++)
+        {
+          GFile *file = g_file_new_for_commandline_arg (argv[i]);
+
+          add_test_for_file (file);
+
+          g_object_unref (file);
+        }
+    }
+
+  return g_test_run ();
+}
+
diff --git a/testsuite/meson.build b/testsuite/meson.build
index f9b97da..d556255 100644
--- a/testsuite/meson.build
+++ b/testsuite/meson.build
@@ -18,3 +18,4 @@ subdir('gdk')
 subdir('css')
 subdir('a11y')
 subdir('reftests')
+subdir('gsksl')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]