Skip to content

Instantly share code, notes, and snippets.

@EGreg
Last active September 1, 2025 16:41
Show Gist options
  • Select an option

  • Save EGreg/8f1e2310e89145b142899ba46d0cf2f7 to your computer and use it in GitHub Desktop.

Select an option

Save EGreg/8f1e2310e89145b142899ba46d0cf2f7 to your computer and use it in GitHub Desktop.
U Language By Example
U Programming Language By Example
Priorities:
* Simpler than C
* Safer than Rust
* More Obvious than Python
1. Comments
1.1 Single-line Comments
// like this
1.2 Block comments can be nested:
/* /* foo */ bar */
2. Keywords
2.1 All keywords are one letter.
a - async, await, actor
b - break, optionally return a value to loop
c - continue
d - define class
e - error, exception
f - function
r - return
t - this
w - while generator
y - yield
2.2 Built-in messages are one letter
.l() - length
.c() - copy on write (may copy later)
.n() - new
.x() - execute
2.3 Types are one letter
B - Bytes / Buffer / Blob (B16, B32 etc.)
D - Double / Decimal
F - Function
I - Integer (I32, I64, etc.)
L - Logical
N - None / Null / Nil
S - String
2.4 Compound Types (not on stack)
[I] - Array of Integers
[[I]] - Array of Array of Integers
{I} - Dictionary of Strings to Integers
2.5 Operators
:= Assignment Operator (: to prevent accidental mistakes, cannot be chained)
== Equality Test
=== Object Equality Test, can be used to test for === N
: The instanceof operator, can be used to test against classes and interfaces (see Classes)
!= Not Equal (also Logical XOR)
! & | Logical Operators (Note: work on Logical)
~! ~& ~| ~^ Bitwise Operators (Because they're far more rare)
+ - * / % ^ Arithmetic Operators (^ is exp)
+= -= *= ^= /= %= ^= Arithmetic Assignment, cannot be chained
~&= ~|= ~^= Bitwise Assignment cannot be chained
++ Increment (prefix only)
-- Decrement (prefix only)
? Binary operator, executes B if A is true, can be chained, evaluates left to right
?? Binary operator, tests if === N, can be chained, evaluates left to right
? : Ternary operator, can be chained, evaluates left to right
; evaluate expression on left, then on right, and return the one on right, chained
2.6 Operator Precedence
Precedence Operators Notes
1 ++ -- Prefix only
2 ^ Exponentiation
3 * / % Multiplicative
4 + - Additive
5 ~! ~& ~| ~^ Bitwise
6 ! & | != Logical
7 == === : Equality and type-checking
9 ? ?? ? : Conditional and ternary flow
10 := += -= etc. Assignment (rightmost, non-chainable)
11 => Function return value assignment
12 ; Sequential evaluation
3. Control Flow
3.1 Expressions
All statements are expressions, that can be composed to return a value
(statement)
3.2 Compound Expressions
Use parentheses and semicolons, executed in order, value is that of last expression
(foo = 4; bar = 5; whatever())
3.3 Blocks
Compound Expressions are also Blocks, and have block scoped variable definitions.
( foo = 4
bar = 5
whatever() )
3.4 Conditional Statements
There is only one construct replacing if, else, switch, case etc. No ambiguities:
expression ? (
foo = 4; bar = 5
) : ( baz = 6; whatever() )
3.5 Whitespace
New lines may be indented by Tab which means creates an implicit block.
Only one way to meaningfully indent: tabs, not spaces.
expression ?
foo = 4; bar = 5
: // newline+tab means start a block
baz = 6
whatever
// newline+old tab level means end the block
3. Definitions
Identifiers must be more than one letter, and start with Unicode alphabetic characters
This encourages developers to spend time entering meaningful names into their code
And also easily presents naming conflicts with any keywords. Encourage underscore_style.
3.1 None
This is the only keyword longer than 1 letter.
There is no "undefined" state for a variable.
Also, None with any operator above is still None, including comparison operators.
3.2 Variables
All variables have to have a type. They are scoped to the block they are defined in.
(
foo:I = 5, bar:S, // N by default
bar + foo // N + anything doesn't throw error
x:[I] = [0] * 20 * 40 // array of 800 elements
y:[[I]] = x / 10 // 10 arrays of 80 elements each
)
3.3 Modifiers
+M - Mutable / Modifiable
-M - Immutable, can only be assigned to Immutable references
+R - Remote / Retain / Release
-R - Local, can only be assigned to other Local references
+P - Pass by reference, even though result is not to be modified or retained
-N - Cannot hold the value N (variables are nullable by default)
3.4 Inline Functions
Types will be required if cannot be inferred from the context
f (x, y) => some_expression_using(x, y) // return values by assigning
3.5 Named Functions
f min(x:I, y:I = 0)
min => x > y ? y : x; // return value by assigning to function's name
3.6 Destructuring Arrays
f moo (x, y) = (x + y, x - y)
[a, b] = moo(2, 3);
3.7 Destructuring Dictionaries
f moo (x, y) = {a: x + y, b: x - y}
{a, b} = moo(2, 3);
3.8 Static Variables in Functions
f moo(x:I, y:S)
moo = ++moo.bar;
moo.bar = 5;
4. Functions
4.1 Definition Order
Functions defined later in the file can be referenced earlier
4.2 Returning values
Functions have a special operator that can be searched for in code,
which assigns values to the function's return value:
f example1(x):I
x ? r // optional, return
example1 => 5
f example2(x):I
r => 5
f example3(x):I => 5 // inline lambda
4.3 Throwing Errors
Errors are just functions that will be called on objects
f example1(x):I
x ? e => ErrorType.n(param1, param2)
4.4 Calling Functions
Instead of try/catch, you can pass an object to receive Error messages.
If the object doesn't handle this message, the stack keeps unwinding.
f(x, y, z) : ErrorHandlerObject
4.5 Modifiers
By default, parameters are not marked as +R (Remote) or +M (Mutable)
f side_effects(x:I+R, y:S+R+M, z) // remote and mutable
z = helper(z); // fine because helper x isn't +R
y = 8; // error because it can't be modified
a:I = 2
someGlobal = y // this is fine, points someGlobal to a proxy of y
someGlobal = a // error because a is local
someGlobal.setRemote(z) // error because z is local
someGlobal.setRemote(z.c()) // this is fine, copy-on-write
f helper(x) => x * 2;
4.6 Passing by Reference
If a parameter is marked +M then it can be modified by the function
If a parameter is marked as +R then it can be assigned anywhere.
Either one would result in passing by reference in every call to the function.
5. Loops
5.1 Iterables
Arrays and Dictionaries are iterable. Classes can also define iterators.
5.1 Generators
f fib(x) (
fib = last_two + last_1;
fib.last_one = fib;
fib.last_two = fib.last_one;
)
fib.last_one = 1; fib.last_two = 1;
5.2 For Loops
generator.x(inline function)
generator.x(function)
array.x(inline function)
dictionary.x(inline function)
5.3 While loops
These just use a generator that always returns true, until you
tell the loop to break and optionally return a value.
result = w
++a;
w = 4;
a < 10 ? c : b => 4; // result = 4 in the end
6. Classes
6.1 Methods and Static Methods
d MyClass : Superclass {
f foo(x:I+R) ()
f bar(x:I+R) ()
}
MyClass.staticFoo = f(x) = x*2;
6.2 Properties and Static Properties
By default, parameters are not marked as +R (Remote) or -M (Imutable)
d MyClass : Superclass {
moo:I-M; // const int, not Modifiable
remoteInt:I+R // remote, points to a proxy
remoteClass:SomeClass+R; // remote, refers to
internalStruct:SomeClass; // not remote, treated as inline struct
}
MyClass.staticMoo = 4;
7. Modifiers
Assigning with different modifiers requires an explicit copy with .c()
The only exception is assigning to a const (-M) if M is the only thing that changed.
@EGreg
Copy link
Author

EGreg commented Aug 13, 2025

Here is some example code:

  // U Demo Program: “Ledger & Fibers”
  // Showcases every language feature in a cohesive file.
  
  /* Notes:
  	- Tabs only for indentation
  	- All statements are expressions
  	- Nested block comments work: /* inside here */ still fine
  */
  
  /* 1) Globals and base values */
  
  versionMajor:I := 1
  versionMinor:I := 0
  applicationName:S := "Ledger & Fibers"
  
  noValue:N := N
  
  userIdList:[I] := [1, 2, 3, 5, 8, 13]
  weightList:[D] := [0.5, 1.5, 2.5]
  idByName:{I} := {"alice": 1, "bob": 2}
  matrixValues:[[I]] := [[1,2,3],[4,5,6],[7,8,9]]
  
  userCount:I := userIdList.l()
  matrixCopy:[[I]] := matrixValues.c()
  
  maybeCount:I := noValue ?? userCount ?? 0
  
  arithResult:I := (2 ^ 3 * 2 + 1 ; 7 * 3)
  
  bitMask:I := (~| 0 ~| 0)
  logicValue:L := !true | true & !false != false
  
  isLedgerType:L := N : Ledger
  
  
  /* 2) Modifiers */
  
  initialBudget:D-M := 100.00
  sharedRemotePool:D+R+M := 1000.00
  localCounter:I-R := 0
  mandatoryId:I-N := 1
  
  globalRemoteValue:D+R
  globalRemoteValue := sharedRemotePool
  
  
  /* 3) Functions */
  
  doubleValue := f(number) => number * 2
  
  f clampValue(input:D, minValue:D := 0.0, maxValue:D := 1.0):D
  	clampValue => input < minValue ? minValue : (input > maxValue ? maxValue : input)
  
  f addAndSubtract(first:I, second:I)
  	addAndSubtract => (first + second, first - second)
  
  sumResult:I; diffResult:I
  [sumResult, diffResult] := addAndSubtract(10, 3)
  
  f calculateStats(values:[I])
  	totalSum:I := 0
  	values.x(f(item) => totalSum += item)
  	calculateStats => {sum: totalSum, count: values.l()}
  
  {sum: totalUsers, count: totalCount} := calculateStats([2,3,5,7])
  
  f nextUniqueId()
  	nextUniqueId := ++nextUniqueId.counterValue
  nextUniqueId.counterValue := 100
  
  
  /* 4) Errors */
  
  d ErrorType {
  	f n(message:S, code:I) ()
  }
  
  f parseIntOrFail(stringValue:S):I
  	(stringValue == "bad") ? e => ErrorType.n("invalid int", 400)
  	parseIntOrFail => 42
  
  d ConsoleErrorHandler {
  	f ErrorType(message:S, code:I) ()
  }
  
  f tryParseInt(stringValue:S):I
  	handlerObject := ConsoleErrorHandler.n()
  	resultValue:I := parseIntOrFail(stringValue) : handlerObject
  	resultValue === N ? 0 : resultValue
  
  
  /* 5) Generators and .x */
  
  f fibonacciGenerator()
  	fibonacciGenerator := fibonacciGenerator.previousValue + fibonacciGenerator.beforePrevious
  	fibonacciGenerator.beforePrevious := fibonacciGenerator
  	fibonacciGenerator.previousValue := fibonacciGenerator.beforePrevious
  fibonacciGenerator.beforePrevious := 1; fibonacciGenerator.previousValue := 1
  
  f collectFibonacciBelow(limitValue:I):[I]
  	fibList:[I] := []
  	indexValue:I := 0
  	w
  		nextFib:I := fibonacciGenerator()
  		(nextFib >= limitValue) ? b => fibList
  		fibList := fibList + [nextFib]
  		c
  
  f dictionaryToPairs(dictValues:{I}):[[S]]
  	outputPairs:[[S]] := []
  	dictValues.x(f(pair)
  		isBig:L := pair.value > 10 ? true : false
  		outputPairs := outputPairs + [[pair.key, isBig ? "big" : "small"]]
  	)
  	dictionaryToPairs => outputPairs
  
  /* Yield example: create an infinite sequence of even numbers */
  f evenNumberGenerator()
  	counterValue:I := 0
  	w
  		y => counterValue
  		counterValue += 2
  
  
  /* 6) Classes */
  
  d Money {
  	amountValue:D-M
  	currencyCode:S-M
  	f n(amount:D, currency:S) ()
  	f addAmount(extra:D):Money ()
  		Money => Money.n(amountValue + extra, currencyCode)
  }
  Money.zeroUSD := Money.n(0.0, "USD")
  Money.addAll := f(moneyList:[Money]) => moneyList.l() == 0 ? Money.zeroUSD : (
  	totalAmount:D := 0.0;
  	moneyList.x(f(moneyItem) => totalAmount += moneyItem.amountValue);
  	Money.n(totalAmount, moneyList[0].currencyCode)
  )
  
  d Ledger {
  	titleText:S-M
  	totalAmount:D-M
  	remoteMirror:D+R
  	entryList:[[S]]
  	f n(title:S, startAmount:D) ()
  	f postEntry(entryDate:S, entryMemo:S+R) ()
  		entryList := entryList + [[entryDate, entryMemo]]
  		Ledger => title
  	f balance():Money ()
  		Ledger.balance => Money.n(totalAmount + remoteMirror, "USD")
  }
  Ledger.createUSD := f(title:S, startAmount:D):Ledger
  	(
  		ledgerInstance:Ledger := Ledger.n(title, startAmount);
  		ledgerInstance.remoteMirror := startAmount * 0.0;
  		ledgerInstance
  	)
  
  f isLedgerInstance(value):L
  	value : Ledger ? true : false
  
  
  /* 7) Remote rules */
  
  globalLedger:Ledger+R
  
  f setGlobalLedger(ledgerValue:Ledger+R)
  	globalLedger := ledgerValue
  
  f linkRemotePoolToLedger(ledgerValue:Ledger+R, poolValue:D+R)
  	ledgerValue.remoteMirror := poolValue
  	linkRemotePoolToLedger => ledgerValue
  
  f addEntryAndClampAmount(ledgerValue:Ledger+R, entryDate:S, entryMemo:S+R, deltaAmount:D):Money
  	clampedAmount:D := clampValue(deltaAmount, -1000.0, 1000.0)
  	resultMoney:Money := ledgerValue.postEntry(entryDate, entryMemo).balance()
  	(resultMoney ; Money.n(resultMoney.amountValue + clampedAmount, resultMoney.currencyCode))
  
  
  /* 8) Arrays & math */
  
  f sumMatrixRow(matrixData:[[I]], rowIndex:I):I
  	rowSum:I := 0
  	matrixData[rowIndex].x(f(value) => rowSum += value)
  	sumMatrixRow => rowSum
  
  f exponentMix(first:I, second:I):I
  	exponentMix => 2 ^ first * second + 1
  
  
  /* 9) While-generator */
  
  f sumValuesUnderLimit(values:[I], limit:I):I
  	accumulator:I := 0
  	indexValue:I := 0
  	w
  		indexValue >= values.l() ? b => accumulator
  		currentValue:I := values[indexValue]; ++indexValue
  		currentValue >= limit ? c : 0
  		accumulator += currentValue
  
  
  /* 10) Error in practice */
  
  f recordAmountFromString(stringValue:S, ledgerValue:Ledger+R):Money
  	parsedAmount:I := tryParseInt(stringValue)
  	addEntryAndClampAmount(ledgerValue, "2025-08-13", "parsed", parsedAmount)
  
  
  /* 11) Main */
  
  f main():I
  	operationsLedger:Ledger := Ledger.createUSD("Ops", 10.0)
  	setGlobalLedger(operationsLedger)
  	linkRemotePoolToLedger(operationsLedger, sharedRemotePool)
  
  	[plusValue, minusValue] := addAndSubtract(8, 5)
  	{sum: sumUsers, count: countUsers} := calculateStats(userIdList)
  
  	fibNumbers:[I] := collectFibonacciBelow(200)
  
  	dictPairs:[[S]] := dictionaryToPairs({"a": 1, "b": 22, "c": 5})
  
  	moneyOne:Money := Money.n(12.5, "USD")
  	moneyTwo:Money := moneyOne.addAmount(7.5)
  	moneyTotal:Money := Money.addAll([moneyOne, moneyTwo])
  
  	bookedOne:Money := recordAmountFromString("bad", operationsLedger)
  	bookedTwo:Money := recordAmountFromString("123", operationsLedger)
  
  	sumSmallValues:I := sumValuesUnderLimit([1,2,50,3,4], 10)
  
  	secondRowSum:I := sumMatrixRow(matrixValues, 1)
  	mixedValue:I := exponentMix(3, 2)
  
  	fibCount:I := fibNumbers.l() ?? 0
  
  	sumFibs:I := 0
  	fibNumbers.x(f(numValue) => sumFibs += numValue)
  
  	isLedgerInstance(operationsLedger) ?
  		sumFibs += 1
  	:
  		sumFibs += 0
  
  	// Use yield-based even number generator for first 5 evens
  	firstEvens:[I] := []
  	evensGen := evenNumberGenerator()
  	counterIndex:I := 0
  	w
  		counterIndex >= 5 ? b => firstEvens
  		firstEvens := firstEvens + [evensGen.y()]
  		counterIndex += 1
  
  	main => (fibCount + secondRowSum + mixedValue + sumFibs + sumUsers + countUsers)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment