Skip to content

strict-lang/Strict

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,275 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Strict

Strict Programming Language

Create well-written and efficient code quickly

Strict is a language computers can understand

Strict is a simple-to-understand programming language that not only humans can read and understand, but also computers are able to understand, modify and write it.

The long-term goal is NOT to create just another programming language, but use Strict as the foundation for a higher level language for computers to understand and write code. This is the first step towards a future where computers can write their own code, which is the ultimate goal of Strict. Again, this is very different from other programming languages that are designed for humans to write code but not for computers to understand it. Even though LLMs can be trained to repeat and imitate, which is amazing to see, they still don't understand anything really and make the most ridiculous mistakes on any project bigger than a handful of files.) Strict needs to be very fast, both in execution and writing code (much faster than any existing system), so millions of lines of code can be thought of by computers and be evaluated in real time (seconds).

More at https://strict-lang.org

Layers

Strict Layers

Strict library

This repository is not just the runtime, but contains the standard library. It is one and the same. Any folder or github repository folder that contains .strict files is a package. The main folder contains all types that have special language treatment and are required to build strict packages. It is imported by default in every source file, here are the most used types:

  • Boolean
  • Number
  • Text
  • List
  • Dictionary
  • Range
  • Error (with Stacktraces, Type and Methods support)
  • Log
  • common traits like Any (applies to all types), App, Generic (replaced by concrete type in implementation), Mutable, File, Directory, FileReader, FileWriter, etc.

That is pretty much it, there are not many more things needed to write code. For more specific things see the Math folder, ImageProcessing, Examples, etc. here in this repository. Keep in mind that Strict is NOT a general purpose language. It is supposed to be writeable, readable and understandable by computers. If you want to build a website, a complex app, database code, a game, etc. you should use external code for that (you can keep the logic in Strict, but it is unlikely that Strict will have those things unless some people work on it and need it). It is quite easy to interface with external code, you can use any web api, C++, C#, Java, Python, etc. libraries. Those are threated as black boxes (thus not longer computer understandable, here we would be limited to LLMs) and will not follow the strict rules in Strict. The main issue here would be if an external library changes, then all your calls to it also have to change, which is less of a problem with Strict code calling Strict code as any changes can be fixed in a more automated manner.

In the rare case you need an optional value you can specify it with the or None type constraint (any other type also works). Keep in mind you still cannot assign None to anything, but we can check if something is None and simply not assigning it will keep it at the None state.

Context.strict

has Parent Context or None
has Name
[...]
if Parent is None
  logger.Log("This is the root")

Parent can just be used as Context and as any derived type (anything that uses Context), but if it was not set and thus is still None, an error will be thrown. You can of course always check for None, but this is a very rare pattern in Strict. An error is also thrown when using a type as a higher level type, which it is not. Let's say our Context is a Package, but we try to use it as a Type (which also is a Context, but clearly not a Package), an Error is thrown telling us we can't use it as a type (like a cast in any c like language would do).

How to use

The best way to learn how any expression or base type works is to look at the code, it is very compact and every single method starts with tests on how to use it. This base library is not just defining how the language works, but the documentation in source code form. All tests use the is comparsion (similar to == in c style languages, = is used for assignments), "is true" at the end of any expression is never used. All expressions (not just tests) must evaluate to true, otherwise the execution stops with an error. If an expression is constant (like all tests, but also many other expressions), it will be removed and optimized out after the first run.

See the Readme.md of the Strict project for more details, how to use and example code.

Language Server Protocol

The LSP (Language Server Protocol) implementation here adds support for VSCode, VS2019, IntelliJ and many more IDEs as possible Editors for Strict.

Architecture at a Glance (from low to high level)

Layer Responsibility When It Runs Rejects / Optimises
Strict.Language Load .strict files, package handling, core type parsing (structure only) File load Bad formatting & package errors
Strict.Expressions Lazy-parse method bodies into an immutable tree of if, for, assignments, calls… First method call Structural/semantic violations
Strict.Validators Visitor-based static analysis & constant folding After expression parsing Duplicate code remover, impossible casts, unused mutables, foldable constants
Strict.HighLevelRuntime Execute expressions directly in interpreter mode for fast checks and tests On demand during test execution Value/type errors, side-effects
Strict.TestRunner Run in-code tests via HighLevelRuntime (method must start with a test) First call to a method Failing test expressions, the central verification point
Strict.Bytecode Generate register-based bytecode instructions from expression trees After tests succeed Unsupported expression patterns
Strict.Optimizers Remove passed tests and other provably dead code from bytecode After bytecode generation Dead instructions, test artefacts, constant folds
Strict (exe) Execute optimized bytecode in the VirtualMachine; create executable Final step Value/type errors, side-effects

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

  • Flags impossible casts ("abc" to Number) and other static mistakes. These are validation errors same as parsing errors that need to be fixed before anything can continue.
  • Runs constant folding & simple propagation (e.g. "5" to Number → 5). This doesn't affect tests code, but for execution (any step after this) it still uses the folded expressions. Production is always folded and collapsed to the simplest form (in an IDE via a warning that needs to be fixed).
  • TODO: finds all duplicate code and removes it (reinventing the wheel over and over), e.g.
    • Reimplementing GetElementsText like the example below will be replaced with existing code: Text.Combine
    • This will affect ALL new code and make it much faster to write code, as you can just write the most straightforward code and Strict will figure out if there is already an existing implementation for it and replace it with that, which is also a great way to learn how to do things in Strict by just writing the most straightforward code and then looking at the optimized version of it.
    • Implemented like ReSharper/Jetbrains warning issues: Since there is no warnings in Strict, everything is an error and thus this needs to be fixed before it can be used.
    • Also removes verbose code that can be shorter, like Text.characters.Length -> Text.Length
  • Separation of concerns: validation without touching runtime state.

4 · Strict.HighLevelRuntime

  • Interpreter-style execution for quick feedback during editing, mostly for test execution.
  • Drives tests and advanced checks without bytecode generation (which is also fast, but not needed, we can stay in memory for most work)
  • Fast enough for most validation and dev workflows. All non external code is always executed and tested immediately like NCrunch (see SCrunch).

5 · Strict.TestRunner

  • Every method must open with a self-contained test (one line methods can be excluded if they are simple enough, e.g. just returning a value or doing a comparison).

  • Tests are executed once; passing expressions become true and are pruned.

  • Guarantees that only verified code reaches lower layers. Since tests are stripped here, no test is ever executed in bytecode, only production code that is actually called remains.

  • 6 · Strict.Bytecode

  • Converts expression trees into flat register-based instructions (BytecodeGenerator).

  • Defines the instruction set (~20 types: arithmetic, jumps, loads, stores, loops, invoke).

  • Pure code generation — no execution happens here.

  • Very compact (most types are just a few bytes, a whole package is less than a kb). Also zipped.

7 · Strict.Optimizers

  • Operates on bytecode instruction lists produced by Strict.Bytecode.
  • Runs optimized code from the above steps: test code removed, constant folding, dead store elimination, unreachable code elimination, redundant load elimination, jump threading, strength reduction.
  • Preserve original line numbers for precise error reporting.

8. Strict.Compiler

  • Compiles bytecode into actual executables for any supported platform (Windows, Linux, macOS).
  • Currently, can utilize MLIR (for the most optimized output, supporting parallelization and GPU execution), LLVM (for a more general approach) or direct NASM code generation (low level assembly).
  • Executables are very tiny and do not require any runtime, the Strict executable contains everything needed to do all these steps, parsing, validation, testing, bytecode generation, optimization and execution. External libraries used or needed for compilation (like MLIR) are not included and need to be on the executing machine. Same for any content used of course (but maybe we include them into the bytecode .strictbinary files like .apk packages).

9 · Strict (exe) — Runner + VirtualMachine

  • The Runner orchestrates the full pipeline: load → parse → validate → test → generate → optimize → execute.
  • The VirtualMachine executes optimized bytecode via a register file, call frames and a program counter.
  • Only the VM sees real values and side-effects — this is where programs actually run.

10 · Strict.Transpiler

  • Optional: Transpiles Strict into C# or CUDA for execution on existing runtimes.
  • Strict.Compiler.Roslyn generates C# source, Strict.Compiler.Cuda generates CUDA kernels.
  • Useful for running select Strict code without the bytecode path.

11 · Strict.LanguageServer

  • VS Code-first LSP implementation for daily Strict development.
  • Solid as all Strict work happens with it directly.
  • Shows all parsing and validation errors, warnings to automatically fix, tests results, code navigation, refactoring support, etc. in real time.

Note: Peripheral projects kept around for specific purposes: Strict.Grammar + Strict.Grammar.Tests (syntax highlighters from Grammar.ebnf), Strict.PackageManager (github-based packages, optional for now).


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 VM handle the hard parts.

  3. VM Is King Only the VirtualMachine sees real values and side-effects — everything before it is compilation.

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


Why This Matters

Only needed code that is actually called is ever parsed, validated, tested and executed in the VM. 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 immediately.

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

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

Examples (all from Strict/Number.strict)

	3 + 4 is 7
	2 is not 3
	5 to Text is "5"
	123.digits is (1, 2, 3)
	5 to Text is "5"
	2.4.Floor is 2
	"Hey" + " " + "you" is "Hey you"
	"Your age is " + 18 is "Your age is 18"
	"5" to Number is 5
	"A" is "A"
	"hi" is not in "hello there"
	"lo" is in "hello there"
	"hello".StartsWith("hel")
	"hello".StartsWith("hel", 1) is false
	"yo mama".StartsWith("mama") is false
	"abc".Count("a") is 1
	"Hi".ToBrackets is "(Hi)"
	"1, 2, 3".ToBrackets is "(1, 2, 3)"

List.strict usage (all lists are simply surrounded by brackets, basically any method call uses a list as the arguments)

	(1, 2) to Text is "(1, 2)"
	(1, 2).Length is 2
	(1, 2) is (1, 2)
	(1, 2, 3) is not (1, 2)
	(1, 2, 3) + (4, 5) is (1, 2, 3, 4, 5)
	("Hello", "World") + (1, 2) is ("Hello", "World", "1", "2")
	("1", "2") + (3, 4) is ("1", "2", "3", "4")
	("1", "2") to Numbers + (3, 4) is (1, 2, 3, 4)
	("3", "4") + (1, 2) to Text is ("3", "4", "(1, 2)")
	("3", "4") + (1, 2) to Texts is ("3", "4", "1", "2")
	(1 + 2) * 3 is not 1 + 2 * 3
	(("1", "2") + (3, 4)) to Numbers is (1, 2, 3, 4)
	3 + (4) is (3, 4)
	(1) + ("Hi") is Error("Cannot downcast Texts to fit to Numbers")
	(1, 2, 3) + 4 is (1, 2, 3, 4)
	("Hello", "World") + 5 is ("Hello", "World", "5")
	(1, 2, 3) - (3) is (1, 2)
	(1, 2, 3) - 3 is (1, 2)
	(1, 2, 3) - 4 is (1, 2, 3)
	(2, 4, 6) / (2) is (1, 2, 3)
	(1, 2) / (2, 4) is (0.5, 0.5)
	(1) / (20, 10) is (0.05, 0.1)
	(2, 4, 6) / 2 is (1, 2, 3)
	(1, 2) * (3, 5) is (3, 10)
	(1, 2) * 3 is (3, 6)
	(1, 2, 3).Sum is 6
	(1, 2, 3).X is 1
	(1, 2).Y is 2
	(1, 2, 3).Z is 3
	(1, 2, 3, 4).W is 4
	(1, 2, 3).First is 1
	(1, 2, 3).Last is 3
	3 is in (1, 2, 3)
	3 is not in (1, 2)
	"b" is in ("a", "b", "c")
	"d" is not in ("a", "b", "c")
	(1, 2, 3).Index(2) is 1
	(1, 2, 3).Index(9) is -1
	(1, 2, 3).Remove(2) is (1, 3)
	(1, 2).Count(1) is 1
	(1, 3).Count(2) is 0
	("Hi", "Hello", "Hi").Count("Hi") is 2

Members, variables, constants, let, mutable

Members are always defined at the top of any type file. Members are basically fields in other languages, but can do much more. For example almost all strict types are composed from more basic types and can be used as such. This kind of allows multiple inheritence (as you can have multiple members) and also gives you any feature from another type you want by just adding it is a member. If a member type is a trait (kind of interface, traits have no implementation), the type must implement all the trait methods.

Members can be either has (default), constant (for anything that does not change, think of static readonly, this includes way more than other programming languages and propagates upward, anything composed out of constants is a constant too, constants are always precomputed and optimized away, even if this is a complex operation) or mutable (rarely used, this means the value is allowed to change, mostly for optimization purposes, should be avoided otherwise as parallelization will struggle with mutables).

Some examples:

Any.strict is the basis of every other type, it doesn't have to be added as a member, all types are always Any. You can never use Any directly, but it implements these methods automatically in every type.

from
to Type
to Text
to HashCode
is(other) Boolean

A character is just a number, but can be created from a single letter text or specifying an UTF value:

has number
from(text)
	Character("b") is "b"
	Character(7) is Tab
	7 to Character is "7"
	Character("ab") is Error
	text.Characters(0)

A text ist just a List of characters:

has characters

A name is just a Text with some constraints

has text with Length > 1 and " " is not in value

NamedType is the base type for Members and Variables internally and this is all the code for it:

has Name
has Type
has IsMutable
has IsConstant
to Text
	NamedType("count", Number) to Text is "count Number"
	Name + " " + Type

Variables are always created in a method body scope. They look almost the same to members, but have a much smaller scope. They can be constant if the value is purely constant and never changes, this is always optimized away. Or it can be let (if the incoming value can be different, e.g. coming from a has member value that is different for each instance). mutable is also allowed here and rare. Only needed if a value needs to change, e.g. counting up in a loop. These kinds of mutable values are not dangerous as long as they don't leave the scope (then the same applies as for members, use rarely, only for specific optimizations, avoid otherwise).

Example:

TrimStart Text
	mutable startIndex = 0
	for characters
		if value is " "
			startIndex = startIndex + 1
		else
			return Substring(startIndex, characters.Length - startIndex)
	""

if expressions

Strict only has if for conditional expressions, which can be in block and single-line forms, plus a selector-style variant for compact matching. Everything is an expression, if and for always return an expression as well, which can be used as an automated return expression as well. In for loops the if expression is often used to filter values like piping in functional languages, see below for more advanced examples.

if value > 0
	value

Single-line conditional are pretty much the same (like '?' in C, C# or Java, just using "then" like in F# or Haskell). This is often used to return or assign simple expressions based on a condition, it all must fit in a line (as always max. length is 100).

value > 0 then value else 0

C#:

var result = value > 0 ? value : 0;

Scala or Haskell:

val result = if value > 0 then value else 0

Finally we have the selector if, it starts pretty much the same as above and also uses "then". The if line ends with "is" or "is not" (and other keywords or expressions that can be compared). It works like a switch or match expression in other languages, but is more powerful as you can use any expression and aretmetic or comparison operator or type check here.

if operation is
	"add" then value
	"subtract" then 0 - value
	"multiply" then value * value

C#:

var result = operation switch
{
	"add" => value,
	"subtract" => 0 - value,
	"multiply" => value * value
	_ => throw new InvalidOperationException("Unknown operation: " + operation)
};

Scala:

val result = operation match
	case "add" => value
	case "subtract" => 0 - value
	case "multiply" => value * value
	case _ => "Unknown operation"

Haskell:

let result = case operation of
	"add" -> value
	"subtract" -> 0 - value
	"multiply" -> value * value
	_ -> "Unknown operation: " ++ operation

It is important to note that if there is no fallback else case at the end an Error is automatically generated and thrown at runtime if no case above it matches.

for

The only iterator in Strict is the "for" expression, it is usually used to enumerate through lists, but anything that implements Iterator can be enumerated (Enum, List, Number, Range and anything that implements List like Dictionary). The syntax for "for" is quite powerful and not much like c-style languages, it is more like in functional languages. A for loop is also usually the only spot where mutable variables are used and make sense, so a lot of optimizations are done here to avoid actual mutable usage at runtime and thus allowing almost all code to be executed without side effects and in parallel. Remember that you don't have to write any parallel code, threads, processes, async, etc. it is all done automatically be the runtime and way more powerful than any other programming language could provide. All the following examples are automatically executed in parallel and even on the GPU if available and if it would be faster to do so.

Unlike the above examples or any other code in strict for expressions are not as self-explanatory, so each use case is explained here in a bit more detail.

Normal iteration

has logger
...
	constant someList = (1, 2, 3)
	for someList
  	logger.Log(index + ": " + value)

Outputs this into the logger (defaults to the console): 0: 1 1: 2 2: 3

The for expression requires an iterator to go through each element, which is what someList is. There is no need to specify an enumerator index or value as this is done automatically, and you can access these via "index" (0, 1, 2, 3, etc.) and "value" (whatever value is at this index). If you need to access the outer value you can use "outer.value".

You can iterate over a number if you just want a quick for loop for let's say 10 iterations. This outputs 10 lines starting from 0 to 9, index is available and the outer value stays unchanged and isn't used or needed here:

  for 10
    logger.Log(index)

Iterating over a Range will start at the Range.Start and end before the Range.ExclusiveEnd is reached. Note that iterating a Range from 0 to a number is not allowed, you have to use the 'for number' syntax just described. The following outputs 2, 3, 4 into 3 lines:

	for Range(2, 5)
  	logger.Log(value)

Or you can enumerate over Enum values, lets say you have Connection.strict (if there are only constants in a type, it is an enum, you don't have to give them values, but here they are all Text values)

constant Google = "https://google.com",
constant Microsoft = "https://microsoft.com"

Now we can iterate over this:

for Connection
  logger.Log(value)

Notice that iterating over an Enum value is something else, if the Enum is a number, it will just iterate to that number like in the above example "for 10". If the Enum value is a Text string, it will iterate over each letter as Text is just a List of Character.

for Connection.Google
  logger.Log(value)

Custom enumerator variables

has logger
LogNumbers
	for element in (1, 2, 3, 4)
		logger.Log(element)

Outputs 1 to 4 in 4 lines. Index still can be used, but value wasn't used or overwritten, so you can access the outer value this way. A more complex example would be:

for y in Range(1, 11)
  for x in Range(1, 11)
    logger.Log((x, y))

which would output 100 values starting at (1, 1) ending at (10, 10). Notice that Strict will evaluate the fastest execution, so for image processing it is not needed to iterate y first. Since output logging is a side effect this can't be executed fully in parallel (the logger will still get all values at once, but in order and output them at once as pretty much no time will have passed from start to finish).

Dictionaries

For dictionaries, you might want to use index, key and value variables, but again: you don't have to. This works fine:

for someDictionary
  if value.Key is "Apple"
    return value.Value

Dictionaries are just Lists of key+value pairs, so each enumeration just returns one of these pairs, and we can grab the first value as the key with value(0), which is exactly what value.Key will do. Same goes for value(1) for value.Value.

Index(other Generic) Number
	(1, 2, 3).Index(2) is 1
	(1, 2, 3).Index(9) is -1
	for elements
		if value is other
			return index
	-1

Unlike the for examples below that use a more compact syntax, here we want to manually abort the for loop iteration when an element is found. Since the list is not sorted by the other value we search for here, we have to iterate the whole elements list and then abort when we found the value. With the index method we are interested in the index of that value, so that is what is returned here (or -1 if nothing is found).

If you look at the source code of Dictionary.strict you might be confused how simple it is, all the optimizations happen at runtime, the code in Strict.Base just shows how the types are functionally working. At runtime everything is optimized. That is also why there are no other collections needed yet, no Stack, LinkedList, Queue, SortedDictionary, HashTable, whatever. You can just use List and Dictionary as is and at runtime Strict will figure out what internal structure makes most sense based on how it is used. This of course means if you mix and match different types like using a queue or stack, but also randomly adding or removing elements in the middle, it can't be as fast. The biggest impact on optimizations is if mutable types are used or not, by default nothing should be mutable in strict (and most mutables can be removed in the Executor so code stays functional and fast), but sometimes you might want to handcraft a mutable iteration like for image processing. If Strict can figure out that your input image stays unchanged and your output is just assigned back to the input, it can remove all mutable automatically and just swap two images (double buffering).

Image processing

has brightness Number with value is not 0
Process(mutable image ColorImage) ColorImage
	mutable testImage = ColorImage(Size(1, 1), (Color(0, 1, 2)))
	AdjustBrightness(0).Process(testImage) is ColorImage(Size(1, 1), (Color(0, 1, 2)))
	AdjustBrightness(5).Process(testImage) is ColorImage(Size(1, 1), (Color(5, 6, 7)))
	for image.Size
		image.Colors(index) = GetBrightnessAdjustedColor(image.Colors(index))
GetBrightnessAdjustedColor(current Color) Color
	AdjustBrightness(0).GetBrightnessAdjustedColor(Color(0, 1, 2)) is Color(0, 1, 2)
	AdjustBrightness(5).GetBrightnessAdjustedColor(Color(0, 0, 0)) is Color(5, 5, 5)
	AdjustBrightness(-5).GetBrightnessAdjustedColor(Color(0, 0, 0)) is Color(0, 0, 0)
	Color(current.Red + brightness, current.Green + brightness, current.Blue + brightness)

If the iterator is multidimensional like Size, it can be used in a for loop and multiple variables can be named (value here is a Vector2 with X and Y values):

has Width Number with value > 0
has Height Number with value > 0
has iterator
for Iterator(Vector2)
	for Height
		for Width
			Vector2(index, outer.index)
...

Notice that Size(0, 0) or Size(-1, 5) would not be valid as all dimensions must be positive numbers as defined above. The iterator is also returning a Vector2, which is just a list with 2 numbers.

For return value

You might already have noticed that for loops can be used as the return value for a method. In the Vector2.for implementation we just have 2 for loops and inside just the Vector2(x, y) is constructed. The method however returns an Iterator(Vector2) which is used in the image processing example to iterate through all possible pixel values of an image.

Here a few more examples as this is used in most useful for loops

in(key Generic) Boolean
	2 is in Dictionary((1, 1), (2, 2))
	3 is not in Dictionary((1, 1), (2, 2))
	for keysAndValues
		value.Key is key

Here the dictionary with keysAndValues is iterated (the actual runtime will use a hashtable implementation to find a key quickly instead of going manually through all elements). On each iteration the for body is executed, which here is just a comparision if the value.Key is the given key value. If that evaluation is true, the for loop is aborted and true is returned (we found the key). If the whole loop finishes and no key was found, the whole for expression returns false. In strict the last line in a method is always automatically the return expression. "return" is only needed when it happens in any line above it.

GetElementsText Text
	(1, 3).GetElementsText is "1, 3"
	for elements
		(index is 0 then "" else ", ") + value

This method goes through the elements and does a quick check if we are at the first element via "index is 0", if yes, then an empty Text string is used, else ", " is used. Then that value is concatenated via "+ value", where value is the current value from the for enumeration. The result of the for body expression is a Text (either just the value as Text for the first iteration or ", " + value otherwise). The same way for expression boolean value was used in the previous example, this Text value is now used to create a new list (since we didn't specify + at the beginning of the for expression, see below for more examples). So the return value of the for expression is a list of Text, which is not what we asked for, we wanted a Text, so Strict automatically concatenates the list into a single Text value, which is then returned. Btw: This implementation would be stripped out of the code and be replaced with the existing Text.Combine method!

Length Number
	(1, 2).Length is 2
	for elements
		1

The same concept can be used to count up some numbers, here the for expression results in a number 1 for each iteration. The for expression returns a list of numbers, all 1, which is then concatenated into the Length Number that we wanted to know.

Sum and Count work the same way, see if you can figure it out:

Sum Generic
	(1, 2, 3).Sum is 6
	for elements
		value
Count(searchFor Generic) Number
	(1, 2).Count(1) is 1
	(1, 3).Count(2) is 0
	("Hi", "Hello", "Hi").Count("Hi") is 2
	for elements
		if value is searchFor
			1

You can of course also use any other operator instead of the default creation of a list and concatenation. The above Sum method would return the same value if it were written as:

Sum Generic
	for elements
		+ value

However, Strict is VERY picky and strict (hence the name) on how to do things and would immediately replace it with the easier above version (as it needs less code).

Functional programming

But maybe you want to do some other operation in the for body, like multiplying:

Product Generic
	for elements
		* value

As you can see this is more like a pipe operator in functional languages (Lisp, Clojure, F#, Haskell, etc.). Let's say we want to do some filtering, mapping and reducing. Let's look at some different languages, all of them pretty much do the same thing, the syntax is just slightly different:

C#

int[] numList = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int result =
    numList
        .Where(n => n % 2 == 0)
        .Select(n => n * 10)
        .Sum();

Haskell

numList :: [Int]
numList = [0..9]
result :: Int
result =
    sum
      . map (* 10)
      . filter even
      $ numList

Clojure

(def num-list [0 1 2 3 4 5 6 7 8 9])
(def result
  (->> num-list
       (filter even?)
       (map #(* 10 %))
       (reduce + 0)))

F#

let numList = [| 0..9 |]
let result =
    numList
    |> Array.filter (fun n -> n % 2 = 0)
    |> Array.map (fun n -> n * 10)
    |> Array.sum

Javascript

const numList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = numList
    .filter(n => n % 2 === 0)
    .map(n => n * 10)
    .reduce((acc, n) => acc + n, 0);

Python

num_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
result = sum(n * 10 for n in num_list if n % 2 == 0)

The python example is a bit more compact, but you have to get used to the syntax as it does the mapping first and the filtering last, unlike all other examples.

Here is the same example in Strict (creates a list of numbers, which is then concatenated automatically for us using List.Sum or the identical output via + concatenation as in the example above):

for 10
  if index % 2
    index * 10

More examples

Let's look at a more complex example (not really useful, but is shows how we can append more things to for enumerations), think in similar terms to the functional examples above.

  for numbers
		to Text
		Length * Length
		if value > 3
			4 + value * 3
			to Text

Here we start iterating through a list of numbers, then we convert each value to Text (value is always used by default on any call, no matter if we are in a method were it defaults to our type instance or in a for loop, where it is the current iteration value).Then we use Length on the new value inside the for body, which is now a Text, so that would be the length of that Text and multiply it by itself. That length is a number again, the if expression now checks if that value is above 3. If so we go into the inner if body and do a quick calculation and finally we convert the resulting number to Text, which is then added to the for expression result. If the return value is a List of Text that would be returned, if it is a Text, then everything is concatenated. Otherwise, an error is given with the incompatible type. Also note that some of these things could be done in one line like (value to Text).Length * (value to Text).Length, but that would be longer and is not preferred. (4 + value * 3) to Text is probably slightly shorter and would be preferred, but the point here is you can call whatever code you like to transform any Iterator.

Here is the final example from Strict.Examples/RemoveParentheses.strict utilizing the selection if described in the if section. Note that we can have different things happening depending on what the selection if decides. Here the value character is only appended if we reach the else block and the parentheses value is still at 0, anything in parentheses will be skipped.

Remove Text
	RemoveParentheses("example(unwanted thing)example").Remove is "exampleexample"
	mutable parentheses = 0
	for text
		if value is
			"(" then parentheses.Increase
			")" then parentheses.Decrease
			else if parentheses is 0
				value

Blog

Details at https://strict-lang.org/blog

History

The idea to write my (Benjamin Nitschke, now CEO of Delta Engine) own programming language originally started in the 1990s after reading my favorite book "Gödel, Escher, Bach" by Douglass R. Hofstadter as a child. Due to lack of time and experience, it wasn't really much more than a bunch of small programs and proof of concept ideas. In 2008–2010 the first iteration was built, and while it worked, it lacked integration and was discarded in favor of Lua. This repository was started 2020-06-16 just for experimentation to create editor plugins and rethinking some of the low-level parsing, using the old Strict sdk as a guide. In mid 2025 I am back working on it alone after a few years of pause while I had some help in 2020–2022 from some employees, in the end the parent project was abandoned. There wasn't a need for creating neural networks and doing image processing with this. There were too many ready-to-use solutions to compete against, and the project ran out of funding and man power.

Now it is just a project to work a little on in the evenings and weekends if I find the time for it. I will continue to the original goal of creating a base layer language computers can actually understand and work on top of. I am thinking more in the way DNA and proteins are the base building blocks of life, but day to day we don't really think about them that much.

About

Strict Programming Language - Create well written and efficient code quickly. Strict is a simple to understand programming language that not only humans can read and understand, but also computer AI is able to read, modify and write.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages