sneq is a domain specific language used throughout QReserve to enable extended end user functionality through our resource manager.
sneq has the following features:
The ergonomics of sneq will require a little getting used to even for a seasoned python programmer, but once you get the hang of things you should be writing error-free scripts to handle your complex resource management requirements in no time!
return "Hello World"
sneq has the following basic types for data:
True
or False
+
), but you can not mutate a list by index i.e. foo[3] = 10
a_list[0]
+
), but you can not mutate a dict by index (foo['bar'] = 10
will not work)+
) create a new dictionary with the old value replacedfoo['bar'] = 10
will not work but:# The merge will approximate the update foo['bar'] = 10
foo = { 'bar': 5, 'val': 23 }
foo = foo + {'bar': 10}
# Now foo == { 'bar': 10, 'val': 23 }
a_dict['foo']
foo(bar="Hello World")
.form
and reservation
._
, e.g. _ = foo()
.None
datetime
library.All data types with the exception of functions are passed by value, not by reference. This includes lists and dictionaries!
Exceptions are not a type but rather a globally accessible way to "crash" your sneq program. Similar to panic
in other languages, raising an exception is the one exception to functional purity and gives all sneq functions an escape hatch.
In parts of the UI, the exception mechanism also provides a way for the user to interact with the user interface. For example, return Exception('Foo')
in a reservation or request script will set the conflict string displayed to the user to "Foo
".
They must be returned. They will break out of any nested function and cause the value in Exception
to be returned.
def foo():
return Exception('No one may reserve this.')
return foo()
==
), inequality (!=
)None
None
value with None
will always be False
None
value with None
will always be True
<
), greater than (>
), less than or equal to (<=
), greater than or equal to (>=
)dict
and function
typesin
and has
and
and
operators (True and True and False == False
)bool
or
or
operators (True or True or False == True
)bool
not
bool
+
)-
)[1, 2, None] - [None] == [1, 2]
*
)decimal
numbers will round downinteger
numbers will truncate down/
)decimal
numbers will round downinteger
numbers will truncate down**
)decimal
and integer
onlydecimal
numbers will round downinteger
numbers will truncate downif
and else
are used to provide conditional control flow.
a = None
if foo == 'bar':
a = 1
else:
a = 2
for
loops are used for iterating through a list or dictionary. In the case of list
s, for
will unpack into a single value. In the case of dict
s, for
will unpack into two values representing the key and value of every row in the dictionary.
# list iteration.
sum = 0
for a in [1,2,3]:
sum = sum + a
# sum's final value is 6.
# dict iteration.
sum = ""
for key, value in {"a": 1, "b": 2, "c": 3}:
sum = sum + key + str(value)
# sum's final value is 'a1b2c3'.
Lists can be comprehended with for
and in
, but, unlike python, conditions can not be used in list comprehension.
foo = [1, 2, 3]
bar = [a ** 2 for a in foo]
return bar == [1, 4, 9] # True
This should effectively be used as a map
function, since sneq does not include map
.
Dictionaries can be comprehended from lists with for
and in
, but, unlike python, conditions can not be used in list comprehension.
foo = { 'chunky': 'ice cream' }
bar = { k + ' monkey' : v + ' cone' for k, v in foo }
return bar == { 'chunky monkey': 'ice cream cone' } # True
When iterating over a list, the list index will be the first argument and the value will be the second argument.
foo_bar = ["foo", "bar"]
expect = {
'foo0': 'test0',
'bar1': 'test1',
}
return { car+str(idx): "test"+str(idx) for idx, car in foo_bar } == expect # True
Values can be cast to other types by using their types and parentheses, e.g. bool(1) == True
. If a type can not be cast as a different type, a DSLTypeError exception will be raised.
len
)The length of a list can be read as a decimal value with len
.
return len([1, 2, 3]) == 3 # True
def
)Functions are defined with def
. All user-declared functions must return a value and only use keyword arguments (foo='bar'
).
def foo(s=''):
return s + ' world!'
return foo('Hello') # 'Hello world!'
unique
)Find unique values in a list containing only int
, dec
, str
, bool
, none
, and datetime
types. For example, unique([1, 2, 3, 3, 3]) == [1, 2, 3]
.
isinstance
)Takes two arguments and returns True
if the first argument is the str
type of the second argument. For example, isinstance('foo', 'str') == True
. The first argument must be a value of some kind, while the second argument must be a string.
find
)find
takes two arguments, the first being a list and the second being a function. The function must contain the keyword key
and must return a bool
. The first value found in the list that causes the function to return True
will be returned.
def key_fn(key=''):
return key['name'] == 'Bob'
return find(
[{'name': 'John'}, {'name': 'Bob'}, {'name': 'Charles'}],
key_fn) # {'name': 'Bob'}
filter
)While simple filters can be performed with list subtraction, more complex filters can be implemented with the filter
function. filter
takes two arguments, the first being a list and the second being a function that returns a bool
and take the keyword argument key
. It returns a list containing all elements for which the function passed returned True
.
def key_fn(key=0):
return key > 5
return filter([1, 4, 7, 10], key_fn) # [7, 10]
reduce
)The reduce
built-in function takes the following arguments:
def reduce(
<list>, # List to reduce
<function>( # Reducing function
acc=<any, mandatory>, # Accumulator
val=<any, mandatory>, # Current value in iterator
idx=<dec, optional>, # Current index, 0=first value
idx_to_end=<dec, optional>, # Current index from end, 0=last value
) -> <any>,
<any>, # Default value
):
And returns the final value of the accumulator after executing over every value in the list.
def red_fn(acc=0, val=0):
return acc + val
return reduce([1,4,7,10], red_fn, 0) # 22
def red_fn(acc='', val='', idx=0, idx_to_end=0):
if idx_to_end == 0:
return acc + val['name'] + ' (' + str(idx+1) + ')'
return acc + val['name'] + ' (' + str(idx+1) + '), '
return reduce([{"name": "John"}, {"name": "Bob"}, {"name": "Charles"}],
red_fn, "") # 'John (1), Bob (2), Charles (3)'
sort
)Sorts a list. Takes one or two arguments, the first being a list
and the second being a function
or None
. The function should take the keyword argument key
. If None
is passed as the second argument, the function will sort by identity.
a = sort([4, 1, 7, 10, 5]) # b = [1, 4, 5, 7, 10]
b = sort([4, 1, 7, 10, 5], None) # a = [1, 4, 5, 7, 10]
def key_fn(key=''):
return key['name']
c = sort([
{'name': 'John'},
{'name': 'Bob'},
{'name': 'Charles'},
], key_fn) # c = [{'name': 'Bob'}, {'name': 'Charles'}, {'name': 'John'}]
list
(reverse
)Returns the reversed list of a list.
a = reverse([1, 2, 3]) # a = [3, 2, 1]