# 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.
"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.
# 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
# Nil
nil
# Tuple
"abc", 123
# parens are only required for these special cases
() # the empty tuple
("solo",) # tuple with one element
# Objects
{ name = "Bob", age = 13 } # anonymous object
Person { name = "Bob", age = 13 }
get_person_type() { name = "Bob", age = 13 } # type can be an expression
{ name: String = "Bob", age: Int = 13 } # 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 - in fact, they are the only non-expression statements in Sphinx
if condition then
echo "yes"
else
echo "no"
end
if condition1 then
"first"
elif condition2 then
"second"
else
"default"
end
cond_expr =
if condition1 then
"first"
elif condition2 then
"second"
else
"default"
end
# if-expressions and assigment expressions
# This is not allowed, otherwise anytime an identifier is encountered
# it would be ambiguous whether you want to lookup the value of a variable
# or use it as an assignment target
if cond then a else b end += 1 # error, found if-expression, expected assignment target on LHS of assignment
# You have to do this
if cond then a += 1 else b += 1 end # works
# 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)
# Commenting this out because its kind of experimental
# # Decorator Syntax
# # The "@" syntax can be used to pass a single argument to a function without needing parens
# @f @g h(x) # equivalent to f(g(h(x)))
# # This is useful for wrapping functions (and classes, which we'll see later), e.g.
# fun trace(tag)
# return fun(func)
# echo "Calling " + tag + "!" # TODO string interpolation
# end
# end
# @trace("do_stuff")
# fun do_stuff()
# # do some stuff here
# end
# # There is a bit of subtlety required to make this work.
# # All function definitions are *expressions*, even named functions.
# # Defining a named function e.g. "fun name() ... end" is equivalent to the
# # assignment expression "(name = fun() ... end)"
# # However, whenver a
# 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