# Sphinx syntax examples
# If you use Sublime Text you can read this with syntax highlighting!
# (highlighter for sublime text provided by Sphinx.sublime-syntax)
#{
Block Comment
#{ Nesting! }#
}#
# Your first Sphinx program!
echo "Hello, world!" # print() function will be available later once there is a builtin library
# Semicolons are optional. The syntax has been designed so that the end of a statement can always be inferred.
"One Stmt"; "Two Stmt"
# Like Lua, the syntax has been chosen to ensure that
# the end of a statement can always be inferred without
# needing to be whitespace-sensitive.
# The language will support type annotations and have structural type inference built in
# Any expression can be annotated, though outside of certain positions you may need parentheses
# This is still WIP
("Type Annotations" : String)
# Booleans
true # Not false.
false # Not *not* false.
# Numbers
1234 # An integer.
12.34 # A decimal number.
0xFACE # Hexadecimal integer.
# Strings
"I am a string"
"" # The empty string.
"123" # self is a string, not a number.
'And then she said, "woah."' # Can be single or double quoted.
"\ttab\nnewline, literal\\backslash" # Escape sequences
r"/[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,4}/" # Literal with escape sequences disabled
let two = 2
$"numbers: 1 {two} 3 {2*2}" # interpolated string
# Nil
nil
# Tuple
"abc", 123
# parens are only required for these special cases
() # the empty tuple
("solo",) # tuple with one element
# Objects
{ value = 0xA } # anonymous object
Person { value = 0xC } # classy object
get_person_type() { name = "Bob", age = 23 } # type can be an expression
{ name: String = "Bob", age: Int = 23 } # with annotations
# Arithmetic expressions
add + me
subtract - me
multiply * me
divide / me
modulo % me
# (x^2) + (y^2) # TODO exponentiation?
# Bitwise and shifts
(17 ^ 73) << 3
2412 & 0xff | ~0xcc00
# unary
-negate_me
~invert
# Comparison and equality
less < than
less_than <= or_equal
greater > than
greater_than >= or_equal
1 == 2 # false.
"cat" != "dog" # true.
314 == "pi" # false.
123 == "123" # false.
# booleans are not implicitly integers (but all expressions do have a "truth" value)
true == 1 # false
true == bool(1) # true
# Logical operators
not true # false.
not false # true.
# these will short circuit
true and false # false.
true and true # true.
false or false # false.
true or false # true.
# Precendence and grouping
average = (min + max) / 2
# Blocks
begin
echo "One statement."
echo "Two statements."
"Result value."
end
begin
if condition then
break "Early exit" # can break out of blocks early if you want
end
"Fallback value."
end
# Variable declaration and assignment
# Declarations are used to create new variables.
# They are written just like assignments except the
# variable name expression (lvalue) is preceeded by
# either "let" or "var":
let im_a_variable = "here is my value" # immutable variable
let i_am_nil = nil # initializer is required
var i_can_be_mutated = "initial_value"
i_can_be_mutated = "another_value"
let im_a_float: Float = 3.14159 # with type annotation
# destructure tuples
var first, second = "abc", "def"
second, first = first, second
# destructuring assignment with type annotations
let (index: Int, val: String) = 0, "example"
# assignment expressions are expressions, and so can be chained
var b = 0
var c = 1
let a = b += c = let d = 1 # chaining assignment (right associative)
let a = (b += (c = (let d = 1))) # previous expression is equivalent to this
# assignment versions of all binary operators are supported for assignment expressions only
inc = 0
inc -= 1 # arithmetic assignment
# variables can be freely redeclared (shadowed)
let breakfast = "bagels"
echo breakfast # "bagels".
var breakfast = breakfast
breakfast = "beignets"
echo breakfast # "beignets".
del a # drop a variable from the current scope
del collection[key] # mostly useful for removing values from objects
# assigment expressions bind weaker than operator expressions
3 * b += c # Error, assignment target is not an lvalue
3 * (b += c) # Ok
# Control Flow
# branching constructs are expressions
# looping constructs are statements
cond_expr =
if condition1 then
"first"
elif condition2 then
"second"
else
"default"
end
# If no branch is entered in an if-expression (which can happen if there is no "else")
# the whole expression evaluates to the last value that was tested. So for example:
# If-expressions evaluate to the last item of the branch that they enter
if (if a then true end) then
echo "if-ception"
end
# If-expressions evaluate to their condition if they don't enter a branch
if (if false then true end) then
assert false
end
# loops support both break and continue statements
var a = 1
while a < 10 do
if exitcond then
break
end
if skipcond then
continue
end
echo a
a += 1
end
# break and continue can use labels to target an outer loop
::outer while example_cond() do
some_work()
while inner_cond() do
if exit_inner() then
break
end
if continue_outer() then
continue ::outer
end
if exit_outer() then
break ::outer
end
end
end
do
var cond = example()
while cond
# for loops using iterators
# here, create_iter() returns an "iterator"
for item in create_iter() do
echo item
end
#{
An iterator is one of the following
A 3-tuple: (iterfunc, invariant, i0)
A callable that when invoked with no arguments creates such a 3-tuple
An object that has the __iter metamethod which returns such a 3-tuple
An iterator 3-tuple must do the following:
iterfunc must accept a (i, invariant) and return (i_next, ...) or nil
invariant is a value that is passed to each invocation of iterfunc
i0 is a value that is passed to iterfunc as the initial value of i
Before each execution of the for-loop body, iterfunc is invoked and the first value of the
returned tuple is saved to be used as the next value of i.
Then, the entire tuple is assigned to item, which may be a tuple-destructuring expression.
After the for-loop body is executed, the process is repeated until iterfunc returns nil.
}#
# Functions
make_breakfast(bacon, eggs, toast)
make_breakfast()
# implicitly returns nil
fun echo_sum(a, b)
echo a + b
end
# function parameters can be let or var, just like other variables (let can be omitted)
fun mutable_params(a, var b)
b = a
end
# note: unlike Python, default arguments are re-evaluated with each invocation
fun echo_default(thing = "default")
echo thing
end
# optional type annotations
fun return_sum(a: Float, b: Float = 0.0) -> Float:
return a + b
end
# Closures
fun add_pair(a, b)
return a + b
end
fun identity(a)
return a
end
echo identity(add_pair)(1, 2) # echos "3".
fun outer_function()
fun local_function()
echo "I'm local!"
end
local_function()
end
fun return_functions()
var outside = "outside"
fun print_value()
echo outside
end
fun hide_outside()
let outside = "local"
echo outside
end
fun set_outside()
outside = "inside"
end
return print_value, hide_outside, set_outside
end
let print, hide, set = return_functions()
print() # prints "outside"
hide() # prints "local"
print() # prints "outside"
set()
print() # prints "inside"
let anon_func = fun(x) x + 1 end
let a = anon_func(3)
# Decorator Syntax
# Using "@" syntax you can apply a function to any value being assigned in a declaration, essentially:
@decorator let name = value
# becomes:
let name = decorator(value)
# Since named function and class definitions are just syntactic sugar over a variable declaration,
# You can use this to wrap functions and classes like you might see in other languages like Python.
fun trace(wrapped)
return fun(args...)
echo "trace"
wrapped(args...)
end
end
@trace
fun increment(x)
return x + 1
end
# The decorator can be any expression that evaluates to a callable, as long as the callable takes a single argument:
@some["strange"].decorator(true)
fun example() end
# A contrived example to show that the mechanism is not specific to functions.
fun super_int(n) return 9001 * n end
@super_int
let super_seven = 7
echo super_seven # 63,007
# It could even work with regular assignment, although the usefulness of this is dubious
# I'm debating whether or not this should even be allowed.
@super_int
super_seven /= 1 # makes it un-super thanks to division
echo super_seven # 7
# Classes
something.field = 1
something.method()
class Breakfast
# declare class-level variables
var classvar = 0
# type annotate possible member variables
# unlike function parameters, mutability is the default for object members
var self.field # member named "field" is mutable
let self.name: String # member named "name" is immutable and has type "String"
# functions whose first parameter is the "self" symbol
# are automatically converted into builtin method descripter objects
fun cook(self)
echo "Eggs a-fryin'!"
end
fun mutates_self(var self, value)
self.field = value
end
# even when created using an anonymous function
var serve = fun(self, who)
echo "Enjoy your breakfast, " + who + "."
end
# use classmethods to define constructor functions
fun new(name)
return Breakfast { field = 1, name = "Eggs and Bacon" }
end
end
# Maybe. TBD
Breakfast.cook = fun(self)
echo "Eggs a-fryin'!"
end
# Store it in variables.
let someVariable = Breakfast
# Pass it to functions.
someFunction(Breakfast)
let breakfast = Breakfast {} # object initializer
echo breakfast # "Breakfast instance".
# Instantiation and initialization
# originally initializers used { name: value }, but I want to reserve colons for future type hinting
var breakfast = Breakfast { meat = "sausage", bread = "sourdough" }
breakfast.drink = "coffee"
del breakfast.bread # remove an object member
let a = 0
let anonymous = { some = a = 1, data = 2 }
class Breakfast
fun serve(self, who)
echo "Enjoy your " + self.meat + " and " +
self.bread + ", " + who + "."
end
# ...
end
class Breakfast
# constructor function
fun new(meat, bread)
return Breakfast { meat = meat, bread = bread }
end
end
var baconAndToast = Breakfast.new("bacon", "toast")
baconAndToast.serve("Dear Reader")
# "Enjoy your bacon and toast, Dear Reader."
# Inheritance
class Brunch: Breakfast
fun drink()
echo "How about a Bloody Mary?"
end
end
# Variadic arguments
fun variadic_fun(normal_arg, variadic...)
echo normal_arg
for variadic_arg in variadic do
echo variadic_arg
end
end
fun variadic_default(sequence... = ())
for item in sequence do
echo item
end
end
variadic_fun("red", "blue", "green") # prints "red" then "blue" then "green"
var mylist = list.new("red", "green", "blue") # important use case: constructor functions for collection types
# Argument unpacking
var things = ("second", "third")
variadic_fun("first", things...) # prints "first" then "second"
variadic_fun(things...) # prints "second" then "third"
# named arguments are not supported
# instead, use a function that takes an object as a single argument
# and use an anonymous object literal
lotta_options({ tag = "example", option1 = true, option2 = 3 })
# Metamethods
class Example
fun __setindex(self, index)
echo index
end
fun __call(self, args...)
echo args
end
var __eq = fun(self, other) true end
end
var ex = Example { [0] = "first", [1] = "second" }
ex[2] = "third"
# List of allowed metamethods:
#
# __add, __mul, __sub, __div, __neg, etc...
# __le, __lt, __eq only, no methamethods for others...
# __getindex, __setindex, __delindex
# __bool, __tostring
# __exit - for context managers
# __call
# class/instance names with double underscores are reserved for metamethods
class Example
var __notallowed = true # error
fun __alsonotallowed(self)
echo "bad"
end
end
Example.__fail = -3 # this will result in an error
# Multiple inheritance
class A
fun method(self)
echo "A.method() called"
end
end
class B
fun method(self)
echo "B.method() called"
end
end
class C: A, B end
class D: C, B end
var d = D {}
d.method() # prints "A.method() called"
# see also: C3 Linearization