Loading a list of products with GraphQL in BigCommerce widgets

Before we begin, the completed solution is available on GitHub.

BigCommerce offers the ability to create reusable components in the page builder called “widgets”. It comes with a few out of the box but also comes with the ability to create your own. They can be created either by pushing them directly via their API or using their widget builder. For the purposes of this demo, I did everything in widget builder.

One of the features BigCommerce offers is the ability to define custom “schema” for widgets, which is a fancy way to say options that can be edited for each instance of the widget. They can be simple, like the title and background image to use for a banner, but can also get more complex and include things like arrays of properties. One notable type is the “product ID” type, which presents itself to your widget as the ID of a product but in the admin panel is a tool that lets you search products in your store, making it very easy to insert a product teaser or something like that.

Additionally, to fetch data from the store, you can include GraphQL in your widget. The GraphQL query will use data supplied from the admin panel (with the structure defined by the schema) and provide the necessary data for the template. For example specific example, you can include a product ID selector in the admin panel then use GraphQL to get the name, image, link, etc. for the product. BigCommerce provides an example of how to do that.

However, one thing they do not seem to document is how to make the same thing work for multiple products. Sure, you could just define a bunch of separate individual products, but that’s going to get really obnoxious to work with and maintain. One alternative is use an array of products, which is better, but it turns out there’s an even better way to do it: product set.

First, we need to define our schema. In widget builder, create the following schema.json file:

[
	{
		"type": "tab",
		"label": "Content",
		"sections": [
			{
				"label": "Products",
				"settings": [
					{
						"type": "productSet",
						"label": "Product Set",
						"id": "product",
						"entryLabel": "Product",
						"default": {
							"type": "manual",
							"value": []
						}
					}
				],
				"typeMeta": {
					"type": "setSection"
				}
			}
		]
	}
]

This defines an attribute of type “productSet” (i.e. a list of products”) with the ID “product”.

Next, we need to pass the values from that selection into GraphQL. We can further edit the schema and add another element of type “graphQl” that specifies the mapping from schema parameters to GraphQL parameters. We do that in the text highlighted below.

[
	{
		"type": "hidden",
		"settings": [
			{
				"type": "graphQl",
				"id": "graphQueries",
				"typeMeta": {
					"mappings": {
						"productIds": {
							"reads": "product.value.*.productId",
							"type": "Int!"
						}
					}
				}
			}
		]
	},
	{
		"type": "tab",
		"label": "Content",
		"sections": [
			{
				"label": "Products",
				"settings": [
					{
						"type": "productSet",
						"label": "Product Set",
						"id": "product",
						"entryLabel": "Product",
						"default": {
							"type": "manual",
							"value": []
						}
					}
				],
				"typeMeta": {
					"type": "setSection"
				}
			}
		]
	}
]

Next, we need to write the GraphQL query. To do that, create the file query.graphql:

query productsById($productIds: [Int!]) { 
  site { 
    products(entityIds: $productIds) { 
      edges {
        node {
          entityId
          sku
          name
          path
        }
      }
    } 
  } 
}

This query looks up the ID, SKU, URL path, and name of each product for any product in the $productIds parameter (which we passed in via GraphQL mapping). Important to note here is the type: $productIds is an array of non-nullable ints ([Int!]), while in the mapping, we specified the type for each individual element to be a non-nullable int (Int!), which was automatically aggregated to be an array by the widget utility.

Finally, we can actually use this data in the template. In this very limited example, you can see a list of the product names by creating widget.html with the following content:

{{#each _.data.site.products.edges}}
<h3><a href="{{node.path}}">{{node.name}}</a></h3>
<div>SKU: {{node.sku}}</div>
{{/each}}

With that, we have rendered a list of products! See the final result below.

The final result

Creating custom Optimizely Forms elements, the simple way

There are several guides out there on how to create an Optimizely Forms element, including a set of samples provided by the people at Optimizely themselves. However, these examples are often pretty hard to follow, so I figured it would be helpful to write a quickstart guide that covers just what you need.

Before anything else, you can find the code for this on GitHub.

For this example, we’re going to build out a very simple form element. For the purposes of this demonstration, we will create a form element that allows you to enter a minimum and maximum range. This is of course a very simple example, but serves to get the point across. The steps will be: 1. Create the backend model for the form element, 2. Create the backend validators, 3. Create the frontend JavaScript validation and value binding logic, and 4. Create the view.

0. Prerequisites
For this to work, you need an existing Optimizely website with the Optimizely Forms and Newtonsoft.Json packages already installed.

1. Create the backend model
Like any Optimizely content type, we need to define a model. Form elements are pretty similar to blocks thankfully, so you can use the same type of thinking. We will first start out with the skeleton class for the model:

[ContentType(DisplayName = "Min-Max Range", GUID = "80ABAC02-ACE3-4828-81FF-F7F58D322ACD", Description = "A mininum and maximum number")]
public class MinMaxRangeFormElementBlock : InputElementBlockBase, IElementCustomFormatValue, IElementRequireClientResources
{
    public override ElementInfo GetElementInfo()
    {
        var baseInfo = base.GetElementInfo();
        baseInfo.CustomBinding = true;
        return baseInfo;
    }
}

Let’s break down each part. The ContentType notation is the same as used for pages and blocks, so I won’t bother explaining that. InputElementBlockBase is the base class for form elements, similiar to PageData or BlockData. IElementCustomFormatValue means that we will be applying some sort of custom logic to extract the value. IElementRequireClientResources means there will be frontend JavaScript. We also in GetElementInfo indicate that custom binding logic will be applied.

Now, we need to attach data to the model. Like any page or block type, we should define what types of properties we want. For this one, we can set a lower and upper bound (i.e. min must be above the lower bound and max must be below the upper bound).

[ContentType(DisplayName = "Min-Max Range", GUID = "80ABAC02-ACE3-4828-81FF-F7F58D322ACD", Description = "A mininum and maximum number")]
public class MinMaxRangeFormElementBlock : InputElementBlockBase, IElementCustomFormatValue, IElementRequireClientResources
{
    [Display(Name = "Lower Bound", Order = 100)]
    public int? LowerBound { get; set; }
    
    [Display(Name = "Upper Bound", Order = 200)]
    public int? UpperBound { get; set; }

    public override ElementInfo GetElementInfo()
    {
        var baseInfo = base.GetElementInfo();
        baseInfo.CustomBinding = true;
        return baseInfo;
    }
}

Now that we have the model, we need to implement the interface. First, we need to add the value binding by overridding GetSubmittedValue. We will use a tuple of two integers (Tuple<int, int>) as our backend storage format. Interestingly, because we already invoke frontend JS, we can serialize it to JSON on the frontend and just deserialize that. However, if the user’s browser does not support JavaScript, we do need to apply binding logic. Additionally, we have to have a way to get the value as a string for display to users and such, so for that we need to override GetFormattedValue which will just return “Min to Max” (for example, “1 to 4”.

public override object GetSubmittedValue()
{
	var rawSubmittedData = HttpContext.Current.Request.Form;

	var strValue = base.GetSubmittedValue() as string ?? string.Empty;

	var isJavaScriptSupport = rawSubmittedData.Get(EPiServer.Forms.Constants.FormWithJavaScriptSupport);
	if (isJavaScriptSupport == "true") //if the user's browser support JS, then deserialize the value provided by the frontend
	{
		var values = JsonConvert.DeserializeObject<List<int>>(strValue);
		if ((values?.Count ?? 0) != 2)
			return null;

		return Tuple.Create(values[0], values[1]);
	}

	//if the user's browser does not support JS, we need to extract the value ourselves from the HTML raw form fields
	var minName = $"{FormElement.ElementName}_min";
	var maxName = $"{FormElement.ElementName}_max";

	if (!int.TryParse(rawSubmittedData[minName], out var min) || !int.TryParse(rawSubmittedData[maxName], out var max))
		return null;

	return Tuple.Create(min, max);
}

public object GetFormattedValue()
{
	var submittedVal = GetSubmittedValue() as Tuple<int, int>;

	if (submittedVal is null)
		return string.Empty;

	return $"{submittedVal.Item1} to {submittedVal.Item2}";
}

Finally, we need to add the client resources for the JavaScript, which we will create in a subsequent step for all the frontend logic:

public IEnumerable<Tuple<string, string>> GetExtraResources()
{
	return new List<Tuple<string, string>>
	{
		new Tuple<string, string>("script", "/Static/js/rangeForm.js")
	};
}

With that, we have a complete class:

[ContentType(DisplayName = "Min-Max Range", GUID = "80ABAC02-ACE3-4828-81FF-F7F58D322ACD", Description = "A mininum and maximum number")]
public class MinMaxRangeFormElementBlock : InputElementBlockBase, IElementCustomFormatValue, IElementRequireClientResources
{
	[Display(Name = "Lower Bound", Order = 100)]
        public virtual int? LowerBound { get; set; }
        
        [Display(Name = "Upper Bound", Order = 200)]
        public virtual int? UpperBound { get; set; }

        public override ElementInfo GetElementInfo()
        {
            var baseInfo = base.GetElementInfo();
            baseInfo.CustomBinding = true;
            return baseInfo;
        }

        public IEnumerable<Tuple<string, string>> GetExtraResources()
        {
            return new List<Tuple<string, string>>
            {
                new Tuple<string, string>("script", "/Static/js/rangeForm.js")
            };
        }

        public override object GetSubmittedValue()
        {
            var rawSubmittedData = HttpContext.Current.Request.Form;

            var strValue = base.GetSubmittedValue() as string ?? string.Empty;

            var isJavaScriptSupport = rawSubmittedData.Get(EPiServer.Forms.Constants.FormWithJavaScriptSupport);
            if (isJavaScriptSupport == "true") //if the user's browser support JS, then deserialize the value provided by the frontend
            {
                var values = JsonConvert.DeserializeObject<List<int>>(strValue);
                if ((values?.Count ?? 0) != 2)
                    return null;

                return Tuple.Create(values[0], values[1]);
            }

            //if the user's browser does not support JS, we need to extract the value ourselves from the HTML raw form fields
            var minName = $"{FormElement.ElementName}_min";
            var maxName = $"{FormElement.ElementName}_max";

            if (!int.TryParse(rawSubmittedData[minName], out var min) || !int.TryParse(rawSubmittedData[maxName], out var max))
                return null;

            return Tuple.Create(min, max);
        }

        public object GetFormattedValue()
        {
            var submittedVal = GetSubmittedValue() as Tuple<int, int>;

            if (submittedVal is null)
                return string.Empty;

            return $"{submittedVal.Item1} to {submittedVal.Item2}";
        }
}

2. Create backend validators
We need to validate that users submit valid data to our form. We will validate in two places: frontend and backend. Backend validators are very important to ensure that a user doesn’t bypass frontend validation and also for users without JavaScript support. First, we need to add a validator class:

public class MinMaxRangeValidator : ElementValidatorBase
{
	public override bool? Validate(IElementValidatable targetElement)
	{
		var submittedValue = targetElement.GetSubmittedValue() as Tuple<int, int>;

		return submittedValue is null || submittedValue.Item1 < submittedValue.Item2;
	}

	public override bool AvailableInEditView
	{
		get {
			return false;
		}
	}

	public override IValidationModel BuildValidationModel(IElementValidatable targetElement)
	{
		var model = base.BuildValidationModel(targetElement);
		if (model != null)
		{
			model.Message = LocalizationService.Current.GetString("Form.Error.MinMaxRangeError");
		}
		return model;
	}
}

Let’s break down what each part does. The Validate function is simple enough: it checks the submitted value and determines whether or not it is valid. In this case, we can accept either a null submitted value or a value where the low bound of the range is less than the high bound of the range. Next, we override AvailableInEditView to be false. If it’s true, this validator can optionally be applied to a form element, but by making it false we don’t even give the choice to the editor. Finally, in BuildValidationModel we apply an error message if there is an error.

The next step is to link this validator to our form element. We first need to decorate the form element class:

[AvailableValidatorTypes(Include = new[] { typeof(MinMaxRangeValidator) })]

Then we need to force the validator to always be present. This is a bit tricky due to how the model works. What we have to do is override the list of applied validators and tack on the one we just created. We can do this by adding this property to our form element class:

public override string Validators
{
	get {
		var customValidator = string.Concat(typeof(MinMaxRangeValidator).FullName);

		var validators = this.GetPropertyValue(content => content.Validators);

		return string.IsNullOrEmpty(validators) ? customValidator : string.Concat(validators, EPiServer.Forms.Constants.RecordSeparator, customValidator);
	}

	set {
		this.SetPropertyValue(content => content.Validators, value);
	}
}

In this, we look for any existing validators specified by the editor and then concatenate our own validator to the end of the list, and make sure to include the record separator.

3. Create the frontend JavaScript validation and frontend binding logic
As stated earlier, we perform validation both on the frontend and backend. Now we need to create a JS file to do all this logic. We need to create the file “Static/js/rangeForm.js” (or whatever name/location you chose earlier in step 1). In that, we need to build out a skeleton file:

(function ($) {
    const originalGetCustomElementValue = epi.EPiServer.Forms.Extension.getCustomElementValue;
    const originalBindCustomElementValue = epi.EPiServer.Forms.Extension.bindCustomElementValue;

    $.extend(true, epi.EPiServer.Forms, {
        Extension: {
            getCustomElementValue: function ($element) {
                //TODO: implement
            },
            bindCustomElementValue: function ($element, val) {
                return originalBindCustomElementValue.apply(this, [$element, val]);
            },
        },
        Validators: {
            'FormsTest.Models.FormElements.MinMaxRangeValidator': function (fieldName, fieldValue, validatorMetaData) {
                // TODO: implement
            }
        },
    });
})($$epiforms);

Now, let’s get into what each of these functions does. First, we need to write the custom binding in getCustomElementValue. The basic idea is it will attempt to call this function for every custom form element (not just ours), so we need to do two things: 1. check if it’s a min-max range, and 2. if it is, apply our own value binding (to a two-element JSON-formatted array).

getCustomElementValue: function ($element) {
	if ($element.hasClass('Form__Element__MinMaxRange')) {
		return JSON.stringify([$element.find('[data-rangepart=min]').val(), $element.find('[data-rangepart=max]').val()])
	}

	return originalGetCustomElementValue.apply(this, [$element]);
},

In this case, we check if it’s our custom element by checking the CSS class, and if it is, we find the min and max inputs within the element using data-rangepart attributes (the CSS class and dataset attributes will be fleshed out when we build the view in the next step).

Next, we need to build out the validator. In it, we are already passed the value (formatted with our JS custom binding we just made as a JSON array), so we just need to see if it’s valid.

Validators: {
	'FormsTest.Models.FormElements.MinMaxRangeValidator': function (fieldName, fieldValue, validatorMetaData) {
		const value = JSON.parse(fieldValue);

		return { isValid: value[0] < value[1] };
	}
},

Notice that the object key has to be the fully qualified name of the validator class, and we return an object in the format { isValid: true }. This code will be run for any element with this validator class (in our case, all instances of the min-max range element because we force-applied this validator in step 2).

4. Build the view
This is the last step, thankfully. We need to build out the frontend view for how this element actually renders. Step 1 is to create a view in your block viewss folder called “MinMaxRangeFormElementBlock.cshtml” (make sure the filename matches the class you just created, of course). In it, we start with the outer wrapping.

@model FormsTest.Models.FormElements.MinMaxRangeFormElementBlock

<fieldset class="Form__Element Form__CustomElement Form__Element__MinMaxRange" data-epiforms-element-name="@formElement.ElementName">
</fieldset>

This is critically important to get right. All three CSS classes are important. Form__Element indicates this is a form element, Form__CustomElement indicates we are performing custom binding logic on it (so it knows to invoke the JS from the previous step), and Form__Element__MinMaxRange is so we can actually tell what type of form element this is. We also need data-epiforms-element-name to properly perform binding.

With that out of the way, let’s build out the view.

@model FormsTest.Models.FormElements.MinMaxRangeFormElementBlock

@{
    var formElement = Model.FormElement;
    var errorMessage = Model.GetErrorMessage();
    var submittedValue = Model.GetSubmittedValue() as Tuple<int, int>;
}

<fieldset class="Form__Element Form__CustomElement Form__Element__MinMaxRange" data-epiforms-element-name="@formElement.ElementName">
    <legend class="Form__Element__Caption">@Model.Label</legend>

    <table border="0">
        <tbody>
            <tr>
                <td><label for="@(formElement.Guid)_min">@Html.Translate("Form.Element.MinMaxRange.Min")</label> <input id="@(formElement.Guid)_min" type="number" data-rangepart="min" name="@(formElement.ElementName)_min" value="@(submittedValue == null ? string.Empty : submittedValue.Item1.ToString())" /></td>
                <td><label for="@(formElement.Guid)_max">@Html.Translate("Form.Element.MinMaxRange.Min")</label> <input id="@(formElement.Guid)_max" type="number" data-rangepart="max" name="@(formElement.ElementName)_max" value="@(submittedValue == null ? string.Empty : submittedValue.Item2.ToString())" /></td>
            </tr>
        </tbody>
    </table>

    <div role="alert" aria-live="polite" data-epiforms-linked-name="@formElement.ElementName" class="Form__Element__ValidationError" style="@(string.IsNullOrEmpty(errorMessage) ? " display:none" : "")">@errorMessage</div>
</fieldset>

In this, we have a number of components. We have the label, we have the min and max inputs (notice how the name attribute lines up with what we look for when doing model binding for non-JS clients and the data-rangepart lines up with what we look for when doing JS model binding), and we have an area to show the error message.

With all these components, we now have a fully working custom form element.

If you want to put this all together, you can see it on GitHub.

Saving modified DOM state in a form with the back button

A common problem encountered with building forms in JS is that when leaving a page and coming back, form state is preserved… sort of. If you have a fixed form with no dynamic behavior, everything will be fine. However, if you have something like this (please excuse my barebones JS and markup, this is to simplify the demo):

<script type="text/javascript">
function addRow() {	
	const newRow = document.createElement('ul');
	const newInput = document.createElement('input');
	newInput.type = 'text';
	newInput.name = 'input' + document.getElementById('inputs').childNodes.length;
	newRow.appendChild(newInput);
	inputs.appendChild(newRow);
}
</script>

<input type="text" name="static" />

<ul id="inputs">
</ul>

<button onclick="addRow();">Add Row</button>

…then if you add some rows, leave the page, and then come back, the static input will still have the value you typed in, but the rows you added are gone.

Why do these rows disappear? The reason is because they were added with JS after the page was created. Any DOM manipulation that happens after a page is created will be lost when you leave the page and then come back with the back button.

So, now, what can we do about it? The answer is simple. We can use a simplified version of the memento design pattern. The fundamental idea of the memento pattern is that we store the state in some form that can be retrieved later. There are three important questions to use the memento design pattern effectively, which will all be taken care of in the next paragraphs: 1. How do we convert the state into a storeable object?, 2. Where do we store the state?, and 3. How do we restore the state?

Let’s take these one at a time:

1. How do we convert the state into a storeable object? To store the state effectively, we need a format to store it in and a way to trigger storing it. JavaScript conveniently provides JSON, which is all we need in this case. Since all we have is a list of strings, in this example we can just store it as a JSON-encoded array of strings. For example, if we have two inputs with “Value 1” and “Value 2”, we can just store ["Value 1", "Value 2"].
Now that we have a format to store it in, we need a way to trigger storing it. Thankfully there is a window event, onbeforeunload, that we can leverage. Basically the event fires when the user leaves the page but before actually leaving. In this case, we can add the following JS to generate JS of the state:

window.onbeforeunload = function() {
	const inputWrapper = document.getElementById('inputs');
	const values = [...inputWrapper.querySelectorAll('input')].map(input => input.value)
	const inputListMemento = JSON.stringify(values);
	console.log(inputListMemento);
	
	// TODO: save this somewhere we can see again when reloading the page
}

Now, if we leave the page after entering some data, we will see an array with all the input values logged to the console (make sure to set console logging to persist between pages). Now, this brings us to the next step to solve that TODO:

2. Where do we store the state? At the beginning of the post I explained that input values in inputs present when the page was first loaded will persist. Thankfully, this also includes input values dynamically populated, even to hidden inputs. We can leverage this to store the state. All we have to do is add a hidden input and store the value there.

<script type="text/javascript">
function addRow() {	
	const newRow = document.createElement('ul');
	const newInput = document.createElement('input');
	newInput.type = 'text';
	newInput.name = 'input' + document.getElementById('inputs').childNodes.length;
	newRow.appendChild(newInput);
	inputs.appendChild(newRow);
}

window.onbeforeunload = function() {
	const inputWrapper = document.getElementById('inputs');
	const values = [...inputWrapper.querySelectorAll('input')].map(input => input.value)
	const inputListMemento = JSON.stringify(values);
	
	document.getElementById('inputListMementoHolder').value = inputListMemento;
}
</script>

<input type="text" name="static" />
<input type="hidden" id="inputListMementoHolder" />

<ul id="inputs">
</ul>

<button onclick="addRow();">Add Row</button>

Then if we leave the page and come back, we can verify in your console that the hidden input you just created does indeed store the previous value of the inputs in the list by running the following code:
document.getElementById('inputListMementoHolder').value
Now that we have the value stored in the memento, we finally need to restore it, which brings us to the final part.

3. How do we restore the state? This we can do at page load by reading the value of the input. JS thankfully provides the window.onload event we can use. First, however, we need to modify our addRow function to take an argument for the value:

function addRow(value = '') {	
	const newRow = document.createElement('ul');
	const newInput = document.createElement('input');
	newInput.type = 'text';
	newInput.name = 'input' + document.getElementById('inputs').childNodes.length;
	newInput.value = value;
	newRow.appendChild(newInput);
	inputs.appendChild(newRow);
}

Now, we can reload the state when reloading the page by adding the event to read the memento:

window.onload = function() {
	const inputListMemento = document.getElementById('inputListMementoHolder').value;
	if (inputListMemento) {
		inputListItems = JSON.parse(inputListMemento);
		inputListItems.forEach(item => addRow(item));
	}
}

With this, you should see that when leaving the page and coming back via the back button, we do not lose any data from our list input. For a recap, here’s the final piece of code:

<script type="text/javascript">
function addRow(value = '') {	
	const newRow = document.createElement('ul');
	const newInput = document.createElement('input');
	newInput.type = 'text';
	newInput.name = 'input' + document.getElementById('inputs').childNodes.length;
	newInput.value = value;
	newRow.appendChild(newInput);
	inputs.appendChild(newRow);
}

window.onbeforeunload = function() {
	const inputWrapper = document.getElementById('inputs');
	const values = [...inputWrapper.querySelectorAll('input')].map(input => input.value)
	const inputListMemento = JSON.stringify(values);
	
	document.getElementById('inputListMementoHolder').value = inputListMemento;
}

window.onload = function() {
	const inputListMemento = document.getElementById('inputListMementoHolder').value;
	if (inputListMemento) {
		inputListItems = JSON.parse(inputListMemento);
		inputListItems.forEach(item => addRow(item));
	}
}
</script>

<input type="text" name="static" />
<input type="hidden" id="inputListMementoHolder" />

<ul id="inputs">
</ul>

<button onclick="addRow();">Add Row</button>

Now we can preserve the state of a dynamic form even when leaving the page and returning later.

A ridiculous but maybe sort-of useful programming language?

I’ve had some free time lately, and I found a project for the past few days to keep me occupied. It all started in a group chat when someone brought up the idea of building his own programming language where as much as possible is symbols, primarily various types of brackets. Every command is in some sort of brackets (sort of like Lisp), and everything not in some sort of bracket is a comment. An example program (provided by the author) is this:

(#A)(&(A>12)*(A-=3)!(A+=2))(A>>)

To break it down, the program does the following:

  • (#A) reads A from standard in and requires A be a number
  • &(A>12) represents an if statement, where the condition is A>12
    • *(A-=3) means that if the condition (A>12) is true, then subtract 3 from A
    • !(A+=2) means that if the condition is false (i.e. A <= 12), then subtract 2 from A
  • (A>>) means print A to standard out

Arrow

This inspired me to build my own programming language. I was trying to figure out what the most ridiculous way to draw out control structures was, and after thinking for a while, I decided to require the programmer to use text to represent the control flow of the program by drawing everything with arrows. Thus, I called the language Arrow (mostly because that was the first name I could think of that I couldn’t find an existing language for). It is available on my GitHub for anyone who wants to read more about it or maybe try using it. You can look in “primesieve.arw” and “string.arw” for some examples of functions. There are two basic control structures: if and loop.

If statement

/--< false condition
| body
| more body
\-->

In that case, if the given condition is false, then the body is skipped over, indicated by the arrow. Here’s a basic example:

bool even
even = true
/--< x % 2 == 0
| even = false
\-->

In that case, if x is even, then the variable “even” will be set to true. How this works is that if x modulo 2 is zero (i.e. x is even), then the body of the if statement will be skipped and even will retain its original value of true. However, if x is odd, then the body of the if statement will not be skipped over and x will evaluate to false.

Loop

/-->
| loop body
| more loop body
\--< continue condition

The loop functions as a standard do-while. I couldn’t find a way to satisfactorily represent a standard while with arrows, so I gave up and just did do-while (you can always skip over it with an if statement if the condition is false if you want a real while loop). The arrow indicates that if the continue condition is true, go back to the top. A basic example of this:

int counter
counter = 0
int base
base = 2
/-->
| base = base * 2
| counter = counter + 1
\--< counter < 5

That code would calculate to the fifth power by looping until the counter reaches five, and at each iteration multiplying base by two.

Functions

I also couldn’t be satisfied with a programming language without adding functions. Functions also use a syntax that involves visualizing the control flow with text. An important nuance here is that functions must only have one return at the very end. Also, functions cannot be void (though I may implement that in a future update it time permits, and you can ignore the return value of a function). A basic example:

function
/--> int five()
|
^ 5

As you might imagine, that’s a function that returns five. The key idea here is how the return is specified (and code body goes above the return). The return (^) points back towards the function name. An important thing to note here is that you can only return from a function in one place (which must be the last instruction in a function body). Similar to how there are no break/continue statements, this requires the control flow to be visually obvious and have no jumping around, keeping the flow visible with the arrows.

Putting this together

With these frameworks, programs can be put together. A simple example of a loop inside a function is a function that calculates an integer square root (or the ceiling if the number is not a perfect square):

function 
/--> int squareRoot(int x)
| int candidate
| candidate = 0
| /--> //repeat the loop until the candidate squared is greater than or equal to the input
| | candidate = candidate + 1
| \--< candidate * candidate < x
^ candidate

You can get more complicated, since control structures can be nested. To make this more fun, here’s a function that detects if a number is prime:

function
/--> bool isPrime(int x)
| bool result
| result = true
| /--< x < 3
| | int factor
| | int ceiling
| | ceiling = squareRoot(x)
| | factor = 2
| | /-->
| | | /--< x % factor != 0
| | | | result = false //if the factor divides the input, then the input is not prime
| | | \-->
| | | factor = factor + 1
| | \--< factor < ceiling and result //bail out as soon as we see a factor
| \-->
^ result

That function (which uses the square root function from earlier) loops from 2 to the square root of a number and sees if any of those values divide the number. If so, it returns false, and if not, returns true.

Lexer Framework

In the process of developing this, I built a fairly powerful lexer framework that can, given a language spec built with objects, perform the lexical analysis to generate tokens automatically. It’s definitely not the most efficient tool (it uses greedy matching and a ton of string operations), but it was a good exercise in object-oriented design and writing reusable tools. The basic building blocks are as follows, and everything except “fixed string” recursively nests on other token types.

  • Fixed string
  • Fixed sequence
  • Repeated
  • Multiple options

Given rules using these elements (as well as a few other rules on how to actually get a useful result, such as what type to attach to each token and when to combine underlying tokens), the lexer can generate the tokens for a program. A few simple examples:

  • Digit is multiple options of the fixed strings “0”, “1”, … , “9”
  • Unsigned integer is repeated digit at least one time
  • Identifier is sequence of letter followed by repeated alphanumerics zero or more times

Type System

Given that I am a very strong believer in strong typing, I created a type system for Arrow. The three base types are int, char, and bool, and arrays of any dimension can be created from those types. Everything is copied/passed by value (and arrays are deep copied). Strings are just arrays of characters, with the length representing the length of the string. Every variable starts out uninitialized, and attempts to read an uninitialized variable will result in an error. Arrays also can be either initialized or uninitialized. To initialize an array, dimensions must be specified in the declaration (for example, int[] x[5] would initialize an array of length five). Once an array is initialized, each cell is an uninitialized value.

Strings are also a part of the language, with a string being an array of chars. Unlike C which uses null-terminated strings, since arrays are a well-defined type in Arrow (as opposed to C which just has a pointer to the head of the array), the length of the array determines the length of the string.

Scoping and static semantics

Using mostly what I learned in Programming Language Concepts class, I was able to implement static semantics fairly easily. I designed the call stack (and the static symbol stack) to have both a static link and a dynamic link, with the static link being used for scoping and dynamic link being used for a return location. I also was able to implement static type synthesis on the parse tree using techniques I learned in that class. Currently there are no global variables, but with how the scoping system is implemented I could add those with minimal effort.

Input/Output

Though it almost came as an afterthought, input/output were added to the language. Output is pretty simple, using the print keyword (note that it’s not a function, but instead a language construct). Multiple things can be printed by separating them with commas. Input is a bit more tricky, and currently input can only be taken to integers and strings. To receive input, use the input keyword than specify a type (for example, “input int” or “input char[]“). Input commands can be used in expressions like a regular function call, but the argument given is a type and not a variable.

Compiler Design

The overall high level design was taken from what I learned in Compiler Design class. In particular, having a separate lexer, parser, parse tree, and backend (interpreter).

I may have accidentally created something useful

I intended the Arrow language mostly to be a joke as well as maybe something to keep me occupied. However, as I was developing it I realized that it may actually have some useful value. Certainly it’s not a great tool for developing real-world applications, but the visualized control flow may be a useful tool for teaching control structures or helping visualize algorithms. The control flow graph is almost built into your source code here. The lack of any sort of GOTO statements (throw, return, break, continue) also means that the control flow has to be painfully obvious and can only be shown with the control structures that require the control flow to be directly visible.

If anyone creates something useful, I’d love to see it!