RSS

Monthly Archives: December 2014

Chapter Unknown (Mercury)

Mercury

© Michael Eric Oberlin, Dec. 24, 2014

 

“You claim to be one of them, material. You do so with your mind and intellect; but had you consulted with your wisdom, and your heart, you would know that you are one of us.”

Her voice moved like oil over water, a drizzling cascade of color and feeling. There was conviction and power in it, a contagious form of conviction, which made one wonder if the sheer sound of it could compel the sky to fall and the ocean to part.

A man with long dark hair and a thin and pointed patch of beard turned in the darkness, hands outstretched. He was shirtless and barefoot, watching, feeling, and knowing. “Either the mind is wrong or the heart is wrong; they can’t both be correct in a deterministic universe. If my spirit bets on my mind, what good could come of listening to the heart?” He spoke with poetry and rhythm, every syllable a self-solving riddle that rolled off of his tongue.

 

Fire and smoke send some creatures running, and others rummaging to investigate. So it was, with the village now called Dust.

The town was riverside amidst jungle and brush, inaccessible save by water. A drought had lowered the river and dried the structures to the point of being brittle and flammable. Uncontrolled fires were a low risk, so close to the water, but one day the compulsion to expel flames was countered by a greater compulsion to set them.

By the time the local sprite princes, the dragons, went to investigate, it seemed that everyone was gone or dead. Homes lay broken, cracked, and smoldering. The smell of ash and soot was unmistakable and overpowering, along with a very vague malignance. Cerdwyn walked among the ruins, fearing no heat against her dragon feet, but worrisome of the loss of all evidence to entropy.

It could have been an accident, she supposed.

 

“Yet you, young Raven, remain indeterminate in your deterministic universe. Your heart strays one way, your mind the other. Trust, now and then, in what you do not know. You may rely on more than what your experience alone tells you.”

The man grinned for a moment, almost snickered, then exhaled. This cavern was as black as ink, but he knew the voice so well he welcomed it more than sunlight. He would find her. “Then that is the real riddle; the one that doesn’t resolve itself. What may I rely on, which I cannot yet know?”

 

She moved like silk through one broken home, scanning for signs of struggle and strife. Her tail coiled and a ball of raw force formed in her palm, bending the light with its tensile vibrations. The broken town made her angry.

She had never made any effort to reveal herself to the people of the village, and never imagined that she would gain any interest. Dragons are reclusive spirits, more prone to melting into rocky dens and insurmountable peaks than roaming socially among materials. Still, there was a peace to this town. If a raider was responsible for this, then her bones ached to fight it.

As she turned a corner, a vision, in the corner of her eye, gave her the most extraordinary start. The ball of force burst in her hand; her left claw raised up to shield herself; but the interloper in her universe was not a threat. So small, yet so still.

 

“If you only rely on what you have seen before, then you are living entirely in the past.” The voice was in his ear now. He slowed to a stop. “And if you speculate too much, you are living only in the future; a future which very much depends upon your heart. This is the wisdom I have for you, your own solution is yours to find.”

Of course it was. She didn’t know, she couldn’t; unfortunately, neither did he. At least not yet. “I always appreciate your advice, Cerdwyn; but whatever you think of my heart, you my body is not a sprite’s, let alone a dragon’s.”

 

A boy, perhaps no older than two, in a cradle of a bed. There was the deepest sadness on the child’s face, and yet, even before the enrapturing eyes of a dragon, so much courage. He was the only living person left, hopeless against the wilderness, and yet, so resolved. She felt the air, felt for movement and intrusion of others amidst the home, and satisfied, knelt before him.

He met her eyes so easily. His eyes were red from crying, but long since dried. He had the strangest of hopes behind them, the kind that one only felt when so much horror had passed, he could only hope for dawn to break, and even the slightest good to come down upon him.

Something else caught Cerdwyn off guard. The nameless boy ran forward, suddenly, off of the bed, and hugged her legs. Protect me, save me, take me. Maybe it was the moment of recognition that she wasn’t here to hurt anyone, wasn’t here to hurt him, that did it. Maybe it was just a plea to get on with it.

 

Lips pecked his cheek, and an arm wrapped around him with a jovial love. Her scales seemed to shimmer even in the darkness, her nails almost glowing. Raven never knew his real parents, and if they were alive at all, didn’t have much of an interest in finding them at this point; he was found, in the ashes of Dust, by Cerdwyn.

Of course, the dragons had nothing to do with the calamity that befell the village. None of them ever found out, either; but a select few roamed down, off of the mountain side, to investigate. Cerdwyn was among them. He was rescued by her strong heart.

 

Cerdwyn almost spoke, but recognized the danger in it. The voice of a dragon was unparalleled, every breath a weapon. She didn’t speak to the child for quite some time. What she did do was lift it, above her head, grasping his torso with two of her strong hands. She looked up at the child, and she saw something familiar.

It was not unheard of for dragons to take part in the raising of a child, but to raise one themselves was a rarity. It might even be a unique occurrence. However, the light and life in the child, the shimmer of intelligence, was something that needed protection. Perhaps she didn’t find him at all, perhaps he found her.

 

Raven knew how fortunate he was to ever have witnessed a dragon at all, let alone to be raised by them. They remained at the edge of myth, yet very real. He remembered little or nothing before that moment, save for his first witnessing of Cerdwyn.

The stories of enormous fire-breathing and flying lizards were barely the shadow of the truth. Her skin shone like it was made of gemstones; her four slender arms moved like waves, each half-foot long finger and its magnificent claw extending gracefully from the end. Her wings were ten feet in span, every bone twisting the light ever-so-subtly like they were made of some golden damask.

Most enchanting of all were her eyes, not piercing or flaming as the stories said, but hypnotic. They seemed to glow with a soul of their own, tickling Raven’s skull with all of the possibilities that her mind contained.

In the end, it seemed that he had an affect on her as well; as for whatever the reason, she raised him, a material child of a much shorter lifespan than any dragon, as her own. He had no idea what his original name was, but for his intelligence, the dragons simply called him “Raven”.

Raven turned and hugged Cerdwyn back, careful not to prick himself on the row of spines along her back. Her golden tail swayed in the half-light, and beneath the many veils of her voice, of his mother’s voice, he found the slightest twitch of a certain kind of sadness. It used to be strong, but was lesser all the time, and this could only mean a single thing. “I’ll be leaving soon, won’t I.”

The same golden eyes looked into his. Raven already knew, she didn’t have to answer; but she did. “It’s in your heart. All dragons are itinerant. You will carry a mixed and unusual legacy with you.”

Dragons, elusive as they are, are manifestations of Mercury. The many legends say that they guard treasure, or they seek treasure, but the truth was so much simpler. With all the wisdom that they gathered throughout their long lives, they were treasure.

Through all of their travels, Raven had felt Mercury manifest itself within him, too; though only as it could for a material. He wasn’t made of the stuff, not like Cerdwyn was; but he had developed an enormous amount of power in it. He could conjure up the dragon’s breath in an instant; he could freeze water and throw ships to land.

“When?” said Raven. He thought that he might have something more sophisticated to say than that, but there were far too many possibilities to weed through. There were too many unknowns. He didn’t particularly want to go.

A tear streaked down Cerdwyn’s face, as she smiled, just a little. In what felt like the blink of an eye, she was across the room, snatching a small glass vial up from a table full of alembics and mortars. She brought it to her face, and captured the tear, then with the press of her hand, melted the vial shut, rounding it in her warm palm into a bead with the slightest of hoops over the top.

She slipped a silver chain through the hoop, and hung it on Raven’s neck, like an amulet. He’d never seen her cry before, he wasn’t sure it was even possible. She kissed him on the forehead. “Whenever your heart is in it, go.”

 
Leave a comment

Posted by on December 27, 2014 in Fiction, The Alchemist

 

Tags: , , , , , , ,

Software Language Engineering: Establishing a Parser, Part Two (Early Edition)

Introductory

So, you’ve ready part one, and you’re at least familiar with a visitor pattern, right? If not, I strongly encourage reading the two injected sections first.

A parser delegates the vast majority of its work to a Visitor. More appropriately stated, it depends upon the Visitor in order to do its work, as the Visitor is responsible for creating the requested nodes.

PhraseStructure classes

I have a simple extension of Visitor which I have created purely for the sake of future modifications. It’s called PhraseStructure. At the moment, it looks like this:

package oberlin.builder.parser;

import oberlin.builder.*;
import oberlin.builder.parser.ast.AST;
import oberlin.builder.visitor.Visitor;

import java.util.*;

public interface PhraseStructure extends Visitor {
}

….which makes it a marker interface. However, should you or I choose to add specific behavior to the Visitor which strictly relates to this program, it’s an excellent low-footprint stand-in.

The point where it, and by that I also mean Visitor, shows its worth is in AlgebraicPhraseStructure.

package oberlin.algebra.builder.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

import oberlin.builder.parser.Parser;
import oberlin.builder.parser.PhraseStructure;
import oberlin.builder.parser.SourcePosition;
import oberlin.builder.parser.ast.AST;
import oberlin.builder.parser.ast.EOT;
import oberlin.algebra.builder.nodes.*;

public class AlgebraicPhraseStructure implements PhraseStructure {
    
    private Map<Class<? extends AST>, BiFunction<Parser<?>, 
        SourcePosition, ? extends AST>> map = new HashMap<>();
    {
        map.put(Program.class, new BiFunction<Parser<?>,
                SourcePosition, AST>() {
            @Override
            public Program apply(Parser<?> parser,
                    SourcePosition position) {
                Program program = null;
                SourcePosition previous =
                    parser.getPreviousTokenPosition();
                AST currentToken = parser.getCurrentToken();
                
                Equality equality = (Equality) parser.getVisitor()
                        .visit(Equality.class, parser, previous);
                program = new Program(previous, equality);
                
                if(!(currentToken instanceof EOT)) {
                    parser.syntacticError("Expected end of program",
                            currentToken.getClass().toString());
                }
                
                return program;
            }
        });
        map.put(Equality.class, new BiFunction<Parser<?>,
            SourcePosition, AST>() {

            @Override
            public AST apply(Parser<?> parser,
                    SourcePosition position) {
                Equality equality = null;
                List<AST> nodes = new ArrayList<>();
                SourcePosition operationPosition =
                    new SourcePosition();
                
                parser.start(operationPosition);
                //parse operation
                AST operation = parser.getVisitor().visit(
                        Operation.class, parser, operationPosition);
                nodes.add(operation);
                if(parser.getCurrentToken() instanceof Equator) {
                    nodes.add(parser.getCurrentToken());
                    parser.forceAccept();
                    nodes.add(parser.getVisitor().visit(
                            Operation.class, parser,
                            operationPosition));
                } else {
                    parser.syntacticError("Expected: equator",
                            Integer.toString(
                                parser.getCurrentToken().getPosition()
                                .getStart()));
                }
                parser.finish(operationPosition);
                
                equality = new Equality(operationPosition, nodes);
                return equality;
            }
            
        });
        map.put(Operation.class, new BiFunction<Parser<?>,
            SourcePosition, AST>() {

            @Override
            public AST apply(Parser<?> parser,
                SourcePosition position) {
                
                Operation operation = null;
                List<AST> nodes = new ArrayList<>();
                SourcePosition operationPosition =
                    new SourcePosition();
                
                parser.start(operationPosition);
                //parse identifier
                AST identifier = parser.getVisitor().visit(
                        Identifier.class,
                        parser, operationPosition);
                nodes.add(identifier);
                //look for operator
                if(parser.getCurrentToken() instanceof Operator) {
                    nodes.add(parser.getCurrentToken());
                    parser.forceAccept();
                    nodes.add(parser.getVisitor().visit(
                            Operation.class,
                            parser, operationPosition));
                }
                parser.finish(operationPosition);
                
                operation = new Operation(operationPosition, nodes);
                return operation;
            }
            
        });
        map.put(Identifier.class, new BiFunction<Parser<?>,
            SourcePosition, AST>() {

            @Override
            public AST apply(Parser<?> parser,
                SourcePosition position) {
                
                Identifier identifier = null;
                List<AST> nodes = new ArrayList<>();
                SourcePosition identifierPosition = new SourcePosition();
                
                parser.start(identifierPosition);
                if(parser.getCurrentToken() instanceof LParen) {
                    nodes.add(parser.getCurrentToken());
                    parser.forceAccept();
                    
                    nodes.add(getHandlerMap().get(Operation.class)
                            .apply(parser, identifierPosition));
                    parser.accept(Operation.class);
                    
                    nodes.add(parser.getCurrentToken());
                    parser.accept(RParen.class);
                } else if(parser.getCurrentToken()
                    instanceof Nominal) {
                    nodes.add(parser.getCurrentToken());
                    parser.forceAccept();
                } else if(parser.getCurrentToken()
                    instanceof Numeric) {
                    nodes.add(parser.getCurrentToken());
                    parser.forceAccept();
                } else {
                    parser.syntacticError(
                            "Nominal or numeric token expected",
                            parser.getCurrentToken().getClass()
                                 .toString());
                }
                parser.finish(identifierPosition);
                identifier =
                    new Identifier(identifierPosition, nodes);
                
                return identifier;
            }
            
        });
    }
    
    @Override
    public Map<Class<? extends AST>, BiFunction<Parser<?>,
        SourcePosition, ? extends AST>> getHandlerMap() {
        // TODO Auto-generated method stub
        return map;
    }
    
}

For all of the code, you’ll note that there’s only one method. getHandlerMap() returns a map, intrinsic to the PhraseStructure, which maps classes (of any extension of AST) to functions which return them. These functions, specifically BiFunctions, accept only a Parser, with all of its delicious utility methods, and a SourcePosition so that they have an idea where they’re looking. All necessary data is in those two items alone.

A Note on Source Position

If you’ve been paying very close attention, you may have noticed that SourcePosition isn’t strictly necessary to translate. You’re right, mostly; but when something goes wrong, it is SourcePosition which tells you where the problem showed up, and what you need to tinker with in order to properly format the program.

It wasn’t always like this. Early compilers would simply indicate that the program was misformatted. More likely, just print “ERROR”, as the notion of software development (which didn’t involve punching holes in punch cards) was relatively young, and ethics weren’t really a thing yet.

This wasn’t a big deal, while programs were generally only a few lines and had exceedingly small lexicons of keywords. When Grace Murray Hopper put together A-0, the idea of adding sophisticated error reporting would have seemed like over-programming; mostly because it would have been over-programming.

As time went on, and machines got more sophisticated, having an error in your code could take days to find. If you had more than one error, then you were really in trouble. So, eventually, a team came up with the idea of reporting the exact point where the format failed, and history was made. (I’m not sure who that was, so if anyone knows, please inform me through the comments.)

Today, every well-designed AST is aware of exactly where it, or its constituents, begin and end. If you want to be especially sophisticated, you can have it remember line number, and even character number, too.

Our current edition of our algebra-like language is generally one-line-only and relatively domain specific, but memorization of where the ASTs go wrong provides room for growth.

Visit Handlers

If you don’t remember specifically, Visitor’s visit is not a complicated method.

public default AST visit(Class<? extends AST> element,
        Parser<?> parser, SourcePosition position) {
    AST ast = getHandlerMap().get(element).apply(parser, position);
    return ast;
}

It simply retrieves the map, grabs the BiFunction associated with the provided element, and applies it to the parser and an initial source position. From there, all work goes on in the map.

The visit handlers themselves can get pretty messy, if you aren’t careful. They begin by initializing their specific brand of AST to null. A NullaryAST or an Optional might be better here, as I have a serious aversion to methods that can return null, but I haven’t made that change yet. This AST is the item which will be initialized through the context of local nodes.

Next, a SourcePosition is initialized. This will be the element passed to the constructor for our AST. When Parser.start(SourcePosition) is called, it updates the starting point of SourcePosition. When Parser.finish(SourcePosition) is called, it updates the end point. These are set to Parser’s currently known coordinate in the code. Thus, before anything else is done, Parser.start(…) is called.

After the SourcePosition has been started, the class of each token is checked against allowed conditions. As such, the bulk of these methods are conditionals. It’s here that I must explain the usage of Parser.accept(…) and Parser.forceAccept().

Parser.accept(…) checks the class of the current token against the provided one, and if they match, increments the internal pointers. If not, it reports a syntactic error, and leaves the pointers alone. Since the pointer is left alone, additional nodes can still be parsed, and multiple errors can be caught, even in the sake of a token simply being missing or skipped. Parser.forceAccept() always accepts the current node, regardless of its type, and increments the pointers. (In fact, it is called from within accept(…) after the conditional checks are completed.

Once all possibilities have been checked for, the AST is initialized and returned. If at any point no possibilities remain for this token, a syntax error is thrown, and the program continues to parse (even though it cannot complete the tree).

Is There Another Way to Do This?

There’s always another way, but that doesn’t mean that it’s necessarily better. One method might be catching customized exceptions on a distinct schedule, which also works pretty well; the down side is that it only allows for the detection of a single error at a time. Another would be the construction of a string representing the AST types, and usage of a regular expression on it; but as I’ve said before, the construction of improper code, even if it compiles, can create devastatingly slow regular expressions at seemingly arbitrary times.

I’ve experimented with both on the way to this code, which is precisely why writing this section took so much longer than the others. There are probably dozens of other readily available methods which I haven’t even thought of yet. One of them, somewhere, might even be faster or sufficiently more effective than the visitor pattern.

This is not me saying that the visitor pattern is perfect, either. This implementation of visitor has a lot of marks against it. It is extremely tightly coupled, for starters, as loose as the interface alone may be. It uses “instanceof” all over the place, which begs for the implementation of further methods to keep to an OOP standard. It has many anonymous classes around, which substantially increase the memory footprint. The slightest of mistakes in the layout of the visitor functions will result in an unbounded recursion, which will quickly and almost silently crash your program, so it is not recommended for avant garde programming—always start with a properly reduced Backus Naur Form of your language. I could go on, such as with the many potential issues with secondary delegation, which the visitor pattern survives on, but this more than covers it.

My advice? Use ethical method names, comment until your fingers bleed, trigger exceptions everywhere the program pointer shouldn’t be, and benchmark benchmark benchmark. In select cases, the Visitor is still your friend, provided that you treat it like a sacred relic wired to a bomb.

Final Notes

You may notice that this is awfully similar to the enumeration used for scanning. You can, in fact, create a Scanner from a Parser, by treating every character as an individual token. However, this has not been done in a long time, as regular expressions are quite reliable for cases like this. I may yet develop a Scanner from a Parser, but only as an example, this does not mean that I recommend it.

You can think of the individual differences between one language and another as the impulse behind these enumerations and mappings. Parser will always be Parser, PhraseStructure will always be PhraseStructure. However, when you need to compile a specific language into an AST tree, the features that make that language what it is can all be stored in the enumerations and maps. Because of this, this API allows for rapid construction of builders.

Next, we talk about identification tables.

 

Tags: , , , , , , , , , , , , , ,

Visual Feedback on an Abstract Parsing Tree with JavaFX

I honestly didn’t expect to be writing this, but it seems fair.

In the past few editions, I’ve been discussing the AST. It can be overwhelmingly complicated for a complete program; so I’ve been using a simple, single line equation as the sample. Unfortunately, that isn’t very realistic; and it would be very helpful to have a procedurally generated visual tree available. That tree is what this lesson is all about.

At first I considered using a graphical tree style, like javax.swing.JTree; but that can be painfully over-simplistic in itself. I would prefer to outline the material the same way I would draw it on a white board (which, if you’re wondering, I do). The best way to do this? JavaFX.

JavaFX whiteboard abstract syntax tree

Graphical AST tree rendering, through JavaFX/F3

If you aren’t familiar with JavaFX, please do me a favor and tolerate the name. It was originally F3, for Form-Follows-Function. I kind of liked F3, until some marketer decided that “JavaFX” sounded better. Functionally speaking, it’s an excellent revision on how user interfaces are designed in Java. I fully stand behind it. It allows for XML structuring and CSS styling, just like a web page, to more hard-coded controls. This is much, much faster; and it allows for significant beauty in user interfaces. However, it works very differently from things like Swing and AWT; and while I’m certain that it isn’t the first API to do so, it takes some getting used to.

I fully intend to write a true tutorial on all of JavaFX on some point. Do you need to understand it to understand translators? Absolutely not. However, this code does work. It is not part of the Github repository, as it is technically a tangential project; but the same license (GNU GPL) applies to it and you are welcome to copy it token for token. I’ll put it up on Github as I get the chance. I’ll make a few minor comments along the way to help you follow it.

1. The Basic Application

We have exceedingly few needs for our app. It simply reads a program from a stream, parses it, and feeds the parse tree to a custom node, which displays it graphically. Accordingly, the program code is rather small. I’ll begin by displaying it, then I’ll spend a moment piecing it together in English for you.

package oberlin.builder.gui;

import oberlin.algebra.builder.AlgebraicBuilder;
import oberlin.builder.parser.ast.AST;
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.stage.*;

public class GUIMain extends Application {

    public static void main(String...args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Pane root = new Pane();
        root.getStyleClass().add("backing");
        
        Scene scene = new Scene(root);
        scene.getStylesheets().add(GUIMain.class.getResource("tree.css").toExternalForm());
        
        primaryStage.setScene(scene);
        root.setMinWidth(640.0);
        root.setMinHeight(480.0);
        
        populate(root);
        
        primaryStage.show();
        primaryStage.centerOnScreen();
    }
    
    private void populate(Pane p) {
        /*
         * This is simply an example, so I've ignored input for now.
         * In theory, you would replace the line below (containing
         * hard code) with an input loop.
         */
        AST program = (new AlgebraicBuilder()).getParseTree("1+2");
        
        p.getChildren().add(new GUITree(program));
    }
}

All JavaFX/F3 programs begin with Application.launch(String…args). JavaFX programs run in what is effectively their own thread, and more so than with Swing-based programs. Launch parses arguments and stores them in their own object, appropriately called Parameters. They can be accessed, at any point later on, via Application.getParameters(). Our available overloads and customizations cut out for a moment, then come back in in the start(Stage) method.

Stage is basically where Frame would be; but it’s a little more complicated than that. Unlike Swing and AWT, which were designed to be platform independent, JavaFX is designed to be hardware context independent. What you are writing here will work equally well on a PC, tablet, and smart phone; as well as anything else built (now or later) that maintains a JavaFX compatibility standard. Thus, what might otherwise be called a frame or window is referred to as a stage, as it might be neither of those things.

You’ll notice that the instantiated Pane is given a style class. If you aren’t familiar with CSS, a style class is what’s used to differentiate between one element and any number of others which, otherwise, would look exactly like it. Thus, it allows CSS to pick and choose which elements of the layout it is styling at a given moment. I’ve chosen “backing” as the name for this element, as it is the backboard of our tree. You will also note that, two lines later, the CSS file itself is loaded.

Next, a Scene is created. Scenes are critically important, and distinct from stages. While a stage represents the context that the layout is drawn in, the scene represents the actual controls and constraints within that space. Thus, while many aspects of Stage are immutable (and unknowable), Scene allows for greater flexibility. JavaFX sees to it that they correspond, so don’t worry about that.

Scene is styled through its root element, which in this case is our pane. You’ll notice that instead of the stricter setWidth() and setHeight() that you might be familiar with from Swing, we are setting a minimum on these bounds. That minimum is not guaranteed, as the display may not be capable of it, but it is treated as a general rule to be followed if at all possible. In this case, I’m going for classic analog low-def TV resolution, 640 width by 480 height. (Looking back, those numbers might be inadequate, but for now they’re quite functional.) If this is too small for you, the frame—if it is a frame, anyway—is easily resizable.

Populate() is a method I wrote to add the paraphernalia to the scene; but note that afterwards we call show(). This is very important, as otherwise our stage will be constructed in memory, but never displayed to the screen. Additionally, there will be no way to kill the JavaFX thread save for a hard interrupt. Once shown, the closing of the primary stage will flag the program to terminate.

1.1. Populate

It’s a generally good habit, but not a necessary one, to populate your frame in a separate and dedicated method. This is what I do here, even though for the moment, I only have one control to add.

The AST method should be old news; it’s a stand-in, for the moment, for an actual code-reading portion. (I’m assuming that you’re looking to compile more than just “1+2”.) GUITree is a custom JavaFX node, which I will explain next. Note that to add a node to a program, you must take some structure (not yet visible) in the scene graph (stemming from your chosen root), and get its Children as a modifiable list. Then, you must add that node to the list.

Note that after a stage is visible, precious little of the scene can be changed save for through the constraints built into it. I’m not going to touch on Expressions and Bindings here, but know that if you pull something that doesn’t play by JavaFX’s rulebook, it will throw an ApplicationException and your program will not launch. Thankfully, while exceedingly picky, that rulebook is small. If you call show() and then try and add a child, you will have problems; it must be the other way around.

If you’re curious, hiding a rendered stage does not count for making it modifiable. You must give it your entire concept first, then make it visible. If you’re familiar with OpenGL, you’ll already understand why.

2. The Tree Itself

The tree is a custom JavaFX node, which I admit is rarely necessary. Still, most of the entities that make it work are core to the API.

package oberlin.builder.gui;

import oberlin.builder.parser.ast.AST;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.CubicCurve;

import java.util.function.IntSupplier;

public class GUITree extends AnchorPane {
    private Bounds bounds = new BoundingBox(0, 0, 640, 480);
    private AnchorPane framing = new AnchorPane();
    private double edgeSize = 0.10;    //ten percent additional length beyond edges of framing
    
    public GUITree(AST ast) {
        this.setMinWidth(bounds.getWidth() * (1 + edgeSize));
        this.setMinHeight(bounds.getHeight() * (1 + edgeSize));
        
        configureFraming();
        
        addNode(ast);
    }
    
    private void configureFraming() {
        framing.setLayoutX(edgeSize * (bounds.getWidth() / 2.0));
        framing.setLayoutY(edgeSize * (bounds.getHeight() / 2.0));
        framing.setMinWidth(bounds.getWidth());
        framing.setMinHeight(bounds.getHeight());
        
        this.getChildren().add(framing);
    }
    
    private ASTNode addNode(AST ast) {
        return this.addNode(ast, new Marker(0), new Counter(), 0, null);
    }
    
    private ASTNode addNode(AST ast, IntSupplier stepsDown, IntSupplier stepsAcross, int index, ASTNode parent) {
        ASTNode node = new ASTNode(ast, stepsDown.getAsInt(), stepsAcross.getAsInt());
        
        //AnchorPane stuff
        calculateAnchoring(node, parent);
        
        framing.getChildren().add(index ++, node);
        
        final StringBuilder tooltipText = new StringBuilder();
        IntSupplier across = new Counter();
        for(AST kid : ast.getContainedNodes()) {
            tooltipText.append(kid.getClass().getSimpleName()).append(" ");
            ASTNode child = addNode(kid,
                    new Marker(stepsDown.getAsInt() + 1),
                    across,
                    index,
                    node);
            CubicCurve cubic = createCubicCurve(node.getNoodleRoot(), child.getTopCenter());
            framing.getChildren().add(cubic);
        }
        node.getType().setTooltip(new Tooltip(tooltipText.toString()));
        
        return node;
    }
    
    private CubicCurve createCubicCurve(Point2D p1, Point2D p2) {
        CubicCurve curve = new CubicCurve();
        
        curve.setStartX(p1.getX());
        curve.setStartY(p1.getY());
        
        curve.setEndX(p2.getX());
        curve.setEndY(p2.getY());
        
        curve.setControlX1(p1.getX());
        curve.setControlY1(p2.getY());
        
        curve.setControlX2(p2.getX());
        curve.setControlY2(p1.getY());
        
        curve.setStroke(Color.BLACK);
        curve.setStrokeWidth(2.0);
        curve.setFill(Color.TRANSPARENT);
        
        return curve;
    }
    
    private void calculateAnchoring(ASTNode node, ASTNode parent) {
        node.setOrigin(new Point2D(parent == null ? (bounds.getWidth() - node.getBounds().getWidth())/2.0 :
            justifyX(node, parent), justifyY(node)));
        AnchorPane.setTopAnchor(node, node.getOrigin().getY());
        AnchorPane.setLeftAnchor(node, node.getOrigin().getX());
    }
    
    private Double justifyX(ASTNode node, ASTNode parent) {
        final double parentCenter = (parent.getOrigin().getX() + (parent.getBounds().getWidth() / 2.0)
                + parent.getNoodleRoot().getX()) / 2.0;
        final double center = parentCenter
                - node.getBounds().getWidth()
                        * (parent.getAST().getElementCount()) / 2.0; 
        return center + node.getOrigin().getX();
    }
    
    private Double justifyY(ASTNode node) {
        return node.getOrigin().getY();
    }
}

That was a bit much at once, I know. The central pane, called “framing”, is 640 by 480. Framing is offset in each direction by a 5% inset, via the convenient features of AnchorPane.

AnchorPane is one of the few prepared ways to control where a node is rendered, with precision, in JavaFX. You may often need to keep your own tabs on where it is rendered, as getMinX() and getMaxX() will return zero more often than you will believe. However, through direct layout control, you can still manage them.

The method addNode(…) adds a custom object called ASTNode. I’ll cite it for you here.

package oberlin.builder.gui;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import oberlin.builder.parser.ast.AST;

class ASTNode extends VBox {
    private Bounds bounds = new BoundingBox(0, 0, 100, 40);
    private Point2D origin = new Point2D(0, 0);
    private final double expanse = 1.10;
    
    private final AST ast;
    
    private Label type;
    private Label hash;
    
    private ObservableList<ASTNode> kids = FXCollections.observableArrayList();
    
    public ASTNode(AST ast) {
        this.ast = ast;
        type = new Label(ast.getClass().getSimpleName().toString());
        type.setTextAlignment(TextAlignment.CENTER);
        type.setAlignment(Pos.CENTER);
        
        hash = new Label(Long.toHexString(ast.hashCode()).toUpperCase());
        hash.setTextAlignment(TextAlignment.CENTER);
        hash.setAlignment(Pos.CENTER);
        
        VBox vbox = new VBox(new StackPane(type), new StackPane(hash));
        vbox.getStyleClass().add("node");
        vbox.setMinWidth(bounds.getWidth());
        vbox.setMinHeight(bounds.getHeight());
        this.getChildren().add(vbox);
        
        for(AST kid : ast.getContainedNodes()) {
            addKid(new ASTNode(kid));
        }
        
    }
    
    public Point2D getNoodleRoot() {
        return new Point2D(getOrigin().getX() + (getBounds().getWidth() / 2),
                getOrigin().getY() + getBounds().getHeight());
    }

    public ASTNode(AST ast, int level) {
        this(ast);
        
        origin = new Point2D(0, level * bounds.getHeight() * expanse);
    }
    
    public ASTNode(AST ast, int levelDown, int levelAcross) {
        this(ast);
        
        origin = new Point2D(getStepAcrossSize(levelAcross), getStepDownSize(levelDown));
    }
    
    public double getStepDownSize(int steps) {
        return steps * bounds.getHeight() * expanse;
    }
    
    public double getStepAcrossSize(int steps) {
        return steps * bounds.getWidth() * expanse;
    }
    public void addKid(ASTNode astNode) {
        this.kids.add(astNode);
    }
    
    public ObservableList<ASTNode> getKids() {
        return kids;
    }
    
    public Bounds getBounds() {
        return bounds;
    }
    
    public Point2D getOrigin() {
        return origin;
    }
    
    public Point2D getTopCenter() {
        return new Point2D(
                getOrigin().getX() + (getBounds().getWidth()/2),
                getOrigin().getY());
    }
    
    public Label getType() {
        return type;
    }
    
    public void setOrigin(Point2D p) {
        this.origin = p;
    }
    
    public AST getAST() {
        return this.ast;
    }
}

ASTNode is a JavaFX Node as well. It simply maintains a reference to the AST itself, and the general presentation of that AST on the tree. There isn’t a lot here. If you’re wondering what VBox is, it’s an abbreviation for “vertical box”. (Naming a class after an abbreviation is bad practice, but it’s long since done by powers above me; I tolerate it as much as I do “AST”.)

Speaking of bad practice, this would ideally actually use Bindings, but I wrote this in a bit of a rush today and will have to correct that in the future. It is also bad practice to repeat data, which is exactly what this program is doing by re-storing the label text in a separate field. All the same…

I’m going to gloss over a lot of the configuration of the labels, as it’s relatively standard. Know that like any other pane in JavaFX, a VBox can be initialized with a list of its bounded nodes; also, a StackPane has the default behavior of centering its own bounded nodes.

The last thing done in the constructor is the creation of additional ASTNodes for each child node of the abstract syntax tree.  Each of them, in turn, renders their own children. This is not perfect, there is a substantial chance that two lists of nodes will overlap one another; however, it is already excellent for debugging visitor pattern based content. In the end, the GUITree renders each node in an assigned place, with a curved cubic line (technically called a “noodle”) connecting it to its parent and its children.

How does it do that? With IntSuppliers.

3. The IntSuppliers

There are only two of these.

package oberlin.builder.gui;

import java.util.function.IntSupplier;

/**
 * For downward counts; always returns provided number.
 * 
 * @author © Michael Eric Oberlin Dec 23, 2014
 *
 */
class Marker implements IntSupplier {
    private int fix;
    
    public Marker(int fix) {
        this.fix = fix;
    }
    
    @Override
    public int getAsInt() {
        return fix;
    }
}


package oberlin.builder.gui;

import java.util.function.IntSupplier;

/**
 * For counts across; always returns next consecutive number.
 * 
 * @author © Michael Eric Oberlin Dec 23, 2014
 *
 */
class Counter implements IntSupplier {
    private int count;
    
    @Override
    public int getAsInt() {
        return count++;
    }
    
}

IntSuppliers (and really all Suppliers) are part of the java.util.function package, new to Java 8. The great advantage of this package is that a function, or any functional interface, allows you to specify a method that serves as a primitive with conditionally defined values. I know that’s a leap, but I’ve been doing it since long before it was formally adopted into the language and it’s a central totem of functional languages.

We could, in theory and practice, use incrementing and decrementing integers in place of either of these. The problem is that the code gets a lot longer and a lot more cluttered when you do. I prefer the sublime simplicity of packing such behavior into an interface.

Of course, these are not everything. There is one, final, issue.

4. What was that that you said about “CSS”?

The CSS is specific to JavaFX; a complete listing of all of the properties is available here. If you are unfamiliar with the syntax of CSS, you can find an excellent tutorial on it (for HTML, at least) at W3Schools. It isn’t as versatile as Java or C, but its creators pulled many of its properties from C-like languages.

tree.css:

.backing {
    -fx-background-color: lightyellow;
    -fx-insets: 0;
    -fx-padding: 15;
    -fx-spacing: 10;
}

.node {
    -fx-background-color: lightblue;
    -fx-background-radius: 5.0;
    -fx-border-color: black;
    -fx-border-radius: 5.0; 
}

Keep this in the same folder as GUIMain, and it will find it as written.

The CSS styling of JavaFX controls is capable of everything HTML 5 is and then some. It’s an excellent fusion of programming and markup. I encourage you to play with the layout of GUIMain’s scene, and the actual program fed to the builder.

 

Tags: , , , , , , , , , , , , , , , ,

Anouk’s New Creation: Intel Edison Based Spider Dress 2.0

Anouk’s New Creation: Intel Edison Based Spider Dress 2.0

Coolest thing since Iron Man.

 
Leave a comment

Posted by on December 19, 2014 in Uncategorized

 

3D Printed Prosthetics Allow Dog To Run

3D Printed Prosthetics Allow Dog To Run

3D-printed prosthetic legs—for dogs!

 
Leave a comment

Posted by on December 16, 2014 in Uncategorized

 

Warrant

I’ve been thinking a lot lately. I’ve also been a little down with a notorious stomach bug, which hasn’t really put me in the mood to write. Vomiting and heartburn does not put one in a pensive mood, and cognitive fatigue can be so very real. Thankfully, I’m over it.

All the same, the one topic that did keep coming to mind is the nature of my work, and why I do it. I can dispel a few illusions right here; I have been published but I don’t write for the sake of getting published; I program but not for the sake of making a lot of money off of my programming, either. (Not that there isn’t a strong chance of that happening.)

I used to actually believe that the right to call oneself a writer came from publication. Later on, I distilled that down to a writer simply being one who writes; but that isn’t strictly true either. It’s more like being one who is fully capable of writing, and for one reason or another, has the impulse to do it from time to time. For “programmer”, I can say the same. In fact, I can say it for virtually any adjective expressing a craft.

It brought to mind the first time that I ever wrote. I was eight or nine, not in such a good situation, running out of books, and wanting to escape. So, on an impulse, I tried to write a novel. It was much more difficult than my ten-year-old brain had imagined that it would be, and I only got a few pages down before stopping. (I had, for the first time in my life, encountered the phenomenon known as “writer’s block”.) It wasn’t about publication back then, either, though. I think part of the story may have been written in crayon.

Don’t get me wrong, I do intend to publish my entire quartet eventually. My most likely route will be through Amazon’s self-publishing services, depending on how they’re standing at the time. The reality remains that few writers, regardless of the quality of their work, make an enormous amount early on. There are a few people who seem to believe that after publication, due to the apparent quality of my work, I will instantly rise to the status of a one-percenter. (Obviously they don’t know me that well.) It’s undue pressure, and rather distasteful.

It doesn’t help to try and explain to them that the software project I’ve got running in the background is like trying to build a jetliner all by myself; especially given that most of the people who I meet throughout my day have never written a line of Python in their lives. It’s outside of their realm; like trying to describe music to someone with little or no musical taste, or a painting to someone art-blind. It’s also inconvenient.

The thing that all of this comes back to is the notion of the future; of imagining things happening that may or may not even happen at all, which I may or may not ever have any say in anyway. I’ve been guilty of this before. Such talents of imagination are better spent on fictional characters than my own life; at least in that instance I have some control. Thinking about the future is the origin of two things; hope, and fear. When you fall on fear more than hope, it’s time to put it away for a while.

The truth is, I am right where I want to be right now. I live in a beautiful town, I have a wonderful girlfriend, I have an excellent set of tools right in front of me. I have peace of mind. I have a fully equipped kitchen, and a set of friends who truly care about me. I have an awesome hair cut and some sleek jewelry. I have a fucking Keurig, too, which is like a having robot barista in my kitchen. Why does this treasure need to exist in the future? Am I worried it will go somewhere?

There will always be a worse day to come. When the bad day comes, there will be a better one following it. It’s the principle of the yin and the yang. How I look at it is the only choice I get, and it’s as arbitrary as anything else, except that I prefer to think of the permanence of the good days. The last bad day might be said to be the day that I die, and when it happens, I will be able to look back on my life, and say that I have truly lived it to the fullest. I have no say in these things, I need no say in them. I’m cool with it.

The whole idea with creative work is to see to it that it feels more like a game than a job anyway. For me, programming, and writing (including on the blog) usually feel very much like games. They keep my mind active and involved and growing. When they start to feel like a task, it’s an impediment worthy of the same dread as writer’s block.

I haven’t experienced writer’s block in a very, very long time. I figured it out, I learned the signs of its approach, and I learned how to undo it. (In fact, that might make a good non-fiction book right there.) There isn’t any reason why I can’t overcome this new problem as well. Like curing a disease, it begins with understanding it.

I declare here and now that these books will not be written for the sake of publication; they will be written for an audience. They will be written to be good and correct stories, with interesting characters and ethical storytelling. “Good enough” is not good enough for me when it comes to my writing. The same can be said for my code.

Jesus, it can definitely be said for the code.

My real work will always be back here. I am at peace here, it is my circle and my temple. And every day, when I’m finished, I have pushed my mountain one further step; when I’m done, I will have delivered a mountain. That is my warrant.

 
Leave a comment

Posted by on December 15, 2014 in Meta-Media, State of the Moment

 

Tags: , , , , , , , , , , , , , , , ,

Software Language Engineering: The Visitor Pattern

This may feel like a slight detour; but believe me, it’s a necessary one. If you are already fully familiar with the Visitor pattern, you are free to skip this section.

The Visitor pattern is hardly the only way to handle grammar parsing; but I’ve been trying to find a better one for a couple of weeks now, and without much success. Thus, I must recommend it.

The Visitor pattern functions as a way to operate on a structure of objects, like our Abstract Syntax Tree, while evading the encapsulation of the algorithm within those objects. It is an approach from the outside, it visits; thus, the name. Unlike most design patterns, you can actually implement the approach, for most purposes, in a single package. I’ll explain how to do than in the second part of this addition.

1. The Recursive Nature of the Visitor Pattern

First, a word on the unusual structure.

Every visitable object is referred to as an “element”; and every element has an inherent method which receives a single visitor object as its parameter. Additionally, every visitor object has a method which receives an element as its singular parameter.

This may sound rather circular, but the meat of the work is done by the visitor’s method. When visit is called, with a specific instance of the element as its parameter type, a window opens to perform specific operations on the available materials (fields and methods) in the element. This window does not require any specific change to the element’s code.

So when is this visit method called? Every time the element’s inherent visitor-receiving method is called. Is this roundabout?

No. The visitor-receiving method may be called again, on other, related, objects, by the visitor’s element-receiving method. You can exit the circle any time you would like.

For the sake of clarity, imagine that the element’s method is called receive(Visitor visitor), and the visitor’s method is called visit(Element element). I’ll give you a more tangible example.

1.1. The Visitor Pattern at Work

Suppose we have an object structure describing a bicycle. Each bicycle part is an extension of BicycleElement, which implements our accept method. The parts include a number of tires, two pedals, a handbrake, a chain, a seat, and a frame. Each of these elements is interchangeable and has a score of properties that belong to it alone.

Putting together such a class structure is trivial for a Java programmer; but what if we want to inventory the parts of a bike? Display their features on an output stream (for simplicity, System.out), as an example? At the same time, this analysis software must remain separate from the software describing the part, in the name of the open/closed principle and the single responsibility principle. At least, if you’re a principled programmer, with the least bit of concern for the next person to touch your code, you want to follow those.

The problem is solved through double-dispatch. Your analysis class, let’s call it Analyzer, might have a set of Visitors that expect to receive each category of BicycleElement. Once your CompleteBikeElement is prepared, Analyzer would call its accept method with a class of Visitor. The accept method would call the visit method with itself (and all properties exposed), and the visit method would display the properties of the BicycleElement.

Naturally, you might be wondering how you would get the properties of every BicycleElement when only the CompleteBikeElement was passed to the Visitor. That’s where receive comes in. CompleteBikeElement has total access to each of its parts, and can easily pass a visitor (maybe the same one) to each of them.

2. Implementing a Visitor Pattern in a single package

I’ve implemented this in the SLE git under oberlin.builder.visitor.

You only need three classes; Element, Visitor, and VisitHandler. In fact, you arguably only strictly need Element and Visitor. Their code is fairly straightforward, remembering that they are all abstract.

Element:

package oberlin.builder.visitor;

public interface Element {
    public void accept(Visitor visitor);
}

Visitor (feel free to be a little creative with this one):

package oberlin.builder.visitor;

import java.util.Map;

public interface Visitor {
    public default void visit(Element element) {
        getHandlerMap().get(element.getClass()).handle(element);
    }
    
    public Map<Class<? extends Element>, VisitHandler> getHandlerMap();
    
    public default void addVisitHandler(Class<? extends Element> elementClass, VisitHandler handler) {
        getHandlerMap().put(elementClass, handler);
    }
    
    public default VisitHandler getVisitHandler(Class<? extends Element> elementClass) {
        return getHandlerMap().get(elementClass);
    }
}

VisitHandler:

package oberlin.builder.visitor;

public abstract class VisitHandler {
    public abstract void handle(Element element);
}

Done. Now, as an aside to get the point across, let’s build that little bicycle project I was talking about.

2.1. Using the Visitor Package

Let’s start with our bike parts. For the sake of brevity, I’m leaving out package and import statements as they’re fairly self-explanatory.

public class ChainElement implements Element {

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

public class FrameElement implements Element {

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

public class HandlebarElement implements Element {

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

public class PedalElement implements Element {

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

public class SeatElement implements Element {

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

public class TireElement implements Element {

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

They could be anything, of course; in this instance, we’re leaving the actual work to Visitor, and how it gets there to Element. It might even be wise to make it a Java 8 Default Method.

Lastly, the complete bike:

package bicycle;

import oberlin.builder.visitor.Element;
import oberlin.builder.visitor.Visitor;

import java.util.*;

public class CompleteBikeElement implements Element {
    
    private FrameElement frame = new FrameElement();
    private SeatElement seat = new SeatElement();
    private PedalElement leftPedal = new PedalElement();
    private PedalElement rightPedal = new PedalElement();
    private TireElement frontTire = new TireElement();
    private TireElement backTire = new TireElement();
    private HandlebarElement handle = new HandlebarElement();
    private ChainElement chain = new ChainElement();
    
    @Override
    public void accept(Visitor visitor) {
        frame.accept(visitor);
        seat.accept(visitor);
        leftPedal.accept(visitor);
        rightPedal.accept(visitor);
        frontTire.accept(visitor);
        backTire.accept(visitor);
        handle.accept(visitor);
        chain.accept(visitor);
    }

}

So, what does our visitor look like?

package bicycle;

import java.util.HashMap;
import java.util.Map;

import oberlin.builder.visitor.Element;
import oberlin.builder.visitor.VisitHandler;
import oberlin.builder.visitor.Visitor;

public class BikeVisitor implements Visitor {
    @SuppressWarnings("serial")
    Map<Class<? extends Element>, VisitHandler> map = new HashMap<Class<? extends Element>, VisitHandler>();
    {
        map.put(TireElement.class, new VisitHandler() {
            public void handle(Element element) {
                System.out.println("[Tire qualities]");
            }
        });
        map.put(FrameElement.class, new VisitHandler() {
            public void handle(Element element) {
                System.out.println("[Frame properties]");
            }
        });
        map.put(ChainElement.class, new VisitHandler() {
            public void handle(Element element) {
                System.out.println("[Chain manufacturing data]");
            }
        });
        map.put(SeatElement.class, new VisitHandler() {
            public void handle(Element element) {
                System.out.println("[Seat qualities]");
            }
        });
        map.put(PedalElement.class, new VisitHandler() {
            public void handle(Element element) {
                System.out.println("[Pedal qualities]");
            }
        });
        map.put(HandlebarElement.class, new VisitHandler() {
            public void handle(Element element) {
                System.out.println("[Handlebar qualities]");
            };
        });
    }

    @Override
    public Map<Class<? extends Element>, VisitHandler> getHandlerMap() {
        return map;
    }
}

And lastly, our analysis program, which doesn’t even need to be in the same package. (Well, it is, but it doesn’t have to be.)

package bicycle;

public class Analysis {

    public static void main(String[] args) {
        CompleteBikeElement bike = new CompleteBikeElement();
        bike.accept(new BikeVisitor());
    }

}

As you can see, the analysis itself doesn’t have to do much. In the past, a different visitor was implemented for each element, which is still occasionally useful. In this instance, to cut down on the number of classes, I simply cross-reference the class of the element on a map, and retrieve the functional interface with the material to operate on that specific type of element. It’s better practice than using instanceof, not because instanceof is slow, but because it’s usually a red light that you’re overlooking a feature of object-oriented languages. This is also referred to as a “bad code smell“. (Map may also be slightly faster, working it out in my head; but I haven’t done any benchmarking.)

The result?

[Frame properties]
[Seat qualities]
[Pedal qualities]
[Pedal qualities]
[Tire qualities]
[Tire qualities]
[Handlebar qualities]
[Chain manufacturing data]

Of course, any element could have broken itself down further, and may continue to. This is a very flexible pattern, and understanding double-dispatching can lead to some very efficient architectures. When you understand how it works, you’re ready to continue!

A Final Note

Speaking of bad code smell, it is also worth noting that double-dispatching is never to be used unless it is truly necessary. Extensive double-dispatching (or triple-dispatching, or—no. It hurts to think about.) can be very bad code smell in itself. If you’re even a little speculative, you may have noticed that excessively open Visitor patterns are asking for trouble.

Uncontrolled dynamic dispatching is like having your fingers tangled in string, or trying to untie a massive wad of Christmas lights before the holiday because someone put them in a box instead of twist-tying them properly. Dynamic dispatching is the box; used improperly, it promises that a year later, you will be dealing with a massive wad of tied up Labyrinthian code, and you’ll have zero fun untying it. BECAUSE REALLY, WHY DID YOU PUT THE CHRISTMAS LIGHTS IN AN EFFING BOX, MOM!?!?

 
 

Tags: , , , , , , , , , ,