Strict Programming Language

Strict Programming Language

  • Docs
  • API
  • Blog

›All Blog Posts

All Blog Posts

  • July 2025 New Strict parsing and runtime design overview
  • March 2023 Mutable
  • January 2023 Multi Line Text Expressions and Introducing New Lines in Strict
  • January 2023 Namings in Strict
  • December 2022 Traits and Components
  • November 2022 Multi Line List Expressions
  • November 2022 Strict Base and Example folders moved to separate repository
  • October 2022 Shunting Yard
  • September 2022 For loops
  • September 2022 Details about Mutability in Strict
  • July 2022 Introduction To Generics In Strict
  • May 2021 Strict Newbie Experiences
  • June 2021 Slow Progress
  • May 2021 BNF Grammar
  • May 2021 Next Steps
  • Jul 2020 Getting Back Into It
  • Jul 2020 Optimizing FindType
  • Jul 2020 Package Loading
  • Jun 2020 Parsing Methods
  • Jun 2020 As Simple As Possible
  • Jun 2020 Back to blogging
  • May 2020 Slow and not steady
  • Apr 2020 Sdk going open source
  • Feb 2020 Still work in progress
  • Jan 2020 website is up

July 2025 New Strict parsing and runtime design overview

July 20, 2025

Benjamin Nitschke

Architecture at a Glance

LayerResponsibilityWhen It RunsRejects / Optimises
Strict.LanguageLoad .strict files, package handling, core type parsing (structure only)File loadBad formatting & package errors
Strict.ExpressionsLazy-parse method bodies into an immutable tree of if, for, assignments, calls…First method callStructural/semantic violations
Strict.ValidatorsVisitor-based static analysis & constant foldingAfter expression parsingImpossible casts, unused mutables, foldable constants
Strict.RuntimeExecute expression tree via ~40 instructionsOn demand by the callerValue/type errors, side-effects
Strict.TestRunnerRun in-code tests (method must start with a test)First call to a methodFailing test expressions
Strict.OptimizersStrip passed tests & other provably dead codeAfter first successful runDead instructions, test artefacts

1 · Strict.Language

  • Parses package headers and type signatures only.
  • Keeps every package cached in memory for instant access.
  • Any malformed file is rejected before deeper compilation.

2 · Strict.Expressions

  • Method bodies are parsed lazily the first time they’re needed.
  • Produces an immutable expression tree — never mutated at runtime.
  • Early structural checks ensure only valid Strict constructs survive.

3 · Strict.Validators

  • Runs constant folding & simple propagation (e.g. "5" to Number → 5).
  • Flags impossible casts ("abc" to Number) and other static mistakes.
  • Separation of concerns: validation without touching runtime state.

4 · Strict.Runtime

  • The Runtime is the single source of truth for dynamic behaviour.
  • Executes ~40 byte-code-like instructions as statements, all in memory for now.
  • Very fast, the goal is to run this in Strict itself and go through millions of lines per second.

5 · Strict.TestRunner

  • Every method must open with a self-contained test.
  • Tests are executed once; passing expressions become true and are pruned.
  • Guarantees that only verified code reaches production.

6 · Strict.Optimizers

  • Final clean-up pass: Remove passed test code.
  • Eliminate unused variables created solely for testing.
  • Preserve original line numbers for precise error reporting.

Note: There are older projects like the VS Code LanguageServer, Cuda Compiler, Roslyn, Grammar, etc. but these are no the focus here, we just want to get the Runtime working, switch all the code to Strict itself and let it rip then ^^


Key Design Decisions

  1. Immutable Expression Tree
    Static analysis only; no runtime state sneaks in.

  2. Validation Before Execution
    Catch the trivial stuff early, let the Runtime handle the hard parts.

  3. Runtime Is King
    Only the VM sees real values and side-effects — optimisation happens post-execution.

  4. One-Time Tests
    Tests live where the code lives, run once, then disappear.


Why This Matters

Only needed code that is actually called is ever parsed, validated, tested and runs in the Runtime. This is a very different approach and allows generating millions of .strict lines of code and discarding anything that is not needed or doesn't work pretty much immediatly.

  • Fast Feedback — formatting and type errors fail instantly, long before runtime.
  • Deterministic Builds — immutable trees + one canonical code style = no drift.
  • Lean Runtime — by the time code hits the Runtime, dead weight is already gone.

Happy coding with Strict — where there’s exactly one way to do it right.

March 2023 Mutable

March 16, 2023

Benjamin Nitschke

Strict is coming along nicely and all low level features are done, the early version of the VM is working, but needs some serious optimizations to be fast and useful for more complex problems. However currently it is still easy to add more language features or simplify some common programming issues, so I guess we are spending a bit more time on those things instead of finishing up.

Murali wrote about mutability last September already, this is a continuation of that blog post: https://strict.dev/blog/2022/09/20/details-on-mutability-in-strict

By default everything is constant

Last year we used "has" for members in types and "let" for constants inside method bodies. We already knew for years that mutable things are no good and want to code things as functional as possible. So "mutable" was used to describe when things had to be mutable, most of the time it was hidden away (like a for loop index counter).

To make things more clear "let" was renamed to "constant" because it will only compute a constant value or be used as a named inline. In method bodies you can use the "mutable" keyword instead of "constant" to make an actual variable you can change over time (and pass or return if needed). In all other cases "mutable" is not an available keyword, so instead you have to use the Mutable(Type) syntax, e.g. for return types or type members.

It is important to note that "mutable" code runs very different from normal code, many optimizations won't work and one of the main things that make Strict fast is not possible with "mutable": calculating once!

The Optimizer

Take this example:

    2 is in Dictionary((1, 1), (2, 2))

Here the Dictionary.in method is called (and tested, this test is actually from that method itself). On the left side we have a 2, which doesn't need any more optimizations or caching, it is just the number 2. On the right we are calling the Dictionary from constructor with a list of 2 lists containing 1, 1 and 2, 2. This will be done once and cached for a very short time (as the compiler will see no use for it after a few lines and discard it, the optimizer will already have checked for smiliar usages).

in the VM it looks like this:

  cached1122Dictionary.in(2) is true

Next up is the actual comparison if the in method returns true for the key 2, which returns true. If false would be returned, the program ends right here and the error is shown, we can't continue with any code path that leads here (TDD yo). So the VM can safely optimize this whole line away into: true, which clearly doesn't do anything useful and can be discarded alltogether (all unit tests work this way).

The same will happen for any other code of course. It is hard to show an example because useless code is not allowed and will lead to a compiler error unlike test code, which checks behavior and gets optimized away when execution is fine. But lets say we are looking at something like the Number.Floor method:

Floor Number
    1.Floor is 1
    2.4.Floor is 2
    value - value % 1

Again, here we can optimize both tests away as they are both true, but let's look in more detail how "value - value % 1" becomes 2 for the input 2.4. The VM will run the second test like this:

  Number(2.4).Floor is 2

which leads to the constant number 2.4 being cached

  value24 - value24 % 1 is 2

value24 % 1 is everything after the decimal point, so it becomes another constant number with the value 0.4

  value24 - value04 is 2

value24 - value04 is 2 (a bit cumbersome to read here, but this is all just optimization steps, nothing is actually computed or cached this way, it is just numbers), which makes the whole expression true and the test passes.

The point is that the compiler might find a faster or more efficient way in the future to calculate the floor of a number (without having to change any code) and nothing here changes. In fact nothing is even recalculated as all values were correct, only new code would be affected. Long term we want to keep results around in the VM, the strict packages and SCrunch, some of that works in memory, but since the VM is not done and optimized, not ready yet.

All non-mutable code is by default parallel, will be executed by as many CPU threads and GPU cores available and on as many computers as provided. Everything always runs asyncronly in the background and the compiler figures out himself how to put data back together if there were big loops or calculations involved.

Mutable

With mutable code most of these optimizations won't work at all or not in the same way. Let's look at an example from Number.Increment, method for mutable numbers:

    Mutable(5).Increment.Decrement.Increment is 6

The initial number 5 can't really be cached into value5 or something, as the very first operation makes it 6, then 5 again, then 6 again. We also cannot cache or optimize any steps in between as we can't know if the mutable outside of one of the mutable methods will mutate it further. Imaging running this in a big loop for every pixel of a 4k image, no good.

Here the compiler can obviously see that the final result of the last Increment is never used again as a mutable and convert it back to a constant and then roll back the whole calculation into:

    5 + 1 - 1 + 1 is 6

which again is true and can be optimized away. However the Mutable(Number).Increment method can't be optimized or used in the same as the Floor method above. The normal way to calculate a new number is of course not using Mutable, but just calculating the value with any operation you like and storing it somewhere else. Also using Mutable only in one place and NOT overwriting it for a long time can still be optimized very well (e.g. an image might be mutable overall, but within a method we will only set one pixel at a time, all pixel operations can be parallized).

Signals

Signals are used in many UI frameworks (javascript, typescript) and engines (godot) and are bascially the observer pattern but with UI elements. They are usually very cumbersome and hard to use, many game engines have something like this as well, but again, very specialized for ui elements or require to write a ton of boilerplate code to connect events (e.g. update ui label when hitpoints change).

I recently saw a very simple implementation and that got me thinking that this would be very useful for strict, obviously it requires a mutable thing, but since mutable is supposed to be used rarely, why not provide this feature for free: https://github.com/motion-canvas/examples/blob/master/examples/motion-canvas/src/scenes/signalsCode.tsx

The following code simply creates a radius 3 (mutable in strict) and then uses it for the area calculation (pi*radius squared). whenever you change radius, area automatically is updated (so internally each signal keeps track on where it is used and updates the outer signal as well, all event driven)

 const radius = createSignal(3);
 const area = createSignal(() => Math.PI * radius() * radius());

In strict:

mutable length = 3
mutable squareArea = length * length
squareArea is 9
length = 4
squareArea is 16

Simple, isn't it? also fits to our earlier ideas that all mutables become lambda calculations automatically anyways (more on that in a later blog post, mutable method calls are really interesting as they are always like lambdas in other languages).

To optimize code you can get away from mutable and lose this feature:

constant squareArea = length * length

Now there is no recalculation happening and it always will stay its initial value.

January 2023 Multi Line Text Expressions and Introducing New Lines in Strict

January 18, 2023

Murali Tandabany

We added support for multi-line text expressions in Strict following the same approach we used to support multi-line List expressions. Along the same line, we also added support for New Lines inside text expressions. In this blog, we will discuss these both topics in detail.

Multi Line Text Expression

When you want to create a text expression with length more than 100 characters, it is very much possible that you are going to violate the Strict line length limitation which is any line with more than 120 characters in it are not allowed and the compiler will throw an error immediately. To resolve this problem, we are now introducing support for multi line text expressions for the text values with length more than 100 characters at least. If your multi-line text expression character length is below 100, then compiler will not parse this as multi line and throw a compile time error to use single text expression instead.

Creating a multi line text expression is very easy in strict. All you need to do is add a '+' operator at the end of the expression and continue the remaining part of the expression in the next line. Please be aware that you still need to end the text expression before '+' operator by using double quotes and also start the next line with the same tab level and a opening double quotes before continuing the remaining text expression values.

Example:

has log
Run
    constant result = "This is considered to be some interesting text data that has more than hundred character length" +
    "so it is continuing in the next line using strict multi line expression syntax"

It is recommended to use large text content stored in a text file and then load it into the strict program using File type instead of storing them in the program directly.

The below usage of multi-line text expression is not allowed as the line length is below 100 characters.

has log
Run
    constant result = "Has less than hundred character" +
    "so it is not allowed to get compiled"

New Lines in Text

Support for new lines is unavoidable in any programming language. Now, we have added support for New Lines in Strict Text values and in this section, we will discuss the syntax and usages of New Lines in Strict language.

The syntax to insert New Line is as follows,

Text.NewLine

This new line syntax can be used along with the text expression to insert a new line wherever required.

Examples:

has log
Run
    constant result = "First Line - Text data that has more than hundred character length" +
    Text.NewLine +
    "Third Line - so it is continuing in the second next line using strict multi line expression syntax" + Text.NewLine

January 2023 Namings in Strict

January 10, 2023

Murali Tandabany

From the beginning, we never allowed numbers or special characters in the names of anything in Strict. Now, we have decided to allow them with strict limitations for package and type names as explained below. Other than package and type names, all other units in Strict are not allowed to have numbers or any special characters in their names.

Naming Rules for Packages

Package names must start with alphabets only but can contain numbers or '-' in middle or end, all other characters are not allowed.

Examples: Allowed Package Names

Hello-World
Math-Algebra
MyPackage2022

Examples: Not Allowed Package Names

1MathVector
Hello_World
(CustomPackage)

Naming Rules for Types

Type names must start with alphabets only and can contain number in the range from 2 to 9 as the last character. No multiple number digits are allowed in the type name. Another constraint in the type name is name without the last number character should not create any conflict with the existing type name.

Example: Allowed Type Names

Vector2
Matrix3
CustomType9

Example: Not Allowed Type Names

Vector0
Martix1
2Vector
Matrix33
Custom-Type9

Special rule for type names is when you already have an existing type named Text and if you try to created Text2 then it is not allowed because Text2 without 2 is creating a conflict in types therefore it is not allowed.

All other Names

Except package and type, all other names such as method names, variable names, member names and parameter names must have alphabets only and no special characters or numbers allowed.

December 2022 Traits and Components

December 30, 2022

Murali Tandabany

In this blog, we are going to discuss about how we can use a strict type as a trait or as a component in any other type. We will also discuss the differences between these two usages.

After we removed the implement keyword and overloaded that behavior to has keyword, we need a new way to identify whether the type used as a member is intended as a trait or a component at the parser level. For example, see the below code snippet.

Calculator.strict

has App
has file
...

Type Calculator has two members App and file and both types are traits(no implementation provided for the methods). Out of these two, we cannot infer that App type is used as a trait and this program type should implement the Run method from the App type. Also, file type is a component to this type and all the public methods and members of the file type will be used in this program.

Trait

In order to differentiate these two type usages, we have come up with a new rule and this also helps the parser to treat the types as per the intended usage. The new rule is if all of the member type methods are implemented in the current type, then it means that member type is used as a trait.

Example program for member used as a Trait

ConsoleApp.strict

has App
has log
Run
    log.Write(6)

In this example, Run method of the type App member is implemented in the ConsoleApp.strict. With this, compiler now can automatically interpret App member is used as a trait.

Component

On the other side, if no methods of the member are implemented in the current type, then it is automatically interpreted that member type will be a component to this current type and all of it's methods are available for the usage. One more syntax that denotes that the type is used as a component is initializing the member with the constructor parameters. In these cases, compiler will allow the program to get parsed without errors and let the runtime figure out which instance type should be injected as a dependency for this component type member.

Example program for member used as a Component

TextContentPublisher.strict

has File = "test.txt"
ReadAndPublish Text
    Publish(File.Read())

In this example, File member methods are not implemented rather Read method is used inside ReadAndPublish method. Therefore, it is clear now that File member is used as a component in TextContentPublisher type.

Usage Not Allowed

One case which is not allowed in this rule is partially implementing the member type methods. This will create confusion in deciding the member usage, hence it is not allowed and the parser will throw an exception to ask the user to implement missing methods of the member type. The below example program demonstrates this invalid case.

CsvFile.strict

has File
Read Text
    constant openedFile = FileStream.OpenFile
    return openedFile

In this example, CsvFile type implemented Read method but missed to implement Lenght, Write and Delete methods. Therefore, this is not allowed and the compiler will throw an exception in this case.

November 2022 Multi Line List Expressions

November 9, 2022

Murali Tandabany

In strict, majority of the expressions will contain less characters and the length of each line can be contained within 120 character which is the hard limit for any line in strict. One exception is the list type containing more number of elements and in few cases the line length can extend beyond 120 limit. Below is one example program where the list expressions line length goes beyond the maximum limit.

has numbers,
has anotherNumbers Numbers,
GetMixedNumbers Numbers
    (numbers(0), numbers(1), numbers(2), numbers(3), numbers(4), numbers(5), numbers(6), anotherNumbers(0), anotherNumbers(1), anotherNumbers(2))

Multi Line List Expressions

In order to resolve this issue, we have introduced a new feature in Strict to support multi line List expressions. If any list expression which has length above 100 characters, then it is allowed to use multiple lines for the list elements with each list element in a separate line ending with comma. These lines should follow the same indentation as beginning line of the list expression.

The above program should be written as shown below using multi line list expressions.

has numbers,
has anotherNumbers Numbers,
GetMixedNumbers Numbers
    (numbers(0),
    numbers(1)
    numbers(2)
    numbers(3)
    numbers(4)
    numbers(5)
    numbers(6)
    anotherNumbers(0)
    anotherNumbers(1)
    anotherNumbers(2))

Usage Not Allowed

If the total length of the list expression is below 100, then it is not allowed to use multi lines to write those list expressions and should be always written in a single line.

For example below program is not allowed in strict as the total length of the list expression is below 100 characters.

has log
Run
    log.Write((1,
    2,
    3,
    4,
    5,
    6,
    7))

For more information, please refer to the strict example programs in the GitHub repositories.

November 2022 Strict Base and Example folders moved to separate repository

November 2, 2022

Murali Tandabany

In strict, we usually store all the Strict.Base and Strict.Example folder inside the same Strict repository and load it from the path everytime the project starts.

Now, we have moved both Strict.Base and Strict.Examples folder out of Strict source code repository and created a new repository inside strict-lang organization folder. We have also modified the Repository.cs to look for the Strict Base and Example folder files in the development folder (C:/code/GitHub/strict-lang/Strict.Base) and load them. If the files are not available in the development folder, then it will be downloaded from the GitHub repository directly and use them.

One more change is also planned to implement package manager service to automatically sync the latest changes from GitHub to locally cached Strict Base and example files.

Here is the repository link of Strict Base and Example folder files.

Base - https://github.com/strict-lang/Strict.Base

Examples - https://github.com/strict-lang/Strict.Examples

October 2022 Shunting Yard

October 15, 2022

Murali Tandabany

When I was struggling with parsing the nested group expressions, Benjamin Nitschke introduced an algorithm to me called Shuting Yard which solves group expression parsing in a much simpler way. In this blog, we are going to discuss the details about this Shunting Yard algorithm and learn it's usages in Strict.

What is Shunting Yard?

It is a stack-based algorithm which takes arithmetic or logical expressions as input and convert them into postfix notation. The output postfix notation gives us the order of execution of the arithmetic or logical expression operations in order to arrive at the correct result.

More details about the algorithm can be found in this wikipedia page link

https://en.wikipedia.org/wiki/Shunting_yard_algorithm

How Shunting Yard is used in Strict?

We took the complete advantage of Shunting Yard algorithm in the Parser section where each line of code needs to be parsed and spit out into correct expressions. Here, the strict program lines can contain any type of expression such as Number, Text or complex nested group Binary expression with member calls and method calls. In order to parse all theses types of expressions, mainly nested group expressions in the parser without having many performance intensive checks and conditions, we need a simple and clever algorithm like Shunting Yard.

The method expression parser itself takes care of all of the easy kind expressions in the front level which helps to improve the performance of parsing phase. When it comes to complex type expressions, we need to give it as input to the Shunting yard where the complex expression is first tokenized using Phrase Tokenizer class and then each token is ordered based on the operator precendence which then finally gives us back the postfix tokens.

These postfix tokens are stored in the Stack and the method expression parser will pop out each token and construct the result expression with few checks. Specifically, constructing binary expressions out of postfix tokens makes the job very much easier with the use of recursion.

For example:

(2 + (3 + 5) * 5) * 2  converted to -> 2, 3, 5, +, 5, *, +, 2, *

What is Phrase Tokenizer?

Phrase Tokenizer is a class inside Shunting Yard and it is used to tokenize the complex expressions into separate tokens based on certains conditions.

For example:

1 + 2 -> tokenized into "1" , "+", "2"
"Hello + World" + 5 -> tokenized into ""Hello + World"", "+", "5"

For certains types of expression where tokenizing is not helpful, Phrase Tokenizer will spit out the whole expression as single token and then the method expression parser will handle it by splitting the expression into much simpler form and uses Shunting yard again if needed. Mostly, if the expression is nested with arithmetic or logical operators, then tokenizing them will make more sense than for method call or member call expressions.

For example:

Add(4 * 5 + 3 - 1)

This whole expression will be treated as single token in the Phrase Tokenizer and then the method expression parser will split this into separate expressions as shown below

Add as Method call 
4 * 5 + 3 - 1 to Shunting Yard and get output as 4, 5, *, 3, +, 1, -

For more details about Shunting yard and phrase tokenizer, refer to the Strict code base in github.

September 2022 For loops

September 29, 2022

Luka Minjoraia

For loops exist in almost all programming languages (except functional ones) as it serves incredibly useful and convinient practicality for certain problems encountered in day-to-day programming.

Strict works with various forms of for loops and they are very simple to write. The expression in the for loop basically is an iterator which iterates through the series of values. Let's look at a very simple example of the for loop:

for number in numbers
    log.Write(number)

Above is given a very simple example that is pretty straightforward : it iterates through the list of numbers and logs each of the number. numbers is an iterable and number is an explicit iterator variable.

How implicit variables work

We can rewrite previous example in a more convinient, "Strict" way. for loops in Strict has two variables by default - index & value. The aformentioned variables are implicit, meaning they don't need to be declared as they always exist within the scope of the loop.

Let's rewrite the previous example using the implicit value variable:

for numbers
    log.Write(value)

This code does exactly the same thing as above. Note that value is basically a pointer to the instance of the current class (like this in C/C++/C#), but as soon as the scope of the loop is entered, it becomes the iterator variable.

index works similarly, but obviously it holds the index of the element.

for ("Hello", "World", "!")
    log.Write(index)

This would log 0, 1 and 2.

Let's check out this function, which is a part of Range.strict and just sums the value from start of the range to the endo of the range:

Sum
    Range(2, 4).Sum is 2 + 3 + 4
    Range(42, 45).Sum is 42 + 43 + 44
    let result = Mutable(0)
    for item in Range(value.Start, value.End)
        result = result + item
    result

We declare a mutable variable, which is supposed to hold the result of the summation, then we iterate through the Range, perform the sum of the item and return the result. Now this can be rewritten in a much easier way by utilizing the features of Strict.

Sum
    Range(2, 4).Sum is 2 + 3 + 4
    Range(42, 45).Sum is 42 + 43 + 44
    for value
        + value

This does exactly the same as above, the return statement AND the summation is simply inlined in the for scope producing the result.

Nested loops

In Strict, you can take full adventage of nested loops, and they're pretty straightforward.

let weeks = 3
let days = 7
for weeks
    log.Write("Week: " + index + 1);
    for days
        log.Write("Day: " + index + 1)

September 2022 Details about Mutability in Strict

September 20, 2022

Murali Tandabany

In Strict programming language, every member or variable is immutable by default. i.e. value can be only assigned during member/variable initialization. Thus, any has or let is constant by default. Strict also supports Mutable types so that member/variable values can be changed after initialization as well. This can be achieved by explicitly specifying the type as Mutable during initialization or if the type implements Mutable trait.

How to define Mutable types?

There are three ways to define Mutable types. 1. Explicitly mention the type as Mutable during member/variable assignment.

```
has counter = Mutable(0)
let counter = Mutable(0) //now counter variable is mutable and its value can be changed
for Range(0, 5)
    counter = counter + 1
```

2. Use the types that implements Mutable as your member/variable type.

```
has counter = Count(0) //here Count is the type that implements Mutable in their class implementation
let counter = Count(0)
```

3. Directly use the type which implements Mutable trait in their class

```
has count //here count is the type that implements Mutable 
```

All of the above three ways does the same operation and enable mutability to member/variable in strict.

Immutable Types Cannot Be Reassigned

Parser always checks the type of the member/variable before reassigning value to it. When any immutable type values are reassigned in strict program, parser will throw ImmutableTypesCannotBeReassigned exception.

has number 
Run
  number = number + 5 -> this will cause ImmutableTypesCannotBeReassigned error, so we MUST use Mutable types when we want to change values
Next →
Strict Programming Language
Docs
Getting StartedCoding StyleAPI Reference
Community
Stack OverflowProject ChatTwitter
More
HelpGitHubStar
Copyright © 2025 strict-lang