Chris X Edwards

If instead of destroying the internet, Google search had steadily improved since 2006, it would by now be exactly as helpful as modern AI.
2026-09-04 07:42
For me AI's productivity perfectly offsets its oversight needs; now my efforts accomplish the same but I'm given less credit.
2026-08-27 09:13
While no means no, sometimes nay means yes, like when speaking Greek. Rejecting near universally honored PIE heritage Ναι means Yes.
2026-08-19 17:44
Is pointer arithmetic really more complicated than picking through DOM elements? At least the CPU agrees with me that it's not.
2026-08-11 14:50
How seriously should we take AI hype? My score card is still 100 spam emails a day and zero compelling in game NPCs. Any day now!
2026-08-05 07:18
Blah Blah
--------------------------

Blender Measurement Fixed

2026-09-02 14:09

Blender is an amazing piece of software and I love it but there are some things which just drive me crazy! Usually the deficiency is actually mine. Once I finally learn some esoteric trick it does turn out that Blender made sense all along and I was just ignorant. My biggest complaint, however, does not involve my clumsiness. I know this because over the last 10 years that I’ve been studying Blender, it has improved dramatically in this area — and it’s still terrible!

The functionality I’m highlighting today is simple measurement. I’m not asking Blender to do the math; no, I know it has already done the math and I just want it to tell me what the answer is! If I put together an excruciatingly careful model I may have questions like, how big of a box would I need to contain this whole model? Will my canoe fit in through the doorway? If this bike seat is correctly modeled with my whole bike, how high off the ground is it? Stuff like that.

The cultural disconnect here is that while this is probably the most important function for a CAD system, for a 3d art platform it is much lower on the list of priorities. Blender artists would be inclined to just model a box to see how big one needs to be to fit a model. They would just make a quick doorway and see if the canoe looked like it fit. It’s good to be able to do that when it confers advantages, but sometimes you just need to know how high the bike saddle is off the ground without modeling a square bar also that high for reference. It is embarrassing to both me and Blender how many times I’ve had to make such reference objects just to find out how big something is.

Blender actually has several ways to get distance information out of a model. All of them have problems.

For starters, you can turn on Mesh Edit Mode Overlays so that edges are numerically annotated with how long they are. That is dynamic and pretty hands off, but the setup is fussy and I find it only solves about 5% of real measurement questions. The precision is seemingly random. And look how crazy tiny these numbers are — no way to control it that I know.

Mesh Edit Mode Overlays

This edge marking can, in theory, help you find errors like the one where my boolean cutting operation left a 0.0133" edge. But that can be hard to spot — did you catch it? (There are two, for example, the far left numerical value.)

In modern Blender versions, there is the promising Measurement Tool. Its icon has two rulers in an L.

The Blender Measure Tool

Here are things I do not like about it.

  • Requires the T (tool) menu. As far as I know there are no key bindings for this making it instantly unsuitable for anyone competent.

  • You must hold down the mouse button between the first and second measurement points. Besides not being how things like this are normally done in Blender, there’s a more serious issue. Imagine you’re trying to pick a point in a complex mess that you’ve zoomed in on and the other point is in a complex mess far away on your model and now off screen. How do you even handle that?

  • I’m sure there is a strategy for how many digits of precision this tool produces, but the fact that I don’t know what it is makes me crazy. This is especially rough when trying to measure a model to find out if your 2x4 is really 3.5000 inches or actually 3.5072 in the model. Good models do not have such inaccuracies!

Measure Tool Precision Mess

  • It leaves the measurement behind as some kind of permanent artifact. Let me stress this: you can not simply check on the dimensions of something in the model without adding to the model! The times when I would want this kind of permanent record of a measurement, I would go with the MeasureIt addon which is yet another ponderous way one can get measurements. The advantage of that tool is that it makes somewhat decent blueprint style dimension lines, better than the Measure Tool’s which are invisible on render. Here’s an example of MeasureIt.

MeasureIt Sample

  • The key bindings and operation of the Measurement Tool are idiosyncratic. When the snap is in effect is odd; when would not having a clear snap make any sense for a measurement that’s intended to be better than just sizing it up visually?

  • It’s mostly impossible to decompose measurements between points only along a particular axis. For example if I measured from a point at the peak of a house roof to a point at the gutter, I know how far rain might travel, but I have no clue about how much higher the roof makes the structure. For that, I want those same points but I only want the distance in the up/down (Z) direction.

Yesterday I was working on one of my custom table designs (which I invented using 3d computer modeling in 1990!) and it is exactly the kind of project that requires the ability to easily do complex measurements. It was driving me crazy!

What is so maddening about it is there is another way to get distance. You can open up a Python console, put the 3d cursor on the first point, then the second point while typing these commands.

p1= C.scene.cursor_location.copy()
p2= C.scene.cursor_location.copy()
print(p2-p1)

That’s it! Like I say, Blender has already done the math! I just need it to tell me about it. But this Python interface is not so fun. Can it be improved? Turns out, yes!

I had some astonishingly good results with Anthropic’s Claude recently and so I made a very detailed careful prompt of exactly what I wanted and how it should work and presto! That’s what I got. This was kind of a perfect mission for it since it has read all of the add-ons and my proposed add-on was so simple and basic that all of the challenge is just producing a sane basic add-on code template. If I had to do this without robot help I would have had to do what Claude did — read enough add-on source code until I could strip away everything that wasn’t common to all of them and then add my ridiculously simple core logic, logic that I worked out long before there were robot helpers.

I’m calling my addon Xed Dist so that I can easily recognize it in the aforementioned crowd of measurement tools. Here’s what its interface looks like.

Xed Dist Interface

There are basically two identical input regions for the two endpoints of a measurement. You can type in the numbers yourself if you know some arbitrary reference point in your model that may not have any geometry. But the more common thing to do would be to fill these fields in by picking at features in your model. Unlike the stock Measure Tool, my system collects points in exactly the same way you specify them in the majority of Blender operations. In the image, for example, I am in vertex edit mode and I selected the bottom (orange) vertex and clicked the Active Vertex button for Point A. Then I held shift and clicked the top (white) vertex) and clicked the Active Vertex button for B. Done. That was it.

The results update whenever the points are changed. How tall is this table? The Z component of the A-B say its 28.5". How would I ever get this out of Blender’s stock measuring tools? But I’m also seeing the full distance as well as verifying that the Y is 0 letting me know I didn’t accidentally stray from my intended plane during modeling.

There are also angles shown. This is more complex but for simple cases like this one it is helpful. The 45deg is confirmed just as the X component matching the Z height suggests it should be.

The Zero button simply sets a point to the world origin, all zeros. The Cursor button is more useful. You can type Shift-S and 2 (standard Blender expert stuff) to put the 3d cursor on a particular vertex, or, better, the average of a collection of them (a good way to get a midpoint). It is also very helpful for non-model geometry questions too, like checking the exact distance between a light source and a camera or just how far above the camera the light source is.

In between the two Point input regions are two buttons. The Swap A/B does the simple thing it promises. The Custom Axes is more complicated but still not too bad because instead of relying on some internal Blender concepts it relies on your model and your understanding of it.

Let’s say I wanted to know how long to cut each of those boards that make up the table. Basically I need to know the maximum length of any part of the object along the long axis. But since the long axis is up on an angle, decomposing the distance vector won’t help. One thing I could do is grab this object, temporarily rotate it flat by 45deg, take my measurement, and then rotate it back. But the Custom Axes allows you to load A with a local axis origin and B with a point on the positive X of a local axis. Basically if I put in the two points shown above and click custom axis, the measurements will be given with respect to that axis. If you’re good at 3d geometry, you’ll know that two points is insufficient to really make a proper coordinate system. The 3d cursor can be used to be a third point on the new local X-Y plane.

Xed Dist Interface - Local

So if the A and B points were chosen as shown in the previous image and the 3d cursor set as shown, after using the Custom Axes button, when I set A and B to the points shown now, the measurements say Local! and show us that this is indeed a 2x4 (3.5in by 1.5in) because even on this angle, in the local system, the components decompose to the board’s orthogonal sense. We can also see that the overall local X is about 43.8 which is what we’d need to cut from a 2x4 to get a long enough piece.

When specifying the third plane point for this feature, it can be helpful to Shift-RMB the 3d cursor onto any surface. Also when you’re done with the Custom Axes and want to return to world axes, just hit the Zero button on both Point A and B and then hit the Custom Axes button again.

A subtle thing you may have noticed if you are a careful modeler who keeps an eye on numerical details is that there is something fishy in the Y value input box of Point A. Why is it 0.98"? It is a typical capricious erroneous Blender rounding problem. We can see in the Results section that the local Z (which is now aligned with world Y) is correctly the width of a 2x4 at 1.5". Shouldn’t the 2.5" shown in Point B’s Y input box minus this 1.5 be exactly 1? It turns out that this particular design has all the table legs shifted in their local Z (the world Y for this one) by 0.025" so that everything lines up correctly. The Y on Point A is actually 0.975, not 0.98 as shown. Much worse, the Y on Point B is not 2.5 as shown but rather 2.475"! This interface just uses the normal Blender input boxes which commonly show a totally different value than you will see if you actually click on it to make an edit. This is why you can’t even trust glancing up at the Item tab’s Vertex display and competently just doing the math in your head! This is yet another reason that this add-on with its stable full precision results section is such a critical improvement.

textboxwoes.png

This is also why I’ve included a generous amount of displayed digits in the results section. You can set this in the code to be whatever you want, but I personally want a way to know everything that Blender knows where practical. There are errors that arise due to the way floating point numbers are encoded and some imperfections come with the territory, but seeing all the decimals can really help spot when something is out of whack because you made a tiny invisible slip up.

One of the reasons that I have input boxes at all is that besides being a reasonable way to show the location of the selected points, it is also a familiar interface with a lot of excellent functionality. Let’s say you’re working on a house frame model and you want to know how far it is from the window opening to the center of the room. The room you can get by selecting its floor rectangle in face mode, putting the 3d cursor on it (Shift-S + 2) and clicking the Cursor button. But let’s say you haven’t framed the window yet. You know the window opening will be one stud from the last you’ve placed plus a supporting king stud. You can select the farthest vertex of the last stud and then go to the input box for that wall’s axis and edit that value by appending + 16 + 1.5. This will put your measurement point somewhere you know is important but where nothing actually exists yet. Other useful things to add might be / 25.4 to put a mm size into your non-metric model. It’s basically a handy calculator.

As you can see with my requirement to deal with 2x4’s I have to suffer gringo units and having good feedback that simply provides Blender native meters and the units I often need to use is helpful. I think even for non-US people it could be nice to note that an odd measurement like 63.5mm is actually 2.5 Freedom Inches.

I don’t know if I’ll ever bother making a project page out of this. Not much of it is my code anyway. It is so ridiculously simple you can just tell your robot friend to make one for you. Heck, in the future, you should be able to give the link of this post to your robot friend and tell it what custom features you’d like. But for my archiving purposes, I’m going to dump the whole source code here for my future self and anyone who might need something like this.

bl_info = {
    "name": "Xed Dist",
    "author": "Chris X Edwards and robot friend Claude",
    "version": (1, 0, 0),
    "blender": (5, 2, 0),
    "location": "View3D > Sidebar (N-panel) > Xed Dist",
    "description": "Replaces the built-in Measure tool with typed-in vertex "
                   "coordinate boxes, live distance/axis-delta/angle output, buttons to:"
                   "swap A/B and to load point from: 3d cursor, origin, and active vertex.",
                   "Also load a custom axes by setting A to X=0, B to X+ value, and the",
                   "3d Cursor somewhere on the XY plane."
    "category": "3D View",
}

import bpy
import bmesh
import math
from mathutils import Vector
from bpy.props import FloatVectorProperty, PointerProperty, EnumProperty
from bpy.types import PropertyGroup, Operator, Panel

# == Helpers ==
def get_active_vertex_world_co(context):
    """Return world-space coordinates of the active vertex in edit mode, or None."""
    obj = context.edit_object
    if obj is None or obj.type != 'MESH':
        return None
    bm = bmesh.from_edit_mesh(obj.data)
    bm.verts.ensure_lookup_table()
    active_vert = None
    # Prefer the true "active" element from the selection history.
    if bm.select_history:
        elem = bm.select_history[-1]
        if isinstance(elem, bmesh.types.BMVert):
            active_vert = elem
        elif hasattr(elem, "verts") and elem.verts:
            # Active edge/face selected: fall back to its last vertex.
            active_vert = elem.verts[-1]
    if active_vert is None:
        # Fall back to the last selected vertex found.
        selected = [v for v in bm.verts if v.select]
        if selected:
            active_vert = selected[-1]
    if active_vert is None:
        return None
    return obj.matrix_world @ active_vert.co

def _wrapped_degrees(angle_rad):
    deg = math.degrees(angle_rad)
    if deg < 0:
        deg += 360.0
    return deg

# == Property Group ==
class MEASURETOOL_Properties(PropertyGroup):
    point_a: FloatVectorProperty(
        name="Point A",
        description="First measurement point. Accepts normal Blender typed "
                    "math in each field (e.g. click in, type '-1.5', Enter)",
        subtype='XYZ',
        unit='LENGTH',
        size=3,
    )
    point_b: FloatVectorProperty(
        name="Point B",
        description="Second measurement point. Accepts normal Blender typed "
                    "math in each field (e.g. click in, type '-1.5', Enter)",
        subtype='XYZ',
        unit='LENGTH',
        size=3,
    )
    local_x_axis: FloatVectorProperty(size=3, subtype='XYZ', default=(1.0, 0.0, 0.0))
    local_y_axis: FloatVectorProperty(size=3, subtype='XYZ', default=(0.0, 1.0, 0.0))
    local_z_axis: FloatVectorProperty(size=3, subtype='XYZ', default=(0.0, 0.0, 1.0))
    use_local_frame: bpy.props.BoolProperty(default=False)

# == Operators ==
class MEASURETOOL_OT_assign_active(Operator):
    bl_idname = "measuretool.assign_active"
    bl_label = "Assign Active Vertex"
    bl_description = "Copy the active vertex's world-space position into this point"
    bl_options = {'REGISTER', 'UNDO'}
    target: EnumProperty(items=[('A', "A", ""), ('B', "B", "")], default='A')
    @classmethod
    def poll(cls, context):
        return context.mode == 'EDIT_MESH' and context.edit_object is not None
    def execute(self, context):
        co = get_active_vertex_world_co(context)
        if co is None:
            self.report({'WARNING'}, "No active vertex found")
            return {'CANCELLED'}
        props = context.scene.measuretool_props
        if self.target == 'A':
            props.point_a = co
        else:
            props.point_b = co
        return {'FINISHED'}

class MEASURETOOL_OT_zero(Operator):
    bl_idname = "measuretool.zero"
    bl_label = "Zero to Origin"
    bl_description = "Set this point to the world origin (0, 0, 0)"
    bl_options = {'REGISTER', 'UNDO'}
    target: EnumProperty(items=[('A', "A", ""), ('B', "B", "")], default='A')
    def execute(self, context):
        props = context.scene.measuretool_props
        if self.target == 'A':
            props.point_a = (0.0, 0.0, 0.0)
        else:
            props.point_b = (0.0, 0.0, 0.0)
        return {'FINISHED'}

class MEASURETOOL_OT_swap(Operator):
    bl_idname = "measuretool.swap"
    bl_label = "Swap A / B"
    bl_description = "Swap the values of Point A and Point B"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        props = context.scene.measuretool_props
        a = tuple(props.point_a)
        b = tuple(props.point_b)
        props.point_a = b
        props.point_b = a
        return {'FINISHED'}

class MEASURETOOL_OT_cursor_to_point(Operator):
    bl_idname = "measuretool.cursor_to_point"
    bl_label = "3D Cursor to Point"
    bl_description = "Copy the 3D cursor's location into this point"
    bl_options = {'REGISTER', 'UNDO'}
    target: EnumProperty(items=[('A', "A", ""), ('B', "B", "")], default='A')
    def execute(self, context):
        props = context.scene.measuretool_props
        cursor_co = context.scene.cursor.location
        if self.target == 'A':
            props.point_a = cursor_co
        else:
            props.point_b = cursor_co
        return {'FINISHED'}

class MEASURETOOL_OT_custom_axes(Operator):
    bl_idname = "measuretool.custom_axes"
    bl_label = "Custom Axes (A->B = +X, Cursor on XY)"
    bl_description = ("Build a local frame where A->B is +X and the 3D cursor "
                       "lies in the XY plane. Delta values then read in this "
                       "frame instead of world space. Zero A, zero B, and "
                       "click again to reset to world axes.")
    bl_options = {'REGISTER', 'UNDO'}
    EPS = 1e-8
    def execute(self, context):
        props = context.scene.measuretool_props
        a = Vector(props.point_a)
        b = Vector(props.point_b)
        cursor = context.scene.cursor.location
        ab = b - a
        if ab.length < self.EPS:
            props.use_local_frame = False
            self.report({'INFO'}, "Reset to world axes")
            return {'FINISHED'}
        local_x = ab.normalized()
        ac = cursor - a
        proj = ac - ac.dot(local_x) * local_x
        if proj.length < self.EPS:
            self.report({'ERROR'}, "3D cursor is colinear with A-B; can't define a plane")
            return {'CANCELLED'}
        local_y = proj.normalized()
        local_z = local_x.cross(local_y)
        props.local_x_axis = local_x
        props.local_y_axis = local_y
        props.local_z_axis = local_z
        props.use_local_frame = True
        return {'FINISHED'}

# == Panel ==
class MEASURETOOL_PT_panel(Panel):
    bl_label = "Xed Dist"
    bl_idname = "MEASURETOOL_PT_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Measure"

    def draw(self, context):
        layout = self.layout
        props = context.scene.measuretool_props
        # === Point A ===
        box = layout.box()
        box.label(text="Point A")
        box.column().prop(props, "point_a", text="")
        row = box.row(align=True)
        row.operator("measuretool.assign_active", text="Active Vertex").target = 'A'
        row.operator("measuretool.zero", text="Zero").target = 'A'
        row.operator("measuretool.cursor_to_point", text="Cursor", icon='CURSOR').target = 'A'
        layout.operator("measuretool.swap", icon='ARROW_LEFTRIGHT')
        layout.operator("measuretool.custom_axes", icon='ORIENTATION_LOCAL')
        # === Point B ===
        box = layout.box()
        box.label(text="Point B")
        box.column().prop(props, "point_b", text="")
        row = box.row(align=True)
        row.operator("measuretool.assign_active", text="Active Vertex").target = 'B'
        row.operator("measuretool.zero", text="Zero").target = 'B'
        row.operator("measuretool.cursor_to_point", text="Cursor", icon='CURSOR').target = 'B'
        # === Results ===
        a = Vector(props.point_a)
        b = Vector(props.point_b)
        d = b - a
        distance = d.length
        dx, dy, dz = abs(d.x), abs(d.y), abs(d.z)
        # Angles: for the plane perpendicular to a given axis, measured CCW
        # starting at the first of the two remaining axes in the cyclic
        # order X -> Y -> Z -> X, so a positive angle sweeps toward the
        # second axis (right-hand rule about the axis you're "looking
        # down"). E.g. looking down Z: 0deg = +X, 90deg = +Y (matches spec).
        angle_z = _wrapped_degrees(math.atan2(d.y, d.x))  # look down Z, 0=+X, 90=+Y
        angle_y = _wrapped_degrees(math.atan2(d.x, d.z))  # look down Y, 0=+Z, 90=+X
        angle_x = _wrapped_degrees(math.atan2(d.z, d.y))  # look down X, 0=+Y, 90=+Z
        box = layout.box()
        box.label(text="Results", icon='DRIVER_DISTANCE')
        col = box.column(align=True)
        col.label(text=f"Dist: {distance:.6f} ({distance/0.0254:.6f}\")")
        if props.use_local_frame:
            lx = Vector(props.local_x_axis)
            ly = Vector(props.local_y_axis)
            lz = Vector(props.local_z_axis)
            dx, dy, dz = abs(d.dot(lx)), abs(d.dot(ly)), abs(d.dot(lz))
            axis_label = "Local! A-B ("
        else:
            dx, dy, dz = abs(d.x), abs(d.y), abs(d.z)
            axis_label = "A-B ("
        col.label(text=f"{axis_label} X): {dx:.7f} ({dx/0.0254:.6F}\")")
        col.label(text=f"{axis_label} Y): {dy:.7f} ({dy/0.0254:.6F}\")")
        col.label(text=f"{axis_label} Z): {dz:.7f} ({dz/0.0254:.6F}\")")
        col = box.column(align=True)
        col.separator()
        col.label(text=f"Angle (down Z, 0°=+X→+Y): {angle_z:.6f}°")
        col.label(text=f"Angle (down Y, 0°=+Z→+X): {angle_y:.6f}°")
        col.label(text=f"Angle (down X, 0°=+Y→+Z): {angle_x:.6f}°")

# == Registration ==
classes = (
    MEASURETOOL_Properties,
    MEASURETOOL_OT_assign_active,
    MEASURETOOL_OT_zero,
    MEASURETOOL_OT_swap,
    MEASURETOOL_PT_panel,
    MEASURETOOL_OT_cursor_to_point,
    MEASURETOOL_OT_custom_axes,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.measuretool_props = PointerProperty(type=MEASURETOOL_Properties)

def unregister():
    del bpy.types.Scene.measuretool_props
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

if __name__ == "__main__":
    register()

To get it working, save this code as something like xeddist.py. Then in Blender, open Preferences (Ctrl-,) then go to the Add-ons section. They’ve made some major changes here recently (after v5) — you have to click on the little V symbol in the far upper right corner and choose Install from Disk… from the pull down. Then find the Python file and that should be it - it should show up in the interface as described.

tableirl.jpg

SnowC - A Demonstration

2026-07-31 20:10

In previous posts I introduced SnowC and explained how it works and what challenges had to be overcome. In this post let’s look at SnowC in action and see what it’s capable of.

Recall that the most challenging project goal was to be able to convert wild C source code into sensible SnowC. That was phase one. Phase two was relatively easy, just following the SnowC rules to convert SnowC back to C. Let’s have a closer look at phase three which was to get both of these programs working well enough that the SnowC conversion programs' source code themselves could be both converted.

We can start by downloading the SnowC project from…

Here is a good way to get to a directory with the SnowC source code.

$ cd /tmp/
$ wget -qO- https://xed.ch/project/snowc/snowc.tgz | tar -xzf -
$ cd snowc-*

To test the round trip conversions, I’ve created a script. The script is pretty illustrative and kind of interesting in a mind bending kind of way, but you can also ignore its details and skip ahead to just focus on the big picture.

#!/bin/bash
# Enter number of rounds as an argument or default to 3.
MAX_ROUNDS=${1:-3}

# === Initialization (Round 0) ===
echo "=== Round 0: Initial Compilation ==="
gcc -Wall -o c2snowc c2snowc.c
gcc -Wall -o snowc2c snowc2c.c
# Generate initial SnowC files (.cno) from original C.
./c2snowc c2snowc.c > c2snowc.cno
./c2snowc snowc2c.c > snowc2c.cno
# Convert initial SnowC back to C (Round 1 output).
./snowc2c c2snowc.cno > C2SNOWC-01.c
./snowc2c snowc2c.cno > SNOWC2C-01.c
# Compile Round 1 C files.
gcc -Wall -o C2SNOWC-01 C2SNOWC-01.c
gcc -Wall -o SNOWC2C-01 SNOWC2C-01.c
# Initial check (Round 1 verification).
echo "=== Round 1 Verification ==="
diff <(./c2snowc C2SNOWC-01.c) <(./C2SNOWC-01 C2SNOWC-01.c)
md5sum <(./c2snowc C2SNOWC-01.c) <(./C2SNOWC-01 C2SNOWC-01.c) | cut -b-32

# === Main Loop (Previous Round = 1 to MAX_ROUNDS - 1) ===
for PR in $(seq -f'%02.0f' $(( ${MAX_ROUNDS} - 1 )) ); do
    R=$(printf "%02d" $((10#$PR + 1))) # This round = PR++.
    echo "=== Round $R ==="
    # 1. Create (R-1) SnowC from (R-1) C.
    # Using the executables from the previous round (PR).
    ./C2SNOWC-$PR C2SNOWC-$PR.c > C2SNOWC-$PR.cno
    ./C2SNOWC-$PR SNOWC2C-$PR.c > SNOWC2C-$PR.cno
    # 2. Create (R) C from (R-1) SnowC.
    # Using the SNOWC2C executable from the previous round.
    ./SNOWC2C-$PR C2SNOWC-$PR.cno > C2SNOWC-$R.c
    ./SNOWC2C-$PR SNOWC2C-$PR.cno > SNOWC2C-$R.c
    # 3. Compile (R) C to (R) executables.
    gcc -Wall -o C2SNOWC-$R C2SNOWC-$R.c
    gcc -Wall -o SNOWC2C-$R SNOWC2C-$R.c
    # 4. Verification: Compare (R) executable output vs (R-1) C input.
    echo "Verifying Round $R..."
    diff <(./C2SNOWC-$R C2SNOWC-$PR.c) <(./C2SNOWC-$R C2SNOWC-$R.c)
    md5sum <(./C2SNOWC-$R C2SNOWC-$PR.c) <(./C2SNOWC-$R C2SNOWC-$R.c) | cut -b-32
done

Basically this script compiles the SnowC conversion programs from the original C source code (the code I originally wrote) and then uses those executables to convert the same source code to SnowC and then back to C. After a new set of C source code files has been generated, you can start the process again with the new files. You can specify how many times you want to make this round trip.

If I run this with a single round trip, it works. The outputs of the new version and the original version match.

$ ./roundtrip 1
=== Round 0: Initial Compilation ===
=== Round 1 Verification ===
13133dd985944b14d2482971baa3c9a1
13133dd985944b14d2482971baa3c9a1

This means the newly generated C source code behaves exactly like the original source code. And if I run the script 99 times and check the 99th executable, it also functions identically to the first.

$ md5sum <(./c2snowc c2snowc.c) <(./C2SNOWC-99 c2snowc.c) | cut -b-32
ba07eb908e548a6e6ceb36b70cd087b3
ba07eb908e548a6e6ceb36b70cd087b3

Hmm, shouldn’t those md5 summaries be the same? Probably, it would be nice for sure. When the script is run with a high number of cycles you can easily see the problem. Those diff checks show that trailing comments are somehow getting pushed to the right by one space every iteration.

drift.gif

It looks like when braces are removed to make the SnowC, all the spaces around them stay, and then when a brace is added back to make the C again, it gets a polite space to format/separate it from the previous characters but all the spaces from the last round are still there too. So the line grows. An interesting bug for sure but minor enough to really be a serious issue only if you’re doing hundreds of round trip conversions!

I’ll fix this bug but it does not affect the critical goal of phase three. With the code supporting a functional round trip, it is now reasonable from here on to be able to continue development in SnowC. Hopefully we’ll see how that turns out sometime in the future.

Getting to phase three where a round trip is possible was the main original goal and now that I have done that, I’ll probably take a break from this project. Besides the issue I just mentioned, I have some known bugs I’d like to fix and I would like to try working directly in SnowC to see what that’s actually like. But what is next? What is phase four?

I think the next major milestone is improving and proving the robustness of the system by having it try to do round trips with C code in the wild. My main target is the Linux kernel.

Ignoring header files (for now) the 7.1.5 Linux kernel is distributed with 36684 C source code files. I used c2snowc to convert all but 11 of them. In just a quick inspection of those, I discovered a nasty configuration of nugatory else matching (see yesterday’s post). Since those 11 all hung (and not, say, a seg fault) I’m hoping they might all have the same problem. That’s only the first half of the mission. I also converted the 36673 Linux kernel SnowC files back into C. No trouble there. But Linux isn’t just some test code. Does that generated C compile? No. Even make tinyconfig dies pretty quickly into the process. But it’s a superb platform for catching C code that is difficult to translate. So I’ll probably work on whatever deficiencies that strategy reveals.

One thing converting 36673 files did answer is how fast is SnowC? Converting the C to SnowC took 2min 55sec or 4.8ms per file. With its simple algorithm, converting those SnowC files back to C was 25% quicker at 2min 12sec or 3.6ms per file. For reference, on my computer just a simple ls or cal take about 12ms. Just showing me the date takes about twice as long as converting SnowC code. So I’m pretty happy with the performance. If you come up with a much quicker way to pull the inherent redundancy out of well written C code, I’d love to see it!

[If you’ve made it this far, thank you for reading all this! Or any of it! I hope you learned something, even if only about my ability to get a little too obsessive about my projects!]

SnowC - Challenges - Some Final Pointers

2026-07-30 16:33

There is actually a point to this series on challenges I faced when creating a decent implementation of SnowC. I think the SnowC idea is very sensible and it is worth pursuing but while I’m proud of what I’ve accomplished, I’m not claiming to be our species' best candidate to implement the idea. And one day in the future — but not today! — maybe our robot friends can even improve on what I’ve done. So these posts serve as a guide for what someone will need to think about if they want to create a SnowC conversion system of their own. This post will round out some of the other random challenges I ran into. Some were expected, some not so much.

Literals

Let’s start with the first thing you need to start with. It may come as a slight surprise that the first thing to deal with is character and string literals. I take a special interest in specifying text in programming languages so it came as no surprise to me to discover that strings must be isolated and dealt with first. It’s pretty easy to see why if we imagine a string talking about the syntax of specifying strings.

char *help = "Use \" to specify strings in C: \"a string\"";

Obviously that’s annoying but really SnowC doesn’t care what your string says, it just needs your strings to not confuse things. That could happen in an example like this.

char *a = "char /*{ick}*/ *b = \"b\"; int c = 0; "; int c = 0;

You can use the c2snowc map diagnostic option (-m) to show how the characters of this line get classified, or mapped into general categories.

char *a = "char /*{ick}*/ *b = \"b\"; int c = 0; "; int c = 0;
CCCC_CC_C_$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$;_CCC_C_C_C;+

With the characters of the string properly identified it’s much easier to see that there are only two proper semicolons here to worry about. The same idea applies to this line involving character literals.

char c = ';' == '\'' + ';'; c++;
CCCC_C_C_'''_CC_''''_C_''';_CCC;+

It may seem odd, but since strings can say any crazy thing, they must be neutralized before anything else is touched. And although the fail cases are subtle, character literals should be immediately neutralized next.

Comments

The next thing to isolate and neutralize must be modern double slash comments. This was originally a C++ thing and looks like this.

char *comments = "can get messy"; // So true!

This (everything after //) is the primary comment style that takes precedence over a secondary comment style. Old school K&R comments (/* which look like this */) are very clever. Too clever. Probably the biggest source of real problems is C’s classic comment style. It’s bad enough looking for the last meaningful character of a line knowing that any random junk can follow in the form of a comment. For example, the last meaningful character here is the first semicolon.

x /= 3; //= 3; ///= 3; /* This is totally valid C code because FML. */

But the problems get infinitely worse when you consider that classic C commenting can be mischievously injected anywhere. Looking for a thing that needs to follow a thing? Well, you better count on it following a near random collection of obfuscating garbage in the form of a comment.

So identify all the characters that are double slash comments and then identify all the other comment characters. Then don’t forget about them, because they could possibly be causing mischief damn near anywhere!

Just remember that you can comment out preprocessor syntax, so deal with comments first. Which brings us to preprocessor problems.

Preprocessor

Comments are hard because they can lurk anywhere muddling important semantic details. But preprocessor directives have a different problem. They are often used to swap in chunks of real C code at the last minute. I actually did not do a super deep dive into insane preprocessor abuse. I am pretty tame with my usage personally and, well, it was a lower priority for me. Sorry.

Basically if the first significant character of a line is a # I’m calling it a preprocessor directive and it gets ignored much like a comment. If there is some kind of #if, #ifdef, #elif, #else or similar construction with some ugly confusing code hiding inside, well, you’ll probably need to take special care.

One conceptual problem is that the SnowC to C conversion injects the missing C syntax (braces, semicolons) back into the code. If a preprocessor range isn’t anticipating this, there can be problems. Here’s a simplified example equivalent to something I found in the Linux kernel starting with the original C.

void fn() {
  for (q1) {
    if (q2) {
      a();
      #if (FANCY_FEATURE == ENABLED)
      fancy_feature();
      #endif
    }
  }
}

The SnowC is fine.

void fn()
  for (q1)
    if (q2)
      a()
      #if (FANCY_FEATURE == ENABLED)
      fancy_feature()
      #endif

But look what happens when it goes back to C.

void fn() {
  for (q1) {
    if (q2) {
      a();
      #if (FANCY_FEATURE == ENABLED)
      fancy_feature();
    }
  }
}
      #endif

Leaving out all the closing braces when the fancy feature is not enabled will be a serious problem. Comments also can be moved to undesired locations in this way but that’s less problematic.

I could have all closing brace generation wait until after any preprocessor lines (and comments) but this is sometimes not the right choice either. I suspect it is the more common choice and I may switch the default to it. I’m starting to think that the correct solution is for SnowC to insist you place your preprocessor statement’s level intentions the same way as everything else — with indent level. Consider it on the to do list.

Labels

C has a quirky syntax for "labels" which are targets for the goto statement. Using goto in the first place is not really encouraged so labels aren’t even all that common. But they’re common enough that they need to be dealt with.

In C, a label is generally a word followed by a colon. Like this.

found:

Easy, right? Well, not so fast! Have a look at some of my test code where I try to anticipate label problems.

label1:
    label_with_leading_spcs:
label_with_trailing_spcs:
label_with_intercolon_spcs   :
/*precomment*/label_with_precomment:
label_with_post_comment:// Post comment.
label_with_post_comment_trailings:/* Post comment w trailing. */
label_with_intercomment/* Inter comment.*/:
_yes_a_label:
_yes_2_label:
3_not_a_label:  // Can't start with a number.
not%a_label:
not^a_label:
default_plus_is_label:
default_ : // Is a label.
default:   // Technically not a label despite appearances.

C has a way of making you think it’s simple and obvious. And then you dig into it and find out, yikes, a lot can be taken for granted by the programmer.

So why exactly do we need to know what text is a label? Because SnowC needs to mostly ignore them sometimes. For example when they are between a singleton control word and the resolving statement.

Unfortunately C has another use for colons. A couple in fact. Look at this syntax — which I checked does actually compile.

 int not_a_label = 0;            | int not_a_label = 0
 x2 = x?                         | x2 = x?\
 not_a_label:                    | not_a_label:
 99;                             | 99

Fortunately, despite failing to realize that not_a_label: is not a label, the SnowC conversion just happens to work because of, well, luck. I am walking away from this one.

Nugatory

If you don’t know what the word "nugatory" means, I suggest you look it up (click here!) and start using it. It’s a great word!

You can probably figure it out from my test file called nugatory.c which gave me a surprising amount of grief. Here it is with the generated SnowC on the right.

 //TEST: Blank statements and blocks.           | //TEST: Blank statements and blocks.
 if (q0) {} // Empty block.                     | if (q0) {}\ // Empty block.
 if (q1) ; // Blank statement.                  | if (q1)  // Blank statement.
 else if (q2) ;                                 | else if (q2)
 else if (q3) {                                 | else if (q3) {\
 }else if (q4) {      // Some spaces.           | }else if (q4) {\      // Some spaces.
 } else if (q5)  // Ick.                        | }else if (q5)\  // Ick.
 { }                                            | {}\
 else if (q6) {                                 | else if (q6) {\
     /* Pass. */                                | /* Pass. */
 } else {// Uncomment for diagnostic.           | }else {\// Uncomment for diagnostic.
     //printf("Error:\n"); /* ERROR */          | //printf("Error:\n"); /* ERROR */
 }                                              | }\
 x++;                                           | x++
                                                |

Dang that’s gnarly! But if you follow the simple SnowC rules to convert that back to C, it will produce semantically equivalent C code.

Note the final line is blank. I also have several tests for blank lines at the beginning and end and other inconvenient places. It’s little things like that which can be a real pain in the ass.

I’m sure there are other challenges I could highlight but that concludes my tour of the main problems that one must deal with when trying to pull the redundancy out of C code. It may not be easy, but for practical use, I have demonstrated that it is possible.

SnowC - Challenges - Else

2026-07-29 05:59

In the last post we looked at how challenging it was to just track level depth. To understand the problem better let’s review how singletons can cause mischief. The classic singleton is something like this conceptual code.

SINGLETON_KEYWORD (optional_conditions) resolving_statement;

The SINGLETON_KEYWORD can be if, for, while, switch, do, or else. When the resolving statement completes, the singleton is complete. But what if the resolving_statement is also another SINGLETON_KEYWORD?

    SINGLETON_KEYWORD
        SINGLETON_KEYWORD
            SINGLETON_KEYWORD
                SINGLETON_KEYWORD
                    resolving_statement;

The resolving statement closes out the nearest singleton which then closes out the next and the whole chain unwinds. Now that I’ve explained it to you, you’re ready to believe that’s how it truly works, right? If you don’t know anything about C you are certainly ready to believe this. Even if you are a professional C programmer, you might still be fooled because no sane person ever writes code like this!

The resolving statement unraveling all the nested singletons is a nice neat concept, but unfortunately, it is not C’s nice neat concept. Let’s see how C really behaves. Check out this singleton puzzle paying attention to the indent levels.

 if (L1)
     for (L2;;)
         if (L3)
             resolve(3,2,1);
 if (L1)  // Previous resolve fully resets level.
     resolve(1);

That does look like the neat algorithm I described. The level of the first if has been fully reset because the entire chain of singletons has been resolved. Seems reasonable, right? C then puts you in the uncomfortable position of thinking the following code is also reasonable even though there is a kind of inconsistency to it.

 if (L1)
     for (L2;0;)
         if (L3)
             resolve(only3);
         else // Previous resolves only one level!
             resolve(3,2,1); // Finally resolves.

The else resolves with a single statement clearly making it a singleton keyword. But there is more to it. It turns out that else is a special singleton keyword that has its own ideas about indent level.

This inconsistent feature of C’s control keywords introduces the need to track if the last singleton keyword was specifically an if and then on closing out that if, the levels get adjusted differently if a look ahead can find an else nearby. Good times.

If you think all that sounds challenging, I’ve got some bad news for you because it turns out to be a lot worse than that. I realized that any else must align with any inner if regardless of either executing a braced clause or not. This actually requires a FILO stack to keep track of any pending if that might possibly go with an else at every level of brace depth. And these must be politely dropped once there is no possibility for an else at that brace level. Don’t worry if that takes you several days to get your head around; been there.

At this point I was seriously starting to question my judgment at taking on this project. But I pressed on and figured out a way. The need for this quirky syntax tracking should almost never arise in any sane modern production code. For that reason each tracked level on the stack gets a leisurely heap allocation and I’m not even going to apologize.

When I finally implemented that, my test setup caught another error in the K&R sample from page 132! There already was a missing semicolon in that code but also I realized line 20 is incorrectly indented! Fortunately SnowC caught it nicely. And of course it must be said that in C, this is not technically an error — it does compile fine. But it sure is a faux pas!

(KR132-cat.c)

 #include <stdio.h>                                              | #include <stdio.h>
 /* cat: concatenate files, version 1 */                         | /* cat: concatenate files, version 1 */
 main(int argc, char *argv[])                                    | main(int argc, char *argv[])
 {                                                               |     FILE *fp
     FILE *fp;                                                   |     void filecopy(FILE *, FILE *)\ //[Original missing ;]
     void filecopy(FILE *, FILE *) //[Original missing ;]        |     if (argc == 1) /* no args; copy standard input */
     if (argc == 1) /* no args; copy standard input */           |         filecopy(stdin, stdout)
         filecopy(stdin, stdout);                                |     else
     else                                                        |         while(--argc > 0)
         while(--argc > 0)                                       |             if ((fp = fopen(*++argv, "r")) == NULL)
             if ((fp = fopen(*++argv, "r")) == NULL) {           |                 printf("cat: can't open %s\n", *argv)
                 printf("cat: can't open %s\n", *argv);          |                 return 1
                 return 1;                                       |             else
             } else {                                            |                 filecopy(fp, stdout)
                 filecopy(fp, stdout);                           |                 fclose(fp)
                 fclose(fp);                                     |     return 0 //[Original indent wrong!]
             }                                                   | /* filecopy: copy file ifp to file ofp */
         return 0; //[Original indent wrong!]                    | void filecopy(FILE *ifp, FILE *ofp)
 }                                                               |     int c
 /* filecopy: copy file ifp to file ofp */                       |     while ((c = getc(ifp)) != EOF)
 void filecopy(FILE *ifp, FILE *ofp)                             |         putc(c, ofp)
 {                                                               |
     int c;                                                      |
     while ((c = getc(ifp)) != EOF)                              |
         putc(c, ofp);                                           |
 }                                                               |

I already noted their page 51 example which highlights this exact problem deliberately. The SnowC on the right is automatically corrected.

 if (n > 0)                                       | if (n > 0)
     for (i = 0; i < n; i++)                      |     for (i = 0; i < n; i++)
         if (s[i] > 0) {                          |         if (s[i] > 0)
             printf("...");                       |             printf("...")
             return i;                            |             return i
         }                                        |         else /* WRONG */
 else /* WRONG */                                 |             printf("error -- n is negative\n")
     printf("error -- n is negative\n");          |

Hopefully you now have a better understanding of just how wrong I was when I originally thought of the SnowC concept and I thought it surely couldn’t be all that hard. It was a hell of a challenge but I’m glad I stuck with it and I am very happy with the results.

I’ll leave you with a couple of extra awful conversion tests that I used during development, starting with an example of singletons, braces, and a mix. Remember, SnowC is ignoring all the (possibly spurious) indentation of the original and recreating it with structural rigor.

(nested_if_else.c)

 int main(int argc, char** argv) {        | int main(int argc, char** argv)
   // All singletons.                     |   // All singletons.
   if (q1)                                |   if (q1)
     if (q2)                              |     if (q2)
       if (q3)                            |       if (q3)
         if (q4)                          |         if (q4)
           a4();                          |           a4()
         else                             |         else
           e4();                          |           e4()
       else                               |       else
         e3();                            |         e3()
     else                                 |     else
       e2();                              |       e2()
   else                                   |   else
     e1();                                |     e1()
   // All brace blocks.                   |   // All brace blocks.
   if (q1) {                              |   if (q1)
     if (q2) {                            |     if (q2)
       if (q3) {                          |       if (q3)
         if (q4) {                        |         if (q4)
           a4();                          |           a4()
         }                                |         else
         else {                           |           e4()
           e4();                          |       else
         }                                |         e3()
       }                                  |     else
       else {                             |       e2()
         e3();                            |   else
       }                                  |     e1()
     }                                    |   // Mixed.
     else {                               |   if (q1)
       e2();                              |     if (q2)
     }                                    |       if (q3)
   }                                      |         if (q4)
   else {                                 |           a4()
     e1();                                |         else
   }                                      |           e4()
   // Mixed.                              |       else
   if (q1) {                              |         e3()
     if (q2)                              |     else e2()
       if (q3) {                          |   else
         if (q4) {                        |     e1()
           a4();                          |
         }                                |
         else                             |
           e4();                          |
       }                                  |
       else {                             |
         e3();                            |
       }                                  |
     else e2();                           |
   }                                      |
   else {                                 |
     e1();                                |
   }                                      |
 }                                        |

Here is another challenging test for the if/else matching. The B is brace level and the S is singleton level.

(badelse.c)

 void main() {                                                          | void main()
     // All singletons.                                                 |     // All singletons.
     for (q1)                                                           |     for (q1)
         for (q2)                                                       |         for (q2)
             if (q3)                                                    |             if (q3)
                 while (q4)                                             |                 while (q4)
                     for (q5)                                           |                     for (q5)
                         a();                                           |                         a()
             else                                                       |             else
                 if                                                     |                 if
                     while                                              |                     while
                         for                                            |                         for
                             b();                                       |                             b()
                 else                                                   |                 else
                     c();                                               |                     c()
     ok1();                                                             |     ok1()
     // With some braces.                                               |     // With some braces.
     for (q1)                                                           |     for (q1)
         for (q2)                                                       |         for (q2)
             if (q3) {                                                  |             if (q3)
                 while (q4)                                             |                 while (q4)
                     for (q5)                                           |                     for (q5)
                         a();                                           |                         a()
             }                                                          |             else
             else {                                                     |                 if
                 if                                                     |                     while
                     while                                              |                         for
                         for                                            |                             b()
                             b();                                       |                 else
                 else                                                   |                     c()
                     c();                                               |     ok2()
             }                                                          |     // With challenging else requirements.
     ok2();                                                             |     if (d)                        // B1 S0
     // With challenging else requirements.                             |         while (w)                 // B1 S1
     if (d)                        // B1 S0                             |             if (c)               // B1 S2 --------+
         while (w)                 // B1 S1                             |                 if (b)            // B2 S0 -----+  |
             if (c) {              // B1 S2 --------+                   |                     for (f)       // B2 S1      |  |
                 if (b)            // B2 S0 -----+  |                   |                         if (a)    // B2 S2 ---+ |  |
                     for (f)       // B2 S1      |  |                   |                             a++  // B2 S3    | |  |
                         if (a)    // B2 S2 ---+ |  |                   |                         else      // B2 S2 ---+ |  |
                             a++;  // B2 S3    | |  |                   |                             a()  // B2 S3      |  |
                         else      // B2 S2 ---+ |  |                   |                 else              // B2 S0 -----+  |
                             a();  // B2 S3      |  |                   |                     b()          // B2 S1         |
                 else              // B2 S0 -----+  |                   |     // B2 S1         |
                     b();          // B2 S1         |                   |             else                  // B1 S2 --------+
             }                     // B2 S1         |                   |                 c()              // B1 S3
             else                  // B1 S2 --------+                   |     else                          // B1 S0
                 c();              // B1 S3                             |         d()                      // B1 s1
     else                          // B1 S0                             |     return                       // B1 S0
         d();                      // B1 s1                             |
     return;                       // B1 S0                             |
 }                                                                      |

SnowC - Challenges - Levels

2026-07-28 19:53

Back in 2015 when I conceived of the idea for SnowC, I first thought it would be so easy to convert from SnowC to C that I could do it by storing the state of the indentation level in the call stack using recursion. That turned out to be too ambitious but it was close.

However what was really delusional and naive was thinking that converting from C might be possible using recursion too. While I got over that quickly, even starting on it fresh this year, I thought, surely this conversion can not be that difficult. Wow, was I profoundly wrong! Over two months later I now know an unhealthy amount about pathological C code and I have been humbled. When it comes to the entire possibility space of C source code, nothing is simple.

Which brings us to indent levels. Easy, right? Well let’s just say that it’s easy when it’s easy. Like this example (C left, SnwoC right).

 // Level 0                                              | // Level 0
 int level1(int x) { // Level 0 -> 1                     | int level1(int x)  // Level 0 -> 1
     if (x) { // Level 1 -> 2                            |     if (x)           // Level 1 -> 2
         if (x % 2) { // Level 2 -> 3                    |         if (x % 2)     // Level 2 -> 3
             x++;                                        |             x++
             for (int y=x; y<99; y++) { // Level 3 -> 4  |             for (int y=x; y<99; y++)  // Level 3 -> 4
                 if (x + y == 99) { // Level 4 -> 5      |                 if (x + y == 99)      // Level 4 -> 5
                     return y;                           |                     return y
                 } // Level 5 -> 4                       |                 // Level 5 -> 4
             } // Level 4 -> 3                           |             // Level 4 -> 3
         } // Level 3 -> 2                               |         // Level 3 -> 2
     } //Level 2 -> 1                                    |     //Level 2 -> 1
 } //Level 1 -> 0                                        | //Level 1 -> 0

And without the comments, the SnowC is pretty legible.

int level1(int x)
    if (x)
        if (x % 2)
            x++
            for (int y=x; y<99; y++)
                if (x + y == 99)
                    return y

Those comments are there to show how the core concept works. You basically need a simple integer variable that stores the indent level. When you find an opening brace ({), increment the indent level, and when you find a closing brace (}), decrement the indent level. Easy, right? Right? Ohhhhh no.

hiding_creeper.jpg

It gets pretty bad. Let’s start with relatively simple singleton hassles. In a recent post we just covered how the C creators loved to do level nesting with no braces at all. I call those cases singletons. Ok, fine, so that is a thing. Sure, whatever. Surely there’s a solution, I thought.

Usually SnowC can happily not really care one bit what your C code is actually doing. But because of this issue, I realized there needed to be a complex parsing effort so all the singleton (braceless) control syntax could correctly contribute to the indent levels being adjusted properly. Not easy but doable. So I dug in and got to it.

Single line singletons are easily handled by just ignoring them. They are resolved before they can even make an impact on the code’s indentation structure.

Multi-line singletons are a different story, so let’s start with them.

An interesting property of SnowC is that a round trip will completely remove multi-line singletons! For example, consider this authentic K&R example (from page 92).

for (i = 1; yearday > daytab[leap][i]; i++)
    yearday -= daytab[leap][i];

In SnowC it becomes.

for (i = 1; yearday > daytab[leap][i]; i++)
    yearday -= daytab[leap][i]

Note how close this actually is to what K&R imagined! But there is no way to hint that you do not want the more ordinary and predictable C you will get on the return conversion. Here is what converting back to C produces.

for (i = 1; yearday > daytab[leap][i]; i++) {
    yearday -= daytab[leap][i];
}

Note that this C produces the exact same SnowC shown above.

What if the entire singleton resolves on one line? And importantly what of subsequent additional elements like b() in this line of C code?

if (q) a(); b();

In theory this could be done by leaving it mostly as is with the non-trailing semicolons intact.

if (q) a(); b()

That is actually valid SnowC and will convert to the previous C one liner. However, when that C one liner converts to SnowC, it will get broken up.

if (q) a()
b()

The reason for this choice can be seen when there are line breaks which can cause trouble. Consider this SnowC.

if (q)
  a(); b()

If you are a real C programmer, hopefully that did not cause some life threatening allergic reaction. Which of these two possibilities is correct when it converts to C?

if (q) {
  a();
}
b();

Or…

if (q) {
  a(); b();
}

Both are reasonable interpretations.

While the first looks good at a glance, the second follows the simple SnowC rules. And for a few good reasons. What we can be sure about is that the following C code won’t ever be possible to generate by converting from SnowC.

if (q)
  a(); b();

Currently converting from C to SnowC, the rule is that singletons get split up if they resolve on the same line as another statement that would not share its level.

To see why this must be consider the following round trip sequences of conversions. Following the conversions vertically, on the left is C → SnowC → C and on the right is SnowC → C → SnowC.

C:                     SnowC:
    if (q)                if (q)
      a; b;                 a; b

   |                     |
   V                     V

SnowC:                 C:
    if (q)                if (q){
      a                     a; b;
    b                     }

   |                     |
   V                     V

C:                     SnowC:
    if(q) {               if (q)
      a;                    a; b
    }
    b;

Note how the SnowC can mostly preserve its form into C and back. But the C structured like this is confusing to begin with, and must get sorted out in SnowC for the braces to actually work when converted back. Because the C style in the upper left is confusing and discouraged (heck, K&R don’t even do it!) it is better to just cut up singletons packed on a line which will prepare them for how SnowC needs them to be. If you want SnowC as found on the right, use braces in the C like a normal person i.e. like the C code in the middle on the right.

If you want to see some serious SnowC puzzles check out my test file called spaceless.c and its corresponding SnowC.

$ echo "=== $F";pr -w$(tput cols) -o1 -s'| ' -tm $D/$F <(./c2snowc -i4 $D/$F)
=== spaceless.c
 void f(int q,int a){                        | void f(int q,int a)
 if(q)a++;                                   |     if(q)a++
 if(q)a++;b++;                               |     if(q)a++
 if(q)a++;else--a;                           |     b++
 if(q)if(q)a++;else--a;                      |     if(q)a++
 if(q)a++;else/**/if(q)a++;else--a;          |     else--a
 }                                           |     if(q)if(q)a++
                                             |     else--a
 if(q)                                       |     if(q)a++
 a();b();                                    |     else/**/if(q)a++
                                             |     else--a
 if(q)a+                                     |
 a;b();                                      | if(q)
                                             |     a()
 if(q){                                      | b()
 a();b();}                                   |
                                             | if(q)a+\
 if(q){                                      | a
 a();b();                                    | b()
 c();}                                       |
                                             | if(q)
                                             |     a();b()
                                             |
                                             | if(q)
                                             |     a();b()
                                             |     c()

Like many of my test files, it is a reminder that polite indenting is a choice in C; in SnowC it is a promise. It’s also an unusual example of a conversion to SnowC that results in more lines.

But those single singletons are relatively easy. Let’s take a look at more serious level hassles.

Check out this normal example from K&R p56, where the braces of the do control keyword control the indent. Also note that the indent is properly controlled for the two if statements too even though there are no braces. (C left, SnowC right)

 /* itoa: convert n to characters in s */                               | /* itoa: convert n to characters in s */
 void itoa(int n, char s[])                                             | void itoa(int n, char s[])
 {                                                                      |     int i, sign
     int i, sign;                                                       |     if ((sign = n) < 0) /* record sign */
     if ((sign = n) < 0) /* record sign */                              |         n = -n /* make n positive */
         n = -n; /* make n positive */                                  |     i = 0
     i = 0;                                                             |     do  /* generate digits in reverse order */
     do { /* generate digits in reverse order */                        |         s[i++] = n % 10 + '0' /* get next digit */
         s[i++] = n % 10 + '0'; /* get next digit */                    |     while ((n /= 10) > 0) /* delete it */
     } while ((n /= 10) > 0); /* delete it */                           |     if (sign < 0)
     if (sign < 0)                                                      |         s[i++] = '-'
         s[i++] = '-';                                                  |     s[i] = '\0'
     s[i] = '\0';                                                       |     reverse(s)
     reverse(s);                                                        |
 }                                                                      |

To pull this off requires parsing the code deeply enough to recognize all 6 of the possible control words that can trigger a singleton (if, while, for, switch, do, else) and recognize when they are not going to be using braces. Just identifying those is a big job. When I finally got that working I was pretty relieved. Mostly that it was even possible.

But it gets worse. The C language allows an unhealthy mix of brace levels and singleton levels. While experimenting with demented code that nobody would ever use (I hope!) I discovered something very unnerving. I slowly started to realize that simply tracking the brace depth and the singleton level depth was not going to do the job. (Theoretically; in practice it mostly would.)

Check out this sample program where I’ve labeled the conditions to reflect the brace depth (e.g. B1) and also the singleton depth (e.g. S1). (mixed_lvls.c, C left, SnowC right)

 void main (int argc, char **argv) {                                    | void main (int argc, char **argv)
  if (B1) {                                                             |   if (B1)
    while (B1S1)                                                        |     while (B1S1)
      for (B1S2)                                                        |       for (B1S2)
        if (B2) {                                                       |         if (B2)
          if (B2S1)                                                     |           if (B2S1)
            if (B2S2)                                                   |             if (B2S2)
              B2S2Resolve();                                            |               B2S2Resolve()
          if (B2S1)                                                     |           if (B2S1)
            if (B2S2)                                                   |             if (B2S2)
              B2S2Resolve();                                            |               B2S2Resolve()
            else //B2S2                                                 |             else //B2S2
              B2S2ElseResolve();                                        |               B2S2ElseResolve()
        }                                                               |   else if (B1)
  } else if (B1) {                                                      |     while (B1S1)
    while (B1S1)                                                        |       for (B1S2)
      for (B1S2)                                                        |         if (B1S3)
        if (B1S3)                                                       |           B1S3Resolve()
           B1S3Resolve();                                               |   else // B1
  } else // B1                                                          |     while (B1S1)
    while (B1S1)                                                        |       for (B1S2)
      for (B1S2)                                                        |         if (B1S3)
        if (B1S3)                                                       |           B1S3Resolve()
          B1S3Resolve();                                                | // End main()
 } // End main()                                                        |

What’s important to note here is that although chains of braceless singletons like if (a) if (b) if (c) do_this(); do increase indent depth, when the do_this() finally runs you can’t just set the singleton depth to zero and call it a day. That whole thing may be nested in one or more sets of braces that each have their own chain of singletons to unwind!

When I realized this, I had to scrap a lot of work and rethink the whole deal. And for what? A crazy obscure problem that in practice would hardly ever come up. But damn it, I was committed to doing it as well as I could.

The solution I came up with is that I create a heap array where I can store the singleton depth for each brace level. This is the only way an accurate unraveling of the most complex structures can be done. The initial allocation allows for ten brace levels deep. Is that the nesting depth limit then? No. If you go beyond ten, it will allocate twenty; if you fill that up, forty more, etc. until you run out of RAM.

This is my test of the dynamic array provision storing a long chain of singletons in a clause 11 brace levels deep.

 int main(int c, char **v) {                                 | int main(int c, char **v)
 if (q) {                                                    |   if (q)
 if (q) {                                                    |     if (q)
 if (q) {                                                    |       if (q)
 if (q) {                                                    |         if (q)
 if (q) {                                                    |           if (q)
 if (q1)   {                                                 |             if (q1)
 if (q2){                                                    |               if (q2)
 if (q3) {                                                   |                 if (q3)
 if (q) {                                                    |                   if (q)
 if (q) {                                                    |                     if (q)
 for (qS) a();                                               |                       for (qS) a()
 if (qS)                                                     |                       if (qS)
 if (qS)                                                     |                         if (qS)
 if (qS)                                                     |                           if (qS)
 if (qS)                                                     |                             if (qS)
 if (qS)                                                     |                               if (qS)
 if (qS)                                                     |                                 if (qS)
 if (qS)                                                     |                                   if (qS)
 if (qS)                                                     |                                     if (qS)
 if (qS)                                                     |                                       if (qS)
 if (qS)                                                     |                                         if (qS)
 if (qS)                                                     |                                           if (qS)
 if (qS)                                                     |                                             if (qS)
 if (qS)                                                     |                                               if (qS)
 if (qS)                                                     |                                                 if (qS)
 a++;                                                        |                                                   a++
   } } } } } } } } } }                                       | // Final line comment immediately after action.
 }                                                           |
 // Final line comment immediately after action.             |

If you understand how ugly that is, buckle up, it gets quite a bit worse! I’ll save that for the next post.

--------------------------

For older posts and RSS feed see the blog archives.
Chris X Edwards © 1999-2026