so this is powerscript, basically a wrapper around .NET that lets you write scripts without all the ceremony. its got a nice interactive shell (repl) and can also run script files. think of it like if powershell and python had a baby but retarded.
this thing compiles to .NET 8.0 and has a bunch of cool stuff:
- interactive shell where you can just type stuff and see what happens
- script execution from files
- direct access to .NET framework (super useful)
- functions that compile to actual lambda expressions
- a standard library with common stuff you need
- proper type system with both static and dynamic types
- custom syntax extensions via
.psxfiles - extend the language with your own patterns and operators - syntax block notation
![...]for inline pattern transformations
build it:
dotnet buildrun the shell:
.\src\PowerScript.CLI\bin\Debug\net8.0\PowerScript.CLI.exeor run a script file:
.\src\PowerScript.CLI\bin\Debug\net8.0\PowerScript.CLI.exe yourscript.pspowerscript has a bunch of different types depending on what you need:
FLEX - for when you want full dynamic typing, can change types on the fly
FLEX x = 10
PRINT x // prints: 10
FLEX x = "hello"
PRINT x // prints: HELLO
VAR - figures out the type from the initial value, then sticks with it
VAR count = 5
VAR message = "test"
count = 10 // ok
count = "oops" // nope, cant do that
INT - classic integers
INT age = 25
INT score = 100
STRING - text stuff
STRING name = "Alice"
STRING greeting = "Hello World"
NUMBER - flexible numeric type (can be int or decimal)
NUMBER value = 42
NUMBER price = 99.99
PREC - when you specifically need floating point
PREC pi = 3.14159
PREC temperature = 98.6
BOOL/BOOLEAN - for true/false values
BOOL isActive = TRUE
BOOLEAN hasPermission = FALSE
IF isActive {
PRINT "Active!"
}
sometimes you need to be specific about memory size:
INT[8] smallNumber = 255 // 8-bit int
INT[16] mediumNumber = 30000 // 16-bit int
INT[32] bigNumber = 2000000 // 32-bit int
NUMBER[64] hugeValue = 9999 // 64-bit number
powerscript supports lightweight object literals with optional type annotations:
basic objects - simple property bags
FLEX person = {name = "John", age = 30}
PRINT person // prints: {NAME = John, AGE = 30}
PRINT person.name // prints: JOHN
PRINT person.age // prints: 30
typed objects - add type information for clarity
FLEX employee = {name = "Alice", role = "Developer"} AS Employee
PRINT employee // prints: {NAME = Alice, ROLE = Developer} as EMPLOYEE
strict types - prevent adding new properties after creation
FLEX point = {x = 10, y = 20} AS Point!
PRINT point.x // prints: 10
// point.z = 30 // error: can't add properties to strict types
expressions in objects - property values can be expressions
FLEX data = {value = 5 + 10, text = "hello"}
PRINT data.value // prints: 15
empty objects - start with nothing, add properties later
FLEX empty = {}
PRINT empty // prints: {}
arrays work pretty much like youd expect:
VAR numbers = [1, 2, 3, 4, 5]
PRINT numbers
VAR first = numbers[0]
PRINT first // prints: 1
VAR names = ["Alice", "Bob", "Charlie"]
PRINT names[1] // prints: BOB
just use PRINT, it works:
PRINT "Hello, World!"
PRINT 42
PRINT myVariable
all the usual suspects work:
VAR sum = 5 + 3 // 8
VAR diff = 10 - 4 // 6
VAR product = 6 * 7 // 42
VAR quotient = 20 / 4 // 5
// use parantheses for precedence
VAR result = (2 + 3) * 4 // 20
x > 5
y < 10
age >= 18
count <= 100
value == 42
name != "test"
IF (x > 5 AND y < 10) {
PRINT "both conditions true"
}
IF (age < 18 OR hasPermission == 1) {
PRINT "at least one is true"
}
powerscript supports extending the language with custom syntax via .psx files:
syntax block notation - use ![...] for pattern transformations
// Define patterns in .psx file:
SYNTAX MAX OF $array:ARRAY => ARRAY_MAX($array)
SYNTAX MIN OF $array:ARRAY => ARRAY_MIN($array)
// Use in .ps file with ![...] blocks:
FLEX numbers = [5, 2, 8, 1, 3]
FLEX maxVal = ![MAX OF numbers] // transforms to ARRAY_MAX(numbers)
FLEX minVal = ![MIN OF numbers] // transforms to ARRAY_MIN(numbers)
PRINT maxVal // prints: 8
PRINT minVal // prints: 1
operator-based extensions - add method-like syntax with ::
// Define in .psx file:
SYNTAX $array::Sort() => ARRAY_SORT($array)
SYNTAX $str::ToUpper() => STR_UPPER($str)
// Use in .ps file:
LINK "arrays.psx"
FLEX numbers = [5, 2, 8, 1]
FLEX sorted = numbers::Sort() // transforms to ARRAY_SORT(numbers)
pattern-based extensions - add natural language patterns
// Define in .psx file:
SYNTAX FILTER $array WHERE $condition => ARRAY_FILTER($array, $condition)
SYNTAX TAKE $count FROM $array => ARRAY_TAKE($array, $count)
// Use in .ps file:
LINK "arrays.psx"
FLEX firstThree = TAKE 3 FROM myArray
FILTER results WHERE IsValid
chaining operations - custom syntax methods can be chained
FLEX result = data::Reverse()::First()
FLEX text = name::ToLower()::Trim()
// chaining with syntax blocks
FLEX result = ![MAX OF numbers]
FLEX chained = ![MIN OF ![TAKE 5 FROM data]]
see stdlib/syntax/ folder for .psx extension files (array_patterns_v2.psx, strings.psx, math_patterns.psx) and docs/custom-syntax-design.md for the full specification.
IF statements work as expected:
IF (age >= 18) {
PRINT "Adult"
} ELSE {
PRINT "Minor"
}
// nested ifs work too
IF (score >= 90) {
PRINT "A grade"
} ELSE {
IF (score >= 80) {
PRINT "B grade"
} ELSE {
PRINT "Keep trying"
}
}
powerscript uses CYCLE for loops, and its got a bunch of different flavours:
basic counting loop:
CYCLE 5 {
PRINT "hello"
}
loop with counter variable:
CYCLE 5 AS i {
PRINT i // prints: 0, 1, 2, 3, 4
}
conditional loop:
VAR x = 0
CYCLE WHILE (x < 5) {
PRINT x
x = x + 1
}
loop over arrays:
VAR numbers = [10, 20, 30]
CYCLE numbers AS num {
PRINT num
}
explicit collection iteration:
VAR items = ["apple", "banana", "orange"]
CYCLE ELEMENTS OF items AS item {
PRINT item
}
range loops:
// using array literal syntax
CYCLE [1, 10] AS i {
PRINT i // 1 to 10
}
// or explicit range syntax
CYCLE RANGE FROM 1 TO 10 AS i {
PRINT i
}
you can even nest them:
CYCLE 3 AS i {
CYCLE 3 AS j {
PRINT i
PRINT j
}
}
define functions with the FUNCTION keyword:
FUNCTION ADD(INT a, INT b)[INT] {
RETURN a + b
}
VAR result = ADD(5, 3)
PRINT result // prints: 8
functions can have different return types:
FUNCTION GET_NAME()[STRING] {
RETURN "Alice"
}
FUNCTION IS_VALID(INT x)[INT] {
IF (x > 0) {
RETURN 1
}
RETURN 0
}
you can call .NET methods directly using the NET. syntax or # shorthand with -> operator:
calling static methods:
NET.System.Console.WriteLine("Hello from .NET!")
#Console -> WriteLine("Hello from .NET!")
#String -> Concat("Hello", " World")
accessing properties and methods on variables:
STRING text = "hello world"
VAR length = #text -> Length
PRINT length // prints: 11
VAR upper = #text -> ToUpper()
PRINT upper // prints: HELLO WORLD
using .NET types:
VAR now = #DateTime -> Now
VAR formatted = #now -> ToString()
PRINT formatted
// or with full syntax
NET.System.DateTime.Now
linking .NET namespaces:
LINK System
LINK System.IO
// now you can use types from those namespaces
use LINK to import other powerscript files:
LINK "Core.ps"
LINK "String.ps"
LINK "Math.ps"
// now you can use functions from those files
VAR sum = ADD(5, 3)
VAR text = STR_UPPER("hello")
the stdlib has a bunch of useful functions already:
Math operations:
VAR sum = ADD(5, 3)
VAR diff = SUB(10, 4)
VAR product = MUL(6, 7)
VAR quotient = DIV(20, 4)
VAR remainder = MOD(10, 3)
VAR power = POW(2, 8)
String operations:
VAR len = STR_LENGTH("hello")
VAR upper = STR_UPPER("hello")
VAR lower = STR_LOWER("HELLO")
VAR reversed = STR_REVERSE("hello")
VAR trimmed = STR_TRIM(" hello ")
VAR contains = STR_CONTAINS("hello world", "world")
VAR concat = STR_CONCAT("hello", " ", "world")
I/O functions:
OUT("text") // output without newline
NEWLINE() // print newline
OUT_MULTI(val1, val2) // output multiple values
just use // for comments:
// this is a comment
VAR x = 10 // comments can go at the end of lines too
FUNCTION FIB(INT n)[INT] {
IF (n <= 1) {
RETURN n
}
VAR prev = FIB(SUB(n, 1))
VAR prevprev = FIB(SUB(n, 2))
RETURN ADD(prev, prevprev)
}
PRINT FIB(10) // prints: 55
FUNCTION BUBBLE_SORT(VAR arr)[VAR] {
VAR len = #arr -> Length
CYCLE RANGE FROM 0 TO SUB(len, 1) AS i {
CYCLE RANGE FROM 0 TO SUB(SUB(len, i), 2) AS j {
VAR current = arr[j]
VAR next = arr[ADD(j, 1)]
IF (current > next) {
arr[j] = next
arr[ADD(j, 1)] = current
}
}
}
RETURN arr
}
VAR numbers = [64, 34, 25, 12, 22, 11, 90]
numbers = BUBBLE_SORT(numbers)
FUNCTION IS_PRIME(INT n)[INT] {
IF (n <= 1) {
RETURN 0
}
IF (n == 2) {
RETURN 1
}
VAR i = 2
CYCLE WHILE (i * i <= n) {
IF (MOD(n, i) == 0) {
RETURN 0
}
i = ADD(i, 1)
}
RETURN 1
}
PRINT IS_PRIME(17) // prints: 1 (true)
PRINT IS_PRIME(20) // prints: 0 (false)
as of now, heres where we stand:
- 62 out of 62 tests passing (100%)
- Simple programs: 7/7 (100%)
- Moderate programs: 7/7 (100%)
- Complex programs: 6/6 (100%)
- Language features: 14/14 (100%)
- Standard library: 7/7 (100%)
- Turing completeness: 2/2 (100%)
- Custom syntax: 5/5 (100%)
- Array operator syntax
- String operator syntax
- Pattern-based syntax
- Chaining operations
- Mixed syntax forms
all tests passing! language features include proper scoping, variable shadowing in IF blocks, type declarations, custom syntax transformations with ![...] blocks, and all standard library functions working correctly.
tokenez/
├── src/
│ ├── PowerScript.CLI/ # command line interface
│ ├── PowerScript.Common/ # shared stuff
│ ├── PowerScript.Compiler/ # compiles tokens to AST
│ ├── PowerScript.Core/ # core types and tokens
│ ├── PowerScript.Interpreter/ # runs the code
│ ├── PowerScript.Parser/ # parses tokens
│ └── PowerScript.Runtime/ # executes the AST
├── stdlib/ # standard library functions
│ └── syntax/ # custom syntax extensions (.psx)
├── test-scripts/ # example scripts
│ ├── simple/ # basic examples
│ ├── moderate/ # medium complexity
│ ├── complex/ # advanced algorithms
│ ├── language/ # language feature tests
│ └── syntax/ # syntax feature tests (objects, etc.)
└── tests/ # unit tests
when you run the interactive shell, you get some built-in commands:
HELP - shows help message
EXIT - quits the shell (QUIT works too)
CLEAR - clears the screen (CLS works too)
HISTORY - shows command history
VERSION - shows version info
ABOUT - about powerscript
the interpreter works like this:
- lexical analysis turns source code into tokens
- tokens get organized into a tree structure
- processors walk the token tree and build an AST
- the runtime executes the AST
- functions get compiled to actual .NET lambda expressions for speed
its all built on reflection so you can call pretty much any .NET method you want.
- all string output gets converted to uppercase (its a feature not a bug)
- variable names are case-insensitive
- you need the # prefix before identifiers when calling .NET methods
- CYCLE is our loop keyword, not FOR or WHILE (though CYCLE WHILE exists)
- arrays are 0-indexed like civilized people expect
if you want to add stuff:
- add your feature to the appropriate processor
- write tests in the tests/ folder
- update this readme with examples
- make sure all tests still pass
do whatever you want with it, im not your boss.
- FOR loops arent implemented (use CYCLE instead)
- decimal number parsing needs some work (3.14 gets split weirdly)
- some nullable warnings in the code (they dont affect functionality)
this started as a learning project and turned into something actually useable. its not meant to replace C# or anything, but for quick scripts and .NET automation its pretty handy. the REPL is nice for experimenting and the .NET interop means you get the entire framework at your fingertips.
enjoy!